mirror of
https://github.com/okieselbach/AutopilotMonitor.git
synced 2026-10-24 12:42:38 +00:00
fix(global-reader): treat GlobalReader exactly like a GA in the sidebar (read-only aside)
Refinesa4171510. A GlobalReader is "GA minus writes" and must see the SAME sidebar as a real Global Admin: - Standard monitoring/rules/operations/configuration groups (Fleet Health, Usage Metrics, SLA, Geographic, Software, Audit, ...) were gated on `adminOrOperator`, so a pure GlobalReader (no own-tenant role) never saw them. Open them to any platform scope: `isAdminOrOperator || hasGlobalScope`. - The (purple) Global Admin section is gated back on the Global View toggle (`hasGlobalScope && globalAdminMode`) -- IDENTICAL to a GA: toggle off hides it, toggle on shows it. (a4171510had decoupled it from the toggle; reverted so reader == GA exactly.) Read-only is enforced inside the pages (useCanMutatePlatform + backend) and by the existing item-level globalAdminOnly filter (Settings/Ops/Software drop out for a reader), not by hiding the data nav. The "fleet" MSP group stays delegated-only, so a GA/Reader gets the standard groups with no duplication. tsc clean; web 488 tests green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -9,6 +9,7 @@ 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<CollapseState, number> = {
|
||||
@@ -45,6 +46,9 @@ export function GlobalSidebar({ children }: { children: ReactNode }) {
|
||||
return () => mql.removeEventListener("change", handler);
|
||||
}, []);
|
||||
|
||||
// Global admin mode toggle — gates the (purple) Global Admin section, identical to a real GA.
|
||||
const { globalAdminMode } = useAdminMode();
|
||||
|
||||
// --- Scroll-spy for page sections ---
|
||||
const [activeSectionId, setActiveSectionId] = useState("");
|
||||
const observerRef = useRef<IntersectionObserver | null>(null);
|
||||
@@ -178,15 +182,17 @@ export function GlobalSidebar({ children }: { children: ReactNode }) {
|
||||
const isGroupVisible = (group: NavGroup | ExpandableNavGroup): boolean => {
|
||||
switch (group.visibility) {
|
||||
case "all": return true;
|
||||
case "adminOrOperator": return isAdminOrOperator;
|
||||
// Cross-tenant nav is a VISIBILITY surface → any platform scope (GA or read-only GlobalReader).
|
||||
// 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;
|
||||
// A GlobalReader is "GA minus writes" — it must see the SAME sidebar as a real GA. The standard
|
||||
// monitoring/rules/operations/configuration groups are normally tenant-admin scope; open them to any
|
||||
// platform scope so a pure GlobalReader (no own-tenant role) gets Fleet Health, Usage Metrics, SLA,
|
||||
// etc. Read-only is enforced inside the pages (useCanMutatePlatform + backend), not by hiding nav.
|
||||
case "adminOrOperator": return isAdminOrOperator || hasGlobalScope;
|
||||
// The (purple) Global Admin section is gated on the Global View toggle — IDENTICAL to a real GA:
|
||||
// toggle off → hidden, toggle on → shown. Item-level globalAdminOnly entries (Settings/Ops/Software)
|
||||
// still drop out for a read-only reader via the item filter below.
|
||||
case "globalAdmin": return hasGlobalScope && globalAdminMode;
|
||||
// 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.
|
||||
// gets the standard monitoring groups above instead, so it stays hidden for them (no duplication).
|
||||
case "fleet": return isDelegated && !hasGlobalScope;
|
||||
default: return false;
|
||||
}
|
||||
@@ -229,7 +235,7 @@ export function GlobalSidebar({ children }: { children: ReactNode }) {
|
||||
return { ...group, items: filteredItems };
|
||||
});
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [isAdminOrOperator, isGlobalAdmin, hasMcpAccess, isAdminLike, canManageBootstrapTokens, bootstrapTokenEnabled, unrestrictedModeEnabled]);
|
||||
}, [isAdminOrOperator, hasGlobalScope, isGlobalAdmin, globalAdminMode, hasMcpAccess, isAdminLike, canManageBootstrapTokens, bootstrapTokenEnabled, unrestrictedModeEnabled]);
|
||||
|
||||
// Auto-expand the group containing the current pathname
|
||||
useEffect(() => {
|
||||
|
||||
Reference in New Issue
Block a user