feat(TALE-36): support exit node enabling/disabling
This commit is contained in:
parent
6e55f442fd
commit
e1c87412d4
@ -62,6 +62,24 @@ export async function menuAction(request: ActionFunctionArgs['request']) {
|
|||||||
return json({ message: 'Route updated' })
|
return json({ message: 'Route updated' })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case 'exit-node': {
|
||||||
|
if (!data.has('routes') || !data.has('enabled')) {
|
||||||
|
return json({ message: 'No route or enabled provided' }, {
|
||||||
|
status: 400,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const routes = data.get('routes')?.toString().split(',') ?? []
|
||||||
|
const enabled = data.get('enabled') === 'true'
|
||||||
|
const postfix = enabled ? 'enable' : 'disable'
|
||||||
|
|
||||||
|
await Promise.all(routes.map(async (route) => {
|
||||||
|
await post(`v1/routes/${route}/${postfix}`, session.get('hsApiKey')!)
|
||||||
|
}))
|
||||||
|
|
||||||
|
return json({ message: 'Exit node updated' })
|
||||||
|
}
|
||||||
|
|
||||||
case 'move': {
|
case 'move': {
|
||||||
if (!data.has('to')) {
|
if (!data.has('to')) {
|
||||||
return json({ message: 'No destination provided' }, {
|
return json({ message: 'No destination provided' }, {
|
||||||
|
|||||||
@ -1,9 +1,10 @@
|
|||||||
import { useFetcher } from '@remix-run/react'
|
import { useFetcher } from '@remix-run/react'
|
||||||
import { type Dispatch, type SetStateAction } from 'react'
|
import { Dispatch, SetStateAction, useMemo } from 'react'
|
||||||
|
|
||||||
import Dialog from '~/components/Dialog'
|
import Dialog from '~/components/Dialog'
|
||||||
import Switch from '~/components/Switch'
|
import Switch from '~/components/Switch'
|
||||||
import { type Machine, type Route } from '~/types'
|
import Link from '~/components/Link'
|
||||||
|
import { Machine, Route } from '~/types'
|
||||||
import { cn } from '~/utils/cn'
|
import { cn } from '~/utils/cn'
|
||||||
|
|
||||||
interface RoutesProps {
|
interface RoutesProps {
|
||||||
@ -16,6 +17,22 @@ interface RoutesProps {
|
|||||||
export default function Routes({ machine, routes, state }: RoutesProps) {
|
export default function Routes({ machine, routes, state }: RoutesProps) {
|
||||||
const fetcher = useFetcher()
|
const fetcher = useFetcher()
|
||||||
|
|
||||||
|
// This is much easier with Object.groupBy but it's too new for us
|
||||||
|
const { exit, subnet } = routes.reduce((acc, route) => {
|
||||||
|
if (route.prefix === '::/0' || route.prefix === '0.0.0.0/0') {
|
||||||
|
acc.exit.push(route)
|
||||||
|
return acc
|
||||||
|
}
|
||||||
|
|
||||||
|
acc.subnet.push(route)
|
||||||
|
return acc
|
||||||
|
}, { exit: [], subnet: [] })
|
||||||
|
|
||||||
|
const exitEnabled = useMemo(() => {
|
||||||
|
if (exit.length !== 2) return false
|
||||||
|
return exit[0].enabled && exit[1].enabled
|
||||||
|
}, [exit])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog>
|
<Dialog>
|
||||||
<Dialog.Panel control={state}>
|
<Dialog.Panel control={state}>
|
||||||
@ -26,9 +43,19 @@ export default function Routes({ machine, routes, state }: RoutesProps) {
|
|||||||
{' '}
|
{' '}
|
||||||
{machine.givenName}
|
{machine.givenName}
|
||||||
</Dialog.Title>
|
</Dialog.Title>
|
||||||
|
<Dialog.Text className="font-bold">
|
||||||
|
Subnet routes
|
||||||
|
</Dialog.Text>
|
||||||
<Dialog.Text>
|
<Dialog.Text>
|
||||||
Connect to devices you can't install Tailscale on
|
Connect to devices you can't install Tailscale on
|
||||||
by advertising IP ranges as subnet routes.
|
by advertising IP ranges as subnet routes.
|
||||||
|
{' '}
|
||||||
|
<Link
|
||||||
|
to="https://tailscale.com/kb/1019/subnets"
|
||||||
|
name="Tailscale Subnets Documentation"
|
||||||
|
>
|
||||||
|
Learn More
|
||||||
|
</Link>
|
||||||
</Dialog.Text>
|
</Dialog.Text>
|
||||||
<div className={cn(
|
<div className={cn(
|
||||||
'rounded-lg overflow-y-auto my-2',
|
'rounded-lg overflow-y-auto my-2',
|
||||||
@ -36,7 +63,7 @@ export default function Routes({ machine, routes, state }: RoutesProps) {
|
|||||||
'border border-zinc-200 dark:border-zinc-700',
|
'border border-zinc-200 dark:border-zinc-700',
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{routes.length === 0
|
{subnet.length === 0
|
||||||
? (
|
? (
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
@ -51,7 +78,7 @@ export default function Routes({ machine, routes, state }: RoutesProps) {
|
|||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
: undefined}
|
: undefined}
|
||||||
{routes.map(route => (
|
{subnet.map(route => (
|
||||||
<div
|
<div
|
||||||
key={route.node.id}
|
key={route.node.id}
|
||||||
className={cn(
|
className={cn(
|
||||||
@ -80,6 +107,66 @@ export default function Routes({ machine, routes, state }: RoutesProps) {
|
|||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
<Dialog.Text className="font-bold mt-8">
|
||||||
|
Exit nodes
|
||||||
|
</Dialog.Text>
|
||||||
|
<Dialog.Text>
|
||||||
|
Allow your network to route internet traffic through this machine.
|
||||||
|
{' '}
|
||||||
|
<Link
|
||||||
|
to="https://tailscale.com/kb/1103/exit-nodes"
|
||||||
|
name="Tailscale Exit-node Documentation"
|
||||||
|
>
|
||||||
|
Learn More
|
||||||
|
</Link>
|
||||||
|
</Dialog.Text>
|
||||||
|
<div className={cn(
|
||||||
|
'rounded-lg overflow-y-auto my-2',
|
||||||
|
'divide-y divide-zinc-200 dark:divide-zinc-700 align-top',
|
||||||
|
'border border-zinc-200 dark:border-zinc-700',
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{exit.length === 0
|
||||||
|
? (
|
||||||
|
<div
|
||||||
|
className={cn(
|
||||||
|
'flex py-4 px-4 bg-ui-100 dark:bg-ui-800',
|
||||||
|
'items-center justify-center',
|
||||||
|
'text-ui-600 dark:text-ui-300',
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<p>
|
||||||
|
This machine is not an exit node.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div
|
||||||
|
className={cn(
|
||||||
|
'flex py-2 px-4 bg-ui-100 dark:bg-ui-800',
|
||||||
|
'items-center justify-between',
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<p>
|
||||||
|
Use as exit node
|
||||||
|
</p>
|
||||||
|
<Switch
|
||||||
|
defaultSelected={exitEnabled}
|
||||||
|
label="Enabled"
|
||||||
|
onChange={(checked) => {
|
||||||
|
const form = new FormData()
|
||||||
|
form.set('id', machine.id)
|
||||||
|
form.set('_method', 'exit-node')
|
||||||
|
form.set('routes', exit.map(route => route.id).join(','))
|
||||||
|
|
||||||
|
form.set('enabled', String(checked))
|
||||||
|
fetcher.submit(form, {
|
||||||
|
method: 'POST',
|
||||||
|
})
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
<div className="mt-6 flex justify-end gap-2 mt-6">
|
<div className="mt-6 flex justify-end gap-2 mt-6">
|
||||||
<Dialog.Action
|
<Dialog.Action
|
||||||
variant="cancel"
|
variant="cancel"
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user