diff --git a/frontend/src/main.jsx b/frontend/src/main.jsx index 660859e..7c01fa3 100644 --- a/frontend/src/main.jsx +++ b/frontend/src/main.jsx @@ -1,6 +1,16 @@ -import React, { useState } from "react"; +import React, { useEffect, useState } from "react"; import { createRoot } from "react-dom/client"; -import { getMe, login, register } from "./api"; +import { + clearToken, + createTemplate, + deactivateTemplate, + getMe, + getToken, + listInstances, + listTemplates, + login, + register, +} from "./api"; import "./styles.css"; function App() { @@ -9,6 +19,34 @@ function App() { const [password, setPassword] = useState(""); const [message, setMessage] = useState(""); const [user, setUser] = useState(null); + const [templates, setTemplates] = useState([]); + const [instances, setInstances] = useState([]); + const [templateForm, setTemplateForm] = useState({ + name: "", + description: "", + guest_type: "vm", + source_vmid: "", + source_template: "", + cores: 1, + memory_mb: 1024, + disk_gb: 10, + }); + + async function loadPanel() { + const [templateData, instanceData] = await Promise.all([ + listTemplates(), + listInstances(), + ]); + setTemplates(templateData); + setInstances(instanceData); + } + + useEffect(() => { + if (!getToken()) return; + Promise.all([getMe(), loadPanel()]).then(([currentUser]) => { + setUser(currentUser); + }).catch(() => clearToken()); + }, []); async function handleSubmit(event) { event.preventDefault(); @@ -20,50 +58,137 @@ function App() { setMode("login"); return; } - const tokenData = await login(email, password); - const currentUser = await getMe(tokenData.access_token); + await login(email, password); + const currentUser = await getMe(); setUser(currentUser); + await loadPanel(); setMessage(`Вы вошли как ${currentUser.role}.`); } catch (error) { setMessage(error.message); } } + async function handleCreateTemplate(event) { + event.preventDefault(); + setMessage(""); + try { + const payload = { + ...templateForm, + cores: Number(templateForm.cores), + memory_mb: Number(templateForm.memory_mb), + disk_gb: Number(templateForm.disk_gb), + source_vmid: templateForm.guest_type === "vm" + ? Number(templateForm.source_vmid) + : null, + source_template: templateForm.guest_type === "lxc" + ? templateForm.source_template + : null, + }; + await createTemplate(payload); + setMessage("Шаблон создан."); + setTemplateForm({ ...templateForm, name: "", description: "" }); + await loadPanel(); + } catch (error) { + setMessage(error.message); + } + } + + async function handleDeactivate(id) { + if (!window.confirm("Деактивировать этот шаблон?")) return; + try { + await deactivateTemplate(id); + await loadPanel(); + setMessage("Шаблон деактивирован."); + } catch (error) { + setMessage(error.message); + } + } + + function logout() { + clearToken(); + setUser(null); + setTemplates([]); + setInstances([]); + } + + if (!user) { + return ( +
+
+

Proxmox VPS Panel

+

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

+
+ + + +
+ + {message &&

{message}

} +
+
+ ); + } + return ( -
-
-

Proxmox VPS Panel

- {user ? ( - <> -

Добро пожаловать, {user.email}

-

Роль: {user.role}

- - - ) : ( - <> -

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

-
- - - -
- - - )} - {message &&

{message}

} +
+
+
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" && } +
+ ))}
+ )} +
+ + {user.role === "admin" &&
+

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

+
+ + + + {templateForm.guest_type === "vm" ? ( + + ) : ( + + )} +
+ + + +
+ +
+
}
); } -createRoot(document.getElementById("root")).render( - , -); +createRoot(document.getElementById("root")).render();