diff --git a/src/Web/StellaOps.Web/src/app/features/dashboard-v3/dashboard-v3.component.ts b/src/Web/StellaOps.Web/src/app/features/dashboard-v3/dashboard-v3.component.ts
index 0928b4f2c..a6ca18bef 100644
--- a/src/Web/StellaOps.Web/src/app/features/dashboard-v3/dashboard-v3.component.ts
+++ b/src/Web/StellaOps.Web/src/app/features/dashboard-v3/dashboard-v3.component.ts
@@ -1,9 +1,9 @@
/**
- * Dashboard V3 - Mission Board
- * Sprint: SPRINT_20260218_012_FE_ui_v2_rewire_dashboard_v3_mission_board (D7-01 through D7-05)
+ * Dashboard V3 - Mission Board (3-column redesign)
+ * Sprint: SPRINT_20260315_005_FE_dashboard_3col_real_api
*
- * Release mission board: aggregates environment risk, SBOM state, reachability,
- * and data-integrity signals. Summarises; does not duplicate domain ownership.
+ * Layout: CSS Grid with security posture (1/3) | environments + actions (2/3)
+ * Data: Real API calls to vulnerability stats, advisory source status, and context store.
*/
import {
@@ -12,13 +12,29 @@ import {
computed,
inject,
signal,
+ OnInit,
} from '@angular/core';
-import { TitleCasePipe, UpperCasePipe } from '@angular/common';
import { RouterLink } from '@angular/router';
+import { catchError, take } from 'rxjs/operators';
+import { of } from 'rxjs';
+
import {
PlatformContextEnvironment,
PlatformContextStore,
} from '../../core/context/platform-context.store';
+import {
+ VULNERABILITY_API,
+ type VulnerabilityApi,
+} from '../../core/api/vulnerability.client';
+import type { VulnerabilityStats } from '../../core/api/vulnerability.models';
+import {
+ SourceManagementApi,
+ type SourceStatusResponse,
+} from '../integrations/advisory-vex-sources/source-management.api';
+import {
+ AUTH_SERVICE,
+ type AuthService,
+} from '../../core/auth/auth.service';
interface EnvironmentCard {
id: string;
@@ -35,33 +51,38 @@ interface EnvironmentCard {
lastDeployedAt: string;
}
-interface NightlyOpsSignal {
- id: string;
- label: string;
- status: 'ok' | 'warn' | 'fail';
- detail: string;
+interface AdvisoryFeedSummary {
+ totalSources: number;
+ enabledSources: number;
+ healthySources: number;
+ failedSources: number;
+ loaded: boolean;
}
-// MissionSummary removed — dashboard now computes from real environment data
-
@Component({
selector: 'app-dashboard-v3',
standalone: true,
- imports: [RouterLink, TitleCasePipe, UpperCasePipe],
+ imports: [RouterLink],
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
-
+
@if (hasNoEnvironments()) {
-
+
} @else {
-
-
-
-
{{ filteredEnvironments().length }}
-
Environments
-
View all
-
+
+
+
+
-
-
-
- {{ healthyCount() }}
-
-
Healthy Environments
-
Ops detail
-
-
+
+
+
+
+
+
{{ filteredEnvironments().length }}
+
Environments
+
+ 0">
+
{{ blockedCount() }}
+
Blocked
+
+ 0">
+
{{ degradedCount() }}
+
Degraded
+
+
+
+
+ {{ healthyCount() }}
+
+
Healthy
+
+
+
+
+
+
+
+
+ @for (env of filteredEnvironments(); track env.id) {
+
+
+
+
+
+ SBOM
+
+ {{ env.sbomFreshness }}
+
+
+
+ CritR
+ 0">
+ {{ env.critRCount }}
+
+
+
+ HighR
+ 0">
+ {{ env.highRCount }}
+
+
+
+ B/I/R
+ {{ env.birCoverage }}
+
+
+ Pending
+ 0">
+ {{ env.pendingApprovals }}
+
+
+
+
+
+
+ }
+
+ @if (filteredEnvironments().length === 0) {
+
+
No environments match the current filter.
+
+ }
+
+
+
+
+ @if (riskEnvironments().length > 0) {
+
+
+
+
+
+
+ | Region/Env |
+ Health |
+ SBOM |
+ CritR |
+ B/I/R |
+ Action |
+
+
+
+ @for (env of riskEnvironments(); track env.id) {
+
+ | {{ env.region }} / {{ env.name }} |
+ {{ env.deployStatus }} |
+ {{ env.sbomFreshness }} |
+ 0">{{ env.critRCount }} |
+ {{ env.birCoverage }} |
+
+
+ Open
+
+ |
+
+ }
+
+
+
+
+ }
+
+
+
+
+
}
-
-