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
|
# 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
|
## [0.6.12] - 2026-02-18
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
version: "0.6.12"
|
version: "0.6.13"
|
||||||
|
|
||||||
bot:
|
bot:
|
||||||
name: "MeshDD-Bot"
|
name: "MeshDD-Bot"
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
import aiosqlite
|
import aiosqlite
|
||||||
import time
|
import time
|
||||||
import logging
|
import logging
|
||||||
from datetime import datetime
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
@ -208,14 +207,13 @@ class Database:
|
||||||
"SELECT COUNT(*) FROM nodes WHERE last_seen >= ?", (day_ago,)
|
"SELECT COUNT(*) FROM nodes WHERE last_seen >= ?", (day_ago,)
|
||||||
) as c:
|
) as c:
|
||||||
stats["nodes_24h"] = (await c.fetchone())[0]
|
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(
|
async with self.db.execute(
|
||||||
"SELECT COUNT(*) FROM commands WHERE timestamp >= ?", (today_start,)
|
"SELECT COUNT(*) FROM commands WHERE timestamp >= ?", (day_ago,)
|
||||||
) as c:
|
) as c:
|
||||||
stats["total_commands"] = (await c.fetchone())[0]
|
stats["total_commands"] = (await c.fetchone())[0]
|
||||||
async with self.db.execute(
|
async with self.db.execute(
|
||||||
"SELECT channel, COUNT(*) as cnt FROM commands WHERE timestamp >= ? GROUP BY channel ORDER BY cnt DESC",
|
"SELECT channel, COUNT(*) as cnt FROM commands WHERE timestamp >= ? GROUP BY channel ORDER BY cnt DESC",
|
||||||
(today_start,),
|
(day_ago,),
|
||||||
) as cursor:
|
) as cursor:
|
||||||
stats["channel_breakdown"] = {row[0]: row[1] async for row in cursor}
|
stats["channel_breakdown"] = {row[0]: row[1] async for row in cursor}
|
||||||
return stats
|
return stats
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@
|
||||||
</button>
|
</button>
|
||||||
<span class="fw-bold me-auto">
|
<span class="fw-bold me-auto">
|
||||||
<i class="bi bi-broadcast-pin text-info me-1"></i>MeshDD-Bot
|
<i class="bi bi-broadcast-pin text-info me-1"></i>MeshDD-Bot
|
||||||
|
<small class="text-body-secondary fw-normal" id="versionLabel"></small>
|
||||||
</span>
|
</span>
|
||||||
<div class="d-flex align-items-center gap-2">
|
<div class="d-flex align-items-center gap-2">
|
||||||
<span id="userMenu" class="d-none">
|
<span id="userMenu" class="d-none">
|
||||||
|
|
|
||||||
|
|
@ -102,4 +102,10 @@ function initPage({ onAuth = null } = {}) {
|
||||||
_setupSidebarToggle();
|
_setupSidebarToggle();
|
||||||
if (onAuth) onAuth(user);
|
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({
|
return L.divIcon({
|
||||||
className: '',
|
className: '',
|
||||||
html: `<svg width="24" height="24" viewBox="0 0 24 24">
|
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>`,
|
</svg>`,
|
||||||
iconSize: [24, 24],
|
iconSize: [24, 24],
|
||||||
iconAnchor: [12, 12],
|
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) {
|
function nodeTooltip(node) {
|
||||||
const name = node.long_name || node.short_name || node.node_id;
|
const name = node.long_name || node.short_name || node.node_id;
|
||||||
const hops = node.hop_count != null ? node.hop_count : '?';
|
const hops = node.hop_count != null ? node.hop_count : '?';
|
||||||
|
|
@ -61,15 +71,28 @@ function updateMarker(node) {
|
||||||
if (!node.lat || !node.lon) return;
|
if (!node.lat || !node.lon) return;
|
||||||
|
|
||||||
const id = node.node_id;
|
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;
|
nodeData[id] = node;
|
||||||
const icon = createIcon(getHopColor(node.hop_count));
|
const icon = createIcon(getHopColor(node.hop_count));
|
||||||
|
|
||||||
if (markers[id]) {
|
if (markers[id]) {
|
||||||
markers[id].setLatLng([node.lat, node.lon]);
|
markers[id].setLatLng([node.lat, node.lon]);
|
||||||
markers[id].setIcon(icon);
|
markers[id].setIcon(icon);
|
||||||
|
markers[id].setOpacity(opacity);
|
||||||
markers[id].setTooltipContent(nodeTooltip(node));
|
markers[id].setTooltipContent(nodeTooltip(node));
|
||||||
} else {
|
} else {
|
||||||
markers[id] = L.marker([node.lat, node.lon], { icon })
|
markers[id] = L.marker([node.lat, node.lon], { icon, opacity })
|
||||||
.addTo(map)
|
.addTo(map)
|
||||||
.bindTooltip(nodeTooltip(node), { direction: 'right', className: 'node-tooltip-wrap' });
|
.bindTooltip(nodeTooltip(node), { direction: 'right', className: 'node-tooltip-wrap' });
|
||||||
}
|
}
|
||||||
|
|
@ -134,6 +157,10 @@ legend.onAdd = function () {
|
||||||
const color = hop != null ? (hopColors[hop] || hopColorDefault) : hopColorDefault;
|
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 += `<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;
|
return div;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@
|
||||||
</button>
|
</button>
|
||||||
<span class="fw-bold me-auto">
|
<span class="fw-bold me-auto">
|
||||||
<i class="bi bi-broadcast-pin text-info me-1"></i>MeshDD-Bot
|
<i class="bi bi-broadcast-pin text-info me-1"></i>MeshDD-Bot
|
||||||
|
<small class="text-body-secondary fw-normal" id="versionLabel"></small>
|
||||||
</span>
|
</span>
|
||||||
<div class="d-flex align-items-center gap-2">
|
<div class="d-flex align-items-center gap-2">
|
||||||
<span class="d-flex align-items-center gap-1">
|
<span class="d-flex align-items-center gap-1">
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@
|
||||||
</button>
|
</button>
|
||||||
<span class="fw-bold me-auto">
|
<span class="fw-bold me-auto">
|
||||||
<i class="bi bi-broadcast-pin text-info me-1"></i>MeshDD-Bot
|
<i class="bi bi-broadcast-pin text-info me-1"></i>MeshDD-Bot
|
||||||
|
<small class="text-body-secondary fw-normal" id="versionLabel"></small>
|
||||||
</span>
|
</span>
|
||||||
<div class="d-flex align-items-center gap-2">
|
<div class="d-flex align-items-center gap-2">
|
||||||
<span class="d-flex align-items-center gap-1">
|
<span class="d-flex align-items-center gap-1">
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@
|
||||||
</button>
|
</button>
|
||||||
<span class="fw-bold me-auto">
|
<span class="fw-bold me-auto">
|
||||||
<i class="bi bi-broadcast-pin text-info me-1"></i>MeshDD-Bot
|
<i class="bi bi-broadcast-pin text-info me-1"></i>MeshDD-Bot
|
||||||
|
<small class="text-body-secondary fw-normal" id="versionLabel"></small>
|
||||||
</span>
|
</span>
|
||||||
<div class="d-flex align-items-center gap-2">
|
<div class="d-flex align-items-center gap-2">
|
||||||
<span id="userMenu" class="d-none">
|
<span id="userMenu" class="d-none">
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@
|
||||||
</button>
|
</button>
|
||||||
<span class="fw-bold me-auto">
|
<span class="fw-bold me-auto">
|
||||||
<i class="bi bi-broadcast-pin text-info me-1"></i>MeshDD-Bot
|
<i class="bi bi-broadcast-pin text-info me-1"></i>MeshDD-Bot
|
||||||
|
<small class="text-body-secondary fw-normal" id="versionLabel"></small>
|
||||||
</span>
|
</span>
|
||||||
<div class="d-flex align-items-center gap-2">
|
<div class="d-flex align-items-center gap-2">
|
||||||
<span id="userMenu" class="d-none">
|
<span id="userMenu" class="d-none">
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue