headplane/app/routes/dns/components/manage-domains.tsx

206 lines
5 KiB
TypeScript
Raw Normal View History

2025-01-28 16:06:41 -05:00
import { DndContext, DragOverlay, closestCorners } from '@dnd-kit/core';
2024-03-28 19:40:06 -04:00
import {
restrictToParentElement,
2024-12-31 10:30:14 +05:30
restrictToVerticalAxis,
} from '@dnd-kit/modifiers';
import {
SortableContext,
2025-01-28 16:06:41 -05:00
arrayMove,
useSortable,
2024-12-31 10:30:14 +05:30
verticalListSortingStrategy,
} from '@dnd-kit/sortable';
import { CSS } from '@dnd-kit/utilities';
2025-02-04 11:42:57 -05:00
import { GripVertical, Lock } from 'lucide-react';
2024-12-31 10:30:14 +05:30
import { useEffect, useState } from 'react';
import { type FetcherWithComponents, Form, useFetcher } from 'react-router';
2025-02-04 11:42:57 -05:00
import Button from '~/components/Button';
import Input from '~/components/Input';
2024-12-31 10:30:14 +05:30
import TableList from '~/components/TableList';
2025-01-28 17:46:16 -05:00
import cn from '~/utils/cn';
interface Props {
searchDomains: string[];
isDisabled: boolean;
magic?: string;
}
export default function ManageDomains({
2024-12-31 10:30:14 +05:30
searchDomains,
isDisabled,
magic,
}: Props) {
2024-12-31 10:30:14 +05:30
const [activeId, setActiveId] = useState<number | string | null>(null);
const [localDomains, setLocalDomains] = useState(searchDomains);
2024-03-28 19:40:06 -04:00
useEffect(() => {
2024-12-31 10:30:14 +05:30
setLocalDomains(searchDomains);
}, [searchDomains]);
return (
2024-12-31 10:30:14 +05:30
<div className="flex flex-col w-2/3">
<h1 className="text-2xl font-medium mb-4">Search Domains</h1>
2025-02-04 17:21:03 -05:00
<p className="mb-4">
2024-12-31 10:30:14 +05:30
Set custom DNS search domains for your Tailnet. When using Magic DNS,
your tailnet domain is used as the first search domain.
</p>
2024-03-28 19:40:06 -04:00
<DndContext
modifiers={[restrictToVerticalAxis, restrictToParentElement]}
collisionDetection={closestCorners}
2024-12-31 10:30:14 +05:30
onDragStart={(event) => {
setActiveId(event.active.id);
2024-03-28 19:40:06 -04:00
}}
2024-12-31 10:30:14 +05:30
onDragEnd={(event) => {
setActiveId(null);
const { active, over } = event;
2024-03-28 19:40:06 -04:00
if (!over) {
2024-12-31 10:30:14 +05:30
return;
2024-03-28 19:40:06 -04:00
}
2024-12-31 10:30:14 +05:30
const activeItem = localDomains[(active.id as number) - 1];
const overItem = localDomains[(over.id as number) - 1];
2024-03-28 19:40:06 -04:00
if (!activeItem || !overItem) {
2024-12-31 10:30:14 +05:30
return;
2024-03-28 19:40:06 -04:00
}
2024-12-31 10:30:14 +05:30
const oldIndex = localDomains.indexOf(activeItem);
const newIndex = localDomains.indexOf(overItem);
2024-03-28 19:40:06 -04:00
if (oldIndex !== newIndex) {
2024-12-31 10:30:14 +05:30
setLocalDomains(arrayMove(localDomains, oldIndex, newIndex));
2024-03-28 19:40:06 -04:00
}
}}
>
<TableList>
{magic ? (
2024-12-31 10:30:14 +05:30
<TableList.Item key="magic-dns-sd">
2025-02-04 17:21:03 -05:00
<div
className={cn(
'flex items-center gap-4',
isDisabled ? 'flex-row-reverse justify-between w-full' : '',
2025-02-04 17:21:03 -05:00
)}
>
2025-02-04 11:42:57 -05:00
<Lock className="p-0.5" />
<p className="font-mono text-sm py-0.5">{magic}</p>
2025-02-04 11:42:57 -05:00
</div>
</TableList.Item>
2024-03-28 19:40:06 -04:00
) : undefined}
<SortableContext
items={localDomains}
strategy={verticalListSortingStrategy}
>
{localDomains.map((sd, index) => (
<Domain
2025-01-28 16:06:41 -05:00
key={sd}
domain={sd}
id={index + 1}
isDisabled={isDisabled}
/>
))}
2024-03-28 19:40:06 -04:00
<DragOverlay adjustScale>
2024-12-31 10:30:14 +05:30
{activeId ? (
<Domain
isDragging
2024-12-31 10:30:14 +05:30
domain={localDomains[(activeId as number) - 1]}
id={(activeId as number) - 1}
isDisabled={isDisabled}
2024-12-31 10:30:14 +05:30
/>
) : undefined}
2024-03-28 19:40:06 -04:00
</DragOverlay>
</SortableContext>
{isDisabled ? undefined : (
2024-12-31 10:30:14 +05:30
<TableList.Item key="add-sd">
<Form
method="POST"
className="flex items-center justify-between w-full"
>
<input type="hidden" name="action_id" value="add_domain" />
<Input
type="text"
className={cn(
'border-none font-mono p-0 text-sm',
'rounded-none focus:ring-0 w-full ml-1',
)}
placeholder="Search Domain"
label="Search Domain"
name="domain"
labelHidden
isRequired
/>
2024-03-30 04:12:37 -04:00
<Button
type="submit"
className={cn(
2025-02-04 11:42:57 -05:00
'px-2 py-1 rounded-md',
'text-blue-500 dark:text-blue-400',
)}
2024-03-30 04:12:37 -04:00
>
Add
</Button>
</Form>
</TableList.Item>
)}
</TableList>
2024-03-28 19:40:06 -04:00
</DndContext>
</div>
2024-12-31 10:30:14 +05:30
);
}
interface DomainProps {
domain: string;
id: number;
isDragging?: boolean;
isDisabled: boolean;
}
function Domain({ domain, id, isDragging, isDisabled }: DomainProps) {
const {
attributes,
listeners,
setNodeRef,
transform,
transition,
isDragging: isSortableDragging,
2024-12-31 10:30:14 +05:30
} = useSortable({ id });
return (
2025-02-04 11:42:57 -05:00
<TableList.Item
ref={setNodeRef}
className={cn(
isSortableDragging ? 'opacity-50' : '',
2025-05-29 09:45:47 -04:00
isDragging ? 'ring-3 bg-white dark:bg-headplane-900' : '',
)}
style={{
transform: CSS.Transform.toString(transform),
2024-12-31 10:30:14 +05:30
transition,
}}
>
2024-12-31 10:30:14 +05:30
<p className="font-mono text-sm flex items-center gap-4">
{isDisabled ? undefined : (
<GripVertical
{...attributes}
{...listeners}
2025-05-29 09:45:47 -04:00
className="p-0.5 focus:ring-3 outline-hidden rounded-md"
/>
)}
{domain}
</p>
{isDragging ? undefined : (
<Form method="POST">
<input type="hidden" name="action_id" value="remove_domain" />
<input type="hidden" name="domain" value={domain} />
<Button
type="submit"
isDisabled={isDisabled}
className={cn(
'px-2 py-1 rounded-md',
'text-red-500 dark:text-red-400',
)}
>
Remove
</Button>
</Form>
)}
2025-02-04 11:42:57 -05:00
</TableList.Item>
2024-12-31 10:30:14 +05:30
);
}