mirror of
https://github.com/tale/headplane.git
synced 2026-08-24 11:36:31 +00:00
fix: switch to new aria components
This commit is contained in:
@@ -79,6 +79,14 @@ export default function MachineRow({
|
||||
tags.unshift('Subnets');
|
||||
}
|
||||
|
||||
const ipOptions = useMemo(() => {
|
||||
if (magic) {
|
||||
return [...machine.ipAddresses, `${machine.givenName}.${prefix}`];
|
||||
}
|
||||
|
||||
return machine.ipAddresses;
|
||||
}, [magic, machine.ipAddresses]);
|
||||
|
||||
return (
|
||||
<tr
|
||||
key={machine.id}
|
||||
@@ -108,46 +116,32 @@ export default function MachineRow({
|
||||
<td className="py-2">
|
||||
<div className="flex items-center gap-x-1">
|
||||
{machine.ipAddresses[0]}
|
||||
<Menu>
|
||||
<Menu placement="bottom end">
|
||||
<Menu.IconButton className="bg-transparent" label="IP Addresses">
|
||||
<ChevronDownIcon className="w-4 h-4" />
|
||||
</Menu.IconButton>
|
||||
<Menu.Items>
|
||||
{machine.ipAddresses.map((ip) => (
|
||||
<Menu.ItemButton
|
||||
key={ip}
|
||||
type="button"
|
||||
className={cn(
|
||||
'flex items-center gap-x-1.5 text-sm',
|
||||
'justify-between w-full',
|
||||
)}
|
||||
onPress={async () => {
|
||||
await navigator.clipboard.writeText(ip);
|
||||
toast('Copied IP address to clipboard');
|
||||
}}
|
||||
>
|
||||
{ip}
|
||||
<CopyIcon className="w-3 h-3" />
|
||||
</Menu.ItemButton>
|
||||
))}
|
||||
{magic ? (
|
||||
<Menu.ItemButton
|
||||
type="button"
|
||||
className={cn(
|
||||
'flex items-center gap-x-1.5 text-sm',
|
||||
'justify-between w-full break-keep',
|
||||
)}
|
||||
onPress={async () => {
|
||||
const ip = `${machine.givenName}.${prefix}`;
|
||||
await navigator.clipboard.writeText(ip);
|
||||
toast('Copied hostname to clipboard');
|
||||
}}
|
||||
>
|
||||
{machine.givenName}.{prefix}
|
||||
<CopyIcon className="w-3 h-3" />
|
||||
</Menu.ItemButton>
|
||||
) : undefined}
|
||||
</Menu.Items>
|
||||
<Menu.Panel
|
||||
onAction={async (key) => {
|
||||
await navigator.clipboard.writeText(key.toString());
|
||||
toast('Copied IP address to clipboard');
|
||||
}}
|
||||
>
|
||||
<Menu.Section>
|
||||
{ipOptions.map((ip) => (
|
||||
<Menu.Item key={ip} textValue={ip}>
|
||||
<div
|
||||
className={cn(
|
||||
'flex items-center justify-between',
|
||||
'text-sm w-full gap-x-6',
|
||||
)}
|
||||
>
|
||||
{ip}
|
||||
<CopyIcon className="w-3 h-3" />
|
||||
</div>
|
||||
</Menu.Item>
|
||||
))}
|
||||
</Menu.Section>
|
||||
</Menu.Panel>
|
||||
</Menu>
|
||||
</div>
|
||||
</td>
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import { KebabHorizontalIcon } from '@primer/octicons-react';
|
||||
import React, { useState } from 'react';
|
||||
import MenuComponent from '~/components/Menu';
|
||||
import { Cog, Ellipsis } from 'lucide-react';
|
||||
import { useState } from 'react';
|
||||
import Menu from '~/components/Menu';
|
||||
import type { Machine, Route, User } from '~/types';
|
||||
import { cn } from '~/utils/cn';
|
||||
|
||||
import Delete from '../dialogs/delete';
|
||||
import Expire from '../dialogs/expire';
|
||||
import Move from '../dialogs/move';
|
||||
@@ -16,17 +15,17 @@ interface MenuProps {
|
||||
routes: Route[];
|
||||
users: User[];
|
||||
magic?: string;
|
||||
buttonChild?: React.ReactNode;
|
||||
isFullButton?: boolean;
|
||||
}
|
||||
|
||||
type Modal = 'rename' | 'expire' | 'remove' | 'routes' | 'move' | 'tags' | null;
|
||||
|
||||
export default function Menu({
|
||||
export default function MachineMenu({
|
||||
machine,
|
||||
routes,
|
||||
magic,
|
||||
users,
|
||||
buttonChild,
|
||||
isFullButton,
|
||||
}: MenuProps) {
|
||||
const [modal, setModal] = useState<Modal>(null);
|
||||
|
||||
@@ -97,44 +96,45 @@ export default function Menu({
|
||||
/>
|
||||
)}
|
||||
|
||||
<MenuComponent>
|
||||
{buttonChild ?? (
|
||||
<MenuComponent.Button
|
||||
<Menu>
|
||||
{isFullButton ? (
|
||||
<Menu.Button className="flex items-center gap-x-2">
|
||||
<Cog className="h-5" />
|
||||
<p>Machine Settings</p>
|
||||
</Menu.Button>
|
||||
) : (
|
||||
<Menu.IconButton
|
||||
label="Machine Options"
|
||||
className={cn(
|
||||
'flex items-center justify-center',
|
||||
'border border-transparent rounded-lg py-0.5 w-10',
|
||||
'group-hover:border-gray-200 dark:group-hover:border-zinc-700',
|
||||
'py-0.5 w-10 bg-transparent border-transparent',
|
||||
'border group-hover:border-headplane-200',
|
||||
'dark:group-hover:border-headplane-700',
|
||||
)}
|
||||
>
|
||||
<KebabHorizontalIcon className="w-5" />
|
||||
</MenuComponent.Button>
|
||||
<Ellipsis className="h-5" />
|
||||
</Menu.IconButton>
|
||||
)}
|
||||
<MenuComponent.Items>
|
||||
<MenuComponent.ItemButton onPress={() => setModal('rename')}>
|
||||
Edit machine name
|
||||
</MenuComponent.ItemButton>
|
||||
<MenuComponent.ItemButton onPress={() => setModal('routes')}>
|
||||
Edit route settings
|
||||
</MenuComponent.ItemButton>
|
||||
<MenuComponent.ItemButton onPress={() => setModal('tags')}>
|
||||
Edit ACL tags
|
||||
</MenuComponent.ItemButton>
|
||||
<MenuComponent.ItemButton onPress={() => setModal('move')}>
|
||||
Change owner
|
||||
</MenuComponent.ItemButton>
|
||||
{expired ? undefined : (
|
||||
<MenuComponent.ItemButton onPress={() => setModal('expire')}>
|
||||
Expire
|
||||
</MenuComponent.ItemButton>
|
||||
)}
|
||||
<MenuComponent.ItemButton
|
||||
className="text-red-500 dark:text-red-400"
|
||||
onPress={() => setModal('remove')}
|
||||
>
|
||||
Remove
|
||||
</MenuComponent.ItemButton>
|
||||
</MenuComponent.Items>
|
||||
</MenuComponent>
|
||||
<Menu.Panel onAction={(key) => setModal(key as Modal)}>
|
||||
<Menu.Section>
|
||||
<Menu.Item key="rename">Edit machine name</Menu.Item>
|
||||
<Menu.Item key="routes">Edit route settings</Menu.Item>
|
||||
<Menu.Item key="tags">Edit ACL tags</Menu.Item>
|
||||
<Menu.Item key="move">Change owner</Menu.Item>
|
||||
</Menu.Section>
|
||||
<Menu.Section>
|
||||
{expired ? (
|
||||
<></>
|
||||
) : (
|
||||
<Menu.Item key="expire" textValue="Expire">
|
||||
<p className="text-red-500 dark:text-red-400">Expire</p>
|
||||
</Menu.Item>
|
||||
)}
|
||||
<Menu.Item key="remove" textValue="Remove">
|
||||
<p className="text-red-500 dark:text-red-400">Remove</p>
|
||||
</Menu.Item>
|
||||
</Menu.Section>
|
||||
</Menu.Panel>
|
||||
</Menu>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -26,9 +26,7 @@ export default function Move({ machine, users, isOpen, setIsOpen }: MoveProps) {
|
||||
defaultSelectedKey={machine.user.id}
|
||||
>
|
||||
{users.map((user) => (
|
||||
<Select.Item key={user.id} id={user.name}>
|
||||
{user.name}
|
||||
</Select.Item>
|
||||
<Select.Item key={user.id}>{user.name}</Select.Item>
|
||||
))}
|
||||
</Select>
|
||||
</Dialog.Panel>
|
||||
|
||||
@@ -1,14 +1,11 @@
|
||||
import { KeyIcon, ServerIcon } from '@primer/octicons-react';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { Link, useFetcher } from 'react-router';
|
||||
|
||||
import { Computer, KeySquare } from 'lucide-react';
|
||||
import { useState } from 'react';
|
||||
import { useNavigate } from 'react-router';
|
||||
import Code from '~/components/Code';
|
||||
import Dialog from '~/components/Dialog';
|
||||
import Input from '~/components/Input';
|
||||
import Menu from '~/components/Menu';
|
||||
import Select from '~/components/Select';
|
||||
import Spinner from '~/components/Spinner';
|
||||
import TextField from '~/components/TextField';
|
||||
import { toast } from '~/components/Toaster';
|
||||
import type { User } from '~/types';
|
||||
|
||||
export interface NewProps {
|
||||
@@ -17,32 +14,14 @@ export interface NewProps {
|
||||
}
|
||||
|
||||
export default function New(data: NewProps) {
|
||||
const fetcher = useFetcher<{ success?: boolean }>();
|
||||
const [pushDialog, setPushDialog] = useState(false);
|
||||
const [mkey, setMkey] = useState('');
|
||||
const [user, setUser] = useState('');
|
||||
const [toasted, setToasted] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (!fetcher.data || toasted) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (fetcher.data.success) {
|
||||
toast('Registered new machine');
|
||||
} else {
|
||||
toast('Failed to register machine due to an invalid key');
|
||||
}
|
||||
|
||||
setToasted(true);
|
||||
}, [fetcher.data, toasted]);
|
||||
const navigate = useNavigate();
|
||||
|
||||
return (
|
||||
<>
|
||||
<Dialog isOpen={pushDialog} onOpenChange={setPushDialog}>
|
||||
<Dialog.Panel
|
||||
isDisabled={!mkey || !mkey.trim().startsWith('mkey:') || !user}
|
||||
>
|
||||
<Dialog.Panel isDisabled={!mkey.trim().startsWith('mkey:')}>
|
||||
<Dialog.Title>Register Machine Key</Dialog.Title>
|
||||
<Dialog.Text className="mb-4">
|
||||
The machine key is given when you run{' '}
|
||||
@@ -54,41 +33,55 @@ export default function New(data: NewProps) {
|
||||
</Dialog.Text>
|
||||
<input type="hidden" name="_method" value="register" />
|
||||
<input type="hidden" name="id" value="_" />
|
||||
<TextField
|
||||
<Input
|
||||
isRequired
|
||||
label="Machine Key"
|
||||
placeholder="mkey:ff....."
|
||||
validationBehavior="native"
|
||||
name="mkey"
|
||||
state={[mkey, setMkey]}
|
||||
className="my-2 font-mono"
|
||||
onChange={setMkey}
|
||||
/>
|
||||
<Select
|
||||
isRequired
|
||||
label="Owner"
|
||||
name="user"
|
||||
placeholder="Select a user"
|
||||
state={[user, setUser]}
|
||||
>
|
||||
{data.users.map((user) => (
|
||||
<Select.Item key={user.id} id={user.name}>
|
||||
{user.name}
|
||||
</Select.Item>
|
||||
<Select.Item key={user.id}>{user.name}</Select.Item>
|
||||
))}
|
||||
</Select>
|
||||
</Dialog.Panel>
|
||||
</Dialog>
|
||||
<Menu>
|
||||
<Menu.Button variant="heavy">Add Device</Menu.Button>
|
||||
<Menu.Items>
|
||||
<Menu.ItemButton onPress={() => setPushDialog(true)}>
|
||||
<ServerIcon className="w-4 h-4 mr-2" />
|
||||
Register Machine Key
|
||||
</Menu.ItemButton>
|
||||
<Menu.ItemButton>
|
||||
<Link to="/settings/auth-keys">
|
||||
<KeyIcon className="w-4 h-4 mr-2" />
|
||||
Generate Pre-auth Key
|
||||
</Link>
|
||||
</Menu.ItemButton>
|
||||
</Menu.Items>
|
||||
<Menu.Panel
|
||||
onAction={(key) => {
|
||||
if (key === 'register') {
|
||||
setPushDialog(true);
|
||||
return;
|
||||
}
|
||||
|
||||
if (key === 'pre-auth') {
|
||||
navigate('/settings/auth-keys');
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Menu.Section>
|
||||
<Menu.Item key="register" textValue="Register Machine Key">
|
||||
<div className="flex items-center gap-x-3">
|
||||
<Computer className="w-4" />
|
||||
Register Machine Key
|
||||
</div>
|
||||
</Menu.Item>
|
||||
<Menu.Item key="pre-auth" textValue="Generate Pre-auth Key">
|
||||
<div className="flex items-center gap-x-3">
|
||||
<KeySquare className="w-4" />
|
||||
Generate Pre-auth Key
|
||||
</div>
|
||||
</Menu.Item>
|
||||
</Menu.Section>
|
||||
</Menu.Panel>
|
||||
</Menu>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { useState } from 'react';
|
||||
import Code from '~/components/Code';
|
||||
import Dialog from '~/components/Dialog';
|
||||
import TextField from '~/components/TextField';
|
||||
import Input from '~/components/Input';
|
||||
import type { Machine } from '~/types';
|
||||
|
||||
interface RenameProps {
|
||||
@@ -29,11 +29,10 @@ export default function Rename({
|
||||
</Dialog.Text>
|
||||
<input type="hidden" name="_method" value="rename" />
|
||||
<input type="hidden" name="id" value={machine.id} />
|
||||
<TextField
|
||||
<Input
|
||||
label="Machine name"
|
||||
placeholder="Machine name"
|
||||
name="name"
|
||||
className="my-2"
|
||||
defaultValue={machine.givenName}
|
||||
onChange={setName}
|
||||
/>
|
||||
|
||||
@@ -138,24 +138,11 @@ export default function Page() {
|
||||
</span>
|
||||
|
||||
<MenuOptions
|
||||
isFullButton
|
||||
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',
|
||||
)}
|
||||
>
|
||||
<GearIcon className="w-5" />
|
||||
Machine Settings
|
||||
</Menu.Button>
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex gap-1 mb-4">
|
||||
@@ -196,7 +183,8 @@ export default function Page() {
|
||||
<Routes
|
||||
machine={machine}
|
||||
routes={routes}
|
||||
state={[showRouting, setShowRouting]}
|
||||
isOpen={showRouting}
|
||||
setIsOpen={setShowRouting}
|
||||
/>
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<p>
|
||||
|
||||
Reference in New Issue
Block a user