95 lines
3.1 KiB
React
95 lines
3.1 KiB
React
import React, { useEffect, useState, useCallback } from "react";
|
|
import { useParams, useNavigate } from "react-router-dom";
|
|
import { api } from "../api.js";
|
|
import ConsoleViewer from "../components/ConsoleViewer.jsx";
|
|
|
|
export default function InstanceDetail() {
|
|
const { id } = useParams();
|
|
const navigate = useNavigate();
|
|
const [instance, setInstance] = useState(null);
|
|
const [showConsole, setShowConsole] = useState(false);
|
|
const [error, setError] = useState("");
|
|
const [busy, setBusy] = useState(false);
|
|
|
|
const refresh = useCallback(async () => {
|
|
const data = await api.getInstance(id);
|
|
setInstance(data);
|
|
}, [id]);
|
|
|
|
useEffect(() => {
|
|
refresh();
|
|
const interval = setInterval(refresh, 5000);
|
|
return () => clearInterval(interval);
|
|
}, [refresh]);
|
|
|
|
async function doAction(action) {
|
|
setBusy(true);
|
|
setError("");
|
|
try {
|
|
await api.instanceAction(id, action);
|
|
await refresh();
|
|
} catch (err) {
|
|
setError(err.message);
|
|
} finally {
|
|
setBusy(false);
|
|
}
|
|
}
|
|
|
|
async function doDelete() {
|
|
if (!confirm("Удалить этот VPS безвозвратно?")) return;
|
|
setBusy(true);
|
|
try {
|
|
await api.deleteInstance(id);
|
|
navigate("/");
|
|
} catch (err) {
|
|
setError(err.message);
|
|
setBusy(false);
|
|
}
|
|
}
|
|
|
|
if (!instance) return <div className="container">Загрузка…</div>;
|
|
|
|
return (
|
|
<div className="container">
|
|
<h1 className="page-title">{instance.name}</h1>
|
|
<p className="page-sub">
|
|
vmid {instance.vmid} · node {instance.node} · {instance.guest_type === "vm" ? "VM (QEMU)" : "LXC"}
|
|
</p>
|
|
|
|
{error && <div className="error-box">{error}</div>}
|
|
|
|
<div className="card" style={{ marginBottom: 24 }}>
|
|
<div className="status-row" style={{ marginBottom: 16 }}>
|
|
<span className={`dot ${instance.status}`} /> {instance.status}
|
|
</div>
|
|
<div className="instance-actions">
|
|
<button className="btn" disabled={busy || instance.status !== "stopped"} onClick={() => doAction("start")}>
|
|
Старт
|
|
</button>
|
|
<button className="btn" disabled={busy || instance.status !== "running"} onClick={() => doAction("reboot")}>
|
|
Перезагрузить
|
|
</button>
|
|
<button className="btn" disabled={busy || instance.status !== "running"} onClick={() => doAction("shutdown")}>
|
|
Выключить
|
|
</button>
|
|
<button className="btn" disabled={busy || instance.status !== "running"} onClick={() => doAction("stop")}>
|
|
Стоп (force)
|
|
</button>
|
|
<button className="btn" onClick={() => setShowConsole((v) => !v)}>
|
|
{showConsole ? "Скрыть консоль" : "Открыть консоль"}
|
|
</button>
|
|
<button className="btn btn-danger" disabled={busy} onClick={doDelete}>
|
|
Удалить
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
{showConsole && (
|
|
<div className="card">
|
|
<ConsoleViewer instanceId={instance.id} />
|
|
</div>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|