Remove redundant headers from pack detail tabs, fix button theming
All 7 pack detail components (dashboard, editor, rule-builder, yaml, simulation, approvals, explain) had their own eyebrow + h1 + lede headers that duplicated the parent pack-shell's dynamic title+subtitle. Changes per component: - Dashboard: removed header, added empty state with guidance, themed btns - Editor: removed header, kept metadata as right-aligned flex, themed btns - YAML: removed header, fixed label colors and button theming - Rule builder: removed header, fixed label colors - Simulation: removed header, kept status pill, themed btns from gradient to btn-primary-bg, fixed hardcoded rgba colors - Approvals: removed header, kept workflow status badges, themed btns - Explain: removed header, kept action links, added btn/btn--secondary CSS Button theming: replaced var(--color-status-info-text) backgrounds and hardcoded rgba() with var(--color-btn-primary-bg/border/text) and var(--color-btn-secondary-bg/border/text) across all components. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -22,33 +22,24 @@ import { PolicyApiService } from '../services/policy-api.service';
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
template: `
|
||||
<section class="approvals" [attr.aria-busy]="loading">
|
||||
<header class="approvals__header">
|
||||
<div>
|
||||
<p class="approvals__eyebrow">Policy Studio · Approvals</p>
|
||||
<h1>Submit, review, approve</h1>
|
||||
<p class="approvals__lede">
|
||||
Two-person approval with deterministic audit trail and scoped activation windows.
|
||||
</p>
|
||||
@if (workflow) {
|
||||
<div class="approvals__actions">
|
||||
<span class="pill" [class.pill--approved]="workflow.status === 'approved'" [class.pill--pending]="workflow.status !== 'approved'">
|
||||
{{ workflow.status | titlecase }}
|
||||
</span>
|
||||
<span class="approvals__count">Required approvers: {{ workflow.requiredApprovers }}</span>
|
||||
<span class="approvals__count">Current: {{ workflow.currentApprovers }}</span>
|
||||
<span class="approvals__badge" [class.approvals__badge--ready]="isReadyToApprove" [class.approvals__badge--missing]="!isReadyToApprove">
|
||||
Two-person rule: {{ isReadyToApprove ? 'Satisfied' : 'Missing second approver' }}
|
||||
</span>
|
||||
<span class="approvals__badge" [class.approvals__badge--ready]="pendingChecklist === 0" [class.approvals__badge--missing]="pendingChecklist > 0">
|
||||
Checklist: {{ pendingChecklist === 0 ? 'Ready' : pendingChecklist + ' open item' + (pendingChecklist === 1 ? '' : 's') }}
|
||||
</span>
|
||||
<span class="approvals__badge" [class.approvals__badge--ready]="scheduleSummary !== 'Unscheduled'" [class.approvals__badge--missing]="scheduleSummary === 'Unscheduled'">
|
||||
Schedule: {{ scheduleSummary }}
|
||||
</span>
|
||||
</div>
|
||||
@if (workflow) {
|
||||
<div class="approvals__meta">
|
||||
<span class="pill" [class.pill--approved]="workflow.status === 'approved'" [class.pill--pending]="workflow.status !== 'approved'">
|
||||
{{ workflow.status | titlecase }}
|
||||
</span>
|
||||
<span class="approvals__count">Required approvers: {{ workflow.requiredApprovers }}</span>
|
||||
<span class="approvals__count">Current: {{ workflow.currentApprovers }}</span>
|
||||
<span class="approvals__badge" [class.approvals__badge--ready]="isReadyToApprove" [class.approvals__badge--missing]="!isReadyToApprove">
|
||||
Two-person rule: {{ isReadyToApprove ? 'Satisfied' : 'Missing second approver' }}
|
||||
</span>
|
||||
<span class="approvals__badge" [class.approvals__badge--ready]="pendingChecklist === 0" [class.approvals__badge--missing]="pendingChecklist > 0">
|
||||
Checklist: {{ pendingChecklist === 0 ? 'Ready' : pendingChecklist + ' open item' + (pendingChecklist === 1 ? '' : 's') }}
|
||||
</span>
|
||||
<span class="approvals__badge" [class.approvals__badge--ready]="scheduleSummary !== 'Unscheduled'" [class.approvals__badge--missing]="scheduleSummary === 'Unscheduled'">
|
||||
Schedule: {{ scheduleSummary }}
|
||||
</span>
|
||||
</div>
|
||||
}
|
||||
</header>
|
||||
}
|
||||
|
||||
@if (workflow) {
|
||||
<div class="approvals__grid">
|
||||
@@ -225,30 +216,13 @@ import { PolicyApiService } from '../services/policy-api.service';
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.approvals__header {
|
||||
.approvals__actions {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
gap: 1rem;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.approvals__eyebrow {
|
||||
margin: 0;
|
||||
color: var(--color-brand-muted);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.05em;
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
|
||||
.approvals__lede {
|
||||
margin: 0.2rem 0 0;
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
.approvals__meta {
|
||||
display: grid;
|
||||
justify-items: end;
|
||||
gap: 0.3rem;
|
||||
justify-content: flex-end;
|
||||
gap: 0.5rem;
|
||||
margin-bottom: 1rem;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.pill {
|
||||
@@ -334,16 +308,16 @@ import { PolicyApiService } from '../services/policy-api.service';
|
||||
}
|
||||
|
||||
.btn {
|
||||
background: var(--color-status-info-text);
|
||||
border: 1px solid var(--color-status-info-text);
|
||||
color: var(--color-text-primary);
|
||||
background: var(--color-btn-primary-bg);
|
||||
border: 1px solid var(--color-btn-primary-border);
|
||||
color: var(--color-btn-primary-text);
|
||||
border-radius: var(--radius-xl);
|
||||
padding: 0.55rem 0.9rem;
|
||||
font-weight: var(--font-weight-bold);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.btn:hover { background: var(--color-status-info-text); }
|
||||
.btn:hover { opacity: 0.9; }
|
||||
|
||||
.btn:disabled { opacity: 0.6; cursor: not-allowed; }
|
||||
|
||||
@@ -469,7 +443,7 @@ import { PolicyApiService } from '../services/policy-api.service';
|
||||
|
||||
.schedule__summary { display: flex; flex-direction: column; gap: 0.15rem; color: var(--color-text-secondary); }
|
||||
|
||||
@media (max-width: 960px) { .approvals__header { flex-direction: column; } }
|
||||
@media (max-width: 960px) { .approvals__actions { justify-content: flex-start; } }
|
||||
`,
|
||||
]
|
||||
})
|
||||
|
||||
@@ -18,20 +18,20 @@ import {
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
template: `
|
||||
<section class="dash" [attr.aria-busy]="loading">
|
||||
<header class="dash__header">
|
||||
<div>
|
||||
<p class="dash__eyebrow">Policy Studio · Runs</p>
|
||||
<h1>Run dashboards</h1>
|
||||
<p class="dash__lede">Heatmap, VEX wins, and suppressions; deterministic ordering.</p>
|
||||
@if (dashboard) {
|
||||
<div class="dash__actions">
|
||||
<span class="pill">Runs: {{ dashboard.runs.length }}</span>
|
||||
<span class="pill">Rules: {{ dashboard.ruleHeatmap.length }}</span>
|
||||
</div>
|
||||
@if (dashboard) {
|
||||
<div class="dash__meta">
|
||||
<span class="pill">Runs: {{ dashboard.runs.length }}</span>
|
||||
<span class="pill">Rules: {{ dashboard.ruleHeatmap.length }}</span>
|
||||
</div>
|
||||
}
|
||||
</header>
|
||||
|
||||
}
|
||||
|
||||
@if (!loading && !dashboard) {
|
||||
<div class="dash__empty">
|
||||
<p class="dash__empty-title">No run data available</p>
|
||||
<p class="dash__empty-hint">Run a policy evaluation to see dashboards, heatmaps, and daily deltas here.</p>
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (dashboard) {
|
||||
<div class="dash__grid">
|
||||
<div class="card">
|
||||
@@ -105,12 +105,12 @@ import {
|
||||
`,
|
||||
styles: [
|
||||
`
|
||||
:host { display: block; background: var(--color-surface-primary); color: var(--color-text-primary); ; }
|
||||
:host { display: block; background: var(--color-surface-primary); color: var(--color-text-primary); }
|
||||
.dash { max-width: 1200px; margin: 0 auto; padding: 1.5rem; }
|
||||
.dash__header { display: flex; justify-content: space-between; align-items: flex-start; gap: 1rem; }
|
||||
.dash__eyebrow { margin: 0; color: var(--color-status-info); text-transform: uppercase; letter-spacing: 0.05em; font-size: 0.8rem; }
|
||||
.dash__lede { margin: 0.2rem 0 0; color: var(--color-text-muted); }
|
||||
.dash__meta { display: flex; gap: 0.5rem; flex-wrap: wrap; }
|
||||
.dash__actions { display: flex; justify-content: flex-end; gap: 0.5rem; margin-bottom: 1rem; flex-wrap: wrap; }
|
||||
.dash__empty { display: flex; flex-direction: column; align-items: center; justify-content: center; padding: 3rem 1.5rem; border: 1px dashed var(--color-border-primary); border-radius: var(--radius-xl); background: var(--color-surface-elevated); text-align: center; }
|
||||
.dash__empty-title { margin: 0; font-size: 1.1rem; font-weight: var(--font-weight-semibold); color: var(--color-text-primary); }
|
||||
.dash__empty-hint { margin: 0.5rem 0 0; color: var(--color-text-muted); font-size: 0.9rem; max-width: 420px; }
|
||||
.pill { border: 1px solid var(--color-border-primary); padding: 0.35rem 0.7rem; border-radius: var(--radius-full); }
|
||||
.dash__grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(320px, 1fr)); gap: 1rem; margin-top: 1rem; }
|
||||
.card { background: var(--color-surface-elevated); border: 1px solid var(--color-border-primary); border-radius: var(--radius-xl); padding: 1rem; box-shadow: 0 10px 30px rgba(0,0,0,0.25); }
|
||||
@@ -124,11 +124,15 @@ import {
|
||||
.heatmap__name { color: var(--color-text-primary); font-weight: var(--font-weight-semibold); }
|
||||
.heatmap__bar { display: flex; align-items: center; gap: 0.4rem; background: var(--color-surface-elevated); border-radius: var(--radius-full); overflow: hidden; padding: 0.2rem; }
|
||||
.heatmap__fill { display: block; height: 10px; background: linear-gradient(90deg, var(--color-status-info), var(--color-status-info-text)); border-radius: var(--radius-full); }
|
||||
.heatmap__value { color: rgba(212, 201, 168, 0.5); font-size: 0.85rem; }
|
||||
.heatmap__value { color: var(--color-text-muted); font-size: 0.85rem; }
|
||||
.heatmap__latency { color: var(--color-text-muted); font-size: 0.85rem; }
|
||||
.chips { display: flex; flex-wrap: wrap; gap: 0.4rem; margin-top: 0.5rem; }
|
||||
.chip { border: 1px solid var(--color-border-primary); border-radius: var(--radius-full); padding: 0.25rem 0.6rem; background: var(--color-surface-primary); }
|
||||
.chip--muted { opacity: 0.8; }
|
||||
.btn { background: var(--color-btn-primary-bg); border: 1px solid var(--color-btn-primary-border); color: var(--color-btn-primary-text); border-radius: var(--radius-lg); padding: 0.4rem 0.8rem; cursor: pointer; font-weight: var(--font-weight-semibold); }
|
||||
.btn:hover { opacity: 0.9; }
|
||||
.btn:disabled { opacity: 0.6; cursor: not-allowed; }
|
||||
.btn--ghost { background: transparent; color: var(--color-text-secondary); border: 1px solid var(--color-border-primary); }
|
||||
`,
|
||||
]
|
||||
})
|
||||
|
||||
@@ -45,35 +45,23 @@ interface ChecklistItem {
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
template: `
|
||||
<section class="policy-editor" [attr.aria-busy]="loadingPack">
|
||||
<header class="policy-editor__header">
|
||||
<div class="policy-editor__title">
|
||||
<p class="policy-editor__eyebrow">Policy Studio · Authoring</p>
|
||||
<h1>{{ pack?.name || 'Loading policy…' }}</h1>
|
||||
@if (pack) {
|
||||
<p class="policy-editor__subtitle">
|
||||
Version {{ pack.version }} · Status: {{ pack.status | titlecase }} ·
|
||||
Digest: {{ pack.digest || 'pending' }}
|
||||
</p>
|
||||
}
|
||||
</div>
|
||||
@if (pack) {
|
||||
<div class="policy-editor__meta">
|
||||
<div class="policy-editor__meta-item">
|
||||
<span class="policy-editor__meta-label">Updated</span>
|
||||
<span>{{ pack.modifiedAt | date: 'medium' }}</span>
|
||||
</div>
|
||||
<div class="policy-editor__meta-item">
|
||||
<span class="policy-editor__meta-label">Authors</span>
|
||||
<span>{{ pack.metadata?.author || pack.createdBy }}</span>
|
||||
</div>
|
||||
<div class="policy-editor__meta-item">
|
||||
<span class="policy-editor__meta-label">Tags</span>
|
||||
<span>{{ pack.tags?.length || 0 }}</span>
|
||||
</div>
|
||||
@if (pack) {
|
||||
<div class="policy-editor__meta">
|
||||
<div class="policy-editor__meta-item">
|
||||
<span class="policy-editor__meta-label">Updated</span>
|
||||
<span>{{ pack.modifiedAt | date: 'medium' }}</span>
|
||||
</div>
|
||||
}
|
||||
</header>
|
||||
|
||||
<div class="policy-editor__meta-item">
|
||||
<span class="policy-editor__meta-label">Authors</span>
|
||||
<span>{{ pack.metadata?.author || pack.createdBy }}</span>
|
||||
</div>
|
||||
<div class="policy-editor__meta-item">
|
||||
<span class="policy-editor__meta-label">Tags</span>
|
||||
<span>{{ pack.tags?.length || 0 }}</span>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
<div class="policy-editor__layout">
|
||||
<section class="policy-editor__main" aria-label="Policy editor">
|
||||
<div class="editor-shell" [class.editor-shell--loading]="loadingEditor">
|
||||
@@ -201,40 +189,12 @@ interface ChecklistItem {
|
||||
padding: 1.5rem;
|
||||
}
|
||||
|
||||
.policy-editor__header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
gap: 1.5rem;
|
||||
align-items: flex-start;
|
||||
margin-bottom: 1.25rem;
|
||||
}
|
||||
|
||||
.policy-editor__title h1 {
|
||||
margin: 0.1rem 0;
|
||||
font-size: 1.8rem;
|
||||
font-weight: var(--font-weight-bold);
|
||||
color: var(--color-text-inverse);
|
||||
}
|
||||
|
||||
.policy-editor__eyebrow {
|
||||
margin: 0;
|
||||
color: var(--color-status-info-border);
|
||||
letter-spacing: 0.05em;
|
||||
text-transform: uppercase;
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
|
||||
.policy-editor__subtitle {
|
||||
margin: 0;
|
||||
color: var(--color-text-muted);
|
||||
font-size: 0.95rem;
|
||||
}
|
||||
|
||||
.policy-editor__meta {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
|
||||
display: flex;
|
||||
gap: 0.75rem;
|
||||
min-width: 320px;
|
||||
justify-content: flex-end;
|
||||
flex-wrap: wrap;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.policy-editor__meta-item {
|
||||
@@ -334,9 +294,9 @@ interface ChecklistItem {
|
||||
}
|
||||
|
||||
.btn {
|
||||
border: 1px solid var(--color-status-info-text);
|
||||
background: var(--color-status-info-text);
|
||||
color: var(--color-text-primary);
|
||||
border: 1px solid var(--color-btn-primary-border);
|
||||
background: var(--color-btn-primary-bg);
|
||||
color: var(--color-btn-primary-text);
|
||||
border-radius: var(--radius-lg);
|
||||
padding: 0.55rem 0.85rem;
|
||||
font-weight: var(--font-weight-semibold);
|
||||
@@ -345,8 +305,7 @@ interface ChecklistItem {
|
||||
}
|
||||
|
||||
.btn:hover {
|
||||
background: var(--color-status-info-text);
|
||||
border-color: var(--color-status-info-text);
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
.btn:disabled {
|
||||
@@ -356,6 +315,7 @@ interface ChecklistItem {
|
||||
|
||||
.btn--ghost {
|
||||
background: transparent;
|
||||
color: var(--color-text-secondary);
|
||||
border-color: var(--color-border-primary);
|
||||
}
|
||||
|
||||
|
||||
@@ -13,24 +13,17 @@ import jsPDF from './jspdf.stub';
|
||||
template: `
|
||||
<section class="expl" [attr.aria-busy]="loading">
|
||||
@if (result) {
|
||||
<header class="expl__header">
|
||||
<div>
|
||||
<p class="expl__eyebrow">Policy Studio · Explain</p>
|
||||
<h1>Run {{ result.runId }}</h1>
|
||||
<p class="expl__lede">Policy {{ result.policyId }} · Version {{ result.policyVersion }}</p>
|
||||
</div>
|
||||
<div class="expl__meta">
|
||||
<a
|
||||
routerLink="/triage/audit-bundles/new"
|
||||
[queryParams]="{ runId: result.runId, policyId: result.policyId }"
|
||||
class="expl__btn"
|
||||
>
|
||||
Create immutable audit bundle
|
||||
</a>
|
||||
<button type="button" (click)="exportJson()">Export JSON</button>
|
||||
<button type="button" (click)="exportPdf()">Export PDF</button>
|
||||
</div>
|
||||
</header>
|
||||
<div class="expl__actions">
|
||||
<a
|
||||
routerLink="/triage/audit-bundles/new"
|
||||
[queryParams]="{ runId: result.runId, policyId: result.policyId }"
|
||||
class="expl__btn"
|
||||
>
|
||||
Create immutable audit bundle
|
||||
</a>
|
||||
<button type="button" class="btn btn--secondary" (click)="exportJson()">Export JSON</button>
|
||||
<button type="button" class="btn btn--secondary" (click)="exportPdf()">Export PDF</button>
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (result) {
|
||||
@@ -73,14 +66,14 @@ import jsPDF from './jspdf.stub';
|
||||
`,
|
||||
styles: [
|
||||
`
|
||||
:host { display: block; background: var(--color-surface-elevated); color: var(--color-text-primary); ; }
|
||||
:host { display: block; background: var(--color-surface-elevated); color: var(--color-text-primary); }
|
||||
.expl { max-width: 1200px; margin: 0 auto; padding: 1.5rem; }
|
||||
.expl__header { display: flex; justify-content: space-between; align-items: center; }
|
||||
.expl__eyebrow { margin: 0; color: var(--color-status-info); text-transform: uppercase; letter-spacing: 0.05em; font-size: 0.8rem; }
|
||||
.expl__lede { margin: 0.2rem 0 0; color: var(--color-text-muted); }
|
||||
.expl__meta { display: flex; gap: 0.5rem; align-items: center; }
|
||||
.expl__btn { display: inline-flex; align-items: center; border: 1px solid var(--color-border-primary); border-radius: var(--radius-lg); padding: 0.35rem 0.65rem; color: var(--color-text-primary); text-decoration: none; background: var(--color-surface-elevated); }
|
||||
.expl__btn:hover { border-color: var(--color-status-info); }
|
||||
.expl__actions { display: flex; justify-content: flex-end; gap: 0.5rem; margin-bottom: 1rem; align-items: center; }
|
||||
.expl__btn { display: inline-flex; align-items: center; border: 1px solid var(--color-btn-secondary-border); border-radius: var(--radius-lg); padding: 0.35rem 0.65rem; color: var(--color-btn-secondary-text); text-decoration: none; background: var(--color-btn-secondary-bg); }
|
||||
.expl__btn:hover { opacity: 0.9; }
|
||||
.btn { background: var(--color-btn-primary-bg); border: 1px solid var(--color-btn-primary-border); color: var(--color-btn-primary-text); border-radius: var(--radius-lg); padding: 0.35rem 0.65rem; cursor: pointer; font-weight: var(--font-weight-semibold); }
|
||||
.btn--secondary { background: var(--color-btn-secondary-bg); border-color: var(--color-btn-secondary-border); color: var(--color-btn-secondary-text); }
|
||||
.btn:hover { opacity: 0.9; }
|
||||
.expl__grid { display: grid; grid-template-columns: 2fr 1fr; gap: 1rem; margin-top: 1rem; }
|
||||
.card { background: var(--color-surface-elevated); border: 1px solid var(--color-border-primary); border-radius: var(--radius-xl); padding: 1rem; }
|
||||
ol { margin: 0.5rem 0 0; padding-left: 1.25rem; }
|
||||
|
||||
@@ -9,17 +9,6 @@ import { ActivatedRoute } from '@angular/router';
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
template: `
|
||||
<section class="rb" [attr.aria-busy]="false">
|
||||
<header class="rb__header">
|
||||
<div>
|
||||
<p class="rb__eyebrow">Policy Studio · Rule Builder</p>
|
||||
<h1>Guided rule construction</h1>
|
||||
<p class="rb__lede">Deterministic preview JSON updates as you edit.</p>
|
||||
</div>
|
||||
<div class="rb__meta">
|
||||
<span>Pack: {{ packId }}</span>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div class="rb__grid">
|
||||
<form [formGroup]="form" class="rb__form">
|
||||
<label>
|
||||
@@ -72,14 +61,11 @@ import { ActivatedRoute } from '@angular/router';
|
||||
`,
|
||||
styles: [
|
||||
`
|
||||
:host { display: block; background: var(--color-surface-primary); color: var(--color-text-primary); ; }
|
||||
:host { display: block; background: var(--color-surface-primary); color: var(--color-text-primary); }
|
||||
.rb { max-width: 1200px; margin: 0 auto; padding: 1.5rem; }
|
||||
.rb__header { display: flex; justify-content: space-between; gap: 1rem; }
|
||||
.rb__eyebrow { margin: 0; color: var(--color-status-info); text-transform: uppercase; letter-spacing: 0.05em; font-size: 0.8rem; }
|
||||
.rb__lede { margin: 0.2rem 0 0; color: var(--color-text-muted); }
|
||||
.rb__grid { display: grid; grid-template-columns: minmax(0, 1.1fr) 1fr; gap: 1rem; margin-top: 1rem; }
|
||||
.rb__grid { display: grid; grid-template-columns: minmax(0, 1.1fr) 1fr; gap: 1rem; }
|
||||
.rb__form { background: var(--color-surface-elevated); border: 1px solid var(--color-border-primary); border-radius: var(--radius-xl); padding: 1rem; display: grid; gap: 0.65rem; }
|
||||
label { display: grid; gap: 0.25rem; color: rgba(212, 201, 168, 0.5); }
|
||||
label { display: grid; gap: 0.25rem; color: var(--color-text-secondary); }
|
||||
input, select { background: var(--color-surface-primary); color: var(--color-text-primary); border: 1px solid var(--color-border-primary); border-radius: var(--radius-lg); padding: 0.5rem; }
|
||||
.rb__preview { background: var(--color-surface-elevated); border: 1px solid var(--color-border-primary); border-radius: var(--radius-xl); padding: 1rem; }
|
||||
pre { margin: 0; max-height: 420px; overflow: auto; background: var(--color-surface-primary); border: 1px solid var(--color-border-primary); border-radius: var(--radius-xl); padding: 0.75rem; }
|
||||
|
||||
@@ -19,24 +19,15 @@ import { PolicyApiService } from '../services/policy-api.service';
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
template: `
|
||||
<section class="sim" [attr.aria-busy]="loading">
|
||||
<header class="sim__header">
|
||||
<div>
|
||||
<p class="sim__eyebrow">Policy Studio · Simulation</p>
|
||||
<h1>What-if analysis</h1>
|
||||
<p class="sim__lede">
|
||||
Run deterministic diffs against the active policy to preview impact before promote.
|
||||
</p>
|
||||
</div>
|
||||
<div class="sim__meta">
|
||||
<span class="pill" [class.pill--ok]="result?.status === 'completed'" [class.pill--warn]="result?.status === 'failed'">
|
||||
{{ result ? result.status : 'Idle' | titlecase }}
|
||||
</span>
|
||||
@if (result) {
|
||||
<span class="sim__timestamp">Completed at {{ result.executedAt | date:'medium' }}</span>
|
||||
}
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div class="sim__actions">
|
||||
<span class="pill" [class.pill--ok]="result?.status === 'completed'" [class.pill--warn]="result?.status === 'failed'">
|
||||
{{ result ? result.status : 'Idle' | titlecase }}
|
||||
</span>
|
||||
@if (result) {
|
||||
<span class="sim__timestamp">Completed at {{ result.executedAt | date:'medium' }}</span>
|
||||
}
|
||||
</div>
|
||||
|
||||
<div class="sim__layout">
|
||||
<form class="sim__form" [formGroup]="form" (ngSubmit)="onRun()" aria-label="Simulation input">
|
||||
<label class="field">
|
||||
@@ -231,32 +222,12 @@ import { PolicyApiService } from '../services/policy-api.service';
|
||||
padding: 1.5rem;
|
||||
}
|
||||
|
||||
.sim__header {
|
||||
.sim__actions {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
gap: 1rem;
|
||||
align-items: flex-start;
|
||||
justify-content: flex-end;
|
||||
gap: 0.5rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.sim__eyebrow {
|
||||
margin: 0;
|
||||
color: var(--color-brand-muted);
|
||||
font-size: 0.8rem;
|
||||
letter-spacing: 0.05em;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.sim__lede {
|
||||
margin: 0.25rem 0 0;
|
||||
color: rgba(212, 201, 168, 0.5);
|
||||
}
|
||||
|
||||
.sim__meta {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.35rem;
|
||||
align-items: flex-end;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.pill {
|
||||
@@ -300,7 +271,7 @@ import { PolicyApiService } from '../services/policy-api.service';
|
||||
.field span {
|
||||
display: block;
|
||||
margin-bottom: 0.25rem;
|
||||
color: rgba(212, 201, 168, 0.5);
|
||||
color: var(--color-text-secondary);
|
||||
font-weight: var(--font-weight-semibold);
|
||||
}
|
||||
|
||||
@@ -322,20 +293,18 @@ import { PolicyApiService } from '../services/policy-api.service';
|
||||
|
||||
.btn {
|
||||
justify-self: start;
|
||||
background: linear-gradient(120deg, var(--color-status-info-text), var(--color-status-info));
|
||||
border: 1px solid var(--color-status-info-text);
|
||||
color: var(--color-text-inverse);
|
||||
background: var(--color-btn-primary-bg);
|
||||
border: 1px solid var(--color-btn-primary-border);
|
||||
color: var(--color-btn-primary-text);
|
||||
font-weight: var(--font-weight-bold);
|
||||
padding: 0.6rem 1rem;
|
||||
border-radius: var(--radius-xl);
|
||||
cursor: pointer;
|
||||
box-shadow: 0 10px 30px rgba(37, 99, 235, 0.25);
|
||||
transition: transform 120ms ease, box-shadow 120ms ease;
|
||||
transition: opacity 120ms ease;
|
||||
}
|
||||
|
||||
.btn:hover {
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 14px 36px rgba(37, 99, 235, 0.35);
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
.btn:disabled {
|
||||
@@ -390,7 +359,7 @@ import { PolicyApiService } from '../services/policy-api.service';
|
||||
|
||||
.diff-grid h4 {
|
||||
margin: 0 0 0.2rem;
|
||||
color: rgba(212, 201, 168, 0.5);
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
.diff-grid ul {
|
||||
@@ -441,7 +410,7 @@ import { PolicyApiService } from '../services/policy-api.service';
|
||||
}
|
||||
|
||||
th {
|
||||
color: rgba(212, 201, 168, 0.5);
|
||||
color: var(--color-text-secondary);
|
||||
position: sticky;
|
||||
top: 0;
|
||||
}
|
||||
|
||||
@@ -19,24 +19,6 @@ interface YamlDiagnostic {
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
template: `
|
||||
<section class="yaml" [attr.aria-busy]="loading">
|
||||
<header class="yaml__header">
|
||||
<div>
|
||||
<p class="yaml__eyebrow">Policy Studio · YAML</p>
|
||||
<h1>{{ pack?.name || 'Loading policy…' }}</h1>
|
||||
@if (pack) {
|
||||
<p class="yaml__lede">
|
||||
Version {{ pack.version }} · Status: {{ pack.status | titlecase }}
|
||||
</p>
|
||||
}
|
||||
</div>
|
||||
@if (pack) {
|
||||
<div class="yaml__meta">
|
||||
<span>Updated {{ pack.modifiedAt | date: 'medium' }}</span>
|
||||
<span>Tags: {{ pack.tags.length }}</span>
|
||||
</div>
|
||||
}
|
||||
</header>
|
||||
|
||||
<div class="yaml__layout">
|
||||
<div class="yaml__editor">
|
||||
<label for="yaml-input">YAML</label>
|
||||
@@ -77,18 +59,14 @@ interface YamlDiagnostic {
|
||||
`,
|
||||
styles: [
|
||||
`
|
||||
:host { display: block; background: var(--color-surface-primary); color: var(--color-text-primary); ; }
|
||||
:host { display: block; background: var(--color-surface-primary); color: var(--color-text-primary); }
|
||||
.yaml { max-width: 1200px; margin: 0 auto; padding: 1.5rem; }
|
||||
.yaml__header { display: flex; justify-content: space-between; gap: 1rem; }
|
||||
.yaml__eyebrow { margin: 0; color: var(--color-status-info); text-transform: uppercase; letter-spacing: 0.05em; font-size: 0.8rem; }
|
||||
.yaml__lede { margin: 0.2rem 0 0; color: var(--color-text-muted); }
|
||||
.yaml__meta { display: grid; justify-items: end; gap: 0.2rem; color: var(--color-text-muted); }
|
||||
.yaml__layout { display: grid; grid-template-columns: minmax(0, 1.4fr) 1fr; gap: 1rem; margin-top: 1rem; }
|
||||
.yaml__layout { display: grid; grid-template-columns: minmax(0, 1.4fr) 1fr; gap: 1rem; }
|
||||
.yaml__editor { background: var(--color-surface-elevated); border: 1px solid var(--color-border-primary); border-radius: var(--radius-xl); padding: 1rem; display: grid; gap: 0.5rem; }
|
||||
label { color: rgba(212, 201, 168, 0.5); font-weight: var(--font-weight-semibold); }
|
||||
label { color: var(--color-text-secondary); font-weight: var(--font-weight-semibold); }
|
||||
textarea { width: 100%; background: var(--color-surface-primary); color: var(--color-text-primary); border: 1px solid var(--color-border-primary); border-radius: var(--radius-xl); padding: 0.75rem; font-family: 'Monaco','Consolas', monospace; }
|
||||
.toolbar { display: flex; gap: 0.5rem; align-items: center; }
|
||||
button { background: var(--color-btn-primary-bg); border: 1px solid var(--color-btn-primary-bg); color: var(--color-text-primary); border-radius: var(--radius-lg); padding: 0.4rem 0.8rem; cursor: pointer; }
|
||||
button { background: var(--color-btn-primary-bg); border: 1px solid var(--color-btn-primary-border); color: var(--color-btn-primary-text); border-radius: var(--radius-lg); padding: 0.4rem 0.8rem; cursor: pointer; }
|
||||
button:disabled { opacity: 0.6; cursor: not-allowed; }
|
||||
.pill { border: 1px solid var(--color-border-primary); padding: 0.3rem 0.6rem; border-radius: var(--radius-full); }
|
||||
.pill--ok { border-color: var(--color-status-success); color: var(--color-status-success); }
|
||||
|
||||
Reference in New Issue
Block a user