Reorder dashboard summary before problem resources

Refs #1429
This commit is contained in:
rcourtman 2026-04-19 19:22:03 +01:00
parent 9de093725f
commit f8d0839806
3 changed files with 49 additions and 4 deletions

View file

@ -1286,6 +1286,12 @@ page should pass the live alert list into `RecentAlertsPanel` and let the
alert-owned surface derive its own summary and acknowledgement state instead of
rebuilding alert summary counts or alert-action runtime inside the
storage/recovery-governed dashboard route.
That same route shell also owns the top-of-dashboard scan order. When
`frontend-modern/src/pages/Dashboard.tsx` renders both the KPI strip and the
problem-resources table, the route must keep the operator snapshot above the
detail table so the page reads as action queue, summary, then problem detail
rather than dropping operators straight into a long exception list before the
dashboard baseline is visible.
The shared recovery type contract must be pinned the same way:
`frontend-modern/src/types/recovery.ts` must stay on the explicit
`recovery-product-surface` proof path instead of riding indirectly on route or

View file

@ -278,10 +278,7 @@ export default function Dashboard() {
findingsNeedingAttention={actions.findingsNeedingAttention()}
/>
{/* 2. Problem Resources Table — only when problems exist */}
<ProblemResourcesTable problems={overview().problemResources} />
{/* 3. KPI Strip — always visible */}
{/* 2. KPI Strip — always visible */}
<KPIStrip
infrastructure={{
total: overview().infrastructure.total,
@ -303,6 +300,9 @@ export default function Dashboard() {
}}
/>
{/* 3. Problem Resources Table — only when problems exist */}
<ProblemResourcesTable problems={overview().problemResources} />
{/* 4–5. Customizable widgets: Trend Charts, Recent Alerts */}
<For each={widgetGroups()}>
{(group) =>

View file

@ -119,6 +119,12 @@ describe('Dashboard page module contract', () => {
reconnectSpy.mockReset();
navigateSpy.mockReset();
overviewMock.health.totalResources = 0;
overviewMock.infrastructure.total = 0;
overviewMock.infrastructure.byStatus = {};
overviewMock.workloads.total = 0;
overviewMock.workloads.running = 0;
overviewMock.workloads.stopped = 0;
overviewMock.problemResources = [];
overviewMock.storage.total = 0;
overviewMock.storage.totalCapacity = 0;
overviewMock.storage.totalUsed = 0;
@ -200,4 +206,37 @@ describe('Dashboard page module contract', () => {
expect(screen.getByText('Last recovery point over 24 hours ago')).toBeInTheDocument();
expect(screen.getAllByText(/1\.95 KB \/ 3\.91 KB/i)).toHaveLength(2);
});
it('keeps the KPI strip above problem resources so the dashboard snapshot reads before detail', () => {
overviewMock.health.totalResources = 5;
overviewMock.infrastructure.total = 5;
overviewMock.infrastructure.byStatus = { online: 5 };
overviewMock.workloads.total = 12;
overviewMock.workloads.running = 9;
overviewMock.problemResources = [
{
resource: {
id: 'storage-1',
type: 'storage',
name: 'Storage 1',
displayName: 'Storage 1',
platformId: 'storage-1',
platformType: 'truenas',
sourceType: 'api',
status: 'offline',
} as DashboardOverview['problemResources'][number]['resource'],
problems: ['Offline'],
worstValue: 200,
},
];
render(() => <DashboardPage />);
const kpiLabel = screen.getByText('Infrastructure');
const problemHeading = screen.getByRole('heading', { name: 'Problem Resources' });
expect(kpiLabel.compareDocumentPosition(problemHeading) & Node.DOCUMENT_POSITION_FOLLOWING).toBe(
Node.DOCUMENT_POSITION_FOLLOWING,
);
});
});