mirror of
https://github.com/tale/headplane.git
synced 2026-08-27 15:37:04 +00:00
chore: cleanup and use aria components in places
This commit is contained in:
@@ -1,6 +1,7 @@
|
|||||||
import { useFetcher } from '@remix-run/react'
|
import { useFetcher } from '@remix-run/react'
|
||||||
|
|
||||||
import Button from '~/components/Button'
|
import Button from '~/components/Button'
|
||||||
|
// TODO: Remove useModal and replace with Dialog
|
||||||
import useModal from '~/components/Modal'
|
import useModal from '~/components/Modal'
|
||||||
import Spinner from '~/components/Spinner'
|
import Spinner from '~/components/Spinner'
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
/* eslint-disable @typescript-eslint/naming-convention */
|
/* eslint-disable @typescript-eslint/naming-convention */
|
||||||
/* eslint-disable unicorn/no-keyword-prefix */
|
/* eslint-disable unicorn/no-keyword-prefix */
|
||||||
import { Dialog } from '@headlessui/react'
|
|
||||||
import { useFetcher } from '@remix-run/react'
|
import { useFetcher } from '@remix-run/react'
|
||||||
import { useState } from 'react'
|
import { useState } from 'react'
|
||||||
|
|
||||||
|
|||||||
@@ -1,17 +1,13 @@
|
|||||||
|
|
||||||
/* eslint-disable @typescript-eslint/no-non-null-assertion */
|
|
||||||
|
|
||||||
import { InformationCircleIcon } from '@heroicons/react/24/outline'
|
import { InformationCircleIcon } from '@heroicons/react/24/outline'
|
||||||
import { type ActionFunctionArgs, json, type LoaderFunctionArgs } from '@remix-run/node'
|
import { type ActionFunctionArgs, json, type LoaderFunctionArgs } from '@remix-run/node'
|
||||||
import { useFetcher, useLoaderData } from '@remix-run/react'
|
import { useFetcher, useLoaderData } from '@remix-run/react'
|
||||||
import clsx from 'clsx'
|
|
||||||
import { Button, Tooltip, TooltipTrigger } from 'react-aria-components'
|
import { Button, Tooltip, TooltipTrigger } from 'react-aria-components'
|
||||||
|
|
||||||
import Code from '~/components/Code'
|
import Code from '~/components/Code'
|
||||||
import useModal from '~/components/Modal'
|
|
||||||
import { type Machine } from '~/types'
|
import { type Machine } from '~/types'
|
||||||
|
import { cn } from '~/utils/cn'
|
||||||
import { getConfig, getContext } from '~/utils/config'
|
import { getConfig, getContext } from '~/utils/config'
|
||||||
import { del, pull } from '~/utils/headscale'
|
import { del, post, pull } from '~/utils/headscale'
|
||||||
import { getSession } from '~/utils/sessions'
|
import { getSession } from '~/utils/sessions'
|
||||||
import { useLiveData } from '~/utils/useLiveData'
|
import { useLiveData } from '~/utils/useLiveData'
|
||||||
|
|
||||||
@@ -20,6 +16,7 @@ import MachineRow from './machine'
|
|||||||
export async function loader({ request }: LoaderFunctionArgs) {
|
export async function loader({ request }: LoaderFunctionArgs) {
|
||||||
const session = await getSession(request.headers.get('Cookie'))
|
const session = await getSession(request.headers.get('Cookie'))
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||||
const data = await pull<{ nodes: Machine[] }>('v1/node', session.get('hsApiKey')!)
|
const data = await pull<{ nodes: Machine[] }>('v1/node', session.get('hsApiKey')!)
|
||||||
const context = await getContext()
|
const context = await getContext()
|
||||||
|
|
||||||
@@ -38,13 +35,6 @@ export async function loader({ request }: LoaderFunctionArgs) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function action({ request }: ActionFunctionArgs) {
|
export async function action({ request }: ActionFunctionArgs) {
|
||||||
const data = await request.json() as { id?: string }
|
|
||||||
if (!data.id) {
|
|
||||||
return json({ message: 'No ID provided' }, {
|
|
||||||
status: 400
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const session = await getSession(request.headers.get('Cookie'))
|
const session = await getSession(request.headers.get('Cookie'))
|
||||||
if (!session.has('hsApiKey')) {
|
if (!session.has('hsApiKey')) {
|
||||||
return json({ message: 'Unauthorized' }, {
|
return json({ message: 'Unauthorized' }, {
|
||||||
@@ -52,8 +42,43 @@ export async function action({ request }: ActionFunctionArgs) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
await del(`v1/node/${data.id}`, session.get('hsApiKey')!)
|
const data = await request.formData()
|
||||||
return json({ message: 'Machine removed' })
|
if (!data.has('_method') || !data.has('id')) {
|
||||||
|
return json({ message: 'No method or ID provided' }, {
|
||||||
|
status: 400
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const id = String(data.get('id'))
|
||||||
|
const method = String(data.get('_method'))
|
||||||
|
|
||||||
|
switch (method) {
|
||||||
|
case 'delete': {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||||
|
await del(`v1/node/${id}`, session.get('hsApiKey')!)
|
||||||
|
return json({ message: 'Machine removed' })
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'rename': {
|
||||||
|
if (!data.has('name')) {
|
||||||
|
return json({ message: 'No name provided' }, {
|
||||||
|
status: 400
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const name = String(data.get('name'))
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||||
|
await post(`v1/node/${id}/rename/${name}`, session.get('hsApiKey')!)
|
||||||
|
return json({ message: 'Machine renamed' })
|
||||||
|
}
|
||||||
|
|
||||||
|
default: {
|
||||||
|
return json({ message: 'Invalid method' }, {
|
||||||
|
status: 400
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function Page() {
|
export default function Page() {
|
||||||
@@ -61,11 +86,8 @@ export default function Page() {
|
|||||||
const data = useLoaderData<typeof loader>()
|
const data = useLoaderData<typeof loader>()
|
||||||
const fetcher = useFetcher()
|
const fetcher = useFetcher()
|
||||||
|
|
||||||
const { Modal, open } = useModal()
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{Modal}
|
|
||||||
<h1 className='text-2xl font-medium mb-4'>Machines</h1>
|
<h1 className='text-2xl font-medium mb-4'>Machines</h1>
|
||||||
<table className='table-auto w-full rounded-lg'>
|
<table className='table-auto w-full rounded-lg'>
|
||||||
<thead className='text-gray-500 dark:text-gray-400'>
|
<thead className='text-gray-500 dark:text-gray-400'>
|
||||||
@@ -79,7 +101,7 @@ export default function Page() {
|
|||||||
<Button>
|
<Button>
|
||||||
<InformationCircleIcon className='w-4 h-4 text-gray-400'/>
|
<InformationCircleIcon className='w-4 h-4 text-gray-400'/>
|
||||||
</Button>
|
</Button>
|
||||||
<Tooltip className={clsx(
|
<Tooltip className={cn(
|
||||||
'text-sm max-w-xs p-2 rounded-lg mb-2',
|
'text-sm max-w-xs p-2 rounded-lg mb-2',
|
||||||
'bg-white dark:bg-zinc-800',
|
'bg-white dark:bg-zinc-800',
|
||||||
'border border-gray-200 dark:border-zinc-700'
|
'border border-gray-200 dark:border-zinc-700'
|
||||||
@@ -99,7 +121,7 @@ export default function Page() {
|
|||||||
<th className='pb-2'>Last Seen</th>
|
<th className='pb-2'>Last Seen</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody className={clsx(
|
<tbody className={cn(
|
||||||
'divide-y divide-zinc-200 dark:divide-zinc-700 align-top',
|
'divide-y divide-zinc-200 dark:divide-zinc-700 align-top',
|
||||||
'border-t border-zinc-200 dark:border-zinc-700'
|
'border-t border-zinc-200 dark:border-zinc-700'
|
||||||
)}
|
)}
|
||||||
@@ -110,7 +132,6 @@ export default function Page() {
|
|||||||
// Typescript isn't smart enough yet
|
// Typescript isn't smart enough yet
|
||||||
machine={machine as unknown as Machine}
|
machine={machine as unknown as Machine}
|
||||||
fetcher={fetcher}
|
fetcher={fetcher}
|
||||||
open={open}
|
|
||||||
magic={data.magic}
|
magic={data.magic}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
|
|||||||
+19
-16
@@ -2,8 +2,8 @@ import { Cog8ToothIcon, CpuChipIcon, GlobeAltIcon, LockClosedIcon, ServerStackIc
|
|||||||
import { type LoaderFunctionArgs, redirect } from '@remix-run/node'
|
import { type LoaderFunctionArgs, redirect } from '@remix-run/node'
|
||||||
import { Form, Outlet, useLoaderData, useRouteError } from '@remix-run/react'
|
import { Form, Outlet, useLoaderData, useRouteError } from '@remix-run/react'
|
||||||
|
|
||||||
import Dropdown from '~/components/Dropdown'
|
|
||||||
import { ErrorPopup } from '~/components/Error'
|
import { ErrorPopup } from '~/components/Error'
|
||||||
|
import Menu from '~/components/Menu'
|
||||||
import TabLink from '~/components/TabLink'
|
import TabLink from '~/components/TabLink'
|
||||||
import { getContext } from '~/utils/config'
|
import { getContext } from '~/utils/config'
|
||||||
import { HeadscaleError, pull } from '~/utils/headscale'
|
import { HeadscaleError, pull } from '~/utils/headscale'
|
||||||
@@ -62,21 +62,24 @@ export default function Layout() {
|
|||||||
<a href='https://github.com/juanfont/headscale' target='_blank' rel='noreferrer' className='text-gray-300 hover:text-white'>
|
<a href='https://github.com/juanfont/headscale' target='_blank' rel='noreferrer' className='text-gray-300 hover:text-white'>
|
||||||
Headscale
|
Headscale
|
||||||
</a>
|
</a>
|
||||||
<Dropdown
|
<Menu>
|
||||||
button={<UserCircleIcon className='w-8 h-8'/>}
|
<Menu.Button>
|
||||||
>
|
<UserCircleIcon className='w-8 h-8'/>
|
||||||
<Dropdown.Item variant='static'>
|
</Menu.Button>
|
||||||
<p className='font-bold'>{data.user?.name}</p>
|
<Menu.Items>
|
||||||
<p>{data.user?.email}</p>
|
<Menu.Item className='text-right'>
|
||||||
</Dropdown.Item>
|
<p className='font-bold'>{data.user?.name}</p>
|
||||||
<Dropdown.Item className='text-red-700 cursor-pointer'>
|
<p>{data.user?.email}</p>
|
||||||
<Form method='POST' action='/logout'>
|
</Menu.Item>
|
||||||
<button type='submit' className='w-full'>
|
<Menu.Item className='text-red-700 cursor-pointer'>
|
||||||
Logout
|
<Form method='POST' action='/logout'>
|
||||||
</button>
|
<button type='submit' className='w-full text-right'>
|
||||||
</Form>
|
Logout
|
||||||
</Dropdown.Item>
|
</button>
|
||||||
</Dropdown>
|
</Form>
|
||||||
|
</Menu.Item>
|
||||||
|
</Menu.Items>
|
||||||
|
</Menu>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className='flex items-center gap-x-4'>
|
<div className='flex items-center gap-x-4'>
|
||||||
|
|||||||
Reference in New Issue
Block a user