feat: Implement data migration functionality and update localization files

This commit is contained in:
web
2025-09-03 01:56:21 -07:00
parent 59faeab34a
commit 6d81bfdaeb
27 changed files with 161 additions and 32 deletions
+59 -24
View File
@@ -5,9 +5,12 @@ import {
createServer,
deleteServer,
filterServerList,
hasMigrateSeverNode,
migrateServerNode,
resetSortWithServer,
updateServer,
} 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 { Card, CardContent } from '@workspace/ui/components/card';
@@ -64,14 +67,40 @@ function RegionIpCell({
);
}
// OnlineUsersCell is now a standalone component
export default function ServersPage() {
const t = useTranslations('servers');
const [loading, setLoading] = useState(false);
const [migrating, setMigrating] = useState(false);
const ref = useRef<ProTableActions>(null);
const { data: hasMigrate, refetch: refetchHasMigrate } = useQuery({
queryKey: ['hasMigrateSeverNode'],
queryFn: async () => {
const { data } = await hasMigrateSeverNode();
return data.data?.has_migrate;
},
});
const handleMigrate = async () => {
setMigrating(true);
try {
const { data } = await migrateServerNode();
const fail = data.data?.fail || 0;
if (fail > 0) {
toast.error(data.data?.message);
} else {
toast.success(t('migrated'));
}
refetchHasMigrate();
ref.current?.refresh();
} catch (error) {
toast.error(t('migrateFailed'));
} finally {
setMigrating(false);
}
};
return (
<div className='space-y-4'>
<Card>
@@ -84,24 +113,31 @@ export default function ServersPage() {
header={{
title: t('pageTitle'),
toolbar: (
<ServerForm
trigger={t('create')}
title={t('drawerCreateTitle')}
loading={loading}
onSubmit={async (values) => {
setLoading(true);
try {
await createServer(values as unknown as API.CreateServerRequest);
toast.success(t('created'));
ref.current?.refresh();
setLoading(false);
return true;
} catch (e) {
setLoading(false);
return false;
}
}}
/>
<div className='flex gap-2'>
{!hasMigrate && (
<Button variant='outline' onClick={handleMigrate} disabled={migrating}>
{migrating ? t('migrating') : t('migrate')}
</Button>
)}
<ServerForm
trigger={t('create')}
title={t('drawerCreateTitle')}
loading={loading}
onSubmit={async (values) => {
setLoading(true);
try {
await createServer(values as unknown as API.CreateServerRequest);
toast.success(t('created'));
ref.current?.refresh();
setLoading(false);
return true;
} catch (e) {
setLoading(false);
return false;
}
}}
/>
</div>
),
}}
columns={[
@@ -156,17 +192,16 @@ export default function ServersPage() {
id: 'status',
header: t('status'),
cell: ({ row }) => {
const s = (row.original.status ?? {}) as API.ServerStatus;
const on = !!(Array.isArray(s.online) && s.online.length > 0);
const offline = row.original.status.status === 'offline';
return (
<div className='flex items-center gap-2'>
<span
className={cn(
'inline-block h-2.5 w-2.5 rounded-full',
on ? 'bg-emerald-500' : 'bg-zinc-400',
offline ? 'bg-zinc-400' : 'bg-emerald-500',
)}
/>
<span className='text-sm'>{on ? t('online') : t('offline')}</span>
<span className='text-sm'>{offline ? t('offline') : t('online')}</span>
</div>
);
},
@@ -33,7 +33,7 @@ import { EnhancedInput } from '@workspace/ui/custom-components/enhanced-input';
import { Icon } from '@workspace/ui/custom-components/icon';
import { useTranslations } from 'next-intl';
import { useEffect, useMemo, useState } from 'react';
import { useFieldArray, useForm, useWatch } from 'react-hook-form';
import { useForm, useWatch } from 'react-hook-form';
import { toast } from 'sonner';
import {
FINGERPRINTS,
@@ -99,7 +99,6 @@ export default function ServerForm<T extends { [x: string]: any }>({
const form = useForm<any>({ resolver: zodResolver(formSchema), defaultValues });
const { control } = form;
const { fields, append, remove } = useFieldArray({ control, name: 'protocols' });
const protocolsValues = useWatch({ control, name: 'protocols' });