mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-10-24 04:33:17 +00:00
Merge remote-tracking branch 'origin/main' into feat/docker-silent-hosts
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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