UI updates

This commit is contained in:
Zakary Timson 2024-10-30 10:19:47 -04:00
parent 2144d7ef69
commit 4b70f8d67b
13 changed files with 246 additions and 57 deletions

View File

@ -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"

View File

@ -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';

View File

@ -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

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
ui/favicon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

View File

@ -1,45 +1,233 @@
<!Doctype html> <!Doctype html>
<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>
html, body {
margin: 0;
padding: 0;
width: 100%;
height: 100%;
background: #1a1a1a;
color: #aaa;
font-family: sans-serif;
}
<style> h1{
html, body { color: white;
margin: 0; }
padding: 0;
width: 100%;
height: 100%;
background: #1a1a1a;
color: white;
font-family: sans-serif;
}
.text-muted { h2, h3, h4, h5, h6, p {
color: #aaa; color: #aaa;
} }
</style>
</head>
<body> .card {
<nav style="padding: 0.5rem;"> background: #333;
<div style="display: flex; flex-direction: row"> }
<div>
<h1 style="display: inline; font-size: 1rem;">Apollo Control</h1>
</div>
<div style="flex-grow: 1"></div>
<div class="text-muted" style="font-size: 1rem">
<span id="time">00:00:00</span> UTC
</div>
</div>
</nav>
<script>
const time = document.querySelector('#time'); .navball {
setInterval(() => { height: 100%;
const now = new Date(); width: 100%;
time.innerHTML = `${now.getHours().toString().padStart(2, 0)}:${now.getMinutes().toString().padStart(2, 0)}:${now.getSeconds().toString().padStart(2, 0)}`; background: url('./assets/navball2.png') repeat;
}, 1000); background-size: 360px;
</script> transition: all 1s linear;
</body> }
.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>
</head>
<body>
<nav class="px-3 pt-3">
<div class="d-flex">
<div>
<h1 style="font-size: 1rem;">Apollo Control</h1>
</div>
<div class="flex-grow-1"></div>
<div class="text-muted" style="font-size: 1rem">
<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>
</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>&deg;</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>&deg;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>&deg;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>
const remote = localStorage.getItem('remote') || 'localhost:1969';
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();
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);
</script>
</body>
</html> </html>