1
0
mirror of https://github.com/Lukas0025/YAGS.git synced 2025-04-09 00:52:11 +01:00

Updated UI

This commit is contained in:
Lukáš Plevač 2023-10-01 10:39:10 +02:00
parent 9bdfeb0379
commit d4750768c2
34 changed files with 1321 additions and 534 deletions

@ -5,6 +5,7 @@ services:
build: ./web
volumes:
- ./web:/var/www/html/
- ./uploads.ini:/usr/local/etc/php/conf.d/uploads.ini
ports:
- 8000:80

Binary file not shown.

Binary file not shown.

8
station/config.py Normal file

@ -0,0 +1,8 @@
masterUrl = "http://10.0.3.41:8000"
pullInterval = 10 # in sec
apiKey = "7b105947-65d6-40ba-bb4c-50b95a3ec1c8"
station = {
"lat": 49.2397383,
"lon": 16.5684175,
"alt": 0.277 #KM
}

36
station/main.py Normal file

@ -0,0 +1,36 @@
import config
import puller
import time
from datetime import datetime, timedelta
from recorder import recorder
def onRecorded(info):
pass
while True:
try:
puller.pull()
for job in puller.watingJobs:
print(f"Job {job['target']['name']} starts at {job['start']}")
if job["start"] <= datetime.utcnow() + timedelta(seconds=60):
if job["end"] <= datetime.utcnow():
puller.setFail(job["id"])
puller.watingJobs.remove(job)
# start record
puller.setRecording(job["id"])
curRecorder = recorder(job)
curRecorder.start()
puller.watingJobs.remove(job)
except:
print("[ERROR] main script fail restarting")
time.sleep(config.pullInterval)

70
station/puller.py Normal file

@ -0,0 +1,70 @@
import config
from datetime import datetime
from urllib.request import urlopen
import requests
import json
import os
import pathlib
watingJobs = []
def getNewJobs():
response = urlopen(config.masterUrl + "/api/observation/record?key=" + config.apiKey)
data_json = json.loads(response.read())
return data_json
def apiSend(url, data, files=None):
r = requests.post(url=config.masterUrl + url, data=data, files=files)
return r.text
def setFail(observation):
apiSend("/api/observation/fail", {"id": observation})
def setAssigned(observation):
apiSend("/api/observation/assigned", {"id": observation})
def setRecording(observation):
apiSend("/api/observation/recording", {"id": observation})
def setRecorded(observation):
apiSend("/api/observation/recorded", {"id": observation})
def setDecoding(observation):
apiSend("/api/observation/decoding", {"id": observation})
def setSuccess(observation):
apiSend("/api/observation/success", {"id": observation})
def setArtefacts(adir, observation):
ufiles = {} # open('file.txt','rb')
print("Uploading artefacts")
for path, subdirs, files in os.walk(adir):
for name in files:
afile = os.path.join(path, name)
fileName = str(afile).replace(str(adir), "").replace("/", "\\")
print(fileName)
ufiles[fileName] = open(afile, 'rb')
apiSend("/api/observation/addArtefacts", {"id": observation}, ufiles)
def parseNewJobs(jobs):
for job in jobs:
job["start"] = datetime.strptime(job["start"], '%Y-%m-%d %H:%M:%S')
job["end"] = datetime.strptime(job["end"], '%Y-%m-%d %H:%M:%S')
if job["start"] < datetime.utcnow():
setFail(job["id"])
continue
setAssigned(job["id"])
watingJobs.append(job)
def pull():
jobs = getNewJobs()
parseNewJobs(jobs)

88
station/recorder.py Normal file

@ -0,0 +1,88 @@
import os
import puller
import threading
from rotator import rotator
from simplecom import simplecom
from pathlib import Path
import config
import time
# A recursive function to remove the folder
def del_folder(path):
for sub in path.iterdir():
if sub.is_dir():
# Delete directory if it's a subdirectory
del_folder(sub)
else :
# Delete file if it is a file:
sub.unlink()
# This removes the top-level folder:
path.rmdir()
class recorder(threading.Thread):
def __init__(self, job):
threading.Thread.__init__(self)
self.job = job
def run(self):
print(f"Recorder for job {self.job['target']['name']} started")
recordTime = (self.job["end"] - self.job["start"]).total_seconds()
#init rotator
rotatorDriver = simplecom("/dev/ttyUSB0")
rotatorCTR = rotator(rotatorDriver, self.job, config.station)
rotatorCTR.start()
baseband = f"records/{self.job['id']}"
fs = max(self.job["receiver"]["params"]["fs"])
# find supported FS
for sample in self.job["receiver"]["params"]["fs"]:
if (sample > (int(self.job['transmitter']['bandwidth']) * 2)) and (sample < fs):
fs = sample
time.sleep(50)
os.system(f"satdump record {baseband} --source {self.job['receiver']['params']['radio']} --samplerate {fs} --frequency {self.job['transmitter']['centerFrequency']} --gain {self.job['receiver']['params']['gain']} --baseband_format s8 --timeout {recordTime}")
print(f"Recorder for job {self.job['target']['name']} stoped")
puller.setRecorded(self.job["id"])
rotatorCTR.kill()
if self.job["proccessPipe"] == []:
return
puller.setDecoding(self.job["id"])
pipe = " && ".join(self.job["proccessPipe"])
#create artecats dir
adir = f"artefacts/{self.job['id']}"
os.makedirs(adir)
#ok now replace
pipe = pipe.replace("{baseband}", str(baseband) + ".s8").replace("{fs}", str(fs)).replace("{artefactDir}", str(adir))
os.system(pipe)
puller.setSuccess(self.job["id"])
puller.setArtefacts(adir, self.job["id"])
# remove basband record
os.remove(str(baseband) + ".s8")
# remove artefacts
path = Path(adir)
try:
del_folder(path)
print("Directory removed successfully")
except OSError as o:
print(f"Error, {o.strerror}: {path}")

49
station/rotator.py Normal file

@ -0,0 +1,49 @@
import threading
from pyorbital.orbital import Orbital
from datetime import datetime, timedelta
import time
class rotator(threading.Thread):
def __init__(self, driver, job, station):
threading.Thread.__init__(self)
self.driver = driver
self.job = job
self.station = station
self.killed = False
def run(self):
print("[INFO] Starting rotator service")
self.driver.reset()
time.sleep(30)
#init pyorbytal
orb = Orbital(self.job["target"]["name"], line1=self.job["target"]["locator"]["tle"]["line1"], line2=self.job["target"]["locator"]["tle"]["line2"])
while (True):
az, el = orb.get_observer_look(
utc_time=datetime.utcnow() + timedelta(seconds=5),
lon=self.station["lon"],
lat=self.station["lat"],
alt=self.station["alt"]
)
az, el = round(az), round(el)
print(f"[INFO] rotator az: {az}, el: {el}")
self.driver.set_azel(az, el)
if (self.killed):
break
time.sleep(10)
# home the rotator on end
self.driver.reset()
time.sleep(60)
def kill(self):
self.killed = True

43
station/simplecom.py Normal file

@ -0,0 +1,43 @@
import serial
import time
class simplecom(object):
def __init__(self, port):
self.port = port
self.serial = serial.Serial(self.port, 9600, timeout=60)
def send(self, cmd):
try:
self.serial.write(cmd.encode("ASCII"))
self.serial.flush()
except:
print("[ERROR] fail to write to serial")
def reset(self):
self.send("RESET\n")
def set_azel(self, az, el):
self.set_az(az)
self.set_el(el)
def set_az(self, az):
while (az < 0):
az += 360
az = round(az % 360)
self.send(f"AZ{az}\n")
#readout target
self.send(f"TAR\n")
def set_el(self, el):
if (el < 0):
el = 0
elif (el > 90):
el = 90
el = round(el)
self.send(f"EL{el}\n")
#readout target
self.send(f"TAR\n")

2
uploads.ini Normal file

@ -0,0 +1,2 @@
upload_max_filesize = 10G
post_max_size = 10G

17
web/API/main.php Normal file

@ -0,0 +1,17 @@
<?php
$container = new \wsos\structs\container();
$api = new \wsos\api\functional\manager("API");
$templates = $container->get("templateLoader");
$context = $container->get("context");
$auth = $container->get("auth");
$router = $container->get("router");
// to show this page user must be logined
//$auth->requireLogin();
//register API functions
include_once(__DIR__ . "/observations.php");
//init API
$api->serve($router->getArgs());

145
web/API/observations.php Normal file

