Dashboard Data Flow
Overview
The Dashboard Data Flow describes how the Stella Ops Console (Web UI) assembles a security-posture overview and presents it to users. The dashboard provides visibility into vulnerability counts, advisory-feed health, environment topology, and personal activity across the tenant’s managed assets.
Reconciliation note (verified against
src/Webon 2026-05-30). The dashboard is composed client-side, not by a single server-side aggregation endpoint. The AngularDashboardV3Component(src/Web/StellaOps.Web/src/app/features/dashboard-v3/dashboard-v3.component.ts) fans out to several independent backend APIs from the browser and merges the results into a persona-aware “Mission Board”. There is noGET /api/v1/dashboardendpoint and no Platform-side fan-out (/internal/stats,/internal/verdicts/summary, etc.) backing the live dashboard — those were never implemented. A separateGET /api/v1/dashboard/summaryPack-adapter endpoint does exist on the Platform service, but it returns hardcoded demo data (IsDemo: true) and the Console does not consume it (see Pack-Adapter Dashboard Summary).
Business Value: Operators gain immediate visibility into their security posture without querying multiple systems, with the layout and lenses tailored to the signed-in user’s role (persona).
Actors
| Actor | Type | Role |
|---|---|---|
| User | Human | Views the persona-aware Mission Board, follows links to deep-dive surfaces |
| Console (Web UI) | System | Composes the dashboard client-side; calls multiple APIs in parallel and merges results |
| Gateway | Service | Routes, authenticates (JWT), and stamps identity headers/envelopes |
| Findings Security | Service | Provides the tenant/scoped production risk aggregate (GET /api/risk/aggregated-status) |
| Concelier / Excititor (Advisory Sources) | Service | Provides advisory & VEX source status (GET /api/v1/advisory-sources/status) |
| Platform Context | Service | Provides regions, environments, and scope preferences (/api/v2/context/*) |
| Notify / Notifier | Service | Provides the personal activity feed (developer persona) |
Prerequisites
- User authenticated via Authority (OAuth/OIDC); the auth interceptor attaches
Authorization: Bearer {jwt}. - Tenant context established. The Console sends the tenant via the
X-StellaOps-TenantIdheader (constantStellaOpsHeaders.Tenantinsrc/Web/StellaOps.Web/src/app/core/http/stella-ops-headers.ts). The legacyX-Tenant-Idform is not used. - The user’s scopes determine which persona (and therefore which lenses) the dashboard shows. With no qualifying scopes the dashboard falls back to the neutral “Overview” persona.
- Backends are reachable for live data; otherwise lenses degrade gracefully (loaders, empty states, honest defaults) rather than showing fabricated values.
Flow Diagram
┌─────────────────────────────────────────────────────────────────────────────────┐
│ Dashboard Data Flow (client-side composition) │
└─────────────────────────────────────────────────────────────────────────────────┘
┌────┐ ┌──────────────────────┐ ┌─────────┐ ┌──────────────────────────┐
│User│ │ Console (DashboardV3) │ │ Gateway │ │ Backend APIs │
└─┬──┘ └──────────┬───────────┘ └────┬────┘ └────────────┬─────────────┘
│ Open Dashboard │ │ │
│───────────────────>│ │ │
│ │ Resolve active persona │ │
│ │ from auth.scopes() │ │
│ │───────┐ │ │
│ │<──────┘ │ │
│ │ │ │
│ │ (parallel client calls; Bearer JWT + X-StellaOps-TenantId)
│ │ │ │
│ │ GET /api/v2/context/regions ─────────────────> │ Platform Context
│ │ GET /api/v2/context/preferences ─────────────> │ Platform Context
│ │ GET /api/v2/context/environments ────────────> │ Platform Context
│ │ GET /api/risk/aggregated-status ─────────────> │ Findings Security
│ │ GET /api/v1/advisory-sources/status ─────────> │ Advisory Sources
│ │ GET (notifications refresh) ─────────────────> │ Notify
│ │ │ │
│ │<──── responses (each lens loads independently) ─│
│ │ │ │
│ Render lens grid │ Merge into signals; │ │
│ (persona-filtered) │ compute derived counts │ │
│<───────────────────│ │ │
Step-by-Step
1. User Opens Dashboard
- The user navigates to the Console home route, which renders
DashboardV3Component(“Mission Board”). - The Angular SPA is served as static assets through the gateway.
2. Authentication & Persona Resolution
- Auth state is managed by
AuthService/AuthSessionStore; an HTTP interceptor attaches theAuthorization: Bearer {jwt}header. If the session is invalid, the app routes to the Authority login flow. - The dashboard derives the active persona from the user’s scopes via
DashboardPersonaService(src/Web/StellaOps.Web/src/app/core/services/dashboard-persona.service.ts). Personas, in default-priority order, are:devops(“Operations”) — release/orchestration/policy/scheduler scopessecurity(“Security”) —vuln:view/vuln:investigate/vuln:operate,findings:read,risk:read, exception/advisory/VEX/policy-review scopesit(“Compliance”) —authority.audit.read,analytics.read,policy:audit, and admin scopes (admin/tenant:admin/signer:admin)developer(“My work”) — scanner/sbom/findings/vuln/release-read scopesoverview— always available; neutral fallback (no scope requirement)
- The highest-priority qualifying persona is the default; an explicit override is remembered in
localStorage(stellaops.dashboard.persona). The persona drives which lenses render and their grid spans.
3. Client-Side Data Fan-Out
On init (ngOnInit), the component issues parallel calls directly from the browser. Each lens loads and degrades independently — there is no single aggregate request.
| Data | Source service | Endpoint (browser → gateway) | Client |
|---|---|---|---|
| Regions | Platform Context | GET /api/v2/context/regions | PlatformContextStore |
| Scope preferences | Platform Context | GET /api/v2/context/preferences (PUT to persist) | PlatformContextStore |
| Environments | Platform Context | GET /api/v2/context/environments?regions=… | PlatformContextStore |
| Vulnerability posture | Findings Security | GET /api/risk/aggregated-status | RiskHttpClient.getAggregatedStatus() |
| Advisory/VEX feed status | Advisory Sources | GET /api/v1/advisory-sources/status | SourceManagementApi.getStatus() |
| Personal activity feed | Notify | notifications store refresh (developer persona) | NotificationsStore |
Notes grounded in source:
RiskHttpClient.getAggregatedStatus()calls the Findings.Security aggregate under the exactfindings:readpolicy. Region/environment selections are forwarded as normalized scope parameters; tenant identity remains bound to the authenticated tenant and tenant header.- Scanner’s
/api/v1/vulnerabilities/statusbelongs only to its explicitly enabled non-production fixture. Production dashboard and Security Posture pages do not call it and do not allowlist its typedscanner.vulnerabilities.not_availableresponse. - On any error each loader clears the affected value and surfaces a partial/unavailable state with recovery instead of fabricating zeroes.
4. Lens Composition & Derived Values
The component holds the responses in signals and computes derived values for the persona-filtered lens grid (visibleLensCards() → selectLensCards(registry, persona, scopes)):
- Risk funnel / vulnerability summary — from the Findings aggregate:
total,bySeverity, andcriticalOpen(critical findings not dispositioned asmitigated). - Feed status — derived from
SourceStatusResponse.sources: total, enabled, healthy (lastCheck.isHealthy === true), failed counts. - Environment health / SBOM health / env-risk table — from the context store’s environments, mapped to
EnvironmentCards. Honest defaults apply: until a real backend reports per-environment scan facts, each card defaults todeployStatus: 'unknown',sbomFreshness: 'missing', and zeroed counts (resolveStatusSeed). There is no fabricated fallback data. - Pending actions — computed from pending approvals, blocked/degraded environment counts, and
criticalOpen. - Developer activity — newest notifications from
NotificationsStore. - Platform health bar — service health (context initialized & no error), feed health, and a “Security” dot driven by whether vuln stats loaded. The Evidence and DLQ dots are currently static placeholders.
When the tenant has no environments, the dashboard shows an empty-install “Bootstrap Release Control” setup guide instead of populated risk lenses.
5. Response Shapes (real contracts)
Vulnerability posture — GET /api/risk/aggregated-status (TypeScript models AggregatedRiskStatus / FindingsPostureStats in src/Web/StellaOps.Web/src/app/core/api/risk.models.ts):
interface FindingsPostureStats {
total: number;
bySeverity: Record<'critical' | 'high' | 'medium' | 'low' | 'unknown', number>;
criticalOpen: number;
computedAt: string; // aggregate generation time; nullable `asOf` carries source currency
traceId: string;
}
Advisory/VEX feed status — GET /api/v1/advisory-sources/status (model SourceStatusResponse in source-management.api.ts):
interface SourceStatusResponse {
sources: Array<{
sourceId: string;
enabled: boolean;
lastCheck?: { isHealthy: boolean; status: string; checkedAt: string; /* … */ } | null;
syncSupported?: boolean;
syncState?: string;
readyForSync?: boolean;
}>;
}
Platform context — /api/v2/context/{regions,environments,preferences} (models in platform-context.store.ts): PlatformContextRegion[], PlatformContextEnvironment[], and PlatformContextPreferences.
Data Contracts
Request Headers (every lens call)
| Header | Required | Description |
|---|---|---|
Authorization | Yes | Bearer JWT (attached by the auth interceptor) |
X-StellaOps-TenantId | Yes | Tenant identifier (constant StellaOpsHeaders.Tenant) |
X-StellaOps-TraceId | No | Observability trace id (stamped by VulnerabilityHttpClient) |
X-StellaOps-RequestId | No | Per-request id (stamped by VulnerabilityHttpClient) |
Content-Type | On POST/PUT | application/json |
Pack-Adapter Dashboard Summary (NOT the live source)
The Platform service exposes GET /api/v1/dashboard/summary (src/Platform/StellaOps.Platform.WebService/Endpoints/PackAdapterEndpoints.cs) guarded by the HealthRead policy. This is a Pack v2 demo projection: it returns a fixed snapshot wrapped with IsDemo: true and hardcoded values (CVE-2026-1234, us-prod, etc.). The live Console dashboard does not call it. Its DTO (for reference only) is:
record DashboardSummaryDto(
DataConfidenceBadgeDto DataConfidence,
int EnvironmentsWithCriticalReachable,
int TotalCriticalReachable,
decimal SbomCoveragePercent,
decimal VexCoveragePercent,
int BlockedApprovals,
int ExceptionsExpiringSoon,
IReadOnlyList<EnvironmentRiskSnapshotDto> EnvironmentRisk,
IReadOnlyList<DashboardDriverDto> TopDrivers);
NOT IMPLEMENTED (was orphaned doc content). Earlier revisions of this flow described a single
GET /api/v1/dashboardaggregation endpoint returning a snake_case DTO withsummary(total_images,images_scanned_24h,critical_vulns,policy_violations),trends(vuln_trend_7d,scan_volume_7d),top_vulns, andpolicy_status. No such endpoint or DTO exists insrc/. The live dashboard has no trend series, notop_vulnsaggregation, and nopolicy_statusblock; those fields were never built. Treat the old contract as roadmap/aspirational, not current behavior.
Error Handling
Because the dashboard is composed of independent client calls, failures are isolated per lens rather than failing the whole page.
| Condition | Observed behavior (per source) |
|---|---|
| Invalid/expired JWT | Auth interceptor / guard routes to the Authority login flow |
getStats() errors (any status) | Loader catches → vulnStats = null; risk/vuln lenses show an empty/loading state |
| Advisory source status errors | Loader catches → empty source list; feed lens + health dot reflect “needs attention” |
| Context regions/preferences error | Store continues with empty regions/defaults so the app stays usable |
| Context environments error | Store records error; environment lenses show empty state |
| No environments for tenant | Dashboard renders the “Bootstrap Release Control” setup guide |
The previous HTTP-status recovery table (
401 → login,404 → tenant selection,504 → partial dashboard with stale-data indicator,429 → backoff) assumed a single aggregation endpoint and a stale-data indicator that do not exist. It has been replaced with the per-lens behavior above.
Observability
NOT VERIFIED IN SOURCE. The metrics, trace spans, and structured log events below (
dashboard_request_total,dashboard_latency_seconds,dashboard_module_latency_seconds, adashboard-requestserver span with per-module child spans, anddashboard.*log events) describe a server-side aggregation service that was never implemented. No such telemetry exists for the client-composed dashboard. The closest real signal is the client-side debug log emitted byVulnerabilityHttpClient.logRequest()(console.debug('[VulnHttpClient]', method, path, statusCode, durationMs)), which records each vuln API call. Keep this section as a forward-looking target only.
(Roadmap) Metrics
| Metric | Type | Labels |
|---|---|---|
dashboard_request_total | Counter | tenant, status |
dashboard_latency_seconds | Histogram | tenant |
dashboard_module_latency_seconds | Histogram | module |
Related Flows
- Scan Submission Flow - How scans that feed the dashboard are created
- Policy Evaluation Flow - How policy verdicts are computed
- Risk Score Dashboard Flow - Detailed risk scoring
