diff --git a/frontend/src/pages/AdminTemplates.jsx b/frontend/src/pages/AdminTemplates.jsx index 6da676d..6d58c73 100644 --- a/frontend/src/pages/AdminTemplates.jsx +++ b/frontend/src/pages/AdminTemplates.jsx @@ -1,7 +1,16 @@ import React, { useEffect, useState } from "react"; import { api } from "../api.js"; -const empty = { name: "", description: "", guest_type: "vm", source_vmid: "", source_template: "", cores: 1, memory_mb: 1024, disk_gb: 10 }; +const empty = { + name: "", + description: "", + guest_type: "vm", + source_vmid: "", + source_template: "", + cores: 1, + memory_mb: 1024, + disk_gb: 10, +}; export default function AdminTemplates() { const [templates, setTemplates] = useState([]); @@ -9,66 +18,196 @@ export default function AdminTemplates() { const [form, setForm] = useState(empty); const [error, setError] = useState(""); const [busy, setBusy] = useState(false); + const [importing, setImporting] = useState(null); - async function refresh() { setTemplates(await api.listTemplates()); } - async function fetchProxmox() { setBusy(true); try { setProxmoxTemplates(await api.fetchProxmoxTemplates()); } catch (err) { setError(err.message); } finally { setBusy(false); } } - async function importVm(vm) { await api.createTemplate({ name: vm.name || "VM-"+vm.vmid, guest_type: "vm", source_vmid: vm.vmid, cores: vm.cores, memory_mb: vm.memory_mb, disk_gb: vm.disk_gb || 10 }); await refresh(); } - async function importLxc(lxc) { await api.createTemplate({ name: lxc.name, guest_type: "lxc", source_template: lxc.volid, cores: 1, memory_mb: 1024, disk_gb: 8 }); await refresh(); } - async function deleteTemplate(id) { if (!confirm("Удалить шаблон?")) return; await api.deleteTemplate(id); await refresh(); } + async function refresh() { + setTemplates(await api.listTemplates()); + } - useEffect(() => { refresh(); }, []); - function set(f, v) { setForm(fr => ({ ...fr, [f]: v })); } + async function fetchProxmox() { + setError(""); + setBusy(true); + try { + setProxmoxTemplates(await api.fetchProxmoxTemplates()); + } catch (err) { + setError(err.message); + } finally { + setBusy(false); + } + } - async function onSubmit(e) { e.preventDefault(); setError(""); setBusy(true); try { await api.createTemplate({ ...form, cores: Number(form.cores), memory_mb: Number(form.memory_mb), disk_gb: Number(form.disk_gb), source_vmid: form.source_vmid ? Number(form.source_vmid) : null, source_template: form.source_template || null }); setForm(empty); await refresh(); } catch (err) { setError(err.message); } finally { setBusy(false); } } + async function importVm(vm) { + setError(""); + setImporting(`vm-${vm.vmid}`); + try { + await api.createTemplate({ + name: vm.name || `VM-${vm.vmid}`, + guest_type: "vm", + source_vmid: Number(vm.vmid), + source_template: null, + cores: vm.cores || 1, + memory_mb: vm.memory_mb || 1024, + disk_gb: vm.disk_gb || 10, + }); + await refresh(); + } catch (err) { + setError(err.message); + } finally { + setImporting(null); + } + } + + async function importLxc(lxc) { + setError(""); + const key = lxc.vmid ? `lxc-vmid-${lxc.vmid}` : `lxc-${lxc.volid}`; + setImporting(key); + try { + await api.createTemplate({ + name: lxc.name || (lxc.vmid ? `LXC-${lxc.vmid}` : "LXC"), + guest_type: "lxc", + source_vmid: lxc.vmid ? Number(lxc.vmid) : null, + source_template: lxc.vmid ? null : lxc.volid, + cores: lxc.cores || 1, + memory_mb: lxc.memory_mb || 1024, + disk_gb: lxc.disk_gb || 8, + }); + await refresh(); + } catch (err) { + setError(err.message); + } finally { + setImporting(null); + } + } + + async function deleteTemplate(id) { + if (!confirm("Удалить шаблон?")) return; + try { + await api.deleteTemplate(id); + await refresh(); + } catch (err) { + setError(err.message); + } + } + + useEffect(() => { + refresh().catch((err) => setError(err.message)); + }, []); + + function set(field, value) { + setForm((current) => ({ ...current, [field]: value })); + } + + async function onSubmit(event) { + event.preventDefault(); + setError(""); + setBusy(true); + try { + await api.createTemplate({ + ...form, + cores: Number(form.cores), + memory_mb: Number(form.memory_mb), + disk_gb: Number(form.disk_gb), + source_vmid: form.source_vmid ? Number(form.source_vmid) : null, + source_template: form.source_template || null, + }); + setForm(empty); + await refresh(); + } catch (err) { + setError(err.message); + } finally { + setBusy(false); + } + } + + const vmTemplates = proxmoxTemplates?.vm_templates || []; + const lxcTemplates = proxmoxTemplates?.lxc_templates || []; return (

Шаблоны VPS

{error &&
{error}
} -
+

🔍 Найти шаблоны на Proxmox

- + + {proxmoxTemplates && ( -
- {proxmoxTemplates.vm_templates?.length > 0 && ( -

🖥 VM-шаблоны

- - {proxmoxTemplates.vm_templates.map(vm => ( - - ))} -
VMIDИмяvCPURAM
{vm.vmid}{vm.name}{vm.cores}{vm.memory_mb} МБ
)} - {proxmoxTemplates.lxc_templates?.length > 0 && ( -

📦 LXC-шаблоны

- - {proxmoxTemplates.lxc_templates.map((lxc,i) => ( - - ))} -
ИмяХранилищеРазмер
{lxc.name}{lxc.storage}{lxc.size_mb} МБ
)} - {(!proxmoxTemplates.vm_templates?.length && !proxmoxTemplates.lxc_templates?.length) &&

Шаблоны не найдены.

} -
)} +
+
+

🖥 VM-шаблоны

+ {vmTemplates.length > 0 ? ( + + + + {vmTemplates.map((vm) => { + const key = `vm-${vm.vmid}`; + return ( + + + + + ); + })} + +
VMIDИмяvCPURAM
{vm.vmid}{vm.name}{vm.cores}{vm.memory_mb} МБ
+ ) :

VM-шаблоны не найдены.

} +
+ +
+

📦 LXC-шаблоны

+ {lxcTemplates.length > 0 ? ( + + + + {lxcTemplates.map((lxc, index) => { + const key = lxc.vmid ? `lxc-vmid-${lxc.vmid}` : `lxc-${lxc.volid || index}`; + const source = lxc.vmid ? "LXC-контейнер" : `${lxc.storage || "—"}: vzdump`; + return ( + + + + + + + + + ); + })} + +
VMIDИмяИсточникvCPURAM
{lxc.vmid || "—"}{lxc.name}{source}{lxc.cores || 1}{lxc.memory_mb || `${lxc.size_mb || 0} МБ`}
+ ) :

LXC-шаблоны не найдены.

} +
+
+ )}
-
+

➕ Добавить вручную

-
-
set("name", e.target.value)} required />
-
- {form.guest_type === "vm" ?
set("source_vmid", e.target.value)} placeholder="999" required />
- :
set("source_template", e.target.value)} placeholder="local:vztmpl/..." required />
} -
set("description", e.target.value)} />
-
set("cores", e.target.value)} />
-
set("memory_mb", e.target.value)} />
-
set("disk_gb", e.target.value)} />
+
+
set("name", e.target.value)} required />
+
+ {form.guest_type === "vm" ?
set("source_vmid", e.target.value)} placeholder="999" required />
+ :
set("source_template", e.target.value)} placeholder="local:vztmpl/..." required />
} +
set("description", e.target.value)} />
+
set("cores", e.target.value)} />
+
set("memory_mb", e.target.value)} />
+
set("disk_gb", e.target.value)} />
- +

📋 Активные шаблоны

- - {templates.map(t => ( - - ))} -
НазваниеТипvCPURAMДиск
{t.name}{t.guest_type==="vm"?"VM":"LXC"}{t.cores}{t.memory_mb} МБ{t.disk_gb} ГБ
-
); + + + {templates.map((template) => ( + + + + + ))} +
НазваниеТипvCPURAMДиск
{template.name}{template.guest_type === "vm" ? "VM" : "LXC"}{template.cores}{template.memory_mb} МБ{template.disk_gb} ГБ
+
+ ); }