From f8ad8d916c1875fdfc25919a2b6ebbf4b93eaf3e Mon Sep 17 00:00:00 2001 From: arsam Date: Fri, 18 Sep 2026 23:20:09 +0330 Subject: [PATCH] Import source: web/src/components/StatusRing Import the existing local project snapshot. This commit organizes existing files; it does not represent newly developed functionality. --- web/src/components/StatusRing.tsx | 237 ++++++++++++++++++++++++++++++ 1 file changed, 237 insertions(+) create mode 100644 web/src/components/StatusRing.tsx diff --git a/web/src/components/StatusRing.tsx b/web/src/components/StatusRing.tsx new file mode 100644 index 0000000..4b1051f --- /dev/null +++ b/web/src/components/StatusRing.tsx @@ -0,0 +1,237 @@ +import { memo, useMemo } from "react"; +import { motion } from "motion/react"; +import type { LiveTarget } from "../lib/types"; +import { arcPath, circumference, latencyScale, polar } from "../lib/geometry"; +import { fmtLatency, fmtUptime, pctSign } from "../lib/format"; +import { springs, STATE_COLOR, useUI } from "../lib/ui"; +import { useSpringNumber } from "../hooks/useSpringNumber"; + +const C = 60; // center +const R_TRACK = 44; // uptime ring radius +const R_LAT = 36; // latency gauge radius +const LAT_SWEEP = 270; // latency gauge max sweep, degrees + +/** 60 watch-face ticks; every fifth is longer — the "fine instrument" detail. */ +function Ticks() { + const ticks = useMemo(() => { + const lines = []; + for (let i = 0; i < 60; i++) { + const major = i % 5 === 0; + const a = i * 6; + const p1 = polar(C, C, major ? 50 : 51.4, a); + const p2 = polar(C, C, 53, a); + lines.push( + , + ); + } + return lines; + }, []); + return {ticks}; +} + +interface StatusRingProps { + target: LiveTarget; + /** Uptime fraction 0..100 for the selected window. */ + uptime?: number; + size?: number; + /** Mount-stagger delay in seconds (boot sequence). */ + delay?: number; +} + +/** + * Circular visualization #1 — the radial status ring. + * Uptime fills the outer arc; latency reads on an inner brass gauge; + * every completed probe lands as a visible pulse. + */ +export const StatusRing = memo(function StatusRing({ + target, + uptime, + size = 150, + delay = 0, +}: StatusRingProps) { + const { lang, reduced } = useUI(); + const color = STATE_COLOR[target.state]; + + const frac = (uptime ?? 0) / 100; + const circ = circumference(R_TRACK); + + const latMS = target.last?.latency_ms ?? target.latency.p50_ms ?? 0; + const latSweep = latencyScale(latMS) * LAT_SWEEP; + const latCirc = circumference(R_LAT); + + const displayUptime = useSpringNumber(uptime ?? 0); + + const arcTransition = reduced + ? { duration: 0 } + : { ...springs.arc, delay }; + + return ( + + + + {/* track */} + + + {/* soft glow under the arc — a wide faint twin, no filters */} + + + {/* uptime arc — springs to its value, never snaps */} + + + {/* latency gauge — brass, log scale 1ms..2.5s */} + + + + + + {/* live pulse — a probe just landed */} + {!reduced && target.pulse > 0 && ( + + )} + + {/* state ripple — a confirmed transition radiates outward */} + {!reduced && target.stateChangedAt > 0 && ( + + )} + + {/* center figures */} + + + {uptime !== undefined ? fmtUptime(lang, displayUptime) : "—"} + + {pctSign(lang)} + + + {/* re-keyed per probe so fresh data visibly lands */} + + {target.last ? fmtLatency(lang, latMS) : "·"} + + + + ); +});