From 752f59b09b3f0b390253d42ba698dca5275b40ae 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" }, + }} > - + ); };