diff --git a/ordinis-admin-ui/src/components/layout/Sidebar.tsx b/ordinis-admin-ui/src/components/layout/Sidebar.tsx index d3dbc0d..8d9d67c 100644 --- a/ordinis-admin-ui/src/components/layout/Sidebar.tsx +++ b/ordinis-admin-ui/src/components/layout/Sidebar.tsx @@ -63,6 +63,13 @@ type NavItem = { * (или {@code ordinis:admin} super-role). Hide если нет. */ reviewerOnly?: boolean + /** + * Admin-only nav entries (outbox/audit/webhooks). Backend возвращает 403 + * audit_internal_required / outbox_internal_required для PUBLIC-scope users. + * Frontend hide'ит вместо показа links → 403 toast (confusing UX). + * Hide когда {@link useHasInternalScope} returns false. + */ + internalOnly?: boolean } export function Sidebar() { @@ -138,6 +145,7 @@ export function Sidebar() { icon: Inbox, badge: outboxPending > 0 ? outboxPending : undefined, authRequired: true, + internalOnly: true, }, { to: '/webhooks', @@ -145,8 +153,13 @@ export function Sidebar() { icon: Webhook, badge: webhooksActive > 0 ? webhooksActive : undefined, authRequired: true, + internalOnly: true, }, - { to: '/audit', label: t('nav.audit'), icon: ScrollText, authRequired: true }, + // /audit, /outbox, /webhooks — admin observability tools. Backend + // возвращает 403 (audit_internal_required / outbox_internal_required) + // для PUBLIC-scope users. UI больше не показывает links — раньше + // PUBLIC users видели nav entries и кликали → 403 toast (confusing). + { to: '/audit', label: t('nav.audit'), icon: ScrollText, authRequired: true, internalOnly: true }, { to: '/search', label: t('nav.search'), icon: Search }, ], }, @@ -158,6 +171,7 @@ export function Sidebar() { items: s.items.filter((i) => { if (i.authRequired && !canMutate) return false if (i.reviewerOnly && !isReviewer) return false + if (i.internalOnly && !hasInternal) return false return true }), })) @@ -502,6 +516,7 @@ export function MobileSidebar({ open, onClose }: { open: boolean; onClose: () => icon: Inbox, badge: outboxPending > 0 ? outboxPending : undefined, authRequired: true, + internalOnly: true, }, { to: '/webhooks', @@ -509,8 +524,9 @@ export function MobileSidebar({ open, onClose }: { open: boolean; onClose: () => icon: Webhook, badge: webhooksActive > 0 ? webhooksActive : undefined, authRequired: true, + internalOnly: true, }, - { to: '/audit', label: t('nav.audit'), icon: ScrollText, authRequired: true }, + { to: '/audit', label: t('nav.audit'), icon: ScrollText, authRequired: true, internalOnly: true }, { to: '/search', label: t('nav.search'), icon: Search }, ], }, @@ -521,6 +537,7 @@ export function MobileSidebar({ open, onClose }: { open: boolean; onClose: () => items: s.items.filter((i) => { if (i.authRequired && !canMutate) return false if (i.reviewerOnly && !isReviewer) return false + if (i.internalOnly && !hasInternal) return false return true }), }))