import React, { useEffect, useState, useCallback } from "react";
import { api } from "../api.js";
import InstanceCard from "../components/InstanceCard.jsx";
const PRESETS = [
{ key: "small", label: "S", cpu: 1, ram: 1024, disk: 10 },
{ key: "medium", label: "M", cpu: 2, ram: 2048, disk: 20 },
{ key: "large", label: "L", cpu: 4, ram: 8192, disk: 50 },
{ key: "xlarge", label: "XL", cpu: 8, ram: 16384, disk: 100 },
];
function matchPreset(templates, preset) {
return templates.find(
(template) =>
template.cores === preset.cpu &&
template.memory_mb === preset.ram &&
template.disk_gb === preset.disk,
) || templates
.filter(
(template) =>
template.cores >= preset.cpu &&
template.memory_mb >= preset.ram &&
template.disk_gb >= preset.disk,
)
.sort((a, b) => a.cores + a.memory_mb / 1024 + a.disk_gb - (b.cores + b.memory_mb / 1024 + b.disk_gb))[0] || null;
}
function StatCard({ label, value, tone = "default" }) {
return (
);
}
export default function Dashboard() {
const [instances, setInstances] = useState([]);
const [templates, setTemplates] = useState([]);
const [activeTab, setActiveTab] = useState("vm");
const [showForm, setShowForm] = useState(false);
const [name, setName] = useState("");
const [ciuser, setCiuser] = useState("root");
const [cipassword, setCipassword] = useState("");
const [templateId, setTemplateId] = useState("");
const [selectedPreset, setSelectedPreset] = useState(null);
const [error, setError] = useState("");
const [busy, setBusy] = useState(false);
const [loading, setLoading] = useState(true);
const refresh = useCallback(async () => {
const [instanceList, templateList] = await Promise.all([
api.listInstances(),
api.listTemplates(),
]);
setInstances(instanceList);
setTemplates(templateList);
}, []);
useEffect(() => {
refresh().finally(() => setLoading(false));
const timer = setInterval(refresh, 8000);
return () => clearInterval(timer);
}, [refresh]);
const typeTemplates = templates.filter((template) => template.guest_type === activeTab);
const visibleInstances = instances.filter((instance) => instance.guest_type === activeTab);
useEffect(() => {
if (selectedPreset && activeTab === "vm") {
const preset = PRESETS.find((item) => item.key === selectedPreset);
const match = preset ? matchPreset(typeTemplates, preset) : null;
setTemplateId(match ? String(match.id) : "");
} else {
setTemplateId(typeTemplates.length ? String(typeTemplates[0].id) : "");
}
}, [activeTab, templates, selectedPreset]);
function switchTab(tab) {
setActiveTab(tab);
setSelectedPreset(null);
setError("");
}
async function onCreate(event) {
event.preventDefault();
if (busy || !templateId || !name.trim()) return;
setError("");
setBusy(true);
try {
await api.createInstance({
name: name.trim(),
template_id: Number(templateId),
// Для архивного LXC backend создаёт новый пароль root.
ciuser: activeTab === "vm" ? ciuser : "root",
cipassword: cipassword || "",
});
setName("");
setCipassword("");
setShowForm(false);
await refresh();
} catch (err) {
setError(err.message);
} finally {
setBusy(false);
}
}
async function onAction(id, action) {
try {
await api.instanceAction(id, action);
await refresh();
} catch (err) {
setError(err.message);
}
}
async function onDelete(id) {
if (!confirm("Удалить инстанс?")) return;
try {
await api.deleteInstance(id);
await refresh();
} catch (err) {
setError(err.message);
}
}
const stats = {
total: instances.length,
running: instances.filter((item) => item.status === "running").length,
stopped: instances.filter((item) => item.status === "stopped").length,
error: instances.filter((item) => item.status === "error").length,
};
return (
Мои VPS
{stats.total === 0 ? "Нет активных серверов" : `${stats.running} из ${stats.total} запущено`}
{stats.total > 0 && (
{stats.error > 0 && }
)}
{showForm && (
)}
{loading ?
Загрузка...
: visibleInstances.length === 0 ? (
☁️
Нет {activeTab === "lxc" ? "LXC-контейнеров" : "виртуальных машин"}
Нажмите «+ Новый VPS», чтобы создать ресурс
) : (
{visibleInstances.map((instance) => )}
)}
);
}