diff --git a/src/components/ServerTable.tsx b/src/components/ServerTable.tsx index 1ecd696..061b856 100644 --- a/src/components/ServerTable.tsx +++ b/src/components/ServerTable.tsx @@ -105,6 +105,37 @@ function Bar({ pct, label }: { pct: number | null; label?: string }) { ) } +// The widest form each formatter writes, in `ch`, measured with tabular figures. +// M is the widest unit letter, so each is measured in megabytes rather than the +// gigabytes a reading is more often in: `compact` spans "0B" to "1023M", that is +// 2.2 to 5.49; `bytes` reaches 7.19 at "1023 MB"; `rate` 10.09 at "1023.0 MB/s". +// Rounded up, since other UI fonts are a few percent wider than the one these +// were measured in. +const SLOT = { compact: 5.6, bytes: 7.3, rate: 10.2 } + +/** + * A figure that changes on every push, held in a slot wide enough for the widest + * form it can take, so a node moving from 19K/s to 8.19K/s leaves the rest of the + * line where it was. Right-aligned, so the unit keeps its place and the digits + * grow towards the arrow instead. + * + * `ch` is the width of a digit under the tabular figures this selects, so a slot + * follows whatever size it is drawn at, down to the 10px the table uses on a + * phone. The width travels as a custom property rather than `min-width` itself, + * which is what allows a caller to drop the reservation where the column is too + * narrow to hold it: an inline style would outrank the class that does so. + */ +function Num({ ch, className, children }: { ch: number; className?: string; children: ReactNode }) { + return ( + + {children} + + ) +} + function Expiry({ node }: { node: Node }) { const days = expiresIn(node) if (days === null) return {FOREVER} @@ -181,7 +212,15 @@ function Details({ node }: { node: Node }) { {m ? m.load.map((n) => n.toFixed(2)).join(" / ") : "—"} {m ? `${m.procs} · TCP ${m.tcp} · UDP ${m.udp}` : "—"} - {m ? `↓ ${rate(m.net_rx)} · ↑ ${rate(m.net_tx)}` : "—"} + + {m ? ( + <> + ↓ {rate(m.net_rx)} · ↑ {rate(m.net_tx)} + + ) : ( + "—" + )} + {flow(node.day_rx, node.day_tx)} {flow(node.month_rx, node.month_tx)} @@ -249,7 +288,19 @@ function Row({ node, index }: { node: Node; index: number }) { {m ? duration(m.uptime) : "—"} {m ? m.load[0].toFixed(2) : "—"} - {m ? `${compact(m.net_rx)} | ${compact(m.net_tx)}` : "— | —"} + {/* No reservation on a phone: the column is 21% of the panel, 60px at + 320px, against the 70px two slots and their separator need, and the + overflow disappears under the bar beside it. */} + + {m ? ( + <> + {compact(m.net_rx)} |{" "} + {compact(m.net_tx)} + + ) : ( + "— | —" + )} + @@ -291,10 +342,15 @@ export function ServerTable({ nodes }: { nodes: Node[] }) {

服务器

- 在线 {nodes.filter((n) => n.online).length} / {nodes.length} · ↓ {compact(sum((n) => n.metrics!.net_rx))}/s · ↑{" "} - {compact(sum((n) => n.metrics!.net_tx))}/s + {/* The online count is reserved for as many digits as the total has, + since it cannot exceed it. */} + 在线 {nodes.filter((n) => n.online).length} / {nodes.length} · ↓{" "} + {compact(sum((n) => n.metrics!.net_rx))}/s · ↑{" "} + {compact(sum((n) => n.metrics!.net_tx))}/s + + + 总流量 ↓ {bytes(totalRx)} · ↑ {bytes(totalTx)} - 总流量 ↓ {bytes(totalRx)} · ↑ {bytes(totalTx)}