From 0a3ff275ae6e212c04091e9b089f660849fc07d3 Mon Sep 17 00:00:00 2001 From: gorkem-bwl Date: Sat, 25 Apr 2026 23:27:35 -0400 Subject: [PATCH] feat(client): diagnostics page polish MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Stats and StatGauges use CSS grid (5 + 4 columns) so cards in each row are equal width; widths overridden against the legacy 25%-flex GradientBox so they fill their grid cell. - DetailGauge gains a maxWidth prop (default 225) so consumers in wide grids can opt out of the cap. - Section explainers above each row: "Runtime stats" + description, "Memory & CPU" + description (i18n driven, propagated to all 16 locales via pages.logs.diagnostics.sections). - Both grids render immediately with placeholder "—" values, so the initial paint shows the layout and values fill in as data loads (eliminates the layout shake when switching to the tab). --- .../src/Components/design-elements/Gauge.tsx | 4 +- client/src/Pages/Logs/TabDiagnostics.tsx | 35 ++++++++- .../src/Pages/Logs/components/StatGauges.tsx | 74 +++++++++++-------- client/src/Pages/Logs/components/Stats.tsx | 48 ++++++++---- 4 files changed, 113 insertions(+), 48 deletions(-) diff --git a/client/src/Components/design-elements/Gauge.tsx b/client/src/Components/design-elements/Gauge.tsx index fda42df3c..a377a5925 100644 --- a/client/src/Components/design-elements/Gauge.tsx +++ b/client/src/Components/design-elements/Gauge.tsx @@ -114,6 +114,7 @@ export const DetailGauge = ({ upperValue, lowerLabel, lowerValue, + maxWidth = 225, }: { title: string; progress: number; @@ -121,13 +122,14 @@ export const DetailGauge = ({ upperValue?: string | number; lowerLabel?: string; lowerValue?: string | number; + maxWidth?: number; }) => { const theme = useTheme(); return ( { const theme = useTheme(); + const { t } = useTranslation(); const { data: diagnostics, isLoading: _isLoading, @@ -16,9 +19,35 @@ export const TabDiagnostics = () => { } = useGet("/diagnostic/system", {}, { refreshInterval: 5000 }); return ( - - - + + + + + {t("pages.logs.diagnostics.sections.runtimeStats.title")} + + + {t("pages.logs.diagnostics.sections.runtimeStats.description")} + + + + + + + + {t("pages.logs.diagnostics.sections.memoryCpu.title")} + + + {t("pages.logs.diagnostics.sections.memoryCpu.description")} + + + + ); }; diff --git a/client/src/Pages/Logs/components/StatGauges.tsx b/client/src/Pages/Logs/components/StatGauges.tsx index 65c443831..9abcbbaed 100644 --- a/client/src/Pages/Logs/components/StatGauges.tsx +++ b/client/src/Pages/Logs/components/StatGauges.tsx @@ -1,4 +1,4 @@ -import Stack from "@mui/material/Stack"; +import Box from "@mui/material/Box"; import { DetailGauge } from "@/Components/design-elements"; import { getPercentage, formatPercentageFromWhole } from "@/Utils/FormatUtils"; @@ -11,62 +11,78 @@ interface StatGaugesProps { diagnostics: Diagnostics | null; } +const PLACEHOLDER = "—"; + export const StatGauges = ({ diagnostics }: StatGaugesProps) => { const theme = useTheme(); const { t } = useTranslation(); - if (!diagnostics) { - return null; - } - const heapTotalSize = getPercentage( - diagnostics?.v8HeapStats?.totalHeapSizeBytes, - diagnostics?.v8HeapStats?.heapSizeLimitBytes - ); + const heapTotalSize = diagnostics + ? getPercentage( + diagnostics.v8HeapStats?.totalHeapSizeBytes, + diagnostics.v8HeapStats?.heapSizeLimitBytes + ) + : 0; + const heapUsedSize = diagnostics + ? getPercentage( + diagnostics.v8HeapStats?.usedHeapSizeBytes, + diagnostics.v8HeapStats?.heapSizeLimitBytes + ) + : 0; + const actualHeapUsed = diagnostics + ? getPercentage( + diagnostics.v8HeapStats?.usedHeapSizeBytes, + diagnostics.v8HeapStats?.totalHeapSizeBytes + ) + : 0; + const cpuUsage = diagnostics?.cpuUsage?.usagePercentage ?? 0; - const heapUsedSize = getPercentage( - diagnostics?.v8HeapStats?.usedHeapSizeBytes, - diagnostics?.v8HeapStats?.heapSizeLimitBytes - ); - - const actualHeapUsed = getPercentage( - diagnostics?.v8HeapStats?.usedHeapSizeBytes, - diagnostics?.v8HeapStats?.totalHeapSizeBytes - ); + const fmt = (n: number) => (diagnostics ? formatPercentageFromWhole(n) : PLACEHOLDER); + const bytes = (n: number | undefined) => + diagnostics ? prettyBytes(n ?? 0) : PLACEHOLDER; return ( - *": { width: "100% !important" }, + }} > - + ); }; diff --git a/client/src/Pages/Logs/components/Stats.tsx b/client/src/Pages/Logs/components/Stats.tsx index 7bf343de3..9fe04fa9d 100644 --- a/client/src/Pages/Logs/components/Stats.tsx +++ b/client/src/Pages/Logs/components/Stats.tsx @@ -1,4 +1,4 @@ -import Stack from "@mui/material/Stack"; +import Box from "@mui/material/Box"; import { StatBox } from "@/Components/design-elements"; import prettyBytes from "pretty-bytes"; @@ -11,41 +11,59 @@ interface StatsProps { diagnostics: Diagnostics | null; } +const PLACEHOLDER = "—"; + export const Stats = ({ diagnostics }: StatsProps) => { const { t } = useTranslation(); const theme = useTheme(); - if (!diagnostics) { - return null; - } + const eventLoopDelay = diagnostics + ? prettyMilliseconds(diagnostics.eventLoopDelayMs ?? 0, { + millisecondsDecimalDigits: 2, + }) + : PLACEHOLDER; + const uptime = diagnostics + ? prettyMilliseconds(diagnostics.uptimeMs ?? 0, { hideSeconds: true }) + : PLACEHOLDER; + const usedHeap = diagnostics + ? prettyBytes(diagnostics.v8HeapStats?.usedHeapSizeBytes ?? 0) + : PLACEHOLDER; + const totalHeap = diagnostics + ? prettyBytes(diagnostics.v8HeapStats?.totalHeapSizeBytes ?? 0) + : PLACEHOLDER; + const osMemory = diagnostics + ? prettyBytes(diagnostics.osStats?.totalMemoryBytes ?? 0) + : PLACEHOLDER; return ( - *": { width: "100% !important" }, + }} > - + ); };