The control plane asks one node on each physical fabric to run
ibnetdiscover every five minutes and parses the text itself, so a
format the parser has not met is a control plane upgrade, not one on
every node. From the map it works out the switch tiers (leaf, spine,
core, by distance from the adapters), which adapter is which node's,
which cables trained below the rate the rest of their kind run at, and
whether the rails line up: every node's Nth adapter on its own leaf.
A page per fabric (/admin/fabrics) draws it: switches in their tiers,
adapters fanned out under their leaves, cables coloured by rate and
bundled, degraded ones marching red with a marker, and the cluster's
own adapters live - down ports glowing red, and particles running each
cable as fast as its adapter is sending and receiving, from the
counters. It zooms, pans, fades what a hovered switch or adapter does
not touch, opens a panel of detail on a click, colours by rail, finds a
node, and refreshes every fifteen seconds. The data is also at
/api/v1/fabrics/{id}/topology.
Placement reads the map: a replica spanning nodes takes nodes of one
leaf together when it can, and nodes whose cable trained short last.
Static assets are named by their content, as the stylesheet already
was.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
70 lines
3.6 KiB
HTML
70 lines
3.6 KiB
HTML
{{define "fabrics"}}<!doctype html>
|
|
<html lang="en"><head>
|
|
<meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
|
|
<title>Fabrics · llmgw</title><link rel="stylesheet" href="{{stylesheet}}">
|
|
</head><body><div class="wrap wide">
|
|
<header>
|
|
<h1>Fabrics</h1>
|
|
<p class="sub">{{len .List}} physical fabric{{if ne (len .List) 1}}s{{end}}, as their adapters find them</p>
|
|
{{if .OnAdminListener}}<nav><a href="/">cluster</a></nav>{{else}}{{template "nav" .Nav}}{{end}}
|
|
</header>
|
|
|
|
{{if not .List}}
|
|
<p class="empty">No InfiniBand or RoCE port has reported yet. A node's adapters appear here with its first
|
|
heartbeat, and its fabric's map within five minutes of that.</p>
|
|
{{else}}
|
|
<nav class="pills" aria-label="Fabric">{{range .List}}<a href="{{$.Base}}/{{.ID}}"{{if $.Current}}{{if eq .ID $.Current.ID}} class="on" aria-current="page"{{end}}{{end}}>
|
|
{{if .Declared}}{{range $i, $d := .Declared}}{{if $i}}, {{end}}{{$d}}{{end}}{{else}}undeclared{{end}}
|
|
<span class="note">{{.Label}}</span></a>{{end}}</nav>
|
|
{{end}}
|
|
|
|
{{with .Current}}
|
|
<div class="fabric-head">
|
|
<div>
|
|
<h2>{{if .Declared}}{{range $i, $d := .Declared}}{{if $i}}, {{end}}<code>{{$d}}</code>{{end}}{{else}}undeclared{{end}}</h2>
|
|
<p class="sub">{{.Label}}
|
|
{{with .SM}} · master <code>{{.GUID}}</code>{{if .LID}} LID {{.LID}}{{end}}{{end}}
|
|
{{if .SMs}} · {{len .SMs}} subnet manager{{if gt (len .SMs) 1}}s{{end}}{{end}}
|
|
{{if not .ScannedAt.IsZero}} · mapped {{ago .ScannedAt}} through {{.ScannedBy}} in {{.TookMs}} ms{{end}}</p>
|
|
</div>
|
|
</div>
|
|
{{if .Problems}}<ul class="fabric-problems">{{range .Problems}}
|
|
<li class="{{.Level}}"><span class="tag{{if eq (printf "%s" .Level) "fail"}} bad{{end}}">{{.Level}}</span> {{.Message}}</li>{{end}}
|
|
</ul>{{end}}
|
|
|
|
<div id="fabric-map" class="fabric-map" data-src="{{$.Base}}/{{.ID}}/topology.json"
|
|
data-nodes="{{if $.OnAdminListener}}/nodes{{else}}/admin/nodes{{end}}">
|
|
<div class="fm-bar">
|
|
<div class="fm-stats"></div>
|
|
<div class="fm-tools">
|
|
<span class="pills small">
|
|
<button type="button" class="on" data-mode="speed">Speed</button>
|
|
<button type="button" data-mode="rails">Rails</button>
|
|
</span>
|
|
<button type="button" class="fm-toggle" data-toggle="traffic">Traffic</button>
|
|
<button type="button" class="fm-toggle" data-toggle="problems">Only problems</button>
|
|
<input type="search" class="fm-search" placeholder="Find a node" aria-label="Find a node">
|
|
<button type="button" class="fm-toggle" data-reset title="Or double-click the map">Reset view</button>
|
|
</div>
|
|
</div>
|
|
<div class="fm-stage">
|
|
<div class="fm-canvas"><noscript><p class="fm-empty">The map is drawn in the browser; the problems above are the same
|
|
without it.</p></noscript></div>
|
|
<div class="fm-tip" hidden></div>
|
|
<aside class="fm-panel" hidden></aside>
|
|
</div>
|
|
<div class="fm-legend">
|
|
<span><i class="sw sp-xdr"></i>XDR</span><span><i class="sw sp-ndr"></i>NDR</span><span><i class="sw sp-hdr"></i>HDR</span>
|
|
<span><i class="sw sp-edr"></i>EDR</span><span><i class="sw sp-slow"></i>slower</span>
|
|
<span><i class="sw degraded"></i>below its kind</span>
|
|
<span><i class="dot known"></i>this cluster's adapter</span><span><i class="dot"></i>another host</span><span><i class="dot down"></i>port down</span>
|
|
<span class="note">scroll to zoom · drag to move · click for detail</span>
|
|
</div>
|
|
</div>
|
|
<script type="application/json" id="fabric-data">{{$.Data}}</script>
|
|
<script src="{{asset "fabric.js"}}" defer></script>
|
|
{{end}}
|
|
|
|
<footer><span>llmgw {{.Version}}</span><span>{{.Now}}</span></footer>
|
|
</div></body></html>{{end}}
|