@ -0,0 +1,145 @@
<?php
namespace API\observation;
function plan($params) {
//first get trasmitter and receiver
$receiver = new \DAL\receiver(new \wsos\database\types\uuid($params["receiver"]));
$transmitter = new \DAL\transmitter(new \wsos\database\types\uuid($params["transmitter"]));
$receiver->fetch();
$transmitter->fetch();
$plan = new \DAL\observation();
$plan->status ->set("planed");
$plan->locator ->set($transmitter->target->get()->locator->get());
$plan->transmitter->set($transmitter);
$plan->receiver ->set($receiver);
$plan->start ->set($params["start"]);
$plan->end ->set($params["end"]);
if ($plan->start >= $plan->end) return ["status" => false];
$plan->commit();
return ["status" => true, "id" => $plan->id->get()];
}
function record($params) {
//get GS and set last seen
$station = new \DAL\station();
if (!$station->find("apiKey", $params["key"])) return ["status" => "bad api key"];
$station->lastSeen->now();
$station->commit();
//get all jobs for ground station
$table = new \wsos\database\core\table(\DAL\observation::class);
$dummyObservation = new \DAL\observation();
$dummyObservation->status->set("planed");
//$dummyObservation->station->set($params["station"]);
$planed = $table->query("(status == ?) && (receiver.station.id == ?)", [$dummyObservation->status->value, $station->id->get()]);
$jobs = new \wsos\structs\vector();
foreach ($planed->values as $plan) {
$jobs->append([
"id" => $plan->id->get(),
"target" => [
"id" => $plan->transmitter->get()->target->get()->id->get(),
"name" => $plan->transmitter->get()->target->get()->name->get(),
"locator" => $plan->locator->get()
],
"transmitter" => [
"centerFrequency" => $plan->transmitter->get()->centerFrequency->get(),
"bandwidth" => $plan->transmitter->get()->bandwidth->get()
],
"receiver" => [
"params" => $plan->receiver->get()->params->get()
],
"proccessPipe" => $plan->transmitter->get()->processPipe->get()->pipe->get(),
"start" => $plan->start->get(),
"end" => $plan->end->get()
]);
}
return $jobs->values;
}
function fail($params) {
$obs = new \DAL\observation();
$obs->id->set($params["id"]);
$obs->fetch();
$obs->status->set("fail");
$obs->commit();
}
function assigned($params) {
$obs = new \DAL\observation();
$obs->id->set($params["id"]);
$obs->fetch();
$obs->status->set("assigned");
$obs->commit();
}
function recording($params) {
$obs = new \DAL\observation();
$obs->id->set($params["id"]);
$obs->fetch();
$obs->status->set("recording");
$obs->commit();
}
function recorded($params) {
$obs = new \DAL\observation();
$obs->id->set($params["id"]);
$obs->fetch();
$obs->status->set("recorded");
$obs->commit();
}
function decoding($params) {
$obs = new \DAL\observation();
$obs->id->set($params["id"]);
$obs->fetch();
$obs->status->set("decoding");
$obs->commit();
}
function success($params) {
$obs = new \DAL\observation();
$obs->id->set($params["id"]);
$obs->fetch();
$obs->status->set("success");
$obs->commit();
}
function addArtefacts($params) {
$obs = new \DAL\observation();
$obs->id->set($params["id"]);
$obs->fetch();
$adir = __DIR__ . "/../ARTEFACTS/" . $params["id"];
mkdir($adir, 0777, true);
$artefacts = $obs->artefacts->get();
foreach ($_FILES as $file) {
move_uploaded_file($file["tmp_name"], $adir . "/" . $file["name"]);
$artefacts[] = "/ARTEFACTS/{$params['id']}/{$file['name']}";
}
$obs->artefacts->set($artefacts);
$obs->commit();
}

0
web/ARTEFACTS/.gitkeep Normal file

@ -8,26 +8,98 @@
// to show this page user must be logined
$auth->requireLogin();
$context["successCount"] = 75;
$context["planedCount"] = 15;
$context["lastPlaned"] = 2;
$context["failCount"] = 5;
$context["observationsCount"] = 5;
// create planed template observations
$planed = new \DAL\observation();
$planed->status->set("planed");
$ob = new \DAL\observation();
/**
* Get the directory size
* @param string $directory
* @return integer
*/
function foldersize($path, $extension = null) {
$total_size = 0;
$files = scandir($path);
$cleanPath = rtrim($path, '/'). '/';
foreach($files as $t) {
if ($t <> "." && $t <> "..") {
$currentFile = $cleanPath . $t;
if (is_dir($currentFile)) {
$size = foldersize($currentFile, $extension);
$total_size += $size;
} else {
if (is_null($extension) || (strtolower(pathinfo($currentFile)['extension']) == $extension)) {
$size = filesize($currentFile);
$total_size += $size;
}
}
}
}
return $total_size;
}
$observationsTable = new \wsos\database\core\table(\DAL\observation::class);
$planedTable = $observationsTable->query("status=?", [$planed->status->value]);
$maxSize = 8000; //8GB
$context["artefactsSpace"] = $maxSize;
/**
* For carts
*/
$context["successCount"] = $observationsTable->count("status==?", [$ob->status->getVal("success")]);
$context["planedCount"] = $observationsTable->count("status==?", [$ob->status->getVal("planed")]);
$context["lastPlaned"] = $observationsTable->count("", []);
$context["failCount"] = $observationsTable->count("status==?", [$ob->status->getVal("fail")]);
$context["observationsCount"] = $observationsTable->count("", []);
/**
* Get used size
*/
$context["usedSize"] = round(foldersize(__DIR__ . "/../artefacts/") / 1000000);
$context["imagesSize"] = round(foldersize(__DIR__ . "/../artefacts/", "png") / 1000000);
$context["basebandSize"] = round(foldersize(__DIR__ . "/../artefacts/", "s8") / 1000000);
$context["otherSize"] = $context["usedSize"] - $context["imagesSize"] + $context["basebandSize"];
$context["freeSize"] = $context["artefactsSpace"] - $context["usedSize"];
/**
* Get observations
*/
$observationTable = new \wsos\database\core\table(\DAL\observation::class);
$context["observations"] = $observationTable->query("", [], "DESC start", 10)->values;
/**
* Get stattions
*/
$context["stations"] = [];
$stations = (new \wsos\database\core\table(\DAL\station::class))->getAll()->values;
foreach ($stations as $station) {
$context["stations"][] = [
"name" => $station->name->get(),
"observations" => $observationTable->count("receiver.station.id == ?", [$station->id->get()]),
"lastSeen" => $station->lastSeen->strDelta()
];
}
/**
* Get planed observations
*/
$planedTable = $observationsTable->query("(status == ?) || (status == ?)", [$ob->status->getVal("assigned"), $ob->status->getVal("planed")]);
$observationsLocators = new \wsos\structs\vector();
foreach($planedTable->values as $obs) {
$observationsLocators->append($obs->locator->get());
$locator = $obs->locator->get();
$locator["start"] = $obs->start->get() . " UTC";
$locator["end"] = $obs->end->get() . " UTC";
$observationsLocators->append($locator);
}
$context["planedLocators"] = json_encode($observationsLocators->values);
$context["planedObservations"] = json_encode($observationsLocators->values);
/**
* Render view
*/
$templates->load("dashboard.html");
$templates->render($context);
$templates->show();

@ -0,0 +1,49 @@
<?php
$container = new \wsos\structs\container();
$templates = $container->get("templateLoader");
$router = $container->get("router");
$context = $container->get("context");
$auth = $container->get("auth");
// to show this page user must be logined
$auth->requireLogin();
//get observation ID
$obId = $router->getArgs()[0];
//$obId = new \DAL\observation();
//$obId->find("status", 4);
//$obId = $obId->id;
//get correct observation
$context["observation"] = new \DAL\observation(new \wsos\database\types\uuid($obId));
$context["observation"]->fetch();
//generate artefacts
$context["artefacts"] = new \wsos\structs\vector();
//get observations whats from same satellite and in 24h interval
$context["observations"] = new \wsos\database\core\table(\DAL\observation::class);
$context["observations"] = $context["observations"]->query(
"(transmitter.target.id == ?) && (start > ?) && (end < ?)",
[
$context["observation"]->transmitter->get()->target->get()->id->get(),
$context["observation"]->start->value - (60 * 60 * 12),
$context["observation"]->end->value + (60 * 60 * 12)
],
"DESC start"
)->values;
foreach ($context["observation"]->artefacts->get() as $art) {
$context["artefacts"]->append([
"name" => basename($art),
"url" => $art
]);
}
$context["artefacts"] = $context["artefacts"]->values;
$templates->load("observation.html");
$templates->render($context);
$templates->show();

