From a41715105d787498526acb52e9895728480fb90e Mon Sep 17 00:00:00 2001 From: Oliver Kieselbach Date: Mon, 29 Jun 2026 11:47:19 +0200 Subject: [PATCH] fix(global-reader): show platform nav on global scope regardless of the Global View toggle The cross-tenant nav group (Managed Tenants, Fleet Health, Software, Geographic, SLA, Usage, Audit...) was gated on `hasGlobalScope && globalAdminMode`. globalAdminMode defaults to false (localStorage) and there is only auto-DISABLE logic, no auto-enable, so a fresh GlobalReader (or a GA with no own-tenant role) saw only "Dashboard" until they discovered the hidden "Global View" toggle in the avatar menu -- a dead end. A delegated MSP admin, by contrast, gets the full fleet nav with no toggle. Decouple nav VISIBILITY from the toggle: the platform nav is reachable for any caller with global scope (GA or read-only GlobalReader). The toggle now governs only the DATA scope (own-tenant vs cross-tenant) of the pages, not whether the nav is reachable. Mutating controls inside these views stay gated on isGlobalAdmin. Removed the now-dead globalAdminMode dependency from the sidebar. tsc clean; web 485 tests green. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../components/GlobalSidebar.tsx | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/Web/autopilot-monitor-web/components/GlobalSidebar.tsx b/src/Web/autopilot-monitor-web/components/GlobalSidebar.tsx index 9c32509d..e1ddea7f 100644 --- a/src/Web/autopilot-monitor-web/components/GlobalSidebar.tsx +++ b/src/Web/autopilot-monitor-web/components/GlobalSidebar.tsx @@ -9,7 +9,6 @@ import { CollapseState } from "../hooks/useSidebarState"; import { DefaultSectionIcon, BookOpenIcon, RocketLaunchIcon, InformationCircleIcon, DocumentTextIcon, ShieldCheckIcon } from "../lib/sidebarIcons"; import { DASHBOARD_ITEM, NAV_GROUPS, EXPANDABLE_NAV_GROUPS, REGULAR_USER_ITEMS, NavItem, NavGroup, ExpandableNavGroup, ExpandableNavItem } from "../lib/globalNavConfig"; import { PublicSiteNavbar } from "./PublicSiteNavbar"; -import { useAdminMode } from "../hooks/useAdminMode"; // Sidebar pixel widths export const SIDEBAR_PX: Record = { @@ -46,9 +45,6 @@ export function GlobalSidebar({ children }: { children: ReactNode }) { return () => mql.removeEventListener("change", handler); }, []); - // Global admin mode from hook - const { globalAdminMode } = useAdminMode(); - // --- Scroll-spy for page sections --- const [activeSectionId, setActiveSectionId] = useState(""); const observerRef = useRef(null); @@ -184,8 +180,11 @@ export function GlobalSidebar({ children }: { children: ReactNode }) { case "all": return true; case "adminOrOperator": return isAdminOrOperator; // Cross-tenant nav is a VISIBILITY surface → any platform scope (GA or read-only GlobalReader). - // Mutating controls inside these views are gated separately on isGlobalAdmin. - case "globalAdmin": return hasGlobalScope && globalAdminMode; + // Deliberately NOT gated on globalAdminMode: a pure GlobalReader (or a GA with no own-tenant role) + // would otherwise see only "Dashboard" until they discovered the hidden "Global View" toggle. The + // toggle now governs only the DATA scope (own-tenant vs cross-tenant) of the pages, not whether the + // platform nav is reachable. Mutating controls inside these views stay gated separately on isGlobalAdmin. + case "globalAdmin": return hasGlobalScope; // Fleet (MSP) nav: shown to a delegated admin who does NOT have full platform scope. A GA/Reader // manages tenants through the richer Global Admin section instead, so it stays hidden for them. case "fleet": return isDelegated && !hasGlobalScope; @@ -230,7 +229,7 @@ export function GlobalSidebar({ children }: { children: ReactNode }) { return { ...group, items: filteredItems }; }); // eslint-disable-next-line react-hooks/exhaustive-deps - }, [isAdminOrOperator, isGlobalAdmin, globalAdminMode, hasMcpAccess, isAdminLike, canManageBootstrapTokens, bootstrapTokenEnabled, unrestrictedModeEnabled]); + }, [isAdminOrOperator, isGlobalAdmin, hasMcpAccess, isAdminLike, canManageBootstrapTokens, bootstrapTokenEnabled, unrestrictedModeEnabled]); // Auto-expand the group containing the current pathname useEffect(() => {