chore: switch to react-router v7

This commit is contained in:
Aarnav Tale 2024-12-31 10:30:14 +05:30
parent 39504e2487
commit aa9872a45b
No known key found for this signature in database
101 changed files with 3825 additions and 6796 deletions

View file

@ -1,40 +1,46 @@
import { ActionFunctionArgs, LoaderFunctionArgs } from '@remix-run/node'
import { Link as RemixLink, useLoaderData } from '@remix-run/react'
import { InfoIcon, GearIcon, CheckCircleIcon, SkipIcon, PersonIcon } from '@primer/octicons-react'
import { useMemo, useState } from 'react'
import { ActionFunctionArgs, LoaderFunctionArgs } from 'react-router';
import { Link as RemixLink, useLoaderData } from 'react-router';
import {
InfoIcon,
GearIcon,
CheckCircleIcon,
SkipIcon,
PersonIcon,
} from '@primer/octicons-react';
import { useMemo, useState } from 'react';
import Attribute from '~/components/Attribute'
import Button from '~/components/Button'
import Card from '~/components/Card'
import Menu from '~/components/Menu'
import Tooltip from '~/components/Tooltip'
import StatusCircle from '~/components/StatusCircle'
import { Machine, Route, User } from '~/types'
import { cn } from '~/utils/cn'
import { loadContext } from '~/utils/config/headplane'
import { loadConfig } from '~/utils/config/headscale'
import { pull } from '~/utils/headscale'
import { getSession } from '~/utils/sessions'
import { useLiveData } from '~/utils/useLiveData'
import Link from '~/components/Link'
import Attribute from '~/components/Attribute';
import Button from '~/components/Button';
import Card from '~/components/Card';
import Menu from '~/components/Menu';
import Tooltip from '~/components/Tooltip';
import StatusCircle from '~/components/StatusCircle';
import { Machine, Route, User } from '~/types';
import { cn } from '~/utils/cn';
import { loadContext } from '~/utils/config/headplane';
import { loadConfig } from '~/utils/config/headscale';
import { pull } from '~/utils/headscale';
import { getSession } from '~/utils/sessions';
import { useLiveData } from '~/utils/useLiveData';
import Link from '~/components/Link';
import { menuAction } from './action'
import MenuOptions from './components/menu'
import Routes from './dialogs/routes'
import { menuAction } from './action';
import MenuOptions from './components/menu';
import Routes from './dialogs/routes';
export async function loader({ request, params }: LoaderFunctionArgs) {
const session = await getSession(request.headers.get('Cookie'))
const session = await getSession(request.headers.get('Cookie'));
if (!params.id) {
throw new Error('No machine ID provided')
throw new Error('No machine ID provided');
}
const context = await loadContext()
let magic: string | undefined
const context = await loadContext();
let magic: string | undefined;
if (context.config.read) {
const config = await loadConfig()
const config = await loadConfig();
if (config.dns.magic_dns) {
magic = config.dns.base_domain
magic = config.dns.base_domain;
}
}
@ -42,110 +48,106 @@ export async function loader({ request, params }: LoaderFunctionArgs) {
pull<{ node: Machine }>(`v1/node/${params.id}`, session.get('hsApiKey')!),
pull<{ routes: Route[] }>('v1/routes', session.get('hsApiKey')!),
pull<{ users: User[] }>('v1/user', session.get('hsApiKey')!),
])
]);
return {
machine: machine.node,
routes: routes.routes.filter(route => route.node.id === params.id),
routes: routes.routes.filter((route) => route.node.id === params.id),
users: users.users,
magic,
}
};
}
export async function action({ request }: ActionFunctionArgs) {
return menuAction(request)
return menuAction(request);
}
export default function Page() {
const { machine, magic, routes, users } = useLoaderData<typeof loader>()
const routesState = useState(false)
useLiveData({ interval: 1000 })
const { machine, magic, routes, users } = useLoaderData<typeof loader>();
const routesState = useState(false);
useLiveData({ interval: 1000 });
const expired = machine.expiry === '0001-01-01 00:00:00'
|| machine.expiry === '0001-01-01T00:00:00Z'
|| machine.expiry === null
? false
: new Date(machine.expiry).getTime() < Date.now()
const expired =
machine.expiry === '0001-01-01 00:00:00' ||
machine.expiry === '0001-01-01T00:00:00Z' ||
machine.expiry === null
? false
: new Date(machine.expiry).getTime() < Date.now();
const tags = [
...machine.forcedTags,
...machine.validTags,
]
const tags = [...machine.forcedTags, ...machine.validTags];
if (expired) {
tags.unshift('Expired')
tags.unshift('Expired');
}
// This is much easier with Object.groupBy but it's too new for us
const { exit, subnet, subnetApproved } = routes.reduce((acc, route) => {
if (route.prefix === '::/0' || route.prefix === '0.0.0.0/0') {
acc.exit.push(route)
return acc
}
const { exit, subnet, subnetApproved } = routes.reduce(
(acc, route) => {
if (route.prefix === '::/0' || route.prefix === '0.0.0.0/0') {
acc.exit.push(route);
return acc;
}
if (route.enabled) {
acc.subnetApproved.push(route)
return acc
}
if (route.enabled) {
acc.subnetApproved.push(route);
return acc;
}
acc.subnet.push(route)
return acc
}, { exit: [], subnetApproved: [], subnet: [] })
acc.subnet.push(route);
return acc;
},
{ exit: [], subnetApproved: [], subnet: [] },
);
const exitEnabled = useMemo(() => {
if (exit.length !== 2) return false
return exit[0].enabled && exit[1].enabled
}, [exit])
if (exit.length !== 2) return false;
return exit[0].enabled && exit[1].enabled;
}, [exit]);
if (exitEnabled) {
tags.unshift('Exit Node')
tags.unshift('Exit Node');
}
if (subnetApproved.length > 0) {
tags.unshift('Subnets')
tags.unshift('Subnets');
}
return (
<div>
<p className="mb-8 text-md">
<RemixLink
to="/machines"
className="font-medium"
>
<RemixLink to="/machines" className="font-medium">
All Machines
</RemixLink>
<span className="mx-2">
/
</span>
<span className="mx-2">/</span>
{machine.givenName}
</p>
<div className={cn(
'flex justify-between items-center',
'border-b border-ui-100 dark:border-ui-800',
)}>
<div
className={cn(
'flex justify-between items-center',
'border-b border-ui-100 dark:border-ui-800',
)}
>
<span className="flex items-baseline gap-x-4 text-sm mb-4">
<h1 className="text-2xl font-medium">
{machine.givenName}
</h1>
<h1 className="text-2xl font-medium">{machine.givenName}</h1>
<StatusCircle isOnline={machine.online} className="w-4 h-4" />
</span>
<MenuOptions
className={cn(
'bg-ui-100 dark:bg-ui-800',
)}
className={cn('bg-ui-100 dark:bg-ui-800')}
machine={machine}
routes={routes}
users={users}
magic={magic}
buttonChild={
<Menu.Button className={cn(
'flex items-center justify-center gap-x-2',
'bg-main-200 dark:bg-main-700/30',
'hover:bg-main-300 dark:hover:bg-main-600/30',
'text-ui-700 dark:text-ui-300 mb-2',
'w-fit text-sm rounded-lg px-3 py-2'
)}>
<Menu.Button
className={cn(
'flex items-center justify-center gap-x-2',
'bg-main-200 dark:bg-main-700/30',
'hover:bg-main-300 dark:hover:bg-main-600/30',
'text-ui-700 dark:text-ui-300 mb-2',
'w-fit text-sm rounded-lg px-3 py-2',
)}
>
<GearIcon className="w-5" />
Machine Settings
</Menu.Button>
@ -166,21 +168,21 @@ export default function Page() {
</Tooltip>
</span>
<div className="flex items-center gap-x-2.5 mt-1">
<div className={cn(
'rounded-full h-7 w-7 flex items-center justify-center',
'border border-ui-200 dark:border-ui-700',
)}>
<div
className={cn(
'rounded-full h-7 w-7 flex items-center justify-center',
'border border-ui-200 dark:border-ui-700',
)}
>
<PersonIcon className="w-4 h-4" />
</div>
{machine.user.name}
</div>
</div>
<div className="p-2 pl-4">
<p className="text-sm text-ui-600 dark:text-ui-300">
Status
</p>
<p className="text-sm text-ui-600 dark:text-ui-300">Status</p>
<div className="flex gap-1 mt-1 mb-8">
{tags.map(tag => (
{tags.map((tag) => (
<span
key={tag}
className={cn(
@ -195,18 +197,11 @@ export default function Page() {
</div>
</div>
</div>
<h2 className="text-xl font-medium mb-4 mt-8">
Subnets & Routing
</h2>
<Routes
machine={machine}
routes={routes}
state={routesState}
/>
<h2 className="text-xl font-medium mb-4 mt-8">Subnets & Routing</h2>
<Routes machine={machine} routes={routes} state={routesState} />
<div className="flex items-center justify-between mb-4">
<p>
Subnets let you expose physical network routes onto Tailscale.
{' '}
Subnets let you expose physical network routes onto Tailscale.{' '}
<Link
to="https://tailscale.com/kb/1019/subnets"
name="Tailscale Subnets Documentation"
@ -214,10 +209,7 @@ export default function Page() {
Learn More
</Link>
</p>
<Button
variant="light"
control={routesState}
>
<Button variant="light" control={routesState}>
Review
</Button>
</div>
@ -236,22 +228,17 @@ export default function Page() {
<InfoIcon className="w-3.5 h-3.5" />
</Tooltip.Button>
<Tooltip.Body>
Traffic to these routes are being
routed through this machine.
Traffic to these routes are being routed through this machine.
</Tooltip.Body>
</Tooltip>
</span>
<div className="mt-1">
{subnetApproved.length === 0 ? (
<span className="text-ui-400 dark:text-ui-300">
</span>
<span className="text-ui-400 dark:text-ui-300"></span>
) : (
<ul className="leading-normal">
{subnetApproved.map(route => (
<li key={route.id}>
{route.prefix}
</li>
{subnetApproved.map((route) => (
<li key={route.id}>{route.prefix}</li>
))}
</ul>
)}
@ -276,23 +263,18 @@ export default function Page() {
<InfoIcon className="w-3.5 h-3.5" />
</Tooltip.Button>
<Tooltip.Body>
This machine is advertising these routes,
but they must be approved before traffic
will be routed to them.
This machine is advertising these routes, but they must be
approved before traffic will be routed to them.
</Tooltip.Body>
</Tooltip>
</span>
<div className="mt-1">
{subnet.length === 0 ? (
<span className="text-ui-400 dark:text-ui-300">
</span>
<span className="text-ui-400 dark:text-ui-300"></span>
) : (
<ul className="leading-normal">
{subnet.map(route => (
<li key={route.id}>
{route.prefix}
</li>
{subnet.map((route) => (
<li key={route.id}>{route.prefix}</li>
))}
</ul>
)}
@ -317,16 +299,13 @@ export default function Page() {
<InfoIcon className="w-3.5 h-3.5" />
</Tooltip.Button>
<Tooltip.Body>
Whether this machine can act as an
exit node for your tailnet.
Whether this machine can act as an exit node for your tailnet.
</Tooltip.Body>
</Tooltip>
</span>
<div className="mt-1">
{exit.length === 0 ? (
<span className="text-ui-400 dark:text-ui-300">
</span>
<span className="text-ui-400 dark:text-ui-300"></span>
) : exitEnabled ? (
<span className="flex items-center gap-x-1">
<CheckCircleIcon className="w-3.5 h-3.5 text-green-700" />
@ -352,19 +331,13 @@ export default function Page() {
</Button>
</div>
</Card>
<h2 className="text-xl font-medium mb-4">
Machine Details
</h2>
<h2 className="text-xl font-medium mb-4">Machine Details</h2>
<Card variant="flat" className="w-full max-w-full">
<Attribute name="Creator" value={machine.user.name} />
<Attribute name="Node ID" value={machine.id} />
<Attribute name="Node Name" value={machine.givenName} />
<Attribute name="Hostname" value={machine.name} />
<Attribute
isCopyable
name="Node Key"
value={machine.nodeKey}
/>
<Attribute isCopyable name="Node Key" value={machine.nodeKey} />
<Attribute
name="Created"
value={new Date(machine.createdAt).toLocaleString()}
@ -375,21 +348,16 @@ export default function Page() {
/>
<Attribute
name="Expiry"
value={expired
? new Date(machine.expiry).toLocaleString()
: 'Never'
}
value={expired ? new Date(machine.expiry).toLocaleString() : 'Never'}
/>
{magic
? (
<Attribute
isCopyable
name="Domain"
value={`${machine.givenName}.${magic}`}
/>
)
: undefined}
{magic ? (
<Attribute
isCopyable
name="Domain"
value={`${machine.givenName}.${magic}`}
/>
) : undefined}
</Card>
</div>
)
);
}