headplane/app/routes/machines/overview.tsx

142 lines
4.1 KiB
TypeScript
Raw Normal View History

2024-12-31 10:30:14 +05:30
import { InfoIcon } from '@primer/octicons-react';
2024-12-31 10:31:50 +05:30
import type { ActionFunctionArgs, LoaderFunctionArgs } from 'react-router';
2024-12-31 10:30:14 +05:30
import { useLoaderData } from 'react-router';
import { Button, Tooltip, TooltipTrigger } from 'react-aria-components';
2024-12-31 10:30:14 +05:30
import Code from '~/components/Code';
import Link from '~/components/Link';
import { cn } from '~/utils/cn';
import { loadContext } from '~/utils/config/headplane';
import { loadConfig } from '~/utils/config/headscale';
import { pull } from '~/utils/headscale';
import { getSession } from '~/utils/sessions.server';
2024-12-31 10:30:14 +05:30
import { useLiveData } from '~/utils/useLiveData';
import type { Machine, Route, User } from '~/types';
2025-01-08 14:33:16 +05:30
import { queryAgent, initAgentSocket } from '~/utils/ws-agent';
2024-12-31 10:30:14 +05:30
import { menuAction } from './action';
import MachineRow from './components/machine';
import NewMachine from './dialogs/new';
2025-01-08 14:33:16 +05:30
export async function loader({ request, context: lC }: LoaderFunctionArgs) {
2024-12-31 10:30:14 +05:30
const session = await getSession(request.headers.get('Cookie'));
const [machines, routes, users] = await Promise.all([
pull<{ nodes: Machine[] }>('v1/node', session.get('hsApiKey')!),
pull<{ routes: Route[] }>('v1/routes', session.get('hsApiKey')!),
pull<{ users: User[] }>('v1/user', session.get('hsApiKey')!),
2024-12-31 10:30:14 +05:30
]);
2025-01-08 14:33:16 +05:30
initAgentSocket(lC);
const stats = await queryAgent(machines.nodes.map((node) => node.nodeKey));
2024-12-31 10:30:14 +05:30
const context = await loadContext();
let magic: string | undefined;
if (context.config.read) {
2024-12-31 10:30:14 +05:30
const config = await loadConfig();
if (config.dns.magic_dns) {
2024-12-31 10:30:14 +05:30
magic = config.dns.base_domain;
}
if (config.dns.use_username_in_magic_dns) {
2024-12-31 10:30:14 +05:30
magic = `[user].${magic}`;
}
}
return {
2024-05-01 02:47:45 -04:00
nodes: machines.nodes,
routes: routes.routes,
users: users.users,
magic,
2025-01-08 14:33:16 +05:30
stats,
server: context.headscaleUrl,
publicServer: context.headscalePublicUrl,
2024-12-31 10:30:14 +05:30
};
}
export async function action({ request }: ActionFunctionArgs) {
2024-12-31 10:30:14 +05:30
return menuAction(request);
}
export default function Page() {
2024-12-31 10:30:14 +05:30
useLiveData({ interval: 3000 });
const data = useLoaderData<typeof loader>();
return (
<>
<div className="flex justify-between items-center mb-8">
<div className="flex flex-col w-2/3">
2024-12-31 10:30:14 +05:30
<h1 className="text-2xl font-medium mb-4">Machines</h1>
<p className="text-gray-700 dark:text-gray-300">
Manage the devices connected to your Tailnet.{' '}
<Link
to="https://tailscale.com/kb/1372/manage-devices"
name="Tailscale Manage Devices Documentation"
>
Learn more
</Link>
</p>
</div>
<NewMachine
server={data.publicServer ?? data.server}
users={data.users}
/>
</div>
<table className="table-auto w-full rounded-lg">
<thead className="text-gray-500 dark:text-gray-400">
<tr className="text-left uppercase text-xs font-bold px-0.5">
<th className="pb-2">Name</th>
<th className="pb-2">
<div className="flex items-center gap-x-1">
Addresses
2024-12-31 10:30:14 +05:30
{data.magic ? (
<TooltipTrigger delay={0}>
<Button>
<InfoIcon className="w-4 h-4" />
</Button>
<Tooltip
className={cn(
'text-sm max-w-xs p-2 rounded-lg mb-2',
'bg-white dark:bg-zinc-800',
'border border-gray-200 dark:border-zinc-700',
)}
2024-12-31 10:30:14 +05:30
>
Since MagicDNS is enabled, you can access devices based on
their name and also at{' '}
<Code>
[name].
{data.magic}
</Code>
</Tooltip>
</TooltipTrigger>
) : undefined}
</div>
</th>
2025-01-08 14:33:16 +05:30
<th className="pb-2">Version</th>
<th className="pb-2">Last Seen</th>
</tr>
</thead>
2024-12-31 10:30:14 +05:30
<tbody
className={cn(
'divide-y divide-zinc-200 dark:divide-zinc-700 align-top',
'border-t border-zinc-200 dark:border-zinc-700',
)}
>
2024-12-31 10:30:14 +05:30
{data.nodes.map((machine) => (
<MachineRow
key={machine.id}
machine={machine}
2024-12-31 10:30:14 +05:30
routes={data.routes.filter(
(route) => route.node.id === machine.id,
)}
users={data.users}
magic={data.magic}
2025-01-08 14:33:16 +05:30
stats={data.stats?.[machine.nodeKey]}
/>
))}
</tbody>
</table>
</>
2024-12-31 10:30:14 +05:30
);
}