From b70e3a6988805ff94b842e7af122c0d0a830c9eb Mon Sep 17 00:00:00 2001 From: host Date: Sun, 9 Aug 2026 17:01:20 +0300 Subject: [PATCH] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB=D0=B5?= =?UTF-8?q?=D0=BD=20=D0=B8=D0=BD=D1=82=D0=B5=D1=80=D1=84=D0=B5=D0=B9=D1=81?= =?UTF-8?q?=20=D0=BF=D0=BE=D0=B8=D1=81=D0=BA=D0=B0=20=D0=B8=20=D0=B8=D0=BC?= =?UTF-8?q?=D0=BF=D0=BE=D1=80=D1=82=D0=B0=20=D1=88=D0=B0=D0=B1=D0=BB=D0=BE?= =?UTF-8?q?=D0=BD=D0=BE=D0=B2=20Proxmox?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/TemplateManager.jsx | 125 +++++++++++++++++++++++++++++++ 1 file changed, 125 insertions(+) create mode 100644 frontend/src/TemplateManager.jsx diff --git a/frontend/src/TemplateManager.jsx b/frontend/src/TemplateManager.jsx new file mode 100644 index 0000000..8ab8708 --- /dev/null +++ b/frontend/src/TemplateManager.jsx @@ -0,0 +1,125 @@ +import React, { useEffect, useState } from "react"; +import { + createTemplate, + deactivateTemplate, + listProxmoxTemplates, + listTemplates, +} from "./api"; + +const empty = { + name: "", + description: "", + guest_type: "vm", + source_vmid: "", + source_template: "", + cores: 1, + memory_mb: 1024, + disk_gb: 10, +}; + +export default function TemplateManager() { + const [templates, setTemplates] = useState([]); + const [discovered, setDiscovered] = useState(null); + const [form, setForm] = useState(empty); + const [message, setMessage] = useState(""); + const [busy, setBusy] = useState(false); + + async function refresh() { + setTemplates(await listTemplates()); + } + + async function findTemplates() { + setBusy(true); + setMessage("Ищем шаблоны на Proxmox..."); + try { + setDiscovered(await listProxmoxTemplates()); + setMessage("Шаблоны Proxmox найдены."); + } catch (error) { + setMessage(error.message); + } finally { + setBusy(false); + } + } + + async function importTemplate(payload) { + setBusy(true); + try { + await createTemplate(payload); + await refresh(); + setMessage("Шаблон импортирован в каталог панели."); + } catch (error) { + setMessage(error.message); + } finally { + setBusy(false); + } + } + + async function submit(event) { + event.preventDefault(); + setBusy(true); + try { + await createTemplate({ + ...form, + source_vmid: form.source_vmid ? Number(form.source_vmid) : null, + source_template: form.source_template || null, + cores: Number(form.cores), + memory_mb: Number(form.memory_mb), + disk_gb: Number(form.disk_gb), + }); + setForm(empty); + await refresh(); + setMessage("Шаблон создан."); + } catch (error) { + setMessage(error.message); + } finally { + setBusy(false); + } + } + + async function remove(id) { + if (!window.confirm("Деактивировать шаблон?")) return; + await deactivateTemplate(id); + await refresh(); + } + + useEffect(() => { refresh().catch((error) => setMessage(error.message)); }, []); + + return
+

Шаблоны VPS

+ {message &&

{message}

} + +

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

+ + + {discovered &&
+

VM-шаблоны

+ {discovered.vm_templates?.map((vm) =>
+ {vm.vmid} — {vm.name} + {vm.cores} vCPU · {vm.memory_mb} MB + +
)} + +

LXC-шаблоны

+ {discovered.lxc_templates?.map((lxc) =>
+ {lxc.name} + {lxc.source_kind === "vmid" ? `CTID ${lxc.vmid}` : `${lxc.storage} · ${lxc.size_mb} MB`} + +
)} +
} + +

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

+
+ + + {form.guest_type === "vm" ? : } + +
+ +
+ +

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

+
{templates.map((template) =>
{template.name}{template.guest_type.toUpperCase()} · {template.cores} CPU · {template.memory_mb} MB · {template.disk_gb} GB
)}
+
; +}