Преглед изворни кода

topo: an HA-OFF switch can never have green ports - fabric port dots and 200G cable flow now require the switch to report on (fabPortOn policy + 6 playwright probes)

Alexander Korolev пре 1 месец
родитељ
комит
706b7a5e03
3 измењених фајлова са 38 додато и 2 уклоњено
  1. 7 0
      specs/tech-architecture/web-dashboard.md
  2. 16 2
      src/web/ui.html
  3. 15 0
      src/web/ui.playwright.mjs

+ 7 - 0
specs/tech-architecture/web-dashboard.md

@@ -325,3 +325,10 @@ occupancy only); `--open` already implemented (trivial).
    A build-toolchain frontend is only worth it if the UI grows a real
    component graph (revisit at ~3k lines).
 2. **Default port:** `3100` (next to the 30000 serving ports).
+
+## Topology power policy (2026-09-14)
+
+An HA-OFF switch can never have a green port: fabric port dots and the
+200G cable flow into them are green only when the switch reports `on`
+(unknown tolerated) AND the node is occupied — `fabPortOn(swOn, occupied)`
+(pure, TOPOPORT markers in ui.html, probed by the playwright suite).

+ 16 - 2
src/web/ui.html

@@ -1157,6 +1157,16 @@ function legendSvg() {
 
 // Per-tick state update: flip LED/port/frame/flow classes in place —
 // the SVG skeleton survives, so CSS animations keep running.
+// Power policy for fabric ports and their cable flows: a port is green only
+// when its switch reports ON (unknown is tolerated) AND the node is
+// occupied — an HA-OFF switch cannot carry traffic, so all of its ports
+// stay gray even if a node still reports a running container.
+/*TOPOPORT:START*/
+function fabPortOn(swOn, occupied) {
+  return swOn !== false && !!occupied;
+}
+/*TOPOPORT:END*/
+
 function topoTick(snap) {
   const t = snap.topo;
   const ns = {};
@@ -1186,19 +1196,23 @@ function topoTick(snap) {
       + (s.ha === false ? " · plug OFF" : "")
       + (s.containers.length ? " · " + s.containers.map((x) => x.name).join(", ") : "");
   }
+  const swStateOf = (ent) => t.states.find((x) => x.entity === ent)?.on;
   for (const l of t.links) {
     const fl = document.querySelector(`#topo path[data-flow="${CSS.escape(l.kind + "|" + l.a + "|" + l.b)}"]`);
     if (!fl) continue;
     const B = ns[l.b], A = l.kind === "dac" ? ns[l.a] : null;
     fl.classList.toggle("live", l.kind === "10g" ? !!B?.reachable
-      : l.kind === "200g" ? !!B?.occupied
+      : l.kind === "200g" ? fabPortOn(swStateOf(l.a), B?.occupied)
       : !!(B?.occupied && A?.occupied));
   }
   for (const name of t.nodes) {
     const lp = document.querySelector(`#topo rect[data-lanport="${CSS.escape(name)}"]`);
     if (lp) lp.classList.toggle("on", !!ns[name]?.reachable);
     const fp = document.querySelector(`#topo rect[data-fabport="${CSS.escape(name)}"]`);
-    if (fp) fp.classList.toggle("on", !!ns[name]?.occupied);
+    if (fp) {
+      const swEnt = fp.closest("[data-sw]")?.dataset?.sw;
+      fp.classList.toggle("on", fabPortOn(swStateOf(swEnt), ns[name]?.occupied));
+    }
   }
   for (const sw of t.switches) {
     const st = t.states.find((x) => x.entity === sw.entity);

+ 15 - 0
src/web/ui.playwright.mjs

@@ -325,6 +325,21 @@ await page.waitForFunction(
 );
 ok("topo: frame chip click activates cluster", true);
 
+// --- topology power policy: an HA-OFF switch can never have green ports ---
+// the pure fabPortOn policy extracted from the live page (between the
+// TOPOPORT markers) and probed under deno, like the report's notifDiff
+const html = await page.content();
+const s = html.indexOf("/*TOPOPORT:START*/"), e = html.indexOf("/*TOPOPORT:END*/");
+ok("topo: fabPortOn policy present in page", s >= 0 && e > s);
+if (s >= 0 && e > s) {
+  const fab = new Function(html.slice(s + "/*TOPOPORT:START*/".length, e) + "\nreturn fabPortOn;")();
+  ok("topo: switch on + occupied -> port green", fab(true, true) === true);
+  ok("topo: switch OFF + occupied -> port NOT green", fab(false, true) === false);
+  ok("topo: switch OFF + idle -> port not green", fab(false, false) === false);
+  ok("topo: switch unknown + occupied -> green (inconclusive)", fab(undefined, true) === true);
+  ok("topo: switch on + idle -> port not green", fab(true, false) === false);
+}
+
 // --- 14. recipes panel: the `sm121 report --serve` page, mounted at /recipes -----
 await page.click("#dock .plus");
 await page.click('.dockmenu .dock-chip:text-is("recipes")');