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 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 [machines, 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; } } return { nodes: machines.nodes, routes: routes.routes, users: users.users, magic, server: context.config.headscale.url, publicServer: context.config.headscale.public_url, agents: context.agents?.tailnetIDs(), stats: context.agents?.lookup(machines.nodes.map((node) => node.nodeKey)), writable: writablePermission, 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.agents !== undefined ? ( ) : undefined} {data.nodes.map((machine) => ( route.node.id === machine.id, )} users={data.users} magic={data.magic} // If we pass undefined, the column will not be rendered // This is useful for when there are no agents configured isAgent={data.agents?.includes(machine.id)} stats={data.stats?.[machine.nodeKey]} isDisabled={ data.writable ? false // If the user has write permissions, they can edit all machines : machine.user.providerId?.split('/').pop() !== data.subject } /> ))}
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
); } export function ErrorBoundary() { return ; }