From da74fb420958cc5de06b602bf48ea6fb1772e2e3 Mon Sep 17 00:00:00 2001 From: rcourtman <8825017+rcourtman@users.noreply.github.com> Date: Wed, 14 Oct 2026 00:21:18 +0100 Subject: [PATCH] Show one chart panel in phone drawers and the rest on request With history charts on every drawer's Overview, a phone stacked three or four chart panels before a guest's facts, a node's hardware or an agent's details: a guest drawer ran 1043px tall at 390px wide. Below the sm breakpoint the shared chart component now shows the first panel (Utilization) and any panel carrying an active alert, then one full-width "Show Network I/O, Disk I/O, Thermals" button that reveals the rest. The guest drawer drops to 657px. Wider layouts are unchanged. It is a CSS class on panels that stay mounted, so it adds no read and the hidden panels keep their data for when they are shown. --- .../subsystems/frontend-primitives.md | 5 ++- .../subsystems/performance-and-scalability.md | 6 ++- .../ec5c5b20f28306bf.json | 36 +++++++++++++++ .../Workloads/GuestDrawerHistory.tsx | 38 +++++++++++++++- .../GuestDrawerHistory.inspection.test.tsx | 44 +++++++++++++++++++ 5 files changed, 124 insertions(+), 5 deletions(-) create mode 100644 frontend-modern/browser-verification/ec5c5b20f28306bf.json diff --git a/docs/release-control/v6/internal/subsystems/frontend-primitives.md b/docs/release-control/v6/internal/subsystems/frontend-primitives.md index ee7417f984..96f4a1bd69 100644 --- a/docs/release-control/v6/internal/subsystems/frontend-primitives.md +++ b/docs/release-control/v6/internal/subsystems/frontend-primitives.md @@ -5478,7 +5478,10 @@ accessible value states each reading's average and range. Every history range picker (these drawers, storage disk and pool details) names its options through `formatHistoryRangeOptionLabel` in `historyChartModel.ts` ("1 hour", "24 hours", "7 days"), so one range is never shown as "24h", "24 hours" and "Last 24 hours" -in three places. Any +in three places. Below the `sm` breakpoint the chart panels stack, so a phone +shows the first panel (Utilization) plus any panel carrying an active alert, +and one full-width "Show " button (`aria-expanded`, hidden from +`sm` up) reveals the rest; wider layouts always show every panel. Any warning- or danger-toned detail row is lifted into a "Worth checking" section in front, whichever section owns it, so demotion never hides a reason a reading cannot be trusted. Guest diff --git a/docs/release-control/v6/internal/subsystems/performance-and-scalability.md b/docs/release-control/v6/internal/subsystems/performance-and-scalability.md index 650e679ffa..4a7f3d2170 100644 --- a/docs/release-control/v6/internal/subsystems/performance-and-scalability.md +++ b/docs/release-control/v6/internal/subsystems/performance-and-scalability.md @@ -4019,8 +4019,10 @@ the compact "Worth checking" list reuses rows already derived, and an alert's dashed trigger line reuses the alert the drawer already holds. The min-max band reads the `min`/`max` each stored bucket already carries: one extra path per series with any spread (none for flat data). Range picker option labels come -from one lookup table (`formatHistoryRangeOptionLabel`). None adds a read or -timer. +from one lookup table (`formatHistoryRangeOptionLabel`). The phone collapse of +later chart panels is a CSS class below `sm` on panels that stay mounted: it +hides nothing from the shared history read and adds no read or timer. None +adds a read or timer. The default stored-history legend resolves one latest timestamp from that same bounded, normalized panel index, then looks up each configured series at diff --git a/frontend-modern/browser-verification/ec5c5b20f28306bf.json b/frontend-modern/browser-verification/ec5c5b20f28306bf.json new file mode 100644 index 0000000000..93554da94e --- /dev/null +++ b/frontend-modern/browser-verification/ec5c5b20f28306bf.json @@ -0,0 +1,36 @@ +{ + "version": 1, + "base_sha": "96d307dcbe2042d70d981fe7ebc3dd2b6f35162f", + "verified_at": "2026-10-13T23:18:00Z", + "result": "passed", + "changed_paths": [ + "frontend-modern/src/components/Workloads/GuestDrawerHistory.tsx" + ], + "content_sha256": { + "frontend-modern/src/components/Workloads/GuestDrawerHistory.tsx": "11ffac0be9469fc1107e63c4e8c99fcd7df26fd401b76ac29664c29cde8a680a" + }, + "routes": [ + "/proxmox/overview", + "/standalone/machines" + ], + "viewports": [ + { + "width": 1440, + "height": 900 + }, + { + "width": 390, + "height": 844 + } + ], + "states": [ + "final built bytes of this tree (current sidecar head with PRs 2920/2921 and this change) embedded in a darwin binary, mock mode, headless Chromium on pulse-dev through a reverse tunnel, full-resolution element screenshots read by eye", + "390px: guest drawer (artifact-cache-310) shows only the Utilization panel, then a full-width \"Show Network I/O, Disk I/O\" button, then the facts strip; drawer height 657px against 1043px with every panel shown", + "390px: node drawer (Analytics A) and agent machine drawer each show Utilization and \"Show Network I/O, Disk I/O, Thermals\" (597px and 715px tall)", + "1440px: guest, node and agent drawers show every panel (3, 4 and 4) and no toggle; layout matches the previous pass" + ], + "interactions": [ + "clicked \"Show Network I/O, Disk I/O\" in the phone guest drawer: Network I/O and Disk I/O panels appeared, the button read \"Show fewer charts\" and the drawer grew to 1043px", + "measured visible panel boxes per drawer at both widths" + ] +} diff --git a/frontend-modern/src/components/Workloads/GuestDrawerHistory.tsx b/frontend-modern/src/components/Workloads/GuestDrawerHistory.tsx index a47a3a8ed7..95f723e349 100644 --- a/frontend-modern/src/components/Workloads/GuestDrawerHistory.tsx +++ b/frontend-modern/src/components/Workloads/GuestDrawerHistory.tsx @@ -71,6 +71,8 @@ interface GuestDrawerHistoryRangeSelectProps { interface GuestDrawerHistoryGroupChartProps { alerts?: Record; + /** Collapsed below the sm breakpoint until the reader asks for more charts. */ + hiddenOnPhone?: boolean; currentMetrics?: Record; deferredMetrics?: Record; group: GuestDrawerHistoryGroupConfig; @@ -472,8 +474,9 @@ const GuestDrawerHistoryGroupChart: Component return (
@@ -806,6 +809,12 @@ const GuestDrawerHistoryGroupChart: Component export const GuestDrawerHistory: Component = (props) => { const [touchInspection, setTouchInspection] = createSignal(false); + // On a phone every panel stacks, so a drawer's Overview ran four charts + // deep before its disks and details. Phones show the first panel, plus any + // panel with an active alert, and the rest on request. Wider layouts show + // every panel; the class only applies below the sm breakpoint. + const [showAllOnPhone, setShowAllOnPhone] = createSignal(false); + const gridId = `history-groups-${createUniqueId()}`; onMount(() => { void loadRuntimeCapabilities(); }); @@ -830,6 +839,13 @@ export const GuestDrawerHistory: Component = (props) => historyQuery.value(), ), ); + const groupHasAlert = (group: GuestDrawerHistoryGroupConfig) => + group.series.some((series) => Boolean(props.alerts?.[series.metric])); + const isPhoneCollapsed = (group: GuestDrawerHistoryGroupConfig, index: number) => + !showAllOnPhone() && index > 0 && !groupHasAlert(group); + const phoneCollapsedGroups = createMemo(() => + groups().filter((group, index) => index > 0 && !groupHasAlert(group)), + ); const hasHistoryPoints = createMemo(() => groups().some((group) => group.series.some( @@ -903,12 +919,14 @@ export const GuestDrawerHistory: Component = (props) => >
3 ? 'xl:grid-cols-4' : 'xl:grid-cols-3'}`} > - {(group) => ( + {(group, index) => ( = (props) => )}
+ 0 || showAllOnPhone()}> + +
diff --git a/frontend-modern/src/components/Workloads/__tests__/GuestDrawerHistory.inspection.test.tsx b/frontend-modern/src/components/Workloads/__tests__/GuestDrawerHistory.inspection.test.tsx index 853e320d4b..9984768f48 100644 --- a/frontend-modern/src/components/Workloads/__tests__/GuestDrawerHistory.inspection.test.tsx +++ b/frontend-modern/src/components/Workloads/__tests__/GuestDrawerHistory.inspection.test.tsx @@ -408,3 +408,47 @@ describe('drawer History range picker', () => { ]); }); }); + +describe('drawer History on a phone', () => { + const groupIds = () => + screen + .getAllByTestId('guest-history-group-chart') + .filter((chart) => chart.dataset.phoneCollapsed !== 'true') + .map((chart) => chart.dataset.historyGroup); + + it('keeps the first panel and collapses the rest behind one toggle', async () => { + vi.spyOn(ChartsAPI, 'getMetricsHistory').mockResolvedValue(response()); + render(() => ); + await screen.findAllByTestId('guest-history-group-chart'); + expect(groupIds()).toEqual(['utilization']); + for (const chart of screen.getAllByTestId('guest-history-group-chart').slice(1)) { + expect(chart).toHaveClass('max-sm:hidden'); + } + const toggle = screen.getByTestId('history-phone-toggle'); + expect(toggle).toHaveClass('sm:hidden'); + expect(toggle).toHaveAttribute('aria-expanded', 'false'); + expect(toggle).toHaveTextContent('Show Network I/O, Disk I/O'); + + fireEvent.click(toggle); + expect(toggle).toHaveAttribute('aria-expanded', 'true'); + expect(toggle).toHaveTextContent('Show fewer charts'); + expect(groupIds()).toEqual(['utilization', 'network', 'disk-io']); + for (const chart of screen.getAllByTestId('guest-history-group-chart')) { + expect(chart).not.toHaveClass('max-sm:hidden'); + } + }); + + it('keeps a panel with an active alert visible on a phone', async () => { + vi.spyOn(ChartsAPI, 'getMetricsHistory').mockResolvedValue(response()); + render(() => ( + + )); + await screen.findAllByTestId('guest-history-group-chart'); + expect(groupIds()).toEqual(['utilization', 'disk-io']); + expect(screen.getByTestId('history-phone-toggle')).toHaveTextContent('Show Network I/O'); + }); +});