tftsr-devops_investigation/src/components/Proxmox/BackupJobList.tsx
Shaun Arman 577512562b 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 15:08:56 -05:00

143 lines
5.2 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import React from 'react';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/index';
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/index';
import { Button } from '@/components/ui/index';
import { MoreHorizontal, Play, Trash2 } from 'lucide-react';
interface BackupJobInfo {
id: string;
name: string;
node: string;
schedule: string;
status: 'idle' | 'running' | 'success' | 'failed';
lastRun?: string;
nextRun?: string;
size?: number;
count?: number;
enabled: boolean;
storage?: string;
vmid?: string | number;
mode?: string;
comment?: string;
}
interface BackupJobListProps {
jobs: BackupJobInfo[];
onRefresh?: () => void;
isLoading?: boolean;
onTrigger?: (job: BackupJobInfo) => void;
onEdit?: (job: BackupJobInfo) => void;
onDelete?: (job: BackupJobInfo) => void;
onEnable?: (job: BackupJobInfo) => void;
onDisable?: (job: BackupJobInfo) => void;
}
export function BackupJobList({
jobs,
onRefresh,
isLoading,
onTrigger,
onEdit,
onDelete,
onEnable,
onDisable,
}: BackupJobListProps) {
return (
<Card>
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle>Backup Jobs</CardTitle>
<div className="flex space-x-2">
<Button variant="outline" size="sm" onClick={onRefresh} disabled={isLoading}>
Refresh
</Button>
<Button size="sm">
<span className="mr-2 h-4 w-4">+</span>
New Job
</Button>
</div>
</CardHeader>
<CardContent>
<div className="overflow-auto">
<Table>
<TableHeader>
<TableRow>
<TableHead>ID</TableHead>
<TableHead>Storage</TableHead>
<TableHead>VMs</TableHead>
<TableHead>Node</TableHead>
<TableHead>Schedule</TableHead>
<TableHead>Enabled</TableHead>
<TableHead>Next Run</TableHead>
<TableHead>Mode</TableHead>
<TableHead className="text-right">Actions</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{jobs.map((job) => (
<TableRow key={job.id}>
<TableCell className="font-medium font-mono text-xs">{job.name}</TableCell>
<TableCell>{job.storage || '-'}</TableCell>
<TableCell className="text-xs">{job.vmid ? String(job.vmid) : 'all'}</TableCell>
<TableCell>{job.node || 'all'}</TableCell>
<TableCell className="font-mono text-xs">{job.schedule}</TableCell>
<TableCell>
<span className={`inline-flex items-center rounded-full px-2 py-1 text-xs font-medium ${
job.enabled ? 'bg-green-100 text-green-800' : 'bg-gray-100 text-gray-800'
}`}>
{job.enabled ? 'enabled' : 'disabled'}
</span>
</TableCell>
<TableCell className="text-xs">{job.nextRun || '-'}</TableCell>
<TableCell className="text-xs">{job.mode || '-'}</TableCell>
<TableCell className="text-right">
<div className="flex items-center justify-end space-x-2">
<button
className="rounded-md p-1 hover:bg-accent"
onClick={() => onTrigger?.(job)}
title="Trigger Now"
>
<Play className="h-4 w-4" />
</button>
<button
className="rounded-md p-1 hover:bg-accent"
onClick={() => onEdit?.(job)}
title="Edit"
>
<span className="h-4 w-4 text-xs"></span>
</button>
<button
className="rounded-md p-1 hover:bg-accent"
onClick={() => job.enabled ? onDisable?.(job) : onEnable?.(job)}
title={job.enabled ? 'Disable' : 'Enable'}
>
{job.enabled ? (
<span className="h-4 w-4 text-xs"></span>
) : (
<span className="h-4 w-4 text-xs"></span>
)}
</button>
<button
className="rounded-md p-1 hover:bg-red-100 hover:text-red-600"
onClick={() => onDelete?.(job)}
title="Delete"
>
<Trash2 className="h-4 w-4" />
</button>
<button
className="rounded-md p-1 hover:bg-accent"
title="More"
>
<MoreHorizontal className="h-4 w-4" />
</button>
</div>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</div>
</CardContent>
</Card>
);
}