import { Suspense, useEffect, useLayoutEffect } from 'react'; import { Navigate, useLocation } from 'react-router-dom-v5-compat'; import { locationSearchToObject, navigationLogger, reportPageview } from '@grafana/runtime'; import { ErrorBoundary } from '@grafana/ui'; import { useGrafana } from '../context/GrafanaContext'; import { contextSrv } from '../services/context_srv'; import { GrafanaRouteError } from './GrafanaRouteError'; import { GrafanaRouteLoading } from './GrafanaRouteLoading'; import { GrafanaRouteComponentProps, RouteDescriptor } from './types'; export interface Props extends Pick<GrafanaRouteComponentProps, 'route' | 'location'> {} export function GrafanaRoute(props: Props) { const { chrome, keybindings } = useGrafana(); chrome.setMatchedRoute(props.route); useLayoutEffect(() => { keybindings.clearAndInitGlobalBindings(props.route); }, [keybindings, props.route]); useEffect(() => { updateBodyClassNames(props.route); cleanupDOM(); navigationLogger('GrafanaRoute', false, 'Mounted', props.route); return () => { navigationLogger('GrafanaRoute', false, 'Unmounted', props.route); updateBodyClassNames(props.route, true); }; // props.match instance change even though only query params changed so to make this effect only trigger on route mount we have to disable exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps }, []); useEffect(() => { cleanupDOM(); reportPageview(); navigationLogger('GrafanaRoute', false, 'Updated', props); }); navigationLogger('GrafanaRoute', false, 'Rendered', props.route); return ( <ErrorBoundary dependencies={[props.route]}> {({ error, errorInfo }) => { if (error) { return <GrafanaRouteError error={error} errorInfo={errorInfo} />; } return ( <Suspense fallback={<GrafanaRouteLoading />}> <props.route.component {...props} queryParams={locationSearchToObject(props.location.search)} /> </Suspense> ); }} </ErrorBoundary> ); } export function GrafanaRouteWrapper({ route }: Pick<Props, 'route'>) { const location = useLocation(); // Add this logging to see ALL routes console.log(`[ROUTE] Processing:`, route.path, `URL:`, location.pathname); const roles = route.roles ? route.roles() : []; if (roles?.length) { const hasRoleAccess = roles.some((r: string) => contextSrv.hasRole(r)); const hasUserMgrAccess = hasUserManagementAccess(route); console.log( `[ROUTE] Roles required:`, roles, `Has role access:`, hasRoleAccess, `Has user mgmt access:`, hasUserMgrAccess ); if (!hasRoleAccess && !hasUserMgrAccess) { console.log(`[ROUTE] BLOCKING access to:`, route.path); return <Navigate replace to="/" />; } } console.log(`[ROUTE] ALLOWING access to:`, route.path); return <GrafanaRoute route={route} location={location} />; } // Make sure you still have this function: function hasUserManagementAccess(route: RouteDescriptor): boolean { console.log(`[DEBUG] Route path: ${route.path}`); console.log(`[DEBUG] User email: ${contextSrv.user.email}`); console.log(`[DEBUG] Is user manager: ${contextSrv.isUserManager()}`); if (!contextSrv.isUserManager()) { return false; } const userMgmtPaths = ['/admin/users', '/admin/teams', '/org/users', '/org/teams', 'admin/users', 'admin/teams']; const hasAccess = userMgmtPaths.some((path) => route.path?.includes(path)); console.log(`[DEBUG] Has user mgmt access: ${hasAccess}`); return hasAccess; } function getPageClasses(route: RouteDescriptor) { return route.pageClass ? route.pageClass.split(' ') : []; } function updateBodyClassNames(route: RouteDescriptor, clear = false) { for (const cls of getPageClasses(route)) { if (clear) { document.body.classList.remove(cls); } else { document.body.classList.add(cls); } } } function cleanupDOM() { document.body.classList.remove('sidemenu-open--xs'); // cleanup tooltips const tooltipById = document.getElementById('tooltip'); tooltipById?.parentElement?.removeChild(tooltipById); const tooltipsByClass = document.querySelectorAll('.tooltip'); for (let i = 0; i < tooltipsByClass.length; i++) { const tooltip = tooltipsByClass[i]; tooltip.parentElement?.removeChild(tooltip); } }
Preview:
downloadDownload PNG
downloadDownload JPEG
downloadDownload SVG
Tip: You can change the style, width & colours of the snippet with the inspect tool before clicking Download!
Click to optimize width for Twitter