diff --git a/frontend/src/pages/Dashboard.jsx b/frontend/src/pages/Dashboard.jsx
index e8a9d34..8cc21dd 100644
--- a/frontend/src/pages/Dashboard.jsx
+++ b/frontend/src/pages/Dashboard.jsx
@@ -1,65 +1,33 @@
import React, { useEffect, useState, useCallback } from "react";
-import { Link } from "react-router-dom";
import { api } from "../api.js";
import InstanceCard from "../components/InstanceCard.jsx";
-const UbuntuLogo = () => (
-
-);
-
-const DebianLogo = () => (
-
-);
-
-const CentOSLogo = () => (
-
-);
-
-const OS_OPTIONS = [
- { key: "ubuntu", label: "Ubuntu", desc: "Универсальный", logo: UbuntuLogo },
- { key: "debian", label: "Debian", desc: "Стабильный", logo: DebianLogo },
- { key: "centos", label: "CentOS", desc: "Корпоративный", logo: CentOSLogo },
-];
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: "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 filterByOS(tpl, os) {
- if (!os) return tpl;
- return tpl.filter(t => t.name.toLowerCase().includes(os));
-}
-function matchPreset(tpl, p) {
- const exact = tpl.find(t => t.cores === p.cpu && t.memory_mb === p.ram && t.disk_gb === p.disk);
- if (exact) return exact;
- return tpl
- .filter(t => t.cores >= p.cpu && t.memory_mb >= p.ram && t.disk_gb >= p.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 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", icon }) {
+function StatCard({ label, value, tone = "default" }) {
return (
-
{icon}
{label}
{value}
@@ -71,55 +39,68 @@ function StatCard({ label, value, tone = "default", icon }) {
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("ubuntu");
+ const [ciuser, setCiuser] = useState("root");
const [cipassword, setCipassword] = useState("");
const [templateId, setTemplateId] = useState("");
- const [selectedOS, setSelectedOS] = useState("ubuntu");
const [selectedPreset, setSelectedPreset] = useState(null);
const [error, setError] = useState("");
const [busy, setBusy] = useState(false);
const [loading, setLoading] = useState(true);
const refresh = useCallback(async () => {
- const [inst, tpl] = await Promise.all([api.listInstances(), api.listTemplates()]);
- setInstances(inst);
- setTemplates(tpl);
+ const [instanceList, templateList] = await Promise.all([
+ api.listInstances(),
+ api.listTemplates(),
+ ]);
+ setInstances(instanceList);
+ setTemplates(templateList);
}, []);
useEffect(() => {
refresh().finally(() => setLoading(false));
- const i = setInterval(refresh, 8000);
- return () => clearInterval(i);
+ const timer = setInterval(refresh, 8000);
+ return () => clearInterval(timer);
}, [refresh]);
- const osT = filterByOS(templates, selectedOS);
+ const typeTemplates = templates.filter((template) => template.guest_type === activeTab);
+ const visibleInstances = instances.filter((instance) => instance.guest_type === activeTab);
+
useEffect(() => {
- if (selectedPreset) {
- const p = PRESETS.find(x => x.key === selectedPreset);
- const m = matchPreset(osT, p);
- if (m) setTemplateId(String(m.id));
- } else if (osT.length > 0) {
- setTemplateId(String(osT[0].id));
+ 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) : "");
}
- }, [selectedOS, selectedPreset, templates]);
+ }, [activeTab, templates, selectedPreset]);
+
+ function switchTab(tab) {
+ setActiveTab(tab);
+ setSelectedPreset(null);
+ setError("");
+ }
+
+ async function onCreate(event) {
+ event.preventDefault();
+ if (busy || !templateId || !name.trim()) return;
- async function onCreate(e) {
- e.preventDefault();
setError("");
setBusy(true);
try {
await api.createInstance({
- name,
+ name: name.trim(),
template_id: Number(templateId),
- ciuser: ciuser || "",
+ // Для архивного LXC backend создаёт новый пароль root.
+ ciuser: activeTab === "vm" ? ciuser : "root",
cipassword: cipassword || "",
});
setName("");
setCipassword("");
setShowForm(false);
- setSelectedPreset(null);
await refresh();
} catch (err) {
setError(err.message);
@@ -138,7 +119,7 @@ export default function Dashboard() {
}
async function onDelete(id) {
- if (!confirm("Удалить VPS?")) return;
+ if (!confirm("Удалить инстанс?")) return;
try {
await api.deleteInstance(id);
await refresh();
@@ -147,12 +128,11 @@ export default function Dashboard() {
}
}
- // Метрики для виджетов.
const stats = {
total: instances.length,
- running: instances.filter(i => i.status === "running").length,
- stopped: instances.filter(i => i.status === "stopped").length,
- error: instances.filter(i => i.status === "error").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 (
@@ -161,207 +141,96 @@ export default function Dashboard() {
Мои VPS
- {stats.total === 0
- ? "Нет активных серверов"
- : `${stats.running} из ${stats.total} запущено`}
+ {stats.total === 0 ? "Нет активных серверов" : `${stats.running} из ${stats.total} запущено`}
-
- {/* Виджеты-метрики */}
{stats.total > 0 && (
- }
- />
- }
- />
- }
- />
- {stats.error > 0 && (
- }
- />
- )}
+
+
+
+ {stats.error > 0 && }
)}
+
+ switchTab("vm")}>
+ Виртуальные машины
+
+ switchTab("lxc")}>
+ LXC-контейнеры
+
+
+
{showForm && (
)}
- {loading ? (
-
Загрузка...
- ) : instances.length === 0 ? (
+ {loading ?
Загрузка...
: visibleInstances.length === 0 ? (
☁️
-
Нет активных VPS
-
Нажмите «+ Новый VPS» чтобы создать
+
Нет {activeTab === "lxc" ? "LXC-контейнеров" : "виртуальных машин"}
+
Нажмите «+ Новый VPS», чтобы создать ресурс
) : (
- {instances.map(inst => (
-
- ))}
+ {visibleInstances.map((instance) => )}
)}
);
}
-
-// Иконки для виджетов.
-const IconServer = () => (
-
-);
-const IconPlay = () => (
-
-);
-const IconStop = () => (
-
-);
-const IconAlert = () => (
-
-);
\ No newline at end of file