'use client'; import { Display } from '@/components/display'; import { ProTable, ProTableActions } from '@/components/pro-table'; import { batchDeleteNode, createNode, deleteNode, getNodeGroupList, getNodeList, nodeSort, updateNode, } from '@/services/admin/server'; import { useQuery } from '@tanstack/react-query'; import { Badge } from '@workspace/ui/components/badge'; import { Button } from '@workspace/ui/components/button'; import { Switch } from '@workspace/ui/components/switch'; import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, } from '@workspace/ui/components/tooltip'; import { ConfirmButton } from '@workspace/ui/custom-components/confirm-button'; import { cn } from '@workspace/ui/lib/utils'; import { useTranslations } from 'next-intl'; import { useRef, useState } from 'react'; import { toast } from 'sonner'; import NodeForm from './node-form'; import { NodeStatusCell } from './node-status'; export default function NodeTable() { const t = useTranslations('server.node'); const [loading, setLoading] = useState(false); const { data: groups } = useQuery({ queryKey: ['getNodeGroupList'], queryFn: async () => { const { data } = await getNodeGroupList(); return (data.data?.list || []) as API.ServerGroup[]; }, }); const ref = useRef(null); return ( action={ref} header={{ toolbar: ( trigger={t('create')} title={t('createNode')} loading={loading} onSubmit={async (values) => { setLoading(true); try { await createNode({ ...values, enable: false }); toast.success(t('createSuccess')); ref.current?.refresh(); setLoading(false); return true; } catch (error) { setLoading(false); return false; } }} /> ), }} columns={[ { accessorKey: 'id', header: 'ID', cell: ({ row }) => ( {row.getValue('id')} {row.original.protocol} ), }, { accessorKey: 'enable', header: t('enable'), cell: ({ row }) => { return ( { await updateNode({ ...row.original, id: row.original.id!, enable: checked, } as API.UpdateNodeRequest); ref.current?.refresh(); }} /> ); }, }, { accessorKey: 'status', header: t('status'), cell: ({ row }) => { return ; }, }, { accessorKey: 'name', header: t('name'), }, { accessorKey: 'server_addr', header: t('serverAddr'), }, { accessorKey: 'speed_limit', header: t('speedLimit'), cell: ({ row }) => ( ), }, { accessorKey: 'traffic_ratio', header: t('trafficRatio'), cell: ({ row }) => {row.getValue('traffic_ratio')} X, }, { accessorKey: 'groupId', header: t('nodeGroup'), cell: ({ row }) => { const name = groups?.find((group) => group.id === row.getValue('groupId'))?.name; return name ? {name} : '--'; }, }, ]} params={[ { key: 'search', }, { key: 'group_id', placeholder: t('nodeGroup'), options: groups?.map((item) => ({ label: item.name, value: String(item.id), })), }, ]} request={async (pagination, filter) => { const { data } = await getNodeList({ ...pagination, ...filter, }); return { list: data.data?.list || [], total: data.data?.total || 0, }; }} actions={{ render: (row) => [ key='edit' trigger={t('edit')} title={t('editNode')} loading={loading} initialValues={row} onSubmit={async (values) => { setLoading(true); try { await updateNode({ ...row, ...values } as API.UpdateNodeRequest); toast.success(t('updateSuccess')); ref.current?.refresh(); setLoading(false); return true; } catch (error) { setLoading(false); return false; } }} />, , {t('delete')}} title={t('confirmDelete')} description={t('deleteWarning')} onConfirm={async () => { await deleteNode({ id: row.id, }); toast.success(t('deleteSuccess')); ref.current?.refresh(); }} cancelText={t('cancel')} confirmText={t('confirm')} />, ], batchRender(rows) { return [ {t('delete')}} title={t('group.confirmDelete')} description={t('group.deleteWarning')} onConfirm={async () => { await batchDeleteNode({ ids: rows.map((item) => item.id), }); toast.success(t('group.deleteSuccess')); ref.current?.refresh(); }} cancelText={t('group.cancel')} confirmText={t('group.confirm')} />, ]; }, }} onSort={async (source, target, items) => { const sourceIndex = items.findIndex((item) => String(item.id) === source); const targetIndex = items.findIndex((item) => String(item.id) === target); const originalSortMap = new Map(items.map((item) => [item.id, item.sort || item.id])); const [movedItem] = items.splice(sourceIndex, 1); items.splice(targetIndex, 0, movedItem!); const updatedItems = items.map((item, index) => { const originalSort = originalSortMap.get(item.id); const newSort = originalSort !== undefined ? originalSort : item.sort; return { ...item, sort: newSort }; }); const changedItems = updatedItems.filter( (item) => originalSortMap.get(item.id) !== item.sort, ); if (changedItems.length > 0) { nodeSort({ sort: changedItems.map((item) => ({ id: item.id, sort: item.sort })), }); } return updatedItems; }} /> ); }