@ -8,7 +8,9 @@
// to show this page user must be logined
$auth->requireLogin();
$context["observations"] = new \wsos\database\core\table(\DAL\observation::class);
$context["observations"] = (new \wsos\database\core\table(\DAL\observation::class))->query("", [], "DESC start")->values;
$context["receivers"] = new \wsos\database\core\table(\DAL\receiver::class);
$context["transmitters"] = new \wsos\database\core\table(\DAL\transmitter::class);
$templates->load("observations.html");
$templates->render($context);

@ -8,6 +8,8 @@
public \wsos\database\types\text $record; // path to record
public \wsos\database\types\json $artefacts; // JSON array of artefacts
public \wsos\database\types\json $locator; // TLE, GPS or URL locator if avaible
public \wsos\database\types\timestamp $start; // start datetime
public \wsos\database\types\timestamp $end; // end datetimr
function __construct(
$id = null,
@ -16,7 +18,9 @@
$status = "",
$record = "",
$artefacts = [],
$locator = ["tle" => null, "gps" => null, "url" => null]
$locator = ["tle" => null, "gps" => null, "url" => null],
$start = "2000-01-01 00:00:00",
$end = "2000-01-01 00:10:00"
) {
parent::__construct($id);
$this->transmitter = new \wsos\database\types\reference($transmitter, \DAL\transmitter::class);
@ -25,14 +29,18 @@
"fail",
"success",
"recording",
"recorded",
"decoding",
"planed",
"assigned",
"unknow"
], "unknow");
$this->record = new \wsos\database\types\text($record);
$this->artefacts = new \wsos\database\types\json($artefacts);
$this->locator = new \wsos\database\types\json($locator);
$this->start = new \wsos\database\types\timestamp($start);
$this->end = new \wsos\database\types\timestamp($end);
}
}
?>

@ -6,6 +6,7 @@
public \wsos\database\types\reference $antenna; // YAGI, DISH, ....
public \wsos\database\types\integer $centerFrequency; // in Hz
public \wsos\database\types\integer $bandwidth; // in Hz
public \wsos\database\types\json $params; // params for use
public \wsos\database\types\integer $gain; // gain of reciver setup
function __construct(
@ -14,6 +15,7 @@
$antenna = null,
$centerFrequency = 0,
$bandwidth = 0,
$params = [],
$gain = 0
) {
parent::__construct($id);
@ -22,6 +24,7 @@
$this->centerFrequency = new \wsos\database\types\integer($centerFrequency);
$this->bandwidth = new \wsos\database\types\integer($bandwidth);
$this->params = new \wsos\database\types\json($params);
$this->gain = new \wsos\database\types\integer($gain);
}
}

