up
Some checks failed
Docs CI / lint-and-preview (push) Has been cancelled
AOC Guard CI / aoc-guard (push) Has been cancelled
AOC Guard CI / aoc-verify (push) Has been cancelled
api-governance / spectral-lint (push) Has been cancelled
oas-ci / oas-validate (push) Has been cancelled
Policy Lint & Smoke / policy-lint (push) Has been cancelled
Policy Simulation / policy-simulate (push) Has been cancelled
SDK Publish & Sign / sdk-publish (push) Has been cancelled

This commit is contained in:
master
2025-11-27 15:05:48 +02:00
parent 4831c7fcb0
commit e950474a77
278 changed files with 81498 additions and 672 deletions

View File

@@ -0,0 +1,116 @@
import { HttpClient } from '@angular/common/http';
import { inject, Injectable } from '@angular/core';
import { Observable, of, delay } from 'rxjs';
import { AppConfigService } from '../config/app-config.service';
import {
AocMetrics,
AocVerificationRequest,
AocVerificationResult,
} from './aoc.models';
@Injectable({ providedIn: 'root' })
export class AocClient {
private readonly http = inject(HttpClient);
private readonly config = inject(AppConfigService);
/**
* Gets AOC metrics for the dashboard.
*/
getMetrics(tenantId: string, windowMinutes = 1440): Observable<AocMetrics> {
// TODO: Replace with real API call when available
// return this.http.get<AocMetrics>(
// this.config.apiBaseUrl + '/aoc/metrics',
// { params: { tenantId, windowMinutes: windowMinutes.toString() } }
// );
// Mock data for development
return of(this.getMockMetrics()).pipe(delay(300));
}
/**
* Triggers verification of documents within a time window.
*/
verify(request: AocVerificationRequest): Observable<AocVerificationResult> {
// TODO: Replace with real API call when available
// return this.http.post<AocVerificationResult>(
// this.config.apiBaseUrl + '/aoc/verify',
// request
// );
// Mock verification result
return of(this.getMockVerificationResult()).pipe(delay(500));
}
private getMockMetrics(): AocMetrics {
const now = new Date();
const dayAgo = new Date(now.getTime() - 24 * 60 * 60 * 1000);
return {
passCount: 12847,
failCount: 23,
totalCount: 12870,
passRate: 99.82,
recentViolations: [
{
code: 'AOC-PROV-001',
description: 'Missing provenance attestation',
count: 12,
severity: 'high',
lastSeen: new Date(now.getTime() - 15 * 60 * 1000).toISOString(),
},
{
code: 'AOC-DIGEST-002',
description: 'Digest mismatch in manifest',
count: 7,
severity: 'critical',
lastSeen: new Date(now.getTime() - 45 * 60 * 1000).toISOString(),
},
{
code: 'AOC-SCHEMA-003',
description: 'Schema validation failed',
count: 4,
severity: 'medium',
lastSeen: new Date(now.getTime() - 2 * 60 * 60 * 1000).toISOString(),
},
],
ingestThroughput: {
docsPerMinute: 8.9,
avgLatencyMs: 145,
p95LatencyMs: 312,
queueDepth: 3,
errorRate: 0.18,
},
timeWindow: {
start: dayAgo.toISOString(),
end: now.toISOString(),
durationMinutes: 1440,
},
};
}
private getMockVerificationResult(): AocVerificationResult {
const verifyId = 'verify-' + Date.now().toString();
return {
verificationId: verifyId,
status: 'passed',
checkedCount: 1523,
passedCount: 1520,
failedCount: 3,
violations: [
{
documentId: 'doc-abc123',
violationCode: 'AOC-PROV-001',
field: 'attestation.provenance',
expected: 'present',
actual: 'missing',
provenance: {
sourceId: 'source-registry-1',
ingestedAt: new Date().toISOString(),
digest: 'sha256:abc123...',
},
},
],
completedAt: new Date().toISOString(),
};
}
}

View File

