mirror of
https://github.com/tale/headplane.git
synced 2026-07-26 07:48:14 +00:00
feat: start dns page
This commit is contained in:
@@ -0,0 +1,53 @@
|
|||||||
|
import { Dialog } from '@headlessui/react'
|
||||||
|
import { useState } from 'react'
|
||||||
|
|
||||||
|
export default function Modal() {
|
||||||
|
const [isOpen, setIsOpen] = useState(false)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<button
|
||||||
|
type='button'
|
||||||
|
className='rounded-lg px-3 py-2 bg-gray-800 text-white w-fit text-sm'
|
||||||
|
onClick={() => {
|
||||||
|
setIsOpen(true)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Rename Tailnet...
|
||||||
|
</button>
|
||||||
|
<Dialog
|
||||||
|
className='relative z-50'
|
||||||
|
open={isOpen} onClose={() => {
|
||||||
|
setIsOpen(false)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div className='fixed inset-0 bg-black/30' aria-hidden='true'/>
|
||||||
|
<div className='fixed inset-0 flex w-screen items-center justify-center p-4'>
|
||||||
|
<Dialog.Panel className='bg-white rounded-lg p-4 w-full max-w-md'>
|
||||||
|
<Dialog.Title>
|
||||||
|
Rename Tailnet
|
||||||
|
</Dialog.Title>
|
||||||
|
<Dialog.Description>
|
||||||
|
<p>
|
||||||
|
Enter a new name for your Tailnet.
|
||||||
|
</p>
|
||||||
|
</Dialog.Description>
|
||||||
|
<div className='flex gap-4'>
|
||||||
|
<input
|
||||||
|
type='text'
|
||||||
|
className='border rounded-lg p-2 w-full'
|
||||||
|
placeholder='New name'
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
type='button'
|
||||||
|
className='rounded-lg px-3 py-2 bg-gray-800 text-white w-fit text-sm'
|
||||||
|
>
|
||||||
|
Rename
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</Dialog.Panel>
|
||||||
|
</div>
|
||||||
|
</Dialog>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,113 @@
|
|||||||
|
import { Switch } from '@headlessui/react'
|
||||||
|
import { useLoaderData } from '@remix-run/react'
|
||||||
|
import clsx from 'clsx'
|
||||||
|
import { useState } from 'react'
|
||||||
|
|
||||||
|
import { getConfig } from '~/utils/config'
|
||||||
|
|
||||||
|
import RenameModal from './rename'
|
||||||
|
|
||||||
|
// We do not want to expose every config value
|
||||||
|
export async function loader() {
|
||||||
|
const config = await getConfig()
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function Page() {
|
||||||
|
const data = useLoaderData<typeof loader>()
|
||||||
|
const [localOverride, setLocalOverride] = useState(data.overrideLocal)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='flex flex-col gap-16'>
|
||||||
|
<div className='flex flex-col w-2/3'>
|
||||||
|
<h1 className='text-2xl font-medium mb-4'>Tailnet Name</h1>
|
||||||
|
<p className='text-gray-700 dark:text-gray-300'>
|
||||||
|
This is the base domain name of your Tailnet.
|
||||||
|
Devices are accessible at
|
||||||
|
{' '}
|
||||||
|
<code className='bg-gray-100 p-1 rounded-md'>
|
||||||
|
[device].[user].{data.baseDomain}
|
||||||
|
</code>
|
||||||
|
{' '}
|
||||||
|
when Magic DNS is enabled.
|
||||||
|
</p>
|
||||||
|
<input
|
||||||
|
readOnly
|
||||||
|
className='my-4 px-3 py-2 border rounded-lg focus:ring-none w-2/3 font-mono text-sm'
|
||||||
|
type='text'
|
||||||
|
value={data.baseDomain}
|
||||||
|
onFocus={event => {
|
||||||
|
event.target.select()
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<RenameModal/>
|
||||||
|
</div>
|
||||||
|
<div className='flex flex-col w-2/3'>
|
||||||
|
<h1 className='text-2xl font-medium mb-4'>Nameservers</h1>
|
||||||
|
<p className='text-gray-700 dark:text-gray-300'>
|
||||||
|
Set the nameservers used by devices on the Tailnet
|
||||||
|
to resolve DNS queries.
|
||||||
|
</p>
|
||||||
|
<div className='my-8'>
|
||||||
|
<div className='flex items-center justify-between mb-2'>
|
||||||
|
<h2 className='text-md font-medium opacity-80'>
|
||||||
|
Global Nameservers
|
||||||
|
</h2>
|
||||||
|
<div className='flex gap-2 items-center'>
|
||||||
|
<span className='text-sm opacity-50'>Override local DNS</span>
|
||||||
|
<Switch
|
||||||
|
checked={localOverride}
|
||||||
|
className={clsx(
|
||||||
|
localOverride ? 'bg-gray-800' : 'bg-gray-200',
|
||||||
|
'relative inline-flex h-4 w-9 items-center rounded-full'
|
||||||
|
)}
|
||||||
|
onChange={() => {
|
||||||
|
setLocalOverride(!localOverride)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<span className='sr-only'>Override local DNS</span>
|
||||||
|
<span
|
||||||
|
className={clsx(
|
||||||
|
localOverride ? 'translate-x-6' : 'translate-x-1',
|
||||||
|
'inline-block h-2 w-2 transform rounded-full bg-white transition'
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</Switch>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className='border border-gray-200 rounded-lg bg-gray-50'>
|
||||||
|
{data.nameservers.map((ns, index) => (
|
||||||
|
<div
|
||||||
|
// eslint-disable-next-line react/no-array-index-key
|
||||||
|
key={index}
|
||||||
|
className={clsx(
|
||||||
|
'flex items-center justify-between px-3 py-2',
|
||||||
|
'border-b border-gray-200 last:border-b-0'
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<p className='font-mono text-sm'>{ns}</p>
|
||||||
|
<button
|
||||||
|
type='button'
|
||||||
|
className='text-sm text-red-700'
|
||||||
|
>
|
||||||
|
Remove
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -11,6 +11,7 @@
|
|||||||
"typecheck": "tsc"
|
"typecheck": "tsc"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@headlessui/react": "^1.7.18",
|
||||||
"@heroicons/react": "^2.1.3",
|
"@heroicons/react": "^2.1.3",
|
||||||
"@remix-run/node": "^2.8.1",
|
"@remix-run/node": "^2.8.1",
|
||||||
"@remix-run/react": "^2.8.1",
|
"@remix-run/react": "^2.8.1",
|
||||||
|
|||||||
Generated
+35
@@ -5,6 +5,9 @@ settings:
|
|||||||
excludeLinksFromLockfile: false
|
excludeLinksFromLockfile: false
|
||||||
|
|
||||||
dependencies:
|
dependencies:
|
||||||
|
'@headlessui/react':
|
||||||
|
specifier: ^1.7.18
|
||||||
|
version: 1.7.18(react-dom@18.2.0)(react@18.2.0)
|
||||||
'@heroicons/react':
|
'@heroicons/react':
|
||||||
specifier: ^2.1.3
|
specifier: ^2.1.3
|
||||||
version: 2.1.3(react@18.2.0)
|
version: 2.1.3(react@18.2.0)
|
||||||
@@ -874,6 +877,19 @@ packages:
|
|||||||
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
|
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/@headlessui/react@1.7.18(react-dom@18.2.0)(react@18.2.0):
|
||||||
|
resolution: {integrity: sha512-4i5DOrzwN4qSgNsL4Si61VMkUcWbcSKueUV7sFhpHzQcSShdlHENE5+QBntMSRvHt8NyoFO2AGG8si9lq+w4zQ==}
|
||||||
|
engines: {node: '>=10'}
|
||||||
|
peerDependencies:
|
||||||
|
react: ^16 || ^17 || ^18
|
||||||
|
react-dom: ^16 || ^17 || ^18
|
||||||
|
dependencies:
|
||||||
|
'@tanstack/react-virtual': 3.2.0(react-dom@18.2.0)(react@18.2.0)
|
||||||
|
client-only: 0.0.1
|
||||||
|
react: 18.2.0
|
||||||
|
react-dom: 18.2.0(react@18.2.0)
|
||||||
|
dev: false
|
||||||
|
|
||||||
/@heroicons/react@2.1.3(react@18.2.0):
|
/@heroicons/react@2.1.3(react@18.2.0):
|
||||||
resolution: {integrity: sha512-fEcPfo4oN345SoqdlCDdSa4ivjaKbk0jTd+oubcgNxnNgAfzysfwWfQUr+51wigiWHQQRiZNd1Ao0M5Y3M2EGg==}
|
resolution: {integrity: sha512-fEcPfo4oN345SoqdlCDdSa4ivjaKbk0jTd+oubcgNxnNgAfzysfwWfQUr+51wigiWHQQRiZNd1Ao0M5Y3M2EGg==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@@ -1369,6 +1385,21 @@ packages:
|
|||||||
dev: true
|
dev: true
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
|
/@tanstack/react-virtual@3.2.0(react-dom@18.2.0)(react@18.2.0):
|
||||||
|
resolution: {integrity: sha512-OEdMByf2hEfDa6XDbGlZN8qO6bTjlNKqjM3im9JG+u3mCL8jALy0T/67oDI001raUUPh1Bdmfn4ZvPOV5knpcg==}
|
||||||
|
peerDependencies:
|
||||||
|
react: ^16.8.0 || ^17.0.0 || ^18.0.0
|
||||||
|
react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0
|
||||||
|
dependencies:
|
||||||
|
'@tanstack/virtual-core': 3.2.0
|
||||||
|
react: 18.2.0
|
||||||
|
react-dom: 18.2.0(react@18.2.0)
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/@tanstack/virtual-core@3.2.0:
|
||||||
|
resolution: {integrity: sha512-P5XgYoAw/vfW65byBbJQCw+cagdXDT/qH6wmABiLt4v4YBT2q2vqCOhihe+D1Nt325F/S/0Tkv6C5z0Lv+VBQQ==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/@types/acorn@4.0.6:
|
/@types/acorn@4.0.6:
|
||||||
resolution: {integrity: sha512-veQTnWP+1D/xbxVrPC3zHnCZRjSrKfhbMUlEA43iMZLu7EsnTtkJklIuwrCPbOi8YkvDQAiW05VQQFvvz9oieQ==}
|
resolution: {integrity: sha512-veQTnWP+1D/xbxVrPC3zHnCZRjSrKfhbMUlEA43iMZLu7EsnTtkJklIuwrCPbOi8YkvDQAiW05VQQFvvz9oieQ==}
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -2117,6 +2148,10 @@ packages:
|
|||||||
engines: {node: '>=6'}
|
engines: {node: '>=6'}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/client-only@0.0.1:
|
||||||
|
resolution: {integrity: sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/clone@1.0.4:
|
/clone@1.0.4:
|
||||||
resolution: {integrity: sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==}
|
resolution: {integrity: sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==}
|
||||||
engines: {node: '>=0.8'}
|
engines: {node: '>=0.8'}
|
||||||
|
|||||||
Reference in New Issue
Block a user