From 2b452d817b93a24e0d6bdccf1ccbb4541f3910ec Mon Sep 17 00:00:00 2001 From: ste87 Date: Sat, 14 Mar 2026 16:08:15 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=20Speed/CreateServerModal.ts?= =?UTF-8?q?x?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Speed/CreateServerModal.tsx | 294 ++++++++++++++++++++++++++++++++++++ 1 file changed, 294 insertions(+) create mode 100644 Speed/CreateServerModal.tsx diff --git a/Speed/CreateServerModal.tsx b/Speed/CreateServerModal.tsx new file mode 100644 index 0000000..8440d63 --- /dev/null +++ b/Speed/CreateServerModal.tsx @@ -0,0 +1,294 @@ +import { useFlashKey } from '@/util/useFlash' +import usePagination from '@/util/usePagination' +import { hostname, password, usKeyboardCharacters } from '@/util/validation' +import { zodResolver } from '@hookform/resolvers/zod' +import { FormProvider, useForm } from 'react-hook-form' +import { useTranslation } from 'react-i18next' +import { z } from 'zod' + +import createServer from '@/api/admin/servers/createServer' +import { ServerResponse } from '@/api/admin/servers/getServers' +import useServersSWR from '@/api/admin/servers/useServersSWR' + +import FlashMessageRender from '@/components/elements/FlashMessageRenderer' +import Modal from '@/components/elements/Modal' +import CheckboxForm from '@/components/elements/forms/CheckboxForm' +import TextInputForm from '@/components/elements/forms/TextInputForm' + +import AddressesMultiSelectForm from '@/components/admin/servers/AddressesMultiSelectForm' +import NodesSelectForm from '@/components/admin/servers/NodesSelectForm' +import TemplatesSelectForm from '@/components/admin/servers/TemplatesSelectForm' +import UsersSelectForm from '@/components/admin/servers/UsersSelectForm' + + +interface Props { + nodeId?: number + userId?: number + open: boolean + onClose: () => void +} + +const CreateServerModal = ({ nodeId, userId, open, onClose }: Props) => { + const [page] = usePagination() + const { mutate } = useServersSWR({ + nodeId, + userId, + page, + query: '', + include: ['node', 'user'], + }) + const { clearFlashes, clearAndAddHttpError } = useFlashKey( + 'admin.servers.create' + ) + const { t } = useTranslation('admin.servers.index') + const { t: tStrings } = useTranslation('strings') + + const schemaWithCreateVm = z.object({ + name: z.string().max(40).nonempty(), + nodeId: z.preprocess(Number, z.number()), + userId: z.preprocess(Number, z.number()), + vmid: z.union([ + z.preprocess(Number, z.number().int().min(100).max(999999999)), + z.literal(''), + ]), + hostname: hostname().max(191).nonempty(), + addressIds: z.array(z.preprocess(Number, z.number())), + cpu: z.preprocess(Number, z.number().min(1)), + memory: z.preprocess(Number, z.number().min(16)), + disk: z.preprocess(Number, z.number().min(1)), + snapshotLimit: z.union([ + z.literal(''), + z.preprocess(Number, z.number().min(0)), + ]), + backupLimit: z.union([ + z.literal(''), + z.preprocess(Number, z.number().min(0)), + ]), + bandwidthLimit: z.union([ + z.literal(''), + z.preprocess(Number, z.number().min(0)), + ]), + accountPassword: password(usKeyboardCharacters()).nonempty(), + shouldCreateServer: z.literal(true), + startOnCompletion: z.boolean(), + templateUuid: z.string().nonempty(), + }) + + const schemaWithoutCreatingVm = z.object({ + name: z.string().max(40).nonempty(), + nodeId: z.preprocess(Number, z.number()), + userId: z.preprocess(Number, z.number()), + vmid: z.union([ + z.preprocess(Number, z.number().min(100).max(999999999)), + z.literal(''), + ]), + hostname: hostname().max(191).nonempty(), + addressIds: z.array(z.preprocess(Number, z.number())), + cpu: z.preprocess(Number, z.number().min(1)), + memory: z.preprocess(Number, z.number().min(16)), + disk: z.preprocess(Number, z.number().min(1)), + snapshotLimit: z.union([ + z.literal(''), + z.preprocess(Number, z.number().min(0)), + ]), + backupLimit: z.union([ + z.literal(''), + z.preprocess(Number, z.number().min(0)), + ]), + bandwidthLimit: z.union([ + z.literal(''), + z.preprocess(Number, z.number().min(0)), + ]), + accountPassword: password(usKeyboardCharacters()).optional(), + shouldCreateServer: z.literal(false), + startOnCompletion: z.boolean(), + templateUuid: z.string(), + }) + + const schema = z.discriminatedUnion('shouldCreateServer', [ + schemaWithCreateVm, + schemaWithoutCreatingVm, + ]) + + const form = useForm({ + resolver: zodResolver(schema), + defaultValues: { + name: '', + nodeId: nodeId?.toString() ?? '', + userId: userId?.toString() ?? '', + vmid: '', + hostname: '', + addressIds: [], + cpu: '', + memory: '', + disk: '', + snapshotLimit: '0', + backupLimit: '', + bandwidthLimit: '', + accountPassword: '', + shouldCreateServer: true, + startOnCompletion: false, + templateUuid: '', + }, + }) + + const watchShouldCreateServer = form.watch('shouldCreateServer') + const watchNodeId = form.watch('nodeId') + + const submit = async (_data: any) => { + const { + vmid, + cpu, + memory, + disk, + snapshotLimit, + backupLimit, + bandwidthLimit, + addressIds, + accountPassword, + ...data + } = _data as z.infer + clearFlashes() + try { + const server = await createServer({ + ...data, + vmid: vmid !== '' ? vmid : null, + limits: { + cpu, + memory: memory * 1048576, + disk: disk * 1048576, + snapshots: snapshotLimit !== '' ? snapshotLimit : null, + backups: backupLimit !== '' ? backupLimit : null, + bandwidth: + bandwidthLimit !== '' ? bandwidthLimit * 1048576 : null, + addressIds, + }, + accountPassword: accountPassword ? accountPassword : null, + }) + + mutate(data => { + if (!data) return data + + return { + ...data, + items: [server, ...data.items], + } as ServerResponse + }, false) + + handleClose() + } catch (e) { + clearAndAddHttpError(e as Error) + } + } + + const handleClose = () => { + clearFlashes() + form.reset() + onClose() + } + + return ( + + + {t('create_modal.title')} + + + +
+ + + + {nodeId ? null : } + {userId ? null : } + + + +
+ + +
+ +
+ + +
+ + + + +
+ + + {tStrings('cancel')} + + + {tStrings('create')} + + +
+
+
+ ) +} + +export default CreateServerModal