feat: Smaller stat cards, battery icons, version in navbar, map fit
- Stats cards compacter with smaller font and padding - Battery status with Bootstrap Icons and color coding - Version displayed in navbar from config - Map fits all nodes on open with invalidateSize Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
9e880a1f36
commit
0dbb1e0184
|
|
@ -5,6 +5,7 @@ import os
|
||||||
|
|
||||||
from aiohttp import web
|
from aiohttp import web
|
||||||
|
|
||||||
|
from meshbot import config
|
||||||
from meshbot.database import Database
|
from meshbot.database import Database
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
@ -56,6 +57,7 @@ class WebServer:
|
||||||
await ws.send_str(json.dumps({"type": "initial", "data": nodes}))
|
await ws.send_str(json.dumps({"type": "initial", "data": nodes}))
|
||||||
|
|
||||||
stats = await self.db.get_stats()
|
stats = await self.db.get_stats()
|
||||||
|
stats["version"] = config.get("version", "0.0.0")
|
||||||
await ws.send_str(json.dumps({"type": "stats_update", "data": stats}))
|
await ws.send_str(json.dumps({"type": "stats_update", "data": stats}))
|
||||||
|
|
||||||
messages = await self.db.get_recent_messages(50)
|
messages = await self.db.get_recent_messages(50)
|
||||||
|
|
|
||||||
|
|
@ -11,38 +11,6 @@
|
||||||
box-shadow: 0 0 6px var(--bs-success);
|
box-shadow: 0 0 6px var(--bs-success);
|
||||||
}
|
}
|
||||||
|
|
||||||
.battery-bar {
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 6px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.battery-indicator {
|
|
||||||
width: 28px;
|
|
||||||
height: 13px;
|
|
||||||
border: 1.5px solid var(--bs-secondary);
|
|
||||||
border-radius: 2px;
|
|
||||||
position: relative;
|
|
||||||
display: inline-block;
|
|
||||||
vertical-align: middle;
|
|
||||||
}
|
|
||||||
|
|
||||||
.battery-indicator::after {
|
|
||||||
content: '';
|
|
||||||
position: absolute;
|
|
||||||
right: -5px;
|
|
||||||
top: 2px;
|
|
||||||
width: 3px;
|
|
||||||
height: 7px;
|
|
||||||
background: var(--bs-secondary);
|
|
||||||
border-radius: 0 2px 2px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.battery-fill {
|
|
||||||
height: 100%;
|
|
||||||
border-radius: 1px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.table-responsive::-webkit-scrollbar {
|
.table-responsive::-webkit-scrollbar {
|
||||||
width: 6px;
|
width: 6px;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@
|
||||||
<nav class="navbar navbar-expand-sm bg-body-tertiary border-bottom">
|
<nav class="navbar navbar-expand-sm bg-body-tertiary border-bottom">
|
||||||
<div class="container-fluid">
|
<div class="container-fluid">
|
||||||
<a class="navbar-brand" href="/">
|
<a class="navbar-brand" href="/">
|
||||||
<i class="bi bi-broadcast-pin text-info me-2"></i>MeshDD-Bot
|
<i class="bi bi-broadcast-pin text-info me-2"></i>MeshDD-Bot <small class="text-body-secondary" id="versionLabel"></small>
|
||||||
</a>
|
</a>
|
||||||
<div class="d-flex align-items-center gap-3">
|
<div class="d-flex align-items-center gap-3">
|
||||||
<a href="/map" target="_blank" class="btn btn-outline-info btn-sm">
|
<a href="/map" target="_blank" class="btn btn-outline-info btn-sm">
|
||||||
|
|
@ -32,35 +32,35 @@
|
||||||
|
|
||||||
<div class="container-fluid py-3">
|
<div class="container-fluid py-3">
|
||||||
<!-- Stats Cards -->
|
<!-- Stats Cards -->
|
||||||
<div class="row g-3 mb-3">
|
<div class="row g-2 mb-3">
|
||||||
<div class="col-6 col-md-3">
|
<div class="col-6 col-md-3">
|
||||||
<div class="card text-center border-info border-opacity-25">
|
<div class="card text-center border-info border-opacity-25">
|
||||||
<div class="card-body py-3">
|
<div class="card-body py-2">
|
||||||
<div class="fs-2 fw-bold text-info" id="statNodes">0</div>
|
<div class="fs-4 fw-bold text-info" id="statNodes">0</div>
|
||||||
<div class="text-body-secondary small">Nodes</div>
|
<div class="text-body-secondary small">Nodes</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-6 col-md-3">
|
<div class="col-6 col-md-3">
|
||||||
<div class="card text-center border-success border-opacity-25">
|
<div class="card text-center border-success border-opacity-25">
|
||||||
<div class="card-body py-3">
|
<div class="card-body py-2">
|
||||||
<div class="fs-2 fw-bold text-success" id="statPositions">0</div>
|
<div class="fs-4 fw-bold text-success" id="statPositions">0</div>
|
||||||
<div class="text-body-secondary small">Mit Position</div>
|
<div class="text-body-secondary small">Mit Position</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-6 col-md-3">
|
<div class="col-6 col-md-3">
|
||||||
<div class="card text-center border-warning border-opacity-25">
|
<div class="card text-center border-warning border-opacity-25">
|
||||||
<div class="card-body py-3">
|
<div class="card-body py-2">
|
||||||
<div class="fs-2 fw-bold text-warning" id="statMessages">0</div>
|
<div class="fs-4 fw-bold text-warning" id="statMessages">0</div>
|
||||||
<div class="text-body-secondary small">Nachrichten</div>
|
<div class="text-body-secondary small">Nachrichten</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-6 col-md-3">
|
<div class="col-6 col-md-3">
|
||||||
<div class="card text-center border-primary border-opacity-25">
|
<div class="card text-center border-primary border-opacity-25">
|
||||||
<div class="card-body py-3">
|
<div class="card-body py-2">
|
||||||
<div class="fs-2 fw-bold text-primary" id="statTextMessages">0</div>
|
<div class="fs-4 fw-bold text-primary" id="statTextMessages">0</div>
|
||||||
<div class="text-body-secondary small">Textnachrichten</div>
|
<div class="text-body-secondary small">Textnachrichten</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -85,13 +85,12 @@ function renderNodes() {
|
||||||
|
|
||||||
function renderBattery(level) {
|
function renderBattery(level) {
|
||||||
if (level == null) return '<span class="text-body-secondary">-</span>';
|
if (level == null) return '<span class="text-body-secondary">-</span>';
|
||||||
let colorClass = 'bg-success';
|
let iconClass, colorClass;
|
||||||
if (level < 20) colorClass = 'bg-danger';
|
if (level > 75) { iconClass = 'bi-battery-full'; colorClass = 'text-success'; }
|
||||||
else if (level < 50) colorClass = 'bg-warning';
|
else if (level > 50) { iconClass = 'bi-battery-half'; colorClass = 'text-success'; }
|
||||||
return `<span class="battery-bar">
|
else if (level > 20) { iconClass = 'bi-battery-half'; colorClass = 'text-warning'; }
|
||||||
<span class="battery-indicator"><span class="battery-fill ${colorClass}" style="width:${Math.min(level, 100)}%"></span></span>
|
else { iconClass = 'bi-battery'; colorClass = 'text-danger'; }
|
||||||
<small>${level}%</small>
|
return `<span class="${colorClass}"><i class="bi ${iconClass} me-1"></i>${level}%</span>`;
|
||||||
</span>`;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function addMessage(msg) {
|
function addMessage(msg) {
|
||||||
|
|
@ -115,6 +114,9 @@ function addMessage(msg) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateStats(stats) {
|
function updateStats(stats) {
|
||||||
|
if (stats.version) {
|
||||||
|
document.getElementById('versionLabel').textContent = `v${stats.version}`;
|
||||||
|
}
|
||||||
document.getElementById('statNodes').textContent = stats.total_nodes || 0;
|
document.getElementById('statNodes').textContent = stats.total_nodes || 0;
|
||||||
document.getElementById('statPositions').textContent = stats.nodes_with_position || 0;
|
document.getElementById('statPositions').textContent = stats.nodes_with_position || 0;
|
||||||
document.getElementById('statMessages').textContent = stats.total_messages || 0;
|
document.getElementById('statMessages').textContent = stats.total_messages || 0;
|
||||||
|
|
|
||||||
|
|
@ -82,7 +82,8 @@ function updateMarker(node) {
|
||||||
function fitBounds() {
|
function fitBounds() {
|
||||||
const coords = Object.values(markers).map(m => m.getLatLng());
|
const coords = Object.values(markers).map(m => m.getLatLng());
|
||||||
if (coords.length > 0) {
|
if (coords.length > 0) {
|
||||||
map.fitBounds(L.latLngBounds(coords).pad(0.1));
|
map.invalidateSize();
|
||||||
|
map.fitBounds(L.latLngBounds(coords).pad(0.1), { maxZoom: 14 });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue