From 906651fa591f36ba14fbe3e7ee5fb65fd772406c Mon Sep 17 00:00:00 2001 From: rcourtman <8825017+rcourtman@users.noreply.github.com> Date: Mon, 5 Oct 2026 01:25:56 +0100 Subject: [PATCH 1/4] Lead the vSphere overview with vCenter's health signals vCenter alarms only appeared on the vSphere page's fourth tab, Health. The Overview fetched hosts and VMs alone, so a host tinted yellow there had no visible reason and datastore or network alarms were not on the page at all. While the Overview is open it now also reads the Health tab's resources, and when vCenter reports any signals the existing health table leads the page above the hosts, without its filter toolbar. Nothing is added when vCenter is quiet, and filtering stays on the Health tab. This is the pattern the TrueNAS Overview already uses for its health alerts. --- .../subsystems/frontend-primitives.md | 9 ++++ .../internal/subsystems/unified-resources.md | 13 ++++++ .../6c5983f639b94947.json | 34 ++++++++++++++ .../src/features/vmware/VmwarePageSurface.tsx | 18 +++++++- .../VmwarePageSurface.contract.test.tsx | 44 +++++++++++++++++++ .../__tests__/VsphereAlertsTable.test.tsx | 18 ++++++++ 6 files changed, 135 insertions(+), 1 deletion(-) create mode 100644 frontend-modern/browser-verification/6c5983f639b94947.json diff --git a/docs/release-control/v6/internal/subsystems/frontend-primitives.md b/docs/release-control/v6/internal/subsystems/frontend-primitives.md index f6fda0fd90..2c8c2f6652 100644 --- a/docs/release-control/v6/internal/subsystems/frontend-primitives.md +++ b/docs/release-control/v6/internal/subsystems/frontend-primitives.md @@ -8304,3 +8304,12 @@ chain at desktop and phone widths with keyboard/touch refresh, class-dark CSS, 403/503 controls and pending recovery. Unit controls separately cover current request ownership and disposal. No mutation, route, navigation entry, queue policy, permission or recipient-delivery contract is added by these states. + +### Platform overviews may lead with their own incident table + +A platform Overview that leads with its provider's health signals reuses +that platform's existing incident table rather than a new summary card: +the vSphere Overview renders `VsphereAlertsTable` with `showToolbar={false}` +above its hosts table, only when signals exist, matching the TrueNAS +Overview's health-alert table. Filtering stays on the platform's Health tab. + diff --git a/docs/release-control/v6/internal/subsystems/unified-resources.md b/docs/release-control/v6/internal/subsystems/unified-resources.md index ae04260b9a..5b32979bee 100644 --- a/docs/release-control/v6/internal/subsystems/unified-resources.md +++ b/docs/release-control/v6/internal/subsystems/unified-resources.md @@ -5859,3 +5859,16 @@ The Actions page now hides its Patrol Watch only guidance while the presentation only: the action audit list, approval counts, review dialog, routing and every recorded action outcome are read and rendered exactly as before, and no resource or action identity changes. + +### vSphere Overview leads with vCenter health signals + +The vSphere Overview answers "is anything in trouble?" before inventory, as +the TrueNAS Overview does. While the Overview is active the page also +hydrates the Health tab's resource query (hosts, VMs, datastores and +networks) and, when `buildVmwareIncidentRows` returns any rows from those +resources' canonical `incidents`, renders the shared `VsphereAlertsTable` +above the hosts table without its filter toolbar. Nothing renders when +vCenter reports no signals. The Overview's own model, workload snapshot and +navigation facets still come from the Overview query; the Health tab keeps +the filterable table. + diff --git a/frontend-modern/browser-verification/6c5983f639b94947.json b/frontend-modern/browser-verification/6c5983f639b94947.json new file mode 100644 index 0000000000..5bd288246b --- /dev/null +++ b/frontend-modern/browser-verification/6c5983f639b94947.json @@ -0,0 +1,34 @@ +{ + "version": 1, + "base_sha": "efe6fc21ae10a6bf173cc17e6203aaf6c97dc130", + "verified_at": "2026-10-05T00:25:56Z", + "result": "passed", + "changed_paths": [ + "frontend-modern/src/features/vmware/VmwarePageSurface.tsx" + ], + "content_sha256": { + "frontend-modern/src/features/vmware/VmwarePageSurface.tsx": "cab90be0a9a6868aebac459576f63b93cd4700a8c08fc3ddae4092b7e1fc04bf" + }, + "routes": [ + "/vmware/overview", + "/vmware/health" + ], + "viewports": [ + { + "width": 1440, + "height": 1000 + }, + { + "width": 390, + "height": 844 + } + ], + "states": [ + "Mock Lab vCenter with five warning signals (two datastores, one host, two networks). Before: the Overview showed only hosts and VMs, esxi-07 tinted yellow with no reason; the alarms lived on the fourth tab.", + "After at 1440: a Health Signals table (Resource, Severity, Signal, vCenter, Entity, Started) leads the Overview above Hosts, with no filter toolbar; the yellow esxi-07 row is explained by the Host has VMware overall status yellow signal.", + "Phone 390x844: the same table shows Resource, Signal, VC and Age above the hosts table; rows open the existing detail drawer." + ], + "interactions": [ + "Loaded the Overview and Health tabs signed in at both widths and inspected the screenshots; component tests cover the toolbar-less table and the overview hydrating the Health query only on Overview and Health." + ] +} diff --git a/frontend-modern/src/features/vmware/VmwarePageSurface.tsx b/frontend-modern/src/features/vmware/VmwarePageSurface.tsx index 704ad5107e..2f2468ae51 100644 --- a/frontend-modern/src/features/vmware/VmwarePageSurface.tsx +++ b/frontend-modern/src/features/vmware/VmwarePageSurface.tsx @@ -53,6 +53,7 @@ import type { Resource } from '@/types/resource'; import { VsphereHostsTable } from './VsphereHostsTable'; import { VMWARE_TAB_SPECS, + buildVmwareIncidentRows, buildVmwarePageModel, getVmwarePageTabSpecs, type VmwarePageModel, @@ -207,7 +208,10 @@ export function VmwarePageSurface() { const activeTab = createMemo(() => tabs().some((tab) => tab.id === requestedTab()) ? requestedTab() : 'overview', ); - const shouldHydrateTab = (tab: VmwarePageTabId) => activeTab() === tab; + // The overview leads with vCenter's own health signals, so it reads the + // Health tab's resource set as well as its own hosts and VMs. + const shouldHydrateTab = (tab: VmwarePageTabId) => + activeTab() === tab || (tab === 'health' && activeTab() === 'overview'); const createTabResources = (tab: VmwarePageTabId) => useUnifiedResources({ query: VMWARE_RESOURCE_QUERY_BY_TAB[tab], @@ -258,6 +262,9 @@ export function VmwarePageSurface() { }, ); const model = createMemo(() => buildVmwarePageModel(resources(), activityTimeline() ?? [])); + const overviewIncidents = createMemo(() => + activeTab() === 'overview' ? buildVmwareIncidentRows(healthResources.resources()) : [], + ); const agentUpdateTargetVersion = createMemo( () => updateStore.versionInfo()?.agentUpdateTargetVersion, ); @@ -343,6 +350,15 @@ export function VmwarePageSurface() { />
+ 0}> + + { ); }); + it('leads the overview with vCenter health signals only when vCenter reports any', () => { + const host = makeResource({ + id: 'esxi-host-1', + type: 'agent', + vmware: { entityType: 'host', managedObjectId: 'host-1' }, + }); + const alarmedDatastore = makeResource({ + id: 'datastore-1', + type: 'storage', + vmware: { entityType: 'datastore', managedObjectId: 'datastore-1' }, + incidents: [{ code: 'vmware_alarm', severity: 'warning', summary: 'Datastore latency' }], + } as Partial & Pick); + // Each query gets its own fixture so the test proves the signals come + // from the Health query while the overview model keeps its own resources. + const byCacheKey = (healthResources: Resource[]) => + mockUseUnifiedResources.mockImplementation((options: { cacheKey?: string }) => { + const resources = options.cacheKey === 'vmware-health' ? healthResources : [host]; + return { + resources: () => resources, + loading: () => false, + error: () => null, + refetch: vi.fn().mockResolvedValue(resources), + }; + }); + + byCacheKey([host]); + const { unmount } = render(() => ); + expect(screen.queryByTestId('alerts-table')).not.toBeInTheDocument(); + unmount(); + + byCacheKey([host, alarmedDatastore]); + render(() => ); + expect(screen.getByTestId('platform-section-tabs')).toHaveAttribute('data-active', 'overview'); + expect(screen.getByTestId('alerts-table')).toBeInTheDocument(); + expect(screen.getByTestId('hosts-table')).toHaveAttribute('data-rows', '1'); + expect( + mockUseUnifiedResources.mock.calls.some( + ([options]) => + (options as { cacheKey?: string }).cacheKey === 'vmware-health' && + (options as { enabled: () => boolean }).enabled(), + ), + ).toBe(true); + }); + it('reuses the source-scoped page snapshot for the vSphere workload surface', async () => { const refetch = vi.fn().mockResolvedValue([]); const resources = [ diff --git a/frontend-modern/src/features/vmware/__tests__/VsphereAlertsTable.test.tsx b/frontend-modern/src/features/vmware/__tests__/VsphereAlertsTable.test.tsx index 81c10176d5..389dd05d76 100644 --- a/frontend-modern/src/features/vmware/__tests__/VsphereAlertsTable.test.tsx +++ b/frontend-modern/src/features/vmware/__tests__/VsphereAlertsTable.test.tsx @@ -42,6 +42,24 @@ afterEach(() => { }); describe('VsphereAlertsTable', () => { + it('renders every signal without the filter toolbar for the overview lead-in', () => { + render(() => ( + } + emptyTitle="No signals" + emptyDescription="No signals" + showToolbar={false} + /> + )); + + // The overview shows the signals as they are; filtering stays on Health. + expect(screen.queryByRole('button', { name: /Attention/ })).not.toBeInTheDocument(); + expect(screen.queryByPlaceholderText('Search vSphere health')).not.toBeInTheDocument(); + expect(document.querySelectorAll('[data-vsphere-alert-row]')).toHaveLength(1); + expect(screen.getByText('Health Signals')).toBeInTheDocument(); + }); + it('renders native vSphere health signal rows with inline details', async () => { const host = makeHost(); const informational = makeHost({ From 3998582278cce518671b45bd40a1db208e2ad376 Mon Sep 17 00:00:00 2001 From: rcourtman <8825017+rcourtman@users.noreply.github.com> Date: Mon, 5 Oct 2026 02:39:28 +0100 Subject: [PATCH 2/4] Take main's frontend-primitives contract ahead of the merge Main appended frontend-primitives notes at the same place this branch did. Taking main's file here lets main merge cleanly. The vSphere note is restored right after the merge. --- .../subsystems/frontend-primitives.md | 75 +++++++++++++++---- 1 file changed, 62 insertions(+), 13 deletions(-) diff --git a/docs/release-control/v6/internal/subsystems/frontend-primitives.md b/docs/release-control/v6/internal/subsystems/frontend-primitives.md index 2c8c2f6652..b982416db6 100644 --- a/docs/release-control/v6/internal/subsystems/frontend-primitives.md +++ b/docs/release-control/v6/internal/subsystems/frontend-primitives.md @@ -249,6 +249,24 @@ Fresh linked Pulse-agent disk evidence with no reason remains unmarked, and non-VM rows do not inherit QGA guidance. The cue adds no action or tab stop; touch on it continues through the existing row disclosure to the full reason. +The opened guest Overview applies the same admission to every filesystem, not +only the aggregate row or its status sentence. A valid `prev-` value is labelled +**Last known** beside its mount identity and used/capacity bytes; retained values +do not receive current utilization bars or threshold colours. Other VM read +reasons and explicit disk telemetry unavailability show **Usage unavailable**, +retaining identity and known capacity without promoting old used bytes. Missing +or invalid retained usage is unavailable, not an empty healthy filesystem. The +read explanation precedes the filesystem breakdown and remains visible before +any sample. Clearing a lock alone cannot clear a still-supplied reason. A fresh +same-guest read restores the normal accessible utilization bar; independent LXC +and linked-agent readings with no VM reason retain their existing semantics. + +Verification: `GuestDrawerOverview.filesystem-provenance.test.tsx`, the existing +filesystem regressions and builder controls in `DiskList.test.tsx`, plus +`browser-tests/guest-filesystem-provenance.cjs` using the full production guest +drawer on desktop/light/dark and touch WebKit. These are synthetic presentation +and input proofs, not native backup/thaw, covered-filesystem writes or resumption. + Verification: mounted `GuestRow.test.tsx` and `MetricMiniSparkline.test.tsx` regressions and `browser-tests/guest-disk-provenance.cjs` exercise retained and pre-sample states, display/lens changes, current-value semantics and same-row @@ -3240,7 +3258,9 @@ default` instead of fusing provider and badge text such as existing table header; Proxmox derives cluster and standalone-node context through `platformEstateOverviewModel.ts` and supplies it to `ProxmoxNodesTable.tsx` - rather than creating a page-level summary. The existing + rather than creating a page-level summary. A single-cluster estate shows that + cluster's name in the same header slot in place of the count, kept + visible while searching, because the repeated Cluster column is dropped. The existing `platformEstateOverviewVisible` preference now governs these inline totals from the shared View menu, preserving the global browser-persisted choice across platform workload surfaces. A page with adjacent totals, such as @@ -7418,6 +7438,11 @@ presentation. The compact row remains actionable: plain-language rationale and provider history/permission limitations live one disclosure deeper beside bounded restore evidence. A posture fetch failure must keep the evidence inspectable and show unknown, never a locally inferred healthy fallback. +The bounded restore evidence keeps the newest point and the newest completed +point of each source before filling with the newest overall, so a run of guest +snapshots or a running job cannot push the latest usable backup out of the +disclosure. A coverage strip footnote is +plain muted text under the legend, not another counter or badge row. Protection table controls continue to compose shared filter, table, status, counter, loading, error, disclosure, and inline-detail primitives. The bounded @@ -8067,12 +8092,14 @@ backend deletion or destination delivery. ### Large platform notices keep the inventory in view -`PlatformOutdatedAgentNotice` previews at most three affected names and exposes -the full list through a keyboard-operable button when more hosts are outdated. -The count, update guidance and action link remain visible. This keeps a -large-estate stale-agent warning from pushing the platform inventory and -Storage search below the phone viewport while preserving every affected name -on demand. The component test pins collapsed, expanded and collapsed-again +`PlatformOutdatedAgentNotice` is maintenance guidance, not an incident, so it +renders as one sentence in the info tone: the count, what the update brings, +a keyboard-operable "Which hosts?" toggle (any count above one) that reveals +every affected name, and the upgrade link inline in the same line rather than +on an action row. This keeps the stale-agent notice from pushing the platform +inventory and Storage search below the fold at desktop and phone widths, and +from competing with real warnings for attention, while preserving every +affected name on demand. The component test pins collapsed, expanded and collapsed-again states; 1440px, 768px and 390px browser checks verify placement and overflow. The shared `InlineNotice` action text uses opaque 800-level colors for its four tones. The current Tailwind configuration overrides several 900-level palette @@ -8305,11 +8332,33 @@ chain at desktop and phone widths with keyboard/touch refresh, class-dark CSS, request ownership and disposal. No mutation, route, navigation entry, queue policy, permission or recipient-delivery contract is added by these states. -### Platform overviews may lead with their own incident table +### Drawer History does not interpolate across missing observations -A platform Overview that leads with its provider's health signals reuses -that platform's existing incident table rather than a new summary card: -the vSphere Overview renders `VsphereAlertsTable` with `showToolbar={false}` -above its hosts table, only when signals exist, matching the TrueNAS -Overview's health-alert table. Filtering stays on the platform's Health tab. +The shared `GuestDrawerHistory` renderer connects a series only across +consecutive stored times in that configured panel. If another series has a +stored observation at an intervening time and this series does not, its line +stops. Each isolated observation, including a measured zero, remains visible +as a point; separate continuous segments retain all their original values, +timestamps and common-window coordinates. Independently sampled series must +not imply simultaneous observations or a continuous trend through absent data. +A visible, SVG-described explanation names the affected configured series and +states the limit: missing readings do not identify an outage's cause or duration. +There is no elapsed-time/cadence heuristic, synthetic zero, invented observation, +new API field or collector change. Times outside the configured panel cannot +split its series. Entirely absent series retain the existing missing/fallback +policy. Live or retained snapshot carriers cannot repair a stored gap; only a +matching stored response can. Source/range/access isolation, transient retained +reads and exact-time keyboard/pointer/touch inspection remain in force. + +`GuestDrawerHistory.gaps.test.tsx` and `guestHistorySegments.test.ts` cover +split lines, singleton recovery, zero rates, interleaved sampling, multiple +segments, configured-group isolation, invalid points, elapsed-time controls, +live updates, stored refresh and denied/transient reads. `GuestDrawer.test.tsx` +checks the full drawer's live-state recovery and unchanged request budget. +Updated pointer/sparse +regressions preserve their original missing-value and fallback obligations. +`browser-tests/history-observation-gaps.cjs` exercises the production full guest +drawer with synthetic APIs in Chromium/WebKit desktop and 390/320-pixel light +and dark views. This is presentation acceptance, not native backup/QGA safety, +provider outage attribution, installed recovery or release availability. From 3cd0e7e0eb1ddc3051edb1a36bfec2b1350842bd Mon Sep 17 00:00:00 2001 From: rcourtman <8825017+rcourtman@users.noreply.github.com> Date: Mon, 5 Oct 2026 02:45:06 +0100 Subject: [PATCH 3/4] Restore the vSphere overview incident-table contract note Re-adds this branch's frontend-primitives note after taking main's file for a clean merge. --- .../v6/internal/subsystems/frontend-primitives.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/docs/release-control/v6/internal/subsystems/frontend-primitives.md b/docs/release-control/v6/internal/subsystems/frontend-primitives.md index b982416db6..13e329be3e 100644 --- a/docs/release-control/v6/internal/subsystems/frontend-primitives.md +++ b/docs/release-control/v6/internal/subsystems/frontend-primitives.md @@ -8362,3 +8362,11 @@ regressions preserve their original missing-value and fallback obligations. drawer with synthetic APIs in Chromium/WebKit desktop and 390/320-pixel light and dark views. This is presentation acceptance, not native backup/QGA safety, provider outage attribution, installed recovery or release availability. + +### Platform overviews may lead with their own incident table + +A platform Overview that leads with its provider's health signals reuses +that platform's existing incident table rather than a new summary card: +the vSphere Overview renders `VsphereAlertsTable` with `showToolbar={false}` +above its hosts table, only when signals exist, matching the TrueNAS +Overview's health-alert table. Filtering stays on the platform's Health tab. From 47598c02909b4b66f4a89837170f445d8acb5f29 Mon Sep 17 00:00:00 2001 From: rcourtman <8825017+rcourtman@users.noreply.github.com> Date: Mon, 5 Oct 2026 03:06:45 +0100 Subject: [PATCH 4/4] Move this branch's frontend-primitives note off the end of the file Every lane appends to the end of this contract, so a note there conflicts with each merge to main. Taking main's file with the note placed beside the platform-page sections lets main merge cleanly now and later. --- .../subsystems/frontend-primitives.md | 20 +++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/docs/release-control/v6/internal/subsystems/frontend-primitives.md b/docs/release-control/v6/internal/subsystems/frontend-primitives.md index 13e329be3e..c3f9830d0e 100644 --- a/docs/release-control/v6/internal/subsystems/frontend-primitives.md +++ b/docs/release-control/v6/internal/subsystems/frontend-primitives.md @@ -8090,6 +8090,14 @@ read, confirm clear, then release the obsolete response at desktop and phone widths. Scripted API responses establish component behaviour, not installed backend deletion or destination delivery. +### Platform overviews may lead with their own incident table + +A platform Overview that leads with its provider's health signals reuses +that platform's existing incident table rather than a new summary card: +the vSphere Overview renders `VsphereAlertsTable` with `showToolbar={false}` +above its hosts table, only when signals exist, matching the TrueNAS +Overview's health-alert table. Filtering stays on the platform's Health tab. + ### Large platform notices keep the inventory in view `PlatformOutdatedAgentNotice` is maintenance guidance, not an incident, so it @@ -8363,10 +8371,10 @@ drawer with synthetic APIs in Chromium/WebKit desktop and 390/320-pixel light and dark views. This is presentation acceptance, not native backup/QGA safety, provider outage attribution, installed recovery or release availability. -### Platform overviews may lead with their own incident table +### Threshold group headers carry a defaults line -A platform Overview that leads with its provider's health signals reuses -that platform's existing incident table rather than a new summary card: -the vSphere Overview renders `VsphereAlertsTable` with `showToolbar={false}` -above its hosts table, only when signals exist, matching the TrueNAS -Overview's health-alert table. Filtering stays on the platform's Health tab. +`CollapsibleSection` takes an optional `defaultsSummary` rendered under the +title as a muted line (`data-thresholds-defaults-summary`) that wraps only +between items on phones and truncates from `sm` up, and is hidden while +`isGloballyDisabled` is set, where it would promise alerts that cannot fire. Callers pass the shared summary helper's output rather than +composing per-section strings.