diff --git a/frontend/src/pages/Dashboard.jsx b/frontend/src/pages/Dashboard.jsx index ee4372d..be9e660 100644 --- a/frontend/src/pages/Dashboard.jsx +++ b/frontend/src/pages/Dashboard.jsx @@ -45,7 +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 [guestType, setGuestType] = useState("vm"); // "vm" | "lxc" — задаётся кнопкой в шапке, внутри формы не меняется const [name, setName] = useState(""); const [ciuser, setCiuser] = useState("ubuntu"); const [cipassword, setCipassword] = useState(""); @@ -68,7 +68,7 @@ export default function Dashboard() { return () => clearInterval(timer); }, [refresh]); - // Шаблоны строго по типу: при выбранном "vm" — только VM, при "lxc" — только LXC. + // Шаблоны строго по выбранному типу. const typeTemplates = useMemo( () => templates.filter((t) => t.guest_type === guestType), [templates, guestType] @@ -78,12 +78,10 @@ export default function Dashboard() { [typeTemplates, selectedOS] ); - // При смене типа — сбросить выбор ОС и тарифа. Иначе может остаться шаблон - // другого типа или несуществующая ОС. + // При смене типа — сбросить выбор тарифа и подобрать доступную ОС. useEffect(() => { setTemplateId(""); setSelectedPreset(null); - // Если текущая выбранная ОС недоступна для нового типа — сбросить на дефолт. if (!OS_OPTIONS.some((os) => os.key === selectedOS && filterByOS(typeTemplates, os.key).length > 0)) { const firstAvailable = OS_OPTIONS.find((os) => filterByOS(typeTemplates, os.key).length > 0); setSelectedOS(firstAvailable?.key || "ubuntu"); @@ -106,6 +104,9 @@ export default function Dashboard() { // Открыть форму для конкретного типа — VM или LXC. const openForm = (type) => { setGuestType(type); + setName(""); + setCipassword(""); + setSelectedPreset(null); setShowForm(true); }; @@ -121,17 +122,12 @@ export default function Dashboard() { setBusy(true); try { const isLxc = guestType === "lxc"; - // Для LXC пользователь — root (зашит в бэкенде), пароль — ввод пользователя. - // Для VM — стандартная cloud-init пара. await api.createInstance({ name, template_id: Number(templateId), ciuser: isLxc ? "" : (ciuser || ""), cipassword: cipassword || "", }); - setName(""); - setCipassword(""); - setSelectedPreset(null); setShowForm(false); await refresh(); } catch (err) { setError(err.message); } @@ -157,11 +153,7 @@ export default function Dashboard() { }; const isLxc = guestType === "lxc"; - const submitLabel = isLxc - ? (busy ? "⏳ Создаём..." : "📦 Создать LXC") - : (busy ? "⏳ Создаём..." : "🚀 Развернуть VM"); const hasTypeTemplates = typeTemplates.length > 0; - const typeLabel = isLxc ? "LXC-контейнеров" : "VM (виртуальных машин)"; return (
@@ -185,123 +177,126 @@ export default function Dashboard() { {stats.total > 0 &&
} />} />} />{stats.error > 0 && } />}
} - {showForm &&
- {error &&
{error}
} + {showForm && ( + + {error &&
{error}
} - {/* Переключатель типа внутри формы — на случай если передумал. */} -
- -
- - + {/* Статический заголовок типа — переключатель убран, тип задаётся + только кнопкой в шапке при открытии формы. */} +
+ {isLxc ? ( + <> + 📦 + + Создаём LXC-контейнер · пользователь root · пароль задаёте сами + + + ) : ( + <> + 🖥 + + Создаём виртуальную машину · cloud-init · любой пользователь + + + )}
-
-
-
setName(event.target.value)} placeholder="my-server" required />
- {isLxc ? ( -
- ) : ( -
setCiuser(event.target.value)} placeholder="ubuntu" />
- )} -
setCipassword(event.target.value)} placeholder="••••••" required={isLxc} title={isLxc ? "Задайте root-пароль для LXC" : "Пароль для cloud-init"} />
-
+
+
setName(event.target.value)} placeholder="my-server" required />
+ {isLxc ? ( +
+ ) : ( +
setCiuser(event.target.value)} placeholder="ubuntu" />
+ )} +
setCipassword(event.target.value)} placeholder="••••••" required={isLxc} title={isLxc ? "Задайте root-пароль для LXC" : "Пароль для cloud-init"} />
+
- {/* Система и тариф — только если есть шаблоны этого типа. */} - {!hasTypeTemplates ? ( -
-
-
📦
-
Нет шаблонов для {typeLabel}
-
- Перейдите в раздел «Шаблоны» слева → - нажмите «🔍 Найти шаблоны на Proxmox» → - импортируйте нужный шаблон. После этого он появится здесь. + {/* Система и тариф — только если есть шаблоны этого типа. */} + {!hasTypeTemplates ? ( +
+
+
📦
+
Нет шаблонов для {isLxc ? "LXC-контейнеров" : "VM (виртуальных машин)"}
+
+ Перейдите в раздел «Шаблоны» слева → + нажмите «🔍 Найти шаблоны на Proxmox» → + импортируйте нужный шаблон. После этого он появится здесь. +
-
- ) : ( - <> -
- -
{OS_OPTIONS.map((os) => { - const active = selectedOS === os.key; - const available = filterByOS(typeTemplates, os.key).length > 0; - return ( - - ); - })}
-
- -
- -
- -
{PRESETS.map((preset) => { - const match = matchPreset(osTemplates, preset); - const available = Boolean(match); + ) : ( + <> +
+ +
{OS_OPTIONS.map((os) => { + const active = selectedOS === os.key; + const available = filterByOS(typeTemplates, os.key).length > 0; return ( ); })}
-
- - )} - - } +
+ +
+ +
{PRESETS.map((preset) => { + const match = matchPreset(osTemplates, preset); + const available = Boolean(match); + return ( + + ); + })}
+
+
+ + )} + + + + )} {loading ?
Загрузка...
: instances.length === 0 ?
☁️
Нет активных VPS
Нажмите «+ Новый VM» или «+ Новый LXC» чтобы создать
:
{instances.map((instance) => )}
}
@@ -309,6 +304,6 @@ export default function Dashboard() { } const IconServer = () => ; -const IconPlay = () => ; +const IconPlay = () => ; const IconStop = () => ; -const IconAlert = () => ; +const IconAlert = () => ; \ No newline at end of file