diff --git a/docs/release-control/v6/internal/subsystems/frontend-primitives.md b/docs/release-control/v6/internal/subsystems/frontend-primitives.md index f0161157db..d22ca2458d 100644 --- a/docs/release-control/v6/internal/subsystems/frontend-primitives.md +++ b/docs/release-control/v6/internal/subsystems/frontend-primitives.md @@ -8100,6 +8100,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 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({