diff --git a/app/components/Header.tsx b/app/components/Header.tsx index c9b561a..1288e9a 100644 --- a/app/components/Header.tsx +++ b/app/components/Header.tsx @@ -15,6 +15,7 @@ import cn from '~/utils/cn'; interface Props { configAvailable: boolean; + uiAccess: boolean; user?: AuthSession['user']; } @@ -135,29 +136,31 @@ export default function Header(data: Props) { ) : undefined} - + {data.uiAccess ? ( + + ) : undefined} ); } diff --git a/app/layouts/shell.tsx b/app/layouts/shell.tsx index 996ea0e..52edaca 100644 --- a/app/layouts/shell.tsx +++ b/app/layouts/shell.tsx @@ -1,12 +1,15 @@ +import { BanIcon } from 'lucide-react'; import { LoaderFunctionArgs, Outlet, redirect, useLoaderData, } from 'react-router'; +import Card from '~/components/Card'; import Footer from '~/components/Footer'; import Header from '~/components/Header'; import type { LoadContext } from '~/server'; +import { Capabilities } from '~/server/web/roles'; // This loads the bare minimum for the application to function // So we know that if context fails to load then well, oops? @@ -25,12 +28,14 @@ export async function loader({ }); } + const check = await context.sessions.check(request, Capabilities.ui_access); return { config: context.hs.c, url: context.config.headscale.public_url ?? context.config.headscale.url, configAvailable: context.hs.readable(), debug: context.config.debug, user: session.get('user'), + uiAccess: check, }; } catch { // No session, so we can just return @@ -40,10 +45,24 @@ export async function loader({ export default function Shell() { const data = useLoaderData(); + return ( <>
- + {data.uiAccess ? ( + + ) : ( + +
+ Access Denied + +
+ + Your account does not have access to the UI. Please contact your + administrator. + +
+ )}