@ -2,15 +2,18 @@
namespace DAL;
class station extends \wsos\database\core\row {
public \wsos\database\types\text $name; // Satellite, ...
public \wsos\database\types\text $name; // Satellite, ...
public \wsos\database\types\uuid $apiKey; // access key
public \wsos\database\types\timestamp $lastSeen;
public \wsos\database\types\text $description;
public \wsos\database\types\json $locator;
function __construct($id = null, $name = "", $description = "", $locator = []) {
function __construct($id = null, $name = "", $apiKey = null, $lastSeen = 0, $description = "", $locator = []) {
parent::__construct($id);
$this->name = new \wsos\database\types\text($name);
$this->description = new \wsos\database\types\text($description);
$this->apiKey = new \wsos\database\types\uuid($apiKey);
$this->lastSeen = new \wsos\database\types\timestamp($lastSeen);
$this->locator = new \wsos\database\types\json($locator);
}
}

0
web/DB/.gitkeep Normal file

@ -11,6 +11,8 @@
{% IF item.status==recording USE bg-info %}
{% IF item.status==decoding USE bg-warning %}
{% IF item.status==planed USE bg-primary %}
{% IF item.status==assigned USE bg-secondary %}
{% IF item.status==recorded USE bg-light %}
me-1"></span> {% BIND item.status %}
</td>
<td>{% BIND item.start %}</td>

@ -0,0 +1,16 @@
<tr onclick="location.href = '/observation/{% BIND item.id %}'">
<td>{% BIND item.transmitter.modulation.name %}</td>
<td>
<span class="badge
{% IF item.status==fail USE bg-danger %}
{% IF item.status==success USE bg-success %}
{% IF item.status==recording USE bg-info %}
{% IF item.status==decoding USE bg-warning %}
{% IF item.status==planed USE bg-primary %}
{% IF item.status==assigned USE bg-secondary %}
{% IF item.status==recorded USE bg-light %}
me-1"></span> {% BIND item.status %}
</td>
<td>{% BIND item.start %}</td>
<td>{% BIND item.end %}</td>
</tr>

@ -0,0 +1,5 @@
<tr>
<td>{% BIND item.name %}</td>
<td>{% BIND item.observations %}</td>
<td>{% BIND item.lastSeen %} ago</td>
</tr>

@ -24,65 +24,32 @@
<div class="page-body">
<div class="container-xl">
<div class="row row-deck row-cards">
<div class="col-sm-6 col-lg-3">
<div class="col-md-4">
<div class="card">
<div class="card-body">
<div class="d-flex align-items-center">
<div class="subheader">Success</div>
<div class="ms-auto lh-1">
<span class="text-secondary">Last 7 days</span>
</div>
</div>
<div class="h1 mb-3">{% BIND successCount %}</div>
<div class="subheader">Success observations</div>
<div class="h3 m-0">{% BIND successCount %}</div>
</div>
</div>
</div>
<div class="col-sm-6 col-lg-3">
<div class="card">
<div class="card-body">
<div class="d-flex align-items-center">
<div class="subheader">Fail</div>
<div class="ms-auto lh-1">
<span class="text-secondary">Last 7 days</span>
</div>
</div>
<div class="h1 mb-3">{% BIND failCount %}</div>
</div>
<div class="col-md-4">
<div class="card">
<div class="card-body">
<div class="subheader">Fail observations</div>
<div class="h3 m-0">{% BIND failCount %}</div>
</div>
</div>
</div>
<div class="col-sm-6 col-lg-3">
<div class="card">
<div class="card-body">
<div class="d-flex align-items-center">
<div class="subheader">Planed</div>
<div class="ms-auto lh-1">
<span class="text-secondary">For {% BIND lastPlaned %} days</span>
</div>
</div>
<div class="h1 mb-3">{% BIND planedCount %}</div>
</div>
<div class="col-md-4">
<div class="card">
<div class="card-body">
<div class="subheader">All observations</div>
<div class="h3 m-0">{% BIND observationsCount %}</div>
</div>
</div>
<div class="col-sm-6 col-lg-3">
<div class="card">
<div class="card-body">
<div class="d-flex align-items-center">
<div class="subheader">all</div>
<div class="ms-auto lh-1">
<span class="text-secondary">Last 7 days</span>
</div>
</div>
<div class="h1 mb-3">{% BIND observationsCount %}</div>
</div>
</div>
</div>
<div class="col-12">
</div>
</div>
<div class="col-lg-6">
@ -90,32 +57,32 @@
<div class="col-12">
<div class="card">
<div class="card-body">
<p class="mb-3">Using Storage <strong>6854.45 MB </strong>of 8 GB</p>
<p class="mb-3">Using Storage <strong>{% BIND usedSize %} MB </strong>of {% BIND artefactsSpace %} MB</p>
<div class="progress progress-separated mb-3">
<div class="progress-bar bg-primary" role="progressbar" style="width: 44%" aria-label="Regular"></div>
<div class="progress-bar bg-info" role="progressbar" style="width: 19%" aria-label="System"></div>
<div class="progress-bar bg-success" role="progressbar" style="width: 9%" aria-label="Shared"></div>
<div class="progress-bar bg-primary" role="progressbar" style="width: {% MUL 100 DIV BIND imagesSize BIND artefactsSpace %}%" aria-label="Images"></div>
<div class="progress-bar bg-info" role="progressbar" style="width: {% MUL 100 DIV BIND basebandSize BIND artefactsSpace %}%" aria-label="Baseband"></div>
<div class="progress-bar bg-success" role="progressbar" style="width: {% MUL 100 DIV BIND otherSize BIND artefactsSpace %}%" aria-label="Other"></div>
</div>
<div class="row">
<div class="col-auto d-flex align-items-center pe-2">
<span class="legend me-2 bg-primary"></span>
<span>Regular</span>
<span class="d-none d-md-inline d-lg-none d-xxl-inline ms-2 text-secondary">915MB</span>
<span>Images</span>
<span class="d-none d-md-inline d-lg-none d-xxl-inline ms-2 text-secondary">{% BIND imagesSize %}MB</span>
</div>
<div class="col-auto d-flex align-items-center px-2">
<span class="legend me-2 bg-info"></span>
<span>System</span>
<span class="d-none d-md-inline d-lg-none d-xxl-inline ms-2 text-secondary">415MB</span>
<span>Baseband</span>
<span class="d-none d-md-inline d-lg-none d-xxl-inline ms-2 text-secondary">{% BIND basebandSize %}MB</span>
</div>
<div class="col-auto d-flex align-items-center px-2">
<span class="legend me-2 bg-success"></span>
<span>Shared</span>
<span class="d-none d-md-inline d-lg-none d-xxl-inline ms-2 text-secondary">201MB</span>
<span>Other</span>
<span class="d-none d-md-inline d-lg-none d-xxl-inline ms-2 text-secondary">{% BIND otherSize %}MB</span>
</div>
<div class="col-auto d-flex align-items-center ps-2">
<span class="legend me-2"></span>
<span>Free</span>
<span class="d-none d-md-inline d-lg-none d-xxl-inline ms-2 text-secondary">612MB</span>
<span class="d-none d-md-inline d-lg-none d-xxl-inline ms-2 text-secondary">{% BIND freeSize %}MB</span>
</div>
</div>
</div>
@ -127,50 +94,35 @@
<div class="col-lg-6">
<div class="card">
<div class="card-header border-0">
<div class="card-title">Planed targets map</div>
<div class="card-title">Assigned targets map</div>
</div>
<div class="position-relative">
<div id="map" style="height:400px;" class="position-absolute top-0 left-0 px-3 mt-1 w-100">
</div>
<script src="/static/js/map.js"></script>
<script>
var map = L.map('map').setView([0, 0], 1);
var tragetIcon = L.icon({
iconUrl: '/static/icons/satellite.svg',
iconSize: [20, 20],
shadowSize: [0, 0],
iconAnchor: [10, 10],
shadowAnchor: [10, 0],
popupAnchor: [0, 0]
});
var map = L.map('map', {
worldCopyJump: false
}).setView([0, 0], 1);
L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png', {
attribution: '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
}).addTo(map);
L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png', {
attribution: '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
}).addTo(map);
var planedObs = JSON.parse('{% BIND planedObservations %}');
var planedLocators = JSON.parse('{% BIND planedLocators %}'.replaceAll("\n", "\\\\n"));
var targetsMarkers = [];
for (var i = 0; i < planedLocators.length; i++) {
targetsMarkers.push(L.marker(map.getCenter(), {icon: tragetIcon}).bindPopup(planedLocators[i]["tle"].split("\\n")[0]).addTo(map));
}
setInterval(function () {
for (var i = 0; i < planedLocators.length; i++) {
var planedLocator = planedLocators[i]["tle"].split("\\n");
var satrec = satellite.twoline2satrec(planedLocator[1], planedLocator[2]);
var positionAndVelocity = satellite.propagate(satrec, new Date());
var gmst = satellite.gstime(new Date());
var positionEci = positionAndVelocity.position;
var positionGd = satellite.eciToGeodetic(positionEci, gmst);
targetsMarkers[i].setLatLng([satellite.degreesLat(positionGd.latitude), satellite.degreesLong(positionGd.longitude)]);
}
}, 1000);
for (var i = 0; i < planedObs.length; i++) {
mapAddTLEPath(
map,
planedObs[i]["tle"]["line1"],
planedObs[i]["tle"]["line2"],
planedObs[i]["start"],
planedObs[i]["end"]
);
}
</script>
</div>
</div>
@ -178,409 +130,52 @@
<div class="col-md-6 col-lg-4">
<div class="col-md-6 col-lg-6">
<div class="card">
<div class="card-header">
<h3 class="card-title">Social Media Traffic</h3>
<h3 class="card-title">Stations</h3>
</div>
<div class="scrollable" style="height: 274px;">
<table class="table card-table table-vcenter">
<thead>
<tr>
<th>Name</th>
<th>Observations</th>
<th>Last seen</th>
</tr>
</thead>
<tbody>
{% FOREACH stations RENDER blocks/station-item.html %}
</tbody>
</table>
</div>
<table class="table card-table table-vcenter">
<thead>
<tr>
<th>Network</th>
<th colspan="2">Visitors</th>
</tr>
</thead>
<tbody>
<tr>
<td>Instagram</td>
<td>3,550</td>
<td class="w-50">
<div class="progress progress-xs">
<div class="progress-bar bg-primary" style="width: 71.0%"></div>
</div>
</td>
</tr>
<tr>
<td>Twitter</td>
<td>1,798</td>
<td class="w-50">
<div class="progress progress-xs">
<div class="progress-bar bg-primary" style="width: 35.96%"></div>
</div>
</td>
</tr>
<tr>
<td>Facebook</td>
<td>1,245</td>
<td class="w-50">
<div class="progress progress-xs">
<div class="progress-bar bg-primary" style="width: 24.9%"></div>
</div>
</td>
</tr>
<tr>
<td>TikTok</td>
<td>986</td>
<td class="w-50">
<div class="progress progress-xs">
<div class="progress-bar bg-primary" style="width: 19.72%"></div>
</div>
</td>
</tr>
<tr>
<td>Pinterest</td>
<td>854</td>
<td class="w-50">
<div class="progress progress-xs">
<div class="progress-bar bg-primary" style="width: 17.08%"></div>
</div>
</td>
</tr>
<tr>
<td>VK</td>
<td>650</td>
<td class="w-50">
<div class="progress progress-xs">
<div class="progress-bar bg-primary" style="width: 13.0%"></div>
</div>
</td>
</tr>
<tr>
<td>Pinterest</td>
<td>420</td>
<td class="w-50">
<div class="progress progress-xs">
<div class="progress-bar bg-primary" style="width: 8.4%"></div>
</div>
</td>
</tr>
</tbody>
</table>
</div>
</div>
<div class="col-12">
<div class="card">
<div class="card-header">
<h3 class="card-title">Invoices</h3>
<h3 class="card-title">Last observations</h3>
</div>
<div class="card-body border-bottom py-3">
<div class="d-flex">
<div class="text-secondary">
Show
<div class="mx-2 d-inline-block">
<input type="text" class="form-control form-control-sm" value="8" size="3" aria-label="Invoices count">
</div>
entries
</div>
<div class="ms-auto text-secondary">
Search:
<div class="ms-2 d-inline-block">
<input type="text" class="form-control form-control-sm" aria-label="Search invoice">
</div>
</div>
</div>
</div>
<div class="table-responsive">
<table class="table card-table table-vcenter text-nowrap datatable">
<div class="table-body">
<table class="table card-table table-vcenter text-nowrap datatable table-hover">
<thead>
<tr>
<th class="w-1"><input class="form-check-input m-0 align-middle" type="checkbox" aria-label="Select all invoices"></th>
<th class="w-1">No. <!-- Download SVG icon from http://tabler-icons.io/i/chevron-up -->
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-sm icon-thick" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M6 15l6 -6l6 6"></path></svg>
</th>
<th>Invoice Subject</th>
<th>Client</th>
<th>VAT No.</th>
<th>Created</th>
<th>Station</th>
<th>Target</th>
<th>Modulation</th>
<th>Type</th>
<th>Frequency</th>
<th>Status</th>
<th>Price</th>
<th></th>
<th>Start</th>
<th>End</th>
</tr>
</thead>
<tbody>
<tr>
<td><input class="form-check-input m-0 align-middle" type="checkbox" aria-label="Select invoice"></td>
<td><span class="text-secondary">001401</span></td>
<td><a href="invoice.html" class="text-reset" tabindex="-1">Design Works</a></td>
<td>
<span class="flag flag-xs flag-country-us me-2"></span>
Carlson Limited
</td>
<td>
87956621
</td>
<td>
15 Dec 2017
</td>
<td>
<span class="badge bg-success me-1"></span> Paid
</td>
<td>$887</td>
<td class="text-end">
<span class="dropdown">
<button class="btn dropdown-toggle align-text-top" data-bs-boundary="viewport" data-bs-toggle="dropdown">Actions</button>
<div class="dropdown-menu dropdown-menu-end">
<a class="dropdown-item" href="#">
Action
</a>
<a class="dropdown-item" href="#">
Another action
</a>
</div>
</span>
</td>
</tr>
<tr>
<td><input class="form-check-input m-0 align-middle" type="checkbox" aria-label="Select invoice"></td>
<td><span class="text-secondary">001402</span></td>
<td><a href="invoice.html" class="text-reset" tabindex="-1">UX Wireframes</a></td>
<td>
<span class="flag flag-xs flag-country-gb me-2"></span>
Adobe
</td>
<td>
87956421
</td>
<td>
12 Apr 2017
</td>
<td>
<span class="badge bg-warning me-1"></span> Pending
</td>
<td>$1200</td>
<td class="text-end">
<span class="dropdown">
<button class="btn dropdown-toggle align-text-top" data-bs-boundary="viewport" data-bs-toggle="dropdown">Actions</button>
<div class="dropdown-menu dropdown-menu-end">
<a class="dropdown-item" href="#">
Action
</a>
<a class="dropdown-item" href="#">
Another action
</a>
</div>
</span>
</td>
</tr>
<tr>
<td><input class="form-check-input m-0 align-middle" type="checkbox" aria-label="Select invoice"></td>
<td><span class="text-secondary">001403</span></td>
<td><a href="invoice.html" class="text-reset" tabindex="-1">New Dashboard</a></td>
<td>
<span class="flag flag-xs flag-country-de me-2"></span>
Bluewolf
</td>
<td>
87952621
</td>
<td>
23 Oct 2017
</td>
<td>
<span class="badge bg-warning me-1"></span> Pending
</td>
<td>$534</td>
<td class="text-end">
<span class="dropdown">
<button class="btn dropdown-toggle align-text-top" data-bs-boundary="viewport" data-bs-toggle="dropdown">Actions</button>
<div class="dropdown-menu dropdown-menu-end">
<a class="dropdown-item" href="#">
Action
</a>
<a class="dropdown-item" href="#">
Another action
</a>
</div>
</span>
</td>
</tr>
<tr>
<td><input class="form-check-input m-0 align-middle" type="checkbox" aria-label="Select invoice"></td>
<td><span class="text-secondary">001404</span></td>
<td><a href="invoice.html" class="text-reset" tabindex="-1">Landing Page</a></td>
<td>
<span class="flag flag-xs flag-country-br me-2"></span>
Salesforce
</td>
<td>
87953421
</td>
<td>
2 Sep 2017
</td>
<td>
<span class="badge bg-secondary me-1"></span> Due in 2 Weeks
</td>
<td>$1500</td>
<td class="text-end">
<span class="dropdown">
<button class="btn dropdown-toggle align-text-top" data-bs-boundary="viewport" data-bs-toggle="dropdown">Actions</button>
<div class="dropdown-menu dropdown-menu-end">
<a class="dropdown-item" href="#">
Action
</a>
<a class="dropdown-item" href="#">
Another action
</a>
</div>
</span>
</td>
</tr>
<tr>
<td><input class="form-check-input m-0 align-middle" type="checkbox" aria-label="Select invoice"></td>
<td><span class="text-secondary">001405</span></td>
<td><a href="invoice.html" class="text-reset" tabindex="-1">Marketing Templates</a></td>
<td>
<span class="flag flag-xs flag-country-pl me-2"></span>
Printic
</td>
<td>
87956621
</td>
<td>
29 Jan 2018
</td>
<td>
<span class="badge bg-danger me-1"></span> Paid Today
</td>
<td>$648</td>
<td class="text-end">
<span class="dropdown">
<button class="btn dropdown-toggle align-text-top" data-bs-boundary="viewport" data-bs-toggle="dropdown">Actions</button>
<div class="dropdown-menu dropdown-menu-end">
<a class="dropdown-item" href="#">
Action
</a>
<a class="dropdown-item" href="#">
Another action
</a>
</div>
</span>
</td>
</tr>
<tr>
<td><input class="form-check-input m-0 align-middle" type="checkbox" aria-label="Select invoice"></td>
<td><span class="text-secondary">001406</span></td>
<td><a href="invoice.html" class="text-reset" tabindex="-1">Sales Presentation</a></td>
<td>
<span class="flag flag-xs flag-country-br me-2"></span>
Tabdaq
</td>
<td>
87956621
</td>
<td>
4 Feb 2018
</td>
<td>
<span class="badge bg-secondary me-1"></span> Due in 3 Weeks
</td>
<td>$300</td>
<td class="text-end">
<span class="dropdown">
<button class="btn dropdown-toggle align-text-top" data-bs-boundary="viewport" data-bs-toggle="dropdown">Actions</button>
<div class="dropdown-menu dropdown-menu-end">
<a class="dropdown-item" href="#">
Action
</a>
<a class="dropdown-item" href="#">
Another action
</a>
</div>
</span>
</td>
</tr>
<tr>
<td><input class="form-check-input m-0 align-middle" type="checkbox" aria-label="Select invoice"></td>
<td><span class="text-secondary">001407</span></td>
<td><a href="invoice.html" class="text-reset" tabindex="-1">Logo &amp; Print</a></td>
<td>
<span class="flag flag-xs flag-country-us me-2"></span>
Apple
</td>
<td>
87956621
</td>
<td>
22 Mar 2018
</td>
<td>
<span class="badge bg-success me-1"></span> Paid Today
</td>
<td>$2500</td>
<td class="text-end">
<span class="dropdown">
<button class="btn dropdown-toggle align-text-top" data-bs-boundary="viewport" data-bs-toggle="dropdown">Actions</button>
<div class="dropdown-menu dropdown-menu-end">
<a class="dropdown-item" href="#">
Action
</a>
<a class="dropdown-item" href="#">
Another action
</a>
</div>
</span>
</td>
</tr>
<tr>
<td><input class="form-check-input m-0 align-middle" type="checkbox" aria-label="Select invoice"></td>
<td><span class="text-secondary">001408</span></td>
<td><a href="invoice.html" class="text-reset" tabindex="-1">Icons</a></td>
<td>
<span class="flag flag-xs flag-country-pl me-2"></span>
Tookapic
</td>
<td>
87956621
</td>
<td>
13 May 2018
</td>
<td>
<span class="badge bg-success me-1"></span> Paid Today
</td>
<td>$940</td>
<td class="text-end">
<span class="dropdown">
<button class="btn dropdown-toggle align-text-top" data-bs-boundary="viewport" data-bs-toggle="dropdown">Actions</button>
<div class="dropdown-menu dropdown-menu-end">
<a class="dropdown-item" href="#">
Action
</a>
<a class="dropdown-item" href="#">
Another action
</a>
</div>
</span>
</td>
</tr>
{% FOREACH observations RENDER blocks/observation-item.html %}
</tbody>
</table>
</div>
<div class="card-footer d-flex align-items-center">
<p class="m-0 text-secondary">Showing <span>1</span> to <span>8</span> of <span>16</span> entries</p>
<ul class="pagination m-0 ms-auto">
<li class="page-item disabled">
<a class="page-link" href="#" tabindex="-1" aria-disabled="true">
<!-- Download SVG icon from http://tabler-icons.io/i/chevron-left -->
<svg xmlns="http://www.w3.org/2000/svg" class="icon" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M15 6l-6 6l6 6"></path></svg>
prev
</a>
</li>
<li class="page-item"><a class="page-link" href="#">1</a></li>
<li class="page-item active"><a class="page-link" href="#">2</a></li>
<li class="page-item"><a class="page-link" href="#">3</a></li>
<li class="page-item"><a class="page-link" href="#">4</a></li>
<li class="page-item"><a class="page-link" href="#">5</a></li>
<li class="page-item">
<a class="page-link" href="#">
next <!-- Download SVG icon from http://tabler-icons.io/i/chevron-right -->
<svg xmlns="http://www.w3.org/2000/svg" class="icon" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M9 6l6 6l-6 6"></path></svg>
</a>
</li>
</ul>
</div>
</div>
</div>
</div>

