diff --git a/docs/release-control/v6/internal/subsystems/storage-recovery.md b/docs/release-control/v6/internal/subsystems/storage-recovery.md index 10115192b6..b3d1827925 100644 --- a/docs/release-control/v6/internal/subsystems/storage-recovery.md +++ b/docs/release-control/v6/internal/subsystems/storage-recovery.md @@ -648,6 +648,11 @@ recovery-specific mini report. Recovery summary cards should prefer concise counts, bars, and badge-backed coverage cues over tall explanatory copy blocks so the protected-item table takes over the page at roughly the same visual density as the infrastructure and workloads monitoring surfaces. +That same summary layout should also stay on the shared monitoring rhythm: +recovery should use the standard four-card `SummaryPanel` grid without letting +one oversized posture card dominate the full strip, so operators can scan +posture, freshness, footprint, and recent history at the same glance depth +they get on infrastructure and workloads. That same summary rule also applies within individual cards: recovery posture, freshness, attention, footprint, and history cards should favor compact rows and metric lists over stacked prose callouts so the summary strip reads like diff --git a/frontend-modern/src/components/Recovery/Recovery.tsx b/frontend-modern/src/components/Recovery/Recovery.tsx index 44b28c8230..c57d4e5eba 100644 --- a/frontend-modern/src/components/Recovery/Recovery.tsx +++ b/frontend-modern/src/components/Recovery/Recovery.tsx @@ -485,13 +485,13 @@ const Recovery: Component = () => { />
-
+
setWorkspaceView(value as RecoveryWorkspaceView)} ariaLabel="Recovery data view" listClass="flex flex-wrap items-center gap-2" - tabClass="min-h-8 px-2.5 py-1.5 text-sm" + tabClass="min-h-7 px-2.5 py-1 text-sm" tabs={[ { value: 'inventory', @@ -514,7 +514,7 @@ const Recovery: Component = () => { ]} /> 0}> - + Focused drill-in diff --git a/frontend-modern/src/components/Recovery/RecoveryProtectedInventorySection.tsx b/frontend-modern/src/components/Recovery/RecoveryProtectedInventorySection.tsx index e7223f9e70..2e517e7ee4 100644 --- a/frontend-modern/src/components/Recovery/RecoveryProtectedInventorySection.tsx +++ b/frontend-modern/src/components/Recovery/RecoveryProtectedInventorySection.tsx @@ -241,7 +241,7 @@ export const RecoveryProtectedInventorySection: Component< tone="card" class="overflow-hidden border-border-subtle bg-surface" > -
+
@@ -282,7 +282,7 @@ export const RecoveryProtectedInventorySection: Component<
-
+
{ expect(screen.getByText('Freshness')).toBeInTheDocument(); expect(screen.getByText('Recent History')).toBeInTheDocument(); expect(screen.getAllByText('Attention Queue').length).toBeGreaterThan(0); - expect(screen.getByText('Stale')).toBeInTheDocument(); + expect(screen.getAllByText('Stale').length).toBeGreaterThan(0); expect(screen.getAllByText('Attention').length).toBeGreaterThan(0); expect(screen.getByText('Recovery Points')).toBeInTheDocument(); expect(screen.getAllByText('Item Types').length).toBeGreaterThan(0); @@ -67,6 +67,6 @@ describe('RecoverySummary', () => { expect(screen.getByText('Platform Mix')).toBeInTheDocument(); expect(screen.getByText('2 protected')).toBeInTheDocument(); expect(screen.getByText('1 healthy')).toBeInTheDocument(); - expect(screen.getByText('Never succeeded')).toBeInTheDocument(); + expect(screen.getAllByText('Never succeeded').length).toBeGreaterThan(0); }); }); diff --git a/frontend-modern/src/components/Recovery/RecoverySummary.tsx b/frontend-modern/src/components/Recovery/RecoverySummary.tsx index 438813e984..c4902c1728 100644 --- a/frontend-modern/src/components/Recovery/RecoverySummary.tsx +++ b/frontend-modern/src/components/Recovery/RecoverySummary.tsx @@ -48,6 +48,30 @@ export const RecoverySummary: Component = (props) => { if (!activitySummary.startLabel || !activitySummary.endLabel) return null; return `${activitySummary.startLabel} to ${activitySummary.endLabel}`; }); + const attentionItems = createMemo(() => + [ + { + label: 'Stale', + count: summary().stale, + tone: 'amber', + }, + { + label: 'Never succeeded', + count: summary().neverSucceeded, + tone: 'rose', + }, + { + label: 'Attention', + count: attentionCount(), + tone: 'amber', + }, + { + label: 'Running', + count: postureSummary().running, + tone: 'blue', + }, + ].filter((item) => item.count > 0), + ); const handleTimeRangeChange = (range: string) => props.onTimeRangeChange?.(range as RecoverySummaryTimeRange); @@ -80,144 +104,130 @@ export const RecoverySummary: Component = (props) => { timeRangeLabels={RECOVERY_SUMMARY_TIME_RANGE_LABELS} testId="recovery-summary" > -
- -
-
-
-
-
Healthy
-
- {healthyCount()} -
-
-
-
Attention
-
- {attentionCount()} -
-
-
-
Protected
-
{summary().total}
-
-
-
-
-
- - {(segment) => ( -
0 ? 4 : 0)}%` }} - title={`${segment.label}: ${segment.count}`} - /> - )} - -
-
-
- - {(segment) => ( -
-
- - {segment.label} -
- - {segment.count} - -
- )} -
-
-
-
-
- Freshness - 0}> - - {summary().neverSucceeded} never succeeded - - -
-
- - {(bucket) => ( -
- - {bucket.label} - {bucket.count} -
- )} -
-
+ +
+
+
+
Healthy
+
+ {healthyCount()}
- -
-
- Attention Queue +
+
Attention
+
+ {attentionCount()}
-
- item.count > 0)} - > - {(item) => ( -
-
-
- - {item.label} -
- - {item.count} - -
-
{item.detail}
-
+
+
+
Protected
+
{summary().total}
+
+
+ +
+
+
+ + {(segment) => ( +
0 ? 4 : 0)}%` }} + title={`${segment.label}: ${segment.count}`} + /> )}
+
+ + {(segment) => ( +
+
+ + {segment.label} +
+ + {segment.count} + +
+ )} +
+
- -
+
+ + + +
+
+
+
Stale
+
{summary().stale}
+
+
+
Never succeeded
+
+ {summary().neverSucceeded} +
+
+
+
Running
+
+ {postureSummary().running} +
+
+
+ +
+
+ Attention Queue +
+
+ + {(item) => ( +
+
+ + {item.label} +
+ + {item.count} + +
+ )} +
+
+
+ +
+
+ Freshness Bands +
+
+ + {(bucket) => ( +
+ + {bucket.label} + {bucket.count} +
+ )} +
+
+
+
+
@@ -300,10 +310,13 @@ export const RecoverySummary: Component = (props) => { emptyMessage={props.seriesFailed?.() ? 'Trend data unavailable' : 'No recovery activity yet'} >
- -
{recentWindowLabel()}
-
+ +
+
Window
+
{recentWindowLabel()}
+
+
Recovery Points
{activity().totalEvents}