feat: v0.6.13 - Version in Navbar, Rolling 24h, Karten-Transparenz
- Version in Navbar aller Seiten (app.js holt /api/stats beim Init) - Statistiken: Anfragen-Zähler rolling 24h statt Mitternacht-Reset - Karte: Nodes nach Alter transparent (<24h voll, 24-48h 45%, 48-72h 20%, >72h unsichtbar) - Legende um Alter-Sektion erweitert Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
ac98191143
commit
9306cce209
12
CHANGELOG.md
12
CHANGELOG.md
|
|
@ -1,5 +1,17 @@
|
|||
# Changelog
|
||||
|
||||
## [0.6.13] - 2026-02-18
|
||||
|
||||
### Added
|
||||
- **Version in Navbar** aller Seiten sichtbar (via `app.js initPage()` + `/api/stats`).
|
||||
- **Karte Alter-Transparenz**: Nodes < 24h voll sichtbar (0.9), 24–48h halb transparent (0.45),
|
||||
48–72h stark transparent (0.2), älter als 72h werden nicht mehr angezeigt.
|
||||
Legende um Alter-Sektion erweitert.
|
||||
|
||||
### Changed
|
||||
- **Statistiken Rolling Window**: Anfragen-Zähler und Kanal-Breakdown nutzen jetzt
|
||||
rollendes 24h-Fenster (jetzt minus 24h) statt Mitternacht-Reset.
|
||||
|
||||
## [0.6.12] - 2026-02-18
|
||||
|
||||
### Fixed
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
version: "0.6.12"
|
||||
version: "0.6.13"
|
||||
|
||||
bot:
|
||||
name: "MeshDD-Bot"
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
import aiosqlite
|
||||
import time
|
||||
import logging
|
||||
from datetime import datetime
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
|
@ -208,14 +207,13 @@ class Database:
|
|||
"SELECT COUNT(*) FROM nodes WHERE last_seen >= ?", (day_ago,)
|
||||
) as c:
|
||||
stats["nodes_24h"] = (await c.fetchone())[0]
|
||||
today_start = datetime.now().replace(hour=0, minute=0, second=0, microsecond=0).timestamp()
|
||||
async with self.db.execute(
|
||||
"SELECT COUNT(*) FROM commands WHERE timestamp >= ?", (today_start,)
|
||||
"SELECT COUNT(*) FROM commands WHERE timestamp >= ?", (day_ago,)
|
||||
) as c:
|
||||
stats["total_commands"] = (await c.fetchone())[0]
|
||||
async with self.db.execute(
|
||||
"SELECT channel, COUNT(*) as cnt FROM commands WHERE timestamp >= ? GROUP BY channel ORDER BY cnt DESC",
|
||||
(today_start,),
|
||||
(day_ago,),
|
||||
) as cursor:
|
||||
stats["channel_breakdown"] = {row[0]: row[1] async for row in cursor}
|
||||
return stats
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@
|
|||
</button>
|
||||
<span class="fw-bold me-auto">
|
||||
<i class="bi bi-broadcast-pin text-info me-1"></i>MeshDD-Bot
|
||||
<small class="text-body-secondary fw-normal" id="versionLabel"></small>
|
||||
</span>
|
||||
<div class="d-flex align-items-center gap-2">
|
||||
<span id="userMenu" class="d-none">
|
||||
|
|
|
|||
|
|
@ -102,4 +102,10 @@ function initPage({ onAuth = null } = {}) {
|
|||
_setupSidebarToggle();
|
||||
if (onAuth) onAuth(user);
|
||||
});
|
||||
const vl = document.getElementById('versionLabel');
|
||||
if (vl) {
|
||||
fetch('/api/stats')
|
||||
.then(r => r.ok ? r.json() : null)
|
||||
.then(d => { if (d?.version) vl.textContent = `v${d.version}`; });
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ function createIcon(color) {
|
|||
return L.divIcon({
|
||||
className: '',
|
||||
html: `<svg width="24" height="24" viewBox="0 0 24 24">
|
||||
<circle cx="12" cy="12" r="8" fill="${color}" stroke="#fff" stroke-width="2" opacity="0.9"/>
|
||||
<circle cx="12" cy="12" r="8" fill="${color}" stroke="#fff" stroke-width="2"/>
|
||||
</svg>`,
|
||||
iconSize: [24, 24],
|
||||
iconAnchor: [12, 12],
|
||||
|
|
@ -37,6 +37,16 @@ function createIcon(color) {
|
|||
});
|
||||
}
|
||||
|
||||
// Returns opacity based on node age: full <24h, half 24-48h, faint 48-72h, null >72h (hide)
|
||||
function getAgeOpacity(lastSeen) {
|
||||
if (!lastSeen) return 0.9;
|
||||
const age = Date.now() / 1000 - lastSeen;
|
||||
if (age < 86400) return 0.9;
|
||||
if (age < 172800) return 0.45;
|
||||
if (age < 259200) return 0.2;
|
||||
return null;
|
||||
}
|
||||
|
||||
function nodeTooltip(node) {
|
||||
const name = node.long_name || node.short_name || node.node_id;
|
||||
const hops = node.hop_count != null ? node.hop_count : '?';
|
||||
|
|
@ -61,15 +71,28 @@ function updateMarker(node) {
|
|||
if (!node.lat || !node.lon) return;
|
||||
|
||||
const id = node.node_id;
|
||||
const opacity = getAgeOpacity(node.last_seen);
|
||||
|
||||
// Node older than 72h: remove from map
|
||||
if (opacity === null) {
|
||||
if (markers[id]) {
|
||||
map.removeLayer(markers[id]);
|
||||
delete markers[id];
|
||||
delete nodeData[id];
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
nodeData[id] = node;
|
||||
const icon = createIcon(getHopColor(node.hop_count));
|
||||
|
||||
if (markers[id]) {
|
||||
markers[id].setLatLng([node.lat, node.lon]);
|
||||
markers[id].setIcon(icon);
|
||||
markers[id].setOpacity(opacity);
|
||||
markers[id].setTooltipContent(nodeTooltip(node));
|
||||
} else {
|
||||
markers[id] = L.marker([node.lat, node.lon], { icon })
|
||||
markers[id] = L.marker([node.lat, node.lon], { icon, opacity })
|
||||
.addTo(map)
|
||||
.bindTooltip(nodeTooltip(node), { direction: 'right', className: 'node-tooltip-wrap' });
|
||||
}
|
||||
|
|
@ -134,6 +157,10 @@ legend.onAdd = function () {
|
|||
const color = hop != null ? (hopColors[hop] || hopColorDefault) : hopColorDefault;
|
||||
div.innerHTML += `<div class="legend-item"><span class="legend-dot" style="background:${color}"></span>${label}</div>`;
|
||||
});
|
||||
div.innerHTML += `<hr style="margin:5px 0;border-color:#aaa"><strong>Alter</strong>
|
||||
<div class="legend-item"><span class="legend-dot" style="background:rgba(128,128,128,.9)"></span>< 24h</div>
|
||||
<div class="legend-item"><span class="legend-dot" style="background:rgba(128,128,128,.45)"></span>24–48h</div>
|
||||
<div class="legend-item"><span class="legend-dot" style="background:rgba(128,128,128,.2)"></span>48–72h</div>`;
|
||||
return div;
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@
|
|||
</button>
|
||||
<span class="fw-bold me-auto">
|
||||
<i class="bi bi-broadcast-pin text-info me-1"></i>MeshDD-Bot
|
||||
<small class="text-body-secondary fw-normal" id="versionLabel"></small>
|
||||
</span>
|
||||
<div class="d-flex align-items-center gap-2">
|
||||
<span class="d-flex align-items-center gap-1">
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@
|
|||
</button>
|
||||
<span class="fw-bold me-auto">
|
||||
<i class="bi bi-broadcast-pin text-info me-1"></i>MeshDD-Bot
|
||||
<small class="text-body-secondary fw-normal" id="versionLabel"></small>
|
||||
</span>
|
||||
<div class="d-flex align-items-center gap-2">
|
||||
<span class="d-flex align-items-center gap-1">
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@
|
|||
</button>
|
||||
<span class="fw-bold me-auto">
|
||||
<i class="bi bi-broadcast-pin text-info me-1"></i>MeshDD-Bot
|
||||
<small class="text-body-secondary fw-normal" id="versionLabel"></small>
|
||||
</span>
|
||||
<div class="d-flex align-items-center gap-2">
|
||||
<span id="userMenu" class="d-none">
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@
|
|||
</button>
|
||||
<span class="fw-bold me-auto">
|
||||
<i class="bi bi-broadcast-pin text-info me-1"></i>MeshDD-Bot
|
||||
<small class="text-body-secondary fw-normal" id="versionLabel"></small>
|
||||
</span>
|
||||
<div class="d-flex align-items-center gap-2">
|
||||
<span id="userMenu" class="d-none">
|
||||
|
|
|
|||
Loading…
Reference in a new issue