Sidebar 5-group restructure + demo data badges + audit emission infrastructure
Sprint 4 — Sidebar restructure (S4-T01+T02):
5 groups: Release Control, Security, Operations, Audit & Evidence, Setup & Admin
Groups 4+5 collapsed by default for new users
Operations extracted from Release Control into own group
Audit extracted from Security into own group
groupOrder and resolveMenuGroupLabel updated
Approvals badge moved to section-level
Sprint 2 — Demo data badges (S2-T04+T05):
Backend: isDemo=true on all compatibility/seed responses in
PackAdapterEndpoints, QuotaCompatibilityEndpoints, VulnerabilitiesController
Frontend: "(Demo)" badges on Usage & Limits page quotas
Frontend: "(Demo)" badges on triage artifact list when seed data
New PlatformItemResponse/PlatformListResponse with IsDemo field
Sprint 6 — Audit emission infrastructure (S6-T01+T02):
New shared library: src/__Libraries/StellaOps.Audit.Emission/
- AuditActionAttribute: [AuditAction("module", "action")] endpoint tag
- AuditActionFilter: IEndpointFilter that auto-emits UnifiedAuditEvent
- HttpAuditEventEmitter: POSTs to Timeline /api/v1/audit/ingest
- Single-line DI: services.AddAuditEmission(configuration)
Timeline service: POST /api/v1/audit/ingest ingestion endpoint
- IngestAuditEventStore: 10k-event ring buffer
- CompositeUnifiedAuditEventProvider: merges HTTP-polled + ingested
Documentation: docs/modules/audit/AUDIT_EMISSION_GUIDE.md
Angular build: 0 errors. .NET builds: 0 errors.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -352,6 +352,7 @@ export class MockVulnerabilityApiService implements VulnerabilityApi {
|
||||
hasMore: offset + items.length < total,
|
||||
etag: '"vuln-list-v1"',
|
||||
traceId,
|
||||
isDemo: true,
|
||||
}).pipe(delay(200));
|
||||
}
|
||||
|
||||
|
||||
@@ -89,6 +89,8 @@ export interface VulnerabilitiesResponse {
|
||||
readonly etag?: string;
|
||||
/** Trace ID for the request. */
|
||||
readonly traceId?: string;
|
||||
/** Whether the response contains demo/seed data. */
|
||||
readonly isDemo?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -13,12 +13,12 @@ import { RouterLink } from '@angular/router';
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
template: `
|
||||
<div class="usage-settings">
|
||||
<h1 class="page-title">Usage & Limits</h1>
|
||||
<h1 class="page-title">Usage & Limits <span class="demo-badge">(Demo)</span></h1>
|
||||
<p class="page-subtitle">Monitor usage and configure quotas</p>
|
||||
|
||||
<div class="usage-grid">
|
||||
<div class="usage-card">
|
||||
<h3>Scans</h3>
|
||||
<h3>Scans <span class="demo-chip">(Demo)</span></h3>
|
||||
<div class="usage-bar">
|
||||
<div class="usage-bar__fill" style="width: 65%"></div>
|
||||
</div>
|
||||
@@ -26,7 +26,7 @@ import { RouterLink } from '@angular/router';
|
||||
</div>
|
||||
|
||||
<div class="usage-card">
|
||||
<h3>Storage</h3>
|
||||
<h3>Storage <span class="demo-chip">(Demo)</span></h3>
|
||||
<div class="usage-bar">
|
||||
<div class="usage-bar__fill" style="width: 42%"></div>
|
||||
</div>
|
||||
@@ -34,7 +34,7 @@ import { RouterLink } from '@angular/router';
|
||||
</div>
|
||||
|
||||
<div class="usage-card">
|
||||
<h3>Evidence Packets</h3>
|
||||
<h3>Evidence Packets <span class="demo-chip">(Demo)</span></h3>
|
||||
<div class="usage-bar">
|
||||
<div class="usage-bar__fill" style="width: 28%"></div>
|
||||
</div>
|
||||
@@ -42,7 +42,7 @@ import { RouterLink } from '@angular/router';
|
||||
</div>
|
||||
|
||||
<div class="usage-card">
|
||||
<h3>API Requests</h3>
|
||||
<h3>API Requests <span class="demo-chip">(Demo)</span></h3>
|
||||
<div class="usage-bar">
|
||||
<div class="usage-bar__fill" style="width: 15%"></div>
|
||||
</div>
|
||||
@@ -104,6 +104,23 @@ import { RouterLink } from '@angular/router';
|
||||
text-decoration: none;
|
||||
}
|
||||
.btn--secondary { background: var(--color-surface-secondary); border: 1px solid var(--color-border-primary); }
|
||||
.demo-badge {
|
||||
display: inline-block;
|
||||
padding: 0.125rem 0.5rem;
|
||||
font-size: 0.75rem;
|
||||
font-weight: var(--font-weight-semibold);
|
||||
color: var(--color-warning, #b45309);
|
||||
background: var(--color-warning-bg, #fef3c7);
|
||||
border-radius: var(--radius-sm, 4px);
|
||||
vertical-align: middle;
|
||||
margin-left: 0.5rem;
|
||||
}
|
||||
.demo-chip {
|
||||
font-size: 0.625rem;
|
||||
font-weight: normal;
|
||||
color: var(--color-warning, #b45309);
|
||||
opacity: 0.85;
|
||||
}
|
||||
`]
|
||||
})
|
||||
export class UsageSettingsPageComponent {}
|
||||
|
||||
@@ -1,7 +1,12 @@
|
||||
<section class="triage-artifacts" data-testid="triage-artifacts-page">
|
||||
<header class="triage-artifacts__header">
|
||||
<div>
|
||||
<h1>Artifact workspace</h1>
|
||||
<h1>
|
||||
Artifact workspace
|
||||
@if (isDemo()) {
|
||||
<span class="demo-badge">(Demo)</span>
|
||||
}
|
||||
</h1>
|
||||
<p class="triage-artifacts__subtitle">
|
||||
Triage live artifacts by lane, then open a single evidence-first decision workspace.
|
||||
</p>
|
||||
@@ -165,6 +170,9 @@
|
||||
</td>
|
||||
<td class="triage-table__td">
|
||||
<code class="artifact-id">{{ row.artifactId }}</code>
|
||||
@if (isDemo()) {
|
||||
<span class="demo-chip">(Demo)</span>
|
||||
}
|
||||
@if (row.readyToDeploy) {
|
||||
<span class="ready-pill" title="Signed evidence is available and no open findings remain.">
|
||||
Ready to deploy
|
||||
|
||||
@@ -370,6 +370,26 @@
|
||||
font-size: var(--font-size-sm);
|
||||
}
|
||||
|
||||
.demo-badge {
|
||||
display: inline-block;
|
||||
padding: 0.125rem 0.5rem;
|
||||
font-size: var(--font-size-xs);
|
||||
font-weight: var(--font-weight-semibold);
|
||||
color: var(--color-status-warning-text, #b45309);
|
||||
background: var(--color-status-warning-bg, #fef3c7);
|
||||
border-radius: var(--radius-sm);
|
||||
vertical-align: middle;
|
||||
margin-left: var(--space-2);
|
||||
}
|
||||
|
||||
.demo-chip {
|
||||
margin-left: var(--space-1);
|
||||
font-size: var(--font-size-xs);
|
||||
font-weight: normal;
|
||||
color: var(--color-status-warning-text, #b45309);
|
||||
opacity: 0.85;
|
||||
}
|
||||
|
||||
.empty-state {
|
||||
padding: var(--space-6);
|
||||
color: var(--color-text-muted);
|
||||
|
||||
@@ -73,6 +73,7 @@ export class TriageArtifactsComponent implements OnInit {
|
||||
readonly loading = signal(false);
|
||||
readonly error = signal<string | null>(null);
|
||||
readonly vulnerabilities = signal<readonly Vulnerability[]>([]);
|
||||
readonly isDemo = signal(false);
|
||||
|
||||
readonly search = signal('');
|
||||
readonly environment = signal<EnvironmentHint | 'all'>('all');
|
||||
@@ -203,6 +204,7 @@ export class TriageArtifactsComponent implements OnInit {
|
||||
try {
|
||||
const resp = await firstValueFrom(this.api.listVulnerabilities({ includeReachability: true }));
|
||||
this.vulnerabilities.set(resp.items);
|
||||
this.isDemo.set(resp.isDemo === true);
|
||||
this.pruneSelection();
|
||||
} catch (err) {
|
||||
this.error.set(err instanceof Error ? err.message : 'Failed to load vulnerabilities');
|
||||
|
||||
@@ -624,11 +624,11 @@ export class AppSidebarComponent implements AfterViewInit {
|
||||
private readonly pendingApprovalsBadgeLoading = signal(false);
|
||||
|
||||
/**
|
||||
* Navigation sections - pre-alpha canonical IA.
|
||||
* Root modules: Mission Control, Releases, Security, Evidence, Ops, Setup.
|
||||
* Navigation sections - canonical 5-group IA.
|
||||
* Groups: Release Control, Security, Operations, Audit & Evidence, Setup & Admin.
|
||||
*/
|
||||
readonly navSections: NavSection[] = [
|
||||
// ── Release Control ──────────────────────────────────────────────
|
||||
// ── Group 1: Release Control ─────────────────────────────────────
|
||||
{
|
||||
id: 'dashboard',
|
||||
label: 'Dashboard',
|
||||
@@ -668,67 +668,70 @@ export class AppSidebarComponent implements AfterViewInit {
|
||||
route: '/releases/health',
|
||||
icon: 'activity',
|
||||
},
|
||||
{
|
||||
id: 'rel-approvals',
|
||||
label: 'Approvals',
|
||||
route: '/releases/approvals',
|
||||
icon: 'check-circle',
|
||||
badge: 0,
|
||||
requireAnyScope: [
|
||||
StellaOpsScopes.RELEASE_PUBLISH,
|
||||
StellaOpsScopes.POLICY_REVIEW,
|
||||
StellaOpsScopes.POLICY_APPROVE,
|
||||
StellaOpsScopes.EXCEPTION_APPROVE,
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'rel-promotions',
|
||||
label: 'Promotions',
|
||||
route: '/releases/promotions',
|
||||
icon: 'git-merge',
|
||||
requireAnyScope: [
|
||||
StellaOpsScopes.RELEASE_READ,
|
||||
StellaOpsScopes.RELEASE_WRITE,
|
||||
StellaOpsScopes.RELEASE_PUBLISH,
|
||||
],
|
||||
},
|
||||
{ id: 'rel-hotfix-list', label: 'Hotfixes', route: '/releases/hotfixes', icon: 'zap' },
|
||||
{ id: 'rel-deployments', label: 'Deployments', route: '/releases/deployments', icon: 'upload-cloud' },
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'ops',
|
||||
label: 'Operations',
|
||||
icon: 'settings',
|
||||
route: '/ops/operations',
|
||||
id: 'promotions',
|
||||
label: 'Promotions',
|
||||
icon: 'git-merge',
|
||||
route: '/releases/promotions',
|
||||
menuGroupId: 'release-control',
|
||||
menuGroupLabel: 'Release Control',
|
||||
sparklineData$: () => this.doctorTrendService.platformTrend(),
|
||||
requireAnyScope: [
|
||||
StellaOpsScopes.UI_ADMIN,
|
||||
StellaOpsScopes.ORCH_READ,
|
||||
StellaOpsScopes.ORCH_OPERATE,
|
||||
StellaOpsScopes.HEALTH_READ,
|
||||
StellaOpsScopes.NOTIFY_VIEWER,
|
||||
StellaOpsScopes.POLICY_READ,
|
||||
],
|
||||
children: [
|
||||
{ id: 'ops-jobs', label: 'Scheduled Jobs', route: '/ops/operations/jobengine', icon: 'clock' },
|
||||
{ id: 'ops-signals', label: 'Signals', route: '/ops/operations/signals', icon: 'radio' },
|
||||
{ id: 'ops-offline-kit', label: 'Offline Kit', route: '/ops/operations/offline-kit', icon: 'download-cloud' },
|
||||
{ id: 'ops-environments', label: 'Environments', route: '/ops/operations/environments', icon: 'globe' },
|
||||
{ id: 'ops-policy', label: 'Policy', route: '/ops/policy', icon: 'shield' },
|
||||
{ id: 'ops-platform-setup', label: 'Platform Setup', route: '/ops/platform-setup', icon: 'cog' },
|
||||
{ id: 'ops-notifications', label: 'Notifications', route: '/ops/operations/notifications', icon: 'bell' },
|
||||
StellaOpsScopes.RELEASE_READ,
|
||||
StellaOpsScopes.RELEASE_WRITE,
|
||||
StellaOpsScopes.RELEASE_PUBLISH,
|
||||
],
|
||||
},
|
||||
// ── Security & Audit ─────────────────────────────────────────────
|
||||
{
|
||||
id: 'approvals',
|
||||
label: 'Approvals',
|
||||
icon: 'check-circle',
|
||||
route: '/releases/approvals',
|
||||
menuGroupId: 'release-control',
|
||||
menuGroupLabel: 'Release Control',
|
||||
badge$: () => this.pendingApprovalsCount(),
|
||||
requireAnyScope: [
|
||||
StellaOpsScopes.RELEASE_PUBLISH,
|
||||
StellaOpsScopes.POLICY_REVIEW,
|
||||
StellaOpsScopes.POLICY_APPROVE,
|
||||
StellaOpsScopes.EXCEPTION_APPROVE,
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'hotfixes',
|
||||
label: 'Hotfixes',
|
||||
icon: 'zap',
|
||||
route: '/releases/hotfixes',
|
||||
menuGroupId: 'release-control',
|
||||
menuGroupLabel: 'Release Control',
|
||||
requireAnyScope: [
|
||||
StellaOpsScopes.RELEASE_READ,
|
||||
StellaOpsScopes.RELEASE_WRITE,
|
||||
],
|
||||
},
|
||||
// ── Group 2: Security ────────────────────────────────────────────
|
||||
{
|
||||
id: 'vulnerabilities',
|
||||
label: 'Vulnerabilities',
|
||||
icon: 'list',
|
||||
route: '/triage/artifacts',
|
||||
menuGroupId: 'security',
|
||||
menuGroupLabel: 'Security',
|
||||
requireAnyScope: [
|
||||
StellaOpsScopes.SCANNER_READ,
|
||||
StellaOpsScopes.FINDINGS_READ,
|
||||
StellaOpsScopes.VULN_VIEW,
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'security-posture',
|
||||
label: 'Security Posture',
|
||||
icon: 'shield',
|
||||
route: '/security',
|
||||
menuGroupId: 'security-audit',
|
||||
menuGroupLabel: 'Security & Audit',
|
||||
menuGroupId: 'security',
|
||||
menuGroupLabel: 'Security',
|
||||
sparklineData$: () => this.doctorTrendService.securityTrend(),
|
||||
requireAnyScope: [
|
||||
StellaOpsScopes.SCANNER_READ,
|
||||
@@ -740,28 +743,145 @@ export class AppSidebarComponent implements AfterViewInit {
|
||||
StellaOpsScopes.VULN_VIEW,
|
||||
],
|
||||
children: [
|
||||
{ id: 'sec-posture', label: 'Posture', route: '/security', icon: 'shield' },
|
||||
{ id: 'sec-triage', label: 'Vulnerabilities', route: '/triage/artifacts', icon: 'list' },
|
||||
{ id: 'sec-supply-chain', label: 'Supply-Chain Data', route: '/security/supply-chain-data', icon: 'graph' },
|
||||
{ id: 'sec-reachability', label: 'Reachability', route: '/security/reachability', icon: 'cpu' },
|
||||
{ id: 'sec-unknowns', label: 'Unknowns', route: '/security/unknowns', icon: 'help-circle' },
|
||||
{
|
||||
id: 'sec-scan-image',
|
||||
label: 'Scan Image',
|
||||
route: '/security/scan',
|
||||
icon: 'search',
|
||||
requireAnyScope: [StellaOpsScopes.SCANNER_READ],
|
||||
},
|
||||
{ id: 'sec-reports', label: 'Reports', route: '/security/reports', icon: 'book-open' },
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'audit',
|
||||
label: 'Audit',
|
||||
id: 'scan-image',
|
||||
label: 'Scan Image',
|
||||
icon: 'search',
|
||||
route: '/security/scan',
|
||||
menuGroupId: 'security',
|
||||
menuGroupLabel: 'Security',
|
||||
requireAnyScope: [StellaOpsScopes.SCANNER_READ],
|
||||
},
|
||||
{
|
||||
id: 'sec-reports',
|
||||
label: 'Reports',
|
||||
icon: 'book-open',
|
||||
route: '/security/reports',
|
||||
menuGroupId: 'security',
|
||||
menuGroupLabel: 'Security',
|
||||
requireAnyScope: [
|
||||
StellaOpsScopes.SCANNER_READ,
|
||||
StellaOpsScopes.FINDINGS_READ,
|
||||
],
|
||||
},
|
||||
// ── Group 3: Operations ──────────────────────────────────────────
|
||||
{
|
||||
id: 'ops',
|
||||
label: 'Operations Hub',
|
||||
icon: 'settings',
|
||||
route: '/ops/operations',
|
||||
menuGroupId: 'operations',
|
||||
menuGroupLabel: 'Operations',
|
||||
sparklineData$: () => this.doctorTrendService.platformTrend(),
|
||||
requireAnyScope: [
|
||||
StellaOpsScopes.UI_ADMIN,
|
||||
StellaOpsScopes.ORCH_READ,
|
||||
StellaOpsScopes.ORCH_OPERATE,
|
||||
StellaOpsScopes.HEALTH_READ,
|
||||
StellaOpsScopes.NOTIFY_VIEWER,
|
||||
StellaOpsScopes.POLICY_READ,
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'ops-jobs',
|
||||
label: 'Scheduled Jobs',
|
||||
icon: 'clock',
|
||||
route: '/ops/operations/jobengine',
|
||||
menuGroupId: 'operations',
|
||||
menuGroupLabel: 'Operations',
|
||||
requireAnyScope: [
|
||||
StellaOpsScopes.ORCH_READ,
|
||||
StellaOpsScopes.ORCH_OPERATE,
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'ops-signals',
|
||||
label: 'Signals',
|
||||
icon: 'radio',
|
||||
route: '/ops/operations/signals',
|
||||
menuGroupId: 'operations',
|
||||
menuGroupLabel: 'Operations',
|
||||
requireAnyScope: [
|
||||
StellaOpsScopes.ORCH_READ,
|
||||
StellaOpsScopes.HEALTH_READ,
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'ops-environments',
|
||||
label: 'Environments',
|
||||
icon: 'globe',
|
||||
route: '/ops/operations/environments',
|
||||
menuGroupId: 'operations',
|
||||
menuGroupLabel: 'Operations',
|
||||
requireAnyScope: [
|
||||
StellaOpsScopes.ORCH_READ,
|
||||
StellaOpsScopes.ORCH_OPERATE,
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'ops-policy',
|
||||
label: 'Policy',
|
||||
icon: 'shield',
|
||||
route: '/ops/policy',
|
||||
menuGroupId: 'operations',
|
||||
menuGroupLabel: 'Operations',
|
||||
requireAnyScope: [StellaOpsScopes.POLICY_READ],
|
||||
},
|
||||
{
|
||||
id: 'ops-diagnostics',
|
||||
label: 'Diagnostics',
|
||||
icon: 'stethoscope',
|
||||
route: '/ops/operations/doctor',
|
||||
menuGroupId: 'operations',
|
||||
menuGroupLabel: 'Operations',
|
||||
requireAnyScope: [StellaOpsScopes.HEALTH_READ, StellaOpsScopes.UI_ADMIN],
|
||||
},
|
||||
{
|
||||
id: 'ops-notifications',
|
||||
label: 'Notifications',
|
||||
icon: 'bell',
|
||||
route: '/ops/operations/notifications',
|
||||
menuGroupId: 'operations',
|
||||
menuGroupLabel: 'Operations',
|
||||
requireAnyScope: [StellaOpsScopes.NOTIFY_VIEWER],
|
||||
},
|
||||
{
|
||||
id: 'ops-feeds-airgap',
|
||||
label: 'Feeds & Airgap',
|
||||
icon: 'download-cloud',
|
||||
route: '/ops/operations/feeds-airgap',
|
||||
menuGroupId: 'operations',
|
||||
menuGroupLabel: 'Operations',
|
||||
requireAnyScope: [
|
||||
StellaOpsScopes.ADVISORY_READ,
|
||||
StellaOpsScopes.VEX_READ,
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'ops-offline-kit',
|
||||
label: 'Offline Kit',
|
||||
icon: 'download-cloud',
|
||||
route: '/ops/operations/offline-kit',
|
||||
menuGroupId: 'operations',
|
||||
menuGroupLabel: 'Operations',
|
||||
requireAnyScope: [
|
||||
StellaOpsScopes.UI_ADMIN,
|
||||
StellaOpsScopes.ORCH_OPERATE,
|
||||
],
|
||||
},
|
||||
// ── Group 4: Audit & Evidence ────────────────────────────────────
|
||||
{
|
||||
id: 'evidence-overview',
|
||||
label: 'Evidence Overview',
|
||||
icon: 'file-text',
|
||||
route: '/evidence/overview',
|
||||
menuGroupId: 'security-audit',
|
||||
menuGroupLabel: 'Security & Audit',
|
||||
menuGroupId: 'audit-evidence',
|
||||
menuGroupLabel: 'Audit & Evidence',
|
||||
requireAnyScope: [
|
||||
StellaOpsScopes.RELEASE_READ,
|
||||
StellaOpsScopes.POLICY_AUDIT,
|
||||
@@ -769,44 +889,132 @@ export class AppSidebarComponent implements AfterViewInit {
|
||||
StellaOpsScopes.SIGNER_READ,
|
||||
StellaOpsScopes.VEX_EXPORT,
|
||||
],
|
||||
children: [
|
||||
{ id: 'ev-overview', label: 'Overview', route: '/evidence/overview', icon: 'home' },
|
||||
{ id: 'ev-capsules', label: 'Decision Capsules', route: '/evidence/capsules', icon: 'archive' },
|
||||
{ id: 'ev-verify', label: 'Replay & Verify', route: '/evidence/verify-replay', icon: 'refresh' },
|
||||
{ id: 'ev-exports', label: 'Export Center', route: '/evidence/exports', icon: 'download' },
|
||||
{ id: 'ev-audit', label: 'Logs', route: '/evidence/audit-log', icon: 'book-open' },
|
||||
{ id: 'ev-bundles', label: 'Bundles', route: '/triage/audit-bundles', icon: 'archive' },
|
||||
},
|
||||
{
|
||||
id: 'evidence-capsules',
|
||||
label: 'Decision Capsules',
|
||||
icon: 'archive',
|
||||
route: '/evidence/capsules',
|
||||
menuGroupId: 'audit-evidence',
|
||||
menuGroupLabel: 'Audit & Evidence',
|
||||
requireAnyScope: [
|
||||
StellaOpsScopes.RELEASE_READ,
|
||||
StellaOpsScopes.POLICY_AUDIT,
|
||||
],
|
||||
},
|
||||
// ── Platform & Setup ─────────────────────────────────────────────
|
||||
{
|
||||
id: 'setup',
|
||||
label: 'Setup',
|
||||
icon: 'server',
|
||||
route: '/setup/system',
|
||||
menuGroupId: 'platform-setup',
|
||||
menuGroupLabel: 'Platform & Setup',
|
||||
id: 'evidence-verify',
|
||||
label: 'Replay & Verify',
|
||||
icon: 'refresh',
|
||||
route: '/evidence/verify-replay',
|
||||
menuGroupId: 'audit-evidence',
|
||||
menuGroupLabel: 'Audit & Evidence',
|
||||
requireAnyScope: [
|
||||
StellaOpsScopes.RELEASE_READ,
|
||||
StellaOpsScopes.SIGNER_READ,
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'evidence-exports',
|
||||
label: 'Export Center',
|
||||
icon: 'download',
|
||||
route: '/evidence/exports',
|
||||
menuGroupId: 'audit-evidence',
|
||||
menuGroupLabel: 'Audit & Evidence',
|
||||
requireAnyScope: [
|
||||
StellaOpsScopes.VEX_EXPORT,
|
||||
StellaOpsScopes.RELEASE_READ,
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'evidence-audit-log',
|
||||
label: 'Audit Log',
|
||||
icon: 'book-open',
|
||||
route: '/evidence/audit-log',
|
||||
menuGroupId: 'audit-evidence',
|
||||
menuGroupLabel: 'Audit & Evidence',
|
||||
requireAnyScope: [
|
||||
StellaOpsScopes.POLICY_AUDIT,
|
||||
StellaOpsScopes.AUTHORITY_AUDIT_READ,
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'evidence-bundles',
|
||||
label: 'Bundles',
|
||||
icon: 'archive',
|
||||
route: '/triage/audit-bundles',
|
||||
menuGroupId: 'audit-evidence',
|
||||
menuGroupLabel: 'Audit & Evidence',
|
||||
requireAnyScope: [
|
||||
StellaOpsScopes.RELEASE_READ,
|
||||
StellaOpsScopes.POLICY_AUDIT,
|
||||
],
|
||||
},
|
||||
// ── Group 5: Setup & Admin ───────────────────────────────────────
|
||||
{
|
||||
id: 'setup-topology',
|
||||
label: 'Topology',
|
||||
icon: 'globe',
|
||||
route: '/setup/topology/overview',
|
||||
menuGroupId: 'setup-admin',
|
||||
menuGroupLabel: 'Setup & Admin',
|
||||
requireAnyScope: [
|
||||
StellaOpsScopes.UI_ADMIN,
|
||||
StellaOpsScopes.RELEASE_READ,
|
||||
StellaOpsScopes.ORCH_READ,
|
||||
StellaOpsScopes.ORCH_OPERATE,
|
||||
],
|
||||
children: [
|
||||
{ id: 'setup-topology', label: 'Topology', route: '/setup/topology/overview', icon: 'globe' },
|
||||
{
|
||||
id: 'setup-diagnostics',
|
||||
label: 'Diagnostics',
|
||||
route: '/ops/operations/doctor',
|
||||
icon: 'stethoscope',
|
||||
requireAnyScope: [StellaOpsScopes.HEALTH_READ, StellaOpsScopes.UI_ADMIN],
|
||||
},
|
||||
{ id: 'setup-integrations', label: 'Integrations', route: '/setup/integrations', icon: 'plug' },
|
||||
{ id: 'setup-iam', label: 'Identity & Access', route: '/setup/identity-access', icon: 'user' },
|
||||
{ id: 'setup-trust-signing', label: 'Trust & Signing', route: '/setup/trust-signing', icon: 'shield' },
|
||||
{ id: 'setup-branding', label: 'Tenant & Branding', route: '/setup/tenant-branding', icon: 'paintbrush' },
|
||||
},
|
||||
{
|
||||
id: 'setup-integrations',
|
||||
label: 'Integrations',
|
||||
icon: 'plug',
|
||||
route: '/setup/integrations',
|
||||
menuGroupId: 'setup-admin',
|
||||
menuGroupLabel: 'Setup & Admin',
|
||||
requireAnyScope: [
|
||||
StellaOpsScopes.UI_ADMIN,
|
||||
StellaOpsScopes.ORCH_OPERATE,
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'setup-iam',
|
||||
label: 'Identity & Access',
|
||||
icon: 'user',
|
||||
route: '/setup/identity-access',
|
||||
menuGroupId: 'setup-admin',
|
||||
menuGroupLabel: 'Setup & Admin',
|
||||
requireAnyScope: [StellaOpsScopes.UI_ADMIN],
|
||||
},
|
||||
{
|
||||
id: 'setup-trust-signing',
|
||||
label: 'Trust & Signing',
|
||||
icon: 'shield',
|
||||
route: '/setup/trust-signing',
|
||||
menuGroupId: 'setup-admin',
|
||||
menuGroupLabel: 'Setup & Admin',
|
||||
requireAnyScope: [
|
||||
StellaOpsScopes.UI_ADMIN,
|
||||
StellaOpsScopes.SIGNER_READ,
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'setup-branding',
|
||||
label: 'Tenant & Branding',
|
||||
icon: 'paintbrush',
|
||||
route: '/setup/tenant-branding',
|
||||
menuGroupId: 'setup-admin',
|
||||
menuGroupLabel: 'Setup & Admin',
|
||||
requireAnyScope: [StellaOpsScopes.UI_ADMIN],
|
||||
},
|
||||
{
|
||||
id: 'setup-system',
|
||||
label: 'System Settings',
|
||||
icon: 'server',
|
||||
route: '/setup/system',
|
||||
menuGroupId: 'setup-admin',
|
||||
menuGroupLabel: 'Setup & Admin',
|
||||
requireAnyScope: [StellaOpsScopes.UI_ADMIN],
|
||||
},
|
||||
];
|
||||
|
||||
/** Navigation sections filtered by user scopes */
|
||||
@@ -828,7 +1036,7 @@ export class AppSidebarComponent implements AfterViewInit {
|
||||
/** Menu groups rendered in deterministic order for scanability */
|
||||
readonly displaySectionGroups = computed<NavSectionGroup[]>(() => {
|
||||
const orderedGroups = new Map<string, NavSectionGroup>();
|
||||
const groupOrder = ['release-control', 'security-audit', 'platform-setup', 'misc'];
|
||||
const groupOrder = ['release-control', 'security', 'operations', 'audit-evidence', 'setup-admin', 'misc'];
|
||||
|
||||
for (const groupId of groupOrder) {
|
||||
orderedGroups.set(groupId, {
|
||||
@@ -900,10 +1108,14 @@ export class AppSidebarComponent implements AfterViewInit {
|
||||
switch (groupId) {
|
||||
case 'release-control':
|
||||
return 'Release Control';
|
||||
case 'security-audit':
|
||||
return 'Security & Audit';
|
||||
case 'platform-setup':
|
||||
return 'Platform & Setup';
|
||||
case 'security':
|
||||
return 'Security';
|
||||
case 'operations':
|
||||
return 'Operations';
|
||||
case 'audit-evidence':
|
||||
return 'Audit & Evidence';
|
||||
case 'setup-admin':
|
||||
return 'Setup & Admin';
|
||||
default:
|
||||
return 'Global Menu';
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ const STORAGE_KEY = 'stellaops.sidebar.preferences';
|
||||
|
||||
const DEFAULTS: SidebarPreferences = {
|
||||
sidebarCollapsed: false,
|
||||
collapsedGroups: [],
|
||||
collapsedGroups: ['audit-evidence', 'setup-admin'],
|
||||
collapsedSections: [],
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user