tftsr-devops_investigation/src/pages/Proxmox/VMsPage.tsx

118 lines
3.9 KiB
TypeScript
Raw Normal View History

import React, { useState, useEffect, useCallback } from 'react';
import { Button } from '@/components/ui/index';
import { RefreshCw, Plus } from 'lucide-react';
import { VMList, CreateVmDialog } 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());
const [showCreateDialog, setShowCreateDialog] = useState(false);
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>
<Button size="sm" onClick={() => setShowCreateDialog(true)} disabled={!selectedClusterId}>
<Plus className="mr-2 h-4 w-4" />
Add VM
</Button>
</div>
</div>
<VMList
vms={vms}
fix(proxmox): resolve 7 dashboard and AI chat issues 1. VM Actions: pass clusterId/clusters props from VMsPage to VMList; rename node→node_id in 14 Rust Tauri command handlers to match Tauri 2.x camelCase→snake_case mapping; wire action menu items through handleAction so menu closes on click. 2. Migration: add Target Remote dropdown in MigrationDialog showing available clusters for cross-datacenter migration; targetCluster passed through to migrate_vm invoke. 3. Storage: switch list_proxmox_datastores to cluster/resources?type=storage (single API call, cluster-wide); normalize plugintype→type, disk/maxdisk→used/size, compute available via saturating_sub. 4. Network: replace free-text Interface Type Input with a Select dropdown listing all PVE network interface types. 5. Firewall New Rule: add onNewRule prop to FirewallRuleList, wire button; add full dialog in FirewallPage with action/protocol/ source/dest/port fields that calls add_firewall_rule; rewrite Rust command to accept rule as serde_json::Value instead of flat params (matches frontend invoke signature). 6. Backup: normalize raw PVE cluster/backup fields (id, storage, node, schedule, enabled, next-run timestamp) to BackupJobInfo shape; update BackupJobList columns to show storage, vmid, mode. 7. AI chat: merge all system prompt sections into a single system message (fixes Qwen 3.5 / LiteLLM rejection of multiple system messages); push assistant message with tool_calls before tool result messages to satisfy OpenAI API contract.
2026-06-21 20:08:56 +00:00
clusterId={selectedClusterId}
clusters={clusters}
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;
});
}}
/>
<CreateVmDialog
isOpen={showCreateDialog}
clusterId={selectedClusterId}
onClose={() => setShowCreateDialog(false)}
onCreated={() => loadVms(selectedClusterId)}
/>
</div>
);
}