Files
rust-rcon/js/pages/serverInfo.js
T
angryzeroandCopilot 0a178d17b9 feat: full de-Angularization with Bootstrap 5 migration
- Removed AngularJS 1.4.8 completely — no more framework dependency
- Extracted core RCON logic into pure vanilla JS (js/rcon.js)
- Added lightweight hash-based SPA router (js/router.js)
- Rewrote all 6 pages as vanilla JS modules in js/pages/
- Migrated Bootstrap 3 → Bootstrap 5 (cards instead of panels, bootstrap-icons instead of glyphicons)
- Removed jQuery dependency (Bootstrap 5 is vanilla JS)
- Purged all Angular templates from html/
- Removed npm artifacts (package.json, node_modules)
- Added nginx.example.conf for static hosting
- Updated rcon.css dark theme for BS5 variables
- Static file only — no Node.js backend required

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-06-13 17:51:50 +02:00

189 lines
6.8 KiB
JavaScript

/**
* Server info page — live dashboard with D3/NVD3 charts.
*/
function ServerInfoPage(params, container) {
var DATA_LIMIT = 100;
var recordedData = [];
var html =
'<div id="ServerInfoController">' +
'<div class="row">' +
'<div class="col-sm-12"><h2 id="si-hostname">Server Info</h2></div>' +
'<div id="si-fields" class="col-sm-12"></div>' +
'</div>' +
'<div class="row">' +
'<div class="col-sm-12">' +
'<div class="card mb-3">' +
'<div class="card-header">' +
'<button class="btn btn-sm btn-outline-primary float-end" role="button" data-bs-toggle="collapse" data-bs-target="#performanceCollapse">toggle</button>' +
'<h4 class="card-title mb-0">Server Performance</h4>' +
'</div>' +
'<div id="performanceCollapse" class="collapse show">' +
'<div class="card-body"><svg id="chart-performance"></svg></div>' +
'</div>' +
'</div>' +
'<div class="card mb-3">' +
'<div class="card-header">' +
'<button class="btn btn-sm btn-outline-primary float-end" role="button" data-bs-toggle="collapse" data-bs-target="#networkCollapse">toggle</button>' +
'<h4 class="card-title mb-0">Server Networking</h4>' +
'</div>' +
'<div id="networkCollapse" class="collapse show">' +
'<div class="card-body"><svg id="chart-network"></svg></div>' +
'</div>' +
'</div>' +
'<div class="card mb-3">' +
'<div class="card-header">' +
'<button class="btn btn-sm btn-outline-primary float-end" role="button" data-bs-toggle="collapse" data-bs-target="#playersCollapse">toggle</button>' +
'<h4 class="card-title mb-0">Players</h4>' +
'</div>' +
'<div id="playersCollapse" class="collapse show">' +
'<div class="card-body"><svg id="chart-players"></svg></div>' +
'</div>' +
'</div>' +
'</div>' +
'</div>' +
'</div>';
container.innerHTML = html;
// ---- NVD3 Charts ----
nv.addGraph(function () {
var perfChart = nv.models.multiChart()
.margin({ top: 30, right: 75, bottom: 40, left: 75 })
.color(['darkred', 'yellowgreen'])
.useInteractiveGuideline(true)
.duration(0);
perfChart.yAxis1.axisLabel('Framerate').tickFormat(d3.format(',d'));
perfChart.yAxis2.axisLabel('Entities').tickFormat(d3.format(',d'));
perfChart.xAxis.axisLabel('Time').tickFormat(function (d) { return d3.time.format('%H:%M:%S')(new Date(d)); });
perfChart.lines1.duration(0);
perfChart.lines2.duration(0);
d3.select('#chart-performance').datum([
{ key: 'Framerate', type: 'line', yAxis: 1, values: [] },
{ key: 'Entities', type: 'line', yAxis: 2, values: [] }
]).call(perfChart);
window._perfChart = perfChart;
});
nv.addGraph(function () {
var netChart = nv.models.stackedAreaChart()
.margin({ top: 0, right: 75, bottom: 40, left: 75 })
.useInteractiveGuideline(true)
.duration(0);
netChart.yAxis.axisLabel('Network').tickFormat(function (bytes) {
var fmt = d3.format('.0f');
if (bytes < 1024) return fmt(bytes) + 'B';
if (bytes < 1048576) return fmt(bytes / 1024) + 'kB';
if (bytes < 1073741824) return fmt(bytes / 1048576) + 'MB';
return fmt(bytes / 1073741824) + 'GB';
});
netChart.xAxis.axisLabel('Time').tickFormat(function (d) { return d3.time.format('%H:%M:%S')(new Date(d)); });
d3.select('#chart-network').datum([
{ key: 'IN', values: [] },
{ key: 'OUT', values: [] }
]).call(netChart);
window._netChart = netChart;
});
nv.addGraph(function () {
var playersChart = nv.models.pieChart()
.x(function (d) { return d.key; })
.y(function (d) { return d.y; })
.showLabels(true)
.donut(true)
.duration(500)
.height(250)
.margin({ top: 0, right: 75, bottom: 0, left: 75 });
playersChart.pie.startAngle(function (d) { return d.startAngle / 2 - Math.PI / 2; });
playersChart.pie.endAngle(function (d) { return d.endAngle / 2 - Math.PI / 2; });
d3.select('#chart-players').datum([]).call(playersChart);
window._playersChart = playersChart;
});
// ---- Data polling ----
var pollTimer = null;
function refresh() {
Rcon.request('serverinfo', function (msg) {
updateData(JSON.parse(msg.Message));
});
}
function updateData(data) {
// Update text fields
var fieldsHtml = '';
for (var key in data) {
if (data.hasOwnProperty(key)) {
var val = data[key];
if (typeof val === 'number') val = formatNumber(val);
fieldsHtml += '<div class="col-sm-4"><p><span class="text-muted">' + key + ':</span> <span>' + val + '</span></p></div>';
}
}
document.getElementById('si-fields').innerHTML = fieldsHtml;
if (data.Hostname) document.getElementById('si-hostname').textContent = data.Hostname;
// Collect chart data
recordedData.push({ ts: Date.now(), data: data });
if (recordedData.length > DATA_LIMIT) {
recordedData = recordedData.slice(recordedData.length - DATA_LIMIT);
}
updateCharts(data);
}
function updateCharts(data) {
// Players pie chart
if (window._playersChart) {
d3.select('#chart-players').datum([
{ key: 'Queued', y: data.Queued || 0 },
{ key: 'Joining', y: data.Joining || 0 },
{ key: 'Players', y: data.Players || 0 },
{ key: 'Free', y: (data.MaxPlayers || 0) - ((data.Joining || 0) + (data.Players || 0)) }
]).call(window._playersChart);
}
// Performance chart
var fpsVals = [], entVals = [], netInVals = [], netOutVals = [];
for (var i = 0; i < recordedData.length; i++) {
var r = recordedData[i];
fpsVals.push({ x: r.ts, y: r.data.Framerate || 0 });
entVals.push({ x: r.ts, y: r.data.EntityCount || 0 });
netInVals.push({ x: r.ts, y: r.data.NetworkIn || 0 });
netOutVals.push({ x: r.ts, y: r.data.NetworkOut || 0 });
}
if (window._perfChart) {
d3.select('#chart-performance').datum([
{ key: 'Framerate', type: 'line', yAxis: 1, values: fpsVals },
{ key: 'Entities', type: 'line', yAxis: 2, values: entVals }
]).call(window._perfChart);
}
if (window._netChart) {
d3.select('#chart-network').datum([
{ key: 'IN', values: netInVals },
{ key: 'OUT', values: netOutVals }
]).call(window._netChart);
}
}
function startPolling() {
refresh();
pollTimer = setInterval(refresh, 1000);
}
if (Rcon.isConnected()) startPolling();
else { var _c = function () { startPolling(); Rcon.off('connected', _c); }; Rcon.on('connected', _c); }
return function cleanup() {
if (pollTimer) clearInterval(pollTimer);
window._perfChart = null;
window._netChart = null;
window._playersChart = null;
};
}