mirror of
https://github.com/Kpa-clawbot/meshcore-analyzer.git
synced 2026-09-27 14:28:07 +00:00
revert: remove View Path bridge-repeater highlighting
The "relays for 2+ distinct region scopes" definition doesn't hold up in practice: most nodes end up carrying both a broad national/regional scope (e.g. #dk, #eu) and several local ones, so the vast majority of repeaters would eventually qualify as "bridge" -- the flag stops meaning anything useful. Removes IsBridge from PacketPathPoint/PacketPathObserver, the markBridgeRepeaters handler step, the purple-outline styling, and the dedicated test file -- keeps PublicKey on PacketPathObserver, since the click-to-node-detail feature depends on it independently. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
6718c246b9
commit
4b4dc9f27f
+2
-12
@@ -1540,13 +1540,6 @@ type PacketPathPoint struct {
|
||||
// those neighbors -- 0 (and omitted) with a single contributor;
|
||||
// larger means the neighbors disagree more about where "nearby" is.
|
||||
ApproxSpreadKm *float64 `json:"approxSpreadKm,omitempty"`
|
||||
// IsBridge is true when this node has been confirmed relaying
|
||||
// traffic for 2+ distinct region scopes -- the same "Bridge" badge
|
||||
// definition as the Foreign Traffic tab (ScopeStatsResponse.
|
||||
// bridgeRepeaters). Set by the handler (routes.go), not GetPacketPath
|
||||
// itself, since the underlying data lives in the in-memory store, not
|
||||
// SQL. Absent/false when the store isn't available.
|
||||
IsBridge bool `json:"isBridge,omitempty"`
|
||||
}
|
||||
|
||||
// PacketPathObserver is the station that produced a given branch's
|
||||
@@ -1558,9 +1551,8 @@ type PacketPathPoint struct {
|
||||
type PacketPathObserver struct {
|
||||
// PublicKey is the observer's mesh pubkey (observers.id for v3,
|
||||
// observer_id for legacy), when it has one -- lets callers link out
|
||||
// to the node detail page or cross-reference other per-node data
|
||||
// (e.g. IsBridge, filled in by the handler). Empty for an observer
|
||||
// whose id never resembled a pubkey.
|
||||
// to the node detail page. Empty for an observer whose id never
|
||||
// resembled a pubkey.
|
||||
PublicKey string `json:"publicKey,omitempty"`
|
||||
Name string `json:"name"`
|
||||
IATA string `json:"iata,omitempty"`
|
||||
@@ -1573,8 +1565,6 @@ type PacketPathObserver struct {
|
||||
Approx bool `json:"approx,omitempty"`
|
||||
ApproxNeighborCount int `json:"approxNeighborCount,omitempty"`
|
||||
ApproxSpreadKm *float64 `json:"approxSpreadKm,omitempty"`
|
||||
// IsBridge -- see PacketPathPoint.IsBridge.
|
||||
IsBridge bool `json:"isBridge,omitempty"`
|
||||
}
|
||||
|
||||
// PacketPathBranch is one station's route to a packet: how far it
|
||||
|
||||
@@ -345,7 +345,6 @@ func componentSchemas() map[string]interface{} {
|
||||
"approx": map[string]interface{}{"type": "boolean", "description": "True when lat/lon are not this node's own position but a count-weighted centroid of its positioned neighbor_edges neighbors instead -- a last-resort stand-in, not a real fix."},
|
||||
"approxNeighborCount": map[string]interface{}{"type": "integer", "description": "Present only when approx=true. How many positioned neighbors fed the centroid -- a rough confidence signal, higher is more confident."},
|
||||
"approxSpreadKm": map[string]interface{}{"type": "number", "nullable": true, "description": "Present only when approx=true and approxNeighborCount>1. Widest distance (km) between any two contributing neighbors -- larger means they disagree more about where 'nearby' is."},
|
||||
"isBridge": map[string]interface{}{"type": "boolean", "description": "True when this node has been confirmed relaying traffic for 2+ distinct region scopes -- same definition/data source as the Foreign Traffic tab's Bridge badge. Set by the handler from the in-memory store; always false when that store isn't available."},
|
||||
},
|
||||
},
|
||||
"PacketPathObserver": map[string]interface{}{
|
||||
@@ -361,7 +360,6 @@ func componentSchemas() map[string]interface{} {
|
||||
"approx": map[string]interface{}{"type": "boolean", "description": "True when lat/lon are not this station's own position but a count-weighted centroid of its positioned neighbors instead -- a last-resort stand-in, not a real fix."},
|
||||
"approxNeighborCount": map[string]interface{}{"type": "integer", "description": "Present only when approx=true. See PacketPathPoint.approxNeighborCount."},
|
||||
"approxSpreadKm": map[string]interface{}{"type": "number", "nullable": true, "description": "Present only when approx=true and approxNeighborCount>1. See PacketPathPoint.approxSpreadKm."},
|
||||
"isBridge": map[string]interface{}{"type": "boolean", "description": "See PacketPathPoint.isBridge."},
|
||||
},
|
||||
},
|
||||
"PacketPathBranch": map[string]interface{}{
|
||||
|
||||
@@ -1,76 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
// TestMarkBridgeRepeaters_NilStore covers the graceful no-op when the
|
||||
// in-memory store isn't available (s.store can legitimately be nil --
|
||||
// see handlePacketPath). Must not panic and must leave every IsBridge
|
||||
// at its zero value (false).
|
||||
func TestMarkBridgeRepeaters_NilStore(t *testing.T) {
|
||||
resp := &PacketPathResponse{
|
||||
Hash: "deadbeef",
|
||||
Branches: []PacketPathBranch{
|
||||
{Points: []PacketPathPoint{{PublicKey: "pk1"}}, Observer: &PacketPathObserver{PublicKey: "obs1"}},
|
||||
},
|
||||
}
|
||||
markBridgeRepeaters(resp, nil, &Config{})
|
||||
if resp.Branches[0].Points[0].IsBridge {
|
||||
t.Errorf("Points[0].IsBridge = true, want false -- store is nil, nothing should be marked")
|
||||
}
|
||||
if resp.Branches[0].Observer.IsBridge {
|
||||
t.Errorf("Observer.IsBridge = true, want false -- store is nil, nothing should be marked")
|
||||
}
|
||||
}
|
||||
|
||||
// TestMarkBridgeRepeaters_TwoScopesIsBridge covers the actual
|
||||
// determination: a pubkey relaying 2+ distinct region scopes (the same
|
||||
// definition/data source as the Foreign Traffic tab's "Bridge" badge,
|
||||
// ScopeStatsResponse.bridgeRepeaters) gets IsBridge=true on every point
|
||||
// and observer entry referencing it; a pubkey with 0 or 1 scope, or one
|
||||
// missing from the relay map entirely, stays false.
|
||||
func TestMarkBridgeRepeaters_TwoScopesIsBridge(t *testing.T) {
|
||||
store := &PacketStore{
|
||||
repeaterRelayCache: map[string]RepeaterRelayInfo{
|
||||
"bridgepk": {TransportedScopes: []string{"#dk", "#se"}},
|
||||
"regionalpk": {TransportedScopes: []string{"#dk"}},
|
||||
"unknownscopepk": {TransportedScopes: nil},
|
||||
},
|
||||
repeaterRelayCacheWin: 24,
|
||||
repeaterRelayAt: time.Now(),
|
||||
}
|
||||
resp := &PacketPathResponse{
|
||||
Hash: "deadbeef",
|
||||
Branches: []PacketPathBranch{
|
||||
{
|
||||
Points: []PacketPathPoint{
|
||||
{PublicKey: "bridgepk"},
|
||||
{PublicKey: "regionalpk"},
|
||||
{PublicKey: "unknownscopepk"},
|
||||
{PublicKey: "notinrelaymap"},
|
||||
},
|
||||
Observer: &PacketPathObserver{PublicKey: "BridgePK"}, // case-insensitive match
|
||||
},
|
||||
},
|
||||
}
|
||||
markBridgeRepeaters(resp, store, &Config{})
|
||||
|
||||
pts := resp.Branches[0].Points
|
||||
if !pts[0].IsBridge {
|
||||
t.Errorf("Points[0] (bridgepk, 2 scopes) IsBridge = false, want true")
|
||||
}
|
||||
if pts[1].IsBridge {
|
||||
t.Errorf("Points[1] (regionalpk, 1 scope) IsBridge = true, want false")
|
||||
}
|
||||
if pts[2].IsBridge {
|
||||
t.Errorf("Points[2] (unknownscopepk, 0 scopes) IsBridge = true, want false")
|
||||
}
|
||||
if pts[3].IsBridge {
|
||||
t.Errorf("Points[3] (notinrelaymap, absent from relay map) IsBridge = true, want false")
|
||||
}
|
||||
if !resp.Branches[0].Observer.IsBridge {
|
||||
t.Errorf("Observer (BridgePK, case-insensitive match on bridgepk) IsBridge = false, want true")
|
||||
}
|
||||
}
|
||||
@@ -3164,46 +3164,9 @@ func (s *Server) handlePacketPath(w http.ResponseWriter, r *http.Request) {
|
||||
writeError(w, 500, err.Error())
|
||||
return
|
||||
}
|
||||
markBridgeRepeaters(resp, s.store, s.cfg)
|
||||
writeJSON(w, resp)
|
||||
}
|
||||
|
||||
// markBridgeRepeaters sets IsBridge on every point/observer in resp whose
|
||||
// pubkey has been confirmed relaying traffic for 2+ distinct region
|
||||
// scopes -- the same "Bridge" definition/data source as the Foreign
|
||||
// Traffic tab's badge (ScopeStatsResponse.bridgeRepeaters), just applied
|
||||
// to whichever handful of pubkeys this one packet's path touches instead
|
||||
// of scanning the whole mesh. No-op when the in-memory store isn't
|
||||
// available (IsBridge stays false/omitted on every entry).
|
||||
func markBridgeRepeaters(resp *PacketPathResponse, store *PacketStore, cfg *Config) {
|
||||
if resp == nil || store == nil || cfg == nil {
|
||||
return
|
||||
}
|
||||
relayMap := store.GetRepeaterRelayInfoMap(cfg.GetHealthThresholds().RelayActiveHours)
|
||||
isBridge := func(pubkey string) bool {
|
||||
if pubkey == "" {
|
||||
return false
|
||||
}
|
||||
info, ok := relayMap[strings.ToLower(pubkey)]
|
||||
return ok && len(info.TransportedScopes) >= 2
|
||||
}
|
||||
mark := func(b *PacketPathBranch) {
|
||||
if b == nil {
|
||||
return
|
||||
}
|
||||
for i := range b.Points {
|
||||
b.Points[i].IsBridge = isBridge(b.Points[i].PublicKey)
|
||||
}
|
||||
if b.Observer != nil {
|
||||
b.Observer.IsBridge = isBridge(b.Observer.PublicKey)
|
||||
}
|
||||
}
|
||||
for i := range resp.Branches {
|
||||
mark(&resp.Branches[i])
|
||||
}
|
||||
mark(resp.First)
|
||||
}
|
||||
|
||||
var iataCoords = map[string]IataCoord{
|
||||
"SJC": {Lat: 37.3626, Lon: -121.929},
|
||||
"SFO": {Lat: 37.6213, Lon: -122.379},
|
||||
|
||||
@@ -100,7 +100,7 @@
|
||||
var chain = located.map(function (p, hi) {
|
||||
return {
|
||||
lat: p.lat, lon: p.lon, name: p.name, label: 'hop ' + (hi + 1) + ' of ' + b.hops, approx: !!p.approx,
|
||||
approxNeighborCount: p.approxNeighborCount, approxSpreadKm: p.approxSpreadKm, role: p.role, isBridge: !!p.isBridge,
|
||||
approxNeighborCount: p.approxNeighborCount, approxSpreadKm: p.approxSpreadKm, role: p.role,
|
||||
publicKey: p.publicKey,
|
||||
};
|
||||
});
|
||||
@@ -112,7 +112,7 @@
|
||||
lat: b.observer.lat, lon: b.observer.lon, name: b.observer.name,
|
||||
label: observerLabel, isObserver: true, approx: !!b.observer.approx,
|
||||
approxNeighborCount: b.observer.approxNeighborCount, approxSpreadKm: b.observer.approxSpreadKm,
|
||||
role: b.observer.role, isBridge: !!b.observer.isBridge, publicKey: b.observer.publicKey,
|
||||
role: b.observer.role, publicKey: b.observer.publicKey,
|
||||
});
|
||||
}
|
||||
return { chain: chain, missing: (b.points || []).length - located.length };
|
||||
@@ -129,7 +129,7 @@
|
||||
'<button type="button" id="packetPathClose" aria-label="Close" ' +
|
||||
'style="position:absolute;top:8px;right:8px;background:none;border:none;cursor:pointer;font-size:22px;line-height:1;color:var(--text-muted)">×</button>' +
|
||||
'<h3 style="margin:0 0 4px;padding-right:24px">Relay Path</h3>' +
|
||||
'<p class="text-muted" style="margin:0 0 10px;font-size:12px">How far and how wide this packet spread. The highlighted route is the farthest-traveled branch; every other station that heard it is shown too. The green ring marks whoever heard it first. Dashed markers are approximate -- estimated from nearby positioned neighbors, not the station\'s own position. A bold purple outline marks a confirmed bridge repeater. Click a marker to open that node\'s detail page.</p>' +
|
||||
'<p class="text-muted" style="margin:0 0 10px;font-size:12px">How far and how wide this packet spread. The highlighted route is the farthest-traveled branch; every other station that heard it is shown too. The green ring marks whoever heard it first. Dashed markers are approximate -- estimated from nearby positioned neighbors, not the station\'s own position. Click a marker to open that node\'s detail page.</p>' +
|
||||
'<div id="packetPathMapContainer" style="height:360px;border-radius:8px;overflow:hidden;background:var(--surface-1)"></div>' +
|
||||
'<div id="packetPathStatus" style="margin-top:8px;font-size:12px;color:var(--text-muted)">Loading…</div>' +
|
||||
'</div>';
|
||||
@@ -217,24 +217,15 @@
|
||||
fillColor: color, fillOpacity: approxFillOpacity(pt.approxNeighborCount), dashArray: '5,4',
|
||||
}
|
||||
: { radius: radius, color: outline, weight: p.primary ? 2 : 1, fillColor: color, fillOpacity: p.primary ? 1 : 0.8 };
|
||||
if (pt.isBridge) {
|
||||
// Bridge repeaters (confirmed relaying for 2+ regions -- same
|
||||
// definition as the Foreign Traffic tab's badge) get a bold
|
||||
// purple outline on top of whatever primary/approx styling
|
||||
// already applies, so they stand out as the mesh's backbone
|
||||
// nodes regardless of which branch they're in.
|
||||
markerOpts = Object.assign({}, markerOpts, { color: cssVar('--status-purple'), weight: markerOpts.weight + 1 });
|
||||
}
|
||||
var approxNote = '';
|
||||
if (pt.approx) {
|
||||
approxNote = ', approx. position';
|
||||
if (pt.approxNeighborCount) approxNote += ' from ' + pt.approxNeighborCount + ' neighbor' + (pt.approxNeighborCount === 1 ? '' : 's');
|
||||
}
|
||||
var bridgeNote = pt.isBridge ? ', bridge repeater' : '';
|
||||
var clickNote = pt.publicKey ? ' — click for node detail' : '';
|
||||
var marker = L.circleMarker([pt.lat, pt.lon], markerOpts)
|
||||
.addTo(map)
|
||||
.bindTooltip(roleIcon(pt.role) + escapeHtml(pt.name) + ' (' + pt.label + approxNote + bridgeNote + ')' + clickNote, { className: 'packet-path-tooltip' });
|
||||
.bindTooltip(roleIcon(pt.role) + escapeHtml(pt.name) + ' (' + pt.label + approxNote + ')' + clickNote, { className: 'packet-path-tooltip' });
|
||||
if (pt.publicKey) {
|
||||
// Same #/nodes/{pubkey} hash route the rest of the app already
|
||||
// links to (see e.g. public/channels.js's node-detail links).
|
||||
|
||||
+4
-4
@@ -2645,10 +2645,10 @@ button.ch-item:hover .ch-icon-btn { opacity: 1; }
|
||||
.leaflet-popup-content { color: var(--text) !important; font-size: 13px !important; }
|
||||
|
||||
/* packet-path-map.js: tooltips got long once they started carrying role,
|
||||
approx/confidence, bridge, and distance/timing info together --
|
||||
Leaflet's default tooltip is white-space:nowrap, which stretched a
|
||||
long one into an unreadable single line spanning the whole map.
|
||||
Wrap it onto multiple lines within a fixed width instead. */
|
||||
approx/confidence, and distance/timing info together -- Leaflet's
|
||||
default tooltip is white-space:nowrap, which stretched a long one
|
||||
into an unreadable single line spanning the whole map. Wrap it onto
|
||||
multiple lines within a fixed width instead. */
|
||||
.leaflet-tooltip.packet-path-tooltip {
|
||||
white-space: normal;
|
||||
max-width: 220px;
|
||||
|
||||
+1
-41
@@ -40,7 +40,7 @@ test('escapes node/observer names before interpolating into tooltip HTML (operat
|
||||
assert.ok(/escapeHtml\(pt\.name\)/.test(src), 'point tooltips must escape the name');
|
||||
});
|
||||
|
||||
test('tooltips use a wrapping CSS class -- Leaflet\'s default nowrap tooltip becomes unreadable once role/approx/bridge/distance/timing info are all combined', () => {
|
||||
test('tooltips use a wrapping CSS class -- Leaflet\'s default nowrap tooltip becomes unreadable once role/approx/distance/timing info are all combined', () => {
|
||||
const bindCalls = (src.match(/\.bindTooltip\(/g) || []).length;
|
||||
const classNameUses = (src.match(/className:\s*'packet-path-tooltip'/g) || []).length;
|
||||
assert.ok(bindCalls > 0, 'expected at least one bindTooltip call');
|
||||
@@ -454,46 +454,6 @@ function makeSandbox(apiImpl) {
|
||||
} catch (e) { failed++; console.log(' ❌ nodes with a known role get a role icon in their tooltip: ' + e.message); }
|
||||
})();
|
||||
|
||||
await (async () => {
|
||||
try {
|
||||
// isBridge=true should get a bold purple outline (overriding the
|
||||
// normal stroke color/weight) and a "bridge repeater" tooltip note.
|
||||
const ctx = makeSandbox(() => Promise.resolve({
|
||||
hash: 'deadbeef',
|
||||
branches: [
|
||||
{
|
||||
hops: 1,
|
||||
points: [
|
||||
{ publicKey: 'pk1', name: 'PlainRepeater', lat: 56.0, lon: 10.0, isBridge: false },
|
||||
{ publicKey: 'pk2', name: 'BridgeRepeater', lat: 56.1, lon: 10.1, isBridge: true },
|
||||
],
|
||||
observer: null,
|
||||
},
|
||||
],
|
||||
}));
|
||||
|
||||
const optsByTooltip = {};
|
||||
ctx.L = {
|
||||
map: () => ({ setView() { return this; }, fitBounds() {}, invalidateSize() {}, remove() {} }),
|
||||
tileLayer: () => ({ addTo() { return this; } }),
|
||||
circleMarker: (latlng, opts) => ({ addTo() { return this; }, bindTooltip(t) { optsByTooltip[t] = opts; return this; }, on() { return this; } }),
|
||||
polyline: () => ({ addTo() { return this; } }),
|
||||
};
|
||||
|
||||
await ctx.window.PacketPathMap.open('deadbeef');
|
||||
const plainKey = Object.keys(optsByTooltip).find((k) => k.includes('PlainRepeater'));
|
||||
const bridgeKey = Object.keys(optsByTooltip).find((k) => k.includes('BridgeRepeater'));
|
||||
assert.ok(plainKey, 'expected a tooltip for PlainRepeater');
|
||||
assert.ok(bridgeKey, 'expected a tooltip for BridgeRepeater');
|
||||
assert.ok(!plainKey.includes('bridge repeater'), 'PlainRepeater tooltip should not mention bridge, got: ' + plainKey);
|
||||
assert.ok(bridgeKey.includes('bridge repeater'), 'BridgeRepeater tooltip should mention bridge, got: ' + bridgeKey);
|
||||
assert.notStrictEqual(optsByTooltip[bridgeKey].color, optsByTooltip[plainKey].color, 'expected the bridge marker to use a distinct outline color');
|
||||
assert.ok(optsByTooltip[bridgeKey].weight > optsByTooltip[plainKey].weight, 'expected the bridge marker outline to be thicker');
|
||||
passed++;
|
||||
console.log(' ✅ bridge repeaters get a distinct outline and tooltip note');
|
||||
} catch (e) { failed++; console.log(' ❌ bridge repeaters get a distinct outline and tooltip note: ' + e.message); }
|
||||
})();
|
||||
|
||||
await (async () => {
|
||||
try {
|
||||
// A marker with a publicKey should register a click handler that
|
||||
|
||||
Reference in New Issue
Block a user