diff --git a/src/Web/StellaOps.Web/src/app/features/console-admin/tenants/tenants-list.component.ts b/src/Web/StellaOps.Web/src/app/features/console-admin/tenants/tenants-list.component.ts
index 88e3e80ee..556de442a 100644
--- a/src/Web/StellaOps.Web/src/app/features/console-admin/tenants/tenants-list.component.ts
+++ b/src/Web/StellaOps.Web/src/app/features/console-admin/tenants/tenants-list.component.ts
@@ -91,7 +91,7 @@ import { FreshAuthService } from '../../../core/auth/fresh-auth.service';
.admin-table {
width: 100%;
border-collapse: collapse;
- background: white;
+ background: var(--color-surface-primary);
border-radius: var(--radius-lg);
overflow: hidden;
}
diff --git a/src/Web/StellaOps.Web/src/app/features/control-plane/control-plane-dashboard.component.ts b/src/Web/StellaOps.Web/src/app/features/control-plane/control-plane-dashboard.component.ts
index f54f2f8ec..30bc1e534 100644
--- a/src/Web/StellaOps.Web/src/app/features/control-plane/control-plane-dashboard.component.ts
+++ b/src/Web/StellaOps.Web/src/app/features/control-plane/control-plane-dashboard.component.ts
@@ -508,8 +508,9 @@ import { LoadingStateComponent } from '../../shared/components/loading-state/loa
flex: 1;
min-width: 150px;
padding: 16px;
- background: var(--so-surface);
- border: 1px solid var(--so-border-light);
+ background: var(--so-surface-elevated);
+ border: 1px solid var(--so-border-medium);
+ border-left: 3px solid var(--so-mute);
border-radius: var(--radius-xl);
text-align: center;
transition: all 220ms var(--so-ease-out);
@@ -521,16 +522,30 @@ import { LoadingStateComponent } from '../../shared/components/loading-state/loa
transform: translateY(-2px);
}
+ .pipeline__stage--healthy {
+ border-color: var(--color-status-success-border);
+ border-left: 3px solid var(--color-status-success-text);
+ background: var(--color-status-success-bg);
+ }
+
.pipeline__stage--degraded {
border-color: var(--color-status-warning-border);
+ border-left: 3px solid var(--color-status-warning-text);
background: var(--color-status-warning-bg);
}
.pipeline__stage--unhealthy {
border-color: var(--color-status-error-border);
+ border-left: 3px solid var(--color-status-error-text);
background: var(--color-status-error-bg);
}
+ .pipeline__stage--unknown {
+ border-color: var(--so-border-medium);
+ border-left: 3px solid var(--so-mute);
+ background: var(--so-surface);
+ }
+
.pipeline__stage-header {
margin-bottom: 10px;
}
diff --git a/src/Web/StellaOps.Web/src/app/features/doctor/components/evidence-viewer/evidence-viewer.component.ts b/src/Web/StellaOps.Web/src/app/features/doctor/components/evidence-viewer/evidence-viewer.component.ts
index c7b5d72aa..962cd9ce1 100644
--- a/src/Web/StellaOps.Web/src/app/features/doctor/components/evidence-viewer/evidence-viewer.component.ts
+++ b/src/Web/StellaOps.Web/src/app/features/doctor/components/evidence-viewer/evidence-viewer.component.ts
@@ -81,7 +81,7 @@ import { Evidence } from '../../models/doctor.models';
}
.evidence-data {
- background: white;
+ background: var(--color-surface-primary);
border: 1px solid var(--color-border-primary);
border-radius: var(--radius-md);
overflow: hidden;
diff --git a/src/Web/StellaOps.Web/src/app/features/doctor/components/export-dialog/export-dialog.component.ts b/src/Web/StellaOps.Web/src/app/features/doctor/components/export-dialog/export-dialog.component.ts
index 304043db8..c026e8dfe 100644
--- a/src/Web/StellaOps.Web/src/app/features/doctor/components/export-dialog/export-dialog.component.ts
+++ b/src/Web/StellaOps.Web/src/app/features/doctor/components/export-dialog/export-dialog.component.ts
@@ -102,7 +102,7 @@ type ExportFormat = 'json' | 'markdown' | 'text' | 'dsse';
}
.dialog {
- background: white;
+ background: var(--color-surface-primary);
border-radius: var(--radius-xl);
width: 100%;
max-width: 600px;
diff --git a/src/Web/StellaOps.Web/src/app/features/doctor/components/remediation-panel/remediation-panel.component.ts b/src/Web/StellaOps.Web/src/app/features/doctor/components/remediation-panel/remediation-panel.component.ts
index 03bc8452d..ab79fc08b 100644
--- a/src/Web/StellaOps.Web/src/app/features/doctor/components/remediation-panel/remediation-panel.component.ts
+++ b/src/Web/StellaOps.Web/src/app/features/doctor/components/remediation-panel/remediation-panel.component.ts
@@ -71,7 +71,7 @@ import { Remediation, RemediationStep } from '../../models/doctor.models';
.remediation-panel {
margin-top: 1rem;
padding: 1rem;
- background: white;
+ background: var(--color-surface-primary);
border: 1px solid var(--color-border-primary);
border-radius: var(--radius-lg);
}
diff --git a/src/Web/StellaOps.Web/src/app/features/doctor/services/doctor.client.ts b/src/Web/StellaOps.Web/src/app/features/doctor/services/doctor.client.ts
index 9fbba9d1d..db91c71c4 100644
--- a/src/Web/StellaOps.Web/src/app/features/doctor/services/doctor.client.ts
+++ b/src/Web/StellaOps.Web/src/app/features/doctor/services/doctor.client.ts
@@ -1,7 +1,6 @@
import { Injectable, InjectionToken, inject } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable, of, delay } from 'rxjs';
-import { environment } from '../../../../environments/environment';
import {
CheckListResponse,
CheckMetadata,
@@ -51,7 +50,7 @@ export const DOCTOR_API = new InjectionToken
('DOCTOR_API');
@Injectable({ providedIn: 'root' })
export class HttpDoctorClient implements DoctorApi {
private readonly http = inject(HttpClient);
- private readonly baseUrl = `${environment.apiBaseUrl}/v1/doctor`;
+ private readonly baseUrl = `/doctor/api/v1/doctor`;
listChecks(category?: string, plugin?: string): Observable {
const params: Record = {};
diff --git a/src/Web/StellaOps.Web/src/app/features/evidence-export/evidence-bundles.component.ts b/src/Web/StellaOps.Web/src/app/features/evidence-export/evidence-bundles.component.ts
index 253ec8376..58751a15b 100644
--- a/src/Web/StellaOps.Web/src/app/features/evidence-export/evidence-bundles.component.ts
+++ b/src/Web/StellaOps.Web/src/app/features/evidence-export/evidence-bundles.component.ts
@@ -3,7 +3,9 @@ import {
ChangeDetectionStrategy,
Component,
computed,
+ Inject,
inject,
+ OnInit,
signal,
} from '@angular/core';
import { FormsModule } from '@angular/forms';
@@ -12,6 +14,7 @@ import {
EvidenceBundleStatus,
VerificationResult,
} from './evidence-export.models';
+import { AUDIT_BUNDLES_API, type AuditBundlesApi } from '../../core/api/audit-bundles.client';
/**
* Evidence Bundles Component (Sprint: SPRINT_20251229_016)
@@ -134,29 +137,41 @@ import {
@if (verificationResult()!.verified) {
- â Verification Passed
+ Verification Passed
} @else {
- â Verification Failed
+ Verification Failed
}
Checksum
- {{ verificationResult()!.checksumMatch ? 'â' : 'â' }}
+ @if (verificationResult()!.checksumMatch) {
+
+ } @else {
+
+ }
Chain Valid
- {{ verificationResult()!.chainValid ? 'â' : 'â' }}
+ @if (verificationResult()!.chainValid) {
+
+ } @else {
+
+ }
@if (verificationResult()!.signatureValid !== undefined) {
Signature
- {{ verificationResult()!.signatureValid ? 'â' : 'â' }}
+ @if (verificationResult()!.signatureValid) {
+
+ } @else {
+
+ }
}
@@ -424,51 +439,66 @@ import {
`],
changeDetection: ChangeDetectionStrategy.OnPush
})
-export class EvidenceBundlesComponent {
+export class EvidenceBundlesComponent implements OnInit {
searchQuery = '';
statusFilter = '';
- readonly bundles = signal
([
- // Mock data
- {
- id: 'eb-001',
- name: 'api-service-v1.2.3',
- imageRef: 'registry.example.com/api-service:v1.2.3',
- createdAt: new Date().toISOString(),
- status: 'ready',
- sizeBytes: 2567890,
- checksumSha256: 'sha256:abc123def456789...',
- format: 'tar.gz',
- contents: {
- hasSbom: true,
- hasScan: true,
- hasAttestation: true,
- hasProvenance: true,
- hasVexDecisions: true,
- hasPolicy: true,
- fileCount: 12,
+ readonly bundles = signal([]);
+ readonly loading = signal(true);
+ readonly error = signal(null);
+
+ constructor(@Inject(AUDIT_BUNDLES_API) private readonly bundlesApi: AuditBundlesApi) {}
+
+ ngOnInit(): void {
+ this.loadBundles();
+ }
+
+ private loadBundles(): void {
+ this.loading.set(true);
+ this.error.set(null);
+ this.bundlesApi.listBundles().subscribe({
+ next: (response) => {
+ const mapped: EvidenceBundle[] = (response.items ?? []).map((item: any) => ({
+ id: item.bundleId ?? item.id ?? '',
+ name: item.subject?.name ?? item.bundleId ?? 'Unknown',
+ imageRef: item.subject?.digest?.['sha256']
+ ? `${item.subject?.name ?? ''}@sha256:${item.subject.digest['sha256'].substring(0, 12)}`
+ : item.ociReference ?? '',
+ createdAt: item.createdAt ?? new Date().toISOString(),
+ status: this.mapStatus(item.status),
+ sizeBytes: 0,
+ checksumSha256: item.sha256 ?? '',
+ format: 'tar.gz' as const,
+ contents: {
+ hasSbom: true,
+ hasScan: true,
+ hasAttestation: !!item.integrityRootHash,
+ hasProvenance: !!item.integrityRootHash,
+ hasVexDecisions: false,
+ hasPolicy: false,
+ fileCount: 0,
+ },
+ }));
+ this.bundles.set(mapped);
+ this.loading.set(false);
},
- },
- {
- id: 'eb-002',
- name: 'web-frontend-v2.0.0',
- imageRef: 'registry.example.com/web-frontend:v2.0.0',
- createdAt: new Date(Date.now() - 86400000).toISOString(),
- status: 'generating',
- sizeBytes: 0,
- checksumSha256: '',
- format: 'tar.gz',
- contents: {
- hasSbom: true,
- hasScan: true,
- hasAttestation: false,
- hasProvenance: false,
- hasVexDecisions: false,
- hasPolicy: false,
- fileCount: 0,
+ error: (err) => {
+ console.error('[EvidenceBundles] Failed to load bundles:', err);
+ this.error.set(err?.message ?? 'Failed to load evidence bundles');
+ this.loading.set(false);
},
- },
- ]);
+ });
+ }
+
+ private mapStatus(status: string): EvidenceBundleStatus {
+ switch (status) {
+ case 'completed': return 'ready';
+ case 'processing': return 'generating';
+ case 'queued': return 'pending';
+ case 'failed': return 'expired';
+ default: return 'pending';
+ }
+ }
readonly expandedBundle = signal(null);
readonly verificationResult = signal(null);
diff --git a/src/Web/StellaOps.Web/src/app/features/evidence-export/provenance-visualization.component.ts b/src/Web/StellaOps.Web/src/app/features/evidence-export/provenance-visualization.component.ts
index 1c37accba..931c5f1f9 100644
--- a/src/Web/StellaOps.Web/src/app/features/evidence-export/provenance-visualization.component.ts
+++ b/src/Web/StellaOps.Web/src/app/features/evidence-export/provenance-visualization.component.ts
@@ -59,9 +59,9 @@ export interface ProvenanceChain {
@if (chain.verified) {
-
â
+
} @else {
-
â¯
+
}
@@ -91,7 +91,11 @@ export interface ProvenanceChain {
- {{ getNodeIcon(node.type) }}
+ @if (node.type === 'verdict') {
+
+ } @else {
+ {{ getNodeIcon(node.type) }}
+ }
@if (!last) {
@@ -150,7 +154,7 @@ export interface ProvenanceChain {
Attestation
@@ -719,7 +723,7 @@ export class ProvenanceVisualizationComponent {
vex: 'V',
policy: 'P',
attestation: 'S',
- verdict: 'â',
+ verdict: 'V',
};
return icons[type];
}
diff --git a/src/Web/StellaOps.Web/src/app/features/evidence/modals/audit-bundle-create-modal.component.ts b/src/Web/StellaOps.Web/src/app/features/evidence/modals/audit-bundle-create-modal.component.ts
index 944f502b6..538264d09 100644
--- a/src/Web/StellaOps.Web/src/app/features/evidence/modals/audit-bundle-create-modal.component.ts
+++ b/src/Web/StellaOps.Web/src/app/features/evidence/modals/audit-bundle-create-modal.component.ts
@@ -341,7 +341,7 @@ export interface AuditBundleConfig {
}
.modal {
- background: white;
+ background: var(--color-surface-primary);
border-radius: var(--radius-xl);
box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25);
width: 100%;
@@ -543,7 +543,7 @@ export interface AuditBundleConfig {
}
.filter-tab--active {
- background: white;
+ background: var(--color-surface-primary);
color: var(--color-text-primary);
box-shadow: var(--shadow-sm);
}
diff --git a/src/Web/StellaOps.Web/src/app/features/feed-mirror/airgap-export.component.ts b/src/Web/StellaOps.Web/src/app/features/feed-mirror/airgap-export.component.ts
index 6f730d3bb..d3e6e004e 100644
--- a/src/Web/StellaOps.Web/src/app/features/feed-mirror/airgap-export.component.ts
+++ b/src/Web/StellaOps.Web/src/app/features/feed-mirror/airgap-export.component.ts
@@ -16,14 +16,13 @@ import {
AirGapBundleRequest,
FeedType,
} from '../../core/api/feed-mirror.models';
-import { FEED_MIRROR_API, MockFeedMirrorApi } from '../../core/api/feed-mirror.client';
+import { FEED_MIRROR_API } from '../../core/api/feed-mirror.client';
type ExportStep = 'select' | 'configure' | 'building' | 'ready';
@Component({
selector: 'app-airgap-export',
imports: [CommonModule, FormsModule, RouterModule],
- providers: [{ provide: FEED_MIRROR_API, useClass: MockFeedMirrorApi }],
template: `