/** * Server info page — live dashboard with D3/NVD3 charts. */ function ServerInfoPage(params, container) { var DATA_LIMIT = 100; var recordedData = []; var html = '
' + '
' + '

Server Info

' + '
Loading…
' + '
' + '
' + '
' + '
' + '
' + '' + '

Server Performance

' + '
' + '
' + '
' + '
' + '
' + '
' + '
' + '
' + '
' + '

Players

' + '
' + '
' + '
' + '
' + '
' + '
' + '
' + '
' + '
' + '' + '

Server Networking

' + '
' + '
' + '
' + '
' + '
' + '
' + '
' + '
'; container.innerHTML = html; // ---- NVD3 Charts ---- nv.addGraph(function () { var perfChart = nv.models.multiChart() .margin({ top: 30, right: 75, bottom: 40, left: 75 }) .color(['#e879f9', '#22d3ee']); 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 }) .color(['#c084fc', '#fbbf24']); 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) .donutLabelsOutside(false) .duration(500) .height(250) .color(['#a78bfa', '#7c3aed', '#e879f9', '#2d1f3d']) .margin({ top: 5, right: 5, bottom: 5, left: 5 }); 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 ---- // Subscribe to header's serverinfo updates (no redundant RCON request) function onServerInfo(e) { updateData(e.detail); } document.addEventListener('serverinfo-update', onServerInfo); // If header already has cached data, use it immediately if (window._lastServerInfo) updateData(window._lastServerInfo); function updateData(data) { // Guard: element may be gone if navigated away var fieldsEl = document.getElementById('si-fields'); if (!fieldsEl) return; // 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 += '

' + key + ': ' + val + '

'; } } fieldsEl.innerHTML = fieldsHtml; if (data.Hostname) { var hn = document.getElementById('si-hostname'); if (hn) hn.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) { // Guard: SVG elements may be gone if navigated away // Players pie chart if (window._playersChart) { var pc = d3.select('#chart-players'); if (!pc.empty()) { pc.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); } } return function cleanup() { document.removeEventListener('serverinfo-update', onServerInfo); window._perfChart = null; window._netChart = null; window._playersChart = null; }; }