import { type ActionFunctionArgs } from '@remix-run/node' import { json, useFetcher, useLoaderData } from '@remix-run/react' import { useState } from 'react' import { Button, Input } from 'react-aria-components' import Code from '~/components/Code' import Notice from '~/components/Notice' import Spinner from '~/components/Spinner' import Switch from '~/components/Switch' import TableList from '~/components/TableList' import { cn } from '~/utils/cn' import { loadContext } from '~/utils/config/headplane' import { loadConfig, patchConfig } from '~/utils/config/headscale' import { restartHeadscale } from '~/utils/docker' import { getSession } from '~/utils/sessions' import { useLiveData } from '~/utils/useLiveData' import Domains from './domains' import MagicModal from './magic' import RenameModal from './rename' // We do not want to expose every config value export async function loader() { const context = await loadContext() if (!context.config.read) { throw new Error('No configuration is available') } const config = await loadConfig() const dns = { prefixes: config.prefixes, magicDns: config.dns_config.magic_dns, baseDomain: config.dns_config.base_domain, overrideLocal: config.dns_config.override_local_dns, nameservers: config.dns_config.nameservers, splitDns: config.dns_config.restricted_nameservers, searchDomains: config.dns_config.domains, extraRecords: config.dns_config.extra_records, } return { ...dns, ...context, } } export async function action({ request }: ActionFunctionArgs) { const session = await getSession(request.headers.get('Cookie')) if (!session.has('hsApiKey')) { return json({ success: false }, { status: 401, }) } const context = await loadContext() if (!context.config.write) { return json({ success: false }, { status: 403, }) } const data = await request.json() as Record await patchConfig(data) await restartHeadscale() return json({ success: true }) } export default function Page() { useLiveData({ interval: 5000 }) const data = useLoaderData() const fetcher = useFetcher() const [localOverride, setLocalOverride] = useState(data.overrideLocal) const [ns, setNs] = useState('') return (
{data.config.write ? undefined : ( The Headscale configuration is read-only. You cannot make changes to the configuration )}

Nameservers

Set the nameservers used by devices on the Tailnet to resolve DNS queries.

Global Nameservers

Override local DNS { fetcher.submit({ // eslint-disable-next-line @typescript-eslint/naming-convention 'dns_config.override_local_dns': !localOverride, }, { method: 'PATCH', encType: 'application/json', }) setLocalOverride(!localOverride) }} />
{data.nameservers.map((ns, index) => ( // eslint-disable-next-line react/no-array-index-key

{ns}

))} {data.config.write ? ( { setNs(event.target.value) }} /> {fetcher.state === 'idle' ? ( ) : ( )} ) : undefined}
{/* TODO: Split DNS and Custom A Records */}

Magic DNS

Automatically register domain names for each device on the tailnet. Devices will be accessible at {' '} [device].[user]. {data.baseDomain} {' '} when Magic DNS is enabled.

) }