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] 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({