Some checks failed
Test / frontend-tests (pull_request) Successful in 1m45s
Test / frontend-typecheck (pull_request) Successful in 1m54s
Test / rust-tests (pull_request) Has been cancelled
Test / rust-fmt-check (pull_request) Has been cancelled
Test / rust-clippy (pull_request) Has been cancelled
PR Review Automation / review (pull_request) Successful in 6m10s
Backend: - Add resume_proxmox_vm, suspend_proxmox_vm, clone_vm, delete_vm Tauri commands - Implement proxmox/vm.rs functions for resume, suspend, clone, delete operations - Register all new commands in lib.rs Frontend: - Fix VMList.tsx: Import confirm from @tauri-apps/plugin-dialog - Fix VMList.tsx: Replace prompt() with window.prompt() for user input - Fix VMList.tsx: Correct paused VM action to resume instead of suspend - Fix VMList.tsx: Implement proper menu positioning with horizontal overflow detection - Fix StorageList.tsx: Add error handling to formatBytes for negative/non-numeric input - Fix VMsPage.tsx: Remove redundant handler stubs, let VMList handle actions All changes pass TypeScript type checking, Rust clippy, and frontend tests (386 tests passing).
104 lines
3.4 KiB
TypeScript
104 lines
3.4 KiB
TypeScript
import React, { useState, useEffect, useCallback } from 'react';
|
|
import { Button } from '@/components/ui/index';
|
|
import { RefreshCw } from 'lucide-react';
|
|
import { VMList } from '@/components/Proxmox';
|
|
import { listProxmoxClusters, listProxmoxVms } from '@/lib/proxmoxClient';
|
|
import type { ClusterInfo } from '@/lib/domain';
|
|
import { toast } from 'sonner';
|
|
|
|
export function ProxmoxVMsPage() {
|
|
const [clusters, setClusters] = useState<ClusterInfo[]>([]);
|
|
const [selectedClusterId, setSelectedClusterId] = useState<string>('');
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
const [vms, setVms] = useState<any[]>([]);
|
|
const [isLoading, setIsLoading] = useState(false);
|
|
const [selectedVMs, setSelectedVMs] = useState<Set<string>>(new Set());
|
|
|
|
useEffect(() => {
|
|
listProxmoxClusters()
|
|
.then((cls) => {
|
|
setClusters(cls);
|
|
if (cls.length > 0) setSelectedClusterId(cls[0].id);
|
|
})
|
|
.catch((err) => {
|
|
console.error('Failed to load clusters:', err);
|
|
toast.error('Failed to load clusters');
|
|
});
|
|
}, []);
|
|
|
|
const loadVms = useCallback(async (clusterId: string) => {
|
|
if (!clusterId) return;
|
|
setIsLoading(true);
|
|
try {
|
|
const data = await listProxmoxVms(clusterId);
|
|
setVms(data);
|
|
} catch (err) {
|
|
console.error('Failed to load VMs:', err);
|
|
toast.error('Failed to load VMs');
|
|
} finally {
|
|
setIsLoading(false);
|
|
}
|
|
}, []);
|
|
|
|
useEffect(() => {
|
|
if (selectedClusterId) loadVms(selectedClusterId);
|
|
}, [selectedClusterId, loadVms]);
|
|
|
|
if (clusters.length === 0 && !isLoading) {
|
|
return (
|
|
<div className="space-y-4">
|
|
<div>
|
|
<h1 className="text-2xl font-bold">Virtual Machines</h1>
|
|
<p className="text-muted-foreground">Manage QEMU/KVM virtual machines</p>
|
|
</div>
|
|
<div className="text-center py-12 text-muted-foreground">
|
|
<p>No Proxmox clusters configured.</p>
|
|
<p className="text-sm mt-1">Add a remote connection first.</p>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<div className="space-y-4">
|
|
<div className="flex items-center justify-between">
|
|
<div>
|
|
<h1 className="text-2xl font-bold">Virtual Machines</h1>
|
|
<p className="text-muted-foreground">Manage QEMU/KVM virtual machines</p>
|
|
</div>
|
|
<div className="flex items-center space-x-2">
|
|
{clusters.length > 1 && (
|
|
<select
|
|
className="rounded-md border px-3 py-1.5 text-sm bg-background"
|
|
value={selectedClusterId}
|
|
onChange={(e) => setSelectedClusterId(e.target.value)}
|
|
>
|
|
{clusters.map((c) => (
|
|
<option key={c.id} value={c.id}>{c.name}</option>
|
|
))}
|
|
</select>
|
|
)}
|
|
<Button variant="outline" size="sm" onClick={() => loadVms(selectedClusterId)}>
|
|
<RefreshCw className="mr-2 h-4 w-4" />
|
|
Refresh
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
|
|
<VMList
|
|
vms={vms}
|
|
onRefresh={() => loadVms(selectedClusterId)}
|
|
selectedVMs={selectedVMs}
|
|
onToggleSelect={(vm) => {
|
|
setSelectedVMs((prev) => {
|
|
const next = new Set(prev);
|
|
const id = String(vm.vmid);
|
|
if (next.has(id)) next.delete(id); else next.add(id);
|
|
return next;
|
|
});
|
|
}}
|
|
/>
|
|
</div>
|
|
);
|
|
}
|