"use client"; import { zodResolver } from "@hookform/resolvers/zod"; import { Accordion, AccordionContent, AccordionItem, AccordionTrigger, } from "@workspace/ui/components/accordion"; import { Badge } from "@workspace/ui/components/badge"; import { Button } from "@workspace/ui/components/button"; import { Checkbox } from "@workspace/ui/components/checkbox"; import { Form } from "@workspace/ui/components/form"; import { Label } from "@workspace/ui/components/label"; import { ScrollArea } from "@workspace/ui/components/scroll-area"; import { Sheet, SheetContent, SheetFooter, SheetHeader, SheetTitle, SheetTrigger, } from "@workspace/ui/components/sheet"; import { Switch } from "@workspace/ui/components/switch"; import { EnhancedInput } from "@workspace/ui/composed/enhanced-input"; import { cn } from "@workspace/ui/lib/utils"; import { updateServer } from "@workspace/ui/services/admin/server"; import { useState } from "react"; import { useForm, useWatch } from "react-hook-form"; import { useTranslation } from "react-i18next"; import { toast } from "sonner"; import { formSchema, getProtocolDefaultConfig, protocols as PROTOCOLS, useProtocolFields, } from "./form-schema"; import { renderGroupCard } from "./server-form"; type SimpleFieldKey = "name" | "country" | "city" | "address"; type FieldKey = SimpleFieldKey | "protocols"; interface SimplePatch { name: string; country: string; city: string; address: string; } export default function ServerBatchSheet({ rows, onSuccess, }: { rows: API.Server[]; onSuccess: () => void; }) { const { t } = useTranslation("servers"); const [open, setOpen] = useState(false); const [loading, setLoading] = useState(false); const [accordionValue, setAccordionValue] = useState(); const [enabledFields, setEnabledFields] = useState>(new Set()); const [patch, setPatch] = useState({ name: "", country: "", city: "", address: "", }); const PROTOCOL_FIELDS = useProtocolFields(); const protocolForm = useForm({ resolver: zodResolver(formSchema), defaultValues: { name: "", address: "", country: "", city: "", protocols: PROTOCOLS.map((type) => getProtocolDefaultConfig(type)), }, }); const protocolsValues = useWatch({ control: protocolForm.control, name: "protocols", }); function toggleField(key: FieldKey) { setEnabledFields((prev) => { const next = new Set(prev); if (next.has(key)) next.delete(key); else next.add(key); return next; }); } function isEnabled(key: FieldKey) { return enabledFields.has(key); } async function handleSubmit() { if (rows.length === 0) return; if (enabledFields.size === 0) { toast.warning( t( "batch_no_fields_selected", "Please select at least one field to update" ) ); return; } // Validate enabled simple fields if (enabledFields.has("name") && !patch.name.trim()) { toast.warning(t("batch_name_required", "Name cannot be empty")); return; } if (enabledFields.has("address") && !patch.address.trim()) { toast.warning(t("batch_address_required", "Address cannot be empty")); return; } const activePatch: Record = {}; for (const key of enabledFields) { if (key === "protocols") { const formValues = protocolForm.getValues(); const filteredProtocols = (formValues.protocols || []).filter( (p: any) => { const port = Number(p?.port); return p && Number.isFinite(port) && port > 0 && port <= 65_535; } ); activePatch.protocols = filteredProtocols; } else { activePatch[key] = patch[key]; } } setLoading(true); try { const results = await Promise.allSettled( rows.map((row) => { const body: API.UpdateServerRequest = { id: row.id, name: row.name, country: row.country as string | undefined, city: row.city as string | undefined, address: row.address, sort: row.sort, protocols: row.protocols, ...activePatch, }; return updateServer(body); }) ); const succeeded = results.filter((r) => r.status === "fulfilled").length; const failed = results.filter((r) => r.status === "rejected").length; if (failed === 0) { toast.success( t("batch_updated", "Updated {{count}} servers", { count: succeeded }) ); setOpen(false); setEnabledFields(new Set()); onSuccess(); } else if (succeeded > 0) { toast.warning( t("batch_partial", "{{succeeded}} updated, {{failed}} failed", { succeeded, failed, }) ); onSuccess(); } else { toast.error(t("batch_update_failed", "Batch update failed")); } } finally { setLoading(false); } } function handleOpen() { setEnabledFields(new Set()); setPatch({ name: "", country: "", city: "", address: "" }); protocolForm.reset({ name: "", address: "", country: "", city: "", protocols: PROTOCOLS.map((type) => getProtocolDefaultConfig(type)), }); setAccordionValue(undefined); setOpen(true); } const simpleFields: Array<{ key: SimpleFieldKey; label: string; placeholder?: string; }> = [ { key: "name", label: t("name", "Name") }, { key: "country", label: t("country", "Country") }, { key: "city", label: t("city", "City") }, { key: "address", label: t("address", "Address"), placeholder: t("address_placeholder", "Server address"), }, ]; return ( {t("batch_update_title", "Batch Update ({{count}} servers)", { count: rows.length, })}

{t( "batch_update_desc", "Check the fields you want to overwrite. Unchecked fields will keep their original values." )}

{/* Simple text fields */} {simpleFields.map(({ key, label, placeholder }) => (
toggleField(key)} />
setPatch((p) => ({ ...p, [key]: String(v ?? "") })) } placeholder={placeholder} value={patch[key]} />
))} {/* Protocol Configurations */}
toggleField("protocols")} />

{t( "protocol_configurations_desc", "Enable and configure the required protocol types" )}

{PROTOCOLS.map((type) => { const i = Math.max(0, PROTOCOLS.indexOf(type)); const current = (protocolsValues?.[i] || {}) as Record< string, any >; const isProtocolEnabled = current?.enable; const fields = PROTOCOL_FIELDS[type] || []; return (
{type} {current.transport && ( {current.transport.toUpperCase()} )} {current.security && current.security !== "none" && ( {current.security.toUpperCase()} )} {current.port && ( {current.port} )}
{isProtocolEnabled ? t("enabled", "Enabled") : t("disabled", "Disabled")}
{ protocolForm.setValue( `protocols.${i}.enable` as any, checked ); }} onClick={(e) => e.stopPropagation()} />
{renderGroupCard( t("basic", "Basic Configuration"), fields, "basic", protocolForm.control, protocolForm, i, current )} {renderGroupCard( t("obfs", "Obfuscation"), fields, "obfs", protocolForm.control, protocolForm, i, current )} {renderGroupCard( t("transport", "Transport"), fields, "transport", protocolForm.control, protocolForm, i, current )} {renderGroupCard( t("security", "Security"), fields, "security", protocolForm.control, protocolForm, i, current )} {renderGroupCard( t("reality", "Reality"), fields, "reality", protocolForm.control, protocolForm, i, current )} {renderGroupCard( t("encryption", "Encryption"), fields, "encryption", protocolForm.control, protocolForm, i, current )}
); })}
); }