diff --git a/CHANGELOG.md b/CHANGELOG.md index dd7be6d..cd803da 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ - The docker container now runs in a non-root, distroless image (closes [#255](https://github.com/tale/headplane/issues/255)). - You may need to run `chown -R 65532:65532 ` on your data directory to ensure the container can write to it. - Removing a Split DNS record will no longer make the split domain unresolvable by clients (closes [#231](https://github.com/tale/headplane/issues/231)). +- Reintroduce the toggle for overriding local DNS settings in the Headscale config (closes [#236](https://github.com/tale/headplane/issues/236)). ### 0.6.0 (May 25, 2025) - Headplane 0.6.0 now requires **Headscale 0.26.0** or newer. diff --git a/app/components/Switch.tsx b/app/components/Switch.tsx index 2230774..6c73a39 100644 --- a/app/components/Switch.tsx +++ b/app/components/Switch.tsx @@ -11,6 +11,7 @@ import cn from '~/utils/cn'; export interface SwitchProps extends AriaSwitchProps { label: string; className?: string; + switchClassName?: string; } export default function Switch(props: SwitchProps) { @@ -45,6 +46,7 @@ export default function Switch(props: SwitchProps) { state.isSelected && 'bg-headplane-900 dark:bg-headplane-950', isFocusVisible && 'ring-2', props.isDisabled && 'opacity-50', + props.className, )} > diff --git a/app/routes/dns/components/manage-ns.tsx b/app/routes/dns/components/manage-ns.tsx index 74a3948..285277f 100644 --- a/app/routes/dns/components/manage-ns.tsx +++ b/app/routes/dns/components/manage-ns.tsx @@ -1,16 +1,24 @@ -import { Form } from 'react-router'; +import { Info } from 'lucide-react'; +import { Form, useSubmit } from 'react-router'; import Button from '~/components/Button'; import Link from '~/components/Link'; +import Switch from '~/components/Switch'; import TableList from '~/components/TableList'; +import Tooltip from '~/components/Tooltip'; import cn from '~/utils/cn'; import AddNS from '../dialogs/add-ns'; interface Props { nameservers: Record; + overrideLocalDns: boolean; isDisabled: boolean; } -export default function ManageNS({ nameservers, isDisabled }: Props) { +export default function ManageNS({ + nameservers, + isDisabled, + overrideLocalDns, +}: Props) { return (

Nameservers

@@ -31,6 +39,7 @@ export default function ManageNS({ nameservers, isDisabled }: Props) { isGlobal={key === 'global'} isDisabled={isDisabled} nameservers={nameservers} + overrideLocalDns={overrideLocalDns} name={key} /> ))} @@ -45,6 +54,7 @@ interface ListProps { isGlobal: boolean; isDisabled: boolean; nameservers: Record; + overrideLocalDns: boolean; name: string; } @@ -52,6 +62,7 @@ function NameserverList({ isGlobal, isDisabled, nameservers, + overrideLocalDns, name, }: ListProps) { const list = isGlobal ? nameservers.global : nameservers[name]; @@ -59,12 +70,54 @@ function NameserverList({ return null; } + const submit = useSubmit(); return (
-

- {isGlobal ? 'Global Nameservers' : name} -

+ {isGlobal ? ( +
+

+ Global Nameservers +

+
+ + + + When enabled, use the DNS servers listed below to resolve + names outside the tailnet. When disabled (default), devices + will prefer their local DNS configuration. + + Learn More + + + +

Override DNS servers

+ { + submit( + { + action_id: 'override_dns', + override_dns: v ? 'true' : 'false', + }, + { + method: 'POST', + }, + ); + }} + /> +
+
+ ) : ( +

{name}

+ )}
{list.length > 0 diff --git a/app/routes/dns/dns-actions.ts b/app/routes/dns/dns-actions.ts index d318737..4d0e60e 100644 --- a/app/routes/dns/dns-actions.ts +++ b/app/routes/dns/dns-actions.ts @@ -42,6 +42,8 @@ export async function dnsAction({ return removeRecord(formData, context); case 'add_record': return addRecord(formData, context); + case 'override_dns': + return overrideDns(formData, context); default: return data({ success: false }, 400); } @@ -230,3 +232,20 @@ async function addRecord(formData: FormData, context: LoadContext) { await context.integration?.onConfigChange(context.client); } + +async function overrideDns(formData: FormData, context: LoadContext) { + const override = formData.get('override_dns')?.toString(); + if (!override) { + return data({ success: false }, 400); + } + + const overrideValue = override === 'true'; + await context.hs.patch([ + { + path: 'dns.override_local_dns', + value: overrideValue, + }, + ]); + + await context.integration?.onConfigChange(context.client); +} diff --git a/app/routes/dns/overview.tsx b/app/routes/dns/overview.tsx index a5794e2..5c4c42a 100644 --- a/app/routes/dns/overview.tsx +++ b/app/routes/dns/overview.tsx @@ -44,6 +44,7 @@ export async function loader({ nameservers: config.dns.nameservers.global, splitDns: config.dns.nameservers.split, searchDomains: config.dns.search_domains, + overrideDns: config.dns.override_local_dns, extraRecords: context.hs.d, }; @@ -84,7 +85,11 @@ export default function Page() { )} - + []), split: type('Record').default(() => ({})),