Добавлен выбор архивного LXC-шаблона в форме
This commit is contained in:
@@ -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>
|
||||||
|
|||||||
Reference in New Issue
Block a user