initial commit: add main bot functionality, web interface, and configuration files
This commit is contained in:
+184
@@ -0,0 +1,184 @@
|
||||
<!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>
|
||||
Reference in New Issue
Block a user