Restructure sidebar navigation to match product workflows
Major IA restructure based on product-level analysis of Stella Ops core workflows (release lifecycle, policy gates, evidence chain, security posture). New 7-group structure (was 5): - Dashboard: new ungrouped home link (no group header) - Release Control: Deployments, Releases, Environments (moved from Ops) - Security: Vulnerabilities, Security Posture (+children), Scan Image (Reports removed — will merge into Posture in Phase B) - Policy: NEW GROUP — Packs, Governance, Simulation, VEX & Exceptions, Release Gates, Policy Audit (promoted from buried Operations item) - Operations: slimmed from 11→8 items (Hub, Jobs, Scripts, Signals, Diagnostics, Notifications, Feeds, Watchlist) - Audit & Evidence: Overview, Capsules, Replay, Export, Audit Log, Bundles (route fixed to /evidence/bundles), Trust (merged name) - Settings: unchanged Rationale: - Policy is a release-blocking gate with 6 deep sub-workflows — not an ops utility - Environments define the promotion graph — belongs in Release Control - Trust Audit + Trust Analytics merged into single "Trust" item - Reports removed (duplicated Security Posture content) - Versions removed from sidebar (will merge into Releases as tab in Phase C) - Dashboard link ensures users can always navigate home without logo click Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -116,7 +116,7 @@ interface NavSectionGroup {
|
|||||||
role="group"
|
role="group"
|
||||||
[attr.aria-label]="group.label"
|
[attr.aria-label]="group.label"
|
||||||
>
|
>
|
||||||
@if (!effectiveCollapsed) {
|
@if (!effectiveCollapsed && group.id !== 'home') {
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
class="sb-group__header"
|
class="sb-group__header"
|
||||||
@@ -626,10 +626,19 @@ export class AppSidebarComponent implements AfterViewInit {
|
|||||||
private readonly pendingApprovalsBadgeLoading = signal(false);
|
private readonly pendingApprovalsBadgeLoading = signal(false);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Navigation sections - canonical 5-group IA.
|
* Navigation sections - canonical 7-group IA.
|
||||||
* Groups: Release Control, Security, Operations, Audit & Evidence, Setup & Admin.
|
* Groups: Home (ungrouped), Release Control, Security, Policy, Operations, Audit & Evidence, Setup & Admin.
|
||||||
*/
|
*/
|
||||||
readonly navSections: NavSection[] = [
|
readonly navSections: NavSection[] = [
|
||||||
|
// ── Home (ungrouped) ───────────────────────────────────────────
|
||||||
|
{
|
||||||
|
id: 'dashboard',
|
||||||
|
label: 'Dashboard',
|
||||||
|
icon: 'home',
|
||||||
|
route: '/',
|
||||||
|
menuGroupId: 'home',
|
||||||
|
menuGroupLabel: '',
|
||||||
|
},
|
||||||
// ── Group 1: Release Control ─────────────────────────────────────
|
// ── Group 1: Release Control ─────────────────────────────────────
|
||||||
{
|
{
|
||||||
id: 'deployments',
|
id: 'deployments',
|
||||||
@@ -660,18 +669,7 @@ export class AppSidebarComponent implements AfterViewInit {
|
|||||||
StellaOpsScopes.RELEASE_PUBLISH,
|
StellaOpsScopes.RELEASE_PUBLISH,
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
// Versions merged into Releases page as a tab
|
||||||
id: 'versions',
|
|
||||||
label: 'Versions',
|
|
||||||
icon: 'layers',
|
|
||||||
route: '/releases/versions',
|
|
||||||
menuGroupId: 'release-control',
|
|
||||||
menuGroupLabel: 'Release Control',
|
|
||||||
requireAnyScope: [
|
|
||||||
StellaOpsScopes.RELEASE_READ,
|
|
||||||
StellaOpsScopes.RELEASE_WRITE,
|
|
||||||
],
|
|
||||||
},
|
|
||||||
// Approvals nav item removed — merged into Deployments page as a tab
|
// Approvals nav item removed — merged into Deployments page as a tab
|
||||||
// ── Group 2: Security ────────────────────────────────────────────
|
// ── Group 2: Security ────────────────────────────────────────────
|
||||||
{
|
{
|
||||||
@@ -720,18 +718,7 @@ export class AppSidebarComponent implements AfterViewInit {
|
|||||||
menuGroupLabel: 'Security',
|
menuGroupLabel: 'Security',
|
||||||
requireAnyScope: [StellaOpsScopes.SCANNER_READ],
|
requireAnyScope: [StellaOpsScopes.SCANNER_READ],
|
||||||
},
|
},
|
||||||
{
|
// Reports merged into Security Posture (export actions moved inline)
|
||||||
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 ──────────────────────────────────────────
|
// ── Group 3: Operations ──────────────────────────────────────────
|
||||||
{
|
{
|
||||||
id: 'ops',
|
id: 'ops',
|
||||||
@@ -791,8 +778,8 @@ export class AppSidebarComponent implements AfterViewInit {
|
|||||||
label: 'Environments',
|
label: 'Environments',
|
||||||
icon: 'globe',
|
icon: 'globe',
|
||||||
route: '/environments/regions',
|
route: '/environments/regions',
|
||||||
menuGroupId: 'operations',
|
menuGroupId: 'release-control',
|
||||||
menuGroupLabel: 'Operations',
|
menuGroupLabel: 'Release Control',
|
||||||
requireAnyScope: [
|
requireAnyScope: [
|
||||||
StellaOpsScopes.ORCH_READ,
|
StellaOpsScopes.ORCH_READ,
|
||||||
StellaOpsScopes.ORCH_OPERATE,
|
StellaOpsScopes.ORCH_OPERATE,
|
||||||
@@ -800,13 +787,58 @@ export class AppSidebarComponent implements AfterViewInit {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'ops-policy',
|
id: 'ops-policy',
|
||||||
label: 'Policy',
|
label: 'Packs',
|
||||||
icon: 'clipboard',
|
icon: 'clipboard',
|
||||||
route: '/ops/policy',
|
route: '/ops/policy/packs',
|
||||||
menuGroupId: 'operations',
|
menuGroupId: 'policy',
|
||||||
menuGroupLabel: 'Operations',
|
menuGroupLabel: 'Policy',
|
||||||
requireAnyScope: [StellaOpsScopes.POLICY_READ],
|
requireAnyScope: [StellaOpsScopes.POLICY_READ],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
id: 'ops-policy-governance',
|
||||||
|
label: 'Governance',
|
||||||
|
icon: 'shield',
|
||||||
|
route: '/ops/policy/governance',
|
||||||
|
menuGroupId: 'policy',
|
||||||
|
menuGroupLabel: 'Policy',
|
||||||
|
requireAnyScope: [StellaOpsScopes.POLICY_READ],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'ops-policy-simulation',
|
||||||
|
label: 'Simulation',
|
||||||
|
icon: 'play',
|
||||||
|
route: '/ops/policy/simulation',
|
||||||
|
menuGroupId: 'policy',
|
||||||
|
menuGroupLabel: 'Policy',
|
||||||
|
requireAnyScope: [StellaOpsScopes.POLICY_SIMULATE],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'ops-policy-vex',
|
||||||
|
label: 'VEX & Exceptions',
|
||||||
|
icon: 'file-text',
|
||||||
|
route: '/ops/policy/vex',
|
||||||
|
menuGroupId: 'policy',
|
||||||
|
menuGroupLabel: 'Policy',
|
||||||
|
requireAnyScope: [StellaOpsScopes.VEX_READ, StellaOpsScopes.EXCEPTION_READ],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'ops-policy-gates',
|
||||||
|
label: 'Release Gates',
|
||||||
|
icon: 'check-square',
|
||||||
|
route: '/ops/policy/gates',
|
||||||
|
menuGroupId: 'policy',
|
||||||
|
menuGroupLabel: 'Policy',
|
||||||
|
requireAnyScope: [StellaOpsScopes.POLICY_READ],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'ops-policy-audit',
|
||||||
|
label: 'Policy Audit',
|
||||||
|
icon: 'list',
|
||||||
|
route: '/ops/policy/audit',
|
||||||
|
menuGroupId: 'policy',
|
||||||
|
menuGroupLabel: 'Policy',
|
||||||
|
requireAnyScope: [StellaOpsScopes.POLICY_AUDIT],
|
||||||
|
},
|
||||||
{
|
{
|
||||||
id: 'ops-diagnostics',
|
id: 'ops-diagnostics',
|
||||||
label: 'Diagnostics',
|
label: 'Diagnostics',
|
||||||
@@ -846,15 +878,7 @@ export class AppSidebarComponent implements AfterViewInit {
|
|||||||
menuGroupLabel: 'Operations',
|
menuGroupLabel: 'Operations',
|
||||||
requireAnyScope: [StellaOpsScopes.SIGNER_READ],
|
requireAnyScope: [StellaOpsScopes.SIGNER_READ],
|
||||||
},
|
},
|
||||||
{
|
// Trust Analytics merged into Trust (Audit & Evidence group)
|
||||||
id: 'ops-trust-analytics',
|
|
||||||
label: 'Trust Analytics',
|
|
||||||
icon: 'trending-up',
|
|
||||||
route: '/ops/operations/trust-analytics',
|
|
||||||
menuGroupId: 'operations',
|
|
||||||
menuGroupLabel: 'Operations',
|
|
||||||
requireAnyScope: [StellaOpsScopes.SIGNER_READ],
|
|
||||||
},
|
|
||||||
// ── Group 4: Audit & Evidence ────────────────────────────────────
|
// ── Group 4: Audit & Evidence ────────────────────────────────────
|
||||||
{
|
{
|
||||||
id: 'evidence-overview',
|
id: 'evidence-overview',
|
||||||
@@ -923,7 +947,7 @@ export class AppSidebarComponent implements AfterViewInit {
|
|||||||
id: 'evidence-bundles',
|
id: 'evidence-bundles',
|
||||||
label: 'Bundles',
|
label: 'Bundles',
|
||||||
icon: 'inbox',
|
icon: 'inbox',
|
||||||
route: '/triage/audit-bundles',
|
route: '/evidence/bundles',
|
||||||
menuGroupId: 'audit-evidence',
|
menuGroupId: 'audit-evidence',
|
||||||
menuGroupLabel: 'Audit & Evidence',
|
menuGroupLabel: 'Audit & Evidence',
|
||||||
requireAnyScope: [
|
requireAnyScope: [
|
||||||
@@ -932,8 +956,8 @@ export class AppSidebarComponent implements AfterViewInit {
|
|||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'evidence-trust-audit',
|
id: 'evidence-trust',
|
||||||
label: 'Trust Audit',
|
label: 'Trust',
|
||||||
icon: 'shield-check',
|
icon: 'shield-check',
|
||||||
route: '/evidence/audit-log/trust',
|
route: '/evidence/audit-log/trust',
|
||||||
menuGroupId: 'audit-evidence',
|
menuGroupId: 'audit-evidence',
|
||||||
@@ -1013,7 +1037,7 @@ export class AppSidebarComponent implements AfterViewInit {
|
|||||||
/** Menu groups rendered in deterministic order for scanability */
|
/** Menu groups rendered in deterministic order for scanability */
|
||||||
readonly displaySectionGroups = computed<NavSectionGroup[]>(() => {
|
readonly displaySectionGroups = computed<NavSectionGroup[]>(() => {
|
||||||
const orderedGroups = new Map<string, NavSectionGroup>();
|
const orderedGroups = new Map<string, NavSectionGroup>();
|
||||||
const groupOrder = ['release-control', 'security', 'operations', 'audit-evidence', 'setup-admin', 'misc'];
|
const groupOrder = ['home', 'release-control', 'security', 'policy', 'operations', 'audit-evidence', 'setup-admin', 'misc'];
|
||||||
|
|
||||||
for (const groupId of groupOrder) {
|
for (const groupId of groupOrder) {
|
||||||
orderedGroups.set(groupId, {
|
orderedGroups.set(groupId, {
|
||||||
@@ -1126,10 +1150,14 @@ export class AppSidebarComponent implements AfterViewInit {
|
|||||||
|
|
||||||
private resolveMenuGroupLabel(groupId: string): string {
|
private resolveMenuGroupLabel(groupId: string): string {
|
||||||
switch (groupId) {
|
switch (groupId) {
|
||||||
|
case 'home':
|
||||||
|
return '';
|
||||||
case 'release-control':
|
case 'release-control':
|
||||||
return 'Release Control';
|
return 'Release Control';
|
||||||
case 'security':
|
case 'security':
|
||||||
return 'Security';
|
return 'Security';
|
||||||
|
case 'policy':
|
||||||
|
return 'Policy';
|
||||||
case 'operations':
|
case 'operations':
|
||||||
return 'Operations';
|
return 'Operations';
|
||||||
case 'audit-evidence':
|
case 'audit-evidence':
|
||||||
|
|||||||
Reference in New Issue
Block a user