fix(snapshots): send required node param and add storage-support hint
useCreateSnapshot omitted the required ?node= query param, so creation failed with a 422. Pass node through, and add a UI hint plus a clear error message when the VM storage does not support snapshots. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import { useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Plus, Trash2, RotateCcw, Loader2, Camera } from 'lucide-react';
|
||||
import { Plus, Trash2, RotateCcw, Loader2, Camera, Info } from 'lucide-react';
|
||||
import { Card, CardContent } from '@/components/ui/card';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Input } from '@/components/ui/input';
|
||||
@@ -39,7 +39,7 @@ interface Props {
|
||||
export default function SnapshotsTab({ serverId, vmid, type, node }: Props) {
|
||||
const { t } = useTranslation();
|
||||
const { data, isLoading } = useSnapshots(serverId, vmid, type, node);
|
||||
const createSnap = useCreateSnapshot(serverId, vmid, type);
|
||||
const createSnap = useCreateSnapshot(serverId, vmid, type, node);
|
||||
const deleteSnap = useDeleteSnapshot(serverId, vmid, type, node);
|
||||
const rollbackSnap = useRollbackSnapshot(serverId, vmid, type, node);
|
||||
|
||||
@@ -64,7 +64,15 @@ export default function SnapshotsTab({ serverId, vmid, type, node }: Props) {
|
||||
setSnapDesc('');
|
||||
setIncludeRAM(false);
|
||||
},
|
||||
onError: (err) => toast.error(err.message),
|
||||
onError: (err) => {
|
||||
// Proxmox returns this when the VM's storage can't do snapshots
|
||||
// (raw disks, plain LVM, iSCSI). Surface a clear explanation.
|
||||
if (/feature is not available|not available/i.test(err.message)) {
|
||||
toast.error(t('instances.snapshot_unsupported_storage'));
|
||||
} else {
|
||||
toast.error(err.message);
|
||||
}
|
||||
},
|
||||
}
|
||||
);
|
||||
};
|
||||
@@ -211,6 +219,10 @@ export default function SnapshotsTab({ serverId, vmid, type, node }: Props) {
|
||||
<Label htmlFor="snap-ram" className="text-sm">Include VM memory (RAM state)</Label>
|
||||
</div>
|
||||
)}
|
||||
<div className="flex gap-2 rounded-md bg-muted/50 p-2.5 text-xs text-muted-foreground">
|
||||
<Info className="mt-0.5 h-3.5 w-3.5 shrink-0" />
|
||||
<span>{t('instances.snapshot_storage_hint')}</span>
|
||||
</div>
|
||||
</div>
|
||||
<DialogFooter>
|
||||
<Button variant="outline" onClick={() => setShowCreate(false)}>Cancel</Button>
|
||||
|
||||
@@ -215,12 +215,12 @@ export function useSnapshots(serverId: number, vmid: number, type: string, node:
|
||||
});
|
||||
}
|
||||
|
||||
export function useCreateSnapshot(serverId: number, vmid: number, type: string) {
|
||||
export function useCreateSnapshot(serverId: number, vmid: number, type: string, node: string) {
|
||||
const qc = useQueryClient();
|
||||
const prefix = type === 'lxc' ? 'container' : 'vm';
|
||||
return useMutation({
|
||||
mutationFn: (body: { snapname: string; description?: string; vmstate?: boolean }) =>
|
||||
apiClient.post(`/proxmox/api/${serverId}/${prefix}/${vmid}/snapshots`, body),
|
||||
apiClient.post(`/proxmox/api/${serverId}/${prefix}/${vmid}/snapshots?node=${node}`, body),
|
||||
onSuccess: () => {
|
||||
qc.invalidateQueries({ queryKey: vmKeys.snapshots(serverId, vmid) });
|
||||
},
|
||||
|
||||
@@ -188,6 +188,19 @@
|
||||
"owner_hint": "Assign this instance to a user (admin only).",
|
||||
"saved_config": "Saved configuration",
|
||||
"saved_config_hint": "Values stored by the panel at creation/reinstall time.",
|
||||
"compute_resources": "Compute Resources",
|
||||
"vcpu_cores": "vCPU Cores",
|
||||
"current_cores": "Current: {{count}} cores",
|
||||
"memory_mb": "Memory (MB)",
|
||||
"current_memory": "Current: {{count}} MB",
|
||||
"apply_changes": "Apply Changes",
|
||||
"apply_changes_note": "Note: VM may need to be restarted for changes to take effect.",
|
||||
"disk_resize": "Disk Resize",
|
||||
"disk_device": "Disk Device",
|
||||
"new_size_gb": "New Size (GB)",
|
||||
"resize_disk": "Resize Disk",
|
||||
"resize_disk_hint": "Disks can only be enlarged. Use the full target size.",
|
||||
"raw_configuration": "Raw Configuration",
|
||||
"saved_name": "Name",
|
||||
"cloud_user": "Cloud-init user",
|
||||
"execute_script": "Execute script",
|
||||
@@ -217,7 +230,9 @@
|
||||
"notes_saved": "Notes saved",
|
||||
"before_update": "before-update",
|
||||
"rollback": "Rollback",
|
||||
"delete_snapshot": "Delete"
|
||||
"delete_snapshot": "Delete",
|
||||
"snapshot_storage_hint": "Snapshots require storage that supports them (qcow2, ZFS, Ceph/RBD, LVM-thin, BTRFS). They are not available on raw disks or plain LVM/iSCSI. The \"Snapshots as Volume-Chain\" option on LVM only enables snapshots for qcow2-format disks — an existing raw disk must first be moved to qcow2.",
|
||||
"snapshot_unsupported_storage": "This VM's storage does not support snapshots. Use a disk on qcow2, ZFS, Ceph, LVM-thin or BTRFS — or use a backup instead."
|
||||
},
|
||||
"wizard": {
|
||||
"title": "Create Instance",
|
||||
@@ -491,6 +506,8 @@
|
||||
"disabled": "Disabled",
|
||||
"run_now": "Run Now",
|
||||
"no_backups": "No backups found",
|
||||
"no_backups_match": "No backups match your search",
|
||||
"search_backups": "Search by VMID, volume, or notes...",
|
||||
"no_jobs": "No scheduled jobs",
|
||||
"volume": "Volume",
|
||||
"notes": "Notes",
|
||||
|
||||
@@ -188,6 +188,19 @@
|
||||
"owner_hint": "Назначить инстанс пользователю (только админ).",
|
||||
"saved_config": "Сохранённая конфигурация",
|
||||
"saved_config_hint": "Значения, сохранённые панелью при создании/переустановке.",
|
||||
"compute_resources": "Вычислительные ресурсы",
|
||||
"vcpu_cores": "Ядра vCPU",
|
||||
"current_cores": "Сейчас: {{count}} ядер",
|
||||
"memory_mb": "Память (МБ)",
|
||||
"current_memory": "Сейчас: {{count}} МБ",
|
||||
"apply_changes": "Применить изменения",
|
||||
"apply_changes_note": "Примечание: для применения изменений может потребоваться перезапуск ВМ.",
|
||||
"disk_resize": "Изменение размера диска",
|
||||
"disk_device": "Дисковое устройство",
|
||||
"new_size_gb": "Новый размер (ГБ)",
|
||||
"resize_disk": "Изменить размер",
|
||||
"resize_disk_hint": "Диск можно только увеличить. Укажите полный итоговый размер.",
|
||||
"raw_configuration": "Исходная конфигурация",
|
||||
"saved_name": "Имя",
|
||||
"cloud_user": "Cloud-init пользователь",
|
||||
"execute_script": "Выполнить скрипт",
|
||||
@@ -217,7 +230,9 @@
|
||||
"notes_saved": "Заметки сохранены",
|
||||
"before_update": "перед-обновлением",
|
||||
"rollback": "Откатить",
|
||||
"delete_snapshot": "Удалить"
|
||||
"delete_snapshot": "Удалить",
|
||||
"snapshot_storage_hint": "Снимки работают только на хранилищах с их поддержкой (qcow2, ZFS, Ceph/RBD, LVM-thin, BTRFS). На raw-дисках и обычном LVM/iSCSI снимки недоступны. Опция «Snapshots as Volume-Chain» на LVM включает снимки только для дисков в формате qcow2 — существующий raw-диск нужно сначала перенести в qcow2.",
|
||||
"snapshot_unsupported_storage": "Хранилище этой ВМ не поддерживает снимки. Перенесите диск на qcow2, ZFS, Ceph, LVM-thin или BTRFS — либо используйте бэкап."
|
||||
},
|
||||
"wizard": {
|
||||
"title": "Создание инстанса",
|
||||
@@ -491,6 +506,8 @@
|
||||
"disabled": "Выключено",
|
||||
"run_now": "Запустить сейчас",
|
||||
"no_backups": "Бэкапы не найдены",
|
||||
"no_backups_match": "Ничего не найдено по запросу",
|
||||
"search_backups": "Поиск по VMID, тому или заметкам...",
|
||||
"no_jobs": "Нет заданий по расписанию",
|
||||
"volume": "Том",
|
||||
"notes": "Заметки",
|
||||
|
||||
Reference in New Issue
Block a user