From e2ce43a2d0eb0692f6744f6191d9d758e9045523 Mon Sep 17 00:00:00 2001 From: Oliver Kieselbach Date: Thu, 25 Jun 2026 22:14:49 +0200 Subject: [PATCH] fix(web): no broken Dashboard nav path for delegated-only MSP users [Phase 3b follow-up] MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Addresses a Codex finding: Phase 3b made delegated-only MSP users non-regular (so they get the group nav), which also rendered the Dashboard link for them — but /dashboard classifies the same user as regular and bounced them to /progress. So clicking Dashboard sent an MSP admin to the end-user progress portal. Fixed on both ends: - GlobalSidebar: gate the Dashboard link on isAdminOrOperator || hasGlobalScope (genuine own-tenant/platform users), not merely !isRegularUser. A delegated-only admin has no own-tenant stake, so the link is hidden; their entry point is the Fleet group. - /dashboard: a delegated-only user who reaches it by deep link now redirects to /fleet instead of /progress, consistent with the AuthGate landing. Web tsc clean; 476 web tests green. Co-Authored-By: Claude Opus 4.8 (1M context) --- src/Web/autopilot-monitor-web/app/dashboard/page.tsx | 7 ++++--- .../autopilot-monitor-web/components/GlobalSidebar.tsx | 8 ++++++-- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/src/Web/autopilot-monitor-web/app/dashboard/page.tsx b/src/Web/autopilot-monitor-web/app/dashboard/page.tsx index 9d5ff283..d6ebe801 100644 --- a/src/Web/autopilot-monitor-web/app/dashboard/page.tsx +++ b/src/Web/autopilot-monitor-web/app/dashboard/page.tsx @@ -144,11 +144,12 @@ function HomeContent() { disabled: isRegularUser, }); - // Redirect regular users (non-admin, non-operator, no platform scope) to the progress portal — they - // must never see the session list. A read-only Global Reader has cross-tenant read scope → stays. + // Redirect users without own-tenant/platform scope away from the session list. A delegated ("MSP") admin + // manages OTHER tenants and belongs on the fleet overview, not the end-user progress portal; everyone else + // without scope goes to /progress. A read-only Global Reader has cross-tenant read scope → stays. useEffect(() => { if (user && !user.isTenantAdmin && !user.isGlobalAdmin && !user.isGlobalReader && user.role !== 'Operator') { - router.replace("/progress"); + router.replace(user.isDelegated ? "/fleet" : "/progress"); } }, [user, router]); diff --git a/src/Web/autopilot-monitor-web/components/GlobalSidebar.tsx b/src/Web/autopilot-monitor-web/components/GlobalSidebar.tsx index 9d797215..79bb8f2c 100644 --- a/src/Web/autopilot-monitor-web/components/GlobalSidebar.tsx +++ b/src/Web/autopilot-monitor-web/components/GlobalSidebar.tsx @@ -399,14 +399,18 @@ export function GlobalSidebar({ children }: { children: ReactNode }) { // Regular users see minimal nav. A read-only Global Reader has platform scope, and a delegated MSP admin // has fleet scope → both get the (group-filtered) nav rather than the minimal regular-user list. const isRegularUser = !isAdminOrOperator && !hasFleetScope; + // The Dashboard (own-tenant session list) is only meaningful for a genuine own-tenant/platform user. + // A delegated-only MSP admin has no own-tenant stake — /dashboard would just bounce them to /fleet — + // so hide the link for them; their entry point is the Fleet group. + const showDashboard = isAdminOrOperator || hasGlobalScope; const renderNavContent = (isMobile = false) => ( <> {/* Global nav — only when authenticated */} {isAuthenticated && ( <> - {/* Dashboard — hidden for regular users (no admin/operator access) */} - {!isRegularUser && ( + {/* Dashboard — own-tenant session list; hidden for regular users AND delegated-only MSP admins */} + {showDashboard && (