mirror of
https://github.com/Kpa-clawbot/meshcore-analyzer.git
synced 2026-07-16 22:01:56 +00:00
Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
86127d5021 |
@@ -876,6 +876,26 @@
|
||||
</div>`;
|
||||
}).join('')}
|
||||
</div>
|
||||
${data.distributionByRepeaters ? (() => {
|
||||
const dr = data.distributionByRepeaters;
|
||||
const totalRepeaters = (dr[1] || 0) + (dr[2] || 0) + (dr[3] || 0);
|
||||
const rpct = (n) => totalRepeaters ? (n / totalRepeaters * 100).toFixed(1) : '0';
|
||||
const maxRepeaters = Math.max(dr[1] || 0, dr[2] || 0, dr[3] || 0, 1);
|
||||
const colors = { 1: '#ef4444', 2: '#22c55e', 3: '#3b82f6' };
|
||||
return `<h4 style="margin:16px 0 4px">By Repeaters</h4>
|
||||
<p class="text-muted">${totalRepeaters.toLocaleString()} unique repeaters</p>
|
||||
<div class="hash-bars">
|
||||
${[1, 2, 3].map(size => {
|
||||
const count = dr[size] || 0;
|
||||
const width = Math.max((count / maxRepeaters) * 100, count ? 2 : 0);
|
||||
return `<div class="hash-bar-row">
|
||||
<div class="hash-bar-label"><strong>${size}-byte</strong></div>
|
||||
<div class="hash-bar-track"><div class="hash-bar-fill" style="width:${width}%;background:${colors[size]};opacity:0.7"></div></div>
|
||||
<div class="hash-bar-value">${count.toLocaleString()} <span class="text-muted">(${rpct(count)}%)</span></div>
|
||||
</div>`;
|
||||
}).join('')}
|
||||
</div>`;
|
||||
})() : ''}
|
||||
</div>
|
||||
<div class="analytics-card flex-1">
|
||||
<h3>📈 Hash Size Over Time</h3>
|
||||
|
||||
+27
-27
@@ -22,9 +22,9 @@
|
||||
<meta name="twitter:title" content="CoreScope">
|
||||
<meta name="twitter:description" content="Real-time MeshCore LoRa mesh network analyzer — live packet visualization, node tracking, channel decryption, and route analysis.">
|
||||
<meta name="twitter:image" content="https://raw.githubusercontent.com/Kpa-clawbot/corescope/master/public/og-image.png">
|
||||
<link rel="stylesheet" href="style.css?v=1774786038">
|
||||
<link rel="stylesheet" href="home.css?v=1774786038">
|
||||
<link rel="stylesheet" href="live.css?v=1774786038">
|
||||
<link rel="stylesheet" href="style.css?v=1774821783">
|
||||
<link rel="stylesheet" href="home.css?v=1774821783">
|
||||
<link rel="stylesheet" href="live.css?v=1774821783">
|
||||
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css"
|
||||
integrity="sha256-p4NxAoJBhIIN+hmNHrzRCf9tD/miZyoHS5obTRR9BMY="
|
||||
crossorigin="anonymous">
|
||||
@@ -81,29 +81,29 @@
|
||||
<main id="app" role="main"></main>
|
||||
|
||||
<script src="vendor/qrcode.js"></script>
|
||||
<script src="roles.js?v=1774786038"></script>
|
||||
<script src="customize.js?v=1774786038" onerror="console.error('Failed to load:', this.src)"></script>
|
||||
<script src="region-filter.js?v=1774786038"></script>
|
||||
<script src="hop-resolver.js?v=1774786038"></script>
|
||||
<script src="hop-display.js?v=1774786038"></script>
|
||||
<script src="app.js?v=1774786038"></script>
|
||||
<script src="home.js?v=1774786038"></script>
|
||||
<script src="packet-filter.js?v=1774786038"></script>
|
||||
<script src="packets.js?v=1774786038"></script>
|
||||
<script src="map.js?v=1774786038" onerror="console.error('Failed to load:', this.src)"></script>
|
||||
<script src="channels.js?v=1774786038" onerror="console.error('Failed to load:', this.src)"></script>
|
||||
<script src="nodes.js?v=1774786038" onerror="console.error('Failed to load:', this.src)"></script>
|
||||
<script src="traces.js?v=1774786038" onerror="console.error('Failed to load:', this.src)"></script>
|
||||
<script src="analytics.js?v=1774786038" onerror="console.error('Failed to load:', this.src)"></script>
|
||||
<script src="audio.js?v=1774786038" onerror="console.error('Failed to load:', this.src)"></script>
|
||||
<script src="audio-v1-constellation.js?v=1774786038" onerror="console.error('Failed to load:', this.src)"></script>
|
||||
<script src="audio-v2-constellation.js?v=1774786038" onerror="console.error('Failed to load:', this.src)"></script>
|
||||
<script src="audio-lab.js?v=1774786038" onerror="console.error('Failed to load:', this.src)"></script>
|
||||
<script src="live.js?v=1774786038" onerror="console.error('Failed to load:', this.src)"></script>
|
||||
<script src="observers.js?v=1774786038" onerror="console.error('Failed to load:', this.src)"></script>
|
||||
<script src="observer-detail.js?v=1774786038" onerror="console.error('Failed to load:', this.src)"></script>
|
||||
<script src="compare.js?v=1774786038" onerror="console.error('Failed to load:', this.src)"></script>
|
||||
<script src="node-analytics.js?v=1774786038" onerror="console.error('Failed to load:', this.src)"></script>
|
||||
<script src="perf.js?v=1774786038" onerror="console.error('Failed to load:', this.src)"></script>
|
||||
<script src="roles.js?v=1774821783"></script>
|
||||
<script src="customize.js?v=1774821783" onerror="console.error('Failed to load:', this.src)"></script>
|
||||
<script src="region-filter.js?v=1774821783"></script>
|
||||
<script src="hop-resolver.js?v=1774821783"></script>
|
||||
<script src="hop-display.js?v=1774821783"></script>
|
||||
<script src="app.js?v=1774821783"></script>
|
||||
<script src="home.js?v=1774821783"></script>
|
||||
<script src="packet-filter.js?v=1774821783"></script>
|
||||
<script src="packets.js?v=1774821783"></script>
|
||||
<script src="map.js?v=1774821783" onerror="console.error('Failed to load:', this.src)"></script>
|
||||
<script src="channels.js?v=1774821783" onerror="console.error('Failed to load:', this.src)"></script>
|
||||
<script src="nodes.js?v=1774821783" onerror="console.error('Failed to load:', this.src)"></script>
|
||||
<script src="traces.js?v=1774821783" onerror="console.error('Failed to load:', this.src)"></script>
|
||||
<script src="analytics.js?v=1774821783" onerror="console.error('Failed to load:', this.src)"></script>
|
||||
<script src="audio.js?v=1774821783" onerror="console.error('Failed to load:', this.src)"></script>
|
||||
<script src="audio-v1-constellation.js?v=1774821783" onerror="console.error('Failed to load:', this.src)"></script>
|
||||
<script src="audio-v2-constellation.js?v=1774821783" onerror="console.error('Failed to load:', this.src)"></script>
|
||||
<script src="audio-lab.js?v=1774821783" onerror="console.error('Failed to load:', this.src)"></script>
|
||||
<script src="live.js?v=1774821783" onerror="console.error('Failed to load:', this.src)"></script>
|
||||
<script src="observers.js?v=1774821783" onerror="console.error('Failed to load:', this.src)"></script>
|
||||
<script src="observer-detail.js?v=1774821783" onerror="console.error('Failed to load:', this.src)"></script>
|
||||
<script src="compare.js?v=1774821783" onerror="console.error('Failed to load:', this.src)"></script>
|
||||
<script src="node-analytics.js?v=1774821783" onerror="console.error('Failed to load:', this.src)"></script>
|
||||
<script src="perf.js?v=1774821783" onerror="console.error('Failed to load:', this.src)"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -1987,9 +1987,17 @@ app.get('/api/analytics/hash-sizes', (req, res) => {
|
||||
.sort(([, a], [, b]) => b.packets - a.packets)
|
||||
.map(([name, data]) => ({ name, ...data }));
|
||||
|
||||
// Distribution by number of repeaters advertising each hash size
|
||||
const distributionByRepeaters = { 1: 0, 2: 0, 3: 0 };
|
||||
for (const [, v] of Object.entries(byNode)) {
|
||||
const s = v.hashSize;
|
||||
if (s >= 1 && s <= 3) distributionByRepeaters[s]++;
|
||||
}
|
||||
|
||||
const _hsResult = {
|
||||
total: packets.length,
|
||||
distribution,
|
||||
distributionByRepeaters,
|
||||
hourly,
|
||||
topHops,
|
||||
multiByteNodes
|
||||
|
||||
@@ -583,6 +583,11 @@ seedTestData();
|
||||
await t('GET /api/analytics/hash-sizes', async () => {
|
||||
const r = await request(app).get('/api/analytics/hash-sizes').expect(200);
|
||||
assert(typeof r.body === 'object', 'should return hash sizes');
|
||||
assert(r.body.distributionByRepeaters, 'should include distributionByRepeaters');
|
||||
assert(typeof r.body.distributionByRepeaters === 'object', 'distributionByRepeaters should be an object');
|
||||
for (const s of [1, 2, 3]) {
|
||||
assert(typeof r.body.distributionByRepeaters[s] === 'number', `distributionByRepeaters[${s}] should be a number`);
|
||||
}
|
||||
});
|
||||
|
||||
await t('GET /api/analytics/hash-sizes with region', async () => {
|
||||
|
||||
Reference in New Issue
Block a user