stage-netd-b network detail: corroboration edges + timeline strip (CSS + template)
This commit is contained in:
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user