diff --git a/frontend/src/pages/Dashboard.jsx b/frontend/src/pages/Dashboard.jsx
index 8cc21dd..dadbbb2 100644
--- a/frontend/src/pages/Dashboard.jsx
+++ b/frontend/src/pages/Dashboard.jsx
@@ -1,7 +1,23 @@
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 },
@@ -9,52 +25,38 @@ const PRESETS = [
{ 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 filterByOS(templates, os) {
+ if (!os) return templates;
+ return templates.filter((template) => template.name.toLowerCase().includes(os));
}
-function StatCard({ label, value, tone = "default" }) {
- return (
-
- );
+function matchPreset(templates, preset) {
+ const exact = templates.find((template) => template.cores === preset.cpu && template.memory_mb === preset.ram && template.disk_gb === preset.disk);
+ if (exact) return exact;
+ return 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 }) {
+ 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 [ciuser, setCiuser] = useState("ubuntu");
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 [instanceList, templateList] = await Promise.all([
- api.listInstances(),
- api.listTemplates(),
- ]);
+ const [instanceList, templateList] = await Promise.all([api.listInstances(), api.listTemplates()]);
setInstances(instanceList);
setTemplates(templateList);
}, []);
@@ -65,67 +67,42 @@ export default function Dashboard() {
return () => clearInterval(timer);
}, [refresh]);
- const typeTemplates = templates.filter((template) => template.guest_type === activeTab);
- const visibleInstances = instances.filter((instance) => instance.guest_type === activeTab);
-
+ const osTemplates = filterByOS(templates, selectedOS);
useEffect(() => {
- if (selectedPreset && activeTab === "vm") {
+ if (selectedPreset) {
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) : "");
+ const match = preset ? matchPreset(osTemplates, preset) : null;
+ if (match) setTemplateId(String(match.id));
+ } else if (osTemplates.length) {
+ setTemplateId(String(osTemplates[0].id));
}
- }, [activeTab, templates, selectedPreset]);
-
- function switchTab(tab) {
- setActiveTab(tab);
- setSelectedPreset(null);
- setError("");
- }
+ }, [selectedOS, selectedPreset, templates]);
async function onCreate(event) {
event.preventDefault();
- if (busy || !templateId || !name.trim()) return;
-
+ if (busy) 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 || "",
- });
+ await api.createInstance({ name, template_id: Number(templateId), ciuser: ciuser || "", cipassword: cipassword || "" });
setName("");
setCipassword("");
setShowForm(false);
+ setSelectedPreset(null);
await refresh();
- } catch (err) {
- setError(err.message);
- } finally {
- setBusy(false);
- }
+ } 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);
- }
+ 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);
- }
+ if (!confirm("Удалить VPS?")) return;
+ try { await api.deleteInstance(id); await refresh(); }
+ catch (err) { setError(err.message); }
}
const stats = {
@@ -137,100 +114,24 @@ export default function Dashboard() {
return (
-
-
-
Мои VPS
-
- {stats.total === 0 ? "Нет активных серверов" : `${stats.running} из ${stats.total} запущено`}
-
-
-
-
+
Мои VPS
{stats.total === 0 ? "Нет активных серверов" : `${stats.running} из ${stats.total} запущено`}
- {stats.total > 0 && (
-
-
-
-
- {stats.error > 0 && }
-
- )}
+ {stats.total > 0 &&
} />} />} />{stats.error > 0 && } />}
}
-
-
-
-
+ {showForm &&
}
- {showForm && (
-
- )}
-
- {loading ?
Загрузка...
: visibleInstances.length === 0 ? (
-
-
☁️
-
Нет {activeTab === "lxc" ? "LXC-контейнеров" : "виртуальных машин"}
-
Нажмите «+ Новый VPS», чтобы создать ресурс
-
- ) : (
-
- {visibleInstances.map((instance) => )}
-
- )}
+ {loading ?
Загрузка...
: instances.length === 0 ?
☁️
Нет активных VPS
Нажмите «+ Новый VPS» чтобы создать
:
{instances.map((instance) => )}
}
);
}
+
+const IconServer = () => ;
+const IconPlay = () => ;
+const IconStop = () => ;
+const IconAlert = () => ;