new file: .gitignore
new file: README.md new file: database/schema.sql new file: paper-plugin/pom.xml new file: paper-plugin/src/main/java/de/simolzimol/mclogger/paper/PaperLoggerPlugin.java new file: paper-plugin/src/main/java/de/simolzimol/mclogger/paper/commands/MCLoggerCommand.java new file: paper-plugin/src/main/java/de/simolzimol/mclogger/paper/database/DatabaseManager.java new file: paper-plugin/src/main/java/de/simolzimol/mclogger/paper/listeners/BlockListener.java new file: paper-plugin/src/main/java/de/simolzimol/mclogger/paper/listeners/EntityListener.java new file: paper-plugin/src/main/java/de/simolzimol/mclogger/paper/listeners/InventoryListener.java new file: paper-plugin/src/main/java/de/simolzimol/mclogger/paper/listeners/LuckPermsListener.java new file: paper-plugin/src/main/java/de/simolzimol/mclogger/paper/listeners/PlayerChatCommandListener.java new file: paper-plugin/src/main/java/de/simolzimol/mclogger/paper/listeners/PlayerDeathListener.java new file: paper-plugin/src/main/java/de/simolzimol/mclogger/paper/listeners/PlayerMiscListener.java new file: paper-plugin/src/main/java/de/simolzimol/mclogger/paper/listeners/PlayerSessionListener.java new file: paper-plugin/src/main/java/de/simolzimol/mclogger/paper/listeners/WorldListener.java new file: paper-plugin/src/main/resources/config.yml new file: paper-plugin/src/main/resources/plugin.yml new file: paper-plugin/target/classes/config.yml new file: paper-plugin/target/classes/de/simolzimol/mclogger/paper/PaperLoggerPlugin.class new file: paper-plugin/target/classes/de/simolzimol/mclogger/paper/commands/MCLoggerCommand$RsConsumer.class new file: paper-plugin/target/classes/de/simolzimol/mclogger/paper/commands/MCLoggerCommand.class new file: paper-plugin/target/classes/de/simolzimol/mclogger/paper/database/DatabaseManager$ThrowingRunnable.class new file: paper-plugin/target/classes/de/simolzimol/mclogger/paper/database/DatabaseManager.class new file: paper-plugin/target/classes/de/simolzimol/mclogger/paper/listeners/BlockListener.class new file: paper-plugin/target/classes/de/simolzimol/mclogger/paper/listeners/EntityListener.class new file: paper-plugin/target/classes/de/simolzimol/mclogger/paper/listeners/InventoryListener.class new file: paper-plugin/target/classes/de/simolzimol/mclogger/paper/listeners/LuckPermsListener.class new file: paper-plugin/target/classes/de/simolzimol/mclogger/paper/listeners/PlayerChatCommandListener.class new file: paper-plugin/target/classes/de/simolzimol/mclogger/paper/listeners/PlayerDeathListener.class new file: paper-plugin/target/classes/de/simolzimol/mclogger/paper/listeners/PlayerMiscListener.class new file: paper-plugin/target/classes/de/simolzimol/mclogger/paper/listeners/PlayerSessionListener.class new file: paper-plugin/target/classes/de/simolzimol/mclogger/paper/listeners/WorldListener.class new file: paper-plugin/target/classes/plugin.yml new file: paper-plugin/target/maven-archiver/pom.properties new file: paper-plugin/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst new file: paper-plugin/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst new file: paper-plugin/target/mclogger-paper-1.0.0.jar new file: paper-plugin/target/original-mclogger-paper-1.0.0.jar new file: velocity-plugin/pom.xml new file: velocity-plugin/src/main/java/de/simolzimol/mclogger/velocity/VelocityLoggerPlugin.java new file: velocity-plugin/src/main/java/de/simolzimol/mclogger/velocity/database/VelocityDatabaseManager.java new file: velocity-plugin/src/main/java/de/simolzimol/mclogger/velocity/listeners/VelocityEventListener.java new file: velocity-plugin/src/main/resources/velocity-config.yml new file: velocity-plugin/target/classes/de/simolzimol/mclogger/velocity/VelocityLoggerPlugin.class new file: velocity-plugin/target/classes/de/simolzimol/mclogger/velocity/database/VelocityDatabaseManager$ThrowingRunnable.class new file: velocity-plugin/target/classes/de/simolzimol/mclogger/velocity/database/VelocityDatabaseManager.class new file: velocity-plugin/target/classes/de/simolzimol/mclogger/velocity/listeners/VelocityEventListener.class new file: velocity-plugin/target/classes/velocity-config.yml new file: velocity-plugin/target/classes/velocity-plugin.json new file: velocity-plugin/target/maven-archiver/pom.properties new file: velocity-plugin/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst new file: velocity-plugin/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst new file: velocity-plugin/target/mclogger-velocity-1.0.0.jar new file: velocity-plugin/target/original-mclogger-velocity-1.0.0.jar new file: web/Dockerfile new file: web/app.py new file: web/blueprints/__init__.py new file: web/blueprints/auth.py new file: web/blueprints/group_admin.py new file: web/blueprints/panel.py new file: web/blueprints/site_admin.py new file: web/config.py new file: web/crypto.py new file: web/docker-compose.yml new file: web/panel_db.py new file: web/requirements.txt new file: web/static/css/style.css new file: web/static/js/main.js new file: web/templates/_pagination.html new file: web/templates/admin/base.html new file: web/templates/admin/dashboard.html new file: web/templates/admin/group_edit.html new file: web/templates/admin/group_members.html new file: web/templates/admin/groups.html new file: web/templates/admin/user_edit.html new file: web/templates/admin/users.html new file: web/templates/auth/admin_login.html new file: web/templates/auth/login.html new file: web/templates/base.html new file: web/templates/blocks.html new file: web/templates/chat.html new file: web/templates/commands.html new file: web/templates/dashboard.html new file: web/templates/deaths.html new file: web/templates/group_admin/base.html new file: web/templates/group_admin/dashboard.html new file: web/templates/group_admin/database.html new file: web/templates/group_admin/member_edit.html new file: web/templates/group_admin/members.html new file: web/templates/login.html new file: web/templates/panel/blocks.html new file: web/templates/panel/chat.html new file: web/templates/panel/commands.html new file: web/templates/panel/dashboard.html new file: web/templates/panel/deaths.html new file: web/templates/panel/no_db.html new file: web/templates/panel/perms.html new file: web/templates/panel/player_detail.html new file: web/templates/panel/players.html new file: web/templates/panel/proxy.html new file: web/templates/panel/server_events.html new file: web/templates/panel/sessions.html new file: web/templates/perms.html new file: web/templates/player_detail.html new file: web/templates/players.html new file: web/templates/proxy.html new file: web/templates/server_events.html new file: web/templates/sessions.html
This commit is contained in:
69
web/templates/panel/blocks.html
Normal file
69
web/templates/panel/blocks.html
Normal file
@@ -0,0 +1,69 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}Block-Events{% endblock %}
|
||||
{% block page_title %}<i class="bi bi-bricks me-2"></i>Block-Events{% endblock %}
|
||||
{% block content %}
|
||||
<form method="get" class="row g-2 align-items-end mb-3">
|
||||
<div class="col-md-2">
|
||||
<select name="type" class="form-select form-select-sm">
|
||||
<option value="">All Types</option>
|
||||
{% for t in ['break','place','ignite','burn','explode','fade','grow','dispense'] %}
|
||||
<option {{ 'selected' if t == event_type }}>{{ t }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
<input type="text" name="player" class="form-control form-control-sm" placeholder="Spieler…" value="{{ player }}">
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
<input type="text" name="block" class="form-control form-control-sm" placeholder="Block-Typ…" value="{{ block }}">
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
<select name="world" class="form-select form-select-sm">
|
||||
<option value="">All Worlds</option>
|
||||
{% for w in worlds %}<option {{ 'selected' if w == world }}>{{ w }}</option>{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
<select name="server" class="form-select form-select-sm">
|
||||
<option value="">All Servers</option>
|
||||
{% for s in servers %}<option {{ 'selected' if s == server }}>{{ s }}</option>{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-auto">
|
||||
<button class="btn btn-success btn-sm">Filter</button>
|
||||
<a href="{{ url_for('panel.blocks') }}" class="btn btn-outline-secondary btn-sm ms-1">Reset</a>
|
||||
</div>
|
||||
</form>
|
||||
<div class="card">
|
||||
<div class="card-header">{{ total }} block events</div>
|
||||
<div class="card-body p-0">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-sm table-hover mb-0">
|
||||
<thead class="table-dark">
|
||||
<tr><th>Time</th><th>Type</th><th>Player</th><th>Block</th><th>World</th><th>Position</th><th>Tool</th><th>Silk</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for r in rows %}
|
||||
<tr>
|
||||
<td class="small text-muted text-nowrap">{{ r.timestamp | fmt_dt }}</td>
|
||||
<td>
|
||||
{% set colors = {'break':'danger','place':'success','ignite':'warning','burn':'orange','explode':'dark'} %}
|
||||
<span class="badge bg-{{ colors.get(r.event_type,'secondary') }}">{{ r.event_type }}</span>
|
||||
</td>
|
||||
<td class="small">{{ r.player_name or '—' }}</td>
|
||||
<td class="small font-monospace">{{ r.block_type }}</td>
|
||||
<td><span class="badge bg-secondary">{{ r.world }}</span></td>
|
||||
<td class="small text-muted">{{ r.x }}, {{ r.y }}, {{ r.z }}</td>
|
||||
<td class="small">{{ r.tool or '—' }}</td>
|
||||
<td>{% if r.is_silk %}<i class="bi bi-check-circle-fill text-success"></i>{% else %}—{% endif %}</td>
|
||||
</tr>
|
||||
{% else %}
|
||||
<tr><td colspan="8" class="text-center text-muted py-4">No block events</td></tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% include "_pagination.html" %}
|
||||
{% endblock %}
|
||||
58
web/templates/panel/chat.html
Normal file
58
web/templates/panel/chat.html
Normal file
@@ -0,0 +1,58 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}Chat Log{% endblock %}
|
||||
{% block page_title %}<i class="bi bi-chat-dots-fill me-2"></i>Chat Log{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<form method="get" class="row g-2 align-items-end mb-3">
|
||||
<div class="col-12 col-md-3">
|
||||
<label class="form-label small">Message</label>
|
||||
<input type="text" name="q" class="form-control form-control-sm" placeholder="Search…" value="{{ search }}">
|
||||
</div>
|
||||
<div class="col-12 col-md-2">
|
||||
<label class="form-label small">Server</label>
|
||||
<select name="server" class="form-select form-select-sm">
|
||||
<option value="">Alle</option>
|
||||
{% for s in servers %}<option {{ 'selected' if s == server }}>{{ s }}</option>{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-12 col-md-2">
|
||||
<label class="form-label small">From</label>
|
||||
<input type="date" name="from" class="form-control form-control-sm" value="{{ date_from }}">
|
||||
</div>
|
||||
<div class="col-12 col-md-2">
|
||||
<label class="form-label small">To</label>
|
||||
<input type="date" name="to" class="form-control form-control-sm" value="{{ date_to }}">
|
||||
</div>
|
||||
<div class="col-auto">
|
||||
<button class="btn btn-success btn-sm">Filter</button>
|
||||
<a href="{{ url_for('panel.chat') }}" class="btn btn-outline-secondary btn-sm ms-1">Reset</a>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-header">{{ total }} messages</div>
|
||||
<div class="card-body p-0">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-sm table-hover mb-0">
|
||||
<thead class="table-dark">
|
||||
<tr><th>Time</th><th>Player</th><th>Server</th><th>Channel</th><th>Message</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for r in rows %}
|
||||
<tr>
|
||||
<td class="small text-muted text-nowrap">{{ r.timestamp | fmt_dt }}</td>
|
||||
<td class="small fw-semibold">{{ r.player_name or '—' }}</td>
|
||||
<td><span class="badge bg-secondary">{{ r.server_name or '—' }}</span></td>
|
||||
<td><span class="badge bg-primary">{{ r.channel or 'global' }}</span></td>
|
||||
<td class="small">{{ r.message }}</td>
|
||||
</tr>
|
||||
{% else %}
|
||||
<tr><td colspan="5" class="text-center text-muted py-4">No messages found</td></tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% include "_pagination.html" %}
|
||||
{% endblock %}
|
||||
51
web/templates/panel/commands.html
Normal file
51
web/templates/panel/commands.html
Normal file
@@ -0,0 +1,51 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}Commands{% endblock %}
|
||||
{% block page_title %}<i class="bi bi-terminal-fill me-2"></i>Commands{% endblock %}
|
||||
{% block content %}
|
||||
<form method="get" class="row g-2 align-items-end mb-3">
|
||||
<div class="col-md-3">
|
||||
<input type="text" name="player" class="form-control form-control-sm" placeholder="Player…" value="{{ player }}">
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<input type="text" name="q" class="form-control form-control-sm" placeholder="Command text…" value="{{ search }}">
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
<select name="server" class="form-select form-select-sm">
|
||||
<option value="">All Servers</option>
|
||||
{% for s in servers %}<option {{ 'selected' if s == server }}>{{ s }}</option>{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-auto">
|
||||
<button class="btn btn-success btn-sm">Filter</button>
|
||||
<a href="{{ url_for('panel.commands') }}" class="btn btn-outline-secondary btn-sm ms-1">Reset</a>
|
||||
</div>
|
||||
</form>
|
||||
<div class="card">
|
||||
<div class="card-header">{{ total }} commands</div>
|
||||
<div class="card-body p-0">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-sm table-hover mb-0">
|
||||
<thead class="table-dark">
|
||||
<tr><th>Time</th><th>Player</th><th>Server</th><th>Command</th><th>Position</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for r in rows %}
|
||||
<tr>
|
||||
<td class="small text-muted text-nowrap">{{ r.timestamp | fmt_dt }}</td>
|
||||
<td class="small fw-semibold">{{ r.player_name or '—' }}</td>
|
||||
<td><span class="badge bg-secondary">{{ r.server_name or '—' }}</span></td>
|
||||
<td class="small font-monospace text-warning">{{ r.command }}</td>
|
||||
<td class="small text-muted">
|
||||
{% if r.world %}{{ r.world }} ({{ r.x|round(0)|int }}, {{ r.y|round(0)|int }}, {{ r.z|round(0)|int }}){% else %}—{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% else %}
|
||||
<tr><td colspan="5" class="text-center text-muted py-4">No commands</td></tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% include "_pagination.html" %}
|
||||
{% endblock %}
|
||||
194
web/templates/panel/dashboard.html
Normal file
194
web/templates/panel/dashboard.html
Normal file
@@ -0,0 +1,194 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}Dashboard{% endblock %}
|
||||
{% block page_title %}<i class="bi bi-speedometer2 me-2"></i>Dashboard{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<!-- ── Statistik-Karten ────────────────────────────────── -->
|
||||
<div class="row g-3 mb-4">
|
||||
{% set cards = [
|
||||
('Total Players', stats.players_total, 'bi-people-fill', 'success'),
|
||||
('Sessions Today', stats.sessions_today, 'bi-clock-history', 'info'),
|
||||
('Chats Today', stats.chat_today, 'bi-chat-dots-fill', 'primary'),
|
||||
('Commands Today', stats.commands_today, 'bi-terminal-fill', 'warning'),
|
||||
('Blocks Today', stats.blocks_today, 'bi-bricks', 'secondary'),
|
||||
('Deaths Today', stats.deaths_today, 'bi-heartbreak-fill', 'danger'),
|
||||
('Entity Events', stats.entity_events_today, 'bi-bug-fill', 'light'),
|
||||
('Proxy Events', stats.proxy_events_today, 'bi-diagram-3-fill', 'dark'),
|
||||
] %}
|
||||
{% for label, value, icon, color in cards %}
|
||||
<div class="col-6 col-md-3 col-xl-3">
|
||||
<div class="card stat-card h-100">
|
||||
<div class="card-body d-flex align-items-center gap-3">
|
||||
<div class="stat-icon bg-{{ color }} bg-opacity-25">
|
||||
<i class="bi {{ icon }} text-{{ color }}"></i>
|
||||
</div>
|
||||
<div>
|
||||
<div class="stat-value">{{ value | int }}</div>
|
||||
<div class="stat-label">{{ label }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
<!-- ── Zeile 2: Online-Spieler + Letzte Aktivität ────── -->
|
||||
<div class="row g-3 mb-4">
|
||||
<div class="col-12 col-lg-4">
|
||||
<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()">
|
||||
<i class="bi bi-arrow-clockwise"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div class="card-body p-0">
|
||||
<div id="online-table">
|
||||
{% if online %}
|
||||
<table class="table table-sm table-hover mb-0">
|
||||
<thead class="table-dark">
|
||||
<tr><th>Player</th><th>Server</th><th>Since</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for s in online %}
|
||||
<tr>
|
||||
<td><a href="{{ url_for('panel.player_detail', uuid=s.get('player_uuid','')) }}">{{ s.player_name }}</a></td>
|
||||
<td><span class="badge bg-secondary">{{ s.server_name }}</span></td>
|
||||
<td class="small text-muted">{{ s.login_time | fmt_dt }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% else %}
|
||||
<div class="text-center text-muted py-4">
|
||||
<i class="bi bi-moon-stars-fill fs-3"></i><br>No players online
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-12 col-lg-8">
|
||||
<div class="card h-100">
|
||||
<div class="card-header">
|
||||
<i class="bi bi-activity me-2"></i>Last 24h Activity
|
||||
</div>
|
||||
<div class="card-body p-0" style="overflow-y:auto; max-height:320px;">
|
||||
<table class="table table-sm table-hover mb-0">
|
||||
<thead class="table-dark sticky-top">
|
||||
<tr><th>Time</th><th>Type</th><th>Player</th><th>Server</th><th>Detail</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for r in recent %}
|
||||
<tr>
|
||||
<td class="small text-muted text-nowrap">{{ r.timestamp | fmt_dt }}</td>
|
||||
<td>
|
||||
{% set badge = {'chat':'primary','command':'warning','block':'secondary','death':'danger'} %}
|
||||
<span class="badge bg-{{ badge.get(r.source,'light') }}">{{ r.source }}</span>
|
||||
</td>
|
||||
<td class="small">{{ r.player_name or '—' }}</td>
|
||||
<td class="small">{{ r.server_name or '—' }}</td>
|
||||
<td class="small text-truncate" style="max-width:200px;" title="{{ r.detail }}">{{ r.detail }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ── Zeile 3: Charts ────────────────────────────────── -->
|
||||
<div class="row g-3 mb-4">
|
||||
<div class="col-12 col-md-6">
|
||||
<div class="card">
|
||||
<div class="card-header"><i class="bi bi-bricks me-2"></i>Block Events (last 7 days)</div>
|
||||
<div class="card-body">
|
||||
<canvas id="blockChart" height="200"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-12 col-md-3">
|
||||
<div class="card">
|
||||
<div class="card-header"><i class="bi bi-heartbreak-fill me-2"></i>Death Causes (7d)</div>
|
||||
<div class="card-body">
|
||||
<canvas id="deathChart" height="200"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-12 col-md-3">
|
||||
<div class="card">
|
||||
<div class="card-header"><i class="bi bi-trophy-fill me-2"></i>Top Playtime</div>
|
||||
<div class="card-body p-0" style="overflow-y:auto;max-height:240px;">
|
||||
<table class="table table-sm mb-0">
|
||||
<tbody>
|
||||
{% for p in top_players %}
|
||||
<tr>
|
||||
<td>{{ loop.index }}. {{ p.username }}</td>
|
||||
<td class="text-end text-muted small">{{ p.total_playtime_sec | fmt_duration }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ── Zeile 4: Server-Events ─────────────────────────── -->
|
||||
<div class="row g-3">
|
||||
<div class="col-12">
|
||||
<div class="card">
|
||||
<div class="card-header"><i class="bi bi-server me-2"></i>Server Events (last 24h)</div>
|
||||
<div class="card-body p-0">
|
||||
<table class="table table-sm table-hover mb-0">
|
||||
<thead class="table-dark"><tr><th>Time</th><th>Type</th><th>Server</th><th>Message</th></tr></thead>
|
||||
<tbody>
|
||||
{% for e in server_events %}
|
||||
<tr>
|
||||
<td class="small text-muted text-nowrap">{{ e.timestamp | fmt_dt }}</td>
|
||||
<td><span class="badge bg-info text-dark">{{ e.event_type }}</span></td>
|
||||
<td class="small">{{ e.server_name }}</td>
|
||||
<td class="small">{{ e.message }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block scripts %}
|
||||
<script>
|
||||
const blockCtx = document.getElementById('blockChart');
|
||||
new Chart(blockCtx, {
|
||||
type: 'bar',
|
||||
data: {
|
||||
labels: {{ block_chart | map(attribute='day') | list | tojson }},
|
||||
datasets: [{ label: 'Block-Events', data: {{ block_chart | map(attribute='cnt') | list | tojson }},
|
||||
backgroundColor: 'rgba(25,135,84,0.7)', borderColor: 'rgba(25,135,84,1)', borderWidth: 1 }]
|
||||
},
|
||||
options: { plugins: { legend: { display: false } }, scales: { y: { beginAtZero: true } } }
|
||||
});
|
||||
const deathCtx = document.getElementById('deathChart');
|
||||
new Chart(deathCtx, {
|
||||
type: 'doughnut',
|
||||
data: {
|
||||
labels: {{ death_causes | map(attribute='cause') | list | tojson }},
|
||||
datasets: [{ data: {{ death_causes | map(attribute='cnt') | list | tojson }},
|
||||
backgroundColor: ['#dc3545','#fd7e14','#ffc107','#198754','#0dcaf0','#6f42c1','#d63384','#6c757d'] }]
|
||||
},
|
||||
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 %}
|
||||
49
web/templates/panel/deaths.html
Normal file
49
web/templates/panel/deaths.html
Normal file
@@ -0,0 +1,49 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}Deaths{% endblock %}
|
||||
{% block page_title %}<i class="bi bi-heartbreak-fill me-2"></i>Deaths{% endblock %}
|
||||
{% block content %}
|
||||
<form method="get" class="row g-2 align-items-end mb-3">
|
||||
<div class="col-md-3">
|
||||
<input type="text" name="player" class="form-control form-control-sm" placeholder="Player…" value="{{ player }}">
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<select name="cause" class="form-select form-select-sm">
|
||||
<option value="">All Causes</option>
|
||||
{% for c in causes %}<option {{ 'selected' if c == cause }}>{{ c }}</option>{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-auto">
|
||||
<button class="btn btn-success btn-sm">Filter</button>
|
||||
<a href="{{ url_for('panel.deaths') }}" class="btn btn-outline-secondary btn-sm ms-1">Reset</a>
|
||||
</div>
|
||||
</form>
|
||||
<div class="card">
|
||||
<div class="card-header">{{ total }} deaths</div>
|
||||
<div class="card-body p-0">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-sm table-hover mb-0">
|
||||
<thead class="table-dark">
|
||||
<tr><th>Time</th><th>Player</th><th>Cause</th><th>Killer</th><th>Killer Type</th><th>Level</th><th>World</th><th>Death Message</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for r in rows %}
|
||||
<tr>
|
||||
<td class="small text-muted text-nowrap">{{ r.timestamp | fmt_dt }}</td>
|
||||
<td class="small fw-semibold">{{ r.player_name }}</td>
|
||||
<td><span class="badge bg-danger">{{ r.cause or '—' }}</span></td>
|
||||
<td class="small">{{ r.killer_name or '—' }}</td>
|
||||
<td class="small text-muted">{{ r.killer_type or '—' }}</td>
|
||||
<td class="small">{{ r.exp_level }}</td>
|
||||
<td><span class="badge bg-secondary">{{ r.world }}</span></td>
|
||||
<td class="small text-muted">{{ r.death_message or '—' }}</td>
|
||||
</tr>
|
||||
{% else %}
|
||||
<tr><td colspan="8" class="text-center text-muted py-4">No deaths</td></tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% include "_pagination.html" %}
|
||||
{% endblock %}
|
||||
24
web/templates/panel/no_db.html
Normal file
24
web/templates/panel/no_db.html
Normal file
@@ -0,0 +1,24 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}No Database{% endblock %}
|
||||
{% block page_title %}<i class="bi bi-database-fill-x me-2"></i>Keine Datenbank{% endblock %}
|
||||
{% block content %}
|
||||
<div class="row justify-content-center mt-5">
|
||||
<div class="col-md-6 text-center">
|
||||
<i class="bi bi-database-fill-x display-1 text-muted mb-4"></i>
|
||||
<h3 class="mb-3">Keine Datenbank konfiguriert</h3>
|
||||
<p class="text-muted mb-4">
|
||||
Für diese Gruppe ist noch keine MC-Datenbank eingerichtet.
|
||||
{% if session.get('role') == 'admin' %}
|
||||
Du kannst die Verbindung als Gruppen-Admin konfigurieren.
|
||||
{% else %}
|
||||
Bitte wende dich an deinen Gruppenadmin.
|
||||
{% endif %}
|
||||
</p>
|
||||
{% if session.get('role') == 'admin' %}
|
||||
<a href="{{ url_for('group_admin.database') }}" class="btn btn-success btn-lg">
|
||||
<i class="bi bi-database-fill-gear me-2"></i>Datenbank konfigurieren
|
||||
</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
64
web/templates/panel/perms.html
Normal file
64
web/templates/panel/perms.html
Normal file
@@ -0,0 +1,64 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}Permissions{% endblock %}
|
||||
{% block page_title %}<i class="bi bi-shield-lock-fill me-2"></i>Permissions{% endblock %}
|
||||
{% block content %}
|
||||
<form method="get" class="row g-2 align-items-end mb-3">
|
||||
<div class="col-md-3">
|
||||
<input type="text" name="player" class="form-control form-control-sm" placeholder="Target player…" value="{{ player }}">
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
<select name="plugin" class="form-select form-select-sm">
|
||||
<option value="">All Plugins</option>
|
||||
{% for pl in plugins %}<option {{ 'selected' if pl == plugin_filter }}>{{ pl }}</option>{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<input type="text" name="type" class="form-control form-control-sm" placeholder="Event type…" value="{{ etype }}">
|
||||
</div>
|
||||
<div class="col-auto">
|
||||
<button class="btn btn-success btn-sm">Filter</button>
|
||||
<a href="{{ url_for('panel.perms') }}" class="btn btn-outline-secondary btn-sm ms-1">Reset</a>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-header">{{ total }} permission events</div>
|
||||
<div class="card-body p-0">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-sm table-hover mb-0">
|
||||
<thead class="table-dark">
|
||||
<tr><th>Time</th><th>Plugin</th><th>Event Type</th><th>Target Player</th><th>Actor</th><th>Target Type</th><th>Target ID</th><th>Action</th><th>Server</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for r in rows %}
|
||||
{% set badge_colors = {
|
||||
'luckperms_permission_set': 'success',
|
||||
'luckperms_permission_unset': 'danger',
|
||||
'luckperms_parent_add': 'primary',
|
||||
'luckperms_parent_remove': 'warning',
|
||||
'luckperms_meta_set': 'info',
|
||||
'luckperms_meta_unset': 'secondary',
|
||||
'luckperms_group_create': 'light',
|
||||
'luckperms_group_delete': 'dark',
|
||||
} %}
|
||||
<tr>
|
||||
<td class="small text-muted text-nowrap">{{ r.timestamp | fmt_dt }}</td>
|
||||
<td><span class="badge bg-secondary">{{ r.plugin_name or '—' }}</span></td>
|
||||
<td><span class="badge bg-{{ badge_colors.get(r.event_type,'secondary') }} text-wrap text-start" style="font-size:.7rem;">{{ r.event_type }}</span></td>
|
||||
<td class="small fw-semibold">{{ r.player_name or '—' }}</td>
|
||||
<td class="small">{{ r.actor_name or '—' }}</td>
|
||||
<td class="small text-muted">{{ r.target_type or '—' }}</td>
|
||||
<td class="small text-muted text-truncate" style="max-width:120px;" title="{{ r.target_id }}">{{ r.target_id or '—' }}</td>
|
||||
<td class="small text-truncate" style="max-width:200px;" title="{{ r.action }}">{{ r.action or '—' }}</td>
|
||||
<td><span class="badge bg-dark">{{ r.server_name or '—' }}</span></td>
|
||||
</tr>
|
||||
{% else %}
|
||||
<tr><td colspan="9" class="text-center text-muted py-4">No permission events found</td></tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% include "_pagination.html" %}
|
||||
{% endblock %}
|
||||
142
web/templates/panel/player_detail.html
Normal file
142
web/templates/panel/player_detail.html
Normal file
@@ -0,0 +1,142 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}{{ player.username }}{% endblock %}
|
||||
{% block page_title %}<i class="bi bi-person-fill me-2"></i>{{ player.username }}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="row g-3 mb-4">
|
||||
<div class="col-12 col-md-4">
|
||||
<div class="card h-100">
|
||||
<div class="card-body text-center py-4">
|
||||
<img src="https://minotar.net/avatar/{{ player.username }}/80"
|
||||
class="rounded mb-3" alt="{{ player.username }}" onerror="this.src='/static/img/default.png'">
|
||||
<h5 class="fw-bold mb-1">{{ player.username }}</h5>
|
||||
{% if player.is_op %}
|
||||
<span class="badge bg-warning text-dark mb-2"><i class="bi bi-shield-fill"></i> OP</span>
|
||||
{% endif %}
|
||||
<table class="table table-sm mt-2 text-start">
|
||||
<tr><th>UUID</th><td class="small text-break">{{ player.uuid }}</td></tr>
|
||||
<tr><th>IP</th><td class="small">{{ player.ip_address or '—' }}</td></tr>
|
||||
<tr><th>Locale</th><td class="small">{{ player.locale or '—' }}</td></tr>
|
||||
<tr><th>Playtime</th><td>{{ player.total_playtime_sec | fmt_duration }}</td></tr>
|
||||
<tr><th>Since</th><td class="small">{{ player.first_seen | fmt_dt }}</td></tr>
|
||||
<tr><th>Last Seen</th><td class="small">{{ player.last_seen | fmt_dt }}</td></tr>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-12 col-md-8">
|
||||
<ul class="nav nav-tabs mb-3" id="playerTabs">
|
||||
<li class="nav-item"><a class="nav-link active" data-bs-toggle="tab" href="#tab-sessions">Sessions ({{ sessions|length }})</a></li>
|
||||
<li class="nav-item"><a class="nav-link" data-bs-toggle="tab" href="#tab-chat">Chat ({{ chat|length }})</a></li>
|
||||
<li class="nav-item"><a class="nav-link" data-bs-toggle="tab" href="#tab-cmds">Commands ({{ commands|length }})</a></li>
|
||||
<li class="nav-item"><a class="nav-link" data-bs-toggle="tab" href="#tab-deaths">Deaths ({{ deaths|length }})</a></li>
|
||||
<li class="nav-item"><a class="nav-link" data-bs-toggle="tab" href="#tab-tp">Teleports ({{ teleports|length }})</a></li>
|
||||
<li class="nav-item"><a class="nav-link" data-bs-toggle="tab" href="#tab-proxy">Proxy ({{ proxy_events|length }})</a></li>
|
||||
</ul>
|
||||
|
||||
<div class="tab-content">
|
||||
<div class="tab-pane fade show active" id="tab-sessions">
|
||||
<div class="table-responsive" style="max-height:400px;overflow-y:auto;">
|
||||
<table class="table table-sm table-hover">
|
||||
<thead class="table-dark sticky-top"><tr><th>Login</th><th>Logout</th><th>Duration</th><th>Server</th><th>IP</th></tr></thead>
|
||||
<tbody>
|
||||
{% for s in sessions %}<tr>
|
||||
<td class="small text-nowrap">{{ s.login_time | fmt_dt }}</td>
|
||||
<td class="small text-nowrap">{{ s.logout_time | fmt_dt }}</td>
|
||||
<td class="small">{{ s.duration_sec | fmt_duration }}</td>
|
||||
<td><span class="badge bg-secondary">{{ s.server_name or '—' }}</span></td>
|
||||
<td class="small text-muted">{{ s.ip_address or '—' }}</td>
|
||||
</tr>{% else %}<tr><td colspan="5" class="text-center text-muted">No sessions</td></tr>{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="tab-pane fade" id="tab-chat">
|
||||
<div class="table-responsive" style="max-height:400px;overflow-y:auto;">
|
||||
<table class="table table-sm table-hover">
|
||||
<thead class="table-dark sticky-top"><tr><th>Time</th><th>Server</th><th>Message</th></tr></thead>
|
||||
<tbody>
|
||||
{% for c in chat %}<tr>
|
||||
<td class="small text-nowrap text-muted">{{ c.timestamp | fmt_dt }}</td>
|
||||
<td><span class="badge bg-secondary">{{ c.server_name or '—' }}</span></td>
|
||||
<td class="small">{{ c.message }}</td>
|
||||
</tr>{% else %}<tr><td colspan="3" class="text-center text-muted">No chat messages</td></tr>{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="tab-pane fade" id="tab-cmds">
|
||||
<div class="table-responsive" style="max-height:400px;overflow-y:auto;">
|
||||
<table class="table table-sm table-hover">
|
||||
<thead class="table-dark sticky-top"><tr><th>Time</th><th>Server</th><th>Command</th><th>Position</th></tr></thead>
|
||||
<tbody>
|
||||
{% for c in commands %}<tr>
|
||||
<td class="small text-nowrap text-muted">{{ c.timestamp | fmt_dt }}</td>
|
||||
<td><span class="badge bg-secondary">{{ c.server_name or '—' }}</span></td>
|
||||
<td class="small font-monospace">{{ c.command }}</td>
|
||||
<td class="small text-muted">{{ c.world or '' }} {% if c.x %}({{ c.x|round(1) }}, {{ c.y|round(1) }}, {{ c.z|round(1) }}){% endif %}</td>
|
||||
</tr>{% else %}<tr><td colspan="4" class="text-center text-muted">No commands</td></tr>{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="tab-pane fade" id="tab-deaths">
|
||||
<div class="table-responsive" style="max-height:400px;overflow-y:auto;">
|
||||
<table class="table table-sm table-hover">
|
||||
<thead class="table-dark sticky-top"><tr><th>Time</th><th>Cause</th><th>Killer</th><th>Level</th><th>World</th></tr></thead>
|
||||
<tbody>
|
||||
{% for d in deaths %}<tr>
|
||||
<td class="small text-nowrap text-muted">{{ d.timestamp | fmt_dt }}</td>
|
||||
<td><span class="badge bg-danger">{{ d.cause or '—' }}</span></td>
|
||||
<td class="small">{{ d.killer_name or '—' }}</td>
|
||||
<td class="small">{{ d.exp_level }}</td>
|
||||
<td class="small text-muted">{{ d.world }}</td>
|
||||
</tr>{% else %}<tr><td colspan="5" class="text-center text-muted">No deaths</td></tr>{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="tab-pane fade" id="tab-tp">
|
||||
<div class="table-responsive" style="max-height:400px;overflow-y:auto;">
|
||||
<table class="table table-sm table-hover">
|
||||
<thead class="table-dark sticky-top"><tr><th>Time</th><th>From</th><th>To</th><th>Cause</th></tr></thead>
|
||||
<tbody>
|
||||
{% for t in teleports %}<tr>
|
||||
<td class="small text-nowrap text-muted">{{ t.timestamp | fmt_dt }}</td>
|
||||
<td class="small">{{ t.from_world }} ({{ t.from_x|round(0)|int }}, {{ t.from_y|round(0)|int }}, {{ t.from_z|round(0)|int }})</td>
|
||||
<td class="small">{{ t.to_world }} ({{ t.to_x|round(0)|int }}, {{ t.to_y|round(0)|int }}, {{ t.to_z|round(0)|int }})</td>
|
||||
<td><span class="badge bg-info text-dark">{{ t.cause or '—' }}</span></td>
|
||||
</tr>{% else %}<tr><td colspan="4" class="text-center text-muted">No teleports</td></tr>{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="tab-pane fade" id="tab-proxy">
|
||||
<div class="table-responsive" style="max-height:400px;overflow-y:auto;">
|
||||
<table class="table table-sm table-hover">
|
||||
<thead class="table-dark sticky-top"><tr><th>Time</th><th>Type</th><th>From</th><th>To</th></tr></thead>
|
||||
<tbody>
|
||||
{% for e in proxy_events %}<tr>
|
||||
<td class="small text-nowrap text-muted">{{ e.timestamp | fmt_dt }}</td>
|
||||
<td><span class="badge bg-primary">{{ e.event_type }}</span></td>
|
||||
<td class="small">{{ e.from_server or '—' }}</td>
|
||||
<td class="small">{{ e.to_server or '—' }}</td>
|
||||
</tr>{% else %}<tr><td colspan="4" class="text-center text-muted">No proxy events</td></tr>{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<a href="{{ url_for('panel.players') }}" class="btn btn-outline-secondary">
|
||||
<i class="bi bi-arrow-left me-1"></i>Back to Overview
|
||||
</a>
|
||||
{% endblock %}
|
||||
56
web/templates/panel/players.html
Normal file
56
web/templates/panel/players.html
Normal file
@@ -0,0 +1,56 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}Players{% endblock %}
|
||||
{% block page_title %}<i class="bi bi-people-fill me-2"></i>Players{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<form method="get" class="row g-2 mb-3">
|
||||
<div class="col-auto flex-grow-1">
|
||||
<input type="text" name="q" class="form-control" placeholder="Search by player name…" value="{{ search }}">
|
||||
</div>
|
||||
<div class="col-auto">
|
||||
<button class="btn btn-success">Search</button>
|
||||
{% if search %}<a href="{{ url_for('panel.players') }}" class="btn btn-outline-secondary ms-1">Reset</a>{% endif %}
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-header d-flex justify-content-between">
|
||||
<span>{{ total }} players found</span>
|
||||
</div>
|
||||
<div class="card-body p-0">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover table-sm mb-0">
|
||||
<thead class="table-dark">
|
||||
<tr><th>Player</th><th>IP</th><th>First Seen</th><th>Last Seen</th><th>Playtime</th><th>OP</th><th></th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for p in players %}
|
||||
<tr>
|
||||
<td class="fw-semibold">
|
||||
<i class="bi bi-person-circle me-1 text-success"></i>{{ p.username }}
|
||||
</td>
|
||||
<td class="small text-muted">{{ p.ip_address or '—' }}</td>
|
||||
<td class="small text-muted text-nowrap">{{ p.first_seen | fmt_dt }}</td>
|
||||
<td class="small text-muted text-nowrap">{{ p.last_seen | fmt_dt }}</td>
|
||||
<td class="small">{{ p.total_playtime_sec | fmt_duration }}</td>
|
||||
<td>
|
||||
{% if p.is_op %}
|
||||
<span class="badge bg-warning text-dark"><i class="bi bi-shield-fill"></i> OP</span>
|
||||
{% else %}<span class="text-muted">—</span>{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
<a href="{{ url_for('panel.player_detail', uuid=p.uuid) }}" class="btn btn-sm btn-outline-primary">
|
||||
<i class="bi bi-eye"></i>
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
{% else %}
|
||||
<tr><td colspan="7" class="text-center text-muted py-4">No players found</td></tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% include "_pagination.html" %}
|
||||
{% endblock %}
|
||||
51
web/templates/panel/proxy.html
Normal file
51
web/templates/panel/proxy.html
Normal file
@@ -0,0 +1,51 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}Proxy Events{% endblock %}
|
||||
{% block page_title %}<i class="bi bi-diagram-3-fill me-2"></i>Proxy Events{% endblock %}
|
||||
{% block content %}
|
||||
<form method="get" class="row g-2 align-items-end mb-3">
|
||||
<div class="col-md-2">
|
||||
<select name="type" class="form-select form-select-sm">
|
||||
<option value="">All Types</option>
|
||||
{% for t in ['login','disconnect','server_switch','command','chat','kick','proxy_start','proxy_stop'] %}
|
||||
<option {{ 'selected' if t == event_type }}>{{ t }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<input type="text" name="player" class="form-control form-control-sm" placeholder="Player…" value="{{ player }}">
|
||||
</div>
|
||||
<div class="col-auto">
|
||||
<button class="btn btn-success btn-sm">Filter</button>
|
||||
<a href="{{ url_for('panel.proxy') }}" class="btn btn-outline-secondary btn-sm ms-1">Reset</a>
|
||||
</div>
|
||||
</form>
|
||||
<div class="card">
|
||||
<div class="card-header">{{ total }} proxy events</div>
|
||||
<div class="card-body p-0">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-sm table-hover mb-0">
|
||||
<thead class="table-dark">
|
||||
<tr><th>Time</th><th>Type</th><th>Player</th><th>Proxy</th><th>From</th><th>To</th><th>IP</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for r in rows %}
|
||||
{% set badge = {'login':'success','disconnect':'danger','server_switch':'primary','command':'warning','proxy_start':'info','proxy_stop':'dark'} %}
|
||||
<tr>
|
||||
<td class="small text-muted text-nowrap">{{ r.timestamp | fmt_dt }}</td>
|
||||
<td><span class="badge bg-{{ badge.get(r.event_type,'secondary') }}">{{ r.event_type }}</span></td>
|
||||
<td class="small fw-semibold">{{ r.player_name or '—' }}</td>
|
||||
<td><span class="badge bg-secondary">{{ r.proxy_name or '—' }}</span></td>
|
||||
<td class="small">{{ r.from_server or '—' }}</td>
|
||||
<td class="small">{{ r.to_server or '—' }}</td>
|
||||
<td class="small text-muted">{{ r.ip_address or '—' }}</td>
|
||||
</tr>
|
||||
{% else %}
|
||||
<tr><td colspan="7" class="text-center text-muted py-4">No proxy events</td></tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% include "_pagination.html" %}
|
||||
{% endblock %}
|
||||
49
web/templates/panel/server_events.html
Normal file
49
web/templates/panel/server_events.html
Normal file
@@ -0,0 +1,49 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}Server Events{% endblock %}
|
||||
{% block page_title %}<i class="bi bi-server me-2"></i>Server Events{% endblock %}
|
||||
{% block content %}
|
||||
<form method="get" class="row g-2 align-items-end mb-3">
|
||||
<div class="col-md-3">
|
||||
<select name="server" class="form-select form-select-sm">
|
||||
<option value="">All Servers</option>
|
||||
{% for s in servers %}<option {{ 'selected' if s == server }}>{{ s }}</option>{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<select name="type" class="form-select form-select-sm">
|
||||
<option value="">All Types</option>
|
||||
{% for t in etypes %}<option {{ 'selected' if t == etype }}>{{ t }}</option>{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-auto">
|
||||
<button class="btn btn-success btn-sm">Filter</button>
|
||||
<a href="{{ url_for('panel.server_events') }}" class="btn btn-outline-secondary btn-sm ms-1">Reset</a>
|
||||
</div>
|
||||
</form>
|
||||
<div class="card">
|
||||
<div class="card-header">{{ total }} server events</div>
|
||||
<div class="card-body p-0">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-sm table-hover mb-0">
|
||||
<thead class="table-dark">
|
||||
<tr><th>Time</th><th>Type</th><th>Server</th><th>Message</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for r in rows %}
|
||||
{% set badge = {'server_start':'success','server_stop':'danger','player_join':'info','player_quit':'secondary','player_kick':'warning'} %}
|
||||
<tr>
|
||||
<td class="small text-muted text-nowrap">{{ r.timestamp | fmt_dt }}</td>
|
||||
<td><span class="badge bg-{{ badge.get(r.event_type,'secondary') }}">{{ r.event_type }}</span></td>
|
||||
<td><span class="badge bg-dark">{{ r.server_name or '—' }}</span></td>
|
||||
<td class="small">{{ r.message or '—' }}</td>
|
||||
</tr>
|
||||
{% else %}
|
||||
<tr><td colspan="4" class="text-center text-muted py-4">No events</td></tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% include "_pagination.html" %}
|
||||
{% endblock %}
|
||||
56
web/templates/panel/sessions.html
Normal file
56
web/templates/panel/sessions.html
Normal file
@@ -0,0 +1,56 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}Sessions{% endblock %}
|
||||
{% block page_title %}<i class="bi bi-clock-history me-2"></i>Sessions{% endblock %}
|
||||
{% block content %}
|
||||
<form method="get" class="row g-2 align-items-end mb-3">
|
||||
<div class="col-md-3">
|
||||
<input type="text" name="player" class="form-control form-control-sm" placeholder="Player…" value="{{ player }}">
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<select name="server" class="form-select form-select-sm">
|
||||
<option value="">All Servers</option>
|
||||
{% for s in servers %}<option {{ 'selected' if s == server }}>{{ s }}</option>{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-auto">
|
||||
<button class="btn btn-success btn-sm">Filter</button>
|
||||
<a href="{{ url_for('panel.sessions') }}" class="btn btn-outline-secondary btn-sm ms-1">Reset</a>
|
||||
</div>
|
||||
</form>
|
||||
<div class="card">
|
||||
<div class="card-header">{{ total }} sessions</div>
|
||||
<div class="card-body p-0">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-sm table-hover mb-0">
|
||||
<thead class="table-dark">
|
||||
<tr><th>Player</th><th>Server</th><th>Login</th><th>Logout</th><th>Duration</th><th>IP</th><th>Country</th><th>Client</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for r in rows %}
|
||||
<tr>
|
||||
<td class="small fw-semibold">
|
||||
<a href="{{ url_for('panel.player_detail', uuid=r.player_uuid) }}" class="text-decoration-none">
|
||||
<i class="bi bi-person-circle me-1 text-success"></i>{{ r.player_name }}
|
||||
</a>
|
||||
</td>
|
||||
<td><span class="badge bg-secondary">{{ r.server_name or '—' }}</span></td>
|
||||
<td class="small text-muted text-nowrap">{{ r.login_time | fmt_dt }}</td>
|
||||
<td class="small text-muted text-nowrap">{{ r.logout_time | fmt_dt }}</td>
|
||||
<td class="small">
|
||||
{% if r.logout_time %}{{ r.duration_sec | fmt_duration }}
|
||||
{% else %}<span class="badge bg-success">Online</span>{% endif %}
|
||||
</td>
|
||||
<td class="small text-muted">{{ r.ip_address or '—' }}</td>
|
||||
<td class="small text-muted">{{ r.country or '—' }}</td>
|
||||
<td class="small text-muted">{{ r.client_version or '—' }}</td>
|
||||
</tr>
|
||||
{% else %}
|
||||
<tr><td colspan="8" class="text-center text-muted py-4">No sessions</td></tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% include "_pagination.html" %}
|
||||
{% endblock %}
|
||||
Reference in New Issue
Block a user