diff --git a/static/app.js b/static/app.js index 38d1280..38cc979 100644 --- a/static/app.js +++ b/static/app.js @@ -109,7 +109,9 @@ function updateTopology(hosts) { const name = node.dataset.host; const host = hosts[name]; if (!host) return; + node.className = node.className.replace(/topo-v2-status-(up|down|degraded|unknown)/g, ''); node.className = node.className.replace(/topo-status-(up|down|degraded|unknown)/g, ''); + node.classList.add(`topo-v2-status-${host.status}`); node.classList.add(`topo-status-${host.status}`); const badge = node.querySelector('.topo-badge'); if (badge) { diff --git a/static/style.css b/static/style.css index 3cdf235..713d386 100644 --- a/static/style.css +++ b/static/style.css @@ -1534,6 +1534,439 @@ a:hover { text-decoration: underline; text-shadow: var(--glow-amber); } text-shadow: var(--glow-cyan); } +/* ── Topology v2 – professional network diagram ──────────────────── */ + +/* Outer wrapper */ +.topo-v2 { + display: flex; + flex-direction: column; + align-items: center; + gap: 0; + min-width: 860px; + padding: 20px 24px 24px; + position: relative; +} + +/* Each tier row */ +.topo-tier { + display: flex; + justify-content: center; + align-items: center; + width: 100%; +} + +/* ── Vertical connector section between tiers ── */ +.topo-vc { + display: flex; + justify-content: center; + align-items: flex-start; + width: 100%; + position: relative; + height: 40px; +} + +/* Single centered vertical wire */ +.topo-vc-wire { + position: absolute; + left: 50%; + top: 0; + transform: translateX(-50%); + width: 2px; + height: 100%; + background: linear-gradient(to bottom, var(--cyan), var(--green)); + opacity: .7; +} + +/* Labeled vertical connector */ +.topo-vc-label { + position: absolute; + left: calc(50% + 6px); + top: 50%; + transform: translateY(-50%); + font-size: .58em; + color: var(--amber); + text-shadow: var(--glow-amber); + white-space: nowrap; + letter-spacing: .06em; + font-family: var(--font); +} + +/* ── WAN tier node (Internet + Router side by side) ── */ +.topo-tier-wan { + gap: 0; + flex-direction: column; + align-items: center; +} + +/* ── Individual node boxes ── */ +.topo-v2-node { + display: flex; + flex-direction: column; + align-items: center; + gap: 3px; + padding: 8px 16px; + border: 1px solid var(--border); + background: var(--bg3); + position: relative; + font-size: .75em; + font-family: var(--font); + min-width: 110px; + text-align: center; + transition: border-color .2s, box-shadow .2s; +} +.topo-v2-node::before { content:'┌'; position:absolute; top:-1px; left:-1px; color:var(--green); font-size:.85rem; line-height:1; pointer-events:none; } +.topo-v2-node::after { content:'┐'; position:absolute; top:-1px; right:-1px; color:var(--green); font-size:.85rem; line-height:1; pointer-events:none; } + +.topo-v2-icon { font-size:1.3em; line-height:1; } +.topo-v2-label { font-weight:bold; letter-spacing:.04em; } +.topo-v2-sub { font-size:.58em; color:var(--text-muted); letter-spacing:.02em; } +.topo-v2-vlan { font-size:.54em; color:var(--cyan); opacity:.75; letter-spacing:.02em; } + +/* Node type colours */ +.topo-v2-internet { border-color:var(--cyan); color:var(--cyan); text-shadow:var(--glow-cyan); } +.topo-v2-router { border-color:var(--cyan); color:var(--cyan); text-shadow:var(--glow-cyan); } +.topo-v2-switch { border-color:var(--amber); color:var(--amber); text-shadow:var(--glow-amber); } +.topo-v2-host { border-color:var(--border); color:var(--text); cursor:default; } + +/* ── Switch tier: both switches with inter-switch link ── */ +.topo-switch-pair { + display: flex; + align-items: center; + gap: 0; +} + +.topo-isl { + display: flex; + flex-direction: column; + align-items: center; + padding: 0 12px; + gap: 4px; +} +.topo-isl-wire { + width: 80px; + height: 2px; + background: linear-gradient(to right, var(--amber), var(--amber)); + opacity: .6; + position: relative; +} +.topo-isl-wire::before, +.topo-isl-wire::after { + content: ''; + position: absolute; + top: -3px; + width: 2px; + height: 8px; + background: var(--amber); + opacity: .6; +} +.topo-isl-wire::before { left: 0; } +.topo-isl-wire::after { right: 0; } +.topo-isl-label { + font-size: .54em; + color: var(--amber); + opacity: .75; + white-space: nowrap; + letter-spacing: .05em; + font-family: var(--font); +} + +/* ── Dual-home bus section ── */ +/* This is the complex section linking two switches to N hosts */ +.topo-bus-section { + width: 100%; + display: flex; + flex-direction: column; + align-items: stretch; + gap: 0; + margin-top: 0; +} + +/* Bus bar row: the horizontal rail that distributes to hosts */ +.topo-bus-bars { + display: flex; + flex-direction: column; + align-items: stretch; + position: relative; + width: 100%; +} + +/* The two drop buses: 10G (green) and 1G mgmt (amber dashed) */ +.topo-bus-10g { + display: flex; + align-items: center; + position: relative; + height: 20px; +} +.topo-bus-10g-line { + flex: 1; + height: 2px; + background: var(--green); + opacity: .45; + margin: 0 4px; +} +.topo-bus-10g-label { + font-size: .56em; + color: var(--green); + text-shadow: var(--glow); + white-space: nowrap; + letter-spacing: .05em; + font-family: var(--font); + opacity: .85; + padding: 0 8px; +} + +.topo-bus-1g { + display: flex; + align-items: center; + position: relative; + height: 18px; +} +.topo-bus-1g-line { + flex: 1; + height: 0; + border-top: 2px dashed var(--amber); + opacity: .35; + margin: 0 4px; +} +.topo-bus-1g-label { + font-size: .56em; + color: var(--amber); + text-shadow: var(--glow-amber); + white-space: nowrap; + letter-spacing: .05em; + font-family: var(--font); + opacity: .8; + padding: 0 8px; +} + +/* ── Host row ── */ +.topo-v2-hosts { + display: flex; + justify-content: center; + gap: 12px; + flex-wrap: wrap; + padding-top: 4px; + width: 100%; +} + +/* Host status colouring */ +.topo-v2-status-up { border-color:var(--green); box-shadow:0 0 8px rgba(0,255,65,.2); } +.topo-v2-status-down { border-color:var(--red); box-shadow:0 0 8px rgba(255,68,68,.35); animation:pulse-glow 2s infinite; } +.topo-v2-status-degraded{ border-color:var(--orange); box-shadow:0 0 8px rgba(255,140,0,.2); } +.topo-v2-status-unknown { border-color:var(--border); } + +/* Off-rack host: dashed border */ +.topo-v2-offrack { border-style: dashed !important; } + +/* ── Legend row ── */ +.topo-legend { + display: flex; + gap: 18px; + align-items: center; + margin-top: 14px; + padding-top: 10px; + border-top: 1px solid rgba(0,255,65,.12); + flex-wrap: wrap; + justify-content: center; +} +.topo-legend-item { + display: flex; + align-items: center; + gap: 5px; + font-size: .58em; + color: var(--text-muted); + font-family: var(--font); +} +.topo-legend-line-10g { + width: 24px; height: 2px; + background: var(--green); + display: inline-block; +} +.topo-legend-line-1g { + width: 24px; height: 0; + border-top: 2px dashed var(--amber); + display: inline-block; +} +.topo-legend-line-isl { + width: 24px; height: 2px; + background: var(--amber); + display: inline-block; +} +.topo-legend-line-wan { + width: 24px; height: 2px; + background: linear-gradient(to right, var(--cyan), var(--green)); + display: inline-block; +} + +/* ── Drop-wire stubs for host dual-homing ── */ +/* Wrapper that sits above each host showing its two connections */ +.topo-v2-host-wrap { + display: flex; + flex-direction: column; + align-items: center; + gap: 0; +} + +.topo-v2-host-wires { + display: flex; + gap: 6px; + height: 28px; + align-items: flex-start; +} +.topo-v2-wire-10g { + width: 2px; + height: 100%; + background: var(--green); + opacity: .55; +} +.topo-v2-wire-1g { + width: 0; + height: 100%; + border-left: 2px dashed var(--amber); + opacity: .45; +} + +/* host badge */ +.topo-v2-badge { + font-size: .65em; + padding: 1px 5px; + border: 1px solid; + letter-spacing: .03em; + margin-top: 2px; +} +.topo-v2-badge-up { color:var(--green); border-color:var(--green); text-shadow:var(--glow); } +.topo-v2-badge-down { color:var(--red); border-color:var(--red); animation:pulse-glow 1.5s infinite; } +.topo-v2-badge-degraded{ color:var(--orange); border-color:var(--orange); } +.topo-v2-badge-unknown { color:var(--text-muted); border-color:var(--border); } + +/* vertical connector from router to switch tier */ +.topo-v2-fork { + display: flex; + flex-direction: column; + align-items: center; + width: 100%; + position: relative; + height: 50px; +} + +/* Fork tree SVG lines from UDM-Pro down to two switches */ +.topo-v2-fork svg { + width: 100%; + height: 100%; + overflow: visible; +} + +/* ── Improved chassis legend ── */ +.chassis-legend { + display: flex; + gap: 16px; + align-items: center; + padding: 7px 16px 8px; + border-top: 1px solid rgba(0,255,65,.1); + background: var(--bg2); + flex-wrap: wrap; +} +.chassis-legend-item { + display: flex; + align-items: center; + gap: 5px; + font-size: .58em; + color: var(--text-muted); + font-family: var(--font); + letter-spacing: .04em; + text-transform: uppercase; +} +.chassis-legend-swatch { + width: 14px; + height: 14px; + border: 1px solid; + flex-shrink: 0; + display: inline-block; +} +.cls-down { background:var(--bg3); border-color:rgba(0,255,65,.15); } +.cls-up { background:rgba(0,255,65,.06); border-color:var(--green-muted); } +.cls-poe { background:var(--amber-dim); border-color:var(--amber); } +.cls-uplink { background:var(--cyan-dim); border-color:var(--cyan); } + +/* ── Port block v2: flex-col with speed sub-label ── */ +.switch-port-block { + flex-direction: column; + justify-content: center; + align-items: center; + gap: 1px; + padding: 2px 1px; +} +.port-num { + line-height: 1; + font-weight: bold; +} +.port-speed { + font-size: .72em; + opacity: .7; + line-height: 1; + font-weight: normal; +} +.port-lldp { + font-size: .62em; + opacity: .65; + line-height: 1; + max-width: 32px; + overflow: hidden; + white-space: nowrap; + text-overflow: clip; + font-weight: normal; +} + +/* US24PRO port group separators (every 2 ports = 1 pair gap) */ +.chassis-row.us24pro-row .switch-port-block:nth-child(2n+1):not(:first-child) { + margin-left: 6px; +} + +/* SFP block: taller and narrower cage look */ +.switch-port-block.sfp-block { + width: 36px; + height: 38px; + font-size: .55em; + letter-spacing: .04em; + border-left-width: 3px; +} +/* SFP port in rows */ +.switch-port-block.sfp-port { + width: 26px; + height: 40px; + font-size: .55em; + border-left-width: 2px; +} + +/* Chassis mounting ears */ +.chassis-body { + position: relative; +} +.chassis-ear-l, +.chassis-ear-r { + position: absolute; + top: 50%; + transform: translateY(-50%); + width: 8px; + height: 36px; + background: var(--bg3); + border: 1px solid var(--border); +} +.chassis-ear-l { left: -9px; border-right: none; } +.chassis-ear-r { right: -9px; border-left: none; } +.chassis-ear-l::before, +.chassis-ear-r::before { + content: ''; + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + width: 4px; + height: 4px; + border-radius: 50%; + background: var(--border); +} + /* ── Responsive ───────────────────────────────────────────────────── */ @media (max-width: 768px) { .host-grid { grid-template-columns:1fr; } diff --git a/templates/index.html b/templates/index.html index 11d9b03..b29a1f9 100644 --- a/templates/index.html +++ b/templates/index.html @@ -29,96 +29,149 @@