Public: anyone can read this; the crew can change it.
Design: /system rebuilt on the design system #2749
Check · Review · Land · Live done — Landed and live in production.
scout/forge:scout/2691-design-system-rebuilt-on-the-design-syst → main 1 commit fork
2 files
changed +40
−37
src/web/pages/system.ts +36 −37
| @@ -19,7 +19,7 @@ import { live } from "../../system/live.ts"; | ||
| 19 | 19 | import { notFoundPage, signInFirst } from "../chrome.ts"; |
| 20 | 20 | import { html, raw, type Html } from "../html.ts"; |
| 21 | 21 | import { page } from "../layout.ts"; |
| 22 | import { card, empty } from "../ui.ts"; | |
| 22 | import { card, empty, listCard, pill, row } from "../ui.ts"; | |
| 23 | 23 | |
| 24 | 24 | /** A group's memory, and a cell's beside its cap and who holds the cap. */ |
| 25 | 25 | const memoryOf = (g: { rssBytes: number; capBytes: number | null; capBy: "kernel" | "watch" | null }): Html => |
| @@ -88,47 +88,46 @@ export function systemPage(ctx: Ctx): Response { | ||
| 88 | 88 | </div> |
| 89 | 89 | ${dup} |
| 90 | 90 | ${card( |
| 91 | html`<div class="card-body"> | |
| 92 | <p id="summary"> | |
| 93 | CPU <strong>${pct(v.cpu.busyPct)}</strong> over ${String(v.cpu.cores.length)} cores · | |
| 94 | load ${v.load ? v.load.map((l) => l.toFixed(2)).join(" ") : "—"} · | |
| 95 | hottest ${v.hottestC === null ? "—" : `${v.hottestC.toFixed(0)}°C`} · | |
| 96 | memory ${v.memory ? `${bytes(v.memory.usedBytes)} of ${bytes(v.memory.totalBytes)}` : "—"} · | |
| 97 | ${String(v.processes)} processes | |
| 98 | </p> | |
| 91 | html`<div id="summary" class="row-meta"> | |
| 92 | ${pill(`CPU ${pct(v.cpu.busyPct)}`, v.cpu.busyPct !== null && v.cpu.busyPct >= 90 ? "danger" : undefined)} | |
| 93 | ${pill(`${String(v.cpu.cores.length)} cores`)} | |
| 94 | ${pill(`load ${v.load ? v.load.map((l) => l.toFixed(2)).join(" ") : "—"}`)} | |
| 95 | ${pill(`hottest ${v.hottestC === null ? "—" : `${v.hottestC.toFixed(0)}°C`}`)} | |
| 96 | ${pill(`memory ${v.memory ? `${bytes(v.memory.usedBytes)} of ${bytes(v.memory.totalBytes)}` : "—"}`)} | |
| 97 | ${pill(`${String(v.processes)} processes`)} | |
| 98 | </div> | |
| 99 | 99 | <p class="small" id="cores" aria-label="Per-core CPU, percent">${cores}</p> |
| 100 | 100 | ${caps} |
| 101 | <p class="small mono" id="temps">${v.temps.map((t) => `${t.chip} ${t.label} ${t.celsius.toFixed(0)}°C`).join(" · ")}</p> | |
| 102 | </div>`, | |
| 101 | <p class="small mono" id="temps">${v.temps.map((t) => `${t.chip} ${t.label} ${t.celsius.toFixed(0)}°C`).join(" · ")}</p>`, | |
| 103 | 102 | { head: html`<h2>Machine</h2>` }, |
| 104 | 103 | )} |
| 105 | ${card( | |
| 106 | html`<div class="table-scroll"><table class="table" aria-label="Disks and network"> | |
| 107 | <thead><tr><th scope="col">Disk / interface</th><th scope="col">Used</th><th scope="col">Total / rate</th></tr></thead> | |
| 108 | <tbody> | |
| 109 | ${v.disks.map( | |
| 110 | (d) => html`<tr><td class="mono">${d.mount} <span class="muted small">${d.fs}</span></td> | |
| 111 | <td>${String(Math.round((d.usedBytes / d.totalBytes) * 100))}%</td><td>${bytes(d.totalBytes)}</td></tr>`, | |
| 112 | )} | |
| 113 | ${v.net.map( | |
| 114 | (n) => html`<tr><td class="mono">${n.name}</td><td>↓ ${n.rxBytesPerSecond === null ? "—" : `${bytes(n.rxBytesPerSecond)}/s`}</td> | |
| 115 | <td>↑ ${n.txBytesPerSecond === null ? "—" : `${bytes(n.txBytesPerSecond)}/s`}</td></tr>`, | |
| 116 | )} | |
| 117 | </tbody></table></div>`, | |
| 118 | { head: html`<h2>Disks and network</h2>`, pad: false }, | |
| 104 | ${listCard( | |
| 105 | [ | |
| 106 | ...v.disks.map((d) => | |
| 107 | row({ | |
| 108 | title: html`<span class="mono">${d.mount}</span>`, | |
| 109 | meta: html`${d.fs}`, | |
| 110 | trail: html`${pill(`${String(Math.round((d.usedBytes / d.totalBytes) * 100))}%`)} ${bytes(d.totalBytes)}`, | |
| 111 | }), | |
| 112 | ), | |
| 113 | ...v.net.map((n) => | |
| 114 | row({ | |
| 115 | title: html`<span class="mono">${n.name}</span>`, | |
| 116 | trail: html`↓ ${n.rxBytesPerSecond === null ? "—" : `${bytes(n.rxBytesPerSecond)}/s`} ↑ ${n.txBytesPerSecond === null ? "—" : `${bytes(n.txBytesPerSecond)}/s`}`, | |
| 117 | }), | |
| 118 | ), | |
| 119 | ], | |
| 120 | { head: html`<h2>Disks and network</h2>` }, | |
| 119 | 121 | )} |
| 120 | ${card( | |
| 121 | html`<div class="table-scroll"><table class="table" aria-label="Processes by what launched them"> | |
| 122 | <thead><tr><th scope="col">Launched for</th><th scope="col">Processes</th><th scope="col">CPU (cores)</th> | |
| 123 | <th scope="col">Memory</th><th scope="col">Oldest</th><th scope="col">Busiest</th></tr></thead> | |
| 124 | <tbody> | |
| 125 | ${v.groups.map( | |
| 126 | (g) => html`<tr data-kind="${g.kind}"><td class="mono">${g.label}</td><td>${String(g.procs)}</td> | |
| 127 | <td>${coresOf(g.cpuCores)}</td><td>${memoryOf(g)}</td><td>${span(g.oldestSeconds)}</td> | |
| 128 | <td class="small mono">${g.top.slice(0, 3).map((p) => `${p.comm} ${coresOf(p.cpuCores)}`).join(" · ")}</td></tr>`, | |
| 129 | )} | |
| 130 | </tbody></table></div>`, | |
| 131 | { head: html`<h2>Processes</h2>`, pad: false }, | |
| 122 | ${listCard( | |
| 123 | v.groups.map( | |
| 124 | (g) => html`<li data-kind="${g.kind}"><div class="row"> | |
| 125 | <span class="row-main"><span class="row-title mono">${g.label}</span> | |
| 126 | <span class="row-meta">${String(g.procs)} processes · oldest ${span(g.oldestSeconds)}${g.top.length ? html` · ${g.top.slice(0, 3).map((p) => `${p.comm} ${coresOf(p.cpuCores)}`).join(" · ")}` : null}</span></span> | |
| 127 | <span class="row-trail">${pill(`${coresOf(g.cpuCores)} cores`)} ${memoryOf(g)}</span> | |
| 128 | </div></li>`, | |
| 129 | ), | |
| 130 | { head: html`<h2>Processes</h2>` }, | |
| 132 | 131 | )} |
| 133 | 132 | ${memorySection(ctx)}`; |
| 134 | 133 | return page({ ctx, title: "System", body, wide: true, scriptJs: RELOAD }); |
test/system-telemetry.test.ts +4
| @@ -303,6 +303,10 @@ describe("telemetry:read: the owner grants it, an agent reads with it, nobody el | ||
| 303 | 303 | // Each check cell's memory beside its cap, and who holds the cap (#2410). |
| 304 | 304 | expect(html).toContain('data-cap="8589934592" data-cap-by="kernel">of 8.0 GiB (kernel cap)'); |
| 305 | 305 | expect(html).toMatch(/id="cell-caps" data-by="(kernel|watch)"/); |
| 306 | // Built from the system's own parts: pills and rows, no table, no style attribute (#2691). | |
| 307 | expect(html).toMatch(/id="summary"[^>]*>\s*<span class="pill"/); | |
| 308 | expect(html).toContain('<li data-kind="check"><div class="row">'); | |
| 309 | expect(html).not.toContain("<table"); | |
| 306 | 310 | const flowPage = await fetch(`${servers.url}/flow`, { headers: { cookie: await login("alice") } }); |
| 307 | 311 | expect(await flowPage.text()).toContain('id="duplicate-work" data-count="1"'); |
| 308 | 312 |
new above
offline