Files

422 lines
11 KiB
Plaintext

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Windrose Server Manager</title>
<style>
body {
margin: 0;
font-family: sans-serif;
background: #1e1e1e;
color: #eee;
}
header {
background: #2b2b2b;
padding: 12px 20px;
font-size: 20px;
font-weight: bold;
}
#container {
display: flex;
height: calc(100vh - 50px);
}
#sidebar {
width: 240px;
background: #252525;
border-right: 1px solid #333;
padding: 10px;
}
#sidebar button {
width: 100%;
margin-bottom: 8px;
padding: 10px;
background: #333;
color: #eee;
border: none;
cursor: pointer;
}
#sidebar button:hover {
background: #444;
}
#content {
flex: 1;
padding: 20px;
overflow-y: auto;
}
table {
width: 100%;
border-collapse: collapse;
margin-top: 10px;
}
th, td {
padding: 8px;
border-bottom: 1px solid #444;
}
.section-title {
font-size: 22px;
margin-bottom: 10px;
}
.btn {
padding: 6px 10px;
background: #444;
color: #eee;
border: none;
cursor: pointer;
margin-right: 6px;
}
.btn:hover {
background: #555;
}
.checkbox {
margin-left: 10px;
}
pre {
background: #111;
padding: 10px;
border: 1px solid #333;
overflow-x: auto;
max-height: 70vh;
}
textarea {
background: #111;
color: #eee;
border: 1px solid #333;
padding: 10px;
width: 100%;
height: 200px;
font-family: monospace;
font-size: 14px;
}
.create-box {
border: 1px solid #444;
padding: 12px;
margin-bottom: 20px;
background: #2a2a2a;
}
.input {
width: 260px;
padding: 6px;
background: #111;
color: #eee;
border: 1px solid #333;
margin-top: 4px;
margin-bottom: 12px;
}
</style>
</head>
<body>
<header>Windrose Server Manager</header>
<div id="container">
<div id="sidebar">
<button onclick="showServers()">Servers</button>
<button onclick="showEvents()">Crash Events</button>
<button onclick="showLogs()">Logs</button>
<button onclick="showConfig()">Config</button>
</div>
<div id="content">
<!-- Dynamic content goes here -->
</div>
</div>
<script>
async function api(path, options = {}) {
const res = await fetch(path, {
headers: { "Content-Type": "application/json" },
...options
});
return res.json();
}
// ---------------------------------------------------------
// CREATE SERVER
// ---------------------------------------------------------
async function createServer() {
const id = document.getElementById("newServerId").value.trim();
const name = document.getElementById("newServerName").value.trim();
const statusEl = document.getElementById("createServerStatus");
if (!id) {
statusEl.style.color = "red";
statusEl.textContent = "Server ID is required.";
return;
}
try {
const res = await fetch(`/servers`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ id, name: name || null })
});
if (!res.ok) {
const err = await res.text();
statusEl.style.color = "red";
statusEl.textContent = "Error: " + err;
return;
}
statusEl.style.color = "#4CAF50";
statusEl.textContent = "Server created successfully.";
document.getElementById("newServerId").value = "";
document.getElementById("newServerName").value = "";
showServers();
} catch (e) {
statusEl.style.color = "red";
statusEl.textContent = "Request failed: " + e.message;
}
}
// ---------------------------------------------------------
// SERVERS PAGE
// ---------------------------------------------------------
async function showServers() {
const servers = await api('/servers');
const html = `
<div class="section-title">Servers</div>
<div class="create-box">
<h3>Create New Server</h3>
<label>Server ID</label><br>
<input id="newServerId" class="input" type="text" placeholder="myserver01"><br>
<label>Server Name (optional)</label><br>
<input id="newServerName" class="input" type="text" placeholder="My Windrose Server"><br>
<button class="btn" onclick="createServer()">Create Server</button>
<div id="createServerStatus" style="margin-top:10px;"></div>
</div>
<table>
<tr>
<th>ID</th>
<th>Name</th>
<th>Status</th>
<th>Version</th>
<th>Auto-Restart</th>
<th>Actions</th>
</tr>
${servers.map(s => `
<tr>
<td>${s.id}</td>
<td>${s.name}</td>
<td>${s.process?.status ?? 'stopped'}</td>
<td>${s.version?.buildId ?? '—'}</td>
<td>
<input type="checkbox" ${s.autoRestart ? 'checked' : ''}
onchange="toggleAutoRestart('${s.id}', this.checked)">
</td>
<td>
<button class="btn" onclick="startServer('${s.id}')">Start</button>
<button class="btn" onclick="stopServer('${s.id}')">Stop</button>
<button class="btn" onclick="restartServer('${s.id}')">Restart</button>
</td>
</tr>
`).join('')}
</table>
`;
document.getElementById('content').innerHTML = html;
}
async function startServer(id) {
await api(`/servers/${id}/start`, { method: 'POST' });
showServers();
}
async function stopServer(id) {
await api(`/servers/${id}/stop`, { method: 'POST' });
showServers();
}
async function restartServer(id) {
await api(`/servers/${id}/restart`, { method: 'POST' });
showServers();
}
async function toggleAutoRestart(id, enabled) {
await api(`/servers/${id}/auto-restart`, {
method: 'POST',
body: JSON.stringify({ enabled })
});
showServers();
}
// ---------------------------------------------------------
// CRASH EVENTS
// ---------------------------------------------------------
async function showEvents() {
const servers = await api('/servers');
let html = `<div class="section-title">Crash Events</div>`;
for (const s of servers) {
const events = await api(`/servers/${s.id}/events`);
html += `
<h3>${s.name} (${s.id})</h3>
<pre>${JSON.stringify(events, null, 2)}</pre>
`;
}
document.getElementById('content').innerHTML = html;
}
// ---------------------------------------------------------
// LIVE LOGS VIEWER
// ---------------------------------------------------------
let logEventSource = null;
async function showLogs() {
const servers = await api('/servers');
const html = `
<div class="section-title">Live Logs Viewer</div>
<p>Select a server and log type to stream logs in real time.</p>
<label>Server:</label>
<select id="logServerSelect" onchange="startLogStream()">
<option value="">-- choose server --</option>
${servers.map(s => `<option value="${s.id}">${s.name}</option>`).join('')}
</select>
<label class="checkbox">Log Type:</label>
<select id="logTypeSelect" onchange="startLogStream()">
<option value="stdout">stdout</option>
<option value="stderr">stderr</option>
</select>
<pre id="logOutput">(no logs yet)</pre>
`;
document.getElementById('content').innerHTML = html;
}
function startLogStream() {
const serverId = document.getElementById('logServerSelect').value;
const type = document.getElementById('logTypeSelect').value;
const output = document.getElementById('logOutput');
if (!serverId) {
output.textContent = '(no server selected)';
return;
}
if (logEventSource) {
logEventSource.close();
}
logEventSource = new EventSource(`/servers/${serverId}/logs/stream?type=${type}`);
output.textContent = '';
logEventSource.onmessage = (event) => {
output.textContent += event.data + '\n';
output.scrollTop = output.scrollHeight;
};
logEventSource.onerror = () => {
output.textContent += '\n[stream disconnected]\n';
};
}
// ---------------------------------------------------------
// CONFIG EDITOR UI
// ---------------------------------------------------------
async function showConfig() {
const servers = await api('/servers');
const html = `
<div class="section-title">Config Editor</div>
<label>Select Server:</label>
<select id="configServerSelect" onchange="loadConfigEditor()">
<option value="">-- choose server --</option>
${servers.map(s => `<option value="${s.id}">${s.name}</option>`).join('')}
</select>
<div id="configEditor" style="margin-top:20px;"></div>
`;
document.getElementById('content').innerHTML = html;
}
async function loadConfigEditor() {
const serverId = document.getElementById("configServerSelect").value;
const editor = document.getElementById("configEditor");
if (!serverId) {
editor.innerHTML = "";
return;
}
const cfg = await api(`/servers/${serverId}/config`);
editor.innerHTML = `
<h3>Editing Config for: ${serverId}</h3>
<h4>server.json</h4>
<textarea id="serverCfg">${JSON.stringify(cfg.serverCfg, null, 2)}</textarea>
<h4>world.json</h4>
<textarea id="worldCfg">${JSON.stringify(cfg.worldCfg, null, 2)}</textarea>
<button class="btn" onclick="saveConfig('${serverId}')">Save Config</button>
<div id="configSaveStatus" style="margin-top:10px;"></div>
`;
}
async function saveConfig(serverId) {
const serverCfgText = document.getElementById("serverCfg").value;
const worldCfgText = document.getElementById("worldCfg").value;
const status = document.getElementById("configSaveStatus");
let serverCfg, worldCfg;
try {
serverCfg = JSON.parse(serverCfgText);
worldCfg = JSON.parse(worldCfgText);
} catch (err) {
status.style.color = "red";
status.textContent = "Invalid JSON: " + err.message;
return;
}
try {
const res = await fetch(`/servers/${serverId}/config`, {
method: "PUT",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ serverCfg, worldCfg })
});
if (!res.ok) {
const err = await res.text();
status.style.color = "red";
status.textContent = "Error: " + err;
return;
}
status.style.color = "#4CAF50";
status.textContent = "Config saved successfully.";
} catch (err) {
status.style.color = "red";
status.textContent = "Request failed: " + err.message;
}
}
// Load servers on startup
showServers();
</script>
</body>
</html>