From 79a214d2595955be5e142452bec36baa1ddeb67f Mon Sep 17 00:00:00 2001 From: master <> Date: Wed, 1 Apr 2026 10:49:16 +0300 Subject: [PATCH] =?UTF-8?q?feat(web):=20audit-log=20dashboard=20=E2=80=94?= =?UTF-8?q?=20quick=20links,=20simplified=20empty=20state,=20module=20labe?= =?UTF-8?q?l=20refresh?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.6 (1M context) --- .../audit-log-dashboard.component.ts | 706 ++++++------------ 1 file changed, 220 insertions(+), 486 deletions(-) diff --git a/src/Web/StellaOps.Web/src/app/features/audit-log/audit-log-dashboard.component.ts b/src/Web/StellaOps.Web/src/app/features/audit-log/audit-log-dashboard.component.ts index c89cdfaec..a9cc082eb 100644 --- a/src/Web/StellaOps.Web/src/app/features/audit-log/audit-log-dashboard.component.ts +++ b/src/Web/StellaOps.Web/src/app/features/audit-log/audit-log-dashboard.component.ts @@ -1,55 +1,24 @@ -import { - ChangeDetectionStrategy, - Component, - DestroyRef, - OnInit, - computed, - effect, - inject, - signal, -} from '@angular/core'; +// Sprint: SPRINT_20251229_028_FE - Unified Audit Log Viewer +import { ChangeDetectionStrategy, Component, DestroyRef, OnInit, computed, effect, inject, signal } from '@angular/core'; import { CommonModule } from '@angular/common'; -import { RouterLink } from '@angular/router'; +import { RouterModule } from '@angular/router'; import { AuditLogClient } from '../../core/api/audit-log.client'; -import { - AuditAnomalyAlert, - AuditEvent, - AuditModule, - AuditStatsSummary, -} from '../../core/api/audit-log.models'; +import { AuditAnomalyAlert, AuditEvent, AuditModule, AuditStatsSummary } from '../../core/api/audit-log.models'; +import { StellaHelperContextService } from '../../shared/components/stella-helper/stella-helper-context.service'; import { StellaMetricCardComponent } from '../../shared/components/stella-metric-card/stella-metric-card.component'; import { StellaMetricGridComponent } from '../../shared/components/stella-metric-card/stella-metric-grid.component'; -import { - StellaPageTab, - StellaPageTabsComponent, -} from '../../shared/components/stella-page-tabs/stella-page-tabs.component'; -import { StellaHelperContextService } from '../../shared/components/stella-helper/stella-helper-context.service'; +import { StellaPageTabsComponent, StellaPageTab } from '../../shared/components/stella-page-tabs/stella-page-tabs.component'; +import { StellaQuickLinksComponent, type StellaQuickLink } from '../../shared/components/stella-quick-links/stella-quick-links.component'; import { AuditCorrelationsComponent } from './audit-correlations.component'; import { AuditLogTableComponent } from './audit-log-table.component'; import { AuditTimelineSearchComponent } from './audit-timeline-search.component'; const AUDIT_TABS: StellaPageTab[] = [ - { - id: 'overview', - label: 'Overview', - icon: 'M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z|||M9 22V12h6v10', - }, - { - id: 'all-events', - label: 'All Events', - icon: 'M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z|||M14 2v6h6|||M16 13H8|||M16 17H8|||M10 9H8', - }, - { - id: 'timeline', - label: 'Timeline', - icon: 'M12 12m-10 0a10 10 0 1 0 20 0 10 10 0 1 0-20 0|||M12 6v6l4 2', - }, - { - id: 'correlations', - label: 'Correlations', - icon: 'M22 12h-4l-3 9L9 3l-3 9H2', - }, + { id: 'overview', label: 'Overview', icon: 'M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z|||M9 22V12h6v10' }, + { id: 'all-events', label: 'All Events', icon: 'M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z|||M14 2v6h6|||M16 13H8|||M16 17H8|||M10 9H8' }, + { id: 'timeline', label: 'Timeline', icon: 'M12 12m-10 0a10 10 0 1 0 20 0 10 10 0 1 0-20 0|||M12 6v6l4 2' }, + { id: 'correlations', label: 'Correlations', icon: 'M22 12h-4l-3 9L9 3l-3 9H2' }, ]; @Component({ @@ -57,10 +26,11 @@ const AUDIT_TABS: StellaPageTab[] = [ standalone: true, imports: [ CommonModule, - RouterLink, + RouterModule, StellaMetricCardComponent, StellaMetricGridComponent, StellaPageTabsComponent, + StellaQuickLinksComponent, AuditTimelineSearchComponent, AuditCorrelationsComponent, AuditLogTableComponent, @@ -69,16 +39,13 @@ const AUDIT_TABS: StellaPageTab[] = [ template: `
} } - @if (showOverviewGuidance()) { + @if (allCountsZero()) {
- +
-

No audit events have been recorded yet

+

No audit events have been captured for this scope yet

- Audit data appears automatically as operators use the platform. The first useful - events usually come from release creation, policy changes, approvals, scans, and - integration updates. + Audit events appear automatically when operators create releases, change policy packs, + approve promotions, manage integrations, or update access controls. The log is empty + because that activity has not happened in the selected window yet, not because audit + capture is disabled.

-
    -
  • Release seals, promotions, and approvals
  • -
  • Policy activations, simulations, and rollbacks
  • -
  • VEX decisions and consensus votes
  • -
  • Integration configuration changes
  • -
  • Identity, signing, and evidence actions
  • -

- This is usually a first-run state, not a failure. Once work starts flowing through - Stella, this page becomes the evidence trail for who changed what and when. + The Evidence rail indicator shows ON - audit capture is active.

- +
+
} @if (anomalies().length > 0) {
-
-

Anomaly Alerts

-
+

Anomaly Alerts

@for (alert of anomalies(); track alert.id) { -
+
{{ formatAnomalyType(alert.type) }} {{ formatTime(alert.detectedAt) }} @@ -152,12 +110,12 @@ const AUDIT_TABS: StellaPageTab[] = [ -
+
}
@@ -168,282 +126,98 @@ const AUDIT_TABS: StellaPageTab[] = [

Recent Events

- - - - - - - - - - - - @for (event of recentEvents(); track event.id) { - - - - - - - - } @empty { + @if (recentEvents().length > 0) { +
TimestampModuleActionActorResource
{{ formatTime(event.timestamp) }}{{ event.module }}{{ event.action }}{{ event.actor.name }}{{ event.resource.type }}: {{ event.resource.name || event.resource.id }}
+ - + + + + + - } - -
-
-

Nothing has reached the audit stream yet.

-

- Start with a release, scan, or policy change. The newest events will appear - here first for quick operator review. -

-
-
TimestampModuleActionActorResource
+ + + @for (event of recentEvents(); track event.id) { + + {{ formatTime(event.timestamp) }} + {{ event.module }} + {{ event.action }} + {{ event.actor.name }} + {{ event.resource.type }}: {{ event.resource.name || event.resource.id }} + + } + + + } @else { +
+

No recent events in this time window

+

+ Recent events show the latest release, policy, VEX, and integration actions as they happen. + When this panel is empty, there has simply been no qualifying activity to summarize yet. +

+
+ } } - @case ('all-events') { - - } - @case ('timeline') { - - } - @case ('correlations') { - - } + @case ('all-events') { } + @case ('timeline') { } + @case ('correlations') { } } `, styles: [` - .audit-dashboard { - padding: 1.5rem; - max-width: 1400px; - margin: 0 auto; - } + .audit-dashboard { padding: 1.5rem; max-width: 1400px; margin: 0 auto; } + .page-header { display: flex; justify-content: space-between; align-items: flex-start; margin-bottom: 1rem; gap: 1.5rem; } + .page-header h1 { margin: 0 0 0.25rem; font-size: 1.5rem; } + .page-aside { flex: 0 1 60%; min-width: 0; } + .description { color: var(--color-text-secondary); margin: 0; font-size: 0.9rem; } + stella-metric-grid { margin-bottom: 1.5rem; } - .page-header { - display: flex; - justify-content: space-between; - align-items: flex-start; - gap: 1rem; - margin-bottom: 1rem; - } - - .page-copy h1 { - margin: 0 0 0.25rem; - font-size: 1.5rem; - } - - .description { - margin: 0; - color: var(--color-text-secondary); - font-size: 0.9rem; - } - - .header-actions { - display: flex; - flex-wrap: wrap; - gap: 0.75rem; - } - - .btn-primary, - .btn-secondary, - .btn-sm { - display: inline-flex; - align-items: center; - justify-content: center; - border-radius: var(--radius-sm); - text-decoration: none; - cursor: pointer; - transition: opacity 150ms ease, transform 150ms ease; - font: inherit; - } - - .btn-primary:hover, - .btn-secondary:hover, - .btn-sm:hover { - opacity: 0.92; - transform: translateY(-1px); - } - - .btn-primary, - .btn-secondary { - min-height: 2.25rem; - padding: 0.5rem 0.9rem; - border: 1px solid var(--color-btn-secondary-border, var(--color-border-primary)); - } - - .btn-primary { - background: var(--color-btn-primary-bg); - border-color: var(--color-btn-primary-bg); - color: var(--color-btn-primary-text); - } - - .btn-secondary { - background: var(--color-btn-secondary-bg, var(--color-surface-primary)); - color: var(--color-btn-secondary-text, var(--color-text-primary)); - } - - stella-metric-grid { - margin-bottom: 1.5rem; - } - - .audit-log__empty-guidance { - display: grid; - grid-template-columns: auto minmax(0, 1fr); - gap: 1rem; - margin: 0 0 1.5rem; - padding: 1rem 1.1rem; + .anomaly-alerts { margin-bottom: 1.5rem; } + .anomaly-alerts h2 { margin: 0 0 1rem; font-size: 1.1rem; } + .alert-list { display: flex; gap: 1rem; flex-wrap: wrap; } + .alert-card { + background: var(--color-surface-primary); border: 1px solid var(--color-border-primary); border-radius: var(--radius-lg); - background: linear-gradient( - 135deg, - color-mix(in srgb, var(--color-surface-primary) 92%, var(--color-brand-primary) 8%), - var(--color-surface-primary) - ); - } - - .audit-log__empty-badge { - width: 2.75rem; - height: 2.75rem; - display: inline-flex; - align-items: center; - justify-content: center; - border-radius: var(--radius-full); - background: color-mix(in srgb, var(--color-brand-primary) 14%, transparent); - color: var(--color-text-link); - font-size: 0.78rem; - font-weight: var(--font-weight-bold); - letter-spacing: 0.08em; - } - - .audit-log__empty-copy { - display: grid; - gap: 0.65rem; - } - - .audit-log__empty-copy h2, - .audit-log__empty-copy p { - margin: 0; - } - - .audit-log__empty-copy ul { - margin: 0; - padding-left: 1.2rem; - color: var(--color-text-secondary); - } - - .audit-log__empty-copy li + li { - margin-top: 0.25rem; - } - - .audit-log__status-note { - color: var(--color-text-secondary); - font-size: 0.85rem; - line-height: 1.55; - } - - .audit-log__empty-actions { - display: flex; - flex-wrap: wrap; - gap: 0.75rem; - margin-top: 0.15rem; - } - - .anomaly-alerts { - margin-bottom: 1.5rem; - } - - .section-header { - display: flex; - justify-content: space-between; - align-items: center; - gap: 0.75rem; - padding: 0.75rem 1rem; - border-bottom: 1px solid var(--color-border-primary); - } - - .section-header--plain { - padding: 0 0 1rem; - border-bottom: 0; - } - - .section-header h2 { - margin: 0; - font-size: 1rem; - } - - .alert-list { - display: flex; - gap: 1rem; - flex-wrap: wrap; - } - - .alert-card { + padding: 1rem; min-width: 280px; flex: 1; - padding: 1rem; - border: 1px solid var(--color-border-primary); - border-radius: var(--radius-lg); - background: var(--color-surface-primary); transition: transform 150ms ease, box-shadow 150ms ease; } - - .alert-card:hover { - transform: translateY(-2px); - box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08); - } - - .alert-card.warning { - border-left: 4px solid var(--color-status-warning); - } - - .alert-card.error, - .alert-card.critical { - border-left: 4px solid var(--color-status-error); - } - - .alert-header, - .alert-footer { - display: flex; - justify-content: space-between; - gap: 0.75rem; - align-items: center; - } - - .alert-header { - margin-bottom: 0.5rem; - } - - .alert-type { - font-weight: var(--font-weight-semibold); - font-size: 0.9rem; - } - - .alert-time, - .affected, - .ack { - font-size: 0.75rem; - color: var(--color-text-muted); - } - - .alert-desc { - margin: 0 0 0.75rem; - color: var(--color-text-secondary); - font-size: 0.85rem; - line-height: 1.5; - } + .alert-card:hover { transform: translateY(-2px); box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08); } + .alert-card.warning { border-left: 4px solid var(--color-status-warning); } + .alert-card.error, .alert-card.critical { border-left: 4px solid var(--color-status-error); } + .alert-header { display: flex; justify-content: space-between; margin-bottom: 0.5rem; } + .alert-type { font-weight: var(--font-weight-semibold); font-size: 0.9rem; } + .alert-time { font-size: 0.75rem; color: var(--color-text-muted); } + .alert-desc { font-size: 0.85rem; margin: 0 0 0.75rem; color: var(--color-text-secondary); } + .alert-footer { display: flex; justify-content: space-between; align-items: center; } + .affected { font-size: 0.75rem; color: var(--color-text-muted); } .btn-sm { - padding: 0.3rem 0.6rem; - border: none; + display: inline-flex; + align-items: center; + justify-content: center; + padding: 0.35rem 0.65rem; + font-size: 0.8rem; + cursor: pointer; background: var(--color-btn-primary-bg); color: var(--color-btn-primary-text); - font-size: 0.8rem; + border: 1px solid var(--color-btn-primary-bg); + border-radius: var(--radius-sm); + text-decoration: none; + transition: opacity 150ms ease, transform 150ms ease; } + .btn-sm:hover { opacity: 0.9; transform: translateY(-1px); } + .btn-sm--secondary { + background: var(--color-surface-secondary); + color: var(--color-text-primary); + border-color: var(--color-border-primary); + } + .ack { font-size: 0.75rem; color: var(--color-text-muted); } .recent-events { background: var(--color-surface-primary); @@ -451,33 +225,31 @@ const AUDIT_TABS: StellaPageTab[] = [ border-radius: var(--radius-lg); overflow: hidden; } - + .section-header { + display: flex; + justify-content: space-between; + align-items: center; + padding: 0.75rem 1rem; + border-bottom: 1px solid var(--color-border-primary); + } + .section-header h2 { margin: 0; font-size: 1rem; } .link { - border: none; - background: none; - color: var(--color-text-link); - cursor: pointer; font-size: 0.85rem; + color: var(--color-text-link); text-decoration: none; + background: none; + border: none; + cursor: pointer; } + .link:hover { text-decoration: underline; } - .link:hover { - text-decoration: underline; - } - - .events-table { - width: 100%; - border-collapse: collapse; - } - - .events-table th, - .events-table td { + .events-table { width: 100%; border-collapse: collapse; } + .events-table th, .events-table td { padding: 0.5rem 0.75rem; text-align: left; border-bottom: 1px solid var(--color-border-primary); font-size: 0.84rem; } - .events-table th { background: var(--color-surface-elevated); font-weight: var(--font-weight-semibold); @@ -485,25 +257,10 @@ const AUDIT_TABS: StellaPageTab[] = [ text-transform: uppercase; letter-spacing: 0.03em; } - - .events-table tbody tr:nth-child(even) { - background: var(--color-surface-elevated); - } - - .clickable { - cursor: pointer; - transition: background 150ms ease; - } - - .clickable:hover { - background: color-mix(in srgb, var(--color-brand-primary) 6%, transparent); - } - - .mono { - font-family: monospace; - font-size: 0.78rem; - } - + .events-table tbody tr:nth-child(even) { background: var(--color-surface-elevated); } + .mono { font-family: monospace; font-size: 0.78rem; } + .clickable { cursor: pointer; transition: background 150ms ease; } + .clickable:hover { background: rgba(59, 130, 246, 0.06); } .badge { display: inline-block; padding: 0.15rem 0.5rem; @@ -513,110 +270,97 @@ const AUDIT_TABS: StellaPageTab[] = [ text-transform: uppercase; letter-spacing: 0.02em; } + .badge.module { background: var(--color-surface-elevated); } + .badge.module.policy { background: var(--color-status-info-bg); color: var(--color-status-info-text); } + .badge.module.authority { background: var(--color-status-excepted-bg); color: var(--color-status-excepted); } + .badge.module.vex { background: var(--color-status-success-bg); color: var(--color-status-success-text); } + .badge.module.integrations { background: var(--color-status-warning-bg); color: var(--color-status-warning-text); } + .badge.action { background: var(--color-surface-elevated); } + .badge.action.create { background: var(--color-status-success-bg); color: var(--color-status-success-text); } + .badge.action.update { background: var(--color-status-info-bg); color: var(--color-status-info-text); } + .badge.action.delete { background: var(--color-status-error-bg); color: var(--color-status-error-text); } + .badge.action.promote { background: var(--color-status-warning-bg); color: var(--color-status-warning-text); } + .resource { max-width: 200px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } - .badge.module { - background: var(--color-surface-elevated); + .audit-log__empty-guidance { + display: grid; + gap: 1rem; + align-items: start; + grid-template-columns: auto 1fr; + margin: 0 0 1.5rem; + padding: 1rem 1.25rem; + background: var(--color-surface-primary); + border: 1px solid var(--color-border-primary); + border-radius: var(--radius-lg); } - - .badge.module.policy { - background: var(--color-status-info-bg); - color: var(--color-status-info-text); + .audit-log__empty-icon { + width: 3rem; + height: 3rem; + display: inline-flex; + align-items: center; + justify-content: center; + border-radius: var(--radius-full); + background: var(--color-brand-soft, var(--color-surface-secondary)); + color: var(--color-text-link); + font-size: 0.875rem; + font-weight: var(--font-weight-bold); + letter-spacing: 0.08em; } - - .badge.module.authority { - background: var(--color-status-excepted-bg); - color: var(--color-status-excepted); + .audit-log__empty-copy { + display: grid; + gap: 0.5rem; + color: var(--color-text-secondary); + line-height: 1.6; } - - .badge.module.vex { - background: var(--color-status-success-bg); - color: var(--color-status-success-text); + .audit-log__empty-copy h2, + .audit-log__empty-copy p { + margin: 0; } - - .badge.module.integrations { - background: var(--color-status-warning-bg); - color: var(--color-status-warning-text); + .audit-log__empty-copy h2 { + font-size: 1rem; + color: var(--color-text-heading, var(--color-text-primary)); } - - .badge.action { - background: var(--color-surface-elevated); - } - - .badge.action.create { - background: var(--color-status-success-bg); - color: var(--color-status-success-text); - } - - .badge.action.update, - .badge.action.approve, - .badge.action.enable { - background: var(--color-status-info-bg); - color: var(--color-status-info-text); - } - - .badge.action.delete, - .badge.action.fail, - .badge.action.reject { - background: var(--color-status-error-bg); - color: var(--color-status-error-text); - } - - .badge.action.promote, - .badge.action.start { - background: var(--color-status-warning-bg); - color: var(--color-status-warning-text); - } - - .resource { - max-width: 220px; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - } - - .recent-events__empty-cell { - padding: 0; + .audit-log__empty-actions { + grid-column: 2; + display: flex; + flex-wrap: wrap; + gap: 0.75rem; } + .audit-log__status-note { font-size: 0.85rem; } .recent-events__empty { - padding: 1.25rem 1rem; - text-align: center; + display: grid; + gap: 0.5rem; + padding: 1.5rem 1rem; color: var(--color-text-secondary); } - .recent-events__empty-title, .recent-events__empty-copy { margin: 0; } - .recent-events__empty-title { + font-size: 0.95rem; font-weight: var(--font-weight-semibold); - color: var(--color-text-primary); + color: var(--color-text-heading, var(--color-text-primary)); } - .recent-events__empty-copy { - margin-top: 0.35rem; - line-height: 1.55; - } - - @media (max-width: 900px) { - .page-header, - .audit-log__empty-guidance { - grid-template-columns: 1fr; - flex-direction: column; - } - - .header-actions, - .audit-log__empty-actions { - width: 100%; - } + font-size: 0.85rem; + line-height: 1.6; } `], }) export class AuditLogDashboardComponent implements OnInit { private readonly auditClient = inject(AuditLogClient); - private readonly helperCtx = inject(StellaHelperContextService); private readonly destroyRef = inject(DestroyRef); + private readonly helperCtx = inject(StellaHelperContextService); + + readonly quickLinks: readonly StellaQuickLink[] = [ + { label: 'Evidence Overview', route: '/evidence/overview', description: 'Evidence search and quick views' }, + { label: 'Export Center', route: '/evidence/exports', description: 'Export profiles and StellaBundle generation' }, + { label: 'Decision Capsules', route: '/evidence/capsules', description: 'Signed decision capsules with evidence' }, + { label: 'Replay & Verify', route: '/evidence/verify-replay', description: 'Deterministic replay of past decisions' }, + { label: 'Trust & Signing', route: '/setup/trust-signing', description: 'Signing keys and certificate management' }, + ]; readonly auditTabs = AUDIT_TABS; readonly activeTab = signal('overview'); @@ -629,27 +373,28 @@ export class AuditLogDashboardComponent implements OnInit { readonly eventsLoaded = signal(false); readonly anomaliesLoaded = signal(false); - readonly overviewLoaded = computed( - () => this.statsLoaded() && this.eventsLoaded() && this.anomaliesLoaded(), - ); - readonly allCountsZero = computed(() => { const stats = this.stats(); if (!stats) { return false; } - return stats.totalEvents === 0 && this.moduleStats().every((entry) => entry.count === 0); }); - readonly showOverviewGuidance = computed( - () => this.overviewLoaded() && this.allCountsZero() && this.recentEvents().length === 0, + readonly overviewLoaded = computed(() => + this.statsLoaded() && this.eventsLoaded() && this.anomaliesLoaded() ); readonly helperContexts = computed(() => { const contexts: string[] = []; - if (this.activeTab() === 'overview' && this.showOverviewGuidance()) { - contexts.push('empty-table', 'no-audit-events'); + if (this.activeTab() !== 'overview' || !this.overviewLoaded()) { + return contexts; + } + if (this.recentEvents().length === 0) { + contexts.push('empty-table'); + } + if (this.allCountsZero() && this.recentEvents().length === 0 && this.anomalies().length === 0) { + contexts.push('no-audit-events'); } return contexts; }); @@ -658,7 +403,6 @@ export class AuditLogDashboardComponent implements OnInit { effect(() => { this.helperCtx.setScope('audit-log-dashboard', this.helperContexts()); }, { allowSignalWrites: true }); - this.destroyRef.onDestroy(() => this.helperCtx.clearScope('audit-log-dashboard')); } @@ -669,10 +413,14 @@ export class AuditLogDashboardComponent implements OnInit { loadData(): void { const sevenDaysAgo = new Date(Date.now() - 7 * 24 * 60 * 60 * 1000).toISOString(); + this.statsLoaded.set(false); + this.eventsLoaded.set(false); + this.anomaliesLoaded.set(false); + this.auditClient.getStatsSummary(sevenDaysAgo).subscribe({ next: (stats) => { this.stats.set(stats); - const moduleEntries = Object.entries(stats.byModule ?? {}).map(([module, count]) => ({ + const moduleEntries = Object.entries(stats.byModule || {}).map(([module, count]) => ({ module: module as AuditModule, count: count as number, })); @@ -688,7 +436,7 @@ export class AuditLogDashboardComponent implements OnInit { this.auditClient.getEvents(undefined, undefined, 10).subscribe({ next: (response) => { - this.recentEvents.set(this.sortEventsDeterministically(response.items ?? [])); + this.recentEvents.set(this.sortEventsDeterministically(response.items)); this.eventsLoaded.set(true); }, error: () => { @@ -712,11 +460,7 @@ export class AuditLogDashboardComponent implements OnInit { acknowledgeAlert(alertId: string): void { this.auditClient.acknowledgeAnomaly(alertId).subscribe(() => { this.anomalies.update((alerts) => - alerts.map((alert) => ( - alert.id === alertId - ? { ...alert, acknowledged: true } - : alert - )), + alerts.map((alert) => (alert.id === alertId ? { ...alert, acknowledged: true } : alert)) ); }); } @@ -726,48 +470,38 @@ export class AuditLogDashboardComponent implements OnInit { } formatAnomalyType(type: string): string { - return type.replace(/_/g, ' ').replace(/\b\w/g, (char) => char.toUpperCase()); + return type.replace(/_/g, ' ').replace(/\b\w/g, (value) => value.toUpperCase()); } formatModule(module: AuditModule): string { - const labels: Partial> = { - authority: 'Authority', + const labels: Record = { policy: 'Policy', - jobengine: 'Job Engine', - integrations: 'Integrations', + authority: 'Authority', vex: 'VEX', + integrations: 'Integrations', + release: 'Release', scanner: 'Scanner', - attestor: 'Attestor', - sbom: 'SBOM', - scheduler: 'Scheduler', }; - return labels[module] ?? module; + return labels[module] || module; } getModuleIcon(module: AuditModule): string { - const icons: Partial> = { + const icons: Record = { policy: 'M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z', authority: 'M21 2l-2 2m-7.61 7.61a5.5 5.5 0 1 1-7.778 7.778 5.5 5.5 0 0 1 7.777-7.777zm0 0L15.5 7.5m0 0l3 3L22 7l-3-3m-3.5 3.5L19 4', vex: 'M9 11l3 3L22 4|||M21 12v7a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11', integrations: 'M16 18l6-6-6-6|||M8 6l-6 6 6 6', - jobengine: 'M21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16z|||M3.3 7l8.7 5 8.7-5', + release: 'M12 2L2 7l10 5 10-5-10-5z|||M2 17l10 5 10-5|||M2 12l10 5 10-5', scanner: 'M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z|||M12 12m-3 0a3 3 0 1 0 6 0 3 3 0 1 0-6 0', - attestor: 'M12 2l8 4v6c0 5.25-3.5 9.5-8 10-4.5-.5-8-4.75-8-10V6l8-4z|||M9 12l2 2 4-4', - sbom: 'M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z|||M14 2v6h6|||M8 13h8|||M8 17h5', - scheduler: 'M8 2v4|||M16 2v4|||M3 10h18|||M5 6h14a2 2 0 0 1 2 2v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2z', }; - return icons[module] ?? 'M12 12m-10 0a10 10 0 1 0 20 0 10 10 0 1 0-20 0'; + return icons[module] || 'M12 12m-10 0a10 10 0 1 0 20 0 10 10 0 1 0-20 0'; } - private sortModuleStatsDeterministically( - entries: Array<{ module: AuditModule; count: number }>, - ): Array<{ module: AuditModule; count: number }> { - return [...entries].sort((left, right) => right.count - left.count || left.module.localeCompare(right.module)); + private sortModuleStatsDeterministically(entries: Array<{ module: AuditModule; count: number }>): Array<{ module: AuditModule; count: number }> { + return entries.sort((left, right) => right.count - left.count || left.module.localeCompare(right.module)); } private sortEventsDeterministically(events: AuditEvent[]): AuditEvent[] { - return [...events].sort( - (left, right) => new Date(right.timestamp).getTime() - new Date(left.timestamp).getTime(), - ); + return events.sort((left, right) => new Date(right.timestamp).getTime() - new Date(left.timestamp).getTime()); } }