UI updates
This commit is contained in:
parent
2144d7ef69
commit
4b70f8d67b
@ -10,6 +10,7 @@
|
|||||||
"author": "",
|
"author": "",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@ztimson/utils": "^0.21.6",
|
||||||
"express": "^4.21.1",
|
"express": "^4.21.1",
|
||||||
"i2c-bus": "^5.2.3",
|
"i2c-bus": "^5.2.3",
|
||||||
"serialport": "^12.0.0"
|
"serialport": "^12.0.0"
|
||||||
|
@ -18,6 +18,11 @@ export default class Daemon {
|
|||||||
res.json(await this.run(cmd));
|
res.json(await this.run(cmd));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.express.get('/favicon.*', (req, res) => {
|
||||||
|
const absolute = path.join(import.meta.url, '/../../ui/favicon.png').replace('file:', '');
|
||||||
|
res.sendFile(absolute);
|
||||||
|
});
|
||||||
|
|
||||||
this.express.get('*', (req, res) => {
|
this.express.get('*', (req, res) => {
|
||||||
let p = req.params['0'];
|
let p = req.params['0'];
|
||||||
if (!p || p == '/') p = 'index.html';
|
if (!p || p == '/') p = 'index.html';
|
||||||
|
20
src/misc.js
20
src/misc.js
@ -9,22 +9,6 @@ export function $(str, ...args) {
|
|||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
export function adjustedInterval(cb, ms) {
|
|
||||||
let cancel = false, timeout = null;
|
|
||||||
const p = async () => {
|
|
||||||
if (cancel) return;
|
|
||||||
const start = new Date().getTime();
|
|
||||||
await cb();
|
|
||||||
const end = new Date().getTime();
|
|
||||||
timeout = setTimeout(() => p(), ms - (end - start) || 1);
|
|
||||||
};
|
|
||||||
p();
|
|
||||||
return () => {
|
|
||||||
cancel = true;
|
|
||||||
if (timeout) clearTimeout(timeout);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function ask(prompt, hide = false) {
|
export function ask(prompt, hide = false) {
|
||||||
const rl = readline.createInterface({
|
const rl = readline.createInterface({
|
||||||
input: process.stdin,
|
input: process.stdin,
|
||||||
@ -65,7 +49,3 @@ export function ask(prompt, hide = false) {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function sleep(ms) {
|
|
||||||
return new Promise(res => setTimeout(res, ms));
|
|
||||||
}
|
|
||||||
|
6
ui/assets/bootstrap.min.css
vendored
Normal file
6
ui/assets/bootstrap.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
9
ui/assets/font-awesome/css/font-awesome.css
vendored
Normal file
9
ui/assets/font-awesome/css/font-awesome.css
vendored
Normal file
File diff suppressed because one or more lines are too long
BIN
ui/assets/font-awesome/webfonts/fa-brands-400.ttf
Normal file
BIN
ui/assets/font-awesome/webfonts/fa-brands-400.ttf
Normal file
Binary file not shown.
BIN
ui/assets/font-awesome/webfonts/fa-brands-400.woff2
Normal file
BIN
ui/assets/font-awesome/webfonts/fa-brands-400.woff2
Normal file
Binary file not shown.
BIN
ui/assets/font-awesome/webfonts/fa-regular-400.ttf
Normal file
BIN
ui/assets/font-awesome/webfonts/fa-regular-400.ttf
Normal file
Binary file not shown.
BIN
ui/assets/font-awesome/webfonts/fa-regular-400.woff2
Normal file
BIN
ui/assets/font-awesome/webfonts/fa-regular-400.woff2
Normal file
Binary file not shown.
BIN
ui/assets/font-awesome/webfonts/fa-solid-900.ttf
Normal file
BIN
ui/assets/font-awesome/webfonts/fa-solid-900.ttf
Normal file
Binary file not shown.
BIN
ui/assets/font-awesome/webfonts/fa-solid-900.woff2
Normal file
BIN
ui/assets/font-awesome/webfonts/fa-solid-900.woff2
Normal file
Binary file not shown.
BIN
ui/favicon.png
Normal file
BIN
ui/favicon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 44 KiB |
210
ui/index.html
210
ui/index.html
@ -3,7 +3,8 @@
|
|||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<title>Apollo v0.0.0</title>
|
<title>Apollo v0.0.0</title>
|
||||||
|
<link href="./assets/bootstrap.min.css" rel="stylesheet"/>
|
||||||
|
<link href="./assets/font-awesome/css/font-awesome.css" rel="stylesheet"/>
|
||||||
<style>
|
<style>
|
||||||
html, body {
|
html, body {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
@ -11,34 +12,221 @@
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
background: #1a1a1a;
|
background: #1a1a1a;
|
||||||
color: white;
|
color: #aaa;
|
||||||
font-family: sans-serif;
|
font-family: sans-serif;
|
||||||
}
|
}
|
||||||
|
|
||||||
.text-muted {
|
h1{
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
h2, h3, h4, h5, h6, p {
|
||||||
color: #aaa;
|
color: #aaa;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.card {
|
||||||
|
background: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.navball {
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
background: url('./assets/navball2.png') repeat;
|
||||||
|
background-size: 360px;
|
||||||
|
transition: all 1s linear;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navball-container {
|
||||||
|
position: relative;
|
||||||
|
height: 180px;
|
||||||
|
width: 180px;
|
||||||
|
border: #aaa 2px solid;
|
||||||
|
border-radius: 50%;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navball-cursor {
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.no-signal {
|
||||||
|
top: 50%;
|
||||||
|
background: red;
|
||||||
|
border: darkred;
|
||||||
|
color: black;
|
||||||
|
}
|
||||||
|
|
||||||
|
.no-signal::backdrop {
|
||||||
|
background-color: rgba(0, 0, 0, 0.5); /* Semi-transparent black */
|
||||||
|
backdrop-filter: blur(5px); /* Optional blur effect */
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-muted {
|
||||||
|
color: #aaa !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status {
|
||||||
|
display: inline;
|
||||||
|
margin: 0.25rem;
|
||||||
|
padding: 0.25rem;
|
||||||
|
width: 62px;
|
||||||
|
text-align: center;
|
||||||
|
color: white !important;
|
||||||
|
background: grey;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<nav style="padding: 0.5rem;">
|
<nav class="px-3 pt-3">
|
||||||
<div style="display: flex; flex-direction: row">
|
<div class="d-flex">
|
||||||
<div>
|
<div>
|
||||||
<h1 style="display: inline; font-size: 1rem;">Apollo Control</h1>
|
<h1 style="font-size: 1rem;">Apollo Control</h1>
|
||||||
</div>
|
</div>
|
||||||
<div style="flex-grow: 1"></div>
|
<div class="flex-grow-1"></div>
|
||||||
<div class="text-muted" style="font-size: 1rem">
|
<div class="text-muted" style="font-size: 1rem">
|
||||||
<span id="time">00:00:00</span> UTC
|
<i class="fa fa-signal mx-2"></i>
|
||||||
|
<i class="fa fa-location-crosshairs mx-2"></i>
|
||||||
|
<span class="battery mx-2">0</span>% <i class="battery-icon fa fa-battery"></i>
|
||||||
|
<span class="mx-2"><span class="time">00:00:00</span> UTC</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
|
<div class="d-flex">
|
||||||
|
<div style="width: 250px">
|
||||||
|
|
||||||
|
<div class="card m-3 p-3">
|
||||||
|
<h2 class="mb-1" style="font-size: 1rem">Altitude</h2>
|
||||||
|
<hr class="my-1" style="border-color: #aaa">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card m-3 p-3">
|
||||||
|
<h2 class="mb-1" style="font-size: 1rem">Model</h2>
|
||||||
|
<hr class="my-1" style="border-color: #aaa">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card m-3 p-3">
|
||||||
|
<h2 class="mb-1" style="font-size: 1rem">Nav Ball</h2>
|
||||||
|
<hr class="mt-1 mb-2" style="border-color: #aaa">
|
||||||
|
<div class="navball-container">
|
||||||
|
<div class="navball"></div>
|
||||||
|
<img class="navball-cursor" src="./assets/navball-cursor.png" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="d-flex flex-column flex-grow-1 my-3">
|
||||||
|
|
||||||
|
<div class="card flex-grow-1 p-3 mb-3"></div>
|
||||||
|
|
||||||
|
<div class="card p-3">
|
||||||
|
<div class="d-flex justify-content-between">
|
||||||
|
<p class="mb-0">T-0</p>
|
||||||
|
<p class="mb-0">T+<span class="t-plus">0</span></p>
|
||||||
|
</div>
|
||||||
|
<input type="range" min="1" max="100" value="50" class="slider" id="myRange">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style="width: 250px">
|
||||||
|
|
||||||
|
<div class="d-flex flex-wrap m-3">
|
||||||
|
<div class="status status-bat">BAT</div>
|
||||||
|
<div class="status status-env">ENV</div>
|
||||||
|
<div class="status status-gps">GPS</div>
|
||||||
|
<div class="status status-agm">IMU</div>
|
||||||
|
<div class="status status-tel">TEL</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card m-3 p-3">
|
||||||
|
<h2 class="mb-1" style="font-size: 1rem">Position</h2>
|
||||||
|
<hr class="my-1" style="border-color: #aaa">
|
||||||
|
<p class="mb-1">Altitude: <span class="altitude">0</span> m</p>
|
||||||
|
<p class="mb-1">Azimuth: <span class="azimuth">0</span>°</p>
|
||||||
|
<p class="mb-1">Coordinates: <span class="latlng">0, 0</span></p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card m-3 p-3">
|
||||||
|
<h2 class="mb-1" style="font-size: 1rem">Sensors</h2>
|
||||||
|
<hr class="my-1" style="border-color: #aaa">
|
||||||
|
<p class="mb-1">Battery: <span class="battery">0</span>%</p>
|
||||||
|
<p class="mb-1">Bat. Temp: <span class="battery-temperature">0</span>°C</p>
|
||||||
|
<p class="mb-1">Humidity: <span class="humidity">0</span>%</p>
|
||||||
|
<p class="mb-1">Pressure: <span class="pressure">0</span> hPa</p>
|
||||||
|
<p class="mb-1">Temperature: <span class="temperature">0</span>°C</p>
|
||||||
|
<p class="mb-1">Voltage: <span class="voltage">0</span> v</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card m-3 p-3">
|
||||||
|
<h2 class="mb-1" style="font-size: 1rem">Telemetry</h2>
|
||||||
|
<hr class="my-1" style="border-color: #aaa">
|
||||||
|
<p class="mb-1">Strength: <span class="azimuth">0</span> db</p>
|
||||||
|
<p class="mb-1">TX/RX: <span class="azimuth">0</span>/0</p>
|
||||||
|
<p class="mb-1">WiFi: AP Mode</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<dialog class="no-signal">
|
||||||
|
<i class="fa fa-tower-broadcast pe-2"></i><strong>NO SIGNAL</strong>
|
||||||
|
</dialog>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
const time = document.querySelector('#time');
|
const remote = localStorage.getItem('remote') || 'localhost:1969';
|
||||||
setInterval(() => {
|
|
||||||
|
function run(cmd) {
|
||||||
|
return fetch(`${remote.startsWith('http') ? '' : 'http://'}${remote}/api/${cmd}`).then(async resp => {
|
||||||
|
const value = await resp.text();
|
||||||
|
try {
|
||||||
|
return JSON.parse(value);
|
||||||
|
} catch {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}).catch(err => err.message);
|
||||||
|
}
|
||||||
|
|
||||||
|
const battery = document.querySelectorAll('.battery');
|
||||||
|
const batteryTemp = document.querySelectorAll('.battery-temperature');
|
||||||
|
const batteryIcon = document.querySelectorAll('.battery-icon');
|
||||||
|
const navball = document.querySelectorAll('.navball');
|
||||||
|
const statusAgm = document.querySelectorAll('.status-agm');
|
||||||
|
const statusBat = document.querySelectorAll('.status-bat');
|
||||||
|
const statusEnv = document.querySelectorAll('.status-env');
|
||||||
|
const statusGps = document.querySelectorAll('.status-gps');
|
||||||
|
const statusTel = document.querySelectorAll('.status-tel');
|
||||||
|
const time = document.querySelectorAll('.time');
|
||||||
|
const tPlus = document.querySelectorAll('.t-plus');
|
||||||
|
const voltage = document.querySelectorAll('.voltage');
|
||||||
|
|
||||||
|
let count = 0;
|
||||||
|
setInterval(async () => {
|
||||||
|
count++;
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
time.innerHTML = `${now.getHours().toString().padStart(2, 0)}:${now.getMinutes().toString().padStart(2, 0)}:${now.getSeconds().toString().padStart(2, 0)}`;
|
time.forEach(t => t.innerHTML = `${now.getUTCHours().toString().padStart(2, 0)}:${now.getMinutes().toString().padStart(2, 0)}:${now.getSeconds().toString().padStart(2, 0)}`);
|
||||||
|
tPlus.forEach(t => t.innerHTML = count.toString());
|
||||||
|
|
||||||
|
run('sensors').then(sensors => {
|
||||||
|
battery.forEach(b => b.innerHTML = (sensors.battery?.percentage || 0) * 100);
|
||||||
|
batteryTemp.forEach(bt => bt.innerHTML = sensors.battery?.temperature || 0);
|
||||||
|
batteryIcon.forEach(bi => bi.className = sensors.battery?.charging ? 'fa fa-bolt' : 'fa fa-battery');
|
||||||
|
voltage.forEach(v => v.innerHTML = sensors.battery?.voltage || 0);
|
||||||
|
});
|
||||||
|
|
||||||
|
run('status').then(status => {
|
||||||
|
statusEnv.forEach(se => se.style.background = status['bme'] === 'ok' ? 'green' : 'red');
|
||||||
|
statusBat.forEach(sb => sb.style.background = status['bms'] === 'ok' ? 'green' : 'red');
|
||||||
|
});
|
||||||
|
|
||||||
|
navball.forEach(n => n.style.transform = `rotate(${count * 5}deg)`);
|
||||||
|
navball.forEach(n => n.style.backgroundPositionY = `${count}px`);
|
||||||
}, 1000);
|
}, 1000);
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
|
Loading…
Reference in New Issue
Block a user