@@ -0,0 +1,74 @@
/**
* AOC (Authorization of Containers) models for dashboard metrics.
*/
export interface AocMetrics {
/** Pass/fail counts for the time window */
passCount: number;
failCount: number;
totalCount: number;
passRate: number;
/** Recent violations grouped by code */
recentViolations: AocViolationSummary[];
/** Ingest throughput metrics */
ingestThroughput: AocIngestThroughput;
/** Time window for these metrics */
timeWindow: {
start: string;
end: string;
durationMinutes: number;
};
}
export interface AocViolationSummary {
code: string;
description: string;
count: number;
severity: 'critical' | 'high' | 'medium' | 'low';
lastSeen: string;
}
export interface AocIngestThroughput {
/** Documents processed per minute */
docsPerMinute: number;
/** Average processing latency in milliseconds */
avgLatencyMs: number;
/** P95 latency in milliseconds */
p95LatencyMs: number;
/** Current queue depth */
queueDepth: number;
/** Error rate percentage */
errorRate: number;
}
export interface AocVerificationRequest {
tenantId: string;
since?: string;
limit?: number;
}
export interface AocVerificationResult {
verificationId: string;
status: 'passed' | 'failed' | 'partial';
checkedCount: number;
passedCount: number;
failedCount: number;
violations: AocViolationDetail[];
completedAt: string;
}
export interface AocViolationDetail {
documentId: string;
violationCode: string;
field?: string;
expected?: string;
actual?: string;
provenance?: {
sourceId: string;
ingestedAt: string;
digest: string;
};
}

View File

@@ -0,0 +1,111 @@
import { CommonModule } from '@angular/common';
import {
ChangeDetectionStrategy,
Component,
computed,
inject,
OnInit,
signal,
} from '@angular/core';
import { AocClient } from '../../core/api/aoc.client';
import {
AocMetrics,
AocViolationSummary,
AocVerificationResult,
} from '../../core/api/aoc.models';
@Component({
selector: 'app-sources-dashboard',
standalone: true,
imports: [CommonModule],
templateUrl: './sources-dashboard.component.html',
styleUrls: ['./sources-dashboard.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class SourcesDashboardComponent implements OnInit {
private readonly aocClient = inject(AocClient);
readonly loading = signal(true);
readonly error = signal<string | null>(null);
readonly metrics = signal<AocMetrics | null>(null);
readonly verifying = signal(false);
readonly verificationResult = signal<AocVerificationResult | null>(null);
readonly passRate = computed(() => {
const m = this.metrics();
return m ? m.passRate.toFixed(2) : '0.00';
});
readonly passRateClass = computed(() => {
const m = this.metrics();
if (!m) return 'neutral';
if (m.passRate >= 99.5) return 'excellent';
if (m.passRate >= 95) return 'good';
if (m.passRate >= 90) return 'warning';
return 'critical';
});
readonly throughputStatus = computed(() => {
const m = this.metrics();
if (!m) return 'neutral';
if (m.ingestThroughput.queueDepth > 100) return 'critical';
if (m.ingestThroughput.queueDepth > 50) return 'warning';
return 'good';
});
ngOnInit(): void {
this.loadMetrics();
}
loadMetrics(): void {
this.loading.set(true);
this.error.set(null);
this.aocClient.getMetrics('default').subscribe({
next: (metrics) => {
this.metrics.set(metrics);
this.loading.set(false);
},
error: (err) => {
this.error.set('Failed to load AOC metrics');
this.loading.set(false);
console.error('AOC metrics error:', err);
},
});
}
onVerifyLast24h(): void {
this.verifying.set(true);
this.verificationResult.set(null);
const since = new Date(Date.now() - 24 * 60 * 60 * 1000).toISOString();
this.aocClient.verify({ tenantId: 'default', since }).subscribe({
next: (result) => {
this.verificationResult.set(result);
this.verifying.set(false);
},
error: (err) => {
this.verifying.set(false);
console.error('AOC verification error:', err);
},
});
}
getSeverityClass(severity: AocViolationSummary['severity']): string {
return 'severity-' + severity;
}
formatRelativeTime(isoDate: string): string {
const date = new Date(isoDate);
const now = new Date();
const diffMs = now.getTime() - date.getTime();
const diffMins = Math.floor(diffMs / 60000);
if (diffMins < 1) return 'just now';
if (diffMins < 60) return diffMins + 'm ago';
const diffHours = Math.floor(diffMins / 60);
if (diffHours < 24) return diffHours + 'h ago';
const diffDays = Math.floor(diffHours / 24);
return diffDays + 'd ago';
}
}