diff --git a/frontend/src/pages/Dashboard.jsx b/frontend/src/pages/Dashboard.jsx index dadbbb2..6f8c6d5 100644 --- a/frontend/src/pages/Dashboard.jsx +++ b/frontend/src/pages/Dashboard.jsx @@ -1,4 +1,4 @@ -import React, { useEffect, useState, useCallback } from "react"; +import React, { useEffect, useState, useCallback, useMemo } from "react"; import { Link } from "react-router-dom"; import { api } from "../api.js"; import InstanceCard from "../components/InstanceCard.jsx"; @@ -45,6 +45,7 @@ export default function Dashboard() { const [instances, setInstances] = useState([]); const [templates, setTemplates] = useState([]); const [showForm, setShowForm] = useState(false); + const [guestType, setGuestType] = useState("vm"); // ← НОВОЕ: "vm" | "lxc" const [name, setName] = useState(""); const [ciuser, setCiuser] = useState("ubuntu"); const [cipassword, setCipassword] = useState(""); @@ -67,7 +68,24 @@ export default function Dashboard() { return () => clearInterval(timer); }, [refresh]); - const osTemplates = filterByOS(templates, selectedOS); + // Фильтрация шаблонов: сначала по типу (vm/lxc), потом по ОС. + const typeTemplates = useMemo( + () => templates.filter((t) => t.guest_type === guestType), + [templates, guestType] + ); + const osTemplates = useMemo( + () => filterByOS(typeTemplates, selectedOS), + [typeTemplates, selectedOS] + ); + + // Сбрасываем выбранный тариф при смене типа — иначе может быть выбран + // шаблон другого типа. + useEffect(() => { + setTemplateId(""); + setSelectedPreset(null); + }, [guestType]); + + // Авто-выбор первого подходящего шаблона при изменении фильтров. useEffect(() => { if (selectedPreset) { const preset = PRESETS.find((item) => item.key === selectedPreset); @@ -75,8 +93,10 @@ export default function Dashboard() { if (match) setTemplateId(String(match.id)); } else if (osTemplates.length) { setTemplateId(String(osTemplates[0].id)); + } else { + setTemplateId(""); } - }, [selectedOS, selectedPreset, templates]); + }, [selectedOS, selectedPreset, osTemplates]); async function onCreate(event) { event.preventDefault(); @@ -84,7 +104,14 @@ export default function Dashboard() { setError(""); setBusy(true); try { - await api.createInstance({ name, template_id: Number(templateId), ciuser: ciuser || "", cipassword: cipassword || "" }); + // Для LXC ciuser/cipassword не используются — пароль генерируется на бэкенде. + // Передаём пустые строки, чтобы схема Pydantic не ругалась. + await api.createInstance({ + name, + template_id: Number(templateId), + ciuser: guestType === "vm" ? (ciuser || "") : "", + cipassword: guestType === "vm" ? (cipassword || "") : "", + }); setName(""); setCipassword(""); setShowForm(false); @@ -112,6 +139,11 @@ export default function Dashboard() { error: instances.filter((item) => item.status === "error").length, }; + const isLxc = guestType === "lxc"; + const submitLabel = isLxc + ? (busy ? "⏳ Создаём..." : "📦 Создать LXC") + : (busy ? "⏳ Создаём..." : "🚀 Развернуть VM"); + return (
{stats.total === 0 ? "Нет активных серверов" : `${stats.running} из ${stats.total} запущено`}