From df704c28a66b63d97de1f6003263c21a0e97d850 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D0=BB=D0=B5=D0=BA=D1=81=D0=B0=D0=BD=D0=B4=D1=80=20?= =?UTF-8?q?=D0=97=D0=B8=D0=BC=D0=B8=D0=BD?= Date: Fri, 15 May 2026 17:05:51 +0000 Subject: [PATCH] =?UTF-8?q?fix(sidebar):=20hide=20outbox/audit/webhooks=20?= =?UTF-8?q?=D0=B4=D0=BB=D1=8F=20PUBLIC=20users?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/components/layout/Sidebar.tsx | 21 +++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) 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 }), }))