diff --git a/src/Web/StellaOps.Web/src/app/app.routes.ts b/src/Web/StellaOps.Web/src/app/app.routes.ts
index 1c6bcd8b9..065a10145 100644
--- a/src/Web/StellaOps.Web/src/app/app.routes.ts
+++ b/src/Web/StellaOps.Web/src/app/app.routes.ts
@@ -198,10 +198,49 @@ export const routes: Routes = [
},
{
path: 'administration',
- title: 'Administration',
- canMatch: [requireConfigGuard, requireBackendsReachableGuard, requireAuthGuard, requireSetupGuard],
- data: { breadcrumb: 'Administration' },
- loadChildren: () => import('./routes/administration.routes').then((m) => m.ADMINISTRATION_ROUTES),
+ children: [
+ { path: '', redirectTo: '/setup', pathMatch: 'full' },
+ { path: 'identity-access', redirectTo: preserveAppRedirect('/setup/identity-access'), pathMatch: 'full' },
+ { path: 'identity-access/:page', redirectTo: preserveAppRedirect('/setup/identity-access'), pathMatch: 'full' },
+ { path: 'profile', redirectTo: '/console/profile', pathMatch: 'full' },
+ { path: 'admin', redirectTo: preserveAppRedirect('/setup/identity-access'), pathMatch: 'full' },
+ { path: 'admin/:page', redirectTo: preserveAppRedirect('/setup/identity-access'), pathMatch: 'full' },
+ { path: 'tenant-branding', redirectTo: preserveAppRedirect('/setup/tenant-branding'), pathMatch: 'full' },
+ { path: 'notifications', redirectTo: preserveAppRedirect('/setup/notifications'), pathMatch: 'full' },
+ { path: 'usage', redirectTo: preserveAppRedirect('/setup/usage'), pathMatch: 'full' },
+ { path: 'policy-governance', redirectTo: preserveAppRedirect('/ops/policy/governance'), pathMatch: 'full' },
+ { path: 'policy-governance/exceptions', redirectTo: preserveAppRedirect('/ops/policy/vex/exceptions'), pathMatch: 'full' },
+ { path: 'policy-governance/exceptions/:id', redirectTo: preserveAppRedirect('/ops/policy/vex/exceptions/:id'), pathMatch: 'full' },
+ { path: 'policy-governance/:page', redirectTo: preserveAppRedirect('/ops/policy/governance/:page'), pathMatch: 'full' },
+ { path: 'policy-governance/:page/:child', redirectTo: preserveAppRedirect('/ops/policy/governance/:page/:child'), pathMatch: 'full' },
+ { path: 'policy', redirectTo: preserveAppRedirect('/ops/policy/governance'), pathMatch: 'full' },
+ { path: 'policy/packs', redirectTo: preserveAppRedirect('/ops/policy/packs'), pathMatch: 'full' },
+ { path: 'policy/exceptions', redirectTo: preserveAppRedirect('/ops/policy/vex/exceptions'), pathMatch: 'full' },
+ { path: 'policy/exceptions/:id', redirectTo: preserveAppRedirect('/ops/policy/vex/exceptions/:id'), pathMatch: 'full' },
+ { path: 'policy/packs/:packId', redirectTo: preserveAppRedirect('/ops/policy/packs/:packId'), pathMatch: 'full' },
+ { path: 'policy/packs/:packId/:page', redirectTo: preserveAppRedirect('/ops/policy/packs/:packId/:page'), pathMatch: 'full' },
+ { path: 'policy/packs/:packId/explain/:runId', redirectTo: preserveAppRedirect('/ops/policy/packs/:packId/explain/:runId'), pathMatch: 'full' },
+ { path: 'policy/governance', redirectTo: preserveAppRedirect('/ops/policy/governance'), pathMatch: 'full' },
+ { path: 'policy/governance/:page', redirectTo: preserveAppRedirect('/ops/policy/governance/:page'), pathMatch: 'full' },
+ { path: 'policy/governance/:page/:child', redirectTo: preserveAppRedirect('/ops/policy/governance/:page/:child'), pathMatch: 'full' },
+ { path: 'audit', redirectTo: preserveAppRedirect('/evidence/audit-log'), pathMatch: 'full' },
+ { path: 'audit/:page', redirectTo: preserveAppRedirect('/evidence/audit-log/:page'), pathMatch: 'full' },
+ { path: 'audit/:page/:child', redirectTo: preserveAppRedirect('/evidence/audit-log/:page/:child'), pathMatch: 'full' },
+ { path: 'trust-signing', redirectTo: preserveAppRedirect('/setup/trust-signing'), pathMatch: 'full' },
+ { path: 'trust', redirectTo: preserveAppRedirect('/setup/trust-signing'), pathMatch: 'full' },
+ { path: 'trust/issuers', redirectTo: preserveAppRedirect('/setup/trust-signing/issuers'), pathMatch: 'full' },
+ { path: 'trust/:page/:child', redirectTo: preserveAppRedirect('/setup/trust-signing/:page/:child'), pathMatch: 'full' },
+ { path: 'trust/:page', redirectTo: preserveAppRedirect('/setup/trust-signing/:page'), pathMatch: 'full' },
+ { path: 'identity-providers', redirectTo: preserveAppRedirect('/setup/identity-providers'), pathMatch: 'full' },
+ { path: 'system', redirectTo: preserveAppRedirect('/setup/system'), pathMatch: 'full' },
+ { path: 'offline', redirectTo: preserveAppRedirect('/setup/offline'), pathMatch: 'full' },
+ { path: 'configuration-pane', redirectTo: preserveAppRedirect('/setup/configuration-pane'), pathMatch: 'full' },
+ { path: 'security-data', redirectTo: preserveAppRedirect('/setup/security-data'), pathMatch: 'full' },
+ { path: 'security-data/trivy', redirectTo: preserveAppRedirect('/setup/security-data/trivy'), pathMatch: 'full' },
+ { path: 'workflows', redirectTo: preserveAppRedirect('/setup/workflows'), pathMatch: 'full' },
+ { path: 'ai-preferences', redirectTo: preserveAppRedirect('/setup/ai-preferences'), pathMatch: 'full' },
+ { path: '**', redirectTo: '/setup' },
+ ],
},
{
path: 'console-admin',
@@ -213,7 +252,7 @@ export const routes: Routes = [
{
path: 'admin',
children: [
- { path: '', redirectTo: '/administration', pathMatch: 'full' },
+ { path: '', redirectTo: '/setup', pathMatch: 'full' },
{ path: 'notifications', redirectTo: preserveAppRedirect('/setup/notifications'), pathMatch: 'full' },
{ path: 'notifications/:page', redirectTo: preserveAppRedirect('/setup/notifications/:page'), pathMatch: 'full' },
{ path: 'trust', redirectTo: preserveAppRedirect('/setup/trust-signing'), pathMatch: 'full' },
@@ -230,7 +269,7 @@ export const routes: Routes = [
pathMatch: 'full',
},
{ path: 'registries', redirectTo: preserveAppRedirect('/setup/integrations'), pathMatch: 'full' },
- { path: '**', redirectTo: '/administration' },
+ { path: '**', redirectTo: '/setup' },
],
},
{
diff --git a/src/Web/StellaOps.Web/src/app/core/navigation/navigation.config.ts b/src/Web/StellaOps.Web/src/app/core/navigation/navigation.config.ts
index 48018a2d1..7b82f8f4c 100644
--- a/src/Web/StellaOps.Web/src/app/core/navigation/navigation.config.ts
+++ b/src/Web/StellaOps.Web/src/app/core/navigation/navigation.config.ts
@@ -637,7 +637,7 @@ export const NAVIGATION_GROUPS: NavGroup[] = [
{
id: 'identity-providers',
label: 'Identity Providers',
- route: '/administration/identity-providers',
+ route: '/setup/identity-providers',
icon: 'id-card',
requiredScopes: ['ui.admin'],
tooltip: 'Configure external identity providers (LDAP, SAML, OIDC)',
diff --git a/src/Web/StellaOps.Web/src/app/features/evidence-pack/evidence-pack-list.component.ts b/src/Web/StellaOps.Web/src/app/features/evidence-pack/evidence-pack-list.component.ts
index cb0f68dbf..6a0748695 100644
--- a/src/Web/StellaOps.Web/src/app/features/evidence-pack/evidence-pack-list.component.ts
+++ b/src/Web/StellaOps.Web/src/app/features/evidence-pack/evidence-pack-list.component.ts
@@ -17,7 +17,7 @@ import {
} from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms';
-import { Router } from '@angular/router';
+import { Router, RouterModule } from '@angular/router';
import {
EvidencePackSummary,
EvidencePackQuery,
@@ -28,7 +28,7 @@ import { FilterBarComponent, FilterOption, ActiveFilter } from '../../shared/ui/
@Component({
selector: 'stellaops-evidence-pack-list',
- imports: [CommonModule, FormsModule, ErrorStateComponent, FilterBarComponent],
+ imports: [CommonModule, FormsModule, RouterModule, ErrorStateComponent, FilterBarComponent],
template: `
@@ -71,7 +71,9 @@ import { FilterBarComponent, FilterOption, ActiveFilter } from '../../shared/ui/
-
No decision capsules found
+
No decision capsules found
+
Decision capsules are created when releases are sealed. Create your first release to generate evidence.
+
Create a Release →
} @else {
@@ -212,6 +214,37 @@ import { FilterBarComponent, FilterOption, ActiveFilter } from '../../shared/ui/
margin-bottom: 1rem;
}
+ .empty-title {
+ margin: 0 0 0.5rem;
+ font-size: 1rem;
+ font-weight: var(--font-weight-semibold);
+ color: var(--color-text-primary);
+ }
+
+ .empty-guidance {
+ margin: 0 0 1rem;
+ font-size: 0.8rem;
+ color: var(--color-text-secondary);
+ max-width: 36ch;
+ }
+
+ .empty-action-link {
+ display: inline-block;
+ padding: 0.5rem 1rem;
+ font-size: 0.8rem;
+ font-weight: var(--font-weight-medium);
+ color: var(--color-brand-primary);
+ border: 1px solid var(--color-brand-primary);
+ border-radius: var(--radius-sm);
+ text-decoration: none;
+ transition: background 0.15s ease, color 0.15s ease;
+ }
+
+ .empty-action-link:hover {
+ background: var(--color-brand-primary);
+ color: #fff;
+ }
+
.retry-btn {
padding: 0.5rem 1rem;
border: 1px solid var(--color-border-primary);
diff --git a/src/Web/StellaOps.Web/src/app/features/platform-ops/data-integrity/data-quality-slos-page.component.ts b/src/Web/StellaOps.Web/src/app/features/platform-ops/data-integrity/data-quality-slos-page.component.ts
index d9dc095ee..6f179fdd6 100644
--- a/src/Web/StellaOps.Web/src/app/features/platform-ops/data-integrity/data-quality-slos-page.component.ts
+++ b/src/Web/StellaOps.Web/src/app/features/platform-ops/data-integrity/data-quality-slos-page.component.ts
@@ -48,7 +48,7 @@ interface SloRow {
diff --git a/src/Web/StellaOps.Web/src/app/features/platform/setup/topology-wizard/topology-wizard.component.ts b/src/Web/StellaOps.Web/src/app/features/platform/setup/topology-wizard/topology-wizard.component.ts
index 8b75b901d..3632ce94a 100644
--- a/src/Web/StellaOps.Web/src/app/features/platform/setup/topology-wizard/topology-wizard.component.ts
+++ b/src/Web/StellaOps.Web/src/app/features/platform/setup/topology-wizard/topology-wizard.component.ts
@@ -367,9 +367,24 @@ import {
}
+
+ @if (!wizard.selectedAgent() && !wizard.agentSkipped()) {
+
+
+
+ }
} @else {
-
-
No agents found. You can onboard an agent first.
+
+
No agents deployed yet. You can:
+
+ - Skip this step and assign an agent later from the Topology page
+ - Deploy an agent using:
stella agent install --target {{ wizard.createdTarget()?.id ?? '<targetId>' }} --host <ssh-host>
+
+
Onboard Agent
@@ -382,6 +397,13 @@ import {
Agent "{{ wizard.selectedAgent()?.displayName }}" selected.
}
+
+ @if (wizard.agentSkipped()) {
+
+
+ Agent assignment skipped. You can assign an agent later from the Topology page.
+
+ }
}
@@ -557,7 +579,7 @@ import {
-
Agent: {{ wizard.selectedAgent()?.displayName ?? 'N/A' }}
+
Agent: {{ wizard.selectedAgent()?.displayName ?? (wizard.agentSkipped() ? 'None (skipped)' : 'N/A') }}
@@ -595,7 +617,7 @@ import {
[disabled]="!wizard.canGoNext()"
(click)="onNext()"
>
- {{ wizard.currentStep() === 'validate' ? 'Finish' : 'Next' }}
+ {{ wizard.currentStep() === 'validate' ? 'Finish' : (wizard.currentStep() === 'agent' && wizard.agentSkipped() && !wizard.selectedAgent() ? 'Next (without agent)' : 'Next') }}
}
@@ -826,6 +848,74 @@ import {
}
}
+ .skip-agent-link {
+ margin-top: 1rem;
+ text-align: center;
+ }
+
+ .btn-link {
+ background: none;
+ border: none;
+ color: var(--color-brand-primary);
+ cursor: pointer;
+ font-size: 0.82rem;
+ text-decoration: underline;
+ padding: 0;
+
+ &:hover {
+ color: var(--color-brand-primary-hover);
+ }
+ }
+
+ .no-agents-guidance {
+ padding: 1.5rem;
+ background: var(--color-surface-secondary);
+ border: 1px solid var(--color-border-primary);
+ border-radius: var(--radius-md);
+ font-size: 0.82rem;
+ color: var(--color-text-secondary);
+
+ p {
+ margin: 0 0 0.75rem;
+ font-weight: var(--font-weight-medium);
+ color: var(--color-text-primary);
+ }
+
+ ul {
+ margin: 0 0 1rem;
+ padding-left: 1.25rem;
+ }
+
+ li {
+ margin-bottom: 0.4rem;
+ }
+
+ code {
+ font-family: ui-monospace, monospace;
+ font-size: 0.75rem;
+ background: var(--color-surface-tertiary);
+ padding: 0.1rem 0.35rem;
+ border-radius: var(--radius-sm);
+ }
+
+ .btn {
+ margin-right: 0.5rem;
+ }
+ }
+
+ .skip-notice {
+ display: flex;
+ align-items: center;
+ gap: 0.4rem;
+ margin-top: 1rem;
+ padding: 0.5rem 0.75rem;
+ border-radius: var(--radius-md);
+ background: var(--color-status-warning-bg, rgba(245, 158, 11, 0.1));
+ color: var(--color-status-warning, #f59e0b);
+ font-size: 0.78rem;
+ border: 1px solid rgba(245, 158, 11, 0.3);
+ }
+
/* Option List / Cards */
.option-list {
display: grid;
@@ -1424,7 +1514,13 @@ export class TopologyWizardComponent implements OnInit, OnDestroy {
}
// --- Agent ---
+ skipAgent(): void {
+ this.wizard.agentSkipped.set(true);
+ this.wizard.selectedAgent.set(null);
+ }
+
selectAgent(agent: Agent): void {
+ this.wizard.agentSkipped.set(false);
this.wizard.selectedAgent.set(agent);
const targetId = this.wizard.createdTarget()?.id;
if (targetId) {
diff --git a/src/Web/StellaOps.Web/src/app/features/platform/setup/topology-wizard/topology-wizard.service.ts b/src/Web/StellaOps.Web/src/app/features/platform/setup/topology-wizard/topology-wizard.service.ts
index 95c757e85..8073744d5 100644
--- a/src/Web/StellaOps.Web/src/app/features/platform/setup/topology-wizard/topology-wizard.service.ts
+++ b/src/Web/StellaOps.Web/src/app/features/platform/setup/topology-wizard/topology-wizard.service.ts
@@ -88,6 +88,7 @@ export class TopologyWizardService {
readonly createdEnvironment = signal
(null);
readonly createdTarget = signal(null);
readonly selectedAgent = signal(null);
+ readonly agentSkipped = signal(false);
readonly resolvedBindings = signal(null);
readonly readinessReport = signal(null);
readonly loading = signal(false);
@@ -104,7 +105,7 @@ export class TopologyWizardService {
case 'environment': return this.createdEnvironment() !== null;
case 'stage-order': return true;
case 'target': return this.createdTarget() !== null;
- case 'agent': return this.selectedAgent() !== null;
+ case 'agent': return this.selectedAgent() !== null || this.agentSkipped();
case 'infrastructure': return true;
case 'validate': return this.readinessReport()?.isReady === true;
default: return false;
@@ -142,6 +143,7 @@ export class TopologyWizardService {
this.createdEnvironment.set(null);
this.createdTarget.set(null);
this.selectedAgent.set(null);
+ this.agentSkipped.set(false);
this.resolvedBindings.set(null);
this.readinessReport.set(null);
this.error.set(null);
diff --git a/src/Web/StellaOps.Web/src/app/features/settings/settings.routes.ts b/src/Web/StellaOps.Web/src/app/features/settings/settings.routes.ts
index e55efb768..639bcdda3 100644
--- a/src/Web/StellaOps.Web/src/app/features/settings/settings.routes.ts
+++ b/src/Web/StellaOps.Web/src/app/features/settings/settings.routes.ts
@@ -83,13 +83,13 @@ export const SETTINGS_ROUTES: Routes = [
{
path: 'admin',
title: 'Identity & Access',
- redirectTo: redirectToCanonical('/administration/admin'),
+ redirectTo: redirectToCanonical('/setup/identity-access'),
pathMatch: 'full' as const,
},
{
path: 'admin/:page',
title: 'Identity & Access',
- redirectTo: redirectToCanonical('/administration/admin/:page'),
+ redirectTo: redirectToCanonical('/setup/identity-access'),
pathMatch: 'full' as const,
},
{
@@ -113,19 +113,19 @@ export const SETTINGS_ROUTES: Routes = [
{
path: 'identity-providers',
title: 'Identity Providers',
- redirectTo: redirectToCanonical('/administration/identity-providers'),
+ redirectTo: redirectToCanonical('/setup/identity-providers'),
pathMatch: 'full' as const,
},
{
path: 'system',
title: 'System',
- redirectTo: redirectToCanonical('/administration/system'),
+ redirectTo: redirectToCanonical('/setup/system'),
pathMatch: 'full' as const,
},
{
path: 'security-data',
title: 'Security Data',
- redirectTo: redirectToCanonical('/administration/security-data'),
+ redirectTo: redirectToCanonical('/setup/security-data'),
pathMatch: 'full' as const,
},
@@ -153,7 +153,7 @@ export const SETTINGS_ROUTES: Routes = [
{
path: 'offline',
title: 'Offline Settings',
- redirectTo: redirectToCanonical('/administration/offline'),
+ redirectTo: redirectToCanonical('/setup/offline'),
pathMatch: 'full' as const,
},
{
diff --git a/src/Web/StellaOps.Web/src/app/features/triage/components/decision-drawer/decision-drawer-enhanced.component.ts b/src/Web/StellaOps.Web/src/app/features/triage/components/decision-drawer/decision-drawer-enhanced.component.ts
index 444c53662..9160169f0 100644
--- a/src/Web/StellaOps.Web/src/app/features/triage/components/decision-drawer/decision-drawer-enhanced.component.ts
+++ b/src/Web/StellaOps.Web/src/app/features/triage/components/decision-drawer/decision-drawer-enhanced.component.ts
@@ -71,178 +71,180 @@ export interface ApprovalResponse {
standalone: true,
imports: [CommonModule, FormsModule, PlaybookSuggestionComponent],
template: `
-
-
-
+
@if (isOpen) {
-
+
+
+
+ Record Decision
+
+
+
+
+
+
+
+
+
+
+ Reason
+
+
+
+
+
+
+ @if (showTtlPicker()) {
+
+ Exception Time-to-Live
+
+ @if (formData().exceptionTtlDays) {
+
+ Expires: {{ computedExpiryDate() | date:'mediumDate' }}
+
+ }
+
+ }
+
+
+
+
+
+ Audit Summary
+
+ - Alert ID
+ - {{ alert?.id ?? '-' }}
+
+ - Artifact
+ - {{ alert?.artifactId ?? '-' }}
+
+ - Vulnerability
+ - {{ alert?.vulnId ?? '-' }}
+
+ - Evidence Hash
+ - {{ evidenceHash || '-' }}
+
+ - Policy Version
+ - {{ policyVersion || '-' }}
+
+
+
+
+
+
+
}
@@ -255,30 +257,35 @@ export interface ApprovalResponse {
}
`,
styles: [`
- .decision-drawer {
+ .decision-overlay {
position: fixed;
- right: 0;
top: 0;
- bottom: 0;
- width: 380px;
- background: var(--color-surface-primary);
- border-left: 1px solid var(--color-border-primary);
- box-shadow: -4px 0 16px rgba(0,0,0,0.1);
+ left: 0;
+ width: 100%;
+ height: 100%;
+ z-index: 1000;
+ background: rgba(0,0,0,0.5);
display: flex;
- flex-direction: column;
- transform: translateX(100%);
- transition: transform 0.3s ease;
- z-index: 101;
- overflow-y: auto;
+ align-items: center;
+ justify-content: center;
}
- .decision-drawer.open { transform: translateX(0); }
+ .decision-modal {
+ width: 520px;
+ max-width: 95vw;
+ max-height: 90vh;
+ background: var(--color-surface-primary);
+ border: 1px solid var(--color-border-primary);
+ border-radius: var(--radius-lg);
+ box-shadow: 0 8px 32px rgba(0,0,0,0.2);
+ display: flex;
+ flex-direction: column;
+ }
- .backdrop {
- position: fixed;
- inset: 0;
- background: rgba(0,0,0,0.3);
- z-index: 100;
+ .modal-body {
+ overflow-y: auto;
+ max-height: calc(90vh - 120px);
+ flex: 1;
}
header {
@@ -287,9 +294,9 @@ export interface ApprovalResponse {
align-items: center;
padding: 16px;
border-bottom: 1px solid var(--color-border-primary);
- position: sticky;
- top: 0;
background: var(--color-surface-primary);
+ border-radius: var(--radius-lg) var(--radius-lg) 0 0;
+ flex-shrink: 0;
}
h3 { margin: 0; font-size: var(--font-size-lg); }
@@ -429,15 +436,14 @@ export interface ApprovalResponse {
.hash { font-family: ui-monospace, monospace; font-size: var(--font-size-xs); word-break: break-all; }
footer {
- margin-top: auto;
padding: 16px;
display: flex;
gap: 8px;
justify-content: flex-end;
border-top: 1px solid var(--color-border-primary);
- position: sticky;
- bottom: 0;
background: var(--color-surface-primary);
+ border-radius: 0 0 var(--radius-lg) var(--radius-lg);
+ flex-shrink: 0;
}
.btn {
diff --git a/src/Web/StellaOps.Web/src/app/features/triage/components/decision-drawer/decision-drawer.component.ts b/src/Web/StellaOps.Web/src/app/features/triage/components/decision-drawer/decision-drawer.component.ts
index 50aab349d..fb4509df4 100644
--- a/src/Web/StellaOps.Web/src/app/features/triage/components/decision-drawer/decision-drawer.component.ts
+++ b/src/Web/StellaOps.Web/src/app/features/triage/components/decision-drawer/decision-drawer.component.ts
@@ -29,140 +29,146 @@ export interface AlertSummary {
selector: 'app-decision-drawer',
imports: [FormsModule],
template: `
-
-
@if (isOpen) {
-
+
+
+
+ Record Decision
+
+
+
+
+
+
+
+ Reason
+
+
+
+
+
+
+ Audit Summary
+
+ - Alert ID
+ - {{ alert?.id ?? '-' }}
+
+ - Artifact
+ - {{ alert?.artifactId ?? '-' }}
+
+ - Vulnerability
+ - {{ alert?.vulnId ?? '-' }}
+
+ - Evidence Hash
+ - {{ evidenceHash || '-' }}
+
+ - Policy Version
+ - {{ policyVersion || '-' }}
+
+
+
+
+
+
+
}
`,
styles: [`
- .decision-drawer {
+ .decision-overlay {
position: fixed;
- right: 0;
top: 0;
- bottom: 0;
- width: 360px;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ z-index: 1000;
+ background: rgba(0,0,0,0.5);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ }
+
+ .decision-modal {
+ width: 480px;
+ max-width: 95vw;
+ max-height: 90vh;
background: var(--color-surface-primary);
- border-left: 1px solid var(--color-border-primary);
- box-shadow: -4px 0 16px rgba(0,0,0,0.1);
+ border: 1px solid var(--color-border-primary);
+ border-radius: var(--radius-lg);
+ box-shadow: 0 8px 32px rgba(0,0,0,0.2);
display: flex;
flex-direction: column;
- transform: translateX(100%);
- transition: transform 0.3s ease;
- z-index: 101;
}
- .decision-drawer.open {
- transform: translateX(0);
- }
-
- .backdrop {
- position: fixed;
- inset: 0;
- background: rgba(0,0,0,0.3);
- z-index: 100;
+ .modal-body {
+ overflow-y: auto;
+ max-height: calc(90vh - 120px);
+ flex: 1;
}
header {
diff --git a/src/Web/StellaOps.Web/src/app/features/unknowns-tracking/unknowns-dashboard.component.ts b/src/Web/StellaOps.Web/src/app/features/unknowns-tracking/unknowns-dashboard.component.ts
index 30524959c..6cc565c18 100644
--- a/src/Web/StellaOps.Web/src/app/features/unknowns-tracking/unknowns-dashboard.component.ts
+++ b/src/Web/StellaOps.Web/src/app/features/unknowns-tracking/unknowns-dashboard.component.ts
@@ -32,12 +32,15 @@ import {
@if (error()) {
-
-
-
Unknowns data is unavailable
-
{{ error() }}
-
-
+
+
+ No unknown components detected
+ The scanner will identify unknowns during the next image scan.
+ Scan an Image →
}
@@ -166,6 +169,50 @@ import {
background: var(--color-surface-primary);
color: var(--color-text-secondary);
}
+ .unknowns-dashboard__empty-state {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ padding: 3rem 2rem;
+ margin-bottom: 1rem;
+ border: 1px solid var(--color-border-primary);
+ border-radius: var(--radius-lg);
+ background: var(--color-surface-secondary, #f9fafb);
+ text-align: center;
+ }
+ .unknowns-dashboard__empty-state .empty-icon {
+ width: 48px;
+ height: 48px;
+ color: var(--color-text-muted, #9ca3af);
+ margin-bottom: 1rem;
+ }
+ .unknowns-dashboard__empty-state h2 {
+ margin: 0 0 0.5rem;
+ font-size: 1.125rem;
+ font-weight: 600;
+ color: var(--color-text-primary);
+ }
+ .unknowns-dashboard__empty-state p {
+ margin: 0 0 1rem;
+ font-size: 0.875rem;
+ color: var(--color-text-secondary);
+ }
+ .empty-action-link {
+ display: inline-block;
+ padding: 0.5rem 1rem;
+ font-size: 0.875rem;
+ font-weight: 500;
+ color: var(--color-brand-primary, #3b82f6);
+ border: 1px solid var(--color-brand-primary, #3b82f6);
+ border-radius: var(--radius-md);
+ text-decoration: none;
+ transition: background 0.15s ease, color 0.15s ease;
+ }
+ .empty-action-link:hover {
+ background: var(--color-brand-primary, #3b82f6);
+ color: #fff;
+ }
`]
})
export class UnknownsDashboardComponent implements OnInit {
diff --git a/src/Web/StellaOps.Web/src/app/routes/setup.routes.ts b/src/Web/StellaOps.Web/src/app/routes/setup.routes.ts
index 9c5c9125e..97ced6f03 100644
--- a/src/Web/StellaOps.Web/src/app/routes/setup.routes.ts
+++ b/src/Web/StellaOps.Web/src/app/routes/setup.routes.ts
@@ -67,4 +67,67 @@ export const SETUP_ROUTES: Routes = [
loadChildren: () =>
import('../features/trust-admin/trust-admin.routes').then((m) => m.trustAdminRoutes),
},
+ {
+ path: 'identity-providers',
+ title: 'Identity Providers',
+ data: { breadcrumb: 'Identity Providers' },
+ loadComponent: () =>
+ import('../features/settings/identity-providers/identity-providers-settings-page.component').then(
+ (m) => m.IdentityProvidersSettingsPageComponent,
+ ),
+ },
+ {
+ path: 'offline',
+ title: 'Offline Settings',
+ data: { breadcrumb: 'Offline Settings' },
+ loadComponent: () =>
+ import('../features/offline-kit/components/offline-dashboard.component').then(
+ (m) => m.OfflineDashboardComponent,
+ ),
+ },
+ {
+ path: 'configuration-pane',
+ title: 'Configuration',
+ data: { breadcrumb: 'Configuration' },
+ loadChildren: () =>
+ import('../features/configuration-pane/configuration-pane.routes').then(
+ (m) => m.CONFIGURATION_PANE_ROUTES,
+ ),
+ },
+ {
+ path: 'security-data',
+ title: 'Security Data',
+ data: { breadcrumb: 'Security Data' },
+ loadComponent: () =>
+ import('../features/settings/security-data/security-data-settings-page.component').then(
+ (m) => m.SecurityDataSettingsPageComponent,
+ ),
+ },
+ {
+ path: 'security-data/trivy',
+ title: 'Trivy DB Settings',
+ data: { breadcrumb: 'Trivy DB' },
+ loadComponent: () =>
+ import('../features/trivy-db-settings/trivy-db-settings-page.component').then(
+ (m) => m.TrivyDbSettingsPageComponent,
+ ),
+ },
+ {
+ path: 'workflows',
+ title: 'Workflows',
+ data: { breadcrumb: 'Workflows' },
+ loadChildren: () =>
+ import('../features/release-orchestrator/workflows/workflows.routes').then(
+ (m) => m.WORKFLOW_ROUTES,
+ ),
+ },
+ {
+ path: 'ai-preferences',
+ title: 'AI Preferences',
+ data: { breadcrumb: 'AI Preferences' },
+ loadComponent: () =>
+ import('../features/settings/ai-preferences-workbench.component').then(
+ (m) => m.AiPreferencesWorkbenchComponent,
+ ),
+ },
];