mirror of
https://github.com/tale/headplane.git
synced 2026-08-31 01:09:25 +00:00
feat: respect context in server
This commit is contained in:
@@ -7,17 +7,18 @@ import Link from '~/components/Link';
|
||||
import Notice from '~/components/Notice';
|
||||
import Spinner from '~/components/Spinner';
|
||||
import Tabs from '~/components/Tabs';
|
||||
import { hs_getConfig } from '~/utils/config/loader';
|
||||
import { HeadscaleError, pull, put } from '~/utils/headscale';
|
||||
import log from '~/utils/log';
|
||||
import { send } from '~/utils/res';
|
||||
import { getSession } from '~/utils/sessions.server';
|
||||
import { hs_getConfig } from '~/utils/state';
|
||||
import toast from '~/utils/toast';
|
||||
import type { AppContext } from '~server/context/app';
|
||||
import { Differ, Editor } from './components/cm.client';
|
||||
import { ErrorView } from './components/error';
|
||||
import { Unavailable } from './components/unavailable';
|
||||
|
||||
export async function loader({ request }: LoaderFunctionArgs) {
|
||||
export async function loader({ request }: LoaderFunctionArgs<AppContext>) {
|
||||
const session = await getSession(request.headers.get('Cookie'));
|
||||
|
||||
// The way policy is handled in 0.23 of Headscale and later is verbose.
|
||||
|
||||
@@ -10,10 +10,14 @@ import Code from '~/components/Code';
|
||||
import Input from '~/components/Input';
|
||||
import type { Key } from '~/types';
|
||||
import { pull } from '~/utils/headscale';
|
||||
import { noContext } from '~/utils/log';
|
||||
import { commitSession, getSession } from '~/utils/sessions.server';
|
||||
import { hp_getConfig } from '~/utils/state';
|
||||
import type { AppContext } from '~server/context/app';
|
||||
|
||||
export async function loader({ request }: LoaderFunctionArgs) {
|
||||
export async function loader({
|
||||
request,
|
||||
context,
|
||||
}: LoaderFunctionArgs<AppContext>) {
|
||||
const session = await getSession(request.headers.get('Cookie'));
|
||||
if (session.has('hsApiKey')) {
|
||||
return redirect('/machines', {
|
||||
@@ -23,28 +27,37 @@ export async function loader({ request }: LoaderFunctionArgs) {
|
||||
});
|
||||
}
|
||||
|
||||
const context = hp_getConfig();
|
||||
if (!context) {
|
||||
throw noContext();
|
||||
}
|
||||
|
||||
// Only set if OIDC is properly enabled anyways
|
||||
if (context.oidc?.disable_api_key_login) {
|
||||
const ctx = context.context;
|
||||
if (ctx.oidc?.disable_api_key_login) {
|
||||
return redirect('/oidc/start');
|
||||
}
|
||||
|
||||
return {
|
||||
oidc: context.oidc?.issuer,
|
||||
apiKey: !context.oidc?.disable_api_key_login,
|
||||
oidc: ctx.oidc?.issuer,
|
||||
apiKey: !ctx.oidc?.disable_api_key_login,
|
||||
};
|
||||
}
|
||||
|
||||
export async function action({ request }: ActionFunctionArgs) {
|
||||
export async function action({
|
||||
request,
|
||||
context,
|
||||
}: ActionFunctionArgs<AppContext>) {
|
||||
const formData = await request.formData();
|
||||
const oidcStart = formData.get('oidc-start');
|
||||
const session = await getSession(request.headers.get('Cookie'));
|
||||
|
||||
if (oidcStart) {
|
||||
const context = hp_getConfig();
|
||||
if (!context) {
|
||||
throw noContext();
|
||||
}
|
||||
|
||||
if (!context.oidc) {
|
||||
const ctx = context.context;
|
||||
if (!ctx.oidc) {
|
||||
throw new Error('An invalid OIDC configuration was provided');
|
||||
}
|
||||
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
import { type LoaderFunctionArgs, redirect } from 'react-router';
|
||||
import { finishAuthFlow, formatError, getRedirectUri } from '~/utils/oidc';
|
||||
import { noContext } from '~/utils/log';
|
||||
import { finishAuthFlow, formatError } from '~/utils/oidc';
|
||||
import { send } from '~/utils/res';
|
||||
import { commitSession, getSession } from '~/utils/sessions.server';
|
||||
import { hp_getConfig } from '~/utils/state';
|
||||
import type { AppContext } from '~server/context/app';
|
||||
|
||||
export async function loader({ request }: LoaderFunctionArgs) {
|
||||
export async function loader({
|
||||
request,
|
||||
context,
|
||||
}: LoaderFunctionArgs<AppContext>) {
|
||||
// Check if we have 0 query parameters
|
||||
const url = new URL(request.url);
|
||||
if (url.searchParams.toString().length === 0) {
|
||||
@@ -16,7 +20,11 @@ export async function loader({ request }: LoaderFunctionArgs) {
|
||||
return redirect('/machines');
|
||||
}
|
||||
|
||||
const { oidc } = hp_getConfig();
|
||||
if (!context) {
|
||||
throw noContext();
|
||||
}
|
||||
|
||||
const { oidc } = context.context;
|
||||
if (!oidc) {
|
||||
throw new Error('An invalid OIDC configuration was provided');
|
||||
}
|
||||
|
||||
@@ -1,17 +1,23 @@
|
||||
import { type LoaderFunctionArgs, redirect } from 'react-router';
|
||||
import { noContext } from '~/utils/log';
|
||||
import { beginAuthFlow, getRedirectUri } from '~/utils/oidc';
|
||||
import { commitSession, getSession } from '~/utils/sessions.server';
|
||||
import { hp_getConfig } from '~/utils/state';
|
||||
import type { AppContext } from '~server/context/app';
|
||||
|
||||
export async function loader({ request }: LoaderFunctionArgs) {
|
||||
export async function loader({
|
||||
request,
|
||||
context,
|
||||
}: LoaderFunctionArgs<AppContext>) {
|
||||
const session = await getSession(request.headers.get('Cookie'));
|
||||
if (session.has('hsApiKey')) {
|
||||
return redirect('/machines');
|
||||
}
|
||||
|
||||
// This is a hold-over from the old code
|
||||
// TODO: Rewrite checkOIDC in the context loader
|
||||
const { oidc } = hp_getConfig();
|
||||
if (!context) {
|
||||
throw noContext();
|
||||
}
|
||||
|
||||
const { oidc } = context.context;
|
||||
if (!oidc) {
|
||||
throw new Error('An invalid OIDC configuration was provided');
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { ActionFunctionArgs, data } from 'react-router';
|
||||
import { hs_patchConfig } from '~/utils/config/loader';
|
||||
import { hs_getConfig, hs_patchConfig } from '~/utils/config/loader';
|
||||
import { auth } from '~/utils/sessions.server';
|
||||
import { hs_getConfig } from '~/utils/state';
|
||||
|
||||
export async function dnsAction({ request }: ActionFunctionArgs) {
|
||||
const session = await auth(request);
|
||||
|
||||
@@ -2,7 +2,7 @@ import type { ActionFunctionArgs } from 'react-router';
|
||||
import { useLoaderData } from 'react-router';
|
||||
import Code from '~/components/Code';
|
||||
import Notice from '~/components/Notice';
|
||||
import { hs_getConfig } from '~/utils/state';
|
||||
import { hs_getConfig } from '~/utils/config/loader';
|
||||
import ManageDomains from './components/manage-domains';
|
||||
import ManageNS from './components/manage-ns';
|
||||
import ManageRecords from './components/manage-records';
|
||||
|
||||
@@ -109,9 +109,13 @@ export async function menuAction(request: ActionFunctionArgs['request']) {
|
||||
const to = String(data.get('to'));
|
||||
|
||||
try {
|
||||
await post(`v1/node/${id}/user?user=${to}`, session.get('hsApiKey')!);
|
||||
await post(`v1/node/${id}/user`, session.get('hsApiKey')!, {
|
||||
user: to,
|
||||
});
|
||||
|
||||
return { message: `Moved node ${id} to ${to}` };
|
||||
} catch {
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
return send(
|
||||
{ message: `Failed to move node ${id} to ${to}` },
|
||||
{
|
||||
|
||||
@@ -11,9 +11,9 @@ import StatusCircle from '~/components/StatusCircle';
|
||||
import Tooltip from '~/components/Tooltip';
|
||||
import type { Machine, Route, User } from '~/types';
|
||||
import cn from '~/utils/cn';
|
||||
import { hs_getConfig } from '~/utils/config/loader';
|
||||
import { pull } from '~/utils/headscale';
|
||||
import { getSession } from '~/utils/sessions.server';
|
||||
import { hs_getConfig } from '~/utils/state';
|
||||
import { menuAction } from './action';
|
||||
import MenuOptions from './components/menu';
|
||||
import Routes from './dialogs/routes';
|
||||
|
||||
@@ -12,12 +12,17 @@ import { getSession } from '~/utils/sessions.server';
|
||||
import { initAgentSocket, queryAgent } from '~/utils/ws-agent';
|
||||
|
||||
import Tooltip from '~/components/Tooltip';
|
||||
import { hp_getConfig, hs_getConfig } from '~/utils/state';
|
||||
import { hs_getConfig } from '~/utils/config/loader';
|
||||
import { noContext } from '~/utils/log';
|
||||
import { AppContext } from '~server/context/app';
|
||||
import { menuAction } from './action';
|
||||
import MachineRow from './components/machine';
|
||||
import NewMachine from './dialogs/new';
|
||||
|
||||
export async function loader({ request, context: lC }: LoaderFunctionArgs) {
|
||||
export async function loader({
|
||||
request,
|
||||
context,
|
||||
}: LoaderFunctionArgs<AppContext>) {
|
||||
const session = await getSession(request.headers.get('Cookie'));
|
||||
const [machines, routes, users] = await Promise.all([
|
||||
pull<{ nodes: Machine[] }>('v1/node', session.get('hsApiKey')!),
|
||||
@@ -25,10 +30,14 @@ export async function loader({ request, context: lC }: LoaderFunctionArgs) {
|
||||
pull<{ users: User[] }>('v1/user', session.get('hsApiKey')!),
|
||||
]);
|
||||
|
||||
initAgentSocket(lC);
|
||||
if (!context) {
|
||||
throw noContext();
|
||||
}
|
||||
|
||||
initAgentSocket(context);
|
||||
|
||||
const stats = await queryAgent(machines.nodes.map((node) => node.nodeKey));
|
||||
const context = hp_getConfig();
|
||||
const ctx = context.context;
|
||||
const { mode, config } = hs_getConfig();
|
||||
|
||||
let magic: string | undefined;
|
||||
@@ -45,8 +54,8 @@ export async function loader({ request, context: lC }: LoaderFunctionArgs) {
|
||||
users: users.users,
|
||||
magic,
|
||||
stats,
|
||||
server: context.headscale.url,
|
||||
publicServer: context.headscale.public_url,
|
||||
server: ctx.headscale.url,
|
||||
publicServer: ctx.headscale.public_url,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -7,9 +7,10 @@ import Select from '~/components/Select';
|
||||
import TableList from '~/components/TableList';
|
||||
import type { PreAuthKey, User } from '~/types';
|
||||
import { post, pull } from '~/utils/headscale';
|
||||
import { noContext } from '~/utils/log';
|
||||
import { send } from '~/utils/res';
|
||||
import { getSession } from '~/utils/sessions.server';
|
||||
import { hp_getConfig } from '~/utils/state';
|
||||
import type { AppContext } from '~server/context/app';
|
||||
import AuthKeyRow from './components/key';
|
||||
import AddPreAuthKey from './dialogs/new';
|
||||
|
||||
@@ -90,14 +91,21 @@ export async function action({ request }: ActionFunctionArgs) {
|
||||
}
|
||||
}
|
||||
|
||||
export async function loader({ request }: LoaderFunctionArgs) {
|
||||
const context = hp_getConfig();
|
||||
export async function loader({
|
||||
request,
|
||||
context,
|
||||
}: LoaderFunctionArgs<AppContext>) {
|
||||
const session = await getSession(request.headers.get('Cookie'));
|
||||
const users = await pull<{ users: User[] }>(
|
||||
'v1/user',
|
||||
session.get('hsApiKey')!,
|
||||
);
|
||||
|
||||
if (!context) {
|
||||
throw noContext();
|
||||
}
|
||||
|
||||
const ctx = context.context;
|
||||
const preAuthKeys = await Promise.all(
|
||||
users.users.map((user) => {
|
||||
const qp = new URLSearchParams();
|
||||
@@ -113,7 +121,7 @@ export async function loader({ request }: LoaderFunctionArgs) {
|
||||
return {
|
||||
keys: preAuthKeys.flatMap((keys) => keys.preAuthKeys),
|
||||
users: users.users,
|
||||
server: context.headscale.public_url ?? context.headscale.url,
|
||||
server: ctx.headscale.public_url ?? ctx.headscale.url,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -1,22 +1,23 @@
|
||||
import { useMemo } from 'react';
|
||||
import { useLoaderData, type LoaderFunctionArgs } from 'react-router';
|
||||
import { getSession, commitSession } from '~/utils/sessions.server'
|
||||
import { queryAgent } from '~/utils/ws-agent'
|
||||
import AgentManagement from './components/agent/manage'
|
||||
import { type LoaderFunctionArgs, useLoaderData } from 'react-router';
|
||||
import { commitSession, getSession } from '~/utils/sessions.server';
|
||||
import { queryAgent } from '~/utils/ws-agent';
|
||||
import AgentManagement from './components/agent/manage';
|
||||
|
||||
export async function loader({ request, context }: LoaderFunctionArgs) {
|
||||
const { ws, wsAuthKey } = context;
|
||||
const session = await getSession(request.headers.get('Cookie'));
|
||||
const onboarding = session.get('agent_onboarding') ?? false;
|
||||
|
||||
const nodeKey = 'nodekey:542dad28354eb8d51e240aada7adf0222ba3ecc74af0bbd56123f03eefdb391b'
|
||||
const nodeKey =
|
||||
'nodekey:542dad28354eb8d51e240aada7adf0222ba3ecc74af0bbd56123f03eefdb391b';
|
||||
const stats = await queryAgent([nodeKey]);
|
||||
|
||||
return {
|
||||
configured: wsAuthKey !== undefined,
|
||||
onboarding,
|
||||
stats: stats[nodeKey]
|
||||
}
|
||||
stats: stats?.[nodeKey],
|
||||
};
|
||||
}
|
||||
|
||||
export default function Page() {
|
||||
@@ -24,21 +25,18 @@ export default function Page() {
|
||||
|
||||
// Whether we show the onboarding or management UI
|
||||
const management = useMemo(() => {
|
||||
return data.configured && (data.onboarding === false)
|
||||
return data.configured && data.onboarding === false;
|
||||
}, [data.configured, data.onboarding]);
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-8 max-w-screen-lg">
|
||||
{management ? (
|
||||
<AgentManagement
|
||||
reachable={true}
|
||||
hostInfo={data.stats}
|
||||
/>
|
||||
<AgentManagement reachable={true} hostInfo={data.stats} />
|
||||
) : (
|
||||
<div>
|
||||
<h1>Local Agent Coming Soon</h1>
|
||||
</div>
|
||||
<div>
|
||||
<h1>Local Agent Coming Soon</h1>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import { Building2, House, Key } from 'lucide-react';
|
||||
import Card from '~/components/Card';
|
||||
import Link from '~/components/Link';
|
||||
import { HeadplaneConfig } from '~/utils/state';
|
||||
import type { AppContext } from '~server/context/app';
|
||||
import CreateUser from '../dialogs/create-user';
|
||||
|
||||
interface Props {
|
||||
oidc?: NonNullable<HeadplaneConfig['oidc']>;
|
||||
oidc?: NonNullable<AppContext['context']['oidc']>;
|
||||
}
|
||||
|
||||
export default function ManageBanner({ oidc }: Props) {
|
||||
|
||||
@@ -14,14 +14,22 @@ import cn from '~/utils/cn';
|
||||
import { pull } from '~/utils/headscale';
|
||||
import { getSession } from '~/utils/sessions.server';
|
||||
|
||||
import { hp_getConfig, hs_getConfig } from '~/utils/state';
|
||||
import { hs_getConfig } from '~/utils/config/loader';
|
||||
import { noContext } from '~/utils/log';
|
||||
import type { AppContext } from '~server/context/app';
|
||||
import ManageBanner from './components/manage-banner';
|
||||
import DeleteUser from './dialogs/delete-user';
|
||||
import RenameUser from './dialogs/rename-user';
|
||||
import { userAction } from './user-actions';
|
||||
|
||||
export async function loader({ request }: LoaderFunctionArgs) {
|
||||
export async function loader({
|
||||
request,
|
||||
context,
|
||||
}: LoaderFunctionArgs<AppContext>) {
|
||||
const session = await getSession(request.headers.get('Cookie'));
|
||||
if (!context) {
|
||||
throw noContext();
|
||||
}
|
||||
|
||||
const [machines, apiUsers] = await Promise.all([
|
||||
pull<{ nodes: Machine[] }>('v1/node', session.get('hsApiKey')!),
|
||||
@@ -33,7 +41,7 @@ export async function loader({ request }: LoaderFunctionArgs) {
|
||||
machines: machines.nodes.filter((machine) => machine.user.id === user.id),
|
||||
}));
|
||||
|
||||
const context = hp_getConfig();
|
||||
const ctx = context.context;
|
||||
const { mode, config } = hs_getConfig();
|
||||
let magic: string | undefined;
|
||||
|
||||
@@ -44,7 +52,7 @@ export async function loader({ request }: LoaderFunctionArgs) {
|
||||
}
|
||||
|
||||
return {
|
||||
oidc: context.oidc,
|
||||
oidc: ctx.oidc,
|
||||
magic,
|
||||
users,
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user