@ -3,13 +3,13 @@
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover"/>
<meta http-equiv="X-UA-Compatible" content="ie=edge"/>
<title>{% BIND pagename %} - RadioMaster</title>
<title>{% BIND pagename %} - {% BIND appName %}</title>
<!-- CSS files tabler -->
<link href="./dist/css/tabler.min.css?1668287865" rel="stylesheet"/>
<link href="./dist/css/tabler-flags.min.css?1668287865" rel="stylesheet"/>
<link href="./dist/css/tabler-payments.min.css?1668287865" rel="stylesheet"/>
<link href="./dist/css/tabler-vendors.min.css?1668287865" rel="stylesheet"/>
<link href="/dist/css/tabler.min.css?1668287865" rel="stylesheet"/>
<link href="/dist/css/tabler-flags.min.css?1668287865" rel="stylesheet"/>
<link href="/dist/css/tabler-payments.min.css?1668287865" rel="stylesheet"/>
<link href="/dist/css/tabler-vendors.min.css?1668287865" rel="stylesheet"/>
<style>
@import url('https://rsms.me/inter/inter.css');

@ -1,8 +1,8 @@
<header class="navbar navbar-expand-md navbar-light d-print-none">
<div class="container-xl">
<h1 class="navbar-brand navbar-brand-autodark d-none-navbar-horizontal pe-0 pe-md-3">
<a href=".">
<img src="{% BIND logo %}" width="110" height="32" alt="Tabler" class="navbar-brand-image">
<a href="/">
<img src="/static/icons/antenna.svg" width="110" height="32" alt="Tabler" class="navbar-brand-image"> {% BIND appName %}
</a>
</h1>
<div class="navbar-nav flex-row order-md-last">

