mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-10-24 04:33:17 +00:00
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.
This commit is contained in:
@@ -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.
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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."
|
||||
]
|
||||
}
|
||||
@@ -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<VmwarePageTabId>(() =>
|
||||
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() {
|
||||
/>
|
||||
<Show when={activeTab() === 'overview'}>
|
||||
<div class="space-y-4">
|
||||
<Show when={overviewIncidents().length > 0}>
|
||||
<VsphereAlertsTable
|
||||
incidents={overviewIncidents()}
|
||||
emptyIcon={vmwareIcon()}
|
||||
emptyTitle="No active vSphere health signals"
|
||||
emptyDescription="vSphere triggered alarms and overall health signals appear here when vCenter reports them."
|
||||
showToolbar={false}
|
||||
/>
|
||||
</Show>
|
||||
<VmwareOverview
|
||||
model={model}
|
||||
metricDisplayMode={metricDisplayMode}
|
||||
|
||||
@@ -186,6 +186,50 @@ describe('VmwarePageSurface contract', () => {
|
||||
);
|
||||
});
|
||||
|
||||
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<Resource> & Pick<Resource, 'id' | 'type'>);
|
||||
// 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(() => <VmwarePageSurface />);
|
||||
expect(screen.queryByTestId('alerts-table')).not.toBeInTheDocument();
|
||||
unmount();
|
||||
|
||||
byCacheKey([host, alarmedDatastore]);
|
||||
render(() => <VmwarePageSurface />);
|
||||
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 = [
|
||||
|
||||
@@ -42,6 +42,24 @@ afterEach(() => {
|
||||
});
|
||||
|
||||
describe('VsphereAlertsTable', () => {
|
||||
it('renders every signal without the filter toolbar for the overview lead-in', () => {
|
||||
render(() => (
|
||||
<VsphereAlertsTable
|
||||
incidents={buildVmwareIncidentRows([makeHost()])}
|
||||
emptyIcon={<span />}
|
||||
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({
|
||||
|
||||
Reference in New Issue
Block a user