- Item-first recovery inventory, with item class and latest-point posture kept visible
- in the main grid instead of buried in drill-in.
+
+ Item class and latest-point posture stay visible in the primary grid.
diff --git a/frontend-modern/src/components/Recovery/RecoverySummary.test.tsx b/frontend-modern/src/components/Recovery/RecoverySummary.test.tsx
index 9cebb687b5..caa4b42b7f 100644
--- a/frontend-modern/src/components/Recovery/RecoverySummary.test.tsx
+++ b/frontend-modern/src/components/Recovery/RecoverySummary.test.tsx
@@ -64,9 +64,10 @@ describe('RecoverySummary', () => {
expect(screen.getByText('Primary Platform')).toBeInTheDocument();
expect(screen.getByText('Platform Mix')).toBeInTheDocument();
expect(screen.getByText('2 protected')).toBeInTheDocument();
- expect(screen.getByText('2 platforms')).toBeInTheDocument();
- expect(screen.getByText('2 item types')).toBeInTheDocument();
expect(screen.getByText('1 healthy')).toBeInTheDocument();
+ expect(
+ screen.getByText((_, element) => element?.textContent === '2 item types across 2 platforms.'),
+ ).toBeInTheDocument();
expect(screen.getByText('Never succeeded')).toBeInTheDocument();
});
});
diff --git a/frontend-modern/src/components/Recovery/RecoverySummary.tsx b/frontend-modern/src/components/Recovery/RecoverySummary.tsx
index 285cced37a..ac8c33aa29 100644
--- a/frontend-modern/src/components/Recovery/RecoverySummary.tsx
+++ b/frontend-modern/src/components/Recovery/RecoverySummary.tsx
@@ -46,42 +46,57 @@ export const RecoverySummary: Component
= (props) => {
const healthyCount = createMemo(() => postureSummary().healthy);
const attentionCount = createMemo(() => postureSummary().attention);
+ const attentionSummary = createMemo(() => {
+ const parts: string[] = [];
+ if (postureSummary().failed > 0) {
+ parts.push(`${postureSummary().failed} failed`);
+ }
+ if (postureSummary().neverSucceeded > 0) {
+ parts.push(`${postureSummary().neverSucceeded} never succeeded`);
+ }
+ if (postureSummary().stale > 0) {
+ parts.push(`${postureSummary().stale} stale`);
+ }
+ if (postureSummary().warning > 0) {
+ parts.push(`${postureSummary().warning} warning`);
+ }
+ if (parts.length === 0 && postureSummary().running > 0) {
+ parts.push(`${postureSummary().running} running`);
+ }
+ return parts.slice(0, 3).join(' • ');
+ });
+ const recentWindowLabel = createMemo(() => {
+ const activitySummary = activity();
+ if (!activitySummary.startLabel || !activitySummary.endLabel) return null;
+ return `${activitySummary.startLabel} to ${activitySummary.endLabel}`;
+ });
return (
-
+
{summary().total} protected
-
-
- {itemCoverage().itemTypeCount} item type
- {itemCoverage().itemTypeCount === 1 ? '' : 's'}
-
-
-
-
- {platformCoverage().platformCount} platform
- {platformCoverage().platformCount === 1 ? '' : 's'}
-
-
-
+
+
{healthyCount()} healthy
0}>
-
+
+
{attentionCount()} attention
0}>
-
+
+
{postureSummary().running} running
@@ -104,39 +119,54 @@ export const RecoverySummary: Component = (props) => {
-
-
+
+
Recovery Posture
- Recovery readiness across the current protected estate, with unhealthy
- coverage surfaced before activity detail.
+ Recovery readiness across the current protected estate, with the attention
+ queue surfaced before timeline detail.
0}>
-
+
Attention Queue
-
+
{attentionCount()}
-
-
-
Protected
-
{summary().total}
-
-
+
+
Healthy
-
{healthyCount()}
+
+ {healthyCount()}
+
+
Recent successful protection within the current window.
-
+
Attention
-
{attentionCount()}
+
+ {attentionCount()}
+
+
+ {attentionSummary() || 'No active recovery risks.'}
+
+
+
+
Protected
+
+ {summary().total}
+
+
+ {itemCoverage().itemTypeCount} item type
+ {itemCoverage().itemTypeCount === 1 ? '' : 's'} across {platformCoverage().platformCount}{' '}
+ platform{platformCoverage().platformCount === 1 ? '' : 's'}.
+
@@ -167,8 +197,9 @@ export const RecoverySummary: Component
= (props) => {
)}
-
-
+
+
+
Freshness
@@ -190,7 +221,7 @@ export const RecoverySummary: Component
= (props) => {
-
+
Attention Queue
@@ -199,7 +230,7 @@ export const RecoverySummary: Component
= (props) => {
fallback={No active recovery risks.
}
>
-
+
{(item) => (
= (props) => {
-
+
Protected Footprint
-
-
-
Item Types
-
- {itemCoverage().itemTypeCount}
+
+
+
+
- Item Types
+ -
+ {itemCoverage().itemTypeCount}
+
-
-
-
Primary Item
-
- {itemCoverage().primaryItemLabel ?? 'n/a'}
+
+
Primary Item
+
+ {itemCoverage().primaryItemLabel ?? 'n/a'}
+
-
-
-
Platforms
-
- {platformCoverage().platformCount}
+
+
Platforms
+
+ {platformCoverage().platformCount}
+
-
-
-
Primary Platform
-
- {platformCoverage().primaryPlatformLabel ?? 'n/a'}
+
+
+ Primary Platform
+
+
+ {platformCoverage().primaryPlatformLabel ?? 'n/a'}
+
-
+
@@ -267,8 +302,10 @@ export const RecoverySummary: Component
= (props) => {
{(item) => (
-
-
+
+
{item.label}
{item.count}
@@ -293,8 +330,10 @@ export const RecoverySummary: Component
= (props) => {
{(item) => (
-
-
+
+
{item.label}
{item.count}
@@ -311,31 +350,49 @@ export const RecoverySummary: Component
= (props) => {
Recent History
-
-
-
-
-
- Recovery Points
-
-
- {activity().totalEvents}
-
-
-
-
Avg / Day
-
- {activity().averagePerDay.toFixed(1)}
-
-
-
-
Days Active
-
- {activity().activeDays}
-
-
+
+
+ {recentWindowLabel()}
+
+
+
+
+
+
- Recovery Points
+ -
+ {activity().totalEvents}
+
-
+
+
Avg / Day
+
+ {activity().averagePerDay.toFixed(1)}
+
+
+
+
Days Active
+
+ {activity().activeDays}
+
+
+
+
Peak Day
+
+ {activity().busiestLabel ?? 'n/a'}
+
+
+
+
+ Latest Activity
+
+
+ {activity().latestLabel ?? 'n/a'}
+
+
+
+
+
+
= (props) => {
-
Peak Day
-
- {activity().busiestLabel ?? 'n/a'}
+
+ Peak Throughput
+
+
+ {activity().busiestCount}
- {activity().busiestCount} recovery point
- {activity().busiestCount === 1 ? '' : 's'}
+ {activity().busiestLabel ?? 'n/a'}
-
Latest Activity
-
- {activity().latestLabel ?? 'n/a'}
+
+ Latest Throughput
+
+
+ {activity().latestCount}
- {activity().latestCount} recovery point
- {activity().latestCount === 1 ? '' : 's'}
+ {activity().latestLabel ?? 'n/a'}
diff --git a/frontend-modern/src/components/Recovery/__tests__/Recovery.test.tsx b/frontend-modern/src/components/Recovery/__tests__/Recovery.test.tsx
index 9298adbbe3..75129daf88 100644
--- a/frontend-modern/src/components/Recovery/__tests__/Recovery.test.tsx
+++ b/frontend-modern/src/components/Recovery/__tests__/Recovery.test.tsx
@@ -316,8 +316,11 @@ describe('Recovery', () => {
it('surfaces item-first recovery coverage in the unified summary', async () => {
render(() =>
);
- expect(await screen.findByText('2 item types')).toBeInTheDocument();
- expect(await screen.findByText('2 platforms')).toBeInTheDocument();
+ expect(
+ await screen.findByText(
+ (_, element) => element?.textContent === '2 item types across 2 platforms.',
+ ),
+ ).toBeInTheDocument();
expect(screen.getByText('Primary Item')).toBeInTheDocument();
expect(screen.getByText('Primary Platform')).toBeInTheDocument();
expect(screen.getByText('Platform Mix')).toBeInTheDocument();