modified: web/app.py

modified:   web/blueprints/auth.py
	modified:   web/blueprints/group_admin.py
	modified:   web/blueprints/panel.py
	modified:   web/blueprints/site_admin.py
	modified:   web/config.py
	new file:   web/templates/404.html
	modified:   web/templates/admin/base.html
	modified:   web/templates/admin/group_edit.html
	modified:   web/templates/admin/group_members.html
	modified:   web/templates/admin/groups.html
	modified:   web/templates/admin/user_edit.html
	modified:   web/templates/admin/users.html
	modified:   web/templates/auth/admin_login.html
	modified:   web/templates/auth/login.html
	modified:   web/templates/base.html
	modified:   web/templates/group_admin/base.html
	modified:   web/templates/group_admin/database.html
	modified:   web/templates/group_admin/member_edit.html
	modified:   web/templates/group_admin/members.html
	modified:   web/templates/login.html
	modified:   web/templates/panel/dashboard.html
This commit is contained in:
simon
2026-04-13 09:55:50 +02:00
parent 486aa2ff18
commit 935dc3f909
22 changed files with 260 additions and 50 deletions

76
web/templates/404.html Normal file
View File

@@ -0,0 +1,76 @@
<!DOCTYPE html>
<html lang="en" data-bs-theme="dark">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>404 - Not Found</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css">
<style>
body {
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
background: radial-gradient(circle at top right, #1b2235 0%, #0d1117 55%, #090c12 100%);
color: #e5e7eb;
}
.error-shell {
width: min(760px, 92vw);
}
.error-card {
background: rgba(23, 27, 40, 0.9);
border: 1px solid #2a3249;
border-radius: 16px;
box-shadow: 0 20px 48px rgba(0, 0, 0, 0.35);
}
.path-chip {
background: rgba(148, 163, 184, 0.14);
border: 1px solid rgba(148, 163, 184, 0.35);
border-radius: 10px;
color: #cbd5e1;
font-family: Consolas, 'Cascadia Code', monospace;
font-size: 0.85rem;
padding: 0.45rem 0.65rem;
word-break: break-all;
}
</style>
</head>
<body>
<div class="error-shell">
<div class="error-card p-4 p-md-5">
<div class="d-flex align-items-center gap-3 mb-3">
<i class="bi bi-signpost-split-fill text-warning" style="font-size: 2.2rem;"></i>
<div>
<h1 class="h3 mb-1">404 - Seite nicht gefunden</h1>
<p class="text-secondary mb-0">Die angeforderte Route existiert nicht oder wurde verschoben.</p>
</div>
</div>
<div class="mb-3">
<div class="small text-secondary mb-1">Anfrage</div>
<div class="path-chip">{{ request_method }} {{ requested_path }}</div>
</div>
<div class="mb-4">
{% if not is_logged_in %}
<p class="mb-0 text-secondary">Du bist aktuell nicht eingeloggt. Starte am besten ueber die Login-Seite.</p>
{% elif is_site_admin and not session.get('group_id') %}
<p class="mb-0 text-secondary">Du bist als Site Admin eingeloggt. Von dort kannst du Gruppen und Benutzer verwalten.</p>
{% elif role == 'admin' %}
<p class="mb-0 text-secondary">Du bist Gruppen-Admin. Nutze Panel oder Group-Admin, um wieder in gueltige Bereiche zu kommen.</p>
{% else %}
<p class="mb-0 text-secondary">Nutze das Dashboard, um wieder in bekannte Bereiche zu navigieren.</p>
{% endif %}
</div>
<div class="d-flex flex-wrap gap-2">
{% for link in links %}
<a href="{{ link.href }}" class="btn {{ link.btn }}">{{ link.label }}</a>
{% endfor %}
<a href="javascript:history.back()" class="btn btn-outline-secondary">Zurueck</a>
</div>
</div>
</div>
</body>
</html>

View File

@@ -18,9 +18,12 @@
<a href="{{ url_for('site_admin.dashboard') }}" class="nav-link text-white {{ 'fw-bold' if request.endpoint == 'site_admin.dashboard' }}">Dashboard</a>
<a href="{{ url_for('site_admin.groups') }}" class="nav-link text-white {{ 'fw-bold' if request.endpoint == 'site_admin.groups' }}">Groups</a>
<a href="{{ url_for('site_admin.users') }}" class="nav-link text-white {{ 'fw-bold' if request.endpoint == 'site_admin.users' }}">Users</a>
<a href="{{ url_for('auth.logout') }}" class="btn btn-outline-light btn-sm">
<i class="bi bi-box-arrow-right"></i> Logout
</a>
<form method="post" action="{{ url_for('auth.logout') }}" class="d-inline">
<input type="hidden" name="_csrf_token" value="{{ csrf_token() }}">
<button type="submit" class="btn btn-outline-light btn-sm">
<i class="bi bi-box-arrow-right"></i> Logout
</button>
</form>
</div>
</div>
</nav>

View File

@@ -13,6 +13,7 @@
<div class="card border-secondary">
<div class="card-body">
<form method="post">
<input type="hidden" name="_csrf_token" value="{{ csrf_token() }}">
<div class="mb-3">
<label class="form-label">Group Name *</label>
<input type="text" name="name" class="form-control" required

View File

@@ -29,12 +29,14 @@
</td>
<td class="text-end">
<form method="post" action="{{ url_for('site_admin.group_member_toggle_role', group_id=group.id, user_id=m.id) }}" class="d-inline">
<input type="hidden" name="_csrf_token" value="{{ csrf_token() }}">
<button type="submit" class="btn btn-sm btn-outline-warning" title="Toggle role">
<i class="bi bi-arrow-left-right"></i>
</button>
</form>
<form method="post" action="{{ url_for('site_admin.group_member_remove', group_id=group.id, user_id=m.id) }}" class="d-inline"
onsubmit="return confirm('Remove {{ m.username }} from group?')">
<input type="hidden" name="_csrf_token" value="{{ csrf_token() }}">
<button type="submit" class="btn btn-sm btn-outline-danger" title="Remove">
<i class="bi bi-person-dash"></i>
</button>
@@ -57,6 +59,7 @@
<div class="card-body">
{% if non_members %}
<form method="post" action="{{ url_for('site_admin.group_member_add', group_id=group.id) }}">
<input type="hidden" name="_csrf_token" value="{{ csrf_token() }}">
<div class="mb-3">
<label class="form-label">Select User</label>
<select name="user_id" class="form-select">

View File

@@ -32,9 +32,12 @@
</td>
<td class="text-muted small">{{ g.created_at | fmt_dt }}</td>
<td class="text-end">
<a href="{{ url_for('site_admin.view_group', group_id=g.id) }}" class="btn btn-sm btn-outline-info" title="Browse data">
<i class="bi bi-eye"></i>
</a>
<form method="post" action="{{ url_for('site_admin.view_group', group_id=g.id) }}" class="d-inline">
<input type="hidden" name="_csrf_token" value="{{ csrf_token() }}">
<button type="submit" class="btn btn-sm btn-outline-info" title="Browse data">
<i class="bi bi-eye"></i>
</button>
</form>
<a href="{{ url_for('site_admin.group_members', group_id=g.id) }}" class="btn btn-sm btn-outline-secondary" title="Members">
<i class="bi bi-people-fill"></i>
</a>
@@ -43,6 +46,7 @@
</a>
<form method="post" action="{{ url_for('site_admin.group_delete', group_id=g.id) }}" class="d-inline"
onsubmit="return confirm('Delete group {{ g.name }}?')">
<input type="hidden" name="_csrf_token" value="{{ csrf_token() }}">
<button type="submit" class="btn btn-sm btn-outline-danger" title="Delete">
<i class="bi bi-trash3"></i>
</button>

View File

@@ -13,6 +13,7 @@
<div class="card border-secondary">
<div class="card-body">
<form method="post">
<input type="hidden" name="_csrf_token" value="{{ csrf_token() }}">
<div class="mb-3">
<label class="form-label">Username *</label>
<input type="text" name="username" class="form-control" required

View File

@@ -37,6 +37,7 @@
</a>
<form method="post" action="{{ url_for('site_admin.user_delete', user_id=u.id) }}" class="d-inline"
onsubmit="return confirm('Delete user {{ u.username }}?')">
<input type="hidden" name="_csrf_token" value="{{ csrf_token() }}">
<button type="submit" class="btn btn-sm btn-outline-danger" title="Delete">
<i class="bi bi-trash3"></i>
</button>

View File

@@ -30,6 +30,7 @@
<div class="card border-danger">
<div class="card-body">
<form method="post">
<input type="hidden" name="_csrf_token" value="{{ csrf_token() }}">
<div class="mb-3">
<label class="form-label">Username</label>
<div class="input-group">

View File

@@ -30,6 +30,7 @@
<div class="card border-secondary">
<div class="card-body">
<form method="post">
<input type="hidden" name="_csrf_token" value="{{ csrf_token() }}">
<div class="mb-3">
<label class="form-label">Username</label>
<div class="input-group">

View File

@@ -103,10 +103,13 @@
<div class="mb-2 sidebar-hide-collapsed">
<small class="text-muted">Switch group:</small>
{% for g in user_groups %}
<a href="{{ url_for('auth.switch_group', group_id=g.id) }}"
class="btn btn-sm w-100 mt-1 {{ 'btn-success' if g.id == session.get('group_id') else 'btn-outline-secondary' }}">
{{ g.name }}
</a>
<form method="post" action="{{ url_for('auth.switch_group', group_id=g.id) }}" class="mt-1">
<input type="hidden" name="_csrf_token" value="{{ csrf_token() }}">
<button type="submit"
class="btn btn-sm w-100 {{ 'btn-success' if g.id == session.get('group_id') else 'btn-outline-secondary' }}">
{{ g.name }}
</button>
</form>
{% endfor %}
</div>
{% endif %}
@@ -119,9 +122,12 @@
{% endif %}
{% if session.get('is_site_admin') %}
{% if session.get('admin_viewing') %}
<a href="{{ url_for('site_admin.stop_view') }}" class="btn btn-warning btn-sm mb-1">
<i class="bi bi-arrow-left"></i> <span>Back to Admin</span>
</a>
<form method="post" action="{{ url_for('site_admin.stop_view') }}" class="mb-1">
<input type="hidden" name="_csrf_token" value="{{ csrf_token() }}">
<button type="submit" class="btn btn-warning btn-sm w-100">
<i class="bi bi-arrow-left"></i> <span>Back to Admin</span>
</button>
</form>
{% else %}
<a href="{{ url_for('site_admin.dashboard') }}" class="btn btn-outline-danger btn-sm mb-1">
<i class="bi bi-shield-fill"></i> <span>Site Admin</span>
@@ -135,9 +141,12 @@
<span id="online-count"></span> <span class="sidebar-hide-collapsed">Online</span>
</div>
<small class="text-muted d-block mb-1 sidebar-hide-collapsed">{{ session.get('username', '') }}</small>
<a href="{{ url_for('auth.logout') }}" class="btn btn-outline-danger btn-sm w-100">
<i class="bi bi-box-arrow-right"></i> <span>Logout</span>
</a>
<form method="post" action="{{ url_for('auth.logout') }}">
<input type="hidden" name="_csrf_token" value="{{ csrf_token() }}">
<button type="submit" class="btn btn-outline-danger btn-sm w-100">
<i class="bi bi-box-arrow-right"></i> <span>Logout</span>
</button>
</form>
</div>
</nav>

View File

@@ -21,9 +21,12 @@
<a href="{{ url_for('panel.dashboard') }}" class="btn btn-outline-dark btn-sm">
<i class="bi bi-grid me-1"></i>Panel
</a>
<a href="{{ url_for('auth.logout') }}" class="btn btn-dark btn-sm">
<i class="bi bi-box-arrow-right"></i>
</a>
<form method="post" action="{{ url_for('auth.logout') }}" class="d-inline">
<input type="hidden" name="_csrf_token" value="{{ csrf_token() }}">
<button type="submit" class="btn btn-dark btn-sm">
<i class="bi bi-box-arrow-right"></i>
</button>
</form>
</div>
</div>
</nav>

View File

@@ -15,6 +15,7 @@
{% endif %}
<form method="post">
<input type="hidden" name="_csrf_token" value="{{ csrf_token() }}">
<div class="row g-3">
<div class="col-md-8">
<label class="form-label">Host *</label>
@@ -48,18 +49,22 @@
</div>
</div>
<div class="d-flex gap-2 mt-4">
<div class="mt-4">
<button type="submit" name="action" value="test_save" class="btn btn-success">
<i class="bi bi-plug-fill me-1"></i>Test & Save
</button>
{% if creds %}
<button type="submit" name="action" value="delete" class="btn btn-outline-danger"
onclick="return confirm('Delete DB configuration?')">
<i class="bi bi-trash3 me-1"></i>Remove
</button>
{% endif %}
</div>
</form>
{% if creds %}
<form method="post" action="{{ url_for('group_admin.database_delete') }}" class="d-inline">
<input type="hidden" name="_csrf_token" value="{{ csrf_token() }}">
<button type="submit" class="btn btn-outline-danger mt-2"
onclick="return confirm('Delete DB configuration?')">
<i class="bi bi-trash3 me-1"></i>Remove
</button>
</form>
{% endif %}
</div>
</div>
</div>
@@ -79,13 +84,13 @@
<p class="small text-muted mb-1"><strong>Required tables:</strong></p>
<ul class="small text-muted">
<li>player_sessions</li>
<li>chat_messages</li>
<li>player_chat</li>
<li>player_commands</li>
<li>block_events</li>
<li>player_deaths</li>
<li>proxy_events</li>
<li>server_events</li>
<li>permission_changes</li>
<li>plugin_events</li>
</ul>
</div>
</div>

View File

@@ -16,6 +16,7 @@
</div>
<div class="card-body">
<form method="post">
<input type="hidden" name="_csrf_token" value="{{ csrf_token() }}">
<div class="mb-3">
<label class="form-label">Role</label>
<select name="role" class="form-select">
@@ -33,7 +34,7 @@
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" role="switch"
name="perm_{{ key }}" id="perm_{{ key }}"
{{ 'checked' if perms.get(key, True) }}>
{{ 'checked' if current_perms.get(key, True) }}>
<label class="form-check-label" for="perm_{{ key }}">{{ label }}</label>
</div>
</div>

View File

@@ -29,6 +29,7 @@
</a>
<form method="post" action="{{ url_for('group_admin.member_remove', user_id=m.id) }}" class="d-inline"
onsubmit="return confirm('Remove {{ m.username }}?')">
<input type="hidden" name="_csrf_token" value="{{ csrf_token() }}">
<button type="submit" class="btn btn-sm btn-outline-danger" title="Remove">
<i class="bi bi-person-dash"></i>
</button>

View File

@@ -24,6 +24,7 @@
{% endif %}
<form method="post">
<input type="hidden" name="_csrf_token" value="{{ csrf_token() }}">
<div class="mb-3">
<label class="form-label">Username</label>
<div class="input-group">

View File

@@ -38,7 +38,7 @@
<div class="card h-100">
<div class="card-header d-flex justify-content-between align-items-center">
<span><i class="bi bi-circle-fill text-success me-2 blink" style="font-size:.5rem"></i>Online Players</span>
<button class="btn btn-sm btn-outline-secondary" onclick="refreshOnline()">
<button class="btn btn-sm btn-outline-secondary" onclick="updateOnlineCount()">
<i class="bi bi-arrow-clockwise"></i>
</button>
</div>
@@ -183,12 +183,5 @@ new Chart(deathCtx, {
},
options: { plugins: { legend: { position: 'bottom', labels: { font: { size:10 } } } } }
});
function refreshOnline() {
fetch('/api/online').then(r => r.json()).then(data => {
document.getElementById('online-count').textContent = data.length;
});
}
setInterval(refreshOnline, 30000);
refreshOnline();
</script>
{% endblock %}