mirror of
https://github.com/MacRimi/ProxMenux.git
synced 2026-02-19 08:56:23 +00:00
Update modal lxc
This commit is contained in:
@@ -1239,14 +1239,15 @@ const handleDownloadLogs = async (vmid: number, vmName: string) => {
|
|||||||
|
|
||||||
<div className="flex-1 overflow-hidden px-6 py-4">
|
<div className="flex-1 overflow-hidden px-6 py-4">
|
||||||
{/* Mobile carousel container */}
|
{/* Mobile carousel container */}
|
||||||
<div className="sm:hidden relative overflow-hidden">
|
<div className="sm:hidden flex flex-col h-full">
|
||||||
<div
|
<div className="flex-1 relative overflow-hidden">
|
||||||
className="flex transition-transform duration-300 ease-in-out w-[200%]"
|
<div
|
||||||
style={{ transform: `translateX(-${modalPage * 50}%)` }}
|
className="flex transition-transform duration-300 ease-in-out h-full"
|
||||||
>
|
style={{ transform: `translateX(-${modalPage * 100}%)` }}
|
||||||
{/* Page 0: Main content */}
|
>
|
||||||
<div className="w-1/2 flex-shrink-0 overflow-y-auto pr-2" style={{ maxHeight: 'calc(100vh - 280px)' }}>
|
{/* Page 0: Main content */}
|
||||||
<div className="space-y-6 pr-1">
|
<div className="w-full flex-shrink-0 overflow-y-auto" style={{ maxHeight: 'calc(100vh - 320px)' }}>
|
||||||
|
<div className="space-y-6">
|
||||||
{selectedVM && (
|
{selectedVM && (
|
||||||
<>
|
<>
|
||||||
<div key={`metrics-mobile-${selectedVM.vmid}`}>
|
<div key={`metrics-mobile-${selectedVM.vmid}`}>
|
||||||
@@ -1365,77 +1366,78 @@ const handleDownloadLogs = async (vmid: number, vmName: string) => {
|
|||||||
) : null}
|
) : null}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Page 1: Backups */}
|
{/* Page 1: Backups */}
|
||||||
<div className="w-1/2 flex-shrink-0 overflow-y-auto pl-2" style={{ maxHeight: 'calc(100vh - 280px)' }}>
|
<div className="w-full flex-shrink-0 overflow-y-auto" style={{ maxHeight: 'calc(100vh - 320px)' }}>
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<Card className="border border-border bg-card/50">
|
<Card className="border border-border bg-card/50">
|
||||||
<CardContent className="p-4">
|
<CardContent className="p-4">
|
||||||
<h3 className="text-sm font-semibold text-muted-foreground uppercase tracking-wide mb-4">Create Backup</h3>
|
<h3 className="text-sm font-semibold text-muted-foreground uppercase tracking-wide mb-4">Create Backup</h3>
|
||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
<div>
|
<div>
|
||||||
<label className="text-xs text-muted-foreground mb-1 block">Storage</label>
|
<label className="text-xs text-muted-foreground mb-1 block">Storage</label>
|
||||||
<Select value={selectedBackupStorage} onValueChange={setSelectedBackupStorage}>
|
<Select value={selectedBackupStorage} onValueChange={setSelectedBackupStorage}>
|
||||||
<SelectTrigger className="w-full">
|
<SelectTrigger className="w-full">
|
||||||
<SelectValue placeholder="Select storage" />
|
<SelectValue placeholder="Select storage" />
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
{backupStorages.map((storage) => (
|
{backupStorages.map((storage) => (
|
||||||
<SelectItem key={storage.storage} value={storage.storage}>
|
<SelectItem key={storage.storage} value={storage.storage}>
|
||||||
{storage.storage} ({storage.avail_human} free)
|
{storage.storage} ({storage.avail_human} free)
|
||||||
</SelectItem>
|
</SelectItem>
|
||||||
))}
|
))}
|
||||||
</SelectContent>
|
</SelectContent>
|
||||||
</Select>
|
</Select>
|
||||||
|
</div>
|
||||||
|
<Button
|
||||||
|
className="w-full bg-amber-600 hover:bg-amber-700 text-white"
|
||||||
|
onClick={handleCreateBackup}
|
||||||
|
disabled={creatingBackup || !selectedBackupStorage}
|
||||||
|
>
|
||||||
|
{creatingBackup ? (
|
||||||
|
<><Loader2 className="h-4 w-4 mr-2 animate-spin" />Creating...</>
|
||||||
|
) : (
|
||||||
|
<><Archive className="h-4 w-4 mr-2" />Create Backup</>
|
||||||
|
)}
|
||||||
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
<Button
|
</CardContent>
|
||||||
className="w-full bg-amber-600 hover:bg-amber-700 text-white"
|
</Card>
|
||||||
onClick={handleCreateBackup}
|
|
||||||
disabled={creatingBackup || !selectedBackupStorage}
|
|
||||||
>
|
|
||||||
{creatingBackup ? (
|
|
||||||
<><Loader2 className="h-4 w-4 mr-2 animate-spin" />Creating...</>
|
|
||||||
) : (
|
|
||||||
<><Archive className="h-4 w-4 mr-2" />Create Backup</>
|
|
||||||
)}
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
|
|
||||||
<Card className="border border-border bg-card/50">
|
<Card className="border border-border bg-card/50">
|
||||||
<CardContent className="p-4">
|
<CardContent className="p-4">
|
||||||
<h3 className="text-sm font-semibold text-muted-foreground uppercase tracking-wide mb-4">
|
<h3 className="text-sm font-semibold text-muted-foreground uppercase tracking-wide mb-4">
|
||||||
Backups ({vmBackups.length})
|
Backups ({vmBackups.length})
|
||||||
</h3>
|
</h3>
|
||||||
{loadingBackups ? (
|
{loadingBackups ? (
|
||||||
<div className="text-center py-4 text-muted-foreground">
|
<div className="text-center py-4 text-muted-foreground">
|
||||||
<Loader2 className="h-5 w-5 animate-spin mx-auto mb-2" />
|
<Loader2 className="h-5 w-5 animate-spin mx-auto mb-2" />
|
||||||
Loading backups...
|
Loading backups...
|
||||||
</div>
|
</div>
|
||||||
) : vmBackups.length === 0 ? (
|
) : vmBackups.length === 0 ? (
|
||||||
<div className="text-center py-4 text-muted-foreground text-sm">
|
<div className="text-center py-4 text-muted-foreground text-sm">
|
||||||
No backups found
|
No backups found
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
{vmBackups.map((backup, index) => (
|
{vmBackups.map((backup, index) => (
|
||||||
<div key={`backup-${backup.volid}-${index}`} className="p-3 rounded-lg bg-muted/50 border border-border">
|
<div key={`backup-${backup.volid}-${index}`} className="p-3 rounded-lg bg-muted/50 border border-border">
|
||||||
<div className="flex justify-between items-start">
|
<div className="flex justify-between items-start">
|
||||||
<div>
|
<div>
|
||||||
<div className="text-sm font-medium">{backup.date}</div>
|
<div className="text-sm font-medium">{backup.date}</div>
|
||||||
<div className="text-xs text-muted-foreground">{backup.storage}</div>
|
<div className="text-xs text-muted-foreground">{backup.storage}</div>
|
||||||
|
</div>
|
||||||
|
<Badge variant="outline" className="text-xs">{backup.size_human}</Badge>
|
||||||
</div>
|
</div>
|
||||||
<Badge variant="outline" className="text-xs">{backup.size_human}</Badge>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
))}
|
||||||
))}
|
</div>
|
||||||
</div>
|
)}
|
||||||
)}
|
</CardContent>
|
||||||
</CardContent>
|
</Card>
|
||||||
</Card>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -1456,9 +1458,9 @@ const handleDownloadLogs = async (vmid: number, vmName: string) => {
|
|||||||
{/* Desktop layout */}
|
{/* Desktop layout */}
|
||||||
<div className="hidden sm:block overflow-y-auto" style={{ maxHeight: 'calc(100vh - 200px)' }}>
|
<div className="hidden sm:block overflow-y-auto" style={{ maxHeight: 'calc(100vh - 200px)' }}>
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
{selectedVM && (
|
{selectedVM && (
|
||||||
<>
|
<>
|
||||||
<div key={`metrics-${selectedVM.vmid}`}>
|
<div key={`metrics-${selectedVM.vmid}`}>
|
||||||
<Card
|
<Card
|
||||||
className="cursor-pointer rounded-lg border border-border bg-card hover:bg-black/5 dark:hover:bg-white/5 transition-colors group"
|
className="cursor-pointer rounded-lg border border-border bg-card hover:bg-black/5 dark:hover:bg-white/5 transition-colors group"
|
||||||
onClick={handleMetricsClick}
|
onClick={handleMetricsClick}
|
||||||
@@ -2159,11 +2161,11 @@ const handleDownloadLogs = async (vmid: number, vmName: string) => {
|
|||||||
</Card>
|
</Card>
|
||||||
</>
|
</>
|
||||||
) : null}
|
) : null}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="border-t border-border bg-background px-6 py-4 mt-auto">
|
<div className="border-t border-border bg-background px-6 py-4 mt-auto">
|
||||||
{/* Terminal button for LXC containers - only when running */}
|
{/* Terminal button for LXC containers - only when running */}
|
||||||
|
|||||||
@@ -5666,6 +5666,10 @@ def api_vm_backups(vmid):
|
|||||||
try:
|
try:
|
||||||
backups = []
|
backups = []
|
||||||
|
|
||||||
|
# Get current node name
|
||||||
|
node_result = subprocess.run(['hostname'], capture_output=True, text=True, timeout=5)
|
||||||
|
node = node_result.stdout.strip() if node_result.returncode == 0 else 'localhost'
|
||||||
|
|
||||||
# Get list of storage locations
|
# Get list of storage locations
|
||||||
result = subprocess.run(['pvesh', 'get', '/storage', '--output-format', 'json'],
|
result = subprocess.run(['pvesh', 'get', '/storage', '--output-format', 'json'],
|
||||||
capture_output=True, text=True, timeout=10)
|
capture_output=True, text=True, timeout=10)
|
||||||
@@ -5681,9 +5685,11 @@ def api_vm_backups(vmid):
|
|||||||
# Only check storages that can contain backups
|
# Only check storages that can contain backups
|
||||||
if 'backup' in content or storage_type == 'pbs':
|
if 'backup' in content or storage_type == 'pbs':
|
||||||
try:
|
try:
|
||||||
|
# Use --vmid filter to get only backups for this VM
|
||||||
content_result = subprocess.run(
|
content_result = subprocess.run(
|
||||||
['pvesh', 'get', f'/nodes/$(hostname)/storage/{storage_id}/content', '--output-format', 'json'],
|
['pvesh', 'get', f'/nodes/{node}/storage/{storage_id}/content',
|
||||||
capture_output=True, text=True, timeout=15, shell=True
|
'--vmid', str(vmid), '--output-format', 'json'],
|
||||||
|
capture_output=True, text=True, timeout=30
|
||||||
)
|
)
|
||||||
|
|
||||||
if content_result.returncode == 0:
|
if content_result.returncode == 0:
|
||||||
@@ -5691,39 +5697,30 @@ def api_vm_backups(vmid):
|
|||||||
|
|
||||||
for item in contents:
|
for item in contents:
|
||||||
if item.get('content') == 'backup':
|
if item.get('content') == 'backup':
|
||||||
volid = item.get('volid', '')
|
# Get backup type from subtype field (PBS) or parse volid (local)
|
||||||
|
backup_type = item.get('subtype', '')
|
||||||
|
if not backup_type:
|
||||||
|
volid = item.get('volid', '')
|
||||||
|
if 'vzdump-qemu-' in volid:
|
||||||
|
backup_type = 'qemu'
|
||||||
|
elif 'vzdump-lxc-' in volid:
|
||||||
|
backup_type = 'lxc'
|
||||||
|
|
||||||
# Check if this backup belongs to the requested vmid
|
size = item.get('size', 0)
|
||||||
backup_vmid = None
|
ctime = item.get('ctime', 0)
|
||||||
backup_type = None
|
notes = item.get('notes', '')
|
||||||
|
|
||||||
if 'vzdump-qemu-' in volid:
|
backups.append({
|
||||||
backup_type = 'qemu'
|
'volid': item.get('volid', ''),
|
||||||
try:
|
'storage': storage_id,
|
||||||
backup_vmid = int(volid.split('vzdump-qemu-')[1].split('-')[0])
|
'type': backup_type,
|
||||||
except:
|
'size': size,
|
||||||
pass
|
'size_human': format_bytes(size),
|
||||||
elif 'vzdump-lxc-' in volid:
|
'timestamp': ctime,
|
||||||
backup_type = 'lxc'
|
'date': datetime.fromtimestamp(ctime).strftime('%Y-%m-%d %H:%M') if ctime else '',
|
||||||
try:
|
'notes': notes
|
||||||
backup_vmid = int(volid.split('vzdump-lxc-')[1].split('-')[0])
|
})
|
||||||
except:
|
except Exception as e:
|
||||||
pass
|
|
||||||
|
|
||||||
if backup_vmid == vmid:
|
|
||||||
size = item.get('size', 0)
|
|
||||||
ctime = item.get('ctime', 0)
|
|
||||||
|
|
||||||
backups.append({
|
|
||||||
'volid': volid,
|
|
||||||
'storage': storage_id,
|
|
||||||
'type': backup_type,
|
|
||||||
'size': size,
|
|
||||||
'size_human': format_bytes(size),
|
|
||||||
'timestamp': ctime,
|
|
||||||
'date': datetime.fromtimestamp(ctime).strftime('%Y-%m-%d %H:%M') if ctime else ''
|
|
||||||
})
|
|
||||||
except:
|
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# Sort by timestamp (newest first)
|
# Sort by timestamp (newest first)
|
||||||
|
|||||||
Reference in New Issue
Block a user