save checkpoint

This commit is contained in:
master
2026-02-14 09:11:48 +02:00
parent 9ca2de05df
commit e9aeadc040
1512 changed files with 30863 additions and 4728 deletions

View File

@@ -54,8 +54,8 @@
},
{
"type": "anyComponentStyle",
"maximumWarning": "12kb",
"maximumError": "20kb"
"maximumWarning": "20kb",
"maximumError": "30kb"
}
],
"outputHashing": "all",
@@ -101,13 +101,13 @@
"setupFiles": ["src/test-setup.ts"],
"exclude": [
"**/*.e2e.spec.ts",
"src/app/core/api/vex-hub.client.spec.ts",
"src/app/core/services/*.spec.ts",
"src/app/features/**/*.spec.ts",
"src/app/shared/components/**/*.spec.ts"
]
}
},
"src/app/core/api/vex-hub.client.spec.ts",
"src/app/core/services/*.spec.ts",
"src/app/features/**/*.spec.ts",
"src/app/shared/components/**/*.spec.ts"
]
}
},
"storybook": {
"builder": "@storybook/angular:start-storybook",
"options": {

View File

@@ -1,114 +1,114 @@
{
"/envsettings.json": {
"target": "https://localhost:10010",
"target": "http://127.1.0.3:80",
"secure": false
},
"/platform": {
"target": "https://localhost:10010",
"target": "http://127.1.0.3:80",
"secure": false
},
"/api": {
"target": "https://localhost:10010",
"target": "http://127.1.0.3:80",
"secure": false
},
"/authority": {
"target": "https://localhost:10020",
"target": "http://127.1.0.4:80",
"secure": false
},
"/console": {
"target": "https://localhost:10020",
"target": "http://127.1.0.4:80",
"secure": false
},
"/connect": {
"target": "https://localhost:10020",
"target": "http://127.1.0.4:80",
"secure": false
},
"/.well-known": {
"target": "https://localhost:10020",
"target": "http://127.1.0.4:80",
"secure": false
},
"/jwks": {
"target": "https://localhost:10020",
"target": "http://127.1.0.4:80",
"secure": false
},
"/scanner": {
"target": "https://localhost:10080",
"target": "http://127.1.0.8:80",
"secure": false
},
"/policyGateway": {
"target": "https://localhost:10140",
"target": "http://127.1.0.14:80",
"secure": false
},
"/policyEngine": {
"target": "https://localhost:10140",
"target": "http://127.1.0.14:80",
"secure": false
},
"/concelier": {
"target": "https://localhost:10090",
"target": "http://127.1.0.9:80",
"secure": false
},
"/attestor": {
"target": "https://localhost:10040",
"target": "http://127.1.0.13:80",
"secure": false
},
"/gateway": {
"target": "https://localhost:10030",
"target": "http://127.1.0.5:80",
"secure": false
},
"/notify": {
"target": "https://localhost:10280",
"target": "http://127.1.0.29:80",
"secure": false
},
"/scheduler": {
"target": "https://localhost:10190",
"target": "http://127.1.0.19:80",
"secure": false
},
"/signals": {
"target": "https://localhost:10430",
"target": "http://127.1.0.43:80",
"secure": false
},
"/excititor": {
"target": "https://localhost:10310",
"target": "http://127.1.0.9:80",
"secure": false
},
"/findingsLedger": {
"target": "https://localhost:10320",
"target": "http://127.1.0.9:80",
"secure": false
},
"/vexhub": {
"target": "https://localhost:10330",
"target": "http://127.1.0.11:80",
"secure": false
},
"/vexlens": {
"target": "https://localhost:10340",
"target": "http://127.1.0.12:80",
"secure": false
},
"/orchestrator": {
"target": "https://localhost:10200",
"target": "http://127.1.0.17:80",
"secure": false
},
"/graph": {
"target": "https://localhost:10350",
"target": "http://127.1.0.20:80",
"secure": false
},
"/doctor": {
"target": "https://localhost:10360",
"target": "http://127.1.0.26:80",
"secure": false
},
"/integrations": {
"target": "https://localhost:10400",
"target": "http://127.1.0.42:80",
"secure": false
},
"/replay": {
"target": "https://localhost:10410",
"target": "http://127.1.0.41:80",
"secure": false
},
"/exportcenter": {
"target": "https://localhost:10420",
"target": "http://127.1.0.40:80",
"secure": false
},
"/healthz": {
"target": "https://localhost:10010",
"target": "http://127.1.0.3:80",
"secure": false
}
}

View File

@@ -197,330 +197,442 @@ import type {
</div>
`,
styles: [`
:host {
--so-brand: #F5A623;
--so-brand-hover: #E09115;
--so-accent: #D4920A;
--so-accent-muted: #C4820A;
--so-brand-soft: #FEF3E2;
--so-surface: #FFFCF5;
--so-surface-elevated: #fff;
--so-base: #FFF9ED;
--so-heading: #1C1200;
--so-text: #3D2E0A;
--so-text-secondary: #6B5A2E;
--so-mute: #D4C9A8;
--so-border-light: hsla(45,34%,75%,.3);
--so-border-medium: hsla(45,34%,75%,.5);
--so-border-emphasis: rgba(245,166,35,.4);
--so-success: #059669;
--so-success-soft: #D1FAE5;
--so-warning: #D97706;
--so-warning-soft: rgba(217,119,6,.08);
--so-error: #DC2626;
--so-error-soft: rgba(220,38,38,.08);
--so-info: #2563EB;
--so-info-soft: rgba(37,99,235,.08);
--so-shadow-brand: rgba(245,166,35,.15);
--so-shadow-dark: rgba(28,18,0,.08);
--so-shadow-ambient: rgba(61,46,10,.04);
--so-gradient-cta: linear-gradient(135deg, var(--so-brand) 0%, var(--so-accent) 100%);
--so-shadow-sm: 0 2px 4px var(--so-shadow-dark), 0 1px 2px var(--so-shadow-ambient);
--so-shadow-md: 0 4px 8px var(--so-shadow-dark), 0 2px 4px var(--so-shadow-ambient);
--so-shadow-lg: 0 8px 24px var(--so-shadow-dark), 0 4px 8px var(--so-shadow-ambient);
--so-shadow-brand-md: 0 4px 16px var(--so-shadow-brand);
--so-ease-out: cubic-bezier(0, 0, 0.2, 1);
--so-ease-bounce: cubic-bezier(0.34, 1.56, 0.64, 1);
font-family: 'Inter', system-ui, -apple-system, sans-serif;
display: block;
}
.dashboard {
max-width: 1400px;
margin: 0 auto;
animation: dash-in 500ms var(--so-ease-out) both;
}
@keyframes dash-in {
from { opacity: 0; transform: translateY(12px); }
to { opacity: 1; transform: translateY(0); }
}
/* Header */
.dashboard__header {
display: flex;
justify-content: space-between;
align-items: flex-start;
gap: var(--space-4);
margin-bottom: var(--space-6);
gap: 16px;
margin-bottom: 28px;
padding: 28px 32px;
background: var(--so-surface-elevated);
border: 1px solid var(--so-border-light);
border-radius: 16px;
box-shadow: var(--so-shadow-sm);
position: relative;
overflow: hidden;
}
.dashboard__header::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
height: 3px;
background: var(--so-gradient-cta);
}
.dashboard__title {
margin: 0 0 var(--space-1);
font-size: var(--font-size-2xl);
font-weight: var(--font-weight-bold);
color: var(--color-text-heading);
margin: 0 0 6px;
font-size: 26px;
font-weight: 800;
color: var(--so-heading);
letter-spacing: -0.035em;
}
.dashboard__subtitle {
margin: 0;
color: var(--color-text-secondary);
font-size: var(--font-size-base);
color: var(--so-text-secondary);
font-size: 14px;
line-height: 1.5;
}
.dashboard__actions {
display: flex;
gap: var(--space-2);
gap: 10px;
flex-shrink: 0;
}
/* Sections */
.dashboard__section {
margin-bottom: var(--space-6);
margin-bottom: 28px;
}
.dashboard__section-title {
margin: 0 0 var(--space-3);
font-size: var(--font-size-lg);
font-weight: var(--font-weight-semibold);
color: var(--color-text-heading);
margin: 0 0 14px;
font-size: 17px;
font-weight: 700;
color: var(--so-heading);
letter-spacing: -0.02em;
}
.dashboard__grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: var(--space-4);
margin-bottom: var(--space-6);
grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
gap: 20px;
margin-bottom: 28px;
}
/* Loading */
.dashboard__loading {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
min-height: 300px;
gap: var(--space-3);
color: var(--color-text-secondary);
gap: 16px;
color: var(--so-text-secondary);
}
.dashboard__spinner {
width: 32px;
height: 32px;
border: 3px solid var(--color-border-default, rgba(212, 201, 168, 0.3));
border-top-color: var(--color-brand-primary);
width: 36px;
height: 36px;
border: 3px solid var(--so-border-light);
border-top-color: var(--so-brand);
border-radius: 50%;
animation: spin 0.8s linear infinite;
}
/* Error */
.dashboard__error {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
min-height: 200px;
gap: var(--space-2);
padding: var(--space-6);
background: var(--color-surface-primary);
border: 1px solid var(--color-status-error-text, #c44);
border-radius: var(--radius-lg);
gap: 10px;
padding: 40px;
background: var(--so-surface-elevated);
border: 1px solid rgba(220,38,38,.2);
border-radius: 16px;
text-align: center;
box-shadow: var(--so-shadow-sm);
}
.dashboard__error-title {
margin: 0;
font-weight: var(--font-weight-semibold);
color: var(--color-text-heading);
font-weight: 700;
color: var(--so-heading);
}
.dashboard__error-message {
margin: 0;
color: var(--color-text-secondary);
font-size: var(--font-size-sm);
color: var(--so-text-secondary);
font-size: 13px;
}
.dashboard__empty {
padding: var(--space-6);
padding: 40px;
text-align: center;
color: var(--color-text-muted);
background: var(--color-surface-primary);
border: 1px dashed var(--color-border-default, rgba(212, 201, 168, 0.3));
border-radius: var(--radius-lg);
color: var(--so-text-secondary);
background: var(--so-surface-elevated);
border: 2px dashed var(--so-mute);
border-radius: 16px;
font-size: 14px;
}
/* Pipeline */
.pipeline {
display: flex;
align-items: center;
gap: var(--space-2);
padding: var(--space-4);
background: var(--color-surface-primary);
border: 1px solid var(--color-border-default, rgba(212, 201, 168, 0.3));
border-radius: var(--radius-lg);
gap: 10px;
padding: 20px 24px;
background: var(--so-surface-elevated);
border: 1px solid var(--so-border-light);
border-radius: 16px;
box-shadow: var(--so-shadow-sm);
overflow-x: auto;
}
.pipeline__stage {
flex: 1;
min-width: 140px;
padding: var(--space-3);
background: var(--color-surface-tertiary);
border: 1px solid var(--color-border-default, rgba(212, 201, 168, 0.3));
border-radius: var(--radius-md);
min-width: 150px;
padding: 16px;
background: var(--so-surface);
border: 1px solid var(--so-border-light);
border-radius: 12px;
text-align: center;
transition: all 220ms var(--so-ease-out);
position: relative;
}
.pipeline__stage:hover {
box-shadow: var(--so-shadow-md);
transform: translateY(-2px);
}
.pipeline__stage--degraded {
border-color: var(--color-status-warning-text);
border-color: rgba(217,119,6,.35);
background: var(--so-warning-soft);
}
.pipeline__stage--unhealthy {
border-color: var(--color-status-error-text, #c44);
border-color: rgba(220,38,38,.35);
background: var(--so-error-soft);
}
.pipeline__stage-header {
margin-bottom: var(--space-2);
margin-bottom: 10px;
}
.pipeline__stage-name {
display: block;
font-weight: var(--font-weight-semibold);
font-size: var(--font-size-sm);
color: var(--color-text-heading);
font-weight: 700;
font-size: 14px;
color: var(--so-heading);
letter-spacing: -0.01em;
}
.pipeline__stage-count {
font-size: var(--font-size-xs);
color: var(--color-text-muted);
font-size: 12px;
color: var(--so-text-secondary);
margin-top: 2px;
display: block;
}
.pipeline__health-badge {
display: inline-block;
padding: 2px var(--space-2);
font-size: var(--font-size-xs);
font-weight: var(--font-weight-medium);
border-radius: var(--radius-sm);
padding: 3px 10px;
font-size: 10px;
font-weight: 700;
border-radius: 6px;
text-transform: uppercase;
letter-spacing: 0.06em;
}
.pipeline__health-badge--healthy {
background: var(--color-status-success-bg);
color: var(--color-status-success-text);
background: var(--so-success-soft);
color: var(--so-success);
border: 1px solid rgba(5,150,105,.2);
}
.pipeline__health-badge--degraded {
background: var(--color-status-warning-bg);
color: var(--color-status-warning-text);
background: var(--so-warning-soft);
color: var(--so-warning);
border: 1px solid rgba(217,119,6,.2);
}
.pipeline__health-badge--unhealthy {
background: var(--color-status-error-bg);
color: var(--color-status-error-text);
background: var(--so-error-soft);
color: var(--so-error);
border: 1px solid rgba(220,38,38,.2);
}
.pipeline__health-badge--unknown {
background: var(--color-surface-secondary);
color: var(--color-text-muted);
background: var(--so-surface);
color: var(--so-mute);
border: 1px solid var(--so-border-light);
}
.pipeline__pending-count {
display: block;
margin-top: var(--space-1);
font-size: var(--font-size-xs);
color: var(--color-status-warning-text);
margin-top: 6px;
font-size: 11px;
font-weight: 600;
color: var(--so-warning);
}
.pipeline__arrow {
flex-shrink: 0;
color: var(--color-text-muted);
color: var(--so-mute);
opacity: .6;
}
/* Cards */
.card {
padding: var(--space-4);
background: var(--color-surface-primary);
border: 1px solid var(--color-border-default, rgba(212, 201, 168, 0.3));
border-radius: var(--radius-lg);
padding: 24px;
background: var(--so-surface-elevated);
border: 1px solid var(--so-border-light);
border-radius: 16px;
box-shadow: var(--so-shadow-sm);
transition: box-shadow 220ms var(--so-ease-out), transform 220ms var(--so-ease-out);
}
.card:hover {
box-shadow: var(--so-shadow-md);
transform: translateY(-2px);
}
.card__title {
margin: 0 0 var(--space-0-5);
font-size: var(--font-size-base);
font-weight: var(--font-weight-semibold);
color: var(--color-text-heading);
margin: 0 0 4px;
font-size: 16px;
font-weight: 700;
color: var(--so-heading);
letter-spacing: -0.01em;
}
.card__subtitle {
margin: 0 0 var(--space-3);
font-size: var(--font-size-sm);
color: var(--color-text-muted);
margin: 0 0 16px;
font-size: 13px;
color: var(--so-text-secondary);
}
.card__empty {
margin: 0 0 var(--space-3);
font-size: var(--font-size-sm);
color: var(--color-text-muted);
margin: 0 0 16px;
font-size: 13px;
color: var(--so-text-secondary);
font-style: italic;
}
.card__list {
margin: 0 0 var(--space-3);
margin: 0 0 16px;
padding: 0;
list-style: none;
}
.card__item {
padding: var(--space-2) 0;
border-bottom: 1px solid var(--color-border-default, rgba(212, 201, 168, 0.15));
padding: 10px 0;
border-bottom: 1px solid var(--so-border-light);
}
&:last-child {
border-bottom: none;
}
.card__item:last-child {
border-bottom: none;
}
.card__item-header {
display: flex;
justify-content: space-between;
align-items: center;
gap: var(--space-2);
gap: 10px;
}
.card__item-link {
font-weight: var(--font-weight-medium);
font-size: var(--font-size-sm);
color: var(--color-brand-primary);
font-weight: 600;
font-size: 13px;
color: var(--so-accent);
text-decoration: none;
transition: color 150ms var(--so-ease-out);
}
&:hover {
text-decoration: underline;
}
.card__item-link:hover {
color: var(--so-brand-hover);
text-decoration: underline;
}
.card__item-detail {
display: block;
font-size: var(--font-size-xs);
color: var(--color-text-muted);
margin-top: var(--space-0-5);
font-size: 12px;
color: var(--so-text-secondary);
margin-top: 4px;
}
.card__urgency {
font-size: var(--font-size-xs);
font-weight: var(--font-weight-medium);
padding: 1px var(--space-2);
border-radius: var(--radius-sm);
font-size: 10px;
font-weight: 700;
padding: 2px 8px;
border-radius: 6px;
text-transform: uppercase;
letter-spacing: 0.04em;
}
.card__urgency--high,
.card__urgency--critical {
background: var(--color-status-error-bg);
color: var(--color-status-error-text);
background: var(--so-error-soft);
color: var(--so-error);
border: 1px solid rgba(220,38,38,.2);
}
.card__urgency--normal {
background: var(--color-surface-secondary);
color: var(--color-text-secondary);
background: var(--so-surface);
color: var(--so-text-secondary);
border: 1px solid var(--so-border-light);
}
.card__urgency--low {
background: var(--color-surface-tertiary);
color: var(--color-text-muted);
background: var(--so-surface);
color: var(--so-mute);
border: 1px solid var(--so-border-light);
}
.card__dep-status {
font-size: var(--font-size-xs);
font-weight: var(--font-weight-medium);
padding: 1px var(--space-2);
border-radius: var(--radius-sm);
font-size: 10px;
font-weight: 700;
padding: 2px 8px;
border-radius: 6px;
text-transform: uppercase;
letter-spacing: 0.04em;
}
.card__dep-status--running {
background: var(--color-status-info-bg);
color: var(--color-status-info-text);
background: var(--so-info-soft);
color: var(--so-info);
border: 1px solid rgba(37,99,235,.2);
}
.card__dep-status--paused,
.card__dep-status--waiting {
background: var(--color-status-warning-bg);
color: var(--color-status-warning-text);
background: var(--so-warning-soft);
color: var(--so-warning);
border: 1px solid rgba(217,119,6,.2);
}
.card__progress {
height: 4px;
background: var(--color-surface-tertiary);
border-radius: 2px;
margin: var(--space-1) 0;
height: 5px;
background: var(--so-border-light);
border-radius: 3px;
margin: 6px 0;
overflow: hidden;
}
.card__progress-bar {
height: 100%;
background: var(--color-brand-primary);
border-radius: 2px;
transition: width var(--motion-duration-sm) var(--motion-ease-standard);
background: var(--so-gradient-cta);
border-radius: 3px;
transition: width 400ms var(--so-ease-out);
}
.card__actions {
display: flex;
gap: var(--space-2);
gap: 8px;
}
/* Table */
.table-container {
overflow-x: auto;
background: var(--color-surface-primary);
border: 1px solid var(--color-border-default, rgba(212, 201, 168, 0.3));
border-radius: var(--radius-lg);
background: var(--so-surface-elevated);
border: 1px solid var(--so-border-light);
border-radius: 16px;
box-shadow: var(--so-shadow-sm);
}
.table {
@@ -530,107 +642,137 @@ import type {
.table th,
.table td {
padding: var(--space-2) var(--space-3);
padding: 12px 18px;
text-align: left;
border-bottom: 1px solid var(--color-border-default, rgba(212, 201, 168, 0.15));
border-bottom: 1px solid var(--so-border-light);
}
.table th {
font-size: var(--font-size-xs);
font-weight: var(--font-weight-semibold);
font-size: 11px;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.05em;
color: var(--color-text-muted);
background: var(--color-surface-tertiary);
letter-spacing: 0.06em;
color: var(--so-text-secondary);
background: var(--so-surface);
}
.table th:first-child { border-radius: 16px 0 0 0; }
.table th:last-child { border-radius: 0 16px 0 0; }
.table td {
font-size: var(--font-size-sm);
color: var(--color-text-primary);
font-size: 13px;
color: var(--so-text);
}
.table tbody tr {
transition: background 150ms var(--so-ease-out);
}
.table tbody tr:hover {
background: var(--so-surface);
}
.table tbody tr:last-child td {
border-bottom: none;
}
.table a {
color: var(--color-brand-primary);
color: var(--so-accent);
text-decoration: none;
font-weight: 600;
}
&:hover {
text-decoration: underline;
}
.table a:hover {
color: var(--so-brand-hover);
text-decoration: underline;
}
/* Badges */
.badge {
display: inline-block;
padding: 2px var(--space-2);
font-size: var(--font-size-xs);
font-weight: var(--font-weight-medium);
border-radius: var(--radius-sm);
padding: 3px 10px;
font-size: 10px;
font-weight: 700;
border-radius: 6px;
text-transform: uppercase;
letter-spacing: 0.04em;
}
.badge--deployed {
background: var(--color-status-success-bg);
color: var(--color-status-success-text);
background: var(--so-success-soft);
color: var(--so-success);
border: 1px solid rgba(5,150,105,.2);
}
.badge--ready,
.badge--promoting {
background: var(--color-status-warning-bg);
color: var(--color-status-warning-text);
background: var(--so-warning-soft);
color: var(--so-warning);
border: 1px solid rgba(217,119,6,.2);
}
.badge--draft {
background: var(--color-surface-secondary);
color: var(--color-text-muted);
background: var(--so-surface);
color: var(--so-text-secondary);
border: 1px solid var(--so-border-light);
}
.badge--failed,
.badge--rolled_back {
background: var(--color-status-error-bg);
color: var(--color-status-error-text);
background: var(--so-error-soft);
color: var(--so-error);
border: 1px solid rgba(220,38,38,.2);
}
.badge--deprecated {
background: var(--color-surface-tertiary);
color: var(--color-text-muted);
background: var(--so-surface);
color: var(--so-mute);
border: 1px solid var(--so-border-light);
}
/* Buttons */
.btn {
display: inline-flex;
align-items: center;
gap: var(--space-1);
padding: var(--space-2) var(--space-4);
gap: 6px;
padding: 10px 20px;
border: none;
border-radius: var(--radius-md);
font-size: var(--font-size-sm);
font-weight: var(--font-weight-medium);
border-radius: 10px;
font-size: 13px;
font-weight: 700;
text-decoration: none;
cursor: pointer;
transition: background-color var(--motion-duration-sm) var(--motion-ease-standard);
transition: all 200ms var(--so-ease-out);
}
.btn--primary {
background: var(--color-brand-primary);
color: #fff;
background: var(--so-gradient-cta);
color: var(--so-heading);
box-shadow: var(--so-shadow-sm), var(--so-shadow-brand-md),
inset 0 1px 0 rgba(255,255,255,.2);
}
&:hover {
background: var(--color-brand-primary-hover, #c47f0f);
}
.btn--primary:hover {
background: linear-gradient(135deg, var(--so-brand-hover) 0%, var(--so-accent-muted) 100%);
box-shadow: var(--so-shadow-md), 0 6px 20px var(--so-shadow-brand);
transform: translateY(-1px);
}
.btn--secondary {
background: var(--color-surface-tertiary);
color: var(--color-text-primary);
border: 1px solid var(--color-border-default, rgba(212, 201, 168, 0.3));
background: var(--so-surface-elevated);
color: var(--so-text);
border: 1px solid var(--so-border-medium);
}
&:hover {
background: var(--color-surface-secondary);
}
.btn--secondary:hover {
background: var(--so-surface);
border-color: var(--so-brand);
}
.btn--small {
padding: var(--space-1) var(--space-2);
font-size: var(--font-size-xs);
padding: 5px 12px;
font-size: 12px;
border-radius: 8px;
}
@keyframes spin {
@@ -638,27 +780,21 @@ import type {
}
@media (prefers-reduced-motion: reduce) {
.dashboard__spinner {
animation: none;
border-top-color: transparent;
}
.btn {
transition: none;
}
.card__progress-bar {
transition: none;
}
.dashboard__spinner { animation: none; border-top-color: transparent; }
.btn, .card, .pipeline__stage, .table tbody tr { transition: none; }
.card__progress-bar { transition: none; }
.dashboard { animation: none; }
}
@media (max-width: 768px) {
.dashboard__header {
flex-direction: column;
padding: 20px;
}
.pipeline {
flex-direction: column;
padding: 16px;
}
.pipeline__arrow {

View File

@@ -1,8 +1,41 @@
// Home Dashboard Styles
// Security-focused landing page with aggregated metrics
// Security-focused landing page — Stella Ops warm amber design language
@use 'tokens/breakpoints' as *;
// ---------------------------------------------------------------------------
// Entrance animation
// ---------------------------------------------------------------------------
@keyframes dash-in {
from {
opacity: 0;
transform: translateY(12px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
@keyframes spin {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
@keyframes skeleton-shimmer {
0% { background-position: 200% 0; }
100% { background-position: -200% 0; }
}
@keyframes score-draw {
from { stroke-dashoffset: 283; }
}
// ---------------------------------------------------------------------------
// Container
// ---------------------------------------------------------------------------
.dashboard {
max-width: var(--container-xl);
margin: 0 auto;
@@ -20,13 +53,15 @@
margin-bottom: var(--space-6);
flex-wrap: wrap;
gap: var(--space-4);
animation: dash-in 0.5s cubic-bezier(0.22, 1, 0.36, 1) both;
}
.dashboard__title {
margin: 0;
font-size: var(--font-size-2xl);
font-weight: var(--font-weight-semibold);
color: var(--color-text-primary);
font-size: 26px;
font-weight: 800;
color: var(--color-text-heading);
letter-spacing: -0.01em;
}
.dashboard__actions {
@@ -38,6 +73,7 @@
.dashboard__updated {
font-size: var(--font-size-sm);
color: var(--color-text-muted);
font-weight: var(--font-weight-medium);
}
.dashboard__refresh {
@@ -46,18 +82,19 @@
gap: var(--space-1-5);
padding: var(--space-2) var(--space-3-5);
font-size: var(--font-size-sm);
font-weight: var(--font-weight-medium);
color: var(--color-text-primary);
background-color: var(--color-surface-tertiary);
border: 1px solid var(--color-border-primary);
font-weight: 600;
color: var(--color-text-heading);
background: linear-gradient(135deg, #FEF3E2, #FFFCF5);
border: 1px solid hsla(45, 34%, 75%, 0.5);
border-radius: var(--radius-md);
cursor: pointer;
transition: background-color var(--motion-duration-fast) var(--motion-ease-default),
border-color var(--motion-duration-fast) var(--motion-ease-default);
transition: all 0.2s cubic-bezier(0.22, 1, 0.36, 1);
&:hover:not(:disabled) {
background-color: var(--color-surface-secondary);
border-color: var(--color-border-secondary);
background: linear-gradient(135deg, #FDE8C8, #FEF3E2);
border-color: rgba(245, 166, 35, 0.4);
box-shadow: 0 2px 8px rgba(245, 166, 35, 0.15);
transform: translateY(-1px);
}
&:disabled {
@@ -70,26 +107,23 @@
}
}
@keyframes spin {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
// =============================================================================
// Errors
// =============================================================================
.dashboard__errors {
margin-bottom: var(--space-4);
animation: dash-in 0.5s 0.1s cubic-bezier(0.22, 1, 0.36, 1) both;
}
.dashboard__error {
padding: var(--space-3) var(--space-4);
font-size: var(--font-size-base);
color: var(--color-severity-high);
background-color: var(--color-status-warning-bg);
border: 1px solid var(--color-status-warning-border);
border-radius: var(--radius-md);
font-weight: var(--font-weight-medium);
color: #8B5E14;
background-color: #FFF5E6;
border: 1px solid #F5A623;
border-radius: 10px;
margin-bottom: var(--space-2);
}
@@ -113,20 +147,42 @@
// =============================================================================
.card {
background-color: var(--color-surface-primary);
border: 1px solid var(--color-border-primary);
border-radius: var(--radius-lg);
position: relative;
background-color: #fff;
border: 1px solid hsla(45, 34%, 75%, 0.3);
border-radius: 16px;
overflow: hidden;
transition:
transform var(--motion-duration-normal) var(--motion-ease-default),
box-shadow var(--motion-duration-normal) var(--motion-ease-default),
border-color var(--motion-duration-normal) var(--motion-ease-default);
transition: all 0.3s cubic-bezier(0.22, 1, 0.36, 1);
animation: dash-in 0.5s cubic-bezier(0.22, 1, 0.36, 1) both;
// Gradient accent bar at top
&::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
height: 3px;
background: linear-gradient(90deg, #F5A623, #D4920A);
opacity: 0;
transition: opacity 0.3s ease;
}
&:hover {
transform: translateY(-2px);
box-shadow: var(--shadow-lg);
border-color: var(--color-border-secondary);
transform: translateY(-3px);
box-shadow: 0 8px 24px rgba(212, 146, 10, 0.1), 0 2px 8px rgba(0, 0, 0, 0.04);
border-color: hsla(45, 34%, 75%, 0.5);
&::before {
opacity: 1;
}
}
// Stagger animation for each card
&:nth-child(1) { animation-delay: 0.1s; }
&:nth-child(2) { animation-delay: 0.2s; }
&:nth-child(3) { animation-delay: 0.3s; }
&:nth-child(4) { animation-delay: 0.4s; }
}
.card__header {
@@ -134,26 +190,29 @@
align-items: center;
justify-content: space-between;
padding: var(--space-4) var(--space-5);
border-bottom: 1px solid var(--color-border-primary);
border-bottom: 1px solid hsla(45, 34%, 75%, 0.2);
background: linear-gradient(135deg, rgba(254, 243, 226, 0.3), transparent);
}
.card__title {
margin: 0;
font-size: var(--font-size-base);
font-weight: var(--font-weight-semibold);
color: var(--color-text-primary);
font-size: 15px;
font-weight: 700;
color: var(--color-text-heading);
letter-spacing: -0.01em;
}
.card__link {
font-size: var(--font-size-sm);
font-weight: var(--font-weight-medium);
color: var(--color-brand-primary);
font-weight: 600;
color: #D4920A;
text-decoration: none;
transition: color var(--motion-duration-fast) var(--motion-ease-default);
transition: all 0.2s ease;
letter-spacing: 0.01em;
&:hover {
text-decoration: underline;
color: var(--color-brand-primary-hover);
color: #F5A623;
}
}
@@ -196,20 +255,22 @@
align-items: center;
margin-top: var(--space-4);
padding-top: var(--space-4);
border-top: 1px solid var(--color-border-primary);
border-top: 1px solid hsla(45, 34%, 75%, 0.2);
}
.card__stat-value {
font-size: var(--font-size-3xl);
font-weight: var(--font-weight-bold);
color: var(--color-text-primary);
font-size: 32px;
font-weight: 800;
color: var(--color-text-heading);
}
.card__stat-label {
font-size: var(--font-size-sm);
color: var(--color-text-muted);
font-size: 11px;
font-weight: 600;
color: #6B5A2E;
text-transform: uppercase;
letter-spacing: 0.05em;
letter-spacing: 0.08em;
margin-top: 2px;
}
// =============================================================================
@@ -219,13 +280,13 @@
.skeleton {
background: linear-gradient(
90deg,
var(--color-surface-tertiary) 25%,
var(--color-surface-secondary) 50%,
var(--color-surface-tertiary) 75%
hsla(45, 34%, 75%, 0.15) 25%,
hsla(45, 34%, 75%, 0.05) 50%,
hsla(45, 34%, 75%, 0.15) 75%
);
background-size: 200% 100%;
animation: skeleton-shimmer 1.5s ease-in-out infinite;
border-radius: var(--radius-sm);
border-radius: 8px;
&--bar {
height: var(--space-6);
@@ -245,11 +306,6 @@
}
}
@keyframes skeleton-shimmer {
0% { background-position: 200% 0; }
100% { background-position: -200% 0; }
}
// =============================================================================
// Severity Bars
// =============================================================================
@@ -257,7 +313,7 @@
.severity-bars {
display: flex;
flex-direction: column;
gap: var(--space-2-5);
gap: 10px;
}
.severity-bar {
@@ -268,33 +324,41 @@
}
.severity-bar__label {
font-size: var(--font-size-sm);
font-weight: var(--font-weight-medium);
color: var(--color-text-secondary);
font-size: 13px;
font-weight: 600;
color: #6B5A2E;
}
.severity-bar__track {
height: 8px;
background-color: var(--color-surface-tertiary);
border-radius: var(--radius-sm);
height: 10px;
background-color: hsla(45, 34%, 75%, 0.15);
border-radius: 5px;
overflow: hidden;
}
.severity-bar__fill {
height: 100%;
border-radius: var(--radius-sm);
transition: width 300ms ease;
border-radius: 5px;
transition: width 600ms cubic-bezier(0.22, 1, 0.36, 1);
}
.severity-bar--critical .severity-bar__fill { background-color: var(--color-severity-critical); }
.severity-bar--high .severity-bar__fill { background-color: var(--color-severity-high); }
.severity-bar--medium .severity-bar__fill { background-color: var(--color-severity-medium); }
.severity-bar--low .severity-bar__fill { background-color: var(--color-severity-low); }
.severity-bar--critical .severity-bar__fill {
background: linear-gradient(90deg, var(--color-severity-critical), #ff6b6b);
}
.severity-bar--high .severity-bar__fill {
background: linear-gradient(90deg, #D4920A, #F5A623);
}
.severity-bar--medium .severity-bar__fill {
background: linear-gradient(90deg, var(--color-severity-medium), #fbbf24);
}
.severity-bar--low .severity-bar__fill {
background: linear-gradient(90deg, var(--color-severity-low), #6ee7b7);
}
.severity-bar__count {
font-size: var(--font-size-base);
font-weight: var(--font-weight-semibold);
color: var(--color-text-primary);
font-size: 14px;
font-weight: 700;
color: var(--color-text-heading);
text-align: right;
}
@@ -311,8 +375,8 @@
.risk-score__circle {
position: relative;
width: 120px;
height: 120px;
width: 130px;
height: 130px;
svg {
transform: rotate(-90deg);
@@ -321,7 +385,7 @@
.risk-score__bg {
fill: none;
stroke: var(--color-surface-tertiary);
stroke: hsla(45, 34%, 75%, 0.2);
stroke-width: 8;
}
@@ -330,7 +394,9 @@
stroke-width: 8;
stroke-linecap: round;
stroke-dasharray: 283;
transition: stroke-dashoffset 500ms ease;
transition: stroke-dashoffset 800ms cubic-bezier(0.22, 1, 0.36, 1);
animation: score-draw 1s cubic-bezier(0.22, 1, 0.36, 1) both;
filter: drop-shadow(0 1px 3px rgba(0, 0, 0, 0.1));
}
.risk-score__value {
@@ -339,30 +405,31 @@
display: flex;
align-items: center;
justify-content: center;
font-size: var(--font-size-3xl);
font-weight: var(--font-weight-bold);
color: var(--color-text-primary);
font-size: 30px;
font-weight: 800;
color: var(--color-text-heading);
}
.risk-score__trend {
display: inline-flex;
align-items: center;
gap: var(--space-1);
padding: var(--space-1) var(--space-2-5);
font-size: var(--font-size-sm);
font-weight: var(--font-weight-semibold);
padding: 4px 12px;
font-size: 12px;
font-weight: 700;
border-radius: var(--radius-full);
background-color: var(--color-surface-tertiary);
color: var(--color-text-secondary);
background-color: hsla(45, 34%, 75%, 0.15);
color: #6B5A2E;
letter-spacing: 0.02em;
&--improving {
background-color: var(--color-status-success-bg);
color: var(--color-severity-low);
background-color: #D1FAE5;
color: #059669;
}
&--worsening {
background-color: var(--color-status-warning-bg);
color: var(--color-severity-high);
background-color: #FFF5E6;
color: #D4920A;
}
}
@@ -371,31 +438,31 @@
gap: var(--space-6);
margin-top: var(--space-4);
padding-top: var(--space-4);
border-top: 1px solid var(--color-border-primary);
border-top: 1px solid hsla(45, 34%, 75%, 0.2);
}
.risk-count {
display: flex;
flex-direction: column;
align-items: center;
gap: var(--space-0-5);
gap: 2px;
}
.risk-count__value {
font-size: var(--font-size-xl);
font-weight: var(--font-weight-bold);
font-size: 20px;
font-weight: 800;
}
.risk-count__label {
font-size: var(--font-size-xs);
font-weight: var(--font-weight-medium);
font-size: 10px;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.05em;
color: var(--color-text-muted);
letter-spacing: 0.08em;
color: #6B5A2E;
}
.risk-count--critical .risk-count__value { color: var(--color-severity-critical); }
.risk-count--high .risk-count__value { color: var(--color-severity-high); }
.risk-count--high .risk-count__value { color: #D4920A; }
.risk-count--medium .risk-count__value { color: var(--color-severity-medium); }
// =============================================================================
@@ -415,24 +482,26 @@
.reachability-donut__bg {
fill: none;
stroke: var(--color-surface-tertiary);
stroke: hsla(45, 34%, 75%, 0.15);
stroke-width: 12;
}
.reachability-donut__reachable {
fill: none;
stroke: var(--color-severity-high);
stroke: #D4920A;
stroke-width: 12;
stroke-linecap: round;
transition: stroke-dasharray 500ms ease;
transition: stroke-dasharray 600ms cubic-bezier(0.22, 1, 0.36, 1);
filter: drop-shadow(0 1px 2px rgba(212, 146, 10, 0.3));
}
.reachability-donut__unreachable {
fill: none;
stroke: var(--color-severity-low);
stroke: #059669;
stroke-width: 12;
stroke-linecap: round;
transition: stroke-dasharray 500ms ease, stroke-dashoffset 500ms ease;
transition: stroke-dasharray 600ms cubic-bezier(0.22, 1, 0.36, 1),
stroke-dashoffset 600ms cubic-bezier(0.22, 1, 0.36, 1);
}
.reachability-donut__center {
@@ -445,23 +514,23 @@
}
.reachability-donut__value {
font-size: var(--font-size-2xl);
font-weight: var(--font-weight-bold);
color: var(--color-text-primary);
font-size: 26px;
font-weight: 800;
color: var(--color-text-heading);
}
.reachability-donut__label {
font-size: var(--font-size-xs);
font-weight: var(--font-weight-medium);
font-size: 10px;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.05em;
color: var(--color-text-muted);
letter-spacing: 0.08em;
color: #6B5A2E;
}
.reachability-legend {
display: flex;
flex-direction: column;
gap: var(--space-2);
gap: 8px;
}
.reachability-legend__item {
@@ -474,22 +543,33 @@
width: 10px;
height: 10px;
border-radius: 50%;
flex-shrink: 0;
}
.reachability-legend__item--reachable .reachability-legend__dot { background-color: var(--color-severity-high); }
.reachability-legend__item--unreachable .reachability-legend__dot { background-color: var(--color-severity-low); }
.reachability-legend__item--uncertain .reachability-legend__dot { background-color: var(--color-text-muted); }
.reachability-legend__item--reachable .reachability-legend__dot {
background-color: #D4920A;
box-shadow: 0 0 0 2px rgba(212, 146, 10, 0.2);
}
.reachability-legend__item--unreachable .reachability-legend__dot {
background-color: #059669;
box-shadow: 0 0 0 2px rgba(5, 150, 105, 0.2);
}
.reachability-legend__item--uncertain .reachability-legend__dot {
background-color: #6B5A2E;
box-shadow: 0 0 0 2px rgba(107, 90, 46, 0.15);
}
.reachability-legend__label {
flex: 1;
font-size: var(--font-size-sm);
color: var(--color-text-secondary);
font-size: 13px;
font-weight: 500;
color: #6B5A2E;
}
.reachability-legend__value {
font-size: var(--font-size-base);
font-weight: var(--font-weight-semibold);
color: var(--color-text-primary);
font-size: 14px;
font-weight: 700;
color: var(--color-text-heading);
}
// =============================================================================
@@ -508,27 +588,38 @@
flex-direction: column;
align-items: center;
text-align: center;
padding: 12px 8px;
border-radius: 12px;
background: linear-gradient(135deg, rgba(254, 243, 226, 0.3), transparent);
border: 1px solid hsla(45, 34%, 75%, 0.15);
transition: all 0.2s ease;
&:hover {
background: linear-gradient(135deg, rgba(254, 243, 226, 0.5), transparent);
border-color: hsla(45, 34%, 75%, 0.3);
}
}
.vex-stat__value {
font-size: var(--font-size-2xl);
font-weight: var(--font-weight-bold);
font-size: 24px;
font-weight: 800;
}
.vex-stat--suppressed .vex-stat__value { color: var(--color-severity-low); }
.vex-stat--active .vex-stat__value { color: var(--color-severity-high); }
.vex-stat--suppressed .vex-stat__value { color: #059669; }
.vex-stat--active .vex-stat__value { color: #D4920A; }
.vex-stat--investigating .vex-stat__value { color: var(--color-severity-medium); }
.vex-stat__label {
font-size: var(--font-size-sm);
font-weight: var(--font-weight-semibold);
color: var(--color-text-primary);
margin-top: var(--space-1);
font-size: 12px;
font-weight: 700;
color: var(--color-text-heading);
margin-top: 4px;
}
.vex-stat__sublabel {
font-size: var(--font-size-xs);
color: var(--color-text-muted);
font-size: 11px;
font-weight: 500;
color: #6B5A2E;
}
.vex-impact {
@@ -536,20 +627,24 @@
flex-direction: column;
align-items: center;
padding-top: var(--space-4);
border-top: 1px solid var(--color-border-primary);
border-top: 1px solid hsla(45, 34%, 75%, 0.2);
}
.vex-impact__percent {
font-size: var(--font-size-3xl);
font-weight: var(--font-weight-bold);
color: var(--color-severity-low);
font-size: 32px;
font-weight: 800;
background: linear-gradient(135deg, #059669, #34d399);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
.vex-impact__label {
font-size: var(--font-size-sm);
color: var(--color-text-muted);
font-size: 11px;
font-weight: 600;
color: #6B5A2E;
text-transform: uppercase;
letter-spacing: 0.05em;
letter-spacing: 0.08em;
}
// =============================================================================
@@ -558,13 +653,15 @@
.quick-actions {
margin-bottom: var(--space-8);
animation: dash-in 0.5s 0.5s cubic-bezier(0.22, 1, 0.36, 1) both;
}
.quick-actions__title {
font-size: var(--font-size-md);
font-weight: var(--font-weight-semibold);
color: var(--color-text-primary);
font-size: 18px;
font-weight: 700;
color: var(--color-text-heading);
margin: 0 0 var(--space-4);
letter-spacing: -0.01em;
}
.quick-actions__grid {
@@ -588,24 +685,30 @@
gap: var(--space-3);
padding: var(--space-5);
text-decoration: none;
background-color: var(--color-surface-primary);
border: 1px solid var(--color-border-primary);
border-radius: var(--radius-lg);
color: var(--color-text-secondary);
transition: background-color var(--motion-duration-fast) var(--motion-ease-default),
border-color var(--motion-duration-fast) var(--motion-ease-default),
color var(--motion-duration-fast) var(--motion-ease-default),
transform var(--motion-duration-fast) var(--motion-ease-default);
background: #fff;
border: 1px solid hsla(45, 34%, 75%, 0.3);
border-radius: 14px;
color: #6B5A2E;
transition: all 0.25s cubic-bezier(0.22, 1, 0.36, 1);
&:hover {
background-color: var(--color-surface-secondary);
border-color: var(--color-brand-primary);
color: var(--color-brand-primary);
transform: translateY(-2px);
background: linear-gradient(135deg, #FEF3E2, #FFFCF5);
border-color: rgba(245, 166, 35, 0.4);
color: #D4920A;
transform: translateY(-3px);
box-shadow: 0 6px 20px rgba(212, 146, 10, 0.12), 0 2px 6px rgba(0, 0, 0, 0.03);
}
svg {
transition: transform 0.25s cubic-bezier(0.34, 1.56, 0.64, 1);
}
&:hover svg {
transform: scale(1.15);
}
span {
font-size: var(--font-size-base);
font-weight: var(--font-weight-medium);
font-size: 14px;
font-weight: 600;
}
}

View File

@@ -6,6 +6,7 @@
/** Setup wizard step identifiers */
export type SetupStepId =
| 'welcome'
| 'database'
| 'cache'
| 'migrations'
@@ -25,6 +26,7 @@ export type SetupStepId =
/** Setup step categories */
export type SetupCategory =
| 'Welcome'
| 'Infrastructure'
| 'Security'
| 'Integration'
@@ -981,6 +983,19 @@ export const SOURCES_PROVIDERS: ProviderInfo[] = [
/** Default step definitions - Infrastructure-First order for CLI/UI parity */
export const DEFAULT_SETUP_STEPS: SetupStep[] = [
// Phase 0: Welcome
{
id: 'welcome',
name: 'Welcome',
description: 'Welcome to Stella Ops — let\u2019s get your platform configured.',
category: 'Welcome',
order: 0,
isRequired: false,
isSkippable: false,
dependencies: [],
validationChecks: [],
status: 'pending',
},
// Phase 1: Core Infrastructure (Required)
{
id: 'database',
@@ -1063,7 +1078,7 @@ export const DEFAULT_SETUP_STEPS: SetupStep[] = [
name: 'Secrets Vault',
description: 'Configure a secrets vault for secure credential storage (HashiCorp Vault, Azure Key Vault, AWS Secrets Manager, or GCP Secret Manager).',
category: 'Integration',
order: 60,
order: 125,
isRequired: false,
isSkippable: true,
dependencies: [],
@@ -1107,9 +1122,9 @@ export const DEFAULT_SETUP_STEPS: SetupStep[] = [
{
id: 'sources',
name: 'Advisory Data Sources',
description: 'Configure CVE/VEX advisory feeds (NVD, GHSA, OSV, distribution-specific feeds) for vulnerability data.',
description: 'Choose Stella Ops Mirror for pre-aggregated feeds or configure custom advisory sources for CVE/VEX vulnerability data.',
category: 'Release Control Plane',
order: 90,
order: 65,
isRequired: false,
isSkippable: true,
dependencies: [],

View File

@@ -293,10 +293,11 @@ export class SetupWizardStateService {
return;
}
// Can only go forward if all previous steps are completed/skipped
// Can only go forward if all previous required steps are completed/skipped
// (optional/skippable steps in pending state do not block forward navigation)
const canNavigate = this.orderedSteps()
.slice(0, stepIndex)
.every(s => s.status === 'completed' || s.status === 'skipped');
.every(s => s.status === 'completed' || s.status === 'skipped' || s.isSkippable);
if (canNavigate) {
this.currentStepId.set(stepId);
@@ -316,14 +317,21 @@ export class SetupWizardStateService {
}
/**
* Navigate to previous step
* Navigate to previous step (skips the welcome step)
*/
goToPreviousStep(): void {
const ordered = this.orderedSteps();
const currentIndex = this.currentStepIndex();
if (currentIndex > 0) {
this.currentStepId.set(ordered[currentIndex - 1].id);
// Skip back past the welcome step — don't return to it
let targetIndex = currentIndex - 1;
if (ordered[targetIndex]?.id === 'welcome') {
targetIndex--;
}
if (targetIndex >= 0) {
this.currentStepId.set(ordered[targetIndex].id);
}
}
}
@@ -557,7 +565,7 @@ export class SetupWizardStateService {
const step = this.currentStep();
if (!step) return false;
// Can proceed if step is completed or skipped
return step.status === 'completed' || step.status === 'skipped';
// Can proceed if step is completed, skipped, or optional (skippable) and still pending
return step.status === 'completed' || step.status === 'skipped' || step.isSkippable;
}
}

View File

@@ -81,7 +81,7 @@ import { OverlayHostComponent } from '../overlay-host/overlay-host.component';
grid-template-columns: var(--sidebar-width, 200px) 1fr;
grid-template-rows: 1fr;
min-height: 100vh;
background: var(--color-surface-secondary);
background: #FFF9ED;
}
.shell--sidebar-collapsed {