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
Proxmox VPS Panel{user.email} · {user.role}
{message &&

{message}

}

Мои инстансы

{instances.length ?
{instances.map((item) => )}
:

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

}

Создать VPS

{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) => )}

Новый VPS

; } +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();