feat: add user page
This commit is contained in:
parent
22c4b9504b
commit
c62da81ea7
@ -10,8 +10,8 @@ type Properties = {
|
||||
export default function Attribute({ name, value, isCopyable }: Properties) {
|
||||
const canCopy = isCopyable ?? false
|
||||
return (
|
||||
<dl className='flex gap-1 text-sm'>
|
||||
<dt className='w-1/4 shrink-0 min-w-0 truncate text-gray-700 dark:text-gray-300'>
|
||||
<dl className='flex gap-1 text-sm w-full'>
|
||||
<dt className='w-1/4 shrink-0 min-w-0 truncate text-gray-700 dark:text-gray-300 py-1'>
|
||||
{name}
|
||||
</dt>
|
||||
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { CpuChipIcon, ServerStackIcon } from '@heroicons/react/24/outline'
|
||||
import { CpuChipIcon, ServerStackIcon, UsersIcon } from '@heroicons/react/24/outline'
|
||||
import { type LoaderFunctionArgs, redirect } from '@remix-run/node'
|
||||
import { Outlet } from '@remix-run/react'
|
||||
|
||||
@ -46,6 +46,7 @@ export default function Layout() {
|
||||
</div>
|
||||
<div className='flex items-center gap-x-4'>
|
||||
<TabLink to='/machines' name='Machines' icon={<ServerStackIcon className='w-4 h-4'/>}/>
|
||||
<TabLink to='/users' name='Users' icon={<UsersIcon className='w-4 h-4'/>}/>
|
||||
</div>
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
66
app/routes/_data.users._index.tsx
Normal file
66
app/routes/_data.users._index.tsx
Normal file
@ -0,0 +1,66 @@
|
||||
/* eslint-disable unicorn/filename-case */
|
||||
import { ClipboardIcon, UserIcon } from '@heroicons/react/24/outline'
|
||||
import { type LoaderFunctionArgs } from '@remix-run/node'
|
||||
import { useLoaderData } from '@remix-run/react'
|
||||
import { toast } from 'react-hot-toast/headless'
|
||||
|
||||
import Attribute from '~/components/Attribute'
|
||||
import StatusCircle from '~/components/StatusCircle'
|
||||
import { type Machine, type User } from '~/types'
|
||||
import { pull } from '~/utils/headscale'
|
||||
import { getSession } from '~/utils/sessions'
|
||||
import { useLiveData } from '~/utils/useLiveData'
|
||||
|
||||
export async function loader({ request }: LoaderFunctionArgs) {
|
||||
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 users = new Map<string, Machine[]>()
|
||||
for (const machine of data.nodes) {
|
||||
const { user } = machine
|
||||
if (!users.has(user.id)) {
|
||||
users.set(user.id, [])
|
||||
}
|
||||
|
||||
users.get(user.id)?.push(machine)
|
||||
}
|
||||
|
||||
return [...users.values()].map(machines => {
|
||||
const { user } = machines[0]
|
||||
|
||||
return {
|
||||
...user,
|
||||
machines
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export default function Page() {
|
||||
const data = useLoaderData<typeof loader>()
|
||||
useLiveData({ interval: 3000 })
|
||||
|
||||
return (
|
||||
<div className='grid grid-cols-2 gap-4 auto-rows-min'>
|
||||
{data.map(user => (
|
||||
<div key={user.id} className='border rounded-lg p-4'>
|
||||
<div className='flex items-center gap-4'>
|
||||
<UserIcon className='w-6 h-6'/>
|
||||
<span className='text-lg font-mono'>
|
||||
{user.name}
|
||||
</span>
|
||||
</div>
|
||||
<div className='py-4'>
|
||||
{user.machines.map(machine => (
|
||||
<div key={machine.id} className='flex items-center w-full gap-4'>
|
||||
<StatusCircle isOnline={machine.online} className='w-4 h-4 px-1 w-fit'/>
|
||||
<Attribute name={`Node ${machine.id}`} value={machine.givenName}/>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user