mirror of
https://github.com/tale/headplane.git
synced 2026-08-20 01:42:17 +00:00
feat: reintroduce missing local dns override (fixes #236)
This commit is contained in:
@@ -8,6 +8,7 @@
|
|||||||
- The docker container now runs in a non-root, distroless image (closes [#255](https://github.com/tale/headplane/issues/255)).
|
- 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 <host_path>` on your data directory to ensure the container can write to it.
|
- You may need to run `chown -R 65532:65532 <host_path>` 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)).
|
- 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)
|
### 0.6.0 (May 25, 2025)
|
||||||
- Headplane 0.6.0 now requires **Headscale 0.26.0** or newer.
|
- Headplane 0.6.0 now requires **Headscale 0.26.0** or newer.
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import cn from '~/utils/cn';
|
|||||||
export interface SwitchProps extends AriaSwitchProps {
|
export interface SwitchProps extends AriaSwitchProps {
|
||||||
label: string;
|
label: string;
|
||||||
className?: string;
|
className?: string;
|
||||||
|
switchClassName?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function Switch(props: SwitchProps) {
|
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',
|
state.isSelected && 'bg-headplane-900 dark:bg-headplane-950',
|
||||||
isFocusVisible && 'ring-2',
|
isFocusVisible && 'ring-2',
|
||||||
props.isDisabled && 'opacity-50',
|
props.isDisabled && 'opacity-50',
|
||||||
|
props.className,
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
@@ -53,6 +55,7 @@ export default function Switch(props: SwitchProps) {
|
|||||||
'bg-white transition duration-50 ease-in-out',
|
'bg-white transition duration-50 ease-in-out',
|
||||||
'translate-x-0 group-selected:translate-x-full',
|
'translate-x-0 group-selected:translate-x-full',
|
||||||
state.isSelected && 'translate-x-full',
|
state.isSelected && 'translate-x-full',
|
||||||
|
props.switchClassName,
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -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 Button from '~/components/Button';
|
||||||
import Link from '~/components/Link';
|
import Link from '~/components/Link';
|
||||||
|
import Switch from '~/components/Switch';
|
||||||
import TableList from '~/components/TableList';
|
import TableList from '~/components/TableList';
|
||||||
|
import Tooltip from '~/components/Tooltip';
|
||||||
import cn from '~/utils/cn';
|
import cn from '~/utils/cn';
|
||||||
import AddNS from '../dialogs/add-ns';
|
import AddNS from '../dialogs/add-ns';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
nameservers: Record<string, string[]>;
|
nameservers: Record<string, string[]>;
|
||||||
|
overrideLocalDns: boolean;
|
||||||
isDisabled: boolean;
|
isDisabled: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function ManageNS({ nameservers, isDisabled }: Props) {
|
export default function ManageNS({
|
||||||
|
nameservers,
|
||||||
|
isDisabled,
|
||||||
|
overrideLocalDns,
|
||||||
|
}: Props) {
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col w-2/3">
|
<div className="flex flex-col w-2/3">
|
||||||
<h1 className="text-2xl font-medium mb-4">Nameservers</h1>
|
<h1 className="text-2xl font-medium mb-4">Nameservers</h1>
|
||||||
@@ -31,6 +39,7 @@ export default function ManageNS({ nameservers, isDisabled }: Props) {
|
|||||||
isGlobal={key === 'global'}
|
isGlobal={key === 'global'}
|
||||||
isDisabled={isDisabled}
|
isDisabled={isDisabled}
|
||||||
nameservers={nameservers}
|
nameservers={nameservers}
|
||||||
|
overrideLocalDns={overrideLocalDns}
|
||||||
name={key}
|
name={key}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
@@ -45,6 +54,7 @@ interface ListProps {
|
|||||||
isGlobal: boolean;
|
isGlobal: boolean;
|
||||||
isDisabled: boolean;
|
isDisabled: boolean;
|
||||||
nameservers: Record<string, string[]>;
|
nameservers: Record<string, string[]>;
|
||||||
|
overrideLocalDns: boolean;
|
||||||
name: string;
|
name: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -52,6 +62,7 @@ function NameserverList({
|
|||||||
isGlobal,
|
isGlobal,
|
||||||
isDisabled,
|
isDisabled,
|
||||||
nameservers,
|
nameservers,
|
||||||
|
overrideLocalDns,
|
||||||
name,
|
name,
|
||||||
}: ListProps) {
|
}: ListProps) {
|
||||||
const list = isGlobal ? nameservers.global : nameservers[name];
|
const list = isGlobal ? nameservers.global : nameservers[name];
|
||||||
@@ -59,12 +70,54 @@ function NameserverList({
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const submit = useSubmit();
|
||||||
return (
|
return (
|
||||||
<div className="mb-8">
|
<div className="mb-8">
|
||||||
<div className="flex items-center justify-between mb-2">
|
<div className="flex items-center justify-between mb-2">
|
||||||
<h2 className="text-md font-medium opacity-80">
|
{isGlobal ? (
|
||||||
{isGlobal ? 'Global Nameservers' : name}
|
<div className="flex items-center justify-between w-full">
|
||||||
</h2>
|
<h2 className="text-md font-medium opacity-80">
|
||||||
|
Global Nameservers
|
||||||
|
</h2>
|
||||||
|
<div className="flex items-center gap-2 text-sm">
|
||||||
|
<Tooltip>
|
||||||
|
<Info className="size-4" />
|
||||||
|
<Tooltip.Body>
|
||||||
|
When enabled, use the DNS servers listed below to resolve
|
||||||
|
names outside the tailnet. When disabled (default), devices
|
||||||
|
will prefer their local DNS configuration.
|
||||||
|
<Link
|
||||||
|
to="https://tailscale.com/kb/1054/dns#global-nameservers"
|
||||||
|
name="Tailscale Global Nameservers Documentation"
|
||||||
|
>
|
||||||
|
Learn More
|
||||||
|
</Link>
|
||||||
|
</Tooltip.Body>
|
||||||
|
</Tooltip>
|
||||||
|
<p>Override DNS servers</p>
|
||||||
|
<Switch
|
||||||
|
label="Override local DNS settings"
|
||||||
|
className="h-[15px] w-[23px] p-[2px]"
|
||||||
|
switchClassName="h-[9px] w-[9px]"
|
||||||
|
name="override_dns"
|
||||||
|
defaultSelected={overrideLocalDns}
|
||||||
|
onChange={(v) => {
|
||||||
|
submit(
|
||||||
|
{
|
||||||
|
action_id: 'override_dns',
|
||||||
|
override_dns: v ? 'true' : 'false',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
method: 'POST',
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<h2 className="text-md font-medium opacity-80">{name}</h2>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
<TableList>
|
<TableList>
|
||||||
{list.length > 0
|
{list.length > 0
|
||||||
|
|||||||
@@ -42,6 +42,8 @@ export async function dnsAction({
|
|||||||
return removeRecord(formData, context);
|
return removeRecord(formData, context);
|
||||||
case 'add_record':
|
case 'add_record':
|
||||||
return addRecord(formData, context);
|
return addRecord(formData, context);
|
||||||
|
case 'override_dns':
|
||||||
|
return overrideDns(formData, context);
|
||||||
default:
|
default:
|
||||||
return data({ success: false }, 400);
|
return data({ success: false }, 400);
|
||||||
}
|
}
|
||||||
@@ -230,3 +232,20 @@ async function addRecord(formData: FormData, context: LoadContext) {
|
|||||||
|
|
||||||
await context.integration?.onConfigChange(context.client);
|
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);
|
||||||
|
}
|
||||||
|
|||||||
@@ -44,6 +44,7 @@ export async function loader({
|
|||||||
nameservers: config.dns.nameservers.global,
|
nameservers: config.dns.nameservers.global,
|
||||||
splitDns: config.dns.nameservers.split,
|
splitDns: config.dns.nameservers.split,
|
||||||
searchDomains: config.dns.search_domains,
|
searchDomains: config.dns.search_domains,
|
||||||
|
overrideDns: config.dns.override_local_dns,
|
||||||
extraRecords: context.hs.d,
|
extraRecords: context.hs.d,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -84,7 +85,11 @@ export default function Page() {
|
|||||||
</Notice>
|
</Notice>
|
||||||
)}
|
)}
|
||||||
<RenameTailnet name={data.baseDomain} isDisabled={isDisabled} />
|
<RenameTailnet name={data.baseDomain} isDisabled={isDisabled} />
|
||||||
<ManageNS nameservers={allNs} isDisabled={isDisabled} />
|
<ManageNS
|
||||||
|
nameservers={allNs}
|
||||||
|
isDisabled={isDisabled}
|
||||||
|
overrideLocalDns={data.overrideDns}
|
||||||
|
/>
|
||||||
<ManageRecords records={data.extraRecords} isDisabled={isDisabled} />
|
<ManageRecords records={data.extraRecords} isDisabled={isDisabled} />
|
||||||
<ManageDomains
|
<ManageDomains
|
||||||
searchDomains={data.searchDomains}
|
searchDomains={data.searchDomains}
|
||||||
|
|||||||
@@ -103,6 +103,7 @@ export const headscaleConfig = type({
|
|||||||
dns: {
|
dns: {
|
||||||
magic_dns: goBool.default(true),
|
magic_dns: goBool.default(true),
|
||||||
base_domain: 'string = "headscale.net"',
|
base_domain: 'string = "headscale.net"',
|
||||||
|
override_local_dns: goBool.default(false),
|
||||||
nameservers: type({
|
nameservers: type({
|
||||||
global: type('string[]').default(() => []),
|
global: type('string[]').default(() => []),
|
||||||
split: type('Record<string, string[]>').default(() => ({})),
|
split: type('Record<string, string[]>').default(() => ({})),
|
||||||
|
|||||||
Reference in New Issue
Block a user