Add server control (start/stop/restart, live logs, broadcast, kick) to webadmin
Docker SDK drives login/char/worldserver containers via docker.sock (mounted read-only into webadmin); live logs stream over SSE with nginx buffering disabled so lines arrive in real time. Broadcast/kick go through a new admin_queue DB table that the world server polls every ~2s in its existing WorldProcess tick loop, avoiding a second IPC channel to the game server.
This commit is contained in:
1 parent
3554c85f2f
commit
15e562cb04
11 files changed
+331
-2
No files matched your search
@@ -0,0 +1,103 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}Server – ROSS Admin{% endblock %}
|
||||
{% block content %}
|
||||
<h2>Serversteuerung</h2>
|
||||
|
||||
<div class="card">
|
||||
<table>
|
||||
<thead><tr><th>Dienst</th><th>Status</th><th>Gestartet</th><th></th></tr></thead>
|
||||
<tbody>
|
||||
{% for service in services %}
|
||||
<tr>
|
||||
<td>{{ service }}</td>
|
||||
<td>
|
||||
{% set st = statuses[service]['status'] %}
|
||||
<span class="badge {{ 'ok' if st == 'running' else 'off' }}">{{ st }}</span>
|
||||
</td>
|
||||
<td>{{ statuses[service]['started_at'] or '-' }}</td>
|
||||
<td class="row-actions">
|
||||
<form class="inline" method="post" action="{{ url_for('server_action', service=service, action='start') }}">
|
||||
<button type="submit">Start</button>
|
||||
</form>
|
||||
<form class="inline" method="post" action="{{ url_for('server_action', service=service, action='restart') }}">
|
||||
<button type="submit" class="secondary">Restart</button>
|
||||
</form>
|
||||
<form class="inline" method="post" action="{{ url_for('server_action', service=service, action='stop') }}"
|
||||
onsubmit="return confirm('{{ service }} wirklich stoppen?');">
|
||||
<button type="submit" class="secondary" style="background:var(--danger)">Stop</button>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3 style="margin-top:0">Live-Logs</h3>
|
||||
<div class="service-tabs">
|
||||
{% for service in services %}
|
||||
<button type="button" class="tab {{ 'active' if loop.first else '' }}" data-service="{{ service }}" onclick="switchLog('{{ service }}')">{{ service }}</button>
|
||||
{% endfor %}
|
||||
</div>
|
||||
<div id="logbox" class="logbox"></div>
|
||||
</div>
|
||||
|
||||
<div class="grid">
|
||||
<div class="card">
|
||||
<h3 style="margin-top:0">Broadcast</h3>
|
||||
<form method="post" action="{{ url_for('server_broadcast') }}">
|
||||
<label>Nachricht an alle Spieler</label>
|
||||
<input type="text" name="message" maxlength="250" required>
|
||||
<div style="margin-top:12px"><button type="submit">Senden</button></div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3 style="margin-top:0">Online-Spieler</h3>
|
||||
{% if online %}
|
||||
<table>
|
||||
<thead><tr><th>Account</th><th>Charakter</th><th></th></tr></thead>
|
||||
<tbody>
|
||||
{% for row in online %}
|
||||
<tr>
|
||||
<td>{{ row['username'] }}</td>
|
||||
<td>{{ row['lastchar'] }}</td>
|
||||
<td class="row-actions">
|
||||
<form class="inline" method="post" action="{{ url_for('server_kick') }}"
|
||||
onsubmit="return confirm('{{ row['lastchar'] }} kicken?');">
|
||||
<input type="hidden" name="charname" value="{{ row['lastchar'] }}">
|
||||
<button type="submit" class="secondary" style="background:var(--danger)">Kick</button>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% else %}
|
||||
<p style="color:var(--muted)">Niemand online.</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
let currentSource = null;
|
||||
function switchLog(service) {
|
||||
document.querySelectorAll('.service-tabs .tab').forEach(function(btn) {
|
||||
btn.classList.toggle('active', btn.dataset.service === service);
|
||||
});
|
||||
const box = document.getElementById('logbox');
|
||||
box.textContent = '';
|
||||
if (currentSource) currentSource.close();
|
||||
currentSource = new EventSource('{{ url_for("server_logs", service="__SERVICE__") }}'.replace('__SERVICE__', service));
|
||||
currentSource.onmessage = function(ev) {
|
||||
const line = JSON.parse(ev.data);
|
||||
const atBottom = box.scrollHeight - box.scrollTop - box.clientHeight < 40;
|
||||
box.textContent += line + '\n';
|
||||
if (atBottom) box.scrollTop = box.scrollHeight;
|
||||
};
|
||||
}
|
||||
switchLog('{{ services[0] }}');
|
||||
window.addEventListener('beforeunload', function() { if (currentSource) currentSource.close(); });
|
||||
</script>
|
||||
{% endblock %}
|
||||
Reference in new issue
Block a user