From ab0cb7b7826550ac5ef220511d9089bc70bb5a91 Mon Sep 17 00:00:00 2001 From: Aarnav Tale Date: Sun, 7 Jul 2024 14:52:47 -0400 Subject: [PATCH] feat(TALE-11): support custom DNS records --- app/routes/_data.dns._index/dialogs/dns.tsx | 121 +++++++++++++++++ app/routes/_data.dns._index/dns.tsx | 137 ++++++++++++++++++++ app/routes/_data.dns._index/route.tsx | 6 + 3 files changed, 264 insertions(+) create mode 100644 app/routes/_data.dns._index/dialogs/dns.tsx create mode 100644 app/routes/_data.dns._index/dns.tsx diff --git a/app/routes/_data.dns._index/dialogs/dns.tsx b/app/routes/_data.dns._index/dialogs/dns.tsx new file mode 100644 index 0000000..28949dc --- /dev/null +++ b/app/routes/_data.dns._index/dialogs/dns.tsx @@ -0,0 +1,121 @@ +import { Form, useSubmit } from '@remix-run/react' +import { useMemo, useState } from 'react' + +import Code from '~/components/Code' +import Dialog from '~/components/Dialog' +import TextField from '~/components/TextField' +import { cn } from '~/utils/cn' + +interface Props { + records: { name: string, type: 'A', value: string }[] +} + +export default function AddDNS({ records }: Props) { + const submit = useSubmit() + const [name, setName] = useState('') + const [ip, setIp] = useState('') + + const isDuplicate = useMemo(() => { + if (name.length === 0 || ip.length === 0) return false + const lookup = records.find(record => record.name === name) + if (!lookup) return false + + return lookup.value === ip + }, [records, name, ip]) + + return ( + + + Add DNS record + + + {close => ( + <> + + Add DNS record + + + Enter the domain and IP address for the new DNS record. + +
{ + event.preventDefault() + if (!name || !ip) return + + setName('') + setIp('') + + submit({ + 'dns_config.extra_records': [ + ...records, + { + name, + type: 'A', + value: ip, + }, + ], + }, { + method: 'PATCH', + encType: 'application/json', + }) + + close() + }} + > + + + {isDuplicate + ? ( +

+ A record with the domain name + {' '} + {name} + {' '} + and IP address + {' '} + {ip} + {' '} + already exists. +

+ ) + : undefined} +
+ + Cancel + + + Add + +
+ + + )} +
+
+ ) +} diff --git a/app/routes/_data.dns._index/dns.tsx b/app/routes/_data.dns._index/dns.tsx new file mode 100644 index 0000000..5eca27e --- /dev/null +++ b/app/routes/_data.dns._index/dns.tsx @@ -0,0 +1,137 @@ +import { useSubmit } from '@remix-run/react' +import { useState } from 'react' +import { Button } from 'react-aria-components' + +import Code from '~/components/Code' +import Link from '~/components/Link' +import Switch from '~/components/Switch' +import TableList from '~/components/TableList' +import { cn } from '~/utils/cn' + +import AddDNS from './dialogs/dns' + +interface Props { + records: { name: string, type: 'A', value: string }[] + isDisabled: boolean +} + +export default function DNS({ records, isDisabled }: Props) { + const submit = useSubmit() + + return ( +
+

DNS Records

+

+ Headscale supports adding custom DNS records to your Tailnet. + As of now, only + {' '} + A + {' '} + records are supported. + {' '} + + Learn More + +

+
+ + {records.length === 0 + ? ( + +

+ No DNS records found +

+
+ ) + : records.map((record, index) => ( + +
+
+

{record.type}

+

{record.name}

+
+

{record.value}

+
+ +
+ ))} +
+ + {isDisabled + ? undefined + : ( + + )} +
+
+ ) +} + +interface ListProps { + isGlobal: boolean + isDisabled: boolean + nameservers: string[] + name: string + override: boolean +} + +function NameserverList({ isGlobal, isDisabled, nameservers, name, override }: ListProps) { + const [localOverride, setLocalOverride] = useState(override) + const submit = useSubmit() + + return ( +
+
+

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

+ {isGlobal + ? ( +
+ + Override local DNS + + { + submit({ + 'dns_config.override_local_dns': !localOverride, + }, { + method: 'PATCH', + encType: 'application/json', + }) + + setLocalOverride(!localOverride) + }} + /> +
+ ) + : undefined} +
+
+ ) +} diff --git a/app/routes/_data.dns._index/route.tsx b/app/routes/_data.dns._index/route.tsx index 9ca30fe..535b1f5 100644 --- a/app/routes/_data.dns._index/route.tsx +++ b/app/routes/_data.dns._index/route.tsx @@ -8,6 +8,7 @@ import { loadConfig, patchConfig } from '~/utils/config/headscale' import { getSession } from '~/utils/sessions' import { useLiveData } from '~/utils/useLiveData' +import DNS from './dns' import Domains from './domains' import MagicModal from './magic' import Nameservers from './nameservers' @@ -90,6 +91,11 @@ export default function Page() { isDisabled={!data.config.write} /> + +