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

View File

@ -26,7 +26,7 @@ import { Combobox } from '@workspace/ui/custom-components/combobox';
import { EnhancedInput } from '@workspace/ui/custom-components/enhanced-input';
import TagInput from '@workspace/ui/custom-components/tag-input';
import { useTranslations } from 'next-intl';
import { useEffect, useMemo } from 'react';
import { useEffect, useMemo, useState } from 'react';
import { useForm } from 'react-hook-form';
import { toast } from 'sonner';
import { z } from 'zod';
@ -77,6 +77,7 @@ export default function NodeForm(props: {
const { trigger, title, loading, initialValues, onSubmit } = props;
const t = useTranslations('nodes');
const Scheme = useMemo(() => buildSchema(t), [t]);
const [open, setOpen] = useState(false);
const form = useForm<NodeFormValues>({
resolver: zodResolver(Scheme),
@ -159,12 +160,15 @@ export default function NodeForm(props: {
async function submit(values: NodeFormValues) {
const ok = await onSubmit(values);
if (ok) form.reset();
if (ok) {
form.reset();
setOpen(false);
}
return ok;
}
return (
<Sheet>
<Sheet open={open} onOpenChange={setOpen}>
<SheetTrigger asChild>
<Button onClick={() => form.reset()}>{trigger}</Button>
</SheetTrigger>
@ -295,7 +299,7 @@ export default function NodeForm(props: {
</ScrollArea>
<SheetFooter className='flex-row justify-end gap-2 pt-3'>
<Button variant='outline' disabled={loading}>
<Button variant='outline' disabled={loading} onClick={() => setOpen(false)}>
{t('cancel')}
</Button>
<Button

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>
);
},

View File

@ -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' });

View File

@ -56,6 +56,10 @@
"id": "ID",
"ipAddresses": "IP adresy",
"memory": "Paměť",
"migrate": "Migrace dat",
"migrateFailed": "Migrace dat se nezdařila",
"migrated": "Data byla úspěšně migrována",
"migrating": "Probíhá migrace...",
"name": "Název",
"noData": "Žádná data",
"notAvailable": "Není k dispozici",

View File

@ -56,6 +56,10 @@
"id": "ID",
"ipAddresses": "IP-Adressen",
"memory": "Speicher",
"migrate": "Daten migrieren",
"migrateFailed": "Datenmigration fehlgeschlagen",
"migrated": "Daten erfolgreich migriert",
"migrating": "Wird migriert...",
"name": "Name",
"noData": "Keine Daten",
"notAvailable": "Nicht verfügbar",

View File

@ -1,5 +1,4 @@
{
"address": "Address",
"address": "Address",
"address_placeholder": "Server address",
"cancel": "Cancel",
@ -57,6 +56,10 @@
"id": "ID",
"ipAddresses": "IP addresses",
"memory": "Memory",
"migrate": "Migrate Data",
"migrateFailed": "Data migration failed",
"migrated": "Data migrated successfully",
"migrating": "Migrating...",
"name": "Name",
"noData": "No data",
"notAvailable": "N/A",

View File

@ -56,6 +56,10 @@
"id": "ID",
"ipAddresses": "Direcciones IP",
"memory": "Memoria",
"migrate": "Migrar datos",
"migrateFailed": "La migración de datos falló",
"migrated": "Datos migrados con éxito",
"migrating": "Migrando...",
"name": "Nombre",
"noData": "Sin datos",
"notAvailable": "N/A",

View File

@ -56,6 +56,10 @@
"id": "ID",
"ipAddresses": "Direcciones IP",
"memory": "Memoria",
"migrate": "Migrar datos",
"migrateFailed": "La migración de datos falló",
"migrated": "Datos migrados con éxito",
"migrating": "Migrando...",
"name": "Nombre",
"noData": "Sin datos",
"notAvailable": "N/A",

View File

@ -56,6 +56,10 @@
"id": "شناسه",
"ipAddresses": "آدرس‌های IP",
"memory": "حافظه",
"migrate": "انتقال داده",
"migrateFailed": "انتقال داده ناموفق بود",
"migrated": "داده با موفقیت منتقل شد",
"migrating": "در حال انتقال...",
"name": "نام",
"noData": "هیچ داده‌ای",
"notAvailable": "غیرقابل دسترسی",

View File

@ -56,6 +56,10 @@
"id": "ID",
"ipAddresses": "IP-osoitteet",
"memory": "Muisti",
"migrate": "Siirrä tiedot",
"migrateFailed": "Tietojen siirto epäonnistui",
"migrated": "Tiedot siirretty onnistuneesti",
"migrating": "Siirretään...",
"name": "Nimi",
"noData": "Ei tietoja",
"notAvailable": "Ei saatavilla",

View File

@ -56,6 +56,10 @@
"id": "ID",
"ipAddresses": "Adresses IP",
"memory": "Mémoire",
"migrate": "Migrer les données",
"migrateFailed": "Échec de la migration des données",
"migrated": "Données migrées avec succès",
"migrating": "Migration en cours...",
"name": "Nom",
"noData": "Aucune donnée",
"notAvailable": "N/A",

View File

@ -56,6 +56,10 @@
"id": "आईडी",
"ipAddresses": "आईपी पते",
"memory": "मेमोरी",
"migrate": "डेटा माइग्रेट करें",
"migrateFailed": "डेटा माइग्रेशन विफल",
"migrated": "डेटा सफलतापूर्वक माइग्रेट किया गया",
"migrating": "माइग्रेट किया जा रहा है...",
"name": "नाम",
"noData": "कोई डेटा नहीं",
"notAvailable": "उपलब्ध नहीं",

View File

@ -56,6 +56,10 @@
"id": "ID",
"ipAddresses": "IP címek",
"memory": "Memória",
"migrate": "Adatok migrálása",
"migrateFailed": "Az adatok migrálása sikertelen",
"migrated": "Az adatok sikeresen migrálva",
"migrating": "Migrálás...",
"name": "Név",
"noData": "Nincs adat",
"notAvailable": "N/A",

View File

@ -56,6 +56,10 @@
"id": "ID",
"ipAddresses": "IPアドレス",
"memory": "メモリ",
"migrate": "データを移行する",
"migrateFailed": "データの移行に失敗しました",
"migrated": "データが正常に移行されました",
"migrating": "移行中...",
"name": "名前",
"noData": "データなし",
"notAvailable": "利用不可",

View File

@ -56,6 +56,10 @@
"id": "ID",
"ipAddresses": "IP 주소",
"memory": "메모리",
"migrate": "데이터 마이그레이션",
"migrateFailed": "데이터 마이그레이션 실패",
"migrated": "데이터가 성공적으로 마이그레이션되었습니다",
"migrating": "마이그레이션 중...",
"name": "이름",
"noData": "데이터 없음",
"notAvailable": "사용 불가",

View File

@ -56,6 +56,10 @@
"id": "ID",
"ipAddresses": "IP-adresser",
"memory": "Minne",
"migrate": "Migrer data",
"migrateFailed": "Datamigrering mislyktes",
"migrated": "Data migrert med suksess",
"migrating": "Migrerer...",
"name": "Navn",
"noData": "Ingen data",
"notAvailable": "Ikke tilgjengelig",

View File

@ -56,6 +56,10 @@
"id": "ID",
"ipAddresses": "Adresy IP",
"memory": "Pamięć",
"migrate": "Migracja danych",
"migrateFailed": "Migracja danych nie powiodła się",
"migrated": "Dane zostały pomyślnie zmigrowane",
"migrating": "Migracja...",
"name": "Nazwa",
"noData": "Brak danych",
"notAvailable": "N/D",

View File

@ -56,6 +56,10 @@
"id": "ID",
"ipAddresses": "Endereços IP",
"memory": "Memória",
"migrate": "Migrar Dados",
"migrateFailed": "A migração de dados falhou",
"migrated": "Dados migrados com sucesso",
"migrating": "Migrando...",
"name": "Nome",
"noData": "Sem dados",
"notAvailable": "N/D",

View File

@ -56,6 +56,10 @@
"id": "ID",
"ipAddresses": "Adrese IP",
"memory": "Memorie",
"migrate": "Migrați datele",
"migrateFailed": "Migrarea datelor a eșuat",
"migrated": "Datele au fost migrate cu succes",
"migrating": "Se migrează...",
"name": "Nume",
"noData": "Fără date",
"notAvailable": "N/A",

View File

@ -56,6 +56,10 @@
"id": "ID",
"ipAddresses": "IP-адреса",
"memory": "Память",
"migrate": "Перенести данные",
"migrateFailed": "Ошибка при переносе данных",
"migrated": "Данные успешно перенесены",
"migrating": "Перенос данных...",
"name": "Имя",
"noData": "Нет данных",
"notAvailable": "Недоступно",

View File

@ -56,6 +56,10 @@
"id": "ID",
"ipAddresses": "ที่อยู่ IP",
"memory": "หน่วยความจำ",
"migrate": "ย้ายข้อมูล",
"migrateFailed": "การย้ายข้อมูลล้มเหลว",
"migrated": "ย้ายข้อมูลสำเร็จ",
"migrating": "กำลังย้ายข้อมูล...",
"name": "ชื่อ",
"noData": "ไม่มีข้อมูล",
"notAvailable": "ไม่สามารถใช้ได้",

View File

@ -56,6 +56,10 @@
"id": "ID",
"ipAddresses": "IP adresleri",
"memory": "Bellek",
"migrate": "Veri Taşı",
"migrateFailed": "Veri taşıma işlemi başarısız oldu",
"migrated": "Veri başarıyla taşındı",
"migrating": "Taşınıyor...",
"name": "İsim",
"noData": "Veri yok",
"notAvailable": "Mevcut değil",

View File

@ -56,6 +56,10 @@
"id": "ID",
"ipAddresses": "IP адреси",
"memory": "Пам'ять",
"migrate": "Міграція даних",
"migrateFailed": "Міграція даних не вдалася",
"migrated": "Дані успішно мігрували",
"migrating": "Міграція...",
"name": "Ім'я",
"noData": "Немає даних",
"notAvailable": "Н/Д",

View File

@ -56,6 +56,10 @@
"id": "ID",
"ipAddresses": "Địa chỉ IP",
"memory": "Bộ nhớ",
"migrate": "Di chuyển dữ liệu",
"migrateFailed": "Di chuyển dữ liệu thất bại",
"migrated": "Dữ liệu đã được di chuyển thành công",
"migrating": "Đang di chuyển...",
"name": "Tên",
"noData": "Không có dữ liệu",
"notAvailable": "Không khả dụng",

View File

@ -1,5 +1,4 @@
{
"address": "地址",
"address": "地址",
"address_placeholder": "服务器地址",
"cancel": "取消",
@ -57,6 +56,10 @@
"id": "编号",
"ipAddresses": "IP 地址",
"memory": "内存",
"migrate": "迁移数据",
"migrateFailed": "数据迁移失败",
"migrated": "数据迁移成功",
"migrating": "迁移中...",
"name": "名称",
"noData": "暂无数据",
"notAvailable": "—",

View File

@ -56,6 +56,10 @@
"id": "ID",
"ipAddresses": "IP 地址",
"memory": "內存",
"migrate": "遷移數據",
"migrateFailed": "數據遷移失敗",
"migrated": "數據已成功遷移",
"migrating": "正在遷移...",
"name": "名稱",
"noData": "無數據",
"notAvailable": "不可用",

View File

@ -1741,6 +1741,7 @@ declare namespace API {
disk: number;
protocol: string;
online: ServerOnlineUser[];
status: string;
};
type ServerTotalDataResponse = {