185 lines
4.8 KiB
HTML
185 lines
4.8 KiB
HTML
<!doctype html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8" />
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
<title>Spawner Control</title>
|
|
<style>
|
|
:root {
|
|
color-scheme: dark;
|
|
}
|
|
|
|
body {
|
|
font-family: Inter, ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto,
|
|
Arial, sans-serif;
|
|
margin: 0;
|
|
min-height: 100vh;
|
|
display: grid;
|
|
place-items: center;
|
|
background: radial-gradient(1200px 600px at 20% -10%, #1f2a44 0%, #0d1018 45%, #090b12 100%);
|
|
color: #e6eaf2;
|
|
}
|
|
|
|
.app {
|
|
width: min(900px, calc(100vw - 2rem));
|
|
background: rgba(18, 22, 34, 0.9);
|
|
border: 1px solid rgba(154, 167, 199, 0.2);
|
|
border-radius: 16px;
|
|
box-shadow: 0 20px 50px rgba(0, 0, 0, 0.45);
|
|
padding: 1.25rem;
|
|
}
|
|
|
|
h1 {
|
|
margin: 0;
|
|
font-size: 1.15rem;
|
|
letter-spacing: 0.02em;
|
|
}
|
|
|
|
.toolbar {
|
|
margin-top: 1rem;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 0.65rem;
|
|
}
|
|
|
|
button {
|
|
border: 1px solid rgba(154, 167, 199, 0.35);
|
|
border-radius: 10px;
|
|
padding: 0.58rem 0.95rem;
|
|
font-size: 0.95rem;
|
|
font-weight: 600;
|
|
cursor: pointer;
|
|
background: linear-gradient(180deg, #212a43 0%, #171f34 100%);
|
|
color: #edf2ff;
|
|
transition: transform 120ms ease, filter 120ms ease, border-color 120ms ease;
|
|
}
|
|
|
|
button:hover {
|
|
filter: brightness(1.08);
|
|
border-color: rgba(154, 167, 199, 0.6);
|
|
}
|
|
|
|
button:active {
|
|
transform: translateY(1px);
|
|
}
|
|
|
|
#status {
|
|
color: #aeb8cf;
|
|
font-size: 0.92rem;
|
|
}
|
|
|
|
#logs {
|
|
margin-top: 1rem;
|
|
border: 1px solid rgba(154, 167, 199, 0.22);
|
|
border-radius: 12px;
|
|
padding: 0.75rem;
|
|
min-height: 280px;
|
|
max-height: 500px;
|
|
overflow: auto;
|
|
background: rgba(7, 10, 17, 0.7);
|
|
}
|
|
|
|
#logs::-webkit-scrollbar {
|
|
width: 10px;
|
|
}
|
|
|
|
#logs::-webkit-scrollbar-thumb {
|
|
background: rgba(154, 167, 199, 0.35);
|
|
border-radius: 999px;
|
|
}
|
|
|
|
.line {
|
|
margin: 0.4rem 0;
|
|
padding: 0.45rem 0.55rem;
|
|
border-radius: 8px;
|
|
background: rgba(255, 255, 255, 0.02);
|
|
white-space: pre-wrap;
|
|
word-break: break-word;
|
|
font-size: 0.93rem;
|
|
}
|
|
|
|
.time {
|
|
color: #96a2c2;
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<main class="app">
|
|
<h1>Spawner Control</h1>
|
|
<div class="toolbar">
|
|
<button id="openSpawner">Open Spawner</button>
|
|
<button id="stopSpawner">Stop Loop</button>
|
|
<span id="status"></span>
|
|
</div>
|
|
|
|
<div id="logs" aria-live="polite"></div>
|
|
</main>
|
|
|
|
<script>
|
|
const statusEl = document.getElementById("status");
|
|
const logsEl = document.getElementById("logs");
|
|
const openBtn = document.getElementById("openSpawner");
|
|
const stopBtn = document.getElementById("stopSpawner");
|
|
|
|
function setStatus(text) {
|
|
statusEl.textContent = text;
|
|
}
|
|
|
|
async function openSpawner() {
|
|
setStatus("opening...");
|
|
try {
|
|
const res = await fetch("/api/spawner", {
|
|
method: "POST",
|
|
});
|
|
if (!res.ok) {
|
|
const data = await res.json().catch(() => ({}));
|
|
throw new Error(data.error || "Request failed");
|
|
}
|
|
setStatus("done");
|
|
} catch (err) {
|
|
setStatus(`error: ${err.message}`);
|
|
}
|
|
}
|
|
|
|
async function stopSpawner() {
|
|
setStatus("stopping...");
|
|
try {
|
|
const res = await fetch("/api/stop", {
|
|
method: "POST",
|
|
});
|
|
if (!res.ok) {
|
|
const data = await res.json().catch(() => ({}));
|
|
throw new Error(data.error || "Request failed");
|
|
}
|
|
setStatus("stopped");
|
|
} catch (err) {
|
|
setStatus(`error: ${err.message}`);
|
|
}
|
|
}
|
|
|
|
async function refreshLogs() {
|
|
try {
|
|
const res = await fetch("/api/logs");
|
|
if (!res.ok) return;
|
|
const data = await res.json();
|
|
logsEl.innerHTML = "";
|
|
for (const item of data.logs) {
|
|
const line = document.createElement("div");
|
|
line.className = "line";
|
|
line.innerHTML = `<span class="time">[${new Date(item.time).toLocaleTimeString()}]</span> ${item.message}`;
|
|
logsEl.appendChild(line);
|
|
}
|
|
logsEl.scrollTop = logsEl.scrollHeight;
|
|
} catch {
|
|
setStatus("could not fetch logs");
|
|
}
|
|
}
|
|
|
|
openBtn.addEventListener("click", openSpawner);
|
|
stopBtn.addEventListener("click", stopSpawner);
|
|
refreshLogs();
|
|
setInterval(refreshLogs, 1000);
|
|
</script>
|
|
</body>
|
|
</html>
|