import React, { useState, useEffect } from 'react'; import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter } from '@/components/ui/index'; import { Button } from '@/components/ui/index'; import { Input } from '@/components/ui/index'; import { Label } from '@/components/ui/index'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/index'; import { Upload } from 'lucide-react'; import { open as openFileDialog } from '@tauri-apps/plugin-dialog'; import { listProxmoxClusters, listProxmoxNodes, listProxmoxStorages, listIsoImages, uploadIsoImage, createProxmoxVm, } from '@/lib/proxmoxClient'; import type { ClusterInfo } from '@/lib/domain'; import { toast } from 'sonner'; interface CreateVmDialogProps { isOpen: boolean; clusterId: string; onClose: () => void; onCreated: () => void; } const OS_TYPES = [ { value: 'l26', label: 'Linux 2.6+' }, { value: 'l24', label: 'Linux 2.4' }, { value: 'win11', label: 'Windows 11' }, { value: 'win10', label: 'Windows 10/2016/2019' }, { value: 'win8', label: 'Windows 8/2012' }, { value: 'win7', label: 'Windows 7/2008' }, { value: 'other', label: 'Other' }, ]; export function CreateVmDialog({ isOpen, clusterId, onClose, onCreated }: CreateVmDialogProps) { const [clusters, setClusters] = useState([]); const [selectedClusterId, setSelectedClusterId] = useState(clusterId); const [nodes, setNodes] = useState([]); const [storages, setStorages] = useState([]); const [isoStorages, setIsoStorages] = useState([]); const [isoImages, setIsoImages] = useState<{ volid: string; name?: string }[]>([]); const [isoStorage, setIsoStorage] = useState(''); const [isSubmitting, setIsSubmitting] = useState(false); const [isUploading, setIsUploading] = useState(false); const [nodeId, setNodeId] = useState(''); const [vmid, setVmid] = useState(100); const [name, setName] = useState(''); const [memory, setMemory] = useState(2048); const [cores, setCores] = useState(2); const [sockets, setSockets] = useState(1); const [osType, setOsType] = useState('l26'); const [storage, setStorage] = useState(''); const [diskSize, setDiskSize] = useState(20); const [netBridge, setNetBridge] = useState('vmbr0'); const [iso, setIso] = useState(''); useEffect(() => { if (!isOpen) return; listProxmoxClusters() .then((cls) => { setClusters(cls); const target = cls.find((c) => c.id === clusterId) ? clusterId : cls[0]?.id ?? clusterId; setSelectedClusterId(target); }) .catch(console.error); }, [isOpen, clusterId]); useEffect(() => { if (!isOpen || !selectedClusterId) return; listProxmoxNodes(selectedClusterId) .then((data) => { const nodeNames = (data as Array<{ node?: string; status?: string }>) .filter((n) => n.status === 'online' || n.node) .map((n) => n.node ?? '') .filter(Boolean); setNodes(nodeNames); setNodeId(nodeNames[0] ?? ''); }) .catch(() => toast.error('Failed to load cluster nodes')); }, [isOpen, selectedClusterId]); useEffect(() => { if (!isOpen || !selectedClusterId || !nodeId) return; listProxmoxStorages(selectedClusterId, nodeId) .then((data) => { const storageIds = data.map((s) => s.storage).filter(Boolean); setStorages(storageIds); setStorage(storageIds[0] ?? 'local-lvm'); const isoCapable = data .filter((s) => !s.content || s.content.includes('iso')) .map((s) => s.storage) .filter(Boolean); setIsoStorages(isoCapable); setIsoStorage(isoCapable[0] ?? ''); }) .catch(() => { setStorages(['local-lvm', 'local']); setStorage('local-lvm'); }); }, [isOpen, selectedClusterId, nodeId]); useEffect(() => { if (!isOpen || !selectedClusterId || !nodeId || !isoStorage) { setIsoImages([]); return; } listIsoImages(selectedClusterId, nodeId, isoStorage) .then((imgs) => { setIsoImages(imgs); }) .catch(() => setIsoImages([])); }, [isOpen, selectedClusterId, nodeId, isoStorage]); const handleUploadIso = async () => { if (!selectedClusterId || !nodeId || !isoStorage) { toast.error('Select a cluster, node, and ISO storage before uploading'); return; } const selected = await openFileDialog({ title: 'Select ISO file', filters: [{ name: 'ISO Images', extensions: ['iso'] }], multiple: false, }); if (!selected) return; const filePath = selected as string; setIsUploading(true); try { await uploadIsoImage(selectedClusterId, nodeId, isoStorage, filePath); toast.success('ISO upload started — refreshing image list'); const imgs = await listIsoImages(selectedClusterId, nodeId, isoStorage); setIsoImages(imgs); } catch (e) { toast.error(`Upload failed: ${e}`); } finally { setIsUploading(false); } }; const handleSubmit = async () => { if (!nodeId) { toast.error('Please select a target node'); return; } if (!name.trim()) { toast.error('VM name is required'); return; } if (vmid < 100 || vmid > 999999999) { toast.error('VMID must be between 100 and 999999999'); return; } setIsSubmitting(true); try { await createProxmoxVm(selectedClusterId, { nodeId, vmid, name: name.trim(), memory, cores, sockets, osType, storage, diskSize, netBridge, iso: iso || undefined, }); toast.success(`VM "${name}" created successfully (VMID: ${vmid})`); onCreated(); handleClose(); } catch (err) { toast.error(`Failed to create VM: ${err}`); } finally { setIsSubmitting(false); } }; const handleClose = () => { setName(''); setVmid(100); setMemory(2048); setCores(2); setSockets(1); setOsType('l26'); setDiskSize(20); setNetBridge('vmbr0'); setIso(''); onClose(); }; const isoLabel = (volid: string, imgName?: string) => { const filename = imgName ?? volid.split('/').pop() ?? volid; return filename; }; return ( Create Virtual Machine
{clusters.length > 1 && (
)}
setVmid(Number(e.target.value))} />
setName(e.target.value)} placeholder="my-vm" />
setMemory(Number(e.target.value))} />
setCores(Number(e.target.value))} />
setSockets(Number(e.target.value))} />
{storages.length > 0 ? ( ) : ( setStorage(e.target.value)} placeholder="local-lvm" /> )}
setDiskSize(Number(e.target.value))} />
setNetBridge(e.target.value)} placeholder="vmbr0" />
{isoStorage && ( )}
{isoStorages.length > 0 && (
)} {isoImages.length > 0 ? ( ) : ( setIso(e.target.value)} placeholder="local:iso/ubuntu-24.04.iso" /> )}

{isoImages.length > 0 ? `${isoImages.length} ISO(s) available` : 'Format: storage:iso/filename'}

); }