From aaae7d771d32c0428ded19aa3fa414b38ce0f590 Mon Sep 17 00:00:00 2001 From: master <> Date: Sat, 28 Mar 2026 10:50:59 +0200 Subject: [PATCH] Fix Policy Governance theming, replace non-Material patterns, remove redundant tabs Theme fixes: - Replace swapped CSS variables across 15 governance sub-components (text colors as backgrounds, surface colors as text, text colors as borders) - Replace 50+ hardcoded rgba() colors with CSS variable references Navigation fixes: - Remove redundant parent tab bar from PolicyDecisioningShellComponent (sidebar already provides section navigation) - Fix governance tab switching: remove conflicting urlParam, add route sync - Remove redundant eyebrow header from governance component Material compliance (AGENTS.md): - Replace 11 window.confirm/prompt calls with app-confirm-dialog and app-modal - Replace custom KPI cards with stella-metric-card/stella-metric-grid (3 components) - Replace custom modal implementations with app-modal (5 modals in 2 components) - Replace custom filter UIs with stella-filter-chip (3 components) - Replace Loading text with app-loading-state component (6 components) - Add [title] attributes to truncated text elements Co-Authored-By: Claude Opus 4.6 (1M context) --- .../policy-decisioning-shell.component.ts | 111 +---- .../policy-decisioning.routes.ts | 8 +- .../conflict-resolution-wizard.component.ts | 106 ++--- .../governance-audit.component.ts | 97 ++-- .../impact-preview.component.ts | 139 +++--- .../policy-conflict-dashboard.component.ts | 310 +++++++------ .../policy-governance.component.ts | 53 ++- .../policy-validator.component.ts | 54 +-- .../risk-budget-config.component.ts | 40 +- .../risk-budget-dashboard.component.ts | 162 +++---- .../risk-profile-editor.component.ts | 50 +-- .../risk-profile-list.component.ts | 207 +++++---- .../schema-docs.component.ts | 88 ++-- .../schema-playground.component.ts | 72 +-- .../sealed-mode-control.component.ts | 419 +++++++----------- .../sealed-mode-overrides.component.ts | 135 ++++-- .../staleness-config.component.ts | 64 ++- .../trust-weighting.component.ts | 383 +++++++--------- 18 files changed, 1174 insertions(+), 1324 deletions(-) diff --git a/src/Web/StellaOps.Web/src/app/features/policy-decisioning/policy-decisioning-shell.component.ts b/src/Web/StellaOps.Web/src/app/features/policy-decisioning/policy-decisioning-shell.component.ts index e44cd3f49..f486254ec 100644 --- a/src/Web/StellaOps.Web/src/app/features/policy-decisioning/policy-decisioning-shell.component.ts +++ b/src/Web/StellaOps.Web/src/app/features/policy-decisioning/policy-decisioning-shell.component.ts @@ -24,15 +24,6 @@ import { import { buildContextRouteParams, } from '../../shared/ui/context-route-state/context-route-state'; -import { StellaPageTabsComponent, StellaPageTab } from '../../shared/components/stella-page-tabs/stella-page-tabs.component'; - -type DecisioningPrimaryTab = - | 'packs' - | 'governance' - | 'simulation' - | 'vex' - | 'gates' - | 'audit'; type DecisioningContextKind = | 'global' @@ -43,7 +34,6 @@ type DecisioningContextKind = | 'evidence'; interface DecisioningShellState { - readonly activeTab: DecisioningPrimaryTab; readonly kind: DecisioningContextKind; readonly packId: string | null; readonly releaseId: string | null; @@ -55,15 +45,6 @@ interface DecisioningShellState { readonly evidenceId: string | null; } -const PAGE_TABS: readonly StellaPageTab[] = [ - { id: 'packs', label: 'Packs', icon: '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.27 6.96L12 12.01l8.73-5.05|||M12 22.08V12' }, - { id: 'governance', label: 'Governance', icon: 'M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z' }, - { id: 'simulation', label: 'Simulation', icon: 'M5 3l14 9-14 9V3z' }, - { id: 'vex', label: 'VEX & Exceptions', 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: 'gates', label: 'Release Gates', icon: 'M9 11l3 3L22 4|||M21 12v7a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11' }, - { id: 'audit', label: 'Audit', icon: 'M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2|||M8 2h8v4H8z' }, -]; - @Component({ selector: 'app-policy-decisioning-shell', imports: [ @@ -71,7 +52,6 @@ const PAGE_TABS: readonly StellaPageTab[] = [ RouterLink, RouterOutlet, ContextHeaderComponent, - StellaPageTabsComponent, ], template: `
@@ -94,15 +74,7 @@ const PAGE_TABS: readonly StellaPageTab[] = [ - - - +
`, styles: [` @@ -136,7 +108,6 @@ export class PolicyDecisioningShellComponent { private readonly router = inject(Router); private readonly destroyRef = inject(DestroyRef); - readonly pageTabs = PAGE_TABS; readonly shellState = signal(this.readShellState()); readonly headerTitle = computed(() => { @@ -172,7 +143,7 @@ export class PolicyDecisioningShellComponent { case 'evidence': return 'Trace evidence, gate posture, and policy or VEX actions from a single canonical route.'; default: - return 'Policy packs, governance, simulation, VEX, exceptions, release gates, and audit.'; + return 'Policy packs, governance, simulation, VEX, exceptions, and audit.'; } }); @@ -236,42 +207,6 @@ export class PolicyDecisioningShellComponent { }); } - onTabChange(tabId: string): void { - const state = this.shellState(); - const queryParams = this.contextQueryParams(); - - let route: readonly unknown[]; - switch (tabId) { - case 'overview': - route = this.overviewRoute(); - break; - case 'packs': - route = state.packId - ? ['/ops/policy/packs', state.packId] - : ['/ops/policy/packs']; - break; - case 'governance': - route = ['/ops/policy/governance']; - break; - case 'simulation': - route = ['/ops/policy/simulation']; - break; - case 'vex': - route = ['/ops/policy/vex']; - break; - case 'gates': - route = this.gatesRoute(); - break; - case 'audit': - route = ['/ops/policy/audit']; - break; - default: - route = this.overviewRoute(); - } - - void this.router.navigate([...route], { queryParams }); - } - overviewRoute(): readonly unknown[] { return ['/ops/policy/overview']; } @@ -318,7 +253,6 @@ export class PolicyDecisioningShellComponent { private readShellState(): DecisioningShellState { const params = collectRouteParams(this.route.snapshot.root); const queryParams = this.route.snapshot.root.queryParams ?? {}; - const currentUrl = this.router.url.split('?')[0] ?? ''; const releaseId = coerceString(params['releaseId']) ?? coerceString(queryParams['releaseId']); const approvalId = coerceString(params['approvalId']) ?? coerceString(queryParams['approvalId']); const packId = coerceString(params['packId']) ?? coerceString(queryParams['packId']); @@ -347,7 +281,6 @@ export class PolicyDecisioningShellComponent { } return { - activeTab: resolvePrimaryTab(currentUrl), kind, packId, releaseId, @@ -382,46 +315,6 @@ function collectRouteParams(snapshot: ActivatedRouteSnapshot | null): Record Approvals { path: 'gates', - title: 'Release Gates', - loadComponent: () => - import('./policy-decisioning-gates-page.component').then( - (m) => m.PolicyDecisioningGatesPageComponent, - ), + redirectTo: '/releases/deployments?view=approvals', + pathMatch: 'full' as const, }, { path: 'gates/catalog', diff --git a/src/Web/StellaOps.Web/src/app/features/policy-governance/conflict-resolution-wizard.component.ts b/src/Web/StellaOps.Web/src/app/features/policy-governance/conflict-resolution-wizard.component.ts index 2a3610d59..2ba6e6f30 100644 --- a/src/Web/StellaOps.Web/src/app/features/policy-governance/conflict-resolution-wizard.component.ts +++ b/src/Web/StellaOps.Web/src/app/features/policy-governance/conflict-resolution-wizard.component.ts @@ -415,7 +415,7 @@ import { injectPolicyGovernanceScopeResolver } from './policy-governance-scope'; margin: 0; font-size: 1.25rem; font-weight: var(--font-weight-semibold); - color: var(--color-surface-secondary); + color: var(--color-text-heading); } .wizard__subtitle { @@ -430,7 +430,7 @@ import { injectPolicyGovernanceScopeResolver } from './policy-governance-scope'; gap: 0.5rem; margin-bottom: 1.5rem; padding: 1rem; - background: var(--color-text-heading); + background: var(--color-surface-elevated); border-radius: var(--radius-lg); } @@ -446,7 +446,7 @@ import { injectPolicyGovernanceScopeResolver } from './policy-governance-scope'; } .wizard-step:hover { - background: var(--color-text-primary); + background: var(--color-surface-tertiary); } .wizard-step__number { @@ -456,7 +456,7 @@ import { injectPolicyGovernanceScopeResolver } from './policy-governance-scope'; align-items: center; justify-content: center; border-radius: var(--radius-full); - background: var(--color-text-primary); + background: var(--color-surface-tertiary); color: var(--color-text-muted); font-size: 0.85rem; font-weight: var(--font-weight-semibold); @@ -468,7 +468,7 @@ import { injectPolicyGovernanceScopeResolver } from './policy-governance-scope'; } .wizard-step--active { - background: var(--color-text-primary); + background: var(--color-surface-tertiary); } .wizard-step--active .wizard-step__number { @@ -477,7 +477,7 @@ import { injectPolicyGovernanceScopeResolver } from './policy-governance-scope'; } .wizard-step--active .wizard-step__label { - color: var(--color-surface-secondary); + color: var(--color-text-heading); } .wizard-step--completed .wizard-step__number { @@ -487,8 +487,8 @@ import { injectPolicyGovernanceScopeResolver } from './policy-governance-scope'; /* Wizard Content */ .wizard-content { - background: var(--color-text-heading); - border: 1px solid var(--color-text-heading); + background: var(--color-surface-elevated); + border: 1px solid var(--color-border-primary); border-radius: var(--radius-lg); padding: 1.5rem; min-height: 400px; @@ -506,7 +506,7 @@ import { injectPolicyGovernanceScopeResolver } from './policy-governance-scope'; .step-title { margin: 0 0 0.25rem; font-size: 1.1rem; - color: var(--color-surface-secondary); + color: var(--color-text-heading); } .step-desc { @@ -517,8 +517,8 @@ import { injectPolicyGovernanceScopeResolver } from './policy-governance-scope'; /* Conflict Summary */ .conflict-summary { - background: var(--color-text-heading); - border: 1px solid var(--color-text-heading); + background: var(--color-surface-elevated); + border: 1px solid var(--color-border-primary); border-radius: var(--radius-lg); padding: 1.25rem; } @@ -537,7 +537,7 @@ import { injectPolicyGovernanceScopeResolver } from './policy-governance-scope'; font-weight: var(--font-weight-semibold); } - .badge--type { background: var(--color-text-primary); color: var(--color-text-muted); } + .badge--type { background: var(--color-surface-tertiary); color: var(--color-text-muted); } .badge--info { background: var(--color-status-info-text); color: #fff; } .badge--warning { background: var(--color-status-warning-text); color: #fff; } .badge--error { background: var(--color-severity-high); color: var(--color-severity-high-bg); } @@ -546,7 +546,7 @@ import { injectPolicyGovernanceScopeResolver } from './policy-governance-scope'; .conflict-title { margin: 0 0 0.5rem; font-size: 1rem; - color: var(--color-surface-secondary); + color: var(--color-text-heading); } .conflict-desc { @@ -558,8 +558,8 @@ import { injectPolicyGovernanceScopeResolver } from './policy-governance-scope'; .impact-box { padding: 0.75rem; - background: rgba(234, 179, 8, 0.1); - border: 1px solid rgba(234, 179, 8, 0.3); + background: var(--color-status-warning-bg); + border: 1px solid var(--color-status-warning-border); border-radius: var(--radius-md); font-size: 0.9rem; color: var(--color-status-warning-border); @@ -583,8 +583,8 @@ import { injectPolicyGovernanceScopeResolver } from './policy-governance-scope'; } .comparison-panel { - background: var(--color-text-heading); - border: 1px solid var(--color-text-heading); + background: var(--color-surface-elevated); + border: 1px solid var(--color-border-primary); border-radius: var(--radius-lg); overflow: hidden; display: flex; @@ -596,19 +596,19 @@ import { injectPolicyGovernanceScopeResolver } from './policy-governance-scope'; justify-content: space-between; align-items: center; padding: 0.75rem 1rem; - background: var(--color-text-primary); + background: var(--color-surface-tertiary); } .panel-header h4 { margin: 0; font-size: 0.9rem; - color: var(--color-surface-secondary); + color: var(--color-text-heading); } .source-badge { font-size: 0.7rem; padding: 0.15rem 0.5rem; - background: var(--color-text-primary); + background: var(--color-surface-tertiary); color: var(--color-text-muted); border-radius: var(--radius-sm); } @@ -635,20 +635,20 @@ import { injectPolicyGovernanceScopeResolver } from './policy-governance-scope'; } .info-value { - color: var(--color-border-primary); + color: var(--color-text-primary); } .info-code { font-family: monospace; font-size: 0.8rem; color: var(--color-status-info); - background: var(--color-text-primary); + background: var(--color-surface-tertiary); padding: 0.1rem 0.35rem; border-radius: var(--radius-sm); } .source-preview { - background: var(--color-text-heading); + background: var(--color-surface-elevated); border-radius: var(--radius-md); padding: 0.75rem; overflow-x: auto; @@ -663,7 +663,7 @@ import { injectPolicyGovernanceScopeResolver } from './policy-governance-scope'; .panel-actions { padding: 0.75rem 1rem; - border-top: 1px solid var(--color-text-heading); + border-top: 1px solid var(--color-border-primary); } .comparison-indicator { @@ -689,8 +689,8 @@ import { injectPolicyGovernanceScopeResolver } from './policy-governance-scope'; align-items: center; gap: 0.5rem; padding: 0.75rem 1rem; - background: rgba(34, 197, 94, 0.1); - border: 1px solid rgba(34, 197, 94, 0.3); + background: var(--color-status-success-bg); + border: 1px solid var(--color-status-success-border); border-radius: var(--radius-md); margin-top: 1rem; color: var(--color-status-success-border); @@ -710,8 +710,8 @@ import { injectPolicyGovernanceScopeResolver } from './policy-governance-scope'; } .strategy-card { - background: var(--color-text-heading); - border: 2px solid var(--color-text-heading); + background: var(--color-surface-elevated); + border: 2px solid var(--color-border-primary); border-radius: var(--radius-lg); padding: 1.25rem; cursor: pointer; @@ -720,12 +720,12 @@ import { injectPolicyGovernanceScopeResolver } from './policy-governance-scope'; } .strategy-card:hover { - border-color: var(--color-text-primary); + border-color: var(--color-border-primary); } .strategy-card--selected { border-color: var(--color-status-info); - background: rgba(34, 211, 238, 0.05); + background: var(--color-status-info-bg); } .strategy-icon { @@ -735,20 +735,20 @@ import { injectPolicyGovernanceScopeResolver } from './policy-governance-scope'; display: flex; align-items: center; justify-content: center; - background: var(--color-text-primary); + background: var(--color-surface-tertiary); border-radius: var(--radius-xl); color: var(--color-text-muted); } .strategy-card--selected .strategy-icon { - background: rgba(34, 211, 238, 0.2); + background: var(--color-status-info-bg); color: var(--color-status-info); } .strategy-name { margin: 0 0 0.35rem; font-size: 0.95rem; - color: var(--color-surface-secondary); + color: var(--color-text-heading); } .strategy-desc { @@ -762,8 +762,8 @@ import { injectPolicyGovernanceScopeResolver } from './policy-governance-scope'; align-items: flex-start; gap: 0.75rem; padding: 1rem; - background: rgba(34, 211, 238, 0.1); - border: 1px solid rgba(34, 211, 238, 0.3); + background: var(--color-status-info-bg); + border: 1px solid var(--color-status-info-border); border-radius: var(--radius-lg); } @@ -787,8 +787,8 @@ import { injectPolicyGovernanceScopeResolver } from './policy-governance-scope'; /* Resolution Summary */ .resolution-summary { - background: var(--color-text-heading); - border: 1px solid var(--color-text-heading); + background: var(--color-surface-elevated); + border: 1px solid var(--color-border-primary); border-radius: var(--radius-lg); padding: 1.25rem; margin-bottom: 1.5rem; @@ -797,7 +797,7 @@ import { injectPolicyGovernanceScopeResolver } from './policy-governance-scope'; .resolution-summary h4 { margin: 0 0 1rem; font-size: 1rem; - color: var(--color-surface-secondary); + color: var(--color-text-heading); } .summary-grid { @@ -818,7 +818,7 @@ import { injectPolicyGovernanceScopeResolver } from './policy-governance-scope'; } .summary-value { - color: var(--color-border-primary); + color: var(--color-text-primary); } .form-field { @@ -827,7 +827,7 @@ import { injectPolicyGovernanceScopeResolver } from './policy-governance-scope'; .form-label { display: block; - color: var(--color-border-primary); + color: var(--color-text-primary); font-size: 0.85rem; font-weight: var(--font-weight-medium); margin-bottom: 0.35rem; @@ -836,10 +836,10 @@ import { injectPolicyGovernanceScopeResolver } from './policy-governance-scope'; .form-textarea { width: 100%; padding: 0.75rem; - background: var(--color-text-heading); - border: 1px solid var(--color-text-primary); + background: var(--color-surface-elevated); + border: 1px solid var(--color-border-primary); border-radius: var(--radius-md); - color: var(--color-border-primary); + color: var(--color-text-primary); font-size: 0.9rem; resize: vertical; } @@ -857,8 +857,8 @@ import { injectPolicyGovernanceScopeResolver } from './policy-governance-scope'; } .preview-changes { - background: var(--color-text-heading); - border: 1px solid var(--color-text-heading); + background: var(--color-surface-elevated); + border: 1px solid var(--color-border-primary); border-radius: var(--radius-lg); padding: 1.25rem; } @@ -866,7 +866,7 @@ import { injectPolicyGovernanceScopeResolver } from './policy-governance-scope'; .preview-changes h4 { margin: 0 0 0.75rem; font-size: 1rem; - color: var(--color-surface-secondary); + color: var(--color-text-heading); } .changes-list { @@ -887,7 +887,7 @@ import { injectPolicyGovernanceScopeResolver } from './policy-governance-scope'; gap: 1rem; margin-top: 1.5rem; padding-top: 1.5rem; - border-top: 1px solid var(--color-text-heading); + border-top: 1px solid var(--color-border-primary); } .wizard-nav__spacer { @@ -912,11 +912,11 @@ import { injectPolicyGovernanceScopeResolver } from './policy-governance-scope'; .btn--primary:hover:not(:disabled) { background: var(--color-status-info); } .btn--primary:disabled { opacity: 0.5; cursor: not-allowed; } - .btn--secondary { background: var(--color-text-primary); color: var(--color-border-primary); border: 1px solid var(--color-text-primary); } - .btn--secondary:hover { background: var(--color-text-primary); } + .btn--secondary { background: var(--color-surface-tertiary); color: var(--color-text-primary); border: 1px solid var(--color-border-primary); } + .btn--secondary:hover { background: var(--color-surface-tertiary); } .btn--ghost { background: transparent; color: var(--color-text-muted); } - .btn--ghost:hover { background: var(--color-text-primary); color: var(--color-border-primary); } + .btn--ghost:hover { background: var(--color-surface-tertiary); color: var(--color-text-primary); } .btn--ghost:disabled { opacity: 0.5; cursor: not-allowed; } .btn--small { padding: 0.35rem 0.75rem; font-size: 0.8rem; } @@ -929,8 +929,8 @@ import { injectPolicyGovernanceScopeResolver } from './policy-governance-scope'; justify-content: center; padding: 4rem 2rem; text-align: center; - background: var(--color-text-heading); - border: 1px solid var(--color-text-heading); + background: var(--color-surface-elevated); + border: 1px solid var(--color-border-primary); border-radius: var(--radius-lg); } @@ -941,7 +941,7 @@ import { injectPolicyGovernanceScopeResolver } from './policy-governance-scope'; .empty-state h3 { margin: 0 0 0.5rem; - color: var(--color-surface-secondary); + color: var(--color-text-heading); } .empty-state p { diff --git a/src/Web/StellaOps.Web/src/app/features/policy-governance/governance-audit.component.ts b/src/Web/StellaOps.Web/src/app/features/policy-governance/governance-audit.component.ts index 124190ee4..8f72bd689 100644 --- a/src/Web/StellaOps.Web/src/app/features/policy-governance/governance-audit.component.ts +++ b/src/Web/StellaOps.Web/src/app/features/policy-governance/governance-audit.component.ts @@ -13,6 +13,8 @@ import { AuditEventType, GovernanceAuditDiff, } from '../../core/api/policy-governance.models'; +import { LoadingStateComponent } from '../../shared/components/loading-state/loading-state.component'; +import { StellaFilterChipComponent } from '../../shared/components/stella-filter-chip/stella-filter-chip.component'; import { injectPolicyGovernanceScopeResolver } from './policy-governance-scope'; /** @@ -23,7 +25,7 @@ import { injectPolicyGovernanceScopeResolver } from './policy-governance-scope'; */ @Component({ selector: 'app-governance-audit', - imports: [CommonModule, FormsModule], + imports: [CommonModule, FormsModule, LoadingStateComponent, StellaFilterChipComponent], changeDetection: ChangeDetectionStrategy.OnPush, template: `
@@ -36,15 +38,12 @@ import { injectPolicyGovernanceScopeResolver } from './policy-governance-scope';
-
- - -
+
@@ -91,7 +90,7 @@ import { injectPolicyGovernanceScopeResolver } from './policy-governance-scope';
{{ formatEventType(event.type) }}
-
{{ event.summary }}
+
{{ event.summary }}
@@ -219,7 +218,7 @@ import { injectPolicyGovernanceScopeResolver } from './policy-governance-scope';
} } @else if (loading()) { -
Loading audit events...
+ } @else {
@@ -245,7 +244,7 @@ import { injectPolicyGovernanceScopeResolver } from './policy-governance-scope'; margin: 0; font-size: 1.25rem; font-weight: var(--font-weight-semibold); - color: var(--color-surface-secondary); + color: var(--color-text-heading); } .audit__subtitle { @@ -262,9 +261,10 @@ import { injectPolicyGovernanceScopeResolver } from './policy-governance-scope'; align-items: flex-end; margin-bottom: 1.5rem; padding: 1rem; - background: var(--color-text-heading); - border: 1px solid var(--color-text-heading); + background: var(--color-surface-elevated); + border: 1px solid var(--color-border-primary); border-radius: var(--radius-lg); + overflow: visible; } .filter-group { @@ -278,17 +278,17 @@ import { injectPolicyGovernanceScopeResolver } from './policy-governance-scope'; color: var(--color-text-muted); } - .form-input, .form-select { + .form-input { padding: 0.5rem 0.75rem; - background: var(--color-text-heading); - border: 1px solid var(--color-text-primary); + background: var(--color-surface-elevated); + border: 1px solid var(--color-border-primary); border-radius: var(--radius-md); - color: var(--color-border-primary); + color: var(--color-text-primary); font-size: 0.85rem; min-width: 150px; } - .form-input:focus, .form-select:focus { + .form-input:focus { outline: none; border-color: var(--color-status-info); } @@ -303,8 +303,8 @@ import { injectPolicyGovernanceScopeResolver } from './policy-governance-scope'; border: none; } - .btn--ghost { background: transparent; color: var(--color-text-muted); border: 1px solid var(--color-text-primary); } - .btn--ghost:hover { background: var(--color-text-primary); color: var(--color-border-primary); } + .btn--ghost { background: transparent; color: var(--color-text-muted); border: 1px solid var(--color-border-primary); } + .btn--ghost:hover { background: var(--color-surface-tertiary); color: var(--color-text-primary); } .btn--ghost:disabled { opacity: 0.5; cursor: not-allowed; } .btn--small { padding: 0.35rem 0.75rem; font-size: 0.8rem; } @@ -317,8 +317,8 @@ import { injectPolicyGovernanceScopeResolver } from './policy-governance-scope'; } .event-card { - background: var(--color-text-heading); - border: 1px solid var(--color-text-heading); + background: var(--color-surface-elevated); + border: 1px solid var(--color-border-primary); border-radius: var(--radius-lg); overflow: hidden; } @@ -333,7 +333,7 @@ import { injectPolicyGovernanceScopeResolver } from './policy-governance-scope'; } .event-card__header:hover { - background: var(--color-text-heading); + background: var(--color-surface-elevated); } .event-card__icon { @@ -346,10 +346,10 @@ import { injectPolicyGovernanceScopeResolver } from './policy-governance-scope'; flex-shrink: 0; } - .event-card__icon--config { background: rgba(34, 211, 238, 0.2); color: var(--color-status-info); } - .event-card__icon--security { background: rgba(234, 179, 8, 0.2); color: var(--color-status-warning); } - .event-card__icon--profile { background: rgba(168, 85, 247, 0.2); color: var(--color-status-excepted); } - .event-card__icon--other { background: rgba(148, 163, 184, 0.2); color: var(--color-text-muted); } + .event-card__icon--config { background: var(--color-status-info-bg); color: var(--color-status-info); } + .event-card__icon--security { background: var(--color-status-warning-bg); color: var(--color-status-warning); } + .event-card__icon--profile { background: var(--color-brand-primary-20); color: var(--color-status-excepted); } + .event-card__icon--other { background: var(--color-surface-tertiary); color: var(--color-text-muted); } .event-card__content { flex: 1; @@ -364,7 +364,7 @@ import { injectPolicyGovernanceScopeResolver } from './policy-governance-scope'; } .event-card__summary { - color: var(--color-surface-secondary); + color: var(--color-text-heading); font-weight: var(--font-weight-medium); white-space: nowrap; overflow: hidden; @@ -382,7 +382,7 @@ import { injectPolicyGovernanceScopeResolver } from './policy-governance-scope'; justify-content: flex-end; gap: 0.5rem; font-size: 0.85rem; - color: var(--color-border-primary); + color: var(--color-text-primary); } .actor-badge { @@ -393,7 +393,7 @@ import { injectPolicyGovernanceScopeResolver } from './policy-governance-scope'; } .actor-badge--user { background: var(--color-status-info-text); color: #fff; } - .actor-badge--system { background: var(--color-text-primary); color: var(--color-text-muted); } + .actor-badge--system { background: var(--color-surface-tertiary); color: var(--color-text-muted); } .actor-badge--automation { background: var(--color-status-success-text); color: #fff; } .event-card__time { @@ -416,8 +416,8 @@ import { injectPolicyGovernanceScopeResolver } from './policy-governance-scope'; /* Event Details */ .event-card__details { padding: 1rem; - border-top: 1px solid var(--color-text-heading); - background: var(--color-text-heading); + border-top: 1px solid var(--color-border-primary); + background: var(--color-surface-elevated); } .detail-row { @@ -433,7 +433,7 @@ import { injectPolicyGovernanceScopeResolver } from './policy-governance-scope'; } .detail-value { - color: var(--color-border-primary); + color: var(--color-text-primary); } .detail-value--mono { @@ -445,13 +445,13 @@ import { injectPolicyGovernanceScopeResolver } from './policy-governance-scope'; .diff-section, .state-section { margin-top: 1rem; padding-top: 1rem; - border-top: 1px solid var(--color-text-heading); + border-top: 1px solid var(--color-border-primary); } .diff-section h4, .state-section h4 { margin: 0 0 0.75rem; font-size: 0.9rem; - color: var(--color-surface-secondary); + color: var(--color-text-heading); } .diff-viewer { @@ -461,7 +461,7 @@ import { injectPolicyGovernanceScopeResolver } from './policy-governance-scope'; } .diff-group { - background: var(--color-text-heading); + background: var(--color-surface-elevated); border-radius: var(--radius-md); padding: 0.75rem; border-left: 3px solid; @@ -501,7 +501,7 @@ import { injectPolicyGovernanceScopeResolver } from './policy-governance-scope'; } .state-block { - background: var(--color-text-heading); + background: var(--color-surface-elevated); border-radius: var(--radius-md); overflow: hidden; } @@ -510,7 +510,7 @@ import { injectPolicyGovernanceScopeResolver } from './policy-governance-scope'; padding: 0.5rem 0.75rem; font-size: 0.75rem; text-transform: uppercase; - background: var(--color-text-primary); + background: var(--color-surface-tertiary); } .state-block--before .state-block__title { color: var(--color-status-error-border); } @@ -520,7 +520,7 @@ import { injectPolicyGovernanceScopeResolver } from './policy-governance-scope'; padding: 0.75rem; margin: 0; font-size: 0.8rem; - color: var(--color-border-primary); + color: var(--color-text-primary); white-space: pre-wrap; overflow-x: auto; } @@ -532,8 +532,8 @@ import { injectPolicyGovernanceScopeResolver } from './policy-governance-scope'; align-items: center; margin-top: 1rem; padding: 1rem; - background: var(--color-text-heading); - border: 1px solid var(--color-text-heading); + background: var(--color-surface-elevated); + border: 1px solid var(--color-border-primary); border-radius: var(--radius-lg); } @@ -550,11 +550,11 @@ import { injectPolicyGovernanceScopeResolver } from './policy-governance-scope'; .pagination__current { font-size: 0.85rem; - color: var(--color-border-primary); + color: var(--color-text-primary); } - /* Empty & Loading States */ - .empty-state, .loading-state { + /* Empty State */ + .empty-state { display: flex; flex-direction: column; align-items: center; @@ -593,6 +593,11 @@ export class GovernanceAuditComponent implements OnInit { 'conflict_resolved', ]; + readonly eventTypeOptions = [ + { id: '', label: 'All Types' }, + ...this.eventTypes.map(t => ({ id: t, label: this.formatEventType(t) })), + ]; + protected filters = { eventType: '', actor: '', diff --git a/src/Web/StellaOps.Web/src/app/features/policy-governance/impact-preview.component.ts b/src/Web/StellaOps.Web/src/app/features/policy-governance/impact-preview.component.ts index 35f4d0b83..1148831ae 100644 --- a/src/Web/StellaOps.Web/src/app/features/policy-governance/impact-preview.component.ts +++ b/src/Web/StellaOps.Web/src/app/features/policy-governance/impact-preview.component.ts @@ -1,5 +1,5 @@ import { CommonModule } from '@angular/common'; -import { Component, ChangeDetectionStrategy, inject, signal, OnInit } from '@angular/core'; +import { Component, ChangeDetectionStrategy, inject, signal, OnInit, ViewChild } from '@angular/core'; import { ActivatedRoute, RouterModule } from '@angular/router'; import { finalize } from 'rxjs/operators'; @@ -11,6 +11,9 @@ import { TrustWeightAffectedFinding, Severity, } from '../../core/api/policy-governance.models'; +import { ConfirmDialogComponent } from '../../shared/components/confirm-dialog/confirm-dialog.component'; +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 { injectPolicyGovernanceScopeResolver } from './policy-governance-scope'; /** @@ -21,7 +24,7 @@ import { injectPolicyGovernanceScopeResolver } from './policy-governance-scope'; */ @Component({ selector: 'app-impact-preview', - imports: [CommonModule, RouterModule], + imports: [CommonModule, RouterModule, ConfirmDialogComponent, StellaMetricCardComponent, StellaMetricGridComponent], changeDetection: ChangeDetectionStrategy.OnPush, template: `
@@ -40,20 +43,23 @@ import { injectPolicyGovernanceScopeResolver } from './policy-governance-scope'; @if (impact(); as i) { -
-
-
{{ i.affectedVulnerabilities }}
-
Affected Vulnerabilities
-
-
-
{{ i.severityChanges }}
-
Severity Changes
-
-
-
{{ i.decisionChanges }}
-
Decision Changes
-
-
+ + + + + @if (getTransitionEntries(i.severityTransitions).length > 0) { @@ -187,6 +193,12 @@ import { injectPolicyGovernanceScopeResolver } from './policy-governance-scope'; Configure Trust Weights
} + +
`, styles: [` @@ -208,7 +220,7 @@ import { injectPolicyGovernanceScopeResolver } from './policy-governance-scope'; margin: 0; font-size: 1.25rem; font-weight: var(--font-weight-semibold); - color: var(--color-surface-secondary); + color: var(--color-text-heading); } .preview__subtitle { @@ -240,54 +252,13 @@ import { injectPolicyGovernanceScopeResolver } from './policy-governance-scope'; .btn--primary:hover:not(:disabled) { background: var(--color-status-info); } .btn--primary:disabled { opacity: 0.5; cursor: not-allowed; } - .btn--ghost { background: transparent; color: var(--color-text-muted); border: 1px solid var(--color-text-primary); } - .btn--ghost:hover { background: var(--color-text-primary); color: var(--color-border-primary); } - - /* Summary Grid */ - .summary-grid { - display: grid; - grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); - gap: 1rem; - margin-bottom: 1.5rem; - } - - .summary-card { - background: var(--color-text-heading); - border: 1px solid var(--color-text-heading); - border-radius: var(--radius-lg); - padding: 1.25rem; - text-align: center; - } - - .summary-card--warning { - border-color: rgba(234, 179, 8, 0.3); - background: rgba(234, 179, 8, 0.05); - } - - .summary-card--danger { - border-color: rgba(239, 68, 68, 0.3); - background: rgba(239, 68, 68, 0.05); - } - - .summary-card__value { - font-size: 2.5rem; - font-weight: var(--font-weight-bold); - color: var(--color-status-info); - } - - .summary-card--warning .summary-card__value { color: var(--color-status-warning); } - .summary-card--danger .summary-card__value { color: var(--color-status-error); } - - .summary-card__label { - margin-top: 0.25rem; - font-size: 0.9rem; - color: var(--color-text-muted); - } + .btn--ghost { background: transparent; color: var(--color-text-muted); border: 1px solid var(--color-border-primary); } + .btn--ghost:hover { background: var(--color-surface-tertiary); color: var(--color-text-primary); } /* Sections */ .section { - background: var(--color-text-heading); - border: 1px solid var(--color-text-heading); + background: var(--color-surface-elevated); + border: 1px solid var(--color-border-primary); border-radius: var(--radius-lg); padding: 1.25rem; margin-bottom: 1rem; @@ -304,13 +275,13 @@ import { injectPolicyGovernanceScopeResolver } from './policy-governance-scope'; margin: 0; font-size: 1rem; font-weight: var(--font-weight-semibold); - color: var(--color-surface-secondary); + color: var(--color-text-heading); } .section__badge { font-size: 0.8rem; padding: 0.2rem 0.6rem; - background: var(--color-text-primary); + background: var(--color-surface-tertiary); color: var(--color-text-muted); border-radius: var(--radius-full); } @@ -327,13 +298,13 @@ import { injectPolicyGovernanceScopeResolver } from './policy-governance-scope'; justify-content: space-between; align-items: center; padding: 0.75rem 1rem; - background: var(--color-text-heading); + background: var(--color-surface-elevated); border-radius: var(--radius-md); } .transition-card__label { font-size: 0.85rem; - color: var(--color-border-primary); + color: var(--color-text-primary); } .transition-card__count { @@ -356,7 +327,7 @@ import { injectPolicyGovernanceScopeResolver } from './policy-governance-scope'; .findings-table td { padding: 0.75rem 1rem; text-align: left; - border-bottom: 1px solid var(--color-text-heading); + border-bottom: 1px solid var(--color-border-primary); } .findings-table th { @@ -365,23 +336,23 @@ import { injectPolicyGovernanceScopeResolver } from './policy-governance-scope'; color: var(--color-text-muted); text-transform: uppercase; letter-spacing: 0.03em; - background: var(--color-text-heading); + background: var(--color-surface-elevated); } .findings-table td { font-size: 0.9rem; - color: var(--color-border-primary); + color: var(--color-text-primary); } .findings-table tr:hover td { - background: var(--color-text-heading); + background: var(--color-surface-elevated); } .purl { font-family: monospace; font-size: 0.8rem; color: var(--color-status-info); - background: var(--color-text-heading); + background: var(--color-surface-elevated); padding: 0.15rem 0.35rem; border-radius: var(--radius-sm); max-width: 200px; @@ -393,7 +364,7 @@ import { injectPolicyGovernanceScopeResolver } from './policy-governance-scope'; .advisory-id { font-family: monospace; font-size: 0.85rem; - color: var(--color-surface-secondary); + color: var(--color-text-heading); } /* Severity Badges */ @@ -410,7 +381,7 @@ import { injectPolicyGovernanceScopeResolver } from './policy-governance-scope'; .severity--high { background: var(--color-severity-high); color: var(--color-severity-high-bg); } .severity--medium { background: var(--color-status-warning-text); color: #fff; } .severity--low { background: var(--color-status-info-text); color: #fff; } - .severity--info { background: var(--color-text-primary); color: var(--color-text-muted); } + .severity--info { background: var(--color-surface-tertiary); color: var(--color-text-muted); } .change-indicator { display: inline-flex; @@ -446,8 +417,8 @@ import { injectPolicyGovernanceScopeResolver } from './policy-governance-scope'; align-items: flex-start; gap: 1rem; padding: 1rem 1.25rem; - background: rgba(239, 68, 68, 0.1); - border: 1px solid rgba(239, 68, 68, 0.3); + background: var(--color-status-error-bg); + border: 1px solid var(--color-status-error-border); border-radius: var(--radius-lg); margin-top: 1rem; } @@ -478,8 +449,8 @@ import { injectPolicyGovernanceScopeResolver } from './policy-governance-scope'; justify-content: center; padding: 4rem 2rem; text-align: center; - background: var(--color-text-heading); - border: 1px solid var(--color-text-heading); + background: var(--color-surface-elevated); + border: 1px solid var(--color-border-primary); border-radius: var(--radius-lg); } @@ -490,7 +461,7 @@ import { injectPolicyGovernanceScopeResolver } from './policy-governance-scope'; .empty-state h3 { margin: 0 0 0.5rem; - color: var(--color-surface-secondary); + color: var(--color-text-heading); } .empty-state p { @@ -512,7 +483,7 @@ import { injectPolicyGovernanceScopeResolver } from './policy-governance-scope'; .spinner { width: 40px; height: 40px; - border: 3px solid var(--color-text-heading); + border: 3px solid var(--color-border-primary); border-top-color: var(--color-status-info); border-radius: var(--radius-full); animation: spin 0.8s linear infinite; @@ -580,11 +551,13 @@ export class ImpactPreviewComponent implements OnInit { return projectedIndex > currentIndex ? 'up' : 'down'; } - protected applyChanges(): void { - if (!confirm('Are you sure you want to apply these changes? This action cannot be undone.')) { - return; - } + @ViewChild('applyConfirmDialog') private applyConfirmDialog!: ConfirmDialogComponent; + protected applyChanges(): void { + this.applyConfirmDialog.open(); + } + + protected executeApply(): void { this.applying.set(true); // In real implementation, apply the trust weight changes setTimeout(() => { diff --git a/src/Web/StellaOps.Web/src/app/features/policy-governance/policy-conflict-dashboard.component.ts b/src/Web/StellaOps.Web/src/app/features/policy-governance/policy-conflict-dashboard.component.ts index 4fbfee583..79f7d9712 100644 --- a/src/Web/StellaOps.Web/src/app/features/policy-governance/policy-conflict-dashboard.component.ts +++ b/src/Web/StellaOps.Web/src/app/features/policy-governance/policy-conflict-dashboard.component.ts @@ -1,5 +1,6 @@ import { CommonModule } from '@angular/common'; import { Component, ChangeDetectionStrategy, inject, signal, OnInit } from '@angular/core'; +import { ModalComponent } from '../../shared/components/modal/modal.component'; import { FormsModule } from '@angular/forms'; import { RouterModule } from '@angular/router'; import { finalize } from 'rxjs/operators'; @@ -13,6 +14,9 @@ import { PolicyConflictType, PolicyConflictSeverity, } from '../../core/api/policy-governance.models'; +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 { StellaFilterChipComponent } from '../../shared/components/stella-filter-chip/stella-filter-chip.component'; import { injectPolicyGovernanceScopeResolver } from './policy-governance-scope'; /** @@ -23,7 +27,7 @@ import { injectPolicyGovernanceScopeResolver } from './policy-governance-scope'; */ @Component({ selector: 'app-policy-conflict-dashboard', - imports: [CommonModule, FormsModule, RouterModule], + imports: [CommonModule, FormsModule, RouterModule, ModalComponent, StellaMetricCardComponent, StellaMetricGridComponent, StellaFilterChipComponent], changeDetection: ChangeDetectionStrategy.OnPush, template: `
@@ -42,24 +46,28 @@ import { injectPolicyGovernanceScopeResolver } from './policy-governance-scope'; @if (dashboard(); as d) { -
-
-
{{ d.totalConflicts }}
-
Total Conflicts
-
-
-
{{ d.openConflicts }}
-
Open
-
-
-
{{ d.bySeverity['warning'] || 0 }}
-
Warnings
-
-
-
{{ d.bySeverity['error'] || 0 }}
-
Errors
-
-
+ + + + + +
@@ -93,27 +101,18 @@ import { injectPolicyGovernanceScopeResolver } from './policy-governance-scope';
-
- - -
-
- - -
+ +
@@ -204,6 +203,48 @@ import { injectPolicyGovernanceScopeResolver } from './policy-governance-scope';

Your policy configuration has no detected conflicts.

} + + + @if (showResolveModal()) { + +
+ + +
+ + + + +
+ } + + + @if (showIgnoreModal()) { + +
+ + +
+ + + + +
+ }
`, styles: [` @@ -224,7 +265,7 @@ import { injectPolicyGovernanceScopeResolver } from './policy-governance-scope'; margin: 0; font-size: 1.25rem; font-weight: var(--font-weight-semibold); - color: var(--color-surface-secondary); + color: var(--color-text-heading); } .conflicts__subtitle { @@ -251,55 +292,19 @@ import { injectPolicyGovernanceScopeResolver } from './policy-governance-scope'; .btn--primary:hover:not(:disabled) { background: var(--color-status-info); } .btn--primary:disabled { opacity: 0.5; cursor: not-allowed; } - .btn--secondary { background: var(--color-text-primary); color: var(--color-border-primary); border: 1px solid var(--color-text-primary); } - .btn--secondary:hover:not(:disabled) { background: var(--color-text-primary); } + .btn--secondary { background: var(--color-surface-tertiary); color: var(--color-text-primary); border: 1px solid var(--color-border-primary); } + .btn--secondary:hover:not(:disabled) { background: var(--color-surface-tertiary); } .btn--secondary:disabled { opacity: 0.5; cursor: not-allowed; } .btn--ghost { background: transparent; color: var(--color-text-muted); } - .btn--ghost:hover { background: var(--color-text-primary); color: var(--color-border-primary); } + .btn--ghost:hover { background: var(--color-surface-tertiary); color: var(--color-text-primary); } .btn--small { padding: 0.35rem 0.75rem; font-size: 0.8rem; } - /* Summary Grid */ - .summary-grid { - display: grid; - grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); - gap: 1rem; - margin-bottom: 1.5rem; - } - - .summary-card { - background: var(--color-text-heading); - border: 1px solid var(--color-text-heading); - border-radius: var(--radius-lg); - padding: 1rem; - text-align: center; - } - - .summary-card--open { - border-color: var(--color-status-warning); - background: rgba(234, 179, 8, 0.05); - } - - .summary-card__value { - font-size: 2rem; - font-weight: var(--font-weight-bold); - color: var(--color-status-info); - } - - .summary-card--open .summary-card__value { - color: var(--color-status-warning); - } - - .summary-card__label { - font-size: 0.85rem; - color: var(--color-text-muted); - } - /* Breakdown Section */ .breakdown-section, .trend-section { - background: var(--color-text-heading); - border: 1px solid var(--color-text-heading); + background: var(--color-surface-elevated); + border: 1px solid var(--color-border-primary); border-radius: var(--radius-lg); padding: 1rem; margin-bottom: 1rem; @@ -309,7 +314,7 @@ import { injectPolicyGovernanceScopeResolver } from './policy-governance-scope'; margin: 0 0 1rem; font-size: 0.9rem; font-weight: var(--font-weight-semibold); - color: var(--color-surface-secondary); + color: var(--color-text-heading); } .type-breakdown { @@ -329,12 +334,12 @@ import { injectPolicyGovernanceScopeResolver } from './policy-governance-scope'; .type-bar__label { font-size: 0.85rem; - color: var(--color-border-primary); + color: var(--color-text-primary); } .type-bar__track { height: 8px; - background: var(--color-text-heading); + background: var(--color-surface-elevated); border-radius: var(--radius-sm); overflow: hidden; } @@ -388,32 +393,7 @@ import { injectPolicyGovernanceScopeResolver } from './policy-governance-scope'; display: flex; gap: 1rem; margin-bottom: 1rem; - } - - .filter-group { - display: flex; - flex-direction: column; - gap: 0.35rem; - } - - .filter-label { - font-size: 0.8rem; - color: var(--color-text-muted); - } - - .form-select { - padding: 0.5rem 0.75rem; - background: var(--color-text-heading); - border: 1px solid var(--color-text-primary); - border-radius: var(--radius-md); - color: var(--color-border-primary); - font-size: 0.85rem; - min-width: 150px; - } - - .form-select:focus { - outline: none; - border-color: var(--color-status-info); + overflow: visible; } /* Conflict List */ @@ -424,8 +404,8 @@ import { injectPolicyGovernanceScopeResolver } from './policy-governance-scope'; } .conflict-card { - background: var(--color-text-heading); - border: 1px solid var(--color-text-heading); + background: var(--color-surface-elevated); + border: 1px solid var(--color-border-primary); border-radius: var(--radius-lg); overflow: hidden; border-left: 3px solid; @@ -441,7 +421,7 @@ import { injectPolicyGovernanceScopeResolver } from './policy-governance-scope'; justify-content: space-between; align-items: center; padding: 0.75rem 1rem; - background: var(--color-text-heading); + background: var(--color-surface-elevated); } .conflict-card__badges { @@ -457,17 +437,17 @@ import { injectPolicyGovernanceScopeResolver } from './policy-governance-scope'; font-weight: var(--font-weight-semibold); } - .badge--type { background: var(--color-text-primary); color: var(--color-text-muted); } + .badge--type { background: var(--color-surface-tertiary); color: var(--color-text-muted); } .badge--info { background: var(--color-status-info-text); color: #fff; } .badge--warning { background: var(--color-status-warning-text); color: #fff; } .badge--error { background: var(--color-severity-high); color: var(--color-severity-high-bg); } .badge--critical { background: var(--color-status-error-text); color: #fff; } - .badge--status { background: var(--color-text-primary); color: var(--color-text-muted); } + .badge--status { background: var(--color-surface-tertiary); color: var(--color-text-muted); } .badge--status-open { background: var(--color-status-warning-text); color: #fff; } .badge--status-acknowledged { background: var(--color-status-info-text); color: #fff; } .badge--status-resolved { background: var(--color-status-success-text); color: #fff; } - .badge--status-ignored { background: var(--color-text-primary); color: var(--color-text-muted); } + .badge--status-ignored { background: var(--color-surface-tertiary); color: var(--color-text-muted); } .conflict-card__time { font-size: 0.75rem; @@ -481,7 +461,7 @@ import { injectPolicyGovernanceScopeResolver } from './policy-governance-scope'; .conflict-card__summary { margin: 0 0 0.5rem; font-size: 1rem; - color: var(--color-surface-secondary); + color: var(--color-text-heading); } .conflict-card__desc { @@ -497,7 +477,7 @@ import { injectPolicyGovernanceScopeResolver } from './policy-governance-scope'; gap: 0.5rem; margin-bottom: 1rem; padding: 0.75rem; - background: var(--color-text-heading); + background: var(--color-surface-elevated); border-radius: var(--radius-md); } @@ -524,7 +504,7 @@ import { injectPolicyGovernanceScopeResolver } from './policy-governance-scope'; } .conflict-impact__text { - color: var(--color-border-primary); + color: var(--color-text-primary); } .conflict-suggestion { @@ -532,7 +512,7 @@ import { injectPolicyGovernanceScopeResolver } from './policy-governance-scope'; align-items: flex-start; gap: 0.5rem; padding: 0.75rem; - background: rgba(34, 211, 238, 0.1); + background: var(--color-status-info-bg); border-radius: var(--radius-md); font-size: 0.85rem; color: var(--color-status-info-border); @@ -546,7 +526,7 @@ import { injectPolicyGovernanceScopeResolver } from './policy-governance-scope'; .conflict-resolution { margin-top: 0.75rem; padding-top: 0.75rem; - border-top: 1px solid var(--color-text-heading); + border-top: 1px solid var(--color-border-primary); } .resolution-by { @@ -564,8 +544,8 @@ import { injectPolicyGovernanceScopeResolver } from './policy-governance-scope'; display: flex; gap: 0.5rem; padding: 0.75rem 1rem; - border-top: 1px solid var(--color-text-heading); - background: var(--color-text-heading); + border-top: 1px solid var(--color-border-primary); + background: var(--color-surface-elevated); } /* Empty State */ @@ -576,8 +556,8 @@ import { injectPolicyGovernanceScopeResolver } from './policy-governance-scope'; justify-content: center; padding: 3rem; text-align: center; - background: var(--color-text-heading); - border: 1px solid var(--color-text-heading); + background: var(--color-surface-elevated); + border: 1px solid var(--color-border-primary); border-radius: var(--radius-lg); } @@ -588,13 +568,42 @@ import { injectPolicyGovernanceScopeResolver } from './policy-governance-scope'; .empty-state h3 { margin: 0 0 0.5rem; - color: var(--color-surface-secondary); + color: var(--color-text-heading); } .empty-state p { margin: 0; color: var(--color-text-muted); } + + /* Form Fields (for modals) */ + .form-field { + margin-bottom: 1rem; + } + + .form-label { + display: block; + color: var(--color-text-primary); + font-size: 0.85rem; + font-weight: var(--font-weight-medium); + margin-bottom: 0.35rem; + } + + .form-textarea { + width: 100%; + padding: 0.5rem 0.75rem; + background: var(--color-surface-elevated); + border: 1px solid var(--color-border-primary); + border-radius: var(--radius-md); + color: var(--color-text-primary); + font-size: 0.9rem; + resize: vertical; + } + + .form-textarea:focus { + outline: none; + border-color: var(--color-status-info); + } `] }) export class PolicyConflictDashboardComponent implements OnInit { @@ -606,9 +615,32 @@ export class PolicyConflictDashboardComponent implements OnInit { protected readonly dashboard = signal(null); protected readonly conflicts = signal([]); + protected readonly showResolveModal = signal(false); + protected readonly showIgnoreModal = signal(false); + protected readonly resolveText = signal(''); + protected readonly ignoreText = signal(''); + private readonly pendingConflict = signal(null); + protected typeFilter: PolicyConflictType | '' = ''; protected severityFilter: PolicyConflictSeverity | '' = ''; + readonly typeOptions = [ + { id: '', label: 'All Types' }, + { id: 'rule_overlap', label: 'Rule Overlap' }, + { id: 'precedence_ambiguity', label: 'Precedence Ambiguity' }, + { id: 'circular_dependency', label: 'Circular Dependency' }, + { id: 'incompatible_actions', label: 'Incompatible Actions' }, + { id: 'scope_collision', label: 'Scope Collision' }, + ]; + + readonly severityOptions = [ + { id: '', label: 'All Severities' }, + { id: 'critical', label: 'Critical' }, + { id: 'error', label: 'Error' }, + { id: 'warning', label: 'Warning' }, + { id: 'info', label: 'Info' }, + ]; + ngOnInit(): void { this.loadDashboard(); this.loadConflicts(); @@ -683,9 +715,17 @@ export class PolicyConflictDashboardComponent implements OnInit { } protected resolveConflict(conflict: PolicyConflict): void { - const resolution = prompt('Enter resolution notes:'); - if (!resolution) return; + this.pendingConflict.set(conflict); + this.resolveText.set(''); + this.showResolveModal.set(true); + } + protected submitResolve(): void { + const conflict = this.pendingConflict(); + const resolution = this.resolveText(); + if (!conflict || !resolution) return; + + this.showResolveModal.set(false); this.api.resolveConflict(conflict.id, resolution, this.governanceScope()).subscribe({ next: () => { this.loadConflicts(); @@ -696,9 +736,17 @@ export class PolicyConflictDashboardComponent implements OnInit { } protected ignoreConflict(conflict: PolicyConflict): void { - const reason = prompt('Enter reason for ignoring:'); - if (!reason) return; + this.pendingConflict.set(conflict); + this.ignoreText.set(''); + this.showIgnoreModal.set(true); + } + protected submitIgnore(): void { + const conflict = this.pendingConflict(); + const reason = this.ignoreText(); + if (!conflict || !reason) return; + + this.showIgnoreModal.set(false); this.api.ignoreConflict(conflict.id, reason, this.governanceScope()).subscribe({ next: () => { this.loadConflicts(); diff --git a/src/Web/StellaOps.Web/src/app/features/policy-governance/policy-governance.component.ts b/src/Web/StellaOps.Web/src/app/features/policy-governance/policy-governance.component.ts index 2c1a39fd7..7a4550bb7 100644 --- a/src/Web/StellaOps.Web/src/app/features/policy-governance/policy-governance.component.ts +++ b/src/Web/StellaOps.Web/src/app/features/policy-governance/policy-governance.component.ts @@ -1,6 +1,8 @@ -import { Component, ChangeDetectionStrategy, signal, inject } from '@angular/core'; -import { Router, RouterOutlet } from '@angular/router'; +import { Component, ChangeDetectionStrategy, signal, inject, OnInit, DestroyRef } from '@angular/core'; +import { Router, RouterOutlet, NavigationEnd, ActivatedRoute } from '@angular/router'; +import { filter } from 'rxjs/operators'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { StellaPageTabsComponent, StellaPageTab } from '../../shared/components/stella-page-tabs/stella-page-tabs.component'; /** @@ -30,7 +32,6 @@ const GOVERNANCE_TABS: readonly StellaPageTab[] = [ template: `
-

Admin / Policy

Policy Governance

Configure risk budgets, trust weights, staleness rules, sealed mode, and risk profiles.

@@ -38,7 +39,6 @@ const GOVERNANCE_TABS: readonly StellaPageTab[] = [ @@ -64,15 +64,6 @@ const GOVERNANCE_TABS: readonly StellaPageTab[] = [ margin-bottom: 1.5rem; } - .governance__eyebrow { - margin: 0; - color: var(--color-status-info); - text-transform: uppercase; - letter-spacing: 0.05em; - font-size: 0.75rem; - font-weight: var(--font-weight-semibold); - } - .governance__title { margin: 0.25rem 0 0; font-size: 1.75rem; @@ -88,7 +79,7 @@ const GOVERNANCE_TABS: readonly StellaPageTab[] = [ `] }) -export class PolicyGovernanceComponent { +export class PolicyGovernanceComponent implements OnInit { private static readonly TAB_ROUTES: Record = { budget: '/ops/policy/governance', trust: '/ops/policy/governance/trust-weights', @@ -102,11 +93,45 @@ export class PolicyGovernanceComponent { 'schema-docs': '/ops/policy/governance/schema-docs', }; + private static readonly ROUTE_TO_TAB: Record = { + '': 'budget', + 'overview': 'budget', + 'risk-budget': 'budget', + 'budget': 'budget', + 'trust-weights': 'trust', + 'staleness': 'staleness', + 'sealed-mode': 'sealed', + 'profiles': 'profiles', + 'validator': 'validator', + 'audit': 'audit', + 'conflicts': 'conflicts', + 'schema-playground': 'schema-playground', + 'schema-docs': 'schema-docs', + }; + private readonly router = inject(Router); + private readonly route = inject(ActivatedRoute); + private readonly destroyRef = inject(DestroyRef); protected readonly activeTab = signal('budget'); protected readonly GOVERNANCE_TABS = GOVERNANCE_TABS; + ngOnInit(): void { + this.syncTabFromRoute(); + this.router.events + .pipe( + filter((e): e is NavigationEnd => e instanceof NavigationEnd), + takeUntilDestroyed(this.destroyRef), + ) + .subscribe(() => this.syncTabFromRoute()); + } + + private syncTabFromRoute(): void { + const childPath = this.route.firstChild?.snapshot.url[0]?.path ?? ''; + const tabId = PolicyGovernanceComponent.ROUTE_TO_TAB[childPath] ?? 'budget'; + this.activeTab.set(tabId); + } + protected onTabChange(tabId: string): void { this.activeTab.set(tabId); const route = PolicyGovernanceComponent.TAB_ROUTES[tabId]; diff --git a/src/Web/StellaOps.Web/src/app/features/policy-governance/policy-validator.component.ts b/src/Web/StellaOps.Web/src/app/features/policy-governance/policy-validator.component.ts index 2305671a5..bf431d8a6 100644 --- a/src/Web/StellaOps.Web/src/app/features/policy-governance/policy-validator.component.ts +++ b/src/Web/StellaOps.Web/src/app/features/policy-governance/policy-validator.component.ts @@ -180,7 +180,7 @@ import { margin: 0; font-size: 1.25rem; font-weight: var(--font-weight-semibold); - color: var(--color-surface-secondary); + color: var(--color-text-heading); } .validator__subtitle { @@ -207,8 +207,8 @@ import { .editor-section { display: flex; flex-direction: column; - background: var(--color-text-heading); - border: 1px solid var(--color-text-heading); + background: var(--color-surface-elevated); + border: 1px solid var(--color-border-primary); border-radius: var(--radius-lg); overflow: hidden; } @@ -218,14 +218,14 @@ import { justify-content: space-between; align-items: center; padding: 0.75rem 1rem; - border-bottom: 1px solid var(--color-text-heading); - background: var(--color-text-heading); + border-bottom: 1px solid var(--color-border-primary); + background: var(--color-surface-elevated); } .editor-section__header h3 { margin: 0; font-size: 0.9rem; - color: var(--color-surface-secondary); + color: var(--color-text-heading); } .editor-controls { @@ -236,9 +236,9 @@ import { .editor { flex: 1; padding: 1rem; - background: var(--color-text-heading); + background: var(--color-surface-elevated); border: none; - color: var(--color-border-primary); + color: var(--color-text-primary); font-family: 'JetBrains Mono', 'Fira Code', monospace; font-size: 0.85rem; line-height: 1.5; @@ -252,8 +252,8 @@ import { .editor-footer { padding: 0.5rem 1rem; - border-top: 1px solid var(--color-text-heading); - background: var(--color-text-heading); + border-top: 1px solid var(--color-border-primary); + background: var(--color-surface-elevated); color: var(--color-text-secondary); font-size: 0.75rem; } @@ -262,8 +262,8 @@ import { .results-section { display: flex; flex-direction: column; - background: var(--color-text-heading); - border: 1px solid var(--color-text-heading); + background: var(--color-surface-elevated); + border: 1px solid var(--color-border-primary); border-radius: var(--radius-lg); overflow: hidden; } @@ -272,9 +272,9 @@ import { margin: 0; padding: 0.75rem 1rem; font-size: 0.9rem; - color: var(--color-surface-secondary); - border-bottom: 1px solid var(--color-text-heading); - background: var(--color-text-heading); + color: var(--color-text-heading); + border-bottom: 1px solid var(--color-border-primary); + background: var(--color-surface-elevated); } /* Buttons */ @@ -292,15 +292,15 @@ import { .btn--primary:hover:not(:disabled) { background: var(--color-status-info); } .btn--primary:disabled { opacity: 0.5; cursor: not-allowed; } - .btn--secondary { background: var(--color-text-primary); color: var(--color-border-primary); border: 1px solid var(--color-text-primary); } - .btn--secondary:hover { background: var(--color-text-primary); } + .btn--secondary { background: var(--color-surface-tertiary); color: var(--color-text-primary); border: 1px solid var(--color-border-primary); } + .btn--secondary:hover { background: var(--color-surface-tertiary); } .form-select--small { padding: 0.35rem 0.5rem; - background: var(--color-text-heading); - border: 1px solid var(--color-text-primary); + background: var(--color-surface-elevated); + border: 1px solid var(--color-border-primary); border-radius: var(--radius-md); - color: var(--color-border-primary); + color: var(--color-text-primary); font-size: 0.85rem; } @@ -315,13 +315,13 @@ import { } .result-summary--valid { - background: rgba(34, 197, 94, 0.1); - border: 1px solid rgba(34, 197, 94, 0.3); + background: var(--color-status-success-bg); + border: 1px solid var(--color-status-success-border); } .result-summary--invalid { - background: rgba(239, 68, 68, 0.1); - border: 1px solid rgba(239, 68, 68, 0.3); + background: var(--color-status-error-bg); + border: 1px solid var(--color-status-error-border); } .result-summary__icon { @@ -338,7 +338,7 @@ import { .result-summary__status { font-size: 1.1rem; font-weight: var(--font-weight-semibold); - color: var(--color-surface-secondary); + color: var(--color-text-heading); } .result-summary__meta { @@ -388,7 +388,7 @@ import { .issue-item { padding: 0.75rem; - background: var(--color-text-heading); + background: var(--color-surface-elevated); border-radius: var(--radius-md); border-left: 3px solid; } @@ -418,7 +418,7 @@ import { } .issue-item__message { - color: var(--color-border-primary); + color: var(--color-text-primary); font-size: 0.9rem; } diff --git a/src/Web/StellaOps.Web/src/app/features/policy-governance/risk-budget-config.component.ts b/src/Web/StellaOps.Web/src/app/features/policy-governance/risk-budget-config.component.ts index f4ed9b49c..4d1d4518b 100644 --- a/src/Web/StellaOps.Web/src/app/features/policy-governance/risk-budget-config.component.ts +++ b/src/Web/StellaOps.Web/src/app/features/policy-governance/risk-budget-config.component.ts @@ -244,7 +244,7 @@ import { injectPolicyGovernanceScopeResolver } from './policy-governance-scope'; margin: 0; font-size: 1.25rem; font-weight: var(--font-weight-semibold); - color: var(--color-surface-secondary); + color: var(--color-text-heading); } .config__subtitle { @@ -261,9 +261,9 @@ import { injectPolicyGovernanceScopeResolver } from './policy-governance-scope'; .error-state { margin-bottom: 1rem; padding: 0.625rem 0.75rem; - border: 1px solid rgba(239, 68, 68, 0.45); + border: 1px solid var(--color-status-error-border); border-radius: var(--radius-lg); - background: rgba(239, 68, 68, 0.12); + background: var(--color-status-error-bg); color: var(--color-status-error-border); font-size: 0.875rem; font-weight: var(--font-weight-medium); @@ -300,12 +300,12 @@ import { injectPolicyGovernanceScopeResolver } from './policy-governance-scope'; .btn--ghost { background: transparent; color: var(--color-text-muted); - border: 1px solid var(--color-text-primary); + border: 1px solid var(--color-border-primary); } .btn--ghost:hover { - background: var(--color-text-primary); - color: var(--color-border-primary); + background: var(--color-surface-tertiary); + color: var(--color-text-primary); } .btn--icon { @@ -321,8 +321,8 @@ import { injectPolicyGovernanceScopeResolver } from './policy-governance-scope'; /* Form Sections */ .config__section { - background: var(--color-text-heading); - border: 1px solid var(--color-text-heading); + background: var(--color-surface-elevated); + border: 1px solid var(--color-border-primary); border-radius: var(--radius-lg); padding: 1.25rem; margin-bottom: 1rem; @@ -332,7 +332,7 @@ import { injectPolicyGovernanceScopeResolver } from './policy-governance-scope'; margin: 0 0 1rem; font-size: 1rem; font-weight: var(--font-weight-semibold); - color: var(--color-surface-secondary); + color: var(--color-text-heading); } .config__section-desc { @@ -364,24 +364,24 @@ import { injectPolicyGovernanceScopeResolver } from './policy-governance-scope'; } .form-label { - color: var(--color-border-primary); + color: var(--color-text-primary); font-size: 0.85rem; font-weight: var(--font-weight-medium); } .form-input, .form-select { padding: 0.5rem 0.75rem; - background: var(--color-text-heading); - border: 1px solid var(--color-text-primary); + background: var(--color-surface-elevated); + border: 1px solid var(--color-border-primary); border-radius: var(--radius-md); - color: var(--color-border-primary); + color: var(--color-text-primary); font-size: 0.9rem; } .form-input:focus, .form-select:focus { outline: none; border-color: var(--color-status-info); - box-shadow: 0 0 0 2px rgba(34, 211, 238, 0.2); + box-shadow: 0 0 0 2px var(--color-status-info-bg); } .form-select--small { @@ -408,7 +408,7 @@ import { injectPolicyGovernanceScopeResolver } from './policy-governance-scope'; } .form-checkbox__label { - color: var(--color-border-primary); + color: var(--color-text-primary); font-size: 0.9rem; } @@ -458,8 +458,8 @@ import { injectPolicyGovernanceScopeResolver } from './policy-governance-scope'; } .threshold-item { - background: var(--color-text-heading); - border: 1px solid var(--color-text-primary); + background: var(--color-surface-elevated); + border: 1px solid var(--color-border-primary); border-radius: var(--radius-lg); padding: 1rem; } @@ -494,8 +494,8 @@ import { injectPolicyGovernanceScopeResolver } from './policy-governance-scope'; align-items: center; gap: 0.35rem; padding: 0.35rem 0.6rem; - background: var(--color-text-primary); - border: 1px solid var(--color-text-primary); + background: var(--color-surface-tertiary); + border: 1px solid var(--color-border-primary); border-radius: var(--radius-full); cursor: pointer; font-size: 0.8rem; @@ -504,7 +504,7 @@ import { injectPolicyGovernanceScopeResolver } from './policy-governance-scope'; } .action-chip:has(input:checked) { - background: rgba(34, 211, 238, 0.2); + background: var(--color-status-info-bg); border-color: var(--color-status-info); color: var(--color-status-info); } diff --git a/src/Web/StellaOps.Web/src/app/features/policy-governance/risk-budget-dashboard.component.ts b/src/Web/StellaOps.Web/src/app/features/policy-governance/risk-budget-dashboard.component.ts index 6a67981bb..511431147 100644 --- a/src/Web/StellaOps.Web/src/app/features/policy-governance/risk-budget-dashboard.component.ts +++ b/src/Web/StellaOps.Web/src/app/features/policy-governance/risk-budget-dashboard.component.ts @@ -11,6 +11,9 @@ import { RiskBudgetContributor, RiskBudgetAlert, } from '../../core/api/policy-governance.models'; +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 { LoadingStateComponent } from '../../shared/components/loading-state/loading-state.component'; import { injectPolicyGovernanceScopeResolver } from './policy-governance-scope'; /** @@ -21,7 +24,7 @@ import { injectPolicyGovernanceScopeResolver } from './policy-governance-scope'; */ @Component({ selector: 'app-risk-budget-dashboard', - imports: [CommonModule, RouterModule], + imports: [CommonModule, RouterModule, StellaMetricCardComponent, StellaMetricGridComponent, LoadingStateComponent], changeDetection: ChangeDetectionStrategy.OnPush, template: `
@@ -41,37 +44,28 @@ import { injectPolicyGovernanceScopeResolver } from './policy-governance-scope'; @if (data(); as d) { -
-
-
Current Utilization
-
- {{ d.utilizationPercent | number:'1.0-0' }}% -
-
{{ d.currentRiskPoints }} / {{ d.config.totalBudget }} points
-
- -
-
Headroom
-
{{ d.headroom }}
-
- {{ d.kpis.headroomDelta24h >= 0 ? '+' : '' }}{{ d.kpis.headroomDelta24h }} (24h) -
-
- -
-
Burn Rate
-
{{ d.kpis.burnRate | number:'1.1-1' }}
-
points/day
-
- -
-
Days to Exceeded
-
- {{ d.kpis.projectedDaysToExceeded ?? '--' }} -
-
projected
-
-
+ + + + + +
@@ -130,7 +124,7 @@ import { injectPolicyGovernanceScopeResolver } from './policy-governance-scope';
  • {{ contrib.type }} - {{ contrib.displayName }} + {{ contrib.displayName }} {{ getTrendIcon(contrib.trend) }} @@ -191,7 +185,7 @@ import { injectPolicyGovernanceScopeResolver } from './policy-governance-scope'; Last Updated: {{ d.updatedAt | date:'medium' }}
    } @else if (loading()) { -
    Loading budget data...
    + }
  • `, @@ -213,7 +207,7 @@ import { injectPolicyGovernanceScopeResolver } from './policy-governance-scope'; margin: 0; font-size: 1.25rem; font-weight: var(--font-weight-semibold); - color: var(--color-surface-secondary); + color: var(--color-text-heading); } .dashboard__subtitle { @@ -225,9 +219,9 @@ import { injectPolicyGovernanceScopeResolver } from './policy-governance-scope'; .error-state { margin-bottom: 1rem; padding: 0.625rem 0.75rem; - border: 1px solid rgba(239, 68, 68, 0.45); + border: 1px solid var(--color-status-error-border); border-radius: var(--radius-lg); - background: rgba(239, 68, 68, 0.12); + background: var(--color-status-error-bg); color: var(--color-status-error-border); font-size: 0.875rem; font-weight: var(--font-weight-medium); @@ -247,13 +241,13 @@ import { injectPolicyGovernanceScopeResolver } from './policy-governance-scope'; } .btn--secondary { - background: var(--color-text-primary); - color: var(--color-border-primary); - border: 1px solid var(--color-text-primary); + background: var(--color-surface-tertiary); + color: var(--color-text-primary); + border: 1px solid var(--color-border-primary); } .btn--secondary:hover { - background: var(--color-text-primary); + background: var(--color-surface-tertiary); } .btn--small { @@ -268,49 +262,6 @@ import { injectPolicyGovernanceScopeResolver } from './policy-governance-scope'; background: var(--color-status-info); } - /* KPI Grid */ - .kpi-grid { - display: grid; - grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); - gap: 1rem; - margin-bottom: 1.5rem; - } - - .kpi-card { - background: var(--color-text-heading); - border: 1px solid var(--color-text-heading); - border-radius: var(--radius-lg); - padding: 1rem; - } - - .kpi-card__label { - color: var(--color-text-muted); - font-size: 0.8rem; - text-transform: uppercase; - letter-spacing: 0.03em; - } - - .kpi-card__value { - font-size: 2rem; - font-weight: var(--font-weight-bold); - color: var(--color-status-info); - margin: 0.25rem 0; - } - - .kpi-card__value--healthy { color: var(--color-status-success); } - .kpi-card__value--warning { color: var(--color-status-warning); } - .kpi-card__value--critical { color: var(--color-status-error); } - .kpi-card__value--exceeded { color: var(--color-status-error); } - - .kpi-card__meta { - color: var(--color-text-secondary); - font-size: 0.85rem; - } - - .kpi-card__meta--negative { - color: var(--color-status-error); - } - /* Progress Bar */ .progress-section { margin-bottom: 1.5rem; @@ -323,7 +274,7 @@ import { injectPolicyGovernanceScopeResolver } from './policy-governance-scope'; .progress-bar__track { position: relative; height: 24px; - background: var(--color-text-heading); + background: var(--color-surface-elevated); border-radius: var(--radius-xl); overflow: hidden; } @@ -403,8 +354,8 @@ import { injectPolicyGovernanceScopeResolver } from './policy-governance-scope'; } .chart-card { - background: var(--color-text-heading); - border: 1px solid var(--color-text-heading); + background: var(--color-surface-elevated); + border: 1px solid var(--color-border-primary); border-radius: var(--radius-lg); padding: 1rem; } @@ -413,7 +364,7 @@ import { injectPolicyGovernanceScopeResolver } from './policy-governance-scope'; margin: 0 0 1rem; font-size: 1rem; font-weight: var(--font-weight-semibold); - color: var(--color-surface-secondary); + color: var(--color-text-heading); } .simple-chart { @@ -467,7 +418,7 @@ import { injectPolicyGovernanceScopeResolver } from './policy-governance-scope'; .contributor-item { padding: 0.5rem 0; - border-bottom: 1px solid var(--color-text-heading); + border-bottom: 1px solid var(--color-border-primary); } .contributor-item:last-child { @@ -484,7 +435,7 @@ import { injectPolicyGovernanceScopeResolver } from './policy-governance-scope'; .contributor-item__type { font-size: 0.7rem; padding: 0.15rem 0.4rem; - background: var(--color-text-primary); + background: var(--color-surface-tertiary); border-radius: var(--radius-sm); color: var(--color-text-muted); text-transform: uppercase; @@ -492,7 +443,7 @@ import { injectPolicyGovernanceScopeResolver } from './policy-governance-scope'; .contributor-item__name { flex: 1; - color: var(--color-border-primary); + color: var(--color-text-primary); font-weight: var(--font-weight-medium); font-size: 0.9rem; overflow: hidden; @@ -510,7 +461,7 @@ import { injectPolicyGovernanceScopeResolver } from './policy-governance-scope'; .contributor-item__bar { height: 6px; - background: var(--color-text-heading); + background: var(--color-surface-elevated); border-radius: var(--radius-sm); margin-bottom: 0.25rem; } @@ -541,7 +492,7 @@ import { injectPolicyGovernanceScopeResolver } from './policy-governance-scope'; margin: 0 0 0.75rem; font-size: 1rem; font-weight: var(--font-weight-semibold); - color: var(--color-surface-secondary); + color: var(--color-text-heading); } .alert-list { @@ -558,19 +509,19 @@ import { injectPolicyGovernanceScopeResolver } from './policy-governance-scope'; align-items: center; gap: 0.75rem; padding: 0.75rem 1rem; - background: var(--color-text-heading); + background: var(--color-surface-elevated); border-radius: var(--radius-lg); border-left: 3px solid var(--color-status-warning); } .alert-item--high, .alert-item--critical { border-left-color: var(--color-status-error); - background: rgba(239, 68, 68, 0.1); + background: var(--color-status-error-bg); } .alert-item--medium { border-left-color: var(--color-status-warning); - background: rgba(234, 179, 8, 0.1); + background: var(--color-status-warning-bg); } .alert-item__icon { @@ -591,7 +542,7 @@ import { injectPolicyGovernanceScopeResolver } from './policy-governance-scope'; } .alert-item__title { - color: var(--color-border-primary); + color: var(--color-text-primary); font-weight: var(--font-weight-medium); } @@ -613,16 +564,9 @@ import { injectPolicyGovernanceScopeResolver } from './policy-governance-scope'; color: var(--color-text-secondary); font-size: 0.8rem; padding-top: 1rem; - border-top: 1px solid var(--color-text-heading); + border-top: 1px solid var(--color-border-primary); } - .loading-state { - display: flex; - align-items: center; - justify-content: center; - height: 200px; - color: var(--color-text-muted); - } `] }) export class RiskBudgetDashboardComponent implements OnInit { @@ -738,6 +682,14 @@ export class RiskBudgetDashboardComponent implements OnInit { return numericValue > 0 ? numericValue : fallback; } + protected formatUtilization(percent: number): string { + return `${Math.round(percent)}%`; + } + + protected formatBurnRate(rate: number): string { + return rate.toFixed(1); + } + protected formatDate(timestamp: string): string { const date = new Date(timestamp); return `${date.getMonth() + 1}/${date.getDate()}`; diff --git a/src/Web/StellaOps.Web/src/app/features/policy-governance/risk-profile-editor.component.ts b/src/Web/StellaOps.Web/src/app/features/policy-governance/risk-profile-editor.component.ts index 3f677d940..55002cf19 100644 --- a/src/Web/StellaOps.Web/src/app/features/policy-governance/risk-profile-editor.component.ts +++ b/src/Web/StellaOps.Web/src/app/features/policy-governance/risk-profile-editor.component.ts @@ -265,7 +265,7 @@ import { injectPolicyGovernanceScopeResolver } from './policy-governance-scope'; margin: 0; font-size: 1.25rem; font-weight: var(--font-weight-semibold); - color: var(--color-surface-secondary); + color: var(--color-text-heading); } .editor__subtitle { @@ -297,11 +297,11 @@ import { injectPolicyGovernanceScopeResolver } from './policy-governance-scope'; .btn--primary:hover:not(:disabled) { background: var(--color-status-info); } .btn--primary:disabled { opacity: 0.5; cursor: not-allowed; } - .btn--secondary { background: var(--color-text-primary); color: var(--color-border-primary); border: 1px solid var(--color-text-primary); } - .btn--secondary:hover:not(:disabled) { background: var(--color-text-primary); } + .btn--secondary { background: var(--color-surface-tertiary); color: var(--color-text-primary); border: 1px solid var(--color-border-primary); } + .btn--secondary:hover:not(:disabled) { background: var(--color-surface-tertiary); } .btn--ghost { background: transparent; color: var(--color-text-muted); } - .btn--ghost:hover { background: var(--color-text-primary); color: var(--color-border-primary); } + .btn--ghost:hover { background: var(--color-surface-tertiary); color: var(--color-text-primary); } .btn--icon { padding: 0.35rem; } .btn--danger:hover { color: var(--color-status-error); } @@ -310,7 +310,7 @@ import { injectPolicyGovernanceScopeResolver } from './policy-governance-scope'; width: 100%; justify-content: center; padding: 0.75rem; - border: 1px dashed var(--color-text-primary); + border: 1px dashed var(--color-border-primary); margin-top: 0.75rem; } @@ -326,14 +326,14 @@ import { injectPolicyGovernanceScopeResolver } from './policy-governance-scope'; } .validation-banner--success { - background: rgba(34, 197, 94, 0.1); - border: 1px solid rgba(34, 197, 94, 0.3); + background: var(--color-status-success-bg); + border: 1px solid var(--color-status-success-border); color: var(--color-status-success-border); } .validation-banner--error { - background: rgba(239, 68, 68, 0.1); - border: 1px solid rgba(239, 68, 68, 0.3); + background: var(--color-status-error-bg); + border: 1px solid var(--color-status-error-border); color: var(--color-status-error-border); } @@ -354,8 +354,8 @@ import { injectPolicyGovernanceScopeResolver } from './policy-governance-scope'; /* Form Sections */ .form-section { - background: var(--color-text-heading); - border: 1px solid var(--color-text-heading); + background: var(--color-surface-elevated); + border: 1px solid var(--color-border-primary); border-radius: var(--radius-lg); padding: 1.25rem; margin-bottom: 1rem; @@ -372,12 +372,12 @@ import { injectPolicyGovernanceScopeResolver } from './policy-governance-scope'; margin: 0; font-size: 1rem; font-weight: var(--font-weight-semibold); - color: var(--color-surface-secondary); + color: var(--color-text-heading); } .form-section__badge { padding: 0.15rem 0.5rem; - background: var(--color-text-primary); + background: var(--color-surface-tertiary); border-radius: var(--radius-full); font-size: 0.8rem; color: var(--color-text-muted); @@ -400,17 +400,17 @@ import { injectPolicyGovernanceScopeResolver } from './policy-governance-scope'; } .form-label { - color: var(--color-border-primary); + color: var(--color-text-primary); font-size: 0.85rem; font-weight: var(--font-weight-medium); } .form-input, .form-select, .form-textarea { padding: 0.5rem 0.75rem; - background: var(--color-text-heading); - border: 1px solid var(--color-text-primary); + background: var(--color-surface-elevated); + border: 1px solid var(--color-border-primary); border-radius: var(--radius-md); - color: var(--color-border-primary); + color: var(--color-text-primary); font-size: 0.9rem; } @@ -436,8 +436,8 @@ import { injectPolicyGovernanceScopeResolver } from './policy-governance-scope'; border-radius: var(--radius-md); } - .weight-sum--valid { background: rgba(34, 197, 94, 0.2); color: var(--color-status-success-border); } - .weight-sum--invalid { background: rgba(234, 179, 8, 0.2); color: var(--color-status-warning-border); } + .weight-sum--valid { background: var(--color-status-success-bg); color: var(--color-status-success-border); } + .weight-sum--invalid { background: var(--color-status-warning-bg); color: var(--color-status-warning-border); } .signal-editor { display: flex; @@ -451,7 +451,7 @@ import { injectPolicyGovernanceScopeResolver } from './policy-governance-scope'; gap: 0.75rem; align-items: center; padding: 0.5rem; - background: var(--color-text-heading); + background: var(--color-surface-elevated); border-radius: var(--radius-md); } @@ -490,7 +490,7 @@ import { injectPolicyGovernanceScopeResolver } from './policy-governance-scope'; position: absolute; cursor: pointer; inset: 0; - background: var(--color-text-primary); + background: var(--color-surface-tertiary); border-radius: var(--radius-2xl); transition: 0.2s; } @@ -502,7 +502,7 @@ import { injectPolicyGovernanceScopeResolver } from './policy-governance-scope'; width: 14px; left: 3px; bottom: 3px; - background: var(--color-border-primary); + background: var(--color-surface-elevated); border-radius: var(--radius-full); transition: 0.2s; } @@ -522,8 +522,8 @@ import { injectPolicyGovernanceScopeResolver } from './policy-governance-scope'; } .override-card { - background: var(--color-text-heading); - border: 1px solid var(--color-text-primary); + background: var(--color-surface-elevated); + border: 1px solid var(--color-border-primary); border-radius: var(--radius-lg); overflow: hidden; } @@ -533,7 +533,7 @@ import { injectPolicyGovernanceScopeResolver } from './policy-governance-scope'; justify-content: space-between; align-items: center; padding: 0.5rem 0.75rem; - background: var(--color-text-primary); + background: var(--color-surface-tertiary); } .override-card__label { diff --git a/src/Web/StellaOps.Web/src/app/features/policy-governance/risk-profile-list.component.ts b/src/Web/StellaOps.Web/src/app/features/policy-governance/risk-profile-list.component.ts index 3c0a2b91d..ac414313c 100644 --- a/src/Web/StellaOps.Web/src/app/features/policy-governance/risk-profile-list.component.ts +++ b/src/Web/StellaOps.Web/src/app/features/policy-governance/risk-profile-list.component.ts @@ -1,5 +1,6 @@ import { CommonModule } from '@angular/common'; -import { Component, ChangeDetectionStrategy, inject, signal, OnInit } from '@angular/core'; +import { Component, ChangeDetectionStrategy, inject, signal, OnInit, ViewChild } from '@angular/core'; +import { FormsModule } from '@angular/forms'; import { RouterModule } from '@angular/router'; import { finalize } from 'rxjs/operators'; @@ -11,6 +12,10 @@ import { RiskProfileGovernanceStatus, } from '../../core/api/policy-governance.models'; import { injectPolicyGovernanceScopeResolver } from './policy-governance-scope'; +import { ConfirmDialogComponent } from '../../shared/components/confirm-dialog/confirm-dialog.component'; +import { ModalComponent } from '../../shared/components/modal/modal.component'; +import { LoadingStateComponent } from '../../shared/components/loading-state/loading-state.component'; +import { StellaFilterChipComponent } from '../../shared/components/stella-filter-chip/stella-filter-chip.component'; /** * Risk Profile List component. @@ -20,7 +25,7 @@ import { injectPolicyGovernanceScopeResolver } from './policy-governance-scope'; */ @Component({ selector: 'app-risk-profile-list', - imports: [CommonModule, RouterModule], + imports: [CommonModule, RouterModule, FormsModule, ConfirmDialogComponent, ModalComponent, LoadingStateComponent, StellaFilterChipComponent], changeDetection: ChangeDetectionStrategy.OnPush, template: `
    @@ -30,28 +35,12 @@ import { injectPolicyGovernanceScopeResolver } from './policy-governance-scope';

    Manage risk evaluation profiles and signal weights.

    -
    - - - - -
    + @@ -117,7 +106,7 @@ import { injectPolicyGovernanceScopeResolver } from './policy-governance-scope'; Edit @if (profile.status === 'draft') { - } @if (profile.status === 'active') { - } @if (profile.status !== 'active') { -
    } @else if (loading()) { -
    Loading profiles...
    + } @else {
    @@ -157,6 +146,30 @@ import { injectPolicyGovernanceScopeResolver } from './policy-governance-scope'; Create Profile
    } + + + + + @if (showDeprecateModal()) { + +
    + + +
    +
    + + +
    +
    + }
    `, styles: [` @@ -179,7 +192,7 @@ import { injectPolicyGovernanceScopeResolver } from './policy-governance-scope'; margin: 0; font-size: 1.25rem; font-weight: var(--font-weight-semibold); - color: var(--color-surface-secondary); + color: var(--color-text-heading); } .profiles__subtitle { @@ -194,30 +207,6 @@ import { injectPolicyGovernanceScopeResolver } from './policy-governance-scope'; gap: 1rem; } - .filter-group { - display: flex; - background: var(--color-text-heading); - border-radius: var(--radius-md); - overflow: hidden; - } - - .filter-btn { - padding: 0.5rem 0.75rem; - background: none; - border: none; - color: var(--color-text-muted); - font-size: 0.85rem; - cursor: pointer; - transition: all 0.15s ease; - } - - .filter-btn:hover { background: var(--color-text-primary); color: var(--color-border-primary); } - - .filter-btn--active { - background: var(--color-status-info); - color: var(--color-text-heading); - } - .btn { padding: 0.5rem 1rem; border-radius: var(--radius-md); @@ -236,7 +225,7 @@ import { injectPolicyGovernanceScopeResolver } from './policy-governance-scope'; .btn--primary:hover { background: var(--color-status-info); } .btn--ghost { background: transparent; color: var(--color-text-muted); } - .btn--ghost:hover { background: var(--color-text-primary); color: var(--color-border-primary); } + .btn--ghost:hover { background: var(--color-surface-tertiary); color: var(--color-text-primary); } .btn--small { padding: 0.35rem 0.6rem; font-size: 0.8rem; } @@ -250,8 +239,8 @@ import { injectPolicyGovernanceScopeResolver } from './policy-governance-scope'; } .profile-card { - background: var(--color-text-heading); - border: 1px solid var(--color-text-heading); + background: var(--color-surface-elevated); + border: 1px solid var(--color-border-primary); border-radius: var(--radius-xl); padding: 1.25rem; display: flex; @@ -262,7 +251,7 @@ import { injectPolicyGovernanceScopeResolver } from './policy-governance-scope'; .profile-card--active { border-left: 3px solid var(--color-status-success); } .profile-card--draft { border-left: 3px solid var(--color-status-warning); } .profile-card--deprecated { border-left: 3px solid var(--color-text-secondary); opacity: 0.8; } - .profile-card--archived { border-left: 3px solid var(--color-text-primary); opacity: 0.6; } + .profile-card--archived { border-left: 3px solid var(--color-border-primary); opacity: 0.6; } .profile-card__header { display: flex; @@ -281,8 +270,8 @@ import { injectPolicyGovernanceScopeResolver } from './policy-governance-scope'; .status--active { background: var(--color-status-success-text); color: #fff; } .status--draft { background: var(--color-status-warning-text); color: #fff; } - .status--deprecated { background: var(--color-text-primary); color: var(--color-text-muted); } - .status--archived { background: var(--color-text-heading); color: var(--color-text-secondary); } + .status--deprecated { background: var(--color-surface-tertiary); color: var(--color-text-muted); } + .status--archived { background: var(--color-surface-elevated); color: var(--color-text-secondary); } .profile-card__version { font-size: 0.8rem; @@ -298,7 +287,7 @@ import { injectPolicyGovernanceScopeResolver } from './policy-governance-scope'; margin: 0; font-size: 1.1rem; font-weight: var(--font-weight-semibold); - color: var(--color-surface-secondary); + color: var(--color-text-heading); } .profile-card__desc { @@ -318,7 +307,7 @@ import { injectPolicyGovernanceScopeResolver } from './policy-governance-scope'; } .profile-card__signals { - background: var(--color-text-heading); + background: var(--color-surface-elevated); border-radius: var(--radius-md); padding: 0.75rem; } @@ -343,7 +332,7 @@ import { injectPolicyGovernanceScopeResolver } from './policy-governance-scope'; font-size: 0.85rem; } - .signal-item__name { color: var(--color-border-primary); } + .signal-item__name { color: var(--color-text-primary); } .signal-item__weight { color: var(--color-status-info); font-weight: var(--font-weight-medium); } .signal-item--more { @@ -363,28 +352,48 @@ import { injectPolicyGovernanceScopeResolver } from './policy-governance-scope'; flex-wrap: wrap; gap: 0.5rem; padding-top: 0.75rem; - border-top: 1px solid var(--color-text-heading); + border-top: 1px solid var(--color-border-primary); } /* Empty State */ .empty-state { text-align: center; padding: 3rem; - background: var(--color-text-heading); - border: 1px solid var(--color-text-heading); + background: var(--color-surface-elevated); + border: 1px solid var(--color-border-primary); border-radius: var(--radius-xl); } .empty-state svg { color: var(--color-text-secondary); margin-bottom: 1rem; } - .empty-state h3 { margin: 0 0 0.5rem; color: var(--color-surface-secondary); } + .empty-state h3 { margin: 0 0 0.5rem; color: var(--color-text-heading); } .empty-state p { margin: 0 0 1.5rem; color: var(--color-text-muted); } - .loading-state { - display: flex; - align-items: center; - justify-content: center; - height: 200px; - color: var(--color-text-muted); + /* Form fields for deprecate modal */ + .form-field { + margin-bottom: 1rem; + } + + .form-label { + display: block; + color: var(--color-text-primary); + font-size: 0.85rem; + font-weight: var(--font-weight-medium); + margin-bottom: 0.35rem; + } + + .form-textarea { + width: 100%; + padding: 0.5rem 0.75rem; + background: var(--color-surface-elevated); + border: 1px solid var(--color-border-primary); + border-radius: var(--radius-md); + color: var(--color-text-primary); + font-size: 0.9rem; + } + + .form-textarea:focus { + outline: none; + border-color: var(--color-status-info); } `] }) @@ -396,6 +405,22 @@ export class RiskProfileListComponent implements OnInit { protected readonly profiles = signal([]); protected readonly statusFilter = signal(null); + readonly statusOptions = [ + { id: '', label: 'All' }, + { id: 'active', label: 'Active' }, + { id: 'draft', label: 'Draft' }, + { id: 'deprecated', label: 'Deprecated' }, + ]; + + protected readonly pendingActivateProfile = signal(null); + protected readonly pendingDeleteProfile = signal(null); + protected readonly pendingDeprecateProfile = signal(null); + protected readonly showDeprecateModal = signal(false); + protected readonly deprecateReasonText = signal(''); + + @ViewChild('activateConfirm') private activateConfirmRef!: ConfirmDialogComponent; + @ViewChild('deleteProfileConfirm') private deleteProfileConfirmRef!: ConfirmDialogComponent; + ngOnInit(): void { this.loadProfiles(); } @@ -417,13 +442,19 @@ export class RiskProfileListComponent implements OnInit { }); } - protected setStatusFilter(status: RiskProfileGovernanceStatus | null): void { - this.statusFilter.set(status); + protected setStatusFilter(status: RiskProfileGovernanceStatus | string | null): void { + this.statusFilter.set((status || null) as RiskProfileGovernanceStatus | null); this.loadProfiles(); } - protected activateProfile(profile: RiskProfileGov): void { - if (!confirm(`Activate profile "${profile.name}"?`)) return; + protected requestActivateProfile(profile: RiskProfileGov): void { + this.pendingActivateProfile.set(profile); + this.activateConfirmRef.open(); + } + + protected executeActivateProfile(): void { + const profile = this.pendingActivateProfile(); + if (!profile) return; this.api.activateRiskProfile(profile.id, this.governanceScope()).subscribe({ next: () => this.loadProfiles(), @@ -431,18 +462,32 @@ export class RiskProfileListComponent implements OnInit { }); } - protected deprecateProfile(profile: RiskProfileGov): void { - const reason = prompt(`Reason for deprecating "${profile.name}":`); - if (!reason) return; + protected requestDeprecateProfile(profile: RiskProfileGov): void { + this.pendingDeprecateProfile.set(profile); + this.deprecateReasonText.set(''); + this.showDeprecateModal.set(true); + } + protected executeDeprecateProfile(): void { + const profile = this.pendingDeprecateProfile(); + const reason = this.deprecateReasonText(); + if (!profile || !reason) return; + + this.showDeprecateModal.set(false); this.api.deprecateRiskProfile(profile.id, reason, this.governanceScope()).subscribe({ next: () => this.loadProfiles(), error: (err) => console.error('Failed to deprecate profile:', err), }); } - protected deleteProfile(profile: RiskProfileGov): void { - if (!confirm(`Delete profile "${profile.name}"? This cannot be undone.`)) return; + protected requestDeleteProfile(profile: RiskProfileGov): void { + this.pendingDeleteProfile.set(profile); + this.deleteProfileConfirmRef.open(); + } + + protected executeDeleteProfile(): void { + const profile = this.pendingDeleteProfile(); + if (!profile) return; this.api.deleteRiskProfile(profile.id, this.governanceScope()).subscribe({ next: () => this.loadProfiles(), diff --git a/src/Web/StellaOps.Web/src/app/features/policy-governance/schema-docs.component.ts b/src/Web/StellaOps.Web/src/app/features/policy-governance/schema-docs.component.ts index e933ceaae..346b0ed8d 100644 --- a/src/Web/StellaOps.Web/src/app/features/policy-governance/schema-docs.component.ts +++ b/src/Web/StellaOps.Web/src/app/features/policy-governance/schema-docs.component.ts @@ -313,7 +313,7 @@ interface SchemaSection { margin: 0; font-size: 1.25rem; font-weight: var(--font-weight-semibold); - color: var(--color-surface-secondary); + color: var(--color-text-heading); } .docs__subtitle { @@ -331,11 +331,11 @@ interface SchemaSection { .search-input { padding: 0.5rem 0.75rem; padding-left: 2.25rem; - background: var(--color-text-heading) url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 24 24' stroke-width='1.5' stroke='%236B5A2E'%3E%3Cpath stroke-linecap='round' stroke-linejoin='round' d='m21 21-5.197-5.197m0 0A7.5 7.5 0 1 0 5.196 5.196a7.5 7.5 0 0 0 10.607 10.607Z'/%3E%3C/svg%3E") no-repeat 0.75rem center; + background: var(--color-surface-elevated) url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 24 24' stroke-width='1.5' stroke='%236B5A2E'%3E%3Cpath stroke-linecap='round' stroke-linejoin='round' d='m21 21-5.197-5.197m0 0A7.5 7.5 0 1 0 5.196 5.196a7.5 7.5 0 0 0 10.607 10.607Z'/%3E%3C/svg%3E") no-repeat 0.75rem center; background-size: 16px; - border: 1px solid var(--color-text-primary); + border: 1px solid var(--color-border-primary); border-radius: var(--radius-md); - color: var(--color-border-primary); + color: var(--color-text-primary); font-size: 0.85rem; width: 240px; } @@ -362,8 +362,8 @@ interface SchemaSection { .btn--primary { background: var(--color-status-info); color: var(--color-btn-primary-text); } .btn--primary:hover { background: var(--color-status-info); } - .btn--secondary { background: var(--color-text-primary); color: var(--color-border-primary); border: 1px solid var(--color-text-primary); } - .btn--secondary:hover { background: var(--color-text-primary); } + .btn--secondary { background: var(--color-surface-tertiary); color: var(--color-text-primary); border: 1px solid var(--color-border-primary); } + .btn--secondary:hover { background: var(--color-surface-tertiary); } /* Content Layout */ .docs__content { @@ -416,7 +416,7 @@ interface SchemaSection { } .nav-link:hover { - color: var(--color-border-primary); + color: var(--color-text-primary); } .nav-link--active { @@ -426,10 +426,10 @@ interface SchemaSection { .version-select { width: 100%; padding: 0.35rem 0.5rem; - background: var(--color-text-heading); - border: 1px solid var(--color-text-primary); + background: var(--color-surface-elevated); + border: 1px solid var(--color-border-primary); border-radius: var(--radius-sm); - color: var(--color-border-primary); + color: var(--color-text-primary); font-size: 0.8rem; } @@ -439,8 +439,8 @@ interface SchemaSection { } .doc-section { - background: var(--color-text-heading); - border: 1px solid var(--color-text-heading); + background: var(--color-surface-elevated); + border: 1px solid var(--color-border-primary); border-radius: var(--radius-lg); padding: 1.5rem; margin-bottom: 1.5rem; @@ -449,7 +449,7 @@ interface SchemaSection { .section-title { margin: 0 0 0.35rem; font-size: 1.1rem; - color: var(--color-surface-secondary); + color: var(--color-text-heading); } .section-desc { @@ -473,7 +473,7 @@ interface SchemaSection { .fields-table td { padding: 0.75rem 1rem; text-align: left; - border-bottom: 1px solid var(--color-text-heading); + border-bottom: 1px solid var(--color-border-primary); } .fields-table th { @@ -482,7 +482,7 @@ interface SchemaSection { text-transform: uppercase; letter-spacing: 0.05em; color: var(--color-text-secondary); - background: var(--color-text-heading); + background: var(--color-surface-elevated); } .field-row { @@ -491,7 +491,7 @@ interface SchemaSection { } .field-row:hover { - background: var(--color-text-heading); + background: var(--color-surface-elevated); } .field-name { @@ -507,7 +507,7 @@ interface SchemaSection { .field-desc { font-size: 0.85rem; - color: var(--color-border-primary); + color: var(--color-text-primary); } .badge { @@ -519,7 +519,7 @@ interface SchemaSection { } .badge--required { background: var(--color-status-error-text); color: #fff; } - .badge--optional { background: var(--color-text-primary); color: var(--color-text-muted); } + .badge--optional { background: var(--color-surface-tertiary); color: var(--color-text-muted); } /* Field Details (expanded) */ .field-details td { @@ -549,8 +549,8 @@ interface SchemaSection { .detail-value { font-family: monospace; font-size: 0.85rem; - color: var(--color-border-primary); - background: var(--color-text-primary); + color: var(--color-text-primary); + background: var(--color-surface-tertiary); padding: 0.15rem 0.4rem; border-radius: var(--radius-sm); } @@ -571,7 +571,7 @@ interface SchemaSection { } .detail-example { - background: var(--color-text-heading); + background: var(--color-surface-elevated); border-radius: var(--radius-sm); padding: 0.75rem; overflow-x: auto; @@ -583,7 +583,7 @@ interface SchemaSection { } .children-list { - background: var(--color-text-heading); + background: var(--color-surface-elevated); border-radius: var(--radius-sm); padding: 0.75rem; } @@ -591,7 +591,7 @@ interface SchemaSection { .child-item { padding: 0.35rem 0; font-size: 0.85rem; - border-bottom: 1px solid var(--color-text-heading); + border-bottom: 1px solid var(--color-border-primary); } .child-item:last-child { @@ -618,7 +618,7 @@ interface SchemaSection { .examples-panel h4 { margin: 0 0 0.75rem; font-size: 0.9rem; - color: var(--color-surface-secondary); + color: var(--color-text-heading); } .examples-tabs { @@ -629,7 +629,7 @@ interface SchemaSection { .example-tab { padding: 0.35rem 0.75rem; - background: var(--color-text-heading); + background: var(--color-surface-elevated); border: none; border-radius: var(--radius-sm); color: var(--color-text-muted); @@ -639,8 +639,8 @@ interface SchemaSection { } .example-tab:hover { - background: var(--color-text-primary); - color: var(--color-border-primary); + background: var(--color-surface-tertiary); + color: var(--color-text-primary); } .example-tab--active { @@ -673,7 +673,7 @@ interface SchemaSection { top: 0.5rem; right: 0.5rem; padding: 0.35rem 0.5rem; - background: var(--color-text-primary); + background: var(--color-surface-tertiary); border: none; border-radius: var(--radius-sm); color: var(--color-text-muted); @@ -686,8 +686,8 @@ interface SchemaSection { } .copy-btn:hover { - background: var(--color-text-primary); - color: var(--color-border-primary); + background: var(--color-surface-tertiary); + color: var(--color-text-primary); } /* Validation Rules */ @@ -698,10 +698,10 @@ interface SchemaSection { } .rule-card { - background: var(--color-text-heading); + background: var(--color-surface-elevated); border-radius: var(--radius-md); padding: 1rem; - border-left: 3px solid var(--color-text-primary); + border-left: 3px solid var(--color-border-primary); } .rule-header { @@ -732,14 +732,14 @@ interface SchemaSection { .rule-desc { margin: 0 0 0.5rem; font-size: 0.9rem; - color: var(--color-border-primary); + color: var(--color-text-primary); } .rule-fix { font-size: 0.85rem; color: var(--color-text-muted); padding-top: 0.5rem; - border-top: 1px solid var(--color-text-heading); + border-top: 1px solid var(--color-border-primary); } /* Best Practices */ @@ -752,7 +752,7 @@ interface SchemaSection { .practice-card { display: flex; gap: 1rem; - background: var(--color-text-heading); + background: var(--color-surface-elevated); border-radius: var(--radius-lg); padding: 1rem; } @@ -763,15 +763,15 @@ interface SchemaSection { display: flex; align-items: center; justify-content: center; - background: var(--color-text-primary); + background: var(--color-surface-tertiary); border-radius: var(--radius-lg); flex-shrink: 0; color: var(--color-text-muted); } - .icon--security { color: var(--color-status-success); background: rgba(34, 197, 94, 0.1); } - .icon--performance { color: var(--color-status-warning); background: rgba(234, 179, 8, 0.1); } - .icon--maintainability { color: var(--color-status-info); background: rgba(34, 211, 238, 0.1); } + .icon--security { color: var(--color-status-success); background: var(--color-status-success-bg); } + .icon--performance { color: var(--color-status-warning); background: var(--color-status-warning-bg); } + .icon--maintainability { color: var(--color-status-info); background: var(--color-status-info-bg); } .practice-content { flex: 1; @@ -781,7 +781,7 @@ interface SchemaSection { .practice-title { margin: 0 0 0.35rem; font-size: 0.95rem; - color: var(--color-surface-secondary); + color: var(--color-text-heading); } .practice-desc { @@ -803,7 +803,7 @@ interface SchemaSection { .practice-example pre { margin: 0.5rem 0 0; padding: 0.75rem; - background: var(--color-text-heading); + background: var(--color-surface-elevated); border-radius: var(--radius-sm); overflow-x: auto; } @@ -821,8 +821,8 @@ interface SchemaSection { justify-content: center; padding: 4rem 2rem; text-align: center; - background: var(--color-text-heading); - border: 1px solid var(--color-text-heading); + background: var(--color-surface-elevated); + border: 1px solid var(--color-border-primary); border-radius: var(--radius-lg); } @@ -833,7 +833,7 @@ interface SchemaSection { .no-results h3 { margin: 0 0 0.5rem; - color: var(--color-surface-secondary); + color: var(--color-text-heading); } .no-results p { diff --git a/src/Web/StellaOps.Web/src/app/features/policy-governance/schema-playground.component.ts b/src/Web/StellaOps.Web/src/app/features/policy-governance/schema-playground.component.ts index 65ae6e4cf..355a8fd91 100644 --- a/src/Web/StellaOps.Web/src/app/features/policy-governance/schema-playground.component.ts +++ b/src/Web/StellaOps.Web/src/app/features/policy-governance/schema-playground.component.ts @@ -278,7 +278,7 @@ import { margin: 0; font-size: 1.25rem; font-weight: var(--font-weight-semibold); - color: var(--color-surface-secondary); + color: var(--color-text-heading); } .playground__subtitle { @@ -295,10 +295,10 @@ import { .form-select { padding: 0.5rem 0.75rem; - background: var(--color-text-heading); - border: 1px solid var(--color-text-primary); + background: var(--color-surface-elevated); + border: 1px solid var(--color-border-primary); border-radius: var(--radius-md); - color: var(--color-border-primary); + color: var(--color-text-primary); font-size: 0.85rem; min-width: 180px; } @@ -325,8 +325,8 @@ import { .btn--primary:hover:not(:disabled) { background: var(--color-status-info); } .btn--primary:disabled { opacity: 0.5; cursor: not-allowed; } - .btn--secondary { background: var(--color-text-primary); color: var(--color-border-primary); border: 1px solid var(--color-text-primary); } - .btn--secondary:hover { background: var(--color-text-primary); } + .btn--secondary { background: var(--color-surface-tertiary); color: var(--color-text-primary); border: 1px solid var(--color-border-primary); } + .btn--secondary:hover { background: var(--color-surface-tertiary); } /* Content Layout */ .playground__content { @@ -344,8 +344,8 @@ import { /* Editor Panel */ .editor-panel, .results-panel { - background: var(--color-text-heading); - border: 1px solid var(--color-text-heading); + background: var(--color-surface-elevated); + border: 1px solid var(--color-border-primary); border-radius: var(--radius-lg); overflow: hidden; display: flex; @@ -358,14 +358,14 @@ import { justify-content: space-between; align-items: center; padding: 0.75rem 1rem; - background: var(--color-text-heading); - border-bottom: 1px solid var(--color-text-heading); + background: var(--color-surface-elevated); + border-bottom: 1px solid var(--color-border-primary); } .panel-header h3 { margin: 0; font-size: 0.9rem; - color: var(--color-surface-secondary); + color: var(--color-text-heading); } .panel-meta { @@ -416,20 +416,20 @@ import { .line-number--error { color: var(--color-status-error); - background: rgba(239, 68, 68, 0.1); + background: var(--color-status-error-bg); } .line-number--warning { color: var(--color-status-warning); - background: rgba(234, 179, 8, 0.1); + background: var(--color-status-warning-bg); } .editor { flex: 1; padding: 1rem; - background: var(--color-text-heading); + background: var(--color-surface-elevated); border: none; - color: var(--color-border-primary); + color: var(--color-text-primary); font-family: 'JetBrains Mono', 'Fira Code', monospace; font-size: 0.85rem; line-height: 1.5rem; @@ -444,7 +444,7 @@ import { align-items: center; padding: 0.5rem 1rem; background: var(--color-surface-inverse); - border-top: 1px solid var(--color-text-heading); + border-top: 1px solid var(--color-border-primary); overflow-x: auto; } @@ -456,8 +456,8 @@ import { .snippet-btn { padding: 0.25rem 0.5rem; - background: var(--color-text-primary); - border: 1px solid var(--color-text-primary); + background: var(--color-surface-tertiary); + border: 1px solid var(--color-border-primary); border-radius: var(--radius-sm); color: var(--color-text-muted); font-size: 0.7rem; @@ -467,8 +467,8 @@ import { } .snippet-btn:hover { - background: var(--color-text-primary); - color: var(--color-border-primary); + background: var(--color-surface-tertiary); + color: var(--color-text-primary); } /* Results Panel */ @@ -487,17 +487,17 @@ import { .summary-stat { flex: 1; padding: 0.75rem; - background: var(--color-text-heading); + background: var(--color-surface-elevated); border-radius: var(--radius-md); text-align: center; } .summary-stat--error { - border: 1px solid rgba(239, 68, 68, 0.3); + border: 1px solid var(--color-status-error-border); } .summary-stat--warning { - border: 1px solid rgba(234, 179, 8, 0.3); + border: 1px solid var(--color-status-warning-border); } .stat-value { @@ -543,7 +543,7 @@ import { .issue-item { padding: 0.75rem; - background: var(--color-text-heading); + background: var(--color-surface-elevated); border-radius: var(--radius-md); margin-bottom: 0.5rem; border-left: 3px solid; @@ -552,7 +552,7 @@ import { } .issue-item:hover { - background: var(--color-text-primary); + background: var(--color-surface-tertiary); } .issue-item--error { @@ -572,7 +572,7 @@ import { .issue-message { font-size: 0.85rem; - color: var(--color-border-primary); + color: var(--color-text-primary); } .issue-path { @@ -604,17 +604,17 @@ import { .parsed-preview { margin-top: 1rem; padding-top: 1rem; - border-top: 1px solid var(--color-text-heading); + border-top: 1px solid var(--color-border-primary); } .parsed-preview h4 { margin: 0 0 0.75rem; font-size: 0.9rem; - color: var(--color-surface-secondary); + color: var(--color-text-heading); } .preview-tree { - background: var(--color-text-heading); + background: var(--color-surface-elevated); border-radius: var(--radius-md); padding: 0.75rem; } @@ -651,8 +651,8 @@ import { /* Schema Reference */ .schema-reference { - background: var(--color-text-heading); - border: 1px solid var(--color-text-heading); + background: var(--color-surface-elevated); + border: 1px solid var(--color-border-primary); border-radius: var(--radius-lg); padding: 1.25rem; } @@ -660,7 +660,7 @@ import { .schema-reference h3 { margin: 0 0 1rem; font-size: 1rem; - color: var(--color-surface-secondary); + color: var(--color-text-heading); } .reference-grid { @@ -670,7 +670,7 @@ import { } .reference-card { - background: var(--color-text-heading); + background: var(--color-surface-elevated); border-radius: var(--radius-md); padding: 1rem; } @@ -695,15 +695,15 @@ import { .reference-card code { font-family: monospace; font-size: 0.8rem; - color: var(--color-border-primary); - background: var(--color-text-primary); + color: var(--color-text-primary); + background: var(--color-surface-tertiary); padding: 0.1rem 0.35rem; border-radius: var(--radius-sm); } .reference-card pre { margin: 0; - background: var(--color-text-heading); + background: var(--color-surface-elevated); padding: 0.75rem; border-radius: var(--radius-sm); overflow-x: auto; diff --git a/src/Web/StellaOps.Web/src/app/features/policy-governance/sealed-mode-control.component.ts b/src/Web/StellaOps.Web/src/app/features/policy-governance/sealed-mode-control.component.ts index 13f3988a9..99a595973 100644 --- a/src/Web/StellaOps.Web/src/app/features/policy-governance/sealed-mode-control.component.ts +++ b/src/Web/StellaOps.Web/src/app/features/policy-governance/sealed-mode-control.component.ts @@ -1,5 +1,5 @@ import { CommonModule } from '@angular/common'; -import { Component, ChangeDetectionStrategy, inject, signal, OnInit } from '@angular/core'; +import { Component, ChangeDetectionStrategy, inject, signal, OnInit, ViewChild } from '@angular/core'; import { FormBuilder, FormGroup, ReactiveFormsModule, FormsModule, Validators, FormArray } from '@angular/forms'; import { finalize } from 'rxjs/operators'; @@ -12,7 +12,10 @@ import { SealedModeToggleRequest, SealedModeOverrideRequest, } from '../../core/api/policy-governance.models'; +import { LoadingStateComponent } from '../../shared/components/loading-state/loading-state.component'; import { injectPolicyGovernanceScopeResolver } from './policy-governance-scope'; +import { ConfirmDialogComponent } from '../../shared/components/confirm-dialog/confirm-dialog.component'; +import { ModalComponent } from '../../shared/components/modal/modal.component'; /** * Sealed Mode Control component. @@ -22,7 +25,7 @@ import { injectPolicyGovernanceScopeResolver } from './policy-governance-scope'; */ @Component({ selector: 'app-sealed-mode-control', - imports: [CommonModule, ReactiveFormsModule, FormsModule], + imports: [CommonModule, ReactiveFormsModule, FormsModule, ConfirmDialogComponent, ModalComponent, LoadingStateComponent], changeDetection: ChangeDetectionStrategy.OnPush, template: `
    @@ -156,7 +159,7 @@ import { injectPolicyGovernanceScopeResolver } from './policy-governance-scope';
    @if (override.active && !isExpired(override)) {
    -
    @@ -174,180 +177,145 @@ import { injectPolicyGovernanceScopeResolver } from './policy-governance-scope'; }
    } @else if (loading()) { -
    Loading sealed mode status...
    + } - @if (showSealConfirm()) { -