stage-netd-b network detail: corroboration edges + timeline strip (CSS + template)

This commit is contained in:
m17hr1l
2026-06-07 00:57:44 +02:00
parent c6c5d3b2ea
commit 15749e050e
2 changed files with 327 additions and 1 deletions

View File

@@ -1244,3 +1244,313 @@ body.wide #federation-network-graph { height: 720px; }
.fn-status-badge-vouched { color: #c4b5fd; border-color: #a78bfa; background: rgba(167,139,250,0.10); }
.fn-status-badge-unknown { color: var(--muted); border-color: var(--muted); }
.fn-status-badge-blocked { color: var(--red); border-color: var(--red); background: rgba(248,113,113,0.10); }
/* ---------- federation network — enriched detail layer ---------------- */
/* Per-node stat badge: small monospace pill sitting just below the
sublabel ("8 sig · 2 vch · 1 quo"). SVG <text> styled, not a real
HTML pill — we keep it inline with the node group for layout. */
.fn-stat-badge {
fill: var(--accent);
font-family: ui-monospace, Menlo, Consolas, monospace;
font-size: 11px;
text-anchor: middle;
pointer-events: none;
opacity: 0.85;
letter-spacing: 0.02em;
}
.fn-distance-2 .fn-stat-badge { display: none; }
/* Corroboration edges — dotted faint accent, lower z visually. */
.fn-kind-corroborate .fn-edge {
stroke: var(--accent);
stroke-width: 1.1;
stroke-dasharray: 1 5;
stroke-linecap: round;
opacity: 0.28;
animation: fn-corr-pulse 3.2s ease-in-out infinite;
}
.fn-kind-corroborate .fn-edge-label {
fill: rgba(170, 220, 255, 0.55);
font-size: 8.5px;
display: none; /* surfaced via tooltip; chart stays calm */
}
.fn-kind-corroborate .fn-edge-grp { pointer-events: none; }
@keyframes fn-corr-pulse {
0%, 100% { stroke-opacity: 0.22; }
50% { stroke-opacity: 0.45; }
}
@media (prefers-reduced-motion: reduce) {
.fn-kind-corroborate .fn-edge { animation: none; }
}
#federation-network-graph.flow-off .fn-kind-corroborate .fn-edge { animation: none; }
/* Hover tooltip — absolutely positioned, accent-bordered HUD pill. */
.fn-tooltip {
position: absolute;
z-index: 50;
background: rgba(15, 17, 21, 0.96);
border: 1px solid var(--accent);
border-radius: 6px;
box-shadow: 0 0 18px var(--accent-glow), 0 6px 22px rgba(0,0,0,0.55);
padding: 8px 10px;
font-family: ui-monospace, Menlo, Consolas, monospace;
font-size: 11px;
color: var(--text);
line-height: 1.45;
pointer-events: none;
max-width: 320px;
white-space: nowrap;
display: none;
}
.fn-tooltip.is-visible { display: block; }
.fn-tooltip-title {
color: var(--accent);
font-family: var(--font-display);
font-size: 12px;
margin-bottom: 4px;
letter-spacing: 0.05em;
}
.fn-tooltip-row { display: flex; gap: 10px; }
.fn-tooltip-row .k { color: var(--muted); min-width: 70px; }
.fn-tooltip-row .v { color: var(--text); }
/* Search/filter bar above the graph. */
.fn-search-bar {
display: flex;
align-items: center;
gap: 10px;
margin: 0 0 10px;
}
.fn-search-bar label {
color: var(--muted);
font-size: 11px;
text-transform: uppercase;
letter-spacing: 0.08em;
}
.fn-search-input {
flex: 1;
max-width: 460px;
background: var(--panel-2);
color: var(--text);
border: 1px solid var(--border);
border-radius: 4px;
padding: 6px 10px;
font-family: ui-monospace, Menlo, Consolas, monospace;
font-size: 12px;
transition: border-color 0.15s, box-shadow 0.15s;
}
.fn-search-input:focus {
outline: none;
border-color: var(--accent);
box-shadow: 0 0 0 3px var(--accent-glow);
}
.fn-search-count { color: var(--muted); font-size: 11px; font-family: ui-monospace, Menlo, Consolas, monospace; }
/* Search dim/highlight states. */
.fn-node.dimmed { opacity: 0.15; }
.fn-node.match circle, .fn-node.match rect { stroke: var(--amber); stroke-width: 2.4; filter: drop-shadow(0 0 8px rgba(251,191,36,0.55)); }
.fn-edge-grp.dimmed { opacity: 0.08; }
/* Rich detail card — sits in the existing .topo-detail container. */
.fn-detail-card {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
gap: 12px;
margin-top: 10px;
}
.fn-detail-sec {
background: var(--panel-2);
border: 1px solid var(--border);
border-radius: 6px;
padding: 10px 12px;
min-width: 0; /* allow children to wrap */
}
.fn-detail-sec h4 {
margin: 0 0 8px;
font-size: 11px;
font-family: var(--font-display);
color: var(--muted);
text-transform: uppercase;
letter-spacing: 0.10em;
font-weight: 600;
}
.fn-detail-sec .row { display: flex; justify-content: space-between; gap: 8px; font-size: 12px; padding: 2px 0; }
.fn-detail-sec .row .k { color: var(--muted); }
.fn-detail-sec .row .v { color: var(--text); font-family: ui-monospace, Menlo, Consolas, monospace; word-break: break-all; }
.fn-detail-sec code {
font-size: 11px;
background: var(--panel);
border: 1px solid var(--border);
border-radius: 3px;
padding: 2px 5px;
word-break: break-all;
display: inline-block;
}
.fn-detail-sec .full-fp { font-size: 11px; line-height: 1.55; }
.fn-copy-btn {
display: inline-block;
background: transparent;
color: var(--accent);
border: 1px solid var(--border);
border-radius: 3px;
font-size: 10px;
padding: 1px 6px;
margin-left: 6px;
font-family: ui-monospace, Menlo, Consolas, monospace;
cursor: pointer;
letter-spacing: 0.04em;
}
.fn-copy-btn:hover { border-color: var(--accent); box-shadow: 0 0 8px var(--accent-glow); }
/* Severity chips inside the Signals section. */
.fn-sev-row { display: flex; flex-wrap: wrap; gap: 4px; margin-top: 6px; }
.fn-sev-chip {
display: inline-flex;
align-items: center;
gap: 4px;
padding: 2px 8px;
border-radius: 10px;
font-size: 10px;
font-family: ui-monospace, Menlo, Consolas, monospace;
border: 1px solid var(--border);
background: var(--panel);
text-transform: uppercase;
letter-spacing: 0.05em;
}
.fn-sev-chip .n { font-weight: 700; }
.fn-sev-critical { color: var(--red); border-color: var(--red); background: rgba(248,113,113,0.10); }
.fn-sev-high { color: var(--amber); border-color: var(--amber); background: rgba(251,191,36,0.10); }
.fn-sev-medium { color: #fde68a; border-color: rgba(253,224,71,0.55); background: rgba(253,224,71,0.06); }
.fn-sev-low { color: var(--muted); border-color: var(--border); }
/* IOC-type chips reuse the chip shell with muted accents. */
.fn-ioc-chip {
display: inline-flex; gap: 4px; padding: 2px 8px;
border-radius: 10px; font-size: 10px;
font-family: ui-monospace, Menlo, Consolas, monospace;
border: 1px solid var(--border); background: var(--panel);
color: var(--text);
}
.fn-ioc-chip .k { color: var(--accent); }
.fn-ioc-chip .n { color: var(--text); font-weight: 700; }
/* Quorum progress bar. */
.fn-quorum-bar {
position: relative;
height: 8px;
background: var(--panel);
border: 1px solid var(--border);
border-radius: 4px;
overflow: hidden;
margin-top: 6px;
}
.fn-quorum-fill {
position: absolute; inset: 0 auto 0 0;
background: linear-gradient(90deg, var(--accent), var(--green));
box-shadow: 0 0 8px var(--accent-glow);
}
/* Translog list inside the detail card. */
.fn-trans-list {
list-style: none; margin: 0; padding: 0;
font-family: ui-monospace, Menlo, Consolas, monospace;
font-size: 11px;
max-height: 180px;
overflow-y: auto;
}
.fn-trans-list li {
display: flex; gap: 8px; padding: 3px 0;
border-bottom: 1px dashed var(--border);
}
.fn-trans-list .id { color: var(--muted); min-width: 38px; }
.fn-trans-list .type { color: var(--accent); min-width: 50px; }
.fn-trans-list .ts { color: var(--muted); }
.fn-trans-list .hash { color: var(--text); }
/* Clickable fingerprint chip — jumps to that peer in the graph. */
.fn-fp-jump {
font-family: ui-monospace, Menlo, Consolas, monospace;
font-size: 11px;
color: var(--accent);
background: var(--panel);
border: 1px solid var(--border);
border-radius: 3px;
padding: 1px 5px;
cursor: pointer;
margin: 2px 4px 2px 0;
display: inline-block;
}
.fn-fp-jump:hover { border-color: var(--accent); text-shadow: 0 0 8px var(--accent-glow); }
/* Action buttons inside the detail card. */
.fn-actions { display: flex; flex-wrap: wrap; gap: 8px; }
.fn-action-btn {
display: inline-block;
padding: 5px 10px;
border: 1px solid var(--border);
border-radius: 4px;
color: var(--accent);
background: var(--panel);
font-size: 12px;
font-family: ui-monospace, Menlo, Consolas, monospace;
cursor: pointer;
text-decoration: none;
}
.fn-action-btn:hover { border-color: var(--accent); box-shadow: 0 0 10px var(--accent-glow); text-decoration: none; }
/* 24h timeline strip. */
.fn-timeline-wrap {
margin-top: 18px;
padding: 12px 14px;
background: var(--panel-2);
border: 1px solid var(--border);
border-radius: 6px;
}
.fn-timeline-head {
display: flex; justify-content: space-between; align-items: baseline;
margin-bottom: 8px;
}
.fn-timeline-head h3 {
margin: 0; font-size: 12px; color: var(--muted);
text-transform: uppercase; letter-spacing: 0.10em; font-weight: 600;
font-family: var(--font-display);
}
.fn-timeline-head .meta { color: var(--muted); font-size: 11px; font-family: ui-monospace, Menlo, Consolas, monospace; }
.fn-timeline {
display: flex;
align-items: flex-end;
gap: 2px;
height: 90px;
border-bottom: 1px solid var(--border);
padding-bottom: 2px;
}
.fn-timeline-bar {
flex: 1;
display: flex;
flex-direction: column-reverse; /* segments stack from bottom up */
align-items: stretch;
min-width: 6px;
height: 100%;
position: relative;
background: rgba(125,133,151,0.04);
border-bottom: 1px solid transparent;
cursor: default;
}
.fn-timeline-bar:hover { background: rgba(30,200,255,0.08); }
.fn-timeline-bar-seg {
width: 100%;
min-height: 1px;
transition: filter 0.15s;
}
.fn-timeline-bar:hover .fn-timeline-bar-seg { filter: brightness(1.25); }
.fn-timeline-axis {
display: flex; gap: 2px; margin-top: 4px;
font-family: ui-monospace, Menlo, Consolas, monospace;
font-size: 9px; color: var(--muted);
}
.fn-timeline-axis span { flex: 1; text-align: center; min-width: 6px; }
.fn-timeline-empty {
color: var(--muted); font-size: 12px; font-style: italic;
text-align: center; padding: 22px 0;
}

View File

@@ -19,6 +19,12 @@
<div class="fn-stat"><div class="fn-stat-label">quorum-met</div><div class="fn-stat-value">{{ stats.quorum_met_count }}</div></div>
</div>
<div class="fn-search-bar">
<label for="fn-search">filter</label>
<input type="search" id="fn-search" class="fn-search-input" placeholder="domain or fingerprint substring…" autocomplete="off" spellcheck="false">
<span id="fn-search-count" class="fn-search-count"></span>
</div>
<div class="topo-stage">
<div class="topo-toolbar">
<div class="topo-layouts" role="tablist">
@@ -33,9 +39,10 @@
<span class="topo-legend"><span class="lg-swatch fn-lg-unknown"></span>unknown</span>
<span class="topo-legend"><span class="lg-swatch fn-lg-blocked"></span>blocked</span>
<button type="button" id="fn-reset" class="btn">re-settle</button>
<span class="topo-hint">drag · scroll to zoom</span>
<span class="topo-hint">drag · scroll to zoom · hover for tooltip</span>
</div>
<svg id="federation-network-graph" preserveAspectRatio="xMidYMid meet"></svg>
<div id="fn-tooltip" class="fn-tooltip"></div>
<div id="fn-loading" class="page-intro" style="text-align:center; margin-top:10px;">loading federation network…</div>
<div id="fn-error" class="gate-error" style="display:none;"></div>
</div>
@@ -44,6 +51,15 @@
<p class="topo-detail-empty">Click any node in the graph above to inspect it.</p>
</div>
<div class="fn-timeline-wrap">
<div class="fn-timeline-head">
<h3>signals · last 24h</h3>
<span class="meta" id="fn-timeline-meta"></span>
</div>
<div id="fn-timeline" class="fn-timeline" aria-label="signals received per hour for the last 24 hours"></div>
<div id="fn-timeline-axis" class="fn-timeline-axis"></div>
</div>
<p class="page-intro" style="margin-top:18px;">Self fingerprint: <code style="color:var(--accent);">{{ fingerprint }}</code></p>
</section>