From 18d4b82bca940af9e505616b7feb124bc486a7fe Mon Sep 17 00:00:00 2001 From: Aaron Kimbrell Date: Fri, 2 Oct 2026 17:57:31 -0500 Subject: [PATCH] feat(dashboard): Database Usage page Totals and disk free, a size chart over a day, week or month, the fastest-growing tables and every table with sorting. The time chart takes a timeLabel for ranges longer than a day. Co-Authored-By: Claude Opus 5.5 --- dDashboardServer/routes/DashboardRoutes.cpp | 1 + dDashboardServer/static/js/perf-charts.js | 7 +- .../templates/database_usage.jinja2 | 173 ++++++++++++++++++ dDashboardServer/templates/header.jinja2 | 4 +- docs/Dashboard.md | 16 ++ 5 files changed, 196 insertions(+), 5 deletions(-) create mode 100644 dDashboardServer/templates/database_usage.jinja2 diff --git a/dDashboardServer/routes/DashboardRoutes.cpp b/dDashboardServer/routes/DashboardRoutes.cpp index 3cddd1389..77b247d1e 100644 --- a/dDashboardServer/routes/DashboardRoutes.cpp +++ b/dDashboardServer/routes/DashboardRoutes.cpp @@ -450,6 +450,7 @@ void RegisterDashboardRoutes() { SimplePage("/diagnostics", Perm("health_view"), "diagnostics.jinja2", "diagnostics", "Packets, bytes and HTTP requests per second of every server"); SimplePage("/network", Perm("health_view"), "network.jinja2", "network", "Live diagram of the traffic between players, the servers and web clients, and the traffic of each connection"); SimplePage("/performance", Perm("health_view"), "performance.jinja2", "performance", "Main loop frame times and phases of every server, slow frames with their scopes, and profiling sessions with a flame graph"); + SimplePage("/database_usage", Perm("health_view"), "database_usage.jinja2", "database_usage", "Database size over time, the fastest-growing tables, every table's size and rows, and free disk space"); SimplePage("/players", Perm("players_view"), "players.jinja2", "players", "Who is online, with kick, rescue and teleport"); SimplePage("/backups", Perm("backups"), "backups.jinja2", "backups", "Database backups"); // The GM level tabs need permissions_manage, the grants tab grants_manage diff --git a/dDashboardServer/static/js/perf-charts.js b/dDashboardServer/static/js/perf-charts.js index bdc58685c..d1abd57f7 100644 --- a/dDashboardServer/static/js/perf-charts.js +++ b/dDashboardServer/static/js/perf-charts.js @@ -138,7 +138,8 @@ /** * A time chart: lines ([{label, color, values, dash}]) or a stack of areas (stacked: true), with a crosshair and - * every series' value in the tooltip; `threshold` draws a dashed line (the slow frame threshold) + * every series' value in the tooltip; `threshold` draws a dashed line (the slow frame threshold); `timeLabel(t, long)` + * names a time (default: the time of day) */ function timeChart(svg, times, series, options) { options = options || {}; @@ -170,7 +171,7 @@ [0, Math.floor((n - 1) / 2), n - 1].forEach(function (i) { if (i < 0 || !times.length) return; el('text', { x: X(i), y: height - 4, 'text-anchor': i === 0 ? 'start' : i === n - 1 ? 'end' : 'middle', class: 'axis' }, svg).textContent = - new Date(times[i] * 1000).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit', second: n <= 400 ? '2-digit' : undefined }); + (options.timeLabel ? options.timeLabel(times[i]) : new Date(times[i] * 1000).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit', second: n <= 400 ? '2-digit' : undefined })); }); if (options.stacked) { var base = new Array(n).fill(0); @@ -213,7 +214,7 @@ return '
' + esc(s.label) + ': ' + (options.format ? options.format(s.values[i]) : esc(s.values[i])) + '
'; }); if (options.extra) rows = rows.concat(options.extra(i)); - showTip(box, '
' + esc(new Date(times[i] * 1000).toLocaleTimeString()) + '
' + rows.join(''), e.clientX - box.getBoundingClientRect().left, e.clientY - box.getBoundingClientRect().top); + showTip(box, '
' + esc(options.timeLabel ? options.timeLabel(times[i], true) : new Date(times[i] * 1000).toLocaleTimeString()) + '
' + rows.join(''), e.clientX - box.getBoundingClientRect().left, e.clientY - box.getBoundingClientRect().top); }); hit.addEventListener('mouseleave', function () { cross.setAttribute('visibility', 'hidden'); hideTip(box); }); } diff --git a/dDashboardServer/templates/database_usage.jinja2 b/dDashboardServer/templates/database_usage.jinja2 new file mode 100644 index 000000000..332517d50 --- /dev/null +++ b/dDashboardServer/templates/database_usage.jinja2 @@ -0,0 +1,173 @@ +{% extends "base.jinja2" %} + +{% block title %}Database Usage - DarkflameServer{% endblock %} + +{% block css %} + +{% endblock %} + +{% block content %} +
+
+

Database Usage

+

How much room the game database takes, measured by the Database usage task (hourly by default) and kept for + database_usage_days. Disk warning: database_disk_warn_percent (Settings).

+
+
+ {% if can.tasks_manage %}{% endif %} +
+ + + +
+
+
+ +
+
No measurements yet. The Database usage task runs every hour{% if can.tasks_manage %}, or use Measure now{% endif %}.
+ +
+
Database
-
+
Data / indexes
-
+
Rows
-
+
Disk free
-
+
+ +
+
+
Size
+
+
Tables
+
+ + +
TableTotalDataIndexesRowsGrowthNew rows
+
+
+
Fastest-growing
+
+
+{% endblock %} + +{% block scripts %} + + +{% endblock %} diff --git a/dDashboardServer/templates/header.jinja2 b/dDashboardServer/templates/header.jinja2 index 218253c23..ea8ccbf50 100644 --- a/dDashboardServer/templates/header.jinja2 +++ b/dDashboardServer/templates/header.jinja2 @@ -105,7 +105,7 @@ {% endif %} - {% set logsPages = ["health", "instances", "diagnostics", "network", "performance", "activity_log", "command_log", "audit_log", "system_log", "client_sysinfo"] %} + {% set logsPages = ["health", "instances", "diagnostics", "network", "performance", "database_usage", "activity_log", "command_log", "audit_log", "system_log", "client_sysinfo"] %} {% if can.health_view or can.logs_activity or can.logs_command or can.logs_audit or can.logs_system or can.client_sysinfo %}