import { eq } from 'drizzle-orm'; import { CircleCheckIcon } from 'lucide-react'; import { LoaderFunctionArgs, Outlet, redirect, useLoaderData, } from 'react-router'; import Button from '~/components/Button'; import Card from '~/components/Card'; import Footer from '~/components/Footer'; import Header from '~/components/Header'; import type { LoadContext } from '~/server'; import { users } from '~/server/db/schema'; import { Capabilities } from '~/server/web/roles'; import toast from '~/utils/toast'; // This loads the bare minimum for the application to function // So we know that if context fails to load then well, oops? export async function loader({ request, context, }: LoaderFunctionArgs) { try { const session = await context.sessions.auth(request); if ( context.oidc && session.user.subject !== 'unknown-non-oauth' && !request.url.endsWith('/onboarding') ) { const [user] = await context.db .select() .from(users) .where(eq(users.sub, session.user.subject)) .limit(1); if (!user?.onboarded) { return redirect('/onboarding'); } } 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.user, uiAccess: check, access: { ui: await context.sessions.check(request, Capabilities.ui_access), dns: await context.sessions.check(request, Capabilities.read_network), users: await context.sessions.check(request, Capabilities.read_users), policy: await context.sessions.check(request, Capabilities.read_policy), machines: await context.sessions.check( request, Capabilities.read_machines, ), settings: await context.sessions.check( request, Capabilities.read_feature, ), }, onboarding: request.url.endsWith('/onboarding'), healthy: await context.client.healthcheck(), }; } catch { return redirect('/login', { headers: { 'Set-Cookie': await context.sessions.destroySession(), }, }); } } export default function Shell() { const data = useLoaderData(); return ( <>
{/* Always show the outlet if we are onboarding */} {(data.onboarding ? true : data.uiAccess) ? ( ) : (
Connected
Connect to Tailscale with your devices to access this Tailnet. Use this command to help you get started:

Click this button to copy the command.

Your account does not have access to the UI. Please contact your administrator if you believe this is a mistake.

)}