@ -6,7 +6,7 @@
<div class="container container-tight py-4">
<div class="text-center mb-4">
<a href="." class="navbar-brand navbar-brand-autodark">
<img src="./static/logo.svg" width="110" height="32" alt="Tabler" class="navbar-brand-image">
<img src="/static/icons/antenna.svg" width="110" height="32" alt="{% BIND appName %}" class="navbar-brand-image">
</a>
</div>
<div class="card card-md">

258
web/VIEWS/observation.html Normal file

@ -0,0 +1,258 @@
<!doctype html>
<html lang="en">
{% INCLUDE layout/head.html %}
<body>
<div class="page">
{% BINDINCLUDE layout/header.html logined %}
<div class="page-header d-print-none mt-4">
<div class="container-xl">
<div class="row g-2 align-items-center">
<div class="col">
<div class="page-pretitle">
{% BIND observation.id %}
</div>
<h2 class="page-title">
Observation of {% BIND observation.transmitter.target.name %}
</h2>
</div>
</div>
</div>
</div>
<div class="page-body">
<div class="container-xl">
<div class="row row-deck row-cards">
<div class="col-12">
<div class="row row-cards">
<div class="col-12">
<div class="card">
<div class="card-header">
<h3 class="card-title">Base info</h3>
</div>
<div class="card-body">
<div class="datagrid">
<div class="datagrid-item">
<div class="datagrid-title">Target</div>
<div class="datagrid-content"><a href="/target/{% BIND observation.transmitter.target.id %}">
{% BIND observation.transmitter.target.name %}
</a></div>
</div>
<div class="datagrid-item">
<div class="datagrid-title">Station</div>
<div class="datagrid-content"><a href="/station/{% BIND observation.receiver.station.id %}">
{% BIND observation.receiver.station.name %}
</a></div>
</div>
<div class="datagrid-item">
<div class="datagrid-title">MODULATION</div>
<div class="datagrid-content"><a
href="/modulation/{% BIND observation.transmitter.modulation.id %}">
{% BIND observation.transmitter.modulation.name %}
</a></div>
</div>
<div class="datagrid-item">
<div class="datagrid-title">TYPE</div>
<div class="datagrid-content"><a
href="/datatype/{% BIND observation.transmitter.dataType.id %}">
{% BIND observation.transmitter.dataType.name %}
</a></div>
</div>
<div class="datagrid-item">
<div class="datagrid-title">Frequency</div>
<div class="datagrid-content">{% BIND observation.transmitter.centerFrequency %}Hz</div>
</div>
<div class="datagrid-item">
<div class="datagrid-title">Bandwidth</div>
<div class="datagrid-content">{% BIND observation.transmitter.bandwidth %}Hz</div>
</div>
<div class="datagrid-item">
<div class="datagrid-title">Status</div>
<div class="datagrid-content">
<span class="status
{% IF observation.status==fail USE status-red %}
{% IF observation.status==success USE status-green %}
{% IF observation.status==recording USE status-white %}
{% IF observation.status==decoding USE status-yelow %}
{% IF observation.status==planed USE status-blue %}
{% IF observation.status==assigned USE status-grey %}
{% IF observation.status==recorded USE status-white %}
">
{% BIND observation.status %}
</span>
</div>
</div>
<div class="datagrid-item">
<div class="datagrid-title">Start UTC Time</div>
<div class="datagrid-content"> -> {% BIND observation.start %}</div>
</div>
<div class="datagrid-item">
<div class="datagrid-title">End UTC Time</div>
<div class="datagrid-content"> <- {% BIND observation.end %}</div>
</div>
<div class="datagrid-item">
<div class="datagrid-title">Transmitter antenna</div>
<div class="datagrid-content">{% BIND observation.transmitter.antenna.name %}</div>
</div>
<div class="datagrid-item">
<div class="datagrid-title">Receiver antenna</div>
<div class="datagrid-content">{% BIND observation.receiver.antenna.name %}</div>
</div>
<div class="datagrid-item">
<div class="datagrid-title">Receiver gain</div>
<div class="datagrid-content">{% BIND observation.receiver.gain %}</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="col-lg-6">
<div class="row row-cards">
<div class="col-12">
<div class="card">
<div class="card-header">
<h3 class="card-title">Another observation of target</h3>
</div>
<div class="table-responsive scrollable" style="height: 400px">
<table class="table card-table table-vcenter text-nowrap datatable table-hover">
<thead>
<tr>
<th>Modulation</th>
<th>Status</th>
<th>Start</th>
<th>End</th>
</tr>
</thead>
<tbody>
{% FOREACH observations RENDER blocks/observation-item_short.html %}
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
<div class="col-lg-6">
<div class="card">
<div class="card-header border-0">
<div class="card-title">Target record map</div>
</div>
<div class="position-relative">
<div id="map" style="height:400px;" tabindex="0">
<script src="/static/js/map.js"></script>
<script>
var map = L.map('map').setView([0, 0], 1);
L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png', {
attribution: '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
}).addTo(map);
mapAddTLEPath(
map,
"{% BIND observation.transmitter.target.locator.tle.line1 %}",
"{% BIND observation.transmitter.target.locator.tle.line2 %}",
"{% BIND observation.start %} UTC",
"{% BIND observation.end %} UTC"
);
</script>
</div>
</div>
</div>
</div>
<div class="col-lg-12">
<div class="row row-cards">
<div class="col-12">
<div class="card">
<div class="row g-0">
<div class="col-12 col-md-3 border-end">
<div class="card-header">
<h3 class="card-title">Artefacts</h3>
</div>
<div class="card-body">
<div class="list-group list-group-transparent scrollable" style="height: 35rem">
{% FOREACH artefacts USE '<a value="(\ BIND item.url \)" onclick="art(this)" class="list-group-item list-group-item-action d-flex align-items-center artefact">(\ BIND item.name \)</a>' %}
</div>
</div>
</div>
<div class="col-12 col-md-9 d-flex flex-column">
<div class="card-body">
<h2 class="mb-4" id="artefact-title"></h2>
<div id="artefact-body" class="row align-items-center scrollable" style="height: 35rem">
<div class="empty">
<div class="empty-img"><img src="/static/icons/undraw_printing_invoices_5r4r.svg" height="128" alt="">
</div>
<p class="empty-title">No artefacts found</p>
<p class="empty-subtitle text-secondary">
Try wait until observation is decoded.
</p>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Tabler Core -->
<script src="/dist/js/tabler.min.js?1668287865" defer=""></script>
<script>
function extension(val) {
var parts = val.split(".");
return parts[parts.length - 1].toLowerCase();
}
function art(el) {
var url = el.getAttribute("value");
var name = el.innerHTML;
document.getElementById("artefact-title").innerHTML = name;
if (extension(name) == "png" || extension(name) == "jpg" ) {
document.getElementById("artefact-body") .innerHTML = "<img src='" + url + "'>";
} else if (extension(name) == "json" || extension(name) == "txt" ) {
var request = new XMLHttpRequest();
request.open('GET', url, true);
request.send();
request.onreadystatechange = function () {
if (request.readyState === 4 && request.status === 200) {
document.getElementById("artefact-body") .innerHTML = "<pre>" + request.responseText + "</pre>";
}
}
} else {
document.getElementById("artefact-body") .innerHTML = "Binnary file";
}
var items = document.getElementsByClassName("artefact");
for (var i = 0; i < items.length; i++) {
items[i].classList.remove("active");
}
el.classList.add("active");
}
var allArtefacts = document.getElementsByClassName("artefact");
if (allArtefacts.length > 0) {
art(allArtefacts[0]);
}
</script>
</div>
</div>
</div>
</body>
</html>

