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

Server Info

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

Server Performance

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

Server Networking

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

Players

' + '
' + '
' + '
' + '
' + '
' + '
' + '
' + '
'; 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 += '

' + key + ': ' + val + '

'; } } 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; }; }