import { InfoIcon } from '@primer/octicons-react'; import type { ActionFunctionArgs, LoaderFunctionArgs } from 'react-router'; import { useLoaderData } from 'react-router'; import Code from '~/components/Code'; import { ErrorPopup } from '~/components/Error'; import Link from '~/components/Link'; import Tooltip from '~/components/Tooltip'; import type { LoadContext } from '~/server'; import { Capabilities } from '~/server/web/roles'; import type { Machine, Route, User } from '~/types'; import cn from '~/utils/cn'; import { mapNodes } from '~/utils/node-info'; import MachineRow from './components/machine-row'; import NewMachine from './dialogs/new'; import { machineAction } from './machine-actions'; export async function loader({ request, context, }: LoaderFunctionArgs) { const session = await context.sessions.auth(request); const user = session.get('user'); if (!user) { throw new Error('Missing user session. Please log in again.'); } const check = await context.sessions.check( request, Capabilities.read_machines, ); if (!check) { // Not authorized to view this page throw new Error( 'You do not have permission to view this page. Please contact your administrator.', ); } const writablePermission = await context.sessions.check( request, Capabilities.write_machines, ); const [{ nodes }, { routes }, { users }] = await Promise.all([ context.client.get<{ nodes: Machine[] }>( 'v1/node', session.get('api_key')!, ), context.client.get<{ routes: Route[] }>( 'v1/routes', session.get('api_key')!, ), context.client.get<{ users: User[] }>('v1/user', session.get('api_key')!), ]); let magic: string | undefined; if (context.hs.readable()) { if (context.hs.c?.dns.magic_dns) { magic = context.hs.c.dns.base_domain; } } const stats = await context.agents?.lookup(nodes.map((node) => node.nodeKey)); const populatedNodes = mapNodes(nodes, routes, stats); return { populatedNodes, nodes, routes, users, magic, server: context.config.headscale.url, publicServer: context.config.headscale.public_url, agent: context.agents?.agentID(), writable: writablePermission, preAuth: await context.sessions.check( request, Capabilities.generate_authkeys, ), subject: user.subject, }; } export async function action(request: ActionFunctionArgs) { return machineAction(request); } export default function Page() { const data = useLoaderData(); return ( <>

Machines

Manage the devices connected to your Tailnet.{' '} Learn more

{/* We only want to show the version column if there are agents */} {data.agent !== undefined ? ( ) : undefined} {data.populatedNodes.map((machine) => ( ))}
Name

Addresses

{data.magic ? ( Since MagicDNS is enabled, you can access devices based on their name and also at{' '} [name]. {data.magic} ) : undefined}
VersionLast Seen
); }