@ -17,6 +17,15 @@
Observations
</h2>
</div>
<div class="col-auto ms-auto d-print-none">
<button type="button" class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#exampleModal">
Plan observation
</button>
</div>
</div>
<div class="col mt-4" style="display: none;" id="created-alert">
<div class="alert alert-success" role="alert">Successly created observation plan <span id="created-id"></span> [<a href="/observations">refresh</a>]?</div>
</div>
</div>
</div>
@ -56,8 +65,66 @@
</div>
</div>
<div class="modal" id="exampleModal" tabindex="-1">
<div class="modal-dialog modal-lg" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">New observation</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<div class="row">
<div class="col-lg-7">
<div class="mb-3">
<label class="form-label">Target transmitter</label>
<select class="form-select" id="plan-transmitter">
{% FOREACH transmitters USE '<option value="(\ BIND item.id \)">
(\ BIND item.target.name \) -
(\ BIND item.modulation.name \) -
(\ BIND item.dataType.name \) @
(\ BIND item.centerFrequency \)Hz
</option>' %}
</select>
</div>
</div>
<div class="col-lg-5">
<div class="mb-3">
<label class="form-label">Station receiver</label>
<select class="form-select" id="plan-receiver">
{% FOREACH receivers USE '<option value="(\ BIND item.id \)">(\ BIND item.station.name \) @ (\ BIND item.centerFrequency \)Hz</option>' %}
</select>
</div>
</div>
</div>
</div>
<div class="modal-body">
<div class="row">
<div class="col-lg-6">
<div class="mb-3">
<label class="form-label">Start UTC time</label>
<input type="datetime-local" id="plan-start" class="form-control">
</div>
</div>
<div class="col-lg-6">
<div class="mb-3">
<label class="form-label">End UTC time</label>
<input type="datetime-local" id="plan-end" class="form-control">
</div>
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn me-auto" data-bs-dismiss="modal">Close</button>
<button type="button" onclick="plan()" class="btn btn-primary" data-bs-dismiss="modal">Plan!</button>
</div>
</div>
</div>
</div>
<!-- Tabler Core -->
<script src="./dist/js/tabler.min.js?1668287865" defer=""></script>
<script src="/dist/js/tabler.min.js?1668287865" defer=""></script>
<script src="/static/js/observations.js"></script>
</div>
</body>
</html>

@ -6,7 +6,7 @@
}
$container = new \wsos\structs\container();
$db = new \wsos\database\drivers\inAppArray();
$db = new \wsos\database\drivers\csv(__DIR__ . "/DB");
$auth = new \wsos\auth\basic\manager(DAL\user::class, "userName", "pass", "/login");
//get current url
@ -15,11 +15,15 @@
$sites = [
"sites" => [
"observations" => ["controller" => __DIR__ . "/CONTROLLERS/observations.php", "name" => "Observations", "icon" => "/static/icons/telescope.svg", "menu" => true],
/*
"stations" => ["controller" => __DIR__ . "/CONTROLLERS/stations.php", "name" => "Stations", "icon" => "/static/icons/radio.svg", "menu" => true],
"targets" => ["controller" => __DIR__ . "/CONTROLLERS/targets.php", "name" => "Targets", "icon" => "/static/icons/focus-2.svg", "menu" => true],
"modulations" => ["controller" => __DIR__ . "/CONTROLLERS/modulations.php", "name" => "Modulations", "icon" => "/static/icons/wave-sine.svg", "menu" => true],
"datatypes" => ["controller" => __DIR__ . "/CONTROLLERS/datatypes.php", "name" => "Data Types", "icon" => "/static/icons/file-analytics.svg", "menu" => true],
"observation" => ["controller" => __DIR__ . "/CONTROLLERS/observation.php", "name" => "Observation view", "menu" => false]
*/
"observation" => ["controller" => __DIR__ . "/CONTROLLERS/observation.php", "name" => "Observation view", "menu" => false],
"login" => ["controller" => __DIR__ . "/CONTROLLERS/login.php", "name" => "Login", "menu" => false],
"api" => ["controller" => __DIR__ . "/API/main.php", "name" => "api", "menu" => false],
],
"controller" => __DIR__ . "/CONTROLLERS/dashboard.php",
@ -34,7 +38,8 @@
$context = [
"url" => $url,
"menu_items" => $router->getFlatMenu()->values,
"logined" => $auth->getActive()
"logined" => $auth->getActive(),
"appName" => "YAGS"
];
// register containers
@ -42,11 +47,11 @@
$container->register("templateLoader", new wsos\templates\loader(__DIR__ . "/VIEWS"));
$container->register("context", $context);
$container->register("auth", $auth);
$container->register("rounter", $router);
$container->register("router", $router);
// seeds DB
// do not do this in release!!
include "seeds.php";
//include "seeds.php";
$router->route($url);
?>

@ -13,28 +13,105 @@
$satType->commit();
$wetImgType = new \DAL\dataType();
$wetImgType->name->set("weather photo");
$wetImgType->name->set("AVHRR");
$wetImgType->commit();
/**
* Antennas seeds
*/
$yagi = new \DAL\Antenna();
$yagi->name->set("YAGI");
$yagi->commit();
$dish = new \DAL\Antenna();
$dish->name->set("DISH");
$dish->commit();
$qfh = new \DAL\Antenna();
$qfh->name->set("QFH");
$qfh->commit();
/**
* Test station
*/
$myStation = new \DAL\station();
$myStation->name->set("Medlánky");
$myStation->name->set("default");
$myStation->commit();
$myStation137 = new \DAL\receiver();
$myStation137->station->set($myStation);
$myStation137->params->set([
"radio" => "rtlsdr",
"gain" => 45,
"agc" => false,
"fs" => [250000, 1024000, 1536000, 1792000, 1920000, 2048000, 2160000, 2400000, 2560000, 2880000, 3200000]
]);
$myStation137->antenna->set($yagi);
$myStation137->centerFrequency->set(137500000);
$myStation137->gain->set(45);
$myStation137->commit();
$myStation2G = new \DAL\receiver();
$myStation2G->station->set($myStation);
$myStation2G->params->set([
"radio" => "hackrf",
"amp" => true,
"lna_gain" => 45,
"vga_gain" => 10,
"bias" => true
]);
$myStation2G->antenna->set($yagi);
$myStation2G->centerFrequency->set(1700000000);
$myStation2G->gain->set(45);
$myStation2G->commit();
/**
* NOAA modulations
* Modulations
*/
$fm = new \DAL\modulation();
$fm->name->set("FM");
$fm->commit();
$apt = new \DAL\modulation();
$apt->name->set("APT");
$apt->commit();
$bpsk = new \DAL\modulation();
$bpsk->name->set("BPSK");
$bpsk->commit();
$hrpt = new \DAL\modulation();
$hrpt->name->set("HRPT");
$hrpt->commit();
$dsb = new \DAL\modulation();
$dsb->name->set("DSB");
$dsb->commit();
$lrpt = new \DAL\modulation();
$lrpt->name->set("LRPT");
$lrpt->commit();
/**
* Process pipes
*/
$aptPipe = new \DAL\processPipe();
$aptPipe->name->set("NOAA APT");
$aptPipe->pipe->set([
"satdump noaa_apt baseband {baseband} {artefactDir} --samplerate {fs} --baseband_format s8",
"cp {baseband} {artefactDir}"
]);
$aptPipe->commit();
$lrptPipe = new \DAL\processPipe();
$lrptPipe->name->set("METEOR LRPT");
$lrptPipe->pipe->set([
"satdump meteor_m2-x_lrpt baseband {baseband} {artefactDir} --samplerate {fs} --baseband_format s8",
"cp {baseband} {artefactDir}"
]);
$lrptPipe->commit();
/**
@ -45,18 +122,41 @@
$noaa19->type->set($satType);
$noaa19->description->set("NOAA 19 is the fifth in a series of five Polar-orbiting Operational Environmental Satellites (POES) with advanced microwave sounding instruments that provide imaging and sounding capabilities.");
$noaa19->locator->set([
"tle" => "NOAA 19\n1 33591U 09005A 23243.18101660 .00000207 00000-0 13587-3 0 9998\n2 33591 99.0938 290.2850 0014342 35.8617 324.3514 14.12812127750532"
"tle" => [
"line1" => "1 33591U 09005A 23243.18101660 .00000207 00000-0 13587-3 0 9998",
"line2" => "2 33591 99.0938 290.2850 0014342 35.8617 324.3514 14.12812127750532"
]
]);
$noaa19->commit();
$noaa19APT = new \DAL\transmitter();
$noaa19APT->target->set($noaa19);
$noaa19APT->dataType->set($wetImgType);
$noaa19APT->bandwidth->set(100);
$noaa19APT->bandwidth->set(34000);
$noaa19APT->centerFrequency->set(137100000);
$noaa19APT->modulation->set($fm);
$noaa19APT->modulation->set($apt);
$noaa19APT->antenna->set($qfh);
$noaa19APT->processPipe->set($aptPipe);
$noaa19APT->commit();
$noaa19DSB = new \DAL\transmitter();
$noaa19DSB->target->set($noaa19);
$noaa19DSB->dataType->set($wetImgType);
$noaa19DSB->bandwidth->set(34000);
$noaa19DSB->centerFrequency->set(137770000);
$noaa19DSB->modulation->set($dsb);
$noaa19DSB->antenna->set($qfh);
$noaa19DSB->commit();
$noaa19HRPT = new \DAL\transmitter();
$noaa19HRPT->target->set($noaa19);
$noaa19HRPT->dataType->set($wetImgType);
$noaa19HRPT->bandwidth->set(3000000);
$noaa19HRPT->centerFrequency->set(1698000000);
$noaa19HRPT->modulation->set($hrpt);
$noaa19HRPT->antenna->set($qfh);
$noaa19HRPT->commit();
/**
* NOAA 18
*/
@ -65,32 +165,124 @@
$noaa18->type->set($satType);
$noaa18->description->set("NOAA 18, known before launch as NOAA-N, is a weather forecasting satellite run by NOAA. NOAA-N (18) was launched into a sun-synchronous orbit at an altitude of 854 km above the Earth, with an orbital period of 102 minutes. It hosts the AMSU-A, MHS, AVHRR, Space Environment Monitor SEM/2 instrument and High Resolution Infrared Radiation Sounder (HIRS) instruments, as well as the SBUV/2 ozone-monitoring instrument.");
$noaa18->locator->set([
"tle" => "NOAA 18\n1 28654U 05018A 23250.34978256 .00000271 00000-0 16921-3 0 9997\n2 28654 98.9045 324.3258 0015006 144.5825 215.6347 14.13000434943172"
"tle" => [
"line1" => "1 28654U 05018A 23250.34978256 .00000271 00000-0 16921-3 0 9997",
"line2" => "2 28654 98.9045 324.3258 0015006 144.5825 215.6347 14.13000434943172"
]
]);
$noaa18->commit();
$noaa18APT = new \DAL\transmitter();
$noaa18APT->target->set($noaa18);
$noaa18APT->dataType->set($wetImgType);
$noaa18APT->bandwidth->set(100);
$noaa18APT->bandwidth->set(34000);
$noaa18APT->centerFrequency->set(137912500);
$noaa18APT->modulation->set($fm);
$noaa18APT->modulation->set($apt);
$noaa18APT->antenna->set($qfh);
$noaa18APT->processPipe->set($aptPipe);
$noaa18APT->commit();
$noaa18DSB = new \DAL\transmitter();
$noaa18DSB->target->set($noaa18);
$noaa18DSB->dataType->set($wetImgType);
$noaa18DSB->bandwidth->set(34000);
$noaa18DSB->centerFrequency->set(137350000);
$noaa18DSB->modulation->set($dsb);
$noaa18DSB->antenna->set($qfh);
$noaa18DSB->commit();
$noaa18HRPT = new \DAL\transmitter();
$noaa18HRPT->target->set($noaa18);
$noaa18HRPT->dataType->set($wetImgType);
$noaa18HRPT->bandwidth->set(3000000);
$noaa18HRPT->centerFrequency->set(1707000000);
$noaa18HRPT->modulation->set($hrpt);
$noaa18HRPT->antenna->set($qfh);
$noaa18HRPT->commit();
/**
* Plan observation
* NOAA 15
*/
$noaa19Plan = new \DAL\observation();
$noaa19Plan->status->set("planed");
$noaa19Plan->locator->set($noaa19->locator->get());
$noaa19Plan->transmitter->set($noaa19APT);
$noaa19Plan->receiver->set($myStation137);
$noaa19Plan->commit();
$noaa15 = new \DAL\target();
$noaa15->name->set("NOAA 15");
$noaa15->type->set($satType);
$noaa15->description->set("");
$noaa15->locator->set([
"tle" => [
"line1" => "1 25338U 98030A 23256.05271705 .00000271 00000-0 13068-3 0 9997",
"line2" => "2 25338 98.6015 283.5892 0011307 61.1013 299.1300 14.26387191317785"
]
]);
$noaa15->commit();
$noaa18Plan = new \DAL\observation();
$noaa18Plan->status->set("planed");
$noaa18Plan->locator->set($noaa18->locator->get());
$noaa18Plan->transmitter->set($noaa18APT);
$noaa18Plan->receiver->set($myStation137);
$noaa18Plan->commit();
$noaa15APT = new \DAL\transmitter();
$noaa15APT->target->set($noaa15);
$noaa15APT->dataType->set($wetImgType);
$noaa15APT->bandwidth->set(34000);
$noaa15APT->centerFrequency->set(137500000);
$noaa15APT->modulation->set($apt);
$noaa15APT->antenna->set($qfh);
$noaa15APT->processPipe->set($aptPipe);
$noaa15APT->commit();
$noaa15DSB = new \DAL\transmitter();
$noaa15DSB->target->set($noaa15);
$noaa15DSB->dataType->set($wetImgType);
$noaa15DSB->bandwidth->set(34000);
$noaa15DSB->centerFrequency->set(1377700000);
$noaa15DSB->modulation->set($dsb);
$noaa15DSB->antenna->set($qfh);
$noaa15DSB->commit();
$noaa15HRPT = new \DAL\transmitter();
$noaa15HRPT->target->set($noaa15);
$noaa15HRPT->dataType->set($wetImgType);
$noaa15HRPT->bandwidth->set(3000000);
$noaa15HRPT->centerFrequency->set(1702500000);
$noaa15HRPT->modulation->set($hrpt);
$noaa15HRPT->antenna->set($qfh);
$noaa15HRPT->commit();
$meteor23 = new \DAL\target();
$meteor23->name->set("METEOR M2-3");
$meteor23->type->set($satType);
$meteor23->description->set("");
$meteor23->locator->set([
"tle" => [
"line1" => "1 57166U 23091A 23258.09139909 .00000046 00000-0 39414-4 0 9995",
"line2" => "2 57166 98.7568 309.6443 0003662 194.1864 165.9212 14.23842173 11338"
]
]);
$meteor23->commit();
$meteor23LRPT1 = new \DAL\transmitter();
$meteor23LRPT1->target->set($meteor23);
$meteor23LRPT1->dataType->set($wetImgType);
$meteor23LRPT1->bandwidth->set(120000);
$meteor23LRPT1->centerFrequency->set(137900000);
$meteor23LRPT1->modulation->set($lrpt);
$meteor23LRPT1->antenna->set($qfh);
$meteor23LRPT1->processPipe->set($lrptPipe);
$meteor23LRPT1->commit();
$meteor23LRPT2 = new \DAL\transmitter();
$meteor23LRPT2->target->set($meteor23);
$meteor23LRPT2->dataType->set($wetImgType);
$meteor23LRPT2->bandwidth->set(120000);
$meteor23LRPT2->centerFrequency->set(137100000);
$meteor23LRPT2->modulation->set($lrpt);
$meteor23LRPT2->antenna->set($qfh);
$meteor23LRPT2->processPipe->set($lrptPipe);
$meteor23LRPT2->commit();
$meteor23HRPT = new \DAL\transmitter();
$meteor23HRPT->target->set($meteor23);
$meteor23HRPT->dataType->set($wetImgType);
$meteor23HRPT->bandwidth->set(3000000);
$meteor23HRPT->centerFrequency->set(1700000000);
$meteor23HRPT->modulation->set($hrpt);
$meteor23HRPT->antenna->set($qfh);
$meteor23HRPT->commit();
die();

File diff suppressed because one or more lines are too long

After

(image error) Size: 42 KiB

26
web/static/js/map.js Normal file

@ -0,0 +1,26 @@
function mapAddTLEPath(map, line1, line2, start, end) {
var pointList = [];
var satrec = satellite.twoline2satrec(line1, line2);
for (var i = Date.parse(start); i < Date.parse(end); i += 1000) {
var positionAndVelocity = satellite.propagate(satrec, new Date(i));
var gmst = satellite.gstime(new Date(i));
var positionEci = positionAndVelocity.position;
var positionGd = satellite.eciToGeodetic(positionEci, gmst);
pointList.push(
new L.LatLng(satellite.degreesLat(positionGd.latitude), satellite.degreesLong(positionGd.longitude))
);
}
var targetPath = new L.Polyline(pointList, {
color: 'red',
weight: 3,
opacity: 0.5,
smoothFactor: 1
});
targetPath.addTo(map);
}

@ -0,0 +1,24 @@
function plan() {
var plan = new FormData();
var transmitter = document.getElementById("plan-transmitter").value;
var receiver = document.getElementById("plan-receiver").value;
var start = document.getElementById("plan-start").value;
var end = document.getElementById("plan-end").value;
plan.append('transmitter', transmitter);
plan.append('receiver', receiver);
plan.append('start', start);
plan.append('end', end);
var xhttp = new XMLHttpRequest();
xhttp.onreadystatechange = function() {
if (this.readyState == 4 && this.status == 200) {
document.getElementById("created-id").innerHTML = JSON.parse(this.responseText).id;
document.getElementById("created-alert").style.display = "block";
}
};
xhttp.open("POST", "/api/observation/plan", true);
xhttp.send(plan);
}