Добавлен выбор архивного LXC-шаблона в форме

This commit is contained in:
2026-08-09 18:59:07 +03:00
parent a0dd658c73
commit 4647e26973
+25 -13
View File
@@ -20,11 +20,14 @@ export default function AdminTemplates() {
const [busy, setBusy] = useState(false); const [busy, setBusy] = useState(false);
const [importing, setImporting] = useState(null); const [importing, setImporting] = useState(null);
const vmTemplates = proxmoxTemplates?.vm_templates || [];
const lxcTemplates = proxmoxTemplates?.lxc_templates || [];
async function refresh() { async function refresh() {
setTemplates(await api.listTemplates()); setTemplates(await api.listTemplates());
} }
async function fetchProxmox() { async function findTemplates() {
setError(""); setError("");
setBusy(true); setBusy(true);
try { try {
@@ -38,8 +41,8 @@ export default function AdminTemplates() {
async function importVm(vm) { async function importVm(vm) {
const key = `vm-${vm.vmid}`; const key = `vm-${vm.vmid}`;
setError("");
setImporting(key); setImporting(key);
setError("");
try { try {
await api.createTemplate({ await api.createTemplate({
name: vm.name || `VM-${vm.vmid}`, name: vm.name || `VM-${vm.vmid}`,
@@ -60,8 +63,8 @@ export default function AdminTemplates() {
async function importLxc(archive) { async function importLxc(archive) {
const key = `lxc-${archive.volid}`; const key = `lxc-${archive.volid}`;
setError("");
setImporting(key); setImporting(key);
setError("");
try { try {
await api.createTemplate({ await api.createTemplate({
name: archive.name, name: archive.name,
@@ -120,9 +123,6 @@ export default function AdminTemplates() {
} }
} }
const vmTemplates = proxmoxTemplates?.vm_templates || [];
const lxcTemplates = proxmoxTemplates?.lxc_templates || [];
return ( return (
<div className="container"> <div className="container">
<h1 className="page-title">Шаблоны VPS</h1> <h1 className="page-title">Шаблоны VPS</h1>
@@ -130,7 +130,7 @@ export default function AdminTemplates() {
<div className="card template-discovery"> <div className="card template-discovery">
<h3>🔍 Найти шаблоны на Proxmox</h3> <h3>🔍 Найти шаблоны на Proxmox</h3>
<button className="btn btn-primary" onClick={fetchProxmox} disabled={busy}> <button className="btn btn-primary" onClick={findTemplates} disabled={busy}>
{busy ? "Ищем…" : "Найти шаблоны"} {busy ? "Ищем…" : "Найти шаблоны"}
</button> </button>
@@ -138,7 +138,7 @@ export default function AdminTemplates() {
<div className="template-results"> <div className="template-results">
<section> <section>
<h4>🖥 VM-шаблоны</h4> <h4>🖥 VM-шаблоны</h4>
{vmTemplates.length > 0 ? ( {vmTemplates.length ? (
<table className="data-table"> <table className="data-table">
<thead><tr><th>VMID</th><th>Имя</th><th>vCPU</th><th>RAM</th><th /></tr></thead> <thead><tr><th>VMID</th><th>Имя</th><th>vCPU</th><th>RAM</th><th /></tr></thead>
<tbody>{vmTemplates.map((vm) => { <tbody>{vmTemplates.map((vm) => {
@@ -156,16 +156,14 @@ export default function AdminTemplates() {
<section> <section>
<h4>📦 Архивные LXC-шаблоны</h4> <h4>📦 Архивные LXC-шаблоны</h4>
{lxcTemplates.length > 0 ? ( {lxcTemplates.length ? (
<table className="data-table"> <table className="data-table">
<thead><tr><th>Имя</th><th>Хранилище</th><th>Размер</th><th /></tr></thead> <thead><tr><th>Имя</th><th>Хранилище</th><th>Размер</th><th /></tr></thead>
<tbody>{lxcTemplates.map((archive) => { <tbody>{lxcTemplates.map((archive) => {
const key = `lxc-${archive.volid}`; const key = `lxc-${archive.volid}`;
return ( return (
<tr key={key}> <tr key={key}>
<td>{archive.name}</td> <td>{archive.name}</td><td>{archive.storage || "—"}</td><td>{archive.size_mb || 0} МБ</td>
<td>{archive.storage || "—"}</td>
<td>{archive.size_mb || 0} МБ</td>
<td><button className="btn btn-primary" disabled={importing === key} onClick={() => importLxc(archive)}>{importing === key ? "Импорт…" : "Импорт"}</button></td> <td><button className="btn btn-primary" disabled={importing === key} onClick={() => importLxc(archive)}>{importing === key ? "Импорт…" : "Импорт"}</button></td>
</tr> </tr>
); );
@@ -182,11 +180,25 @@ export default function AdminTemplates() {
<div className="template-form-grid"> <div className="template-form-grid">
<div className="field"><label>Название</label><input value={form.name} onChange={(e) => set("name", e.target.value)} required /></div> <div className="field"><label>Название</label><input value={form.name} onChange={(e) => set("name", e.target.value)} required /></div>
<div className="field"><label>Тип</label><select value={form.guest_type} onChange={(e) => set("guest_type", e.target.value)}><option value="vm">VM</option><option value="lxc">LXC</option></select></div> <div className="field"><label>Тип</label><select value={form.guest_type} onChange={(e) => set("guest_type", e.target.value)}><option value="vm">VM</option><option value="lxc">LXC</option></select></div>
{form.guest_type === "vm" ? ( {form.guest_type === "vm" ? (
<div className="field"><label>VMID шаблона</label><input type="number" value={form.source_vmid} onChange={(e) => set("source_vmid", e.target.value)} placeholder="999" required /></div> <div className="field"><label>VMID шаблона</label><input type="number" value={form.source_vmid} onChange={(e) => set("source_vmid", e.target.value)} placeholder="999" required /></div>
) : ( ) : (
<div className="field"><label>Архив LXC-шаблона</label><input value={form.source_template} onChange={(e) => set("source_template", e.target.value)} placeholder="local:vztmpl/..." required /></div> <div className="field">
<label>Архив LXC-шаблона</label>
<select value={form.source_template} onChange={(e) => set("source_template", e.target.value)} required>
<option value="" disabled>
{lxcTemplates.length ? "Выберите архивный шаблон" : "Сначала нажмите «Найти шаблоны»"}
</option>
{lxcTemplates.map((archive) => (
<option key={archive.volid} value={archive.volid}>
{archive.name} · {archive.storage} · {archive.size_mb} МБ
</option>
))}
</select>
</div>
)} )}
<div className="field"><label>Описание</label><input value={form.description} onChange={(e) => set("description", e.target.value)} /></div> <div className="field"><label>Описание</label><input value={form.description} onChange={(e) => set("description", e.target.value)} /></div>
<div className="field"><label>vCPU</label><input type="number" min={1} value={form.cores} onChange={(e) => set("cores", e.target.value)} /></div> <div className="field"><label>vCPU</label><input type="number" min={1} value={form.cores} onChange={(e) => set("cores", e.target.value)} /></div>
<div className="field"><label>RAM, МБ</label><input type="number" min={256} step={256} value={form.memory_mb} onChange={(e) => set("memory_mb", e.target.value)} /></div> <div className="field"><label>RAM, МБ</label><input type="number" min={256} step={256} value={form.memory_mb} onChange={(e) => set("memory_mb", e.target.value)} /></div>