@@ -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
-
-
+
+
+ = 0 ? '+' : '') + d.kpis.headroomDelta24h + ' (24h)'"
+ icon="M2.25 18 9 11.25l4.306 4.306a11.95 11.95 0 0 1 5.814-5.518l2.74-1.22M16.5 6.75V10.5h3.75" />
+
+
+
@@ -130,7 +124,7 @@ import { injectPolicyGovernanceScopeResolver } from './policy-governance-scope';
} @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.
-
-
-
-
-
-
+
@if (profile.status === 'draft') {
-
} @else if (loading()) {
-
Loading profiles...
+
} @else {
}
+
+
+
+
+ @if (showDeprecateModal()) {
+
+
+
+
+
+
+ Cancel
+ Deprecate
+
+
+ }
`,
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