mirror of
https://github.com/tale/headplane.git
synced 2026-08-06 12:17:41 +00:00
a1efe36ff1
Member role simply has no UI permissions — not a pending state. Co-authored-by: Amp <amp@ampcode.com> Amp-Thread-ID: https://ampcode.com/threads/T-019cce57-c9e1-7732-9709-8288127573a9
102 lines
3.3 KiB
TypeScript
102 lines
3.3 KiB
TypeScript
import { Form, Outlet, redirect } from "react-router";
|
|
|
|
import Button from "~/components/Button";
|
|
import Card from "~/components/Card";
|
|
import Footer from "~/components/Footer";
|
|
import Header from "~/components/Header";
|
|
import { Capabilities } from "~/server/web/roles";
|
|
|
|
import { Route } from "./+types/shell";
|
|
|
|
// 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 }: Route.LoaderArgs) {
|
|
try {
|
|
const principal = await context.auth.require(request);
|
|
|
|
if (
|
|
typeof context.oidc === "object" &&
|
|
principal.kind === "oidc" &&
|
|
!principal.user.onboarded &&
|
|
!request.url.endsWith("/onboarding")
|
|
) {
|
|
return redirect("/onboarding");
|
|
}
|
|
|
|
const apiKey = context.auth.getHeadscaleApiKey(principal, context.oidc?.apiKey);
|
|
const api = context.hsApi.getRuntimeClient(apiKey);
|
|
const check = context.auth.can(principal, Capabilities.ui_access);
|
|
|
|
const user =
|
|
principal.kind === "oidc"
|
|
? {
|
|
subject: principal.user.subject,
|
|
name: principal.profile.name,
|
|
email: principal.profile.email,
|
|
username: principal.profile.username,
|
|
picture: principal.profile.picture,
|
|
}
|
|
: { subject: "api_key", name: principal.displayName };
|
|
|
|
return {
|
|
config: context.hs.c,
|
|
url: context.config.headscale.public_url ?? context.config.headscale.url,
|
|
configAvailable: context.hs.readable(),
|
|
debug: context.config.debug,
|
|
user,
|
|
access: {
|
|
ui: check,
|
|
dns: context.auth.can(principal, Capabilities.read_network),
|
|
users: context.auth.can(principal, Capabilities.read_users),
|
|
policy: context.auth.can(principal, Capabilities.read_policy),
|
|
machines: context.auth.can(principal, Capabilities.read_machines),
|
|
settings: context.auth.can(principal, Capabilities.read_feature),
|
|
},
|
|
onboarding: request.url.endsWith("/onboarding"),
|
|
noAccess: !check && principal.kind === "oidc",
|
|
healthy: await api.isHealthy(),
|
|
};
|
|
} catch {
|
|
return redirect("/login", {
|
|
headers: {
|
|
"Set-Cookie": await context.auth.destroySession(request),
|
|
},
|
|
});
|
|
}
|
|
}
|
|
|
|
export default function Shell({ loaderData }: Route.ComponentProps) {
|
|
if (loaderData.noAccess && !loaderData.onboarding) {
|
|
return (
|
|
<>
|
|
<Header {...loaderData} />
|
|
<main className="container mx-auto mt-4 mb-24 overscroll-contain">
|
|
<div className="mx-auto mt-24 max-w-lg">
|
|
<Card variant="flat">
|
|
<Card.Title className="mb-4">No Access</Card.Title>
|
|
<Card.Text>
|
|
Your account doesn't have permission to access the dashboard. Contact an
|
|
administrator if you need access.
|
|
</Card.Text>
|
|
<Form action="/logout" className="mt-6" method="POST">
|
|
<Button className="w-full" type="submit" variant="light">
|
|
Sign out
|
|
</Button>
|
|
</Form>
|
|
</Card>
|
|
</div>
|
|
</main>
|
|
<Footer {...loaderData} />
|
|
</>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<>
|
|
<Header {...loaderData} />
|
|
<Outlet />
|
|
<Footer {...loaderData} />
|
|
</>
|
|
);
|
|
}
|