diff --git a/Plan/common/src/main/resources/assets/plan/web/js/graphs.js b/Plan/common/src/main/resources/assets/plan/web/js/graphs.js index 3cade7fe8..9a37d2463 100644 --- a/Plan/common/src/main/resources/assets/plan/web/js/graphs.js +++ b/Plan/common/src/main/resources/assets/plan/web/js/graphs.js @@ -1,4 +1,4 @@ -var linegraphButtons = [{ +const linegraphButtons = [{ type: 'hour', count: 12, text: '12h' @@ -19,7 +19,7 @@ var linegraphButtons = [{ text: 'All' }]; -var graphs = []; +const graphs = []; window.calendars = {}; function activityPie(id, activitySeries) { @@ -406,15 +406,15 @@ function serverPie(id, serverSeries) { } function formatTimeAmount(ms) { - var out = ""; + let out = ""; - var seconds = Math.floor(ms / 1000); + let seconds = Math.floor(ms / 1000); - var dd = Math.floor(seconds / 86400); + const dd = Math.floor(seconds / 86400); seconds -= (dd * 86400); - var dh = Math.floor(seconds / 3600); + const dh = Math.floor(seconds / 3600); seconds -= (dh * 3600); - var dm = Math.floor(seconds / 60); + const dm = Math.floor(seconds / 60); seconds -= (dm * 60); seconds = Math.floor(seconds); if (dd !== 0) { @@ -610,9 +610,9 @@ function worldMap(id, colorMin, colorMax, mapSeries) { } function worldPie(id, worldSeries, gmSeries) { - var defaultTitle = ''; - var defaultSubtitle = 'Click to expand'; - var chart = Highcharts.chart(id, { + const defaultTitle = ''; + const defaultSubtitle = 'Click to expand'; + const chart = Highcharts.chart(id, { chart: { plotBackgroundColor: null, plotBorderWidth: null, diff --git a/Plan/common/src/main/resources/assets/plan/web/js/server-values.js b/Plan/common/src/main/resources/assets/plan/web/js/server-values.js index 05af4fc66..8e8a345d4 100644 --- a/Plan/common/src/main/resources/assets/plan/web/js/server-values.js +++ b/Plan/common/src/main/resources/assets/plan/web/js/server-values.js @@ -345,4 +345,237 @@ function loadPerformanceValues(json, error) { element.querySelector('#data_low_tps_entities').innerText = data.low_tps_entities; element.querySelector('#data_low_tps_chunks').innerText = data.low_tps_chunks; element.querySelector('#data_low_tps_cpu').innerText = data.low_tps_cpu; +} + +function loadOptimizedPerformanceGraph(json, error) { + if (json) { + const zones = { + tps: [{ + value: json.zones.tpsThresholdMed, + color: json.colors.low + }, { + value: json.zones.tpsThresholdHigh, + color: json.colors.med + }, { + value: 30, + color: json.colors.high + }], + disk: [{ + value: json.zones.diskThresholdMed, + color: json.colors.low + }, { + value: json.zones.tpsThresholdHigh, + color: json.colors.med + }, { + value: Number.MAX_VALUE, + color: json.colors.high + }] + }; + const dataSeries = mapToDataSeries(json.values); + const series = { + playersOnline: { + name: s.name.playersOnline, type: s.type.areaSpline, tooltip: s.tooltip.zeroDecimals, + data: dataSeries.playersOnline, color: json.colors.playersOnline, yAxis: 0 + }, + tps: { + name: s.name.tps, type: s.type.spline, color: json.colors.high, + zones: zones.tps, tooltip: s.tooltip.twoDecimals, data: dataSeries.tps, + yAxis: 1 + }, + cpu: { + name: s.name.cpu, type: s.type.spline, tooltip: s.tooltip.twoDecimals, + data: dataSeries.cpu, color: json.colors.cpu, yAxis: 2 + }, + cpu_alt: { + name: s.name.cpu, type: s.type.spline, tooltip: s.tooltip.twoDecimals, + data: dataSeries.cpu, color: json.colors.cpu, yAxis: 1 + }, + ram: { + name: s.name.ram, type: s.type.spline, tooltip: s.tooltip.zeroDecimals, + data: dataSeries.ram, color: json.colors.ram, yAxis: 3 + }, + ram_alt: { + name: s.name.ram, type: s.type.spline, tooltip: s.tooltip.zeroDecimals, + data: dataSeries.ram, color: json.colors.ram, yAxis: 2 + }, + entities: { + name: s.name.entities, type: s.type.spline, tooltip: s.tooltip.zeroDecimals, + data: dataSeries.entities, color: json.colors.entities, yAxis: 4 + }, + entities_alt: { + name: s.name.entities, type: s.type.spline, tooltip: s.tooltip.zeroDecimals, + data: dataSeries.entities, color: json.colors.entities, yAxis: 1 + }, + chunks: { + name: s.name.chunks, type: s.type.spline, tooltip: s.tooltip.zeroDecimals, + data: dataSeries.chunks, color: json.colors.chunks, yAxis: 5 + }, + chunks_alt: { + name: s.name.chunks, type: s.type.spline, tooltip: s.tooltip.zeroDecimals, + data: dataSeries.chunks, color: json.colors.chunks, yAxis: 2 + }, + disk: { + name: s.name.disk, type: s.type.spline, color: json.colors.high, + zones: zones.disk, tooltip: s.tooltip.zeroDecimals, data: dataSeries.disk + } + }; + playersChart('playersOnlineChart', series.playersOnline, 2); + performanceChart('performanceGraph', series.playersOnline, series.tps, series.cpu, series.ram, series.entities, series.chunks); + tpsChart('tpsGraph', series.tps, series.playersOnline); + resourceChart('resourceGraph', series.cpu_alt, series.ram_alt, series.playersOnline); + worldChart('worldGraph', series.entities_alt, series.chunks_alt, series.playersOnline); + diskChart('diskGraph', [series.disk]); + } else if (error) { + const errorMessage = `Failed to load graph data: ${error}`; + document.getElementById('playersOnlineChart').innerText = errorMessage; + document.getElementById('performanceGraph').innerText = errorMessage; + document.getElementById('tpsGraph').innerText = errorMessage; + document.getElementById('resourceGraph').innerText = errorMessage; + document.getElementById('worldGraph').innerText = errorMessage; + document.getElementById('diskGraph').innerText = errorMessage; + } +} + +function loadPingGraph(json, error) { + if (json) { + const series = { + avgPing: { + name: s.name.avgPing, + type: s.type.spline, + tooltip: s.tooltip.twoDecimals, + data: json.avg_ping_series, + color: json.colors.avg + }, + maxPing: { + name: s.name.maxPing, + type: s.type.spline, + tooltip: s.tooltip.zeroDecimals, + data: json.max_ping_series, + color: json.colors.max + }, + minPing: { + name: s.name.minPing, + type: s.type.spline, + tooltip: s.tooltip.zeroDecimals, + data: json.min_ping_series, + color: json.colors.min + } + }; + lineChart('pingGraph', [series.avgPing, series.maxPing, series.minPing]); + } else if (error) { + document.getElementById('pingGraph').innerText = `Failed to load graph data: ${error}`; + } +} + +function loadWorldPie(json, error) { + if (json) { + const worldSeries = {name: 'World Playtime', colorByPoint: true, data: json.world_series}; + const gmSeries = json.gm_series; + worldPie("worldPie", worldSeries, gmSeries); + } else if (error) { + document.getElementById('worldPie').innerText = `Failed to load graph data: ${error}`; + } +} + +function loadActivityGraph(json, error) { + if (json) { + activityPie('activityPie', { + name: s.name.unit_players, colorByPoint: true, data: json.activity_pie_series + }); + stackChart('activityStackGraph', json.activity_labels, json.activity_series, s.name.unit_players); + } else if (error) { + const errorMessage = `Failed to load graph data: ${error}`; + document.getElementById('activityPie').innerText = errorMessage; + document.getElementById('activityStackGraph').innerText = errorMessage; + } +} + +function loadGeolocationGraph(json, error) { + if (json) { + const geolocationSeries = { + name: s.name.unit_players, + type: 'map', + mapData: Highcharts.maps['custom/world'], + data: json.geolocation_series, + joinBy: ['iso-a3', 'code'] + }; + const geolocationBarSeries = { + color: json.colors.bars, + name: s.name.unit_players, + data: json.geolocation_bar_series.map(function (bar) { + return bar.value + }) + }; + const geolocationBarCategories = json.geolocation_bar_series.map(function (bar) { + return bar.label + }); + worldMap('worldMap', json.colors.low, json.colors.high, geolocationSeries); + horizontalBarChart('countryBarChart', geolocationBarCategories, [geolocationBarSeries], s.name.unit_players); + } else if (error) { + const errorMessage = `Failed to load graph data: ${error}`; + document.getElementById('worldMap').innerText = errorMessage; + document.getElementById('countryBarChart').innerText = errorMessage; + } +} + +function loadUniqueAndNewGraph(json, error) { + if (json) { + const uniquePlayers = { + name: s.name.uniquePlayers, type: s.type.spline, tooltip: s.tooltip.zeroDecimals, + data: json.uniquePlayers, color: json.colors.playersOnline + }; + const newPlayers = { + name: s.name.newPlayers, type: s.type.spline, tooltip: s.tooltip.zeroDecimals, + data: json.newPlayers, color: json.colors.newPlayers + }; + dayByDay('uniqueChart', [uniquePlayers, newPlayers]); + } else if (error) { + document.getElementById('uniqueChart').innerText = `Failed to load graph data: ${error}`; + } +} + +function loadHourlyUniqueAndNewGraph(json, error) { + if (json) { + const uniquePlayers = { + name: s.name.uniquePlayers, type: s.type.spline, tooltip: s.tooltip.zeroDecimals, + data: json.uniquePlayers, color: json.colors.playersOnline + }; + const newPlayers = { + name: s.name.newPlayers, type: s.type.spline, tooltip: s.tooltip.zeroDecimals, + data: json.newPlayers, color: json.colors.newPlayers + }; + dayByDay('hourlyUniqueChart', [uniquePlayers, newPlayers]); + } else if (error) { + document.getElementById('uniqueChart').innerText = `Failed to load graph data: ${error}`; + } +} + +function loadServerCalendar(json, error) { + if (json) { + document.getElementById('calendar').innerText = ''; + onlineActivityCalendar('#calendar', json.data, json.firstDay); + document.getElementById('online-calendar-tab').addEventListener('click', function () { + // Wrapping this in a 0ms setTimeout waits for all other event handlers + // to finish. We need this because if the calendar is rendered + // immediately, it renders for a width of 0. + setTimeout(function () { + window.calendars.online_activity.render(); + }, 0); + }); + } else if (error) { + document.getElementById('calendar').innerText = `Failed to load calendar data: ${error}`; + } +} + +function loadPunchCard(json, error) { + if (json) { + const punchCardSeries = { + name: 'Relative Join Activity', + color: json.color, + data: json.punchCard + }; + punchCard('punchCard', punchCardSeries); + } else if (error) { + document.getElementById('punchCard').innerText = `Failed to load graph data: ${error}`; + } } \ No newline at end of file diff --git a/Plan/common/src/main/resources/assets/plan/web/js/xmlhttprequests.js b/Plan/common/src/main/resources/assets/plan/web/js/xmlhttprequests.js index 4cadca183..f7e154751 100644 --- a/Plan/common/src/main/resources/assets/plan/web/js/xmlhttprequests.js +++ b/Plan/common/src/main/resources/assets/plan/web/js/xmlhttprequests.js @@ -1,3 +1,6 @@ +// Stored by tab {'tab-id': ['address', 'address']} +const currentlyRefreshing = {}; + function refreshingJsonRequest(address, callback, tabID) { const timestamp = Date.now(); const addressWithTimestamp = address.includes('?') @@ -6,12 +9,18 @@ function refreshingJsonRequest(address, callback, tabID) { const refreshElement = document.querySelector(`#${tabID} .refresh-element`); refreshElement.querySelector('i').addEventListener('click', () => { + if (currentlyRefreshing[tabID].includes(address)) { + return; + } refreshElement.querySelector('.refresh-notice').innerHTML = ' Updating..'; refreshingJsonRequest(address, callback, tabID); }); let timeout = 1000; + if (!currentlyRefreshing[tabID]) currentlyRefreshing[tabID] = []; + currentlyRefreshing[tabID].push(address); + function makeTheRequest() { jsonRequest(addressWithTimestamp, (json, error) => { if (error) { @@ -32,7 +41,10 @@ function refreshingJsonRequest(address, callback, tabID) { setTimeout(makeTheRequest, timeout); timeout = timeout >= 12000 ? timeout : timeout * 2; } else { - refreshElement.querySelector('.refresh-notice').innerHTML = ""; + currentlyRefreshing[tabID].splice(currentlyRefreshing[tabID].indexOf(address), 1); + if (!currentlyRefreshing[tabID].length) { + refreshElement.querySelector('.refresh-notice').innerHTML = ""; + } } callback(json, error); }) diff --git a/Plan/common/src/main/resources/assets/plan/web/network.html b/Plan/common/src/main/resources/assets/plan/web/network.html index 33401f624..ee7bd9156 100644 --- a/Plan/common/src/main/resources/assets/plan/web/network.html +++ b/Plan/common/src/main/resources/assets/plan/web/network.html @@ -27,7 +27,7 @@