Добавлен интерфейс поиска и импорта шаблонов Proxmox

This commit is contained in:
2026-08-09 17:01:20 +03:00
parent 55a5f6e67f
commit b70e3a6988
+125
View File
@@ -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 <section className="card template-manager">
<h2>Шаблоны VPS</h2>
{message && <p className="message">{message}</p>}
<h3>Найти шаблоны на Proxmox</h3>
<button type="button" onClick={findTemplates} disabled={busy}>
{busy ? "Ищем..." : "Найти шаблоны"}
</button>
{discovered && <div className="discovered">
<h3>VM-шаблоны</h3>
{discovered.vm_templates?.map((vm) => <div className="item" key={`vm-${vm.vmid}`}>
<strong>{vm.vmid} {vm.name}</strong>
<span>{vm.cores} vCPU · {vm.memory_mb} MB</span>
<button type="button" disabled={busy} onClick={() => importTemplate({ name: vm.name || `VM-${vm.vmid}`, guest_type: "vm", source_vmid: vm.vmid, cores: vm.cores, memory_mb: vm.memory_mb, disk_gb: 10 })}>Импортировать</button>
</div>)}
<h3>LXC-шаблоны</h3>
{discovered.lxc_templates?.map((lxc) => <div className="item" key={lxc.volid}>
<strong>{lxc.name}</strong>
<span>{lxc.source_kind === "vmid" ? `CTID ${lxc.vmid}` : `${lxc.storage} · ${lxc.size_mb} MB`}</span>
<button type="button" disabled={busy} onClick={() => importTemplate({ name: lxc.name, guest_type: "lxc", source_template: lxc.volid, cores: lxc.cores || 1, memory_mb: lxc.memory_mb || 1024, disk_gb: lxc.disk_gb || 10 })}>Импортировать</button>
</div>)}
</div>}
<h3>Добавить вручную</h3>
<form onSubmit={submit}>
<label>Название<input value={form.name} onChange={(e) => setForm({ ...form, name: e.target.value })} required /></label>
<label>Тип<select value={form.guest_type} onChange={(e) => setForm({ ...form, guest_type: e.target.value })}><option value="vm">VM</option><option value="lxc">LXC</option></select></label>
{form.guest_type === "vm" ? <label>VMID шаблона<input type="number" min="1" value={form.source_vmid} onChange={(e) => setForm({ ...form, source_vmid: e.target.value })} required /></label> : <label>CTID или CT-шаблон<input placeholder="2000 или local:vztmpl/..." value={form.source_template} onChange={(e) => setForm({ ...form, source_template: e.target.value.startsWith("2000") ? "vmid:2000" : e.target.value })} required /></label>}
<label>Описание<input value={form.description} onChange={(e) => setForm({ ...form, description: e.target.value })} /></label>
<div className="form-row"><label>vCPU<input type="number" min="1" value={form.cores} onChange={(e) => setForm({ ...form, cores: e.target.value })} /></label><label>RAM, MB<input type="number" min="128" value={form.memory_mb} onChange={(e) => setForm({ ...form, memory_mb: e.target.value })} /></label><label>Диск, GB<input type="number" min="1" value={form.disk_gb} onChange={(e) => setForm({ ...form, disk_gb: e.target.value })} /></label></div>
<button type="submit" disabled={busy}>Добавить шаблон</button>
</form>
<h3>Активные шаблоны</h3>
<div className="items">{templates.map((template) => <div className="item" key={template.id}><strong>{template.name}</strong><span>{template.guest_type.toUpperCase()} · {template.cores} CPU · {template.memory_mb} MB · {template.disk_gb} GB</span><button className="danger" type="button" onClick={() => remove(template.id)}>Деактивировать</button></div>)}</div>
</section>;
}