This commit is contained in:
Aaron Kimbrell
2026-03-26 09:56:29 -05:00
parent 8372202d8f
commit f658da19a3
27 changed files with 1669 additions and 685 deletions

View File

@@ -11,7 +11,7 @@
<div class="card">
<div class="card-header">
<h2>Account #{{ account.id }} - {{ account.name }}</h2>
<h2>Account #{{ account.id }} - <span class="account-name">{{ account.name }}</span></h2>
<p class="text-muted">View account details and manage settings</p>
</div>
<div class="card-body">
@@ -23,7 +23,7 @@
<div class="detail-item">
<div class="detail-label">Username</div>
<div class="detail-value">{{ account.name }}</div>
<div class="detail-value"><span class="account-name">{{ account.name }}</span></div>
</div>
<div class="detail-item">
@@ -35,9 +35,9 @@
<div class="detail-label">GM Level</div>
<div class="detail-value">
{% if account.gm_level > 0 %}
<span class="badge badge-gm">GM {{ account.gm_level }}</span>
<span class="account-gm-level badge badge-gm">GM {{ account.gm_level }}</span>
{% else %}
<span class="badge badge-inactive">User</span>
<span class="account-gm-level badge badge-inactive">User</span>
{% endif %}
</div>
</div>
@@ -46,9 +46,9 @@
<div class="detail-label">Ban Status</div>
<div class="detail-value">
{% if account.banned %}
<span class="badge badge-banned">BANNED</span>
<span class="account-banned badge badge-banned">BANNED</span>
{% else %}
<span class="badge badge-active">Active</span>
<span class="account-banned badge badge-active">Active</span>
{% endif %}
</div>
</div>
@@ -57,9 +57,9 @@
<div class="detail-label">Lock Status</div>
<div class="detail-value">
{% if account.locked %}
<span class="badge badge-locked">LOCKED</span>
<span class="account-locked badge badge-locked">LOCKED</span>
{% else %}
<span class="badge badge-active">Unlocked</span>
<span class="account-locked badge badge-active">Unlocked</span>
{% endif %}
</div>
</div>
@@ -67,11 +67,13 @@
<div class="detail-item">
<div class="detail-label">Mute Expires</div>
<div class="detail-value">
{% if account.mute_expire > 0 %}
<span>{{ account.mute_expire }}</span>
{% else %}
<span class="text-muted">Not muted</span>
{% endif %}
<span class="account-mute-expire">
{% if account.mute_expire > 0 %}
Muted until {{ account.mute_expire }}
{% else %}
Not muted
{% endif %}
</span>
</div>
</div>
</div>
@@ -100,7 +102,18 @@
{% endblock %}
{% block scripts %}
<script src="/js/realtime.js"></script>
<script>
// Set current account for real-time detail panel updates
document.addEventListener('DOMContentLoaded', () => {
realtimeManager.SetCurrentEntity('account', {{ account.id }});
// Clean up when leaving the page
window.addEventListener('beforeunload', () => {
realtimeManager.ClearCurrentEntity();
});
});
function EditAccount() {
alert("Edit functionality coming soon");
// TODO: Open edit modal
@@ -110,6 +123,7 @@
if (confirm("Are you sure you want to ban this account?")) {
alert("Ban functionality coming soon");
// TODO: Call ban API endpoint
// After successful ban, call: BroadcastAccountUpdate({{ account.id }})
}
}
@@ -117,6 +131,7 @@
if (confirm("Are you sure you want to unban this account?")) {
alert("Unban functionality coming soon");
// TODO: Call unban API endpoint
// After successful unban, call: BroadcastAccountUpdate({{ account.id }})
}
}
@@ -124,6 +139,7 @@
if (confirm("Are you sure you want to lock this account?")) {
alert("Lock functionality coming soon");
// TODO: Call lock API endpoint
// After successful lock, call: BroadcastAccountUpdate({{ account.id }})
}
}
@@ -131,6 +147,7 @@
if (confirm("Are you sure you want to unlock this account?")) {
alert("Unlock functionality coming soon");
// TODO: Call unlock API endpoint
// After successful unlock, call: BroadcastAccountUpdate({{ account.id }})
}
}
</script>

View File

@@ -7,12 +7,12 @@
{% block content %}
<div class="accounts-container">
<div class="container-fluid">
<div class="table-card">
<div class="table-header">
<h2 class="mb-0">Accounts</h2>
<p class="text-muted">View and manage user accounts</p>
<div class="card">
<div class="card-header">
<h2>Accounts</h2>
<p class="text-muted">View and manage user accounts - <span class="account-count">0</span> total</p>
</div>
<div class="table-body">
<div class="card-body">
<div class="table-responsive">
<table id="accountsTable" class="table table-dark table-hover mb-0">
<thead>
@@ -39,10 +39,11 @@
{% endblock %}
{% block scripts %}
<script src="/js/realtime.js"></script>
<script>
$(document).ready(function() {
// Initialize DataTable with server-side processing
$('#accountsTable').DataTable({
const table = $('#accountsTable').DataTable({
processing: true,
serverSide: true,
pageLength: 25,
@@ -119,6 +120,25 @@
order: [[0, 'asc']],
stateSave: false
});
// Register table for real-time updates
realtimeManager.RegisterTable('account', table);
// Update account count when DataTable loads
table.on('draw.dt', function() {
const info = table.page.info();
const countEl = document.querySelector('.account-count');
if (countEl && info) {
countEl.textContent = info.recordsTotal;
}
});
// Initialize real-time listener when WebSocket is ready
const waitForWS = setInterval(() => {
if (typeof realtimeManager !== 'undefined' && wsConnectionReady) {
clearInterval(waitForWS);
}
}, 100);
});
function viewAccount(id) {

View File

@@ -28,6 +28,40 @@
<script src="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/5.3.0/js/bootstrap.bundle.min.js" integrity="sha384-geWF76RCwLtnZ8qwWowPQNguL3RmwHVBC9FhGdlKrxdiJJigb/j/68SIy3Te4Bkz" crossorigin="anonymous"></script>
<script src="https://cdn.datatables.net/v/bs5/jq-3.7.0/dt-2.3.7/b-3.2.6/fh-4.0.6/sc-2.4.3/datatables.min.js" integrity="sha384-BPUXtS4tH3onFfu5m+dPbFfpLOXQwSWGwrsNWxOAAwqqJx6tJHhFkGF6uitrmEui" crossorigin="anonymous"></script>
<script>
// Global logout function (available on all pages)
function deleteCookie(name) {
document.cookie = `${name}=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/; SameSite=Strict`;
}
function logout() {
deleteCookie('dashboardToken');
deleteCookie('gmLevel');
localStorage.removeItem('dashboardToken');
window.location.href = '/login';
}
// Setup logout button
document.addEventListener('DOMContentLoaded', () => {
const logoutBtn = document.getElementById('logoutBtn');
if (logoutBtn) {
logoutBtn.addEventListener('click', (e) => {
e.preventDefault();
logout();
});
}
});
</script>
<!-- Load dashboard.js only on dashboard pages (not on login) -->
<script>
const isLoginPage = document.querySelector('.login-container') !== null;
if (!isLoginPage) {
const script = document.createElement('script');
script.src = '/js/dashboard.js';
document.head.appendChild(script);
}
</script>
{% block scripts %}{% endblock %}
</body>

View File

@@ -6,12 +6,13 @@
{% block content %}
<div class="bug-reports-container">
<div class="table-card">
<div class="table-header">
<h2 class="mb-0">Bug Reports</h2>
<p class="text-muted">View and manage bug reports from players</p>
</div>
<div class="table-body">
<div class="container-fluid">
<div class="card">
<div class="card-header">
<h2>Bug Reports <span class="bug_report-count">0</span></h2>
<p class="text-muted">View and manage bug reports from players</p>
</div>
<div class="card-body">
<table id="bugReportsTable" class="table table-dark table-striped table-hover">
<thead>
<tr>
@@ -27,16 +28,18 @@
<!-- Data populated by DataTables -->
</tbody>
</table>
</div>
</div>
</div>
</div>
{% endblock %}
{% block scripts %}
<script src="/js/realtime.js"></script>
<script>
$(document).ready(function() {
// Initialize DataTable with server-side processing
$('#bugReportsTable').DataTable({
const table = $('#bugReportsTable').DataTable({
processing: true,
serverSide: true,
pageLength: 25,
@@ -80,6 +83,25 @@
order: [[0, 'desc']],
stateSave: false
});
// Register table for real-time updates
realtimeManager.RegisterTable('bug_report', table);
// Update bug report count on table draw
table.on('draw.dt', function() {
const info = table.page.info();
const countEl = document.querySelector('.bug_report-count');
if (countEl && info) {
countEl.textContent = info.recordsTotal;
}
});
// Initialize real-time listener when WebSocket is ready
const waitForWS = setInterval(() => {
if (typeof realtimeManager !== 'undefined' && wsConnectionReady) {
clearInterval(waitForWS);
}
}, 100);
});
function viewReport(id) {

View File

@@ -6,12 +6,13 @@
{% block content %}
<div class="characters-container">
<div class="table-card">
<div class="table-header">
<h2 class="mb-0">Characters</h2>
<p class="text-muted">View and manage player characters</p>
</div>
<div class="table-body">
<div class="container-fluid">
<div class="card">
<div class="card-header">
<h2>Characters</h2>
<p class="text-muted">View and manage player characters - <span class="character-count">0</span> total</p>
</div>
<div class="card-body">
<table id="charactersTable" class="table table-dark table-striped table-hover">
<thead>
<tr>
@@ -26,16 +27,18 @@
<!-- Data populated by DataTables -->
</tbody>
</table>
</div>
</div>
</div>
</div>
{% endblock %}
{% block scripts %}
<script src="/js/realtime.js"></script>
<script>
$(document).ready(function() {
// Initialize DataTable with server-side processing
$('#charactersTable').DataTable({
const table = $('#charactersTable').DataTable({
processing: true,
serverSide: true,
pageLength: 25,
@@ -75,6 +78,25 @@
order: [[0, 'asc']],
stateSave: false
});
// Register table for real-time updates
realtimeManager.RegisterTable('character', table);
// Update character count when DataTable loads
table.on('draw.dt', function() {
const info = table.page.info();
const countEl = document.querySelector('.character-count');
if (countEl && info) {
countEl.textContent = info.recordsTotal;
}
});
// Initialize WebSocket realtime updates
const waitForWS = setInterval(() => {
if (typeof realtimeManager !== 'undefined' && wsConnectionReady) {
clearInterval(waitForWS);
}
}, 100);
});
function viewCharacter(id) {

View File

@@ -1,5 +1,5 @@
{# Navigation #}
<nav class="navbar navbar-dark bg-dark flex-column" style="width: 280px; height: 100vh; position: fixed; left: 0; top: 0; overflow-y: auto;">
<nav class="navbar navbar-dark bg-dark flex-column">
<div class="p-3">
<a class="navbar-brand fw-bold" href="/">🎮 DarkflameServer</a>
</div>

View File

@@ -3,33 +3,33 @@
{% block title %}Dashboard - DarkflameServer{% endblock %}
{% block content %}
<!-- Main Content -->
<main style="margin-left: 280px;">
<div class="container-fluid p-3 p-md-4">
<div class="row g-3">
{% include "server_status.jinja2" %}
{% include "statistics.jinja2" %}
</div>
{% include "world_instances.jinja2" %}
<div class="container-fluid py-5 px-4">
<div class="dashboard-header mb-4">
<div>
<h1>Welcome to DarkflameServer Dashboard</h1>
<p class="text-muted">Real-time server monitoring and administration</p>
</div>
</main>
</div>
<div class="row g-3">
{% include "server_status.jinja2" %}
{% include "statistics.jinja2" %}
</div>
{% include "world_instances.jinja2" %}
</div>
{% endblock %}
{% block scripts %}
<script src="/js/dashboard.js"></script>
<script>
// Check authentication and initialize dashboard
// Initialize dashboard when dashboard.js is loaded
document.addEventListener('DOMContentLoaded', () => {
// checkAuthentication is now async and calls connectWebSocket when ready
checkAuthentication();
// Setup logout button
document.getElementById('logoutBtn').addEventListener('click', (e) => {
e.preventDefault();
logout();
});
// Wait a bit for dashboard.js to load
setTimeout(() => {
if (typeof checkAuthentication === 'function') {
checkAuthentication();
}
}, 100);
});
</script>
{% endblock %}

View File

@@ -2,50 +2,62 @@
{% block title %}Dashboard Login - DarkflameServer{% endblock %}
{% block extra_css %}
{% block css %}
<link rel="stylesheet" href="/css/login.css">
{% endblock %}
{% block content %}
<div class="min-vh-100 d-flex align-items-center justify-content-center">
<div class="container">
<div class="row justify-content-center">
<div class="col-md-6 col-lg-5">
<div class="card shadow-lg border-0">
<div class="card-body p-5">
<h1 class="text-center mb-4">🎮 DarkflameServer</h1>
<div id="alert" class="alert" role="alert" style="display: none;"></div>
<form id="loginForm">
<div class="mb-3">
<label for="username" class="form-label">Username</label>
<input type="text" class="form-control" id="username" name="username" required autofocus>
</div>
<div class="mb-3">
<label for="password" class="form-label">Password</label>
<input type="password" class="form-control" id="password" name="password" required maxlength="40">
</div>
<div class="mb-3 form-check">
<input type="checkbox" class="form-check-input" id="rememberMe" name="rememberMe">
<label class="form-check-label" for="rememberMe">
Remember me for 30 days
</label>
</div>
<button type="submit" class="btn btn-primary w-100" id="loginBtn">
<span id="loading" class="spinner-border spinner-border-sm me-2" role="status" aria-hidden="true" style="display: none;"></span>
<span>Login</span>
</button>
</form>
</div>
<div class="login-container">
<div class="login-wrapper">
<div class="login-left">
<div class="login-branding">
<div class="logo">⚙️</div>
<h1>DarkflameServer</h1>
<p>Administration Dashboard</p>
</div>
<div class="login-info">
<h3>Welcome Back</h3>
<p>Manage your DarkflameServer infrastructure with ease. Secure access to server administration tools.</p>
</div>
</div>
<div class="login-right">
<div class="login-form-wrapper">
<h2>Sign In</h2>
<div id="alert" class="alert-box" role="alert" style="display: none;"></div>
<form id="loginForm" class="login-form">
<div class="form-group">
<label for="username">Username</label>
<input type="text" id="username" name="username" required autofocus placeholder="Enter your username">
<div class="form-focus"></div>
</div>
<div class="form-group">
<label for="password">Password</label>
<input type="password" id="password" name="password" required maxlength="40" placeholder="Enter your password">
<div class="form-focus"></div>
</div>
<div class="form-checkbox">
<input type="checkbox" id="rememberMe" name="rememberMe">
<label for="rememberMe">Remember me for 30 days</label>
</div>
<button type="submit" class="btn-login" id="loginBtn">
<span id="loading" class="spinner" style="display: none;"></span>
<span class="btn-text">Sign In</span>
</button>
</form>
<div class="login-footer">
<p>Contact your administrator if you don't have access</p>
</div>
</div>
</div>
</div>
</div>
{% endblock %}
{% block scripts %}

View File

@@ -6,12 +6,13 @@
{% block content %}
<div class="play-keys-container">
<div class="table-card">
<div class="table-header">
<h2 class="mb-0">Play Keys</h2>
<p class="text-muted">View and manage play keys</p>
</div>
<div class="table-body">
<div class="container-fluid">
<div class="card">
<div class="card-header">
<h2>Play Keys <span class="play_key-count">0</span></h2>
<p class="text-muted">View and manage play keys</p>
</div>
<div class="card-body">
<table id="playKeysTable" class="table table-dark table-striped table-hover">
<thead>
<tr>
@@ -27,16 +28,18 @@
<!-- Data populated by DataTables -->
</tbody>
</table>
</div>
</div>
</div>
</div>
{% endblock %}
{% block scripts %}
<script src="/js/realtime.js"></script>
<script>
$(document).ready(function() {
// Initialize DataTable with server-side processing
$('#playKeysTable').DataTable({
const table = $('#playKeysTable').DataTable({
processing: true,
serverSide: true,
pageLength: 25,
@@ -80,6 +83,25 @@
order: [[0, 'asc']],
stateSave: false
});
// Register table for real-time updates
realtimeManager.RegisterTable('play_key', table);
// Update play key count on table draw
table.on('draw.dt', function() {
const info = table.page.info();
const countEl = document.querySelector('.play_key-count');
if (countEl && info) {
countEl.textContent = info.recordsTotal;
}
});
// Initialize real-time listener when WebSocket is ready
const waitForWS = setInterval(() => {
if (typeof realtimeManager !== 'undefined' && wsConnectionReady) {
clearInterval(waitForWS);
}
}, 100);
});
function viewKey(id) {

View File

@@ -6,12 +6,13 @@
{% block content %}
<div class="properties-container">
<div class="table-card">
<div class="table-header">
<h2 class="mb-0">Properties</h2>
<p class="text-muted">View and manage player properties</p>
</div>
<div class="table-body">
<div class="container-fluid">
<div class="card">
<div class="card-header">
<h2>Properties <span class="property-count">0</span></h2>
<p class="text-muted">View and manage player properties</p>
</div>
<div class="card-body">
<table id="propertiesTable" class="table table-dark table-striped table-hover">
<thead>
<tr>
@@ -28,16 +29,18 @@
<!-- Data populated by DataTables -->
</tbody>
</table>
</div>
</div>
</div>
</div>
{% endblock %}
{% block scripts %}
<script src="/js/realtime.js"></script>
<script>
$(document).ready(function() {
// Initialize DataTable with server-side processing
$('#propertiesTable').DataTable({
const table = $('#propertiesTable').DataTable({
processing: true,
serverSide: true,
pageLength: 25,
@@ -77,6 +80,25 @@
order: [[0, 'asc']],
stateSave: false
});
// Register table for real-time updates
realtimeManager.RegisterTable('property', table);
// Update property count on table draw
table.on('draw.dt', function() {
const info = table.page.info();
const countEl = document.querySelector('.property-count');
if (countEl && info) {
countEl.textContent = info.recordsTotal;
}
});
// Initialize real-time listener when WebSocket is ready
const waitForWS = setInterval(() => {
if (typeof realtimeManager !== 'undefined' && wsConnectionReady) {
clearInterval(waitForWS);
}
}, 100);
});
function viewProperty(id) {

View File

@@ -1,28 +1,28 @@
<div class="col-md-6 col-lg-4">
<div class="card border-0 shadow-sm h-100">
<div class="card-header bg-light">
<div class="card h-100">
<div class="card-header">
<h5 class="mb-0">Server Status</h5>
</div>
<div class="card-body">
<div class="d-flex justify-content-between align-items-center mb-3">
<span>Auth Server</span>
{% if auth.online %}
<span class="badge bg-success" id="auth-status">Online</span>
<span class="badge badge-active" id="auth-status">Online</span>
{% else %}
<span class="badge bg-danger" id="auth-status">Offline</span>
<span class="badge badge-banned" id="auth-status">Offline</span>
{% endif %}
</div>
<div class="d-flex justify-content-between align-items-center mb-3">
<span>Chat Server</span>
{% if chat.online %}
<span class="badge bg-success" id="chat-status">Online</span>
<span class="badge badge-active" id="chat-status">Online</span>
{% else %}
<span class="badge bg-danger" id="chat-status">Offline</span>
<span class="badge badge-banned" id="chat-status">Offline</span>
{% endif %}
</div>
<div class="d-flex justify-content-between align-items-center">
<span>Active Worlds</span>
<span class="badge bg-primary" id="world-count">{{ length(worlds) }}</span>
<span class="badge badge-primary" id="world-count">{{ length(worlds) }}</span>
</div>
</div>
</div>

View File

@@ -1,20 +1,20 @@
<div class="col-md-6 col-lg-4">
<div class="card border-0 shadow-sm h-100">
<div class="card-header bg-light">
<div class="card h-100">
<div class="card-header">
<h5 class="mb-0">Statistics</h5>
</div>
<div class="card-body">
<div class="d-flex justify-content-between align-items-center mb-3">
<span>Online Players</span>
<span class="badge bg-info" id="online-players">{{ stats.onlinePlayers }}</span>
<span class="badge badge-primary" id="online-players">{{ stats.onlinePlayers }}</span>
</div>
<div class="d-flex justify-content-between align-items-center mb-3">
<span>Total Accounts</span>
<span class="badge bg-info" id="total-accounts">{{ stats.totalAccounts }}</span>
<span class="badge badge-primary" id="total-accounts">{{ stats.totalAccounts }}</span>
</div>
<div class="d-flex justify-content-between align-items-center">
<span>Total Characters</span>
<span class="badge bg-info" id="total-characters">{{ stats.totalCharacters }}</span>
<span class="badge badge-primary" id="total-characters">{{ stats.totalCharacters }}</span>
</div>
</div>
</div>

View File

@@ -1,6 +1,6 @@
<div class="card border-0 shadow-sm mt-4">
<div class="card-header bg-light">
<h5 class="mb-0">Active World Instances</h5>
<div class="card mt-4">
<div class="card-header">
<h2>Active World Instances</h2>
</div>
<div class="card-body">
<div id="world-list">
@@ -8,8 +8,8 @@
<p class="text-muted text-center mb-0">No active world instances</p>
{% else %}
<div class="table-responsive">
<table class="table table-sm mb-0">
<thead class="table-light">
<table class="table table-dark table-hover mb-0">
<thead>
<tr>
<th>Zone</th>
<th>Instance</th>
@@ -24,8 +24,8 @@
<td>{{ world.mapID }}</td>
<td>{{ world.instanceID }}</td>
<td>{{ world.cloneID }}</td>
<td><span class="badge bg-secondary">{{ world.players }}</span></td>
<td>{% if world.isPrivate %}<span class="badge bg-warning">Private</span>{% else %}<span class="badge bg-primary">Public</span>{% endif %}</td>
<td><span class="badge badge-primary">{{ world.players }}</span></td>
<td>{% if world.isPrivate %}<span class="badge badge-warning">Private</span>{% else %}<span class="badge badge-success">Public</span>{% endif %}</td>
</tr>
{% endfor %}
</tbody>