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

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