diff --git a/frontend/src/main.jsx b/frontend/src/main.jsx
index adc9bef..69ef6b5 100644
--- a/frontend/src/main.jsx
+++ b/frontend/src/main.jsx
@@ -5,29 +5,31 @@ import TemplateManager from "./TemplateManager.jsx";
import "./styles.css";
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); } }
- return Proxmox VPS Panel
{mode === "login" ? "Вход" : "Регистрация"}
{message && {message}
};
+ const [registerMode, setRegisterMode] = useState(false); const [email, setEmail] = useState(""); const [password, setPassword] = useState(""); const [error, setError] = useState("");
+ async function submit(event) { event.preventDefault(); try { if (registerMode) { await register(email, password); setRegisterMode(false); setError("Регистрация завершена. Войдите в панель."); return; } await login(email, password); onLogin(await getMe()); } catch (e) { setError(e.message); } }
+ return VPVPS Panel
{registerMode ? "Регистрация" : "Вход в панель"}
{error && {error}
};
}
-function formatBytes(value) { if (!value) return "0 B"; const units = ["B", "KB", "MB", "GB", "TB"]; let number = value; let index = 0; while (number >= 1024 && index < units.length - 1) { number /= 1024; index += 1; } return `${number.toFixed(index > 1 ? 1 : 0)} ${units[index]}`; }
-
-function InstanceCard({ item, locked, onChanged }) {
+function InstanceCard({ item, onRefresh }) {
const [stats, setStats] = useState(null); const [busy, setBusy] = useState(false); const [error, setError] = useState("");
- async function loadStats() { try { setStats(await getInstanceStats(item.id)); } catch (e) { setError(e.message); } }
- useEffect(() => { loadStats(); const timer = setInterval(loadStats, 10000); return () => clearInterval(timer); }, [item.id]);
- async function action(name) { setBusy(true); setError(""); try { await instanceAction(item.id, name); await onChanged(); await loadStats(); } catch (e) { setError(e.message); } finally { setBusy(false); } }
- async function remove() { if (!window.confirm(`Удалить ${item.name} (VMID ${item.vmid})?`)) return; setBusy(true); try { await deleteInstance(item.id); await onChanged(); } catch (e) { setError(e.message); setBusy(false); } }
- return
{item.name}{item.guest_type.toUpperCase()} · VMID {item.vmid}{stats?.status || item.status} · {item.node}{stats &&
CPU: {stats.cpu_pct}%RAM: {formatBytes(stats.memory_used_bytes)} / {formatBytes(stats.memory_total_bytes)}
}
{stats?.status !== "running" && }{stats?.status === "running" && }
{error &&
{error}}
;
+ async function load() { try { setStats(await getInstanceStats(item.id)); } catch (e) { setError(e.message); } }
+ useEffect(() => { load(); const timer = setInterval(load, 10000); return () => clearInterval(timer); }, [item.id]);
+ async function action(name) { setBusy(true); try { await instanceAction(item.id, name); await onRefresh(); await load(); } catch (e) { setError(e.message); } finally { setBusy(false); } }
+ async function remove() { if (!window.confirm(`Удалить ${item.name} (VMID ${item.vmid})?`)) return; setBusy(true); try { await deleteInstance(item.id); await onRefresh(); } catch (e) { setError(e.message); setBusy(false); } }
+ const running = stats?.status === "running" || item.status === "running";
+ return {running ? "работает" : item.status}{item.guest_type.toUpperCase()}
{item.name}
vmid {item.vmid} · node {item.node} · {stats ? `CPU ${stats.cpu_pct}%` : "обновление..."}
{stats && RAM: {Math.round((stats.memory_used_bytes || 0) / 1024 / 1024)} МБиз {Math.round((stats.memory_total_bytes || 0) / 1024 / 1024)} МБ
}{error && {error}};
}
-function App() {
- const [user, setUser] = useState(null); const [message, setMessage] = useState(""); const [templates, setTemplates] = useState([]); const [instances, setInstances] = useState([]); const [creating, setCreating] = useState(false); const [form, setForm] = useState({ name: "", template_id: "", username: "paneluser", password: "" });
- async function refresh() { const [ts, ins] = await Promise.all([listTemplates(), listInstances()]); setTemplates(ts); setInstances(ins); setForm((current) => ({ ...current, template_id: current.template_id || String(ts[0]?.id || "") })); }
- useEffect(() => { if (!getToken()) return; Promise.all([getMe(), refresh()]).then(([current]) => setUser(current)).catch(() => clearToken()); }, []);
- async function submit(event) { event.preventDefault(); if (creating) return; setCreating(true); setMessage("Создание VPS выполняется. Повторные клики заблокированы..."); try { const result = await createInstance({ ...form, template_id: Number(form.template_id) }); setMessage(`Создан VMID ${result.vmid}. Пароль: ${result.initial_password}`); setForm((v) => ({ ...v, name: "", password: "" })); await refresh(); } catch (e) { setMessage(e.message); } finally { setCreating(false); } }
- if (!user) return ;
- return {message && {message}
}Мои инстансы
{instances.length ? {instances.map((item) => )}
: Инстансов пока нет.
}{user.role === "admin" && };
+function Dashboard({ user, onNavigate }) {
+ const [instances, setInstances] = useState([]); const [templates, setTemplates] = useState([]); const [form, setForm] = useState({ name: "", template_id: "", username: "paneluser", password: "" }); const [busy, setBusy] = useState(false); const [message, setMessage] = useState("");
+ async function refresh() { const [ts, ins] = await Promise.all([listTemplates(), listInstances()]); setTemplates(ts); setInstances(ins); setForm((v) => ({ ...v, template_id: v.template_id || String(ts[0]?.id || "") })); }
+ useEffect(() => { refresh().catch((e) => setMessage(e.message)); }, []);
+ async function create(event) { event.preventDefault(); if (busy) return; setBusy(true); setMessage("Создание VPS выполняется..."); try { const result = await createInstance({ ...form, template_id: Number(form.template_id) }); setMessage(`VPS создан, VMID ${result.vmid}. Пароль: ${result.initial_password}`); setForm({ ...form, name: "", password: "" }); await refresh(); } catch (e) { setMessage(e.message); } finally { setBusy(false); } }
+ return Мои VPS
{instances.length} из {instances.length} запущено
ВСЕГО{instances.length}
ЗАПУЩЕНО{instances.filter((i) => i.status === "running").length}
ОСТАНОВЛЕНО{instances.filter((i) => i.status !== "running").length}
{message &&
{message}
}
{instances.map((item) => )}
;
}
+function Layout({ user, onLogout }) { const [page, setPage] = useState("vps"); return {page === "vps" ? :
} ; }
+
+function App() { const [user, setUser] = useState(null); useEffect(() => { if (getToken()) getMe().then(setUser).catch(clearToken); }, []); if (!user) return ; return { clearToken(); setUser(null); }} />; }
+
createRoot(document.getElementById("root")).render();