feat: redesign network topology diagram with accurate rack layout

Replace linear Internet→UDM→Agg→PoE→all-hosts chain with accurate topology:
- USW-Aggregation and Pro 24 PoE switch shown side-by-side with horizontal
  10G SFP+ link between them (not in series)
- 5 compute/storage/monitor nodes fanned out under Agg Switch with 10G labels
  and rack unit positions (RU4–12, RU14–17) as sublabels
- large1 shown separately under PoE switch, dashed border = off-rack (table)
- Add device specs as subtitles on all nodes (Dream Machine Pro · RU24, etc.)
- Shorter display names: csg-01 / cs-01 instead of full hostnames
- Live status badges still updated by JS via data-host attributes
- New CSS: .topo-node-sub, .topo-switch-tier, .topo-h-link, .topo-host-tier,
  .topo-host-table (dashed), .topo-badge-unknown

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-14 22:06:03 -04:00
parent c1fd53f9bd
commit e779b21db4
2 changed files with 137 additions and 30 deletions

View File

@@ -356,6 +356,66 @@ a:hover { text-decoration: underline; text-shadow: var(--glow-amber); }
.topo-status-dot { width:7px; height:7px; border:1px solid var(--text-muted); background:transparent; position:absolute; top:5px; right:5px; }
/* Topology subtitle text */
.topo-node-sub {
font-size: .58em;
color: var(--text-muted);
letter-spacing: .02em;
font-weight: normal;
}
.topo-badge-unknown { color:var(--text-muted); border-color:var(--border); }
/* Switch tier: two switches with horizontal connector */
.topo-switch-tier {
display: flex;
align-items: center;
gap: 0;
}
.topo-h-link {
display: flex;
flex-direction: column;
align-items: center;
padding: 0 8px;
}
.topo-h-link-line {
width: 70px;
height: 1px;
background: var(--amber);
opacity: .5;
}
.topo-h-link-label {
font-size: .52em;
color: var(--amber);
opacity: .7;
margin-top: 3px;
letter-spacing: .04em;
}
/* Host tier: two groups side by side */
.topo-host-tier {
display: flex;
justify-content: center;
align-items: flex-start;
gap: 40px;
margin-top: 0;
}
.topo-host-group { flex-shrink: 0; }
/* PoE host group: offset right to sit below PoE switch */
.topo-poe-hosts {
padding-top: 0;
}
/* Off-rack node (dashed border) */
.topo-host-table {
border-style: dashed;
}
/* ── Host cards ───────────────────────────────────────────────────── */
.host-grid {
display: grid;

View File

@@ -29,56 +29,103 @@
</div>
<div class="topology" id="topology-diagram">
<div class="topo-row topo-row-internet">
<!-- ── Tier 1: Internet ───────────────────────── -->
<div class="topo-row">
<div class="topo-node topo-internet">
<span class="topo-icon"></span>
<span class="topo-label">Internet</span>
</div>
</div>
<div class="topo-connectors single">
<div class="topo-line"></div>
<div class="topo-line topo-line-labeled" data-link-label="WAN 10G SFP+"></div>
</div>
<!-- ── Tier 2: Router ─────────────────────────── -->
<div class="topo-row">
<div class="topo-node topo-unifi" id="topo-gateway">
<div class="topo-node topo-unifi">
<span class="topo-icon"></span>
<span class="topo-label">UDM-Pro</span>
<span class="topo-status-dot" data-topo-target="gateway"></span>
<span class="topo-node-sub">Dream Machine Pro · RU24</span>
</div>
</div>
<div class="topo-connectors single">
<div class="topo-line topo-line-labeled" data-link-label="10G DAC"></div>
</div>
<!-- ── Tier 3: Switches (Agg + PoE side by side) ─ -->
<div class="topo-row">
<div class="topo-node topo-switch" id="topo-switch-agg">
<span class="topo-icon"></span>
<span class="topo-label">Agg Switch</span>
<span class="topo-status-dot" data-topo-target="switch-agg"></span>
<div class="topo-switch-tier">
<div class="topo-node topo-switch" id="topo-switch-agg">
<span class="topo-icon"></span>
<span class="topo-label">USW-Agg</span>
<span class="topo-node-sub">8×10G SFP+ · RU22</span>
</div>
<div class="topo-h-link">
<div class="topo-h-link-line"></div>
<span class="topo-h-link-label">10G SFP+</span>
</div>
<div class="topo-node topo-switch" id="topo-switch-poe">
<span class="topo-icon"></span>
<span class="topo-label">Pro 24 PoE</span>
<span class="topo-node-sub">24×1G PoE · RU23</span>
</div>
</div>
</div>
<div class="topo-connectors single">
<div class="topo-line topo-line-labeled" data-link-label="10G DAC"></div>
</div>
<div class="topo-row">
<div class="topo-node topo-switch" id="topo-switch-poe">
<span class="topo-icon"></span>
<span class="topo-label">PoE Switch</span>
<span class="topo-status-dot" data-topo-target="switch-poe"></span>
<!-- ── Tier 4: Hosts ──────────────────────────── -->
<div class="topo-host-tier">
<!-- Agg-connected hosts (10G) -->
<div class="topo-host-group">
<div class="topo-connectors" style="gap:32px; justify-content:center">
<div class="topo-line"></div>
<div class="topo-line"></div>
<div class="topo-line"></div>
<div class="topo-line"></div>
<div class="topo-line"></div>
</div>
<div class="topo-row topo-hosts-row">
{%- set topo_h = snapshot.hosts if snapshot.hosts else {} -%}
{%- set agg_defs = [
('compute-storage-gpu-01', 'csg-01', 'RU412 · 10G'),
('compute-storage-01', 'cs-01', 'RU1417 · 10G'),
('storage-01', 'storage-01','10G SFP+'),
('monitor-01', 'monitor-01','ZimaBoard · 10G'),
('monitor-02', 'monitor-02','ZimaBoard · 10G'),
] -%}
{%- for hname, hlabel, hsub in agg_defs -%}
{%- set st = topo_h[hname].status if hname in topo_h else 'unknown' -%}
<div class="topo-node topo-host topo-status-{{ st }}" data-host="{{ hname }}">
<span class="topo-icon"></span>
<span class="topo-label">{{ hlabel }}</span>
<span class="topo-node-sub">{{ hsub }}</span>
<span class="topo-badge topo-badge-{{ st }}">{{ st if st != 'unknown' else '' }}</span>
</div>
{%- endfor -%}
</div>
</div>
</div>
<div class="topo-connectors wide">
{% for name in snapshot.hosts %}
<div class="topo-line"></div>
{% endfor %}
</div>
<div class="topo-row topo-hosts-row">
{% for name, host in snapshot.hosts.items() %}
<div class="topo-node topo-host topo-status-{{ host.status }}" data-host="{{ name }}">
<span class="topo-icon"></span>
<span class="topo-label">{{ name }}</span>
<span class="topo-badge topo-badge-{{ host.status }}">{{ host.status }}</span>
<!-- PoE-connected hosts -->
<div class="topo-host-group topo-poe-hosts">
<div class="topo-connectors single">
<div class="topo-line topo-line-labeled" data-link-label="1G PoE"></div>
</div>
<div class="topo-row">
{%- set topo_h = snapshot.hosts if snapshot.hosts else {} -%}
{%- set lst = topo_h['large1'].status if 'large1' in topo_h else 'unknown' -%}
<div class="topo-node topo-host topo-host-table topo-status-{{ lst }}" data-host="large1">
<span class="topo-icon"></span>
<span class="topo-label">large1</span>
<span class="topo-node-sub">on table · 1G</span>
<span class="topo-badge topo-badge-{{ lst }}">{{ lst if lst != 'unknown' else '' }}</span>
</div>
</div>
</div>
{% endfor %}
</div>
</div><!-- /topo-host-tier -->
</div>
<!-- Host cards -->