diff --git a/frontend/src/main.jsx b/frontend/src/main.jsx index d67222b..37b9951 100644 --- a/frontend/src/main.jsx +++ b/frontend/src/main.jsx @@ -2,50 +2,25 @@ import React, { useEffect, useState } from "react"; import { createRoot } from "react-dom/client"; import { clearToken, createInstance, createTemplate, deactivateTemplate, - getMe, getToken, listInstances, listTemplates, login, register, + deleteInstance, getMe, getToken, listInstances, listTemplates, login, register, } from "./api"; import "./styles.css"; -const emptyTemplate = { - name: "", description: "", guest_type: "vm", source_vmid: "", - source_template: "", cores: 1, memory_mb: 1024, disk_gb: 10, -}; +const emptyTemplate = { name: "", description: "", guest_type: "vm", source_vmid: "", source_template: "", cores: 1, memory_mb: 1024, disk_gb: 10 }; function Auth({ onLogin }) { const [mode, setMode] = useState("login"); const [email, setEmail] = useState(""); const [password, setPassword] = useState(""); const [message, setMessage] = useState(""); - async function submit(event) { event.preventDefault(); try { - if (mode === "register") { - await register(email, password); - setMessage("Регистрация завершена. Теперь войдите."); - setMode("login"); - return; - } - await login(email, password); - onLogin(await getMe()); - } catch (error) { - setMessage(error.message); - } + if (mode === "register") { await register(email, password); setMessage("Регистрация завершена. Теперь войдите."); setMode("login"); return; } + await login(email, password); onLogin(await getMe()); + } catch (error) { setMessage(error.message); } } - - return
-

Proxmox VPS Panel

-

{mode === "login" ? "Вход" : "Регистрация"}

-
- - - -
- - {message &&

{message}

} -
; + return

Proxmox VPS Panel

{mode === "login" ? "Вход" : "Регистрация"}

{message &&

{message}

}
; } function App() { @@ -55,42 +30,44 @@ function App() { const [instances, setInstances] = useState([]); const [templateForm, setTemplateForm] = useState(emptyTemplate); const [instanceForm, setInstanceForm] = useState({ name: "", template_id: "", username: "paneluser", password: "" }); + const [creating, setCreating] = useState(false); + const [deletingId, setDeletingId] = useState(null); async function refresh() { const [templateData, instanceData] = await Promise.all([listTemplates(), listInstances()]); - setTemplates(templateData); - setInstances(instanceData); + setTemplates(templateData); setInstances(instanceData); setInstanceForm((current) => ({ ...current, template_id: current.template_id || String(templateData[0]?.id || "") })); } - - useEffect(() => { - if (!getToken()) return; - Promise.all([getMe(), refresh()]).then(([current]) => setUser(current)).catch(() => clearToken()); - }, []); + useEffect(() => { if (!getToken()) return; Promise.all([getMe(), refresh()]).then(([current]) => setUser(current)).catch(() => clearToken()); }, []); async function submitTemplate(event) { event.preventDefault(); try { - await createTemplate({ - ...templateForm, - source_vmid: templateForm.guest_type === "vm" ? Number(templateForm.source_vmid) : null, - source_template: templateForm.guest_type === "lxc" ? templateForm.source_template : null, - cores: Number(templateForm.cores), memory_mb: Number(templateForm.memory_mb), disk_gb: Number(templateForm.disk_gb), - }); - setTemplateForm(emptyTemplate); - setMessage("Шаблон создан."); - await refresh(); + await createTemplate({ ...templateForm, source_vmid: templateForm.guest_type === "vm" ? Number(templateForm.source_vmid) : null, source_template: templateForm.guest_type === "lxc" ? templateForm.source_template : null, cores: Number(templateForm.cores), memory_mb: Number(templateForm.memory_mb), disk_gb: Number(templateForm.disk_gb) }); + setTemplateForm(emptyTemplate); setMessage("Шаблон создан."); await refresh(); } catch (error) { setMessage(error.message); } } async function submitInstance(event) { event.preventDefault(); + if (creating) return; + setCreating(true); setMessage("Создание VPS выполняется. Не нажимайте кнопку повторно..."); try { const result = await createInstance({ ...instanceForm, template_id: Number(instanceForm.template_id) }); - setMessage(`Инстанс создан: VMID ${result.vmid}. Пароль покажите пользователю и больше нигде не сохраняйте: ${result.initial_password}`); + setMessage(`Инстанс создан: VMID ${result.vmid}. Пароль: ${result.initial_password}`); setInstanceForm((current) => ({ ...current, name: "", password: "" })); await refresh(); } catch (error) { setMessage(error.message); } + finally { setCreating(false); } + } + + async function removeInstance(item) { + if (deletingId || creating) return; + if (!window.confirm(`Удалить ${item.name} (VMID ${item.vmid}) из Proxmox? Это действие необратимо.`)) return; + setDeletingId(item.id); setMessage(`Удаление ${item.name} выполняется...`); + try { await deleteInstance(item.id); setMessage("Инстанс удалён."); await refresh(); } + catch (error) { setMessage(error.message); } + finally { setDeletingId(null); } } async function deactivate(id) { @@ -99,30 +76,11 @@ function App() { } if (!user) return ; - - return
-
Proxmox VPS Panel{user.email} · {user.role}
- {message &&

{message}

} -
-

Мои инстансы

{instances.length === 0 ?

Инстансов пока нет.

:
{instances.map((item) =>
{item.name}{item.guest_type.toUpperCase()} · VMID {item.vmid ?? "не назначен"}{item.status} · {item.node}
)}
}
-

Доступные шаблоны

{templates.length === 0 ?

Шаблонов пока нет.

:
{templates.map((item) =>
{item.name}{item.guest_type.toUpperCase()} · {item.cores} CPU · {item.memory_mb} MB · {item.disk_gb} GB{user.role === "admin" && }
)}
}
-
- -

Создать VPS

Операция может занимать несколько минут. Пароль будет показан только в ответе создания.

- - -
- -
- - {user.role === "admin" &&

Создать шаблон

- - - - {templateForm.guest_type === "vm" ? : } -
- -
} + return
Proxmox VPS Panel{user.email} · {user.role}
{message &&

{message}

} +

Мои инстансы

{instances.length === 0 ?

Инстансов пока нет.

:
{instances.map((item) =>
{item.name}{item.guest_type.toUpperCase()} · VMID {item.vmid ?? "не назначен"}{item.status} · {item.node}{item.vmid && }
)}
}
+

Доступные шаблоны

{templates.length === 0 ?

Шаблонов пока нет.

:
{templates.map((item) =>
{item.name}{item.guest_type.toUpperCase()} · {item.cores} CPU · {item.memory_mb} MB · {item.disk_gb} GB{user.role === "admin" && }
)}
}
+

Создать VPS

Операция может занимать несколько минут. Повторная отправка заблокирована до завершения операции.

+ {user.role === "admin" &&

Создать шаблон

{templateForm.guest_type === "vm" ? : }
}
; }