From 33af19ba0c61ac19d5807ae51ca242c9bd0d5cd4 Mon Sep 17 00:00:00 2001 From: Denozordec Date: Tue, 4 Aug 2026 19:57:18 +0700 Subject: [PATCH] Refactor MePoolPanel component to integrate DC and writer metrics, enhancing data presentation with new RTT and coverage indicators. Update TypeScript interfaces for improved data handling and parsing of DC and writer statuses. Adjust runtime page to fetch and display new metrics, ensuring comprehensive monitoring of ME pool performance. --- .../src/components/runtime/me-pool-panel.tsx | 317 ++++++++++++++++-- apps/web/src/lib/telemt.ts | 135 ++++++++ apps/web/src/routes/runtime.tsx | 23 +- 3 files changed, 451 insertions(+), 24 deletions(-) diff --git a/apps/web/src/components/runtime/me-pool-panel.tsx b/apps/web/src/components/runtime/me-pool-panel.tsx index b8f352b..c216ce2 100644 --- a/apps/web/src/components/runtime/me-pool-panel.tsx +++ b/apps/web/src/components/runtime/me-pool-panel.tsx @@ -3,67 +3,124 @@ import { Badge } from '@/components/reui/badge' import { Frame, FrameDescription, FrameHeader, FramePanel, FrameTitle } from '@/components/reui/frame' import { MetricListFrame, MetricRow, StatusBadge } from '@/components/metric-list' +import { cn } from '@telemt/ui/lib/utils' +import { Progress } from '@telemt/ui/components/progress' import { formatEpoch, formatNumber, + formatRttMs, + type DcStatusData, + type MeWritersData, type RuntimeMePoolStateData, type RuntimeMeQualityData, } from '@/lib/telemt' +function rttTone(ms: number | null | undefined): 'success-light' | 'warning-light' | 'destructive-light' | 'secondary' { + if (ms == null || !Number.isFinite(ms)) return 'secondary' + if (ms < 80) return 'success-light' + if (ms < 180) return 'warning-light' + return 'destructive-light' +} + +function coverageTone(pct: number | null | undefined): string { + const n = Number(pct) + if (!Number.isFinite(n)) return '**:data-[slot=progress-indicator]:bg-muted-foreground' + if (n >= 90) return '**:data-[slot=progress-indicator]:bg-emerald-500' + if (n >= 60) return '**:data-[slot=progress-indicator]:bg-amber-500' + return '**:data-[slot=progress-indicator]:bg-red-500' +} + +function WriterStateBadge({ + state, + degraded, + draining, +}: { + state?: string + degraded?: boolean + draining?: boolean +}) { + if (draining) return draining + if (degraded) return degraded + if (state === 'active') return active + if (state === 'warm') return warm + return {state ?? '—'} +} + /** - * ME Telemt status — stats-7 writers strip + Frame detail panels. - * Preview: https://reui.io/preview/base/stats-7 + * ME Telemt status — stats-7 + DC/endpoint RTT tables (telemt_panel SoT). + * Preview: https://reui.io/preview/base/stats-7 · https://reui.io/preview/base/list-11 * Docs: https://reui.io/blocks · https://reui.io/docs/components/base/frame */ export function MePoolPanel({ pool, quality, + dcs, + writers, isLoading, }: { pool: RuntimeMePoolStateData quality: RuntimeMeQualityData + dcs: DcStatusData + writers: MeWritersData isLoading?: boolean }) { const enabled = pool.enabled !== false - const writers = pool.data?.writers + const poolWriters = pool.data?.writers const generations = pool.data?.generations const hardswap = pool.data?.hardswap const refill = pool.data?.refill const q = quality.data + const dcRows = + (dcs.dcs?.length ? dcs.dcs : null) ?? + (q?.dc_rtt ?? []).map((row) => ({ + dc: row.dc, + rtt_ms: row.rtt_ema_ms, + alive_writers: row.alive_writers, + required_writers: row.required_writers, + coverage_pct: row.coverage_pct, + endpoints: [] as string[], + endpoint_writers: [] as Array<{ endpoint?: string; active_writers?: number }>, + available_endpoints: undefined as number | undefined, + available_pct: undefined as number | undefined, + load: undefined as number | undefined, + fresh_alive_writers: undefined as number | undefined, + fresh_coverage_pct: undefined as number | undefined, + })) + + const writerRows = [...(writers.writers ?? [])].sort((a, b) => { + const ra = a.rtt_ema_ms ?? Number.POSITIVE_INFINITY + const rb = b.rtt_ema_ms ?? Number.POSITIVE_INFINITY + return ra - rb + }) + const writerCards = [ { title: 'Живые writers', subtitle: 'alive_non_draining', - value: formatNumber(writers?.alive_non_draining), - badge: { - text: 'healthy', - variant: 'success-light' as const, - }, - subtext: `всего ${formatNumber(writers?.total)} · contour active ${formatNumber(writers?.contour?.active)}`, + value: formatNumber(poolWriters?.alive_non_draining ?? writers.summary?.alive_writers), + badge: { text: 'healthy', variant: 'success-light' as const }, + subtext: `всего ${formatNumber(poolWriters?.total ?? writers.summary?.configured_endpoints)} · coverage ${formatNumber(writers.summary?.coverage_pct)}%`, }, { title: 'Draining', subtitle: 'draining', - value: formatNumber(writers?.draining), - badge: { - text: 'drain', - variant: 'warning-light' as const, - }, - subtext: `contour draining ${formatNumber(writers?.contour?.draining)}`, + value: formatNumber(poolWriters?.draining), + badge: { text: 'drain', variant: 'warning-light' as const }, + subtext: `contour draining ${formatNumber(poolWriters?.contour?.draining)}`, }, { title: 'Degraded', subtitle: 'degraded', - value: formatNumber(writers?.degraded), + value: formatNumber(poolWriters?.degraded), badge: { - text: writers && (writers.degraded ?? 0) > 0 ? 'attention' : 'ok', + text: poolWriters && (poolWriters.degraded ?? 0) > 0 ? 'attention' : 'ok', variant: - writers && (writers.degraded ?? 0) > 0 + poolWriters && (poolWriters.degraded ?? 0) > 0 ? ('destructive-light' as const) : ('secondary' as const), }, - subtext: `health healthy ${formatNumber(writers?.health?.healthy)}`, + subtext: `endpoints available ${formatNumber(writers.summary?.available_endpoints)} / ${formatNumber(writers.summary?.configured_endpoints)}`, }, ] @@ -74,7 +131,7 @@ export function MePoolPanel({
ME Pool - Middle-End writers, generations и refill ·{' '} + Middle-End writers, DC latency и endpoints ·{' '} {pool.generated_at_epoch_secs ? formatEpoch(pool.generated_at_epoch_secs) : isLoading @@ -97,9 +154,9 @@ export function MePoolPanel({

) : (

- Writers health: healthy {formatNumber(writers?.health?.healthy)} · - degraded {formatNumber(writers?.health?.degraded)} · draining{' '} - {formatNumber(writers?.health?.draining)} + Writers health: healthy {formatNumber(poolWriters?.health?.healthy)} · + degraded {formatNumber(poolWriters?.health?.degraded)} · draining{' '} + {formatNumber(poolWriters?.health?.draining)}

)} @@ -133,6 +190,220 @@ export function MePoolPanel({ + {/* Per-DC servers + RTT — list-11 DNA. Preview: https://reui.io/preview/base/list-11 */} + 0} + okLabel={`${dcRows.length} DC`} + failLabel={dcs.reason ? String(dcs.reason) : 'Нет DC'} + /> + } + > + {dcRows.length === 0 ? ( +

+ {dcs.reason ?? + 'Нет данных /v1/stats/dcs (нужен minimal_runtime_enabled) и нет dc_rtt из me_quality.'} +

+ ) : ( +
+ + + + + + + + + + + + + {dcRows.map((row) => { + const coverage = Number(row.coverage_pct ?? 0) + const endpoints = + row.endpoints?.length + ? row.endpoints + : (row.endpoint_writers ?? []).map((e) => e.endpoint).filter(Boolean) + return ( + + + + + + + + + ) + })} + +
DCRTTWritersCoverageLoadEndpoints
+ DC {formatNumber(row.dc)} + + + {formatRttMs(row.rtt_ms)} + + + {formatNumber(row.alive_writers)} / {formatNumber(row.required_writers)} + +
+ + + {Number.isFinite(coverage) ? `${Math.round(coverage)}%` : '—'} + +
+
+ {row.load != null ? formatNumber(row.load) : '—'} + + {endpoints.length === 0 ? ( + + ) : ( +
    + {endpoints.slice(0, 4).map((ep) => { + const writersOnEp = (row.endpoint_writers ?? []).find( + (w) => w.endpoint === ep, + ) + return ( +
  • + {ep} + {writersOnEp?.active_writers != null ? ( + + {formatNumber(writersOnEp.active_writers)} w + + ) : null} +
  • + ) + })} + {endpoints.length > 4 ? ( +
  • + +{endpoints.length - 4} ещё +
  • + ) : null} +
+ )} +
+
+ )} +
+ + {/* Per-writer / endpoint latency */} + 0} + okLabel={`${writerRows.length} writers`} + failLabel={writers.reason ? String(writers.reason) : 'Нет writers'} + /> + } + > + {writerRows.length === 0 ? ( +

+ {writers.reason ?? + 'Нет данных /v1/stats/me-writers. Включите minimal_runtime_enabled в Telemt API.'} +

+ ) : ( +
+ + + + + + + + + + + + + {writerRows.slice(0, 40).map((w) => ( + + + + + + + + + ))} + +
EndpointDCСостояниеRTTКлиентыGen
{w.endpoint ?? '—'} + {w.dc != null ? formatNumber(w.dc) : '—'} + + + + + {formatRttMs(w.rtt_ema_ms)} + + + {formatNumber(w.bound_clients)} + + {formatNumber(w.generation)} +
+ {writerRows.length > 40 ? ( +

+ Показаны 40 из {writerRows.length} writers (сортировка по RTT). +

+ ) : null} +
+ )} +
+ + {(q?.family_states ?? []).length > 0 ? ( + +
+ + + + + + + + + + + {(q?.family_states ?? []).map((f) => ( + + + + + + + ))} + +
FamilyStateFail streakRecover streak
{f.family ?? '—'} + + {f.state ?? '—'} + + {formatNumber(f.fail_streak)} + {formatNumber(f.recover_success_streak)} +
+
+
+ ) : null} +
api('/api/telemt/runtime/me_quality').catch(() => null), refetchInterval: 10_000, }) + const meDcs = useQuery({ + queryKey: ['telemt', 'stats_dcs'], + queryFn: () => api('/api/telemt/stats/dcs').catch(() => null), + refetchInterval: 10_000, + }) + const meWriters = useQuery({ + queryKey: ['telemt', 'stats_me_writers'], + queryFn: () => api('/api/telemt/stats/me-writers').catch(() => null), + refetchInterval: 10_000, + }) const gates = useQuery({ queryKey: ['telemt', 'gates'], queryFn: () => api('/api/telemt/runtime/gates').catch(() => null), @@ -54,6 +66,8 @@ function RuntimePage() { const pool = parseMePoolState(mePool.data) const quality = parseMeQuality(meQuality.data) + const dcs = parseDcStatus(meDcs.data) + const writers = parseMeWriters(meWriters.data) const gateData = asRecord(unwrapData(gates.data) ?? gates.data) const conn = asRecord(unwrapData(connSummary.data) ?? connSummary.data) const connPayload = asRecord(conn.data ?? conn) @@ -127,7 +141,14 @@ function RuntimePage() {