Merge pull request #156 from Cryptkeeper/use-serverids

release 5.2.0
This commit is contained in:
Nick Krecklow 2020-04-29 04:28:03 -05:00 committed by GitHub
commit f878357511
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 97 additions and 160 deletions

@ -99,7 +99,7 @@ export class App {
// error = defined with "Waiting" description // error = defined with "Waiting" description
// info = safely defined with configured data // info = safely defined with configured data
const latestPing = pings[pings.length - 1] const latestPing = pings[pings.length - 1]
const serverRegistration = this.serverRegistry.createServerRegistration(latestPing.info.name) const serverRegistration = this.serverRegistry.createServerRegistration(latestPing.serverId)
serverRegistration.initServerStatus(latestPing) serverRegistration.initServerStatus(latestPing)

@ -148,11 +148,7 @@ export class GraphDisplayManager {
this.loadLocalStorage() this.loadLocalStorage()
} }
// Remap the incoming data from being string (serverName) keyed into serverId keys this._graphData = graphData
for (const serverName of Object.keys(graphData)) {
const serverRegistration = this._app.serverRegistry.getServerRegistration(serverName)
this._graphData[serverRegistration.serverId] = graphData[serverName]
}
// Explicitly define a height so flot.js can rescale the Y axis // Explicitly define a height so flot.js can rescale the Y axis
document.getElementById('big-graph').style.height = '400px' document.getElementById('big-graph').style.height = '400px'

@ -34,7 +34,10 @@ document.addEventListener('DOMContentLoaded', function () {
let lastRowCounter = 0 let lastRowCounter = 0
let controlsHTML = '' let controlsHTML = ''
Object.keys(data).sort().forEach(function (serverName) { app.serverRegistry.getServerRegistrations()
.map(serverRegistration => serverRegistration.data.name)
.sort()
.forEach(serverName => {
const serverRegistration = app.serverRegistry.getServerRegistration(serverName) const serverRegistration = app.serverRegistry.getServerRegistration(serverName)
controlsHTML += '<td>' + controlsHTML += '<td>' +
@ -59,25 +62,6 @@ document.addEventListener('DOMContentLoaded', function () {
app.graphDisplayManager.initEventListeners() app.graphDisplayManager.initEventListeners()
}) })
socket.on('updateHistoryGraph', function (data) {
// Skip any incoming updates if the graph is disabled
// The backend shouldn't send these anyways
if (!app.graphDisplayManager.isVisible) {
return
}
const serverRegistration = app.serverRegistry.getServerRegistration(data.name)
if (serverRegistration) {
app.graphDisplayManager.addGraphPoint(serverRegistration.serverId, data.timestamp, data.playerCount)
// Only redraw the graph if not mutating hidden data
if (serverRegistration.isVisible) {
app.graphDisplayManager.requestRedraw()
}
}
})
socket.on('add', function (data) { socket.on('add', function (data) {
data.forEach(app.addServer) data.forEach(app.addServer)
}) })
@ -86,11 +70,25 @@ document.addEventListener('DOMContentLoaded', function () {
// The backend may send "update" events prior to receiving all "add" events // The backend may send "update" events prior to receiving all "add" events
// A server has only been added once it's ServerRegistration is defined // A server has only been added once it's ServerRegistration is defined
// Checking undefined protects from this race condition // Checking undefined protects from this race condition
const serverRegistration = app.serverRegistry.getServerRegistration(data.info.name) const serverRegistration = app.serverRegistry.getServerRegistration(data.serverId)
if (serverRegistration) { if (serverRegistration) {
serverRegistration.updateServerStatus(data, false, app.publicConfig.minecraftVersions) serverRegistration.updateServerStatus(data, false, app.publicConfig.minecraftVersions)
} }
// Use update payloads to conditionally append data to graph
// Skip any incoming updates if the graph is disabled
if (data.updateHistoryGraph && app.graphDisplayManager.isVisible) {
// Update may not be successful, safely append 0 points
const playerCount = data.result ? data.result.players.online : 0
app.graphDisplayManager.addGraphPoint(serverRegistration.serverId, data.timestamp, playerCount)
// Only redraw the graph if not mutating hidden data
if (serverRegistration.isVisible) {
app.graphDisplayManager.requestRedraw()
}
}
}) })
socket.on('updateMojangServices', function (data) { socket.on('updateMojangServices', function (data) {
@ -121,26 +119,6 @@ document.addEventListener('DOMContentLoaded', function () {
app.handleSyncComplete() app.handleSyncComplete()
}) })
socket.on('updatePeak', function (data) {
const serverRegistration = app.serverRegistry.getServerRegistration(data.name)
if (serverRegistration) {
serverRegistration.updateServerPeak(data.timestamp, data.playerCount)
}
})
socket.on('peaks', function (data) {
Object.keys(data).forEach(function (serverName) {
const serverRegistration = app.serverRegistry.getServerRegistration(serverName)
if (serverRegistration) {
const graphPeak = data[serverName]
serverRegistration.updateServerPeak(graphPeak.timestamp, graphPeak.playerCount)
}
})
})
window.addEventListener('resize', function () { window.addEventListener('resize', function () {
app.percentageBar.redraw() app.percentageBar.redraw()

@ -1,4 +1,4 @@
import { formatNumber, formatTimestamp, formatDate, formatMinecraftServerAddress, formatMinecraftVersions, isArrayEqual, isObjectEqual } from './util' import { formatNumber, formatTimestamp, formatDate, formatMinecraftServerAddress, formatMinecraftVersions } from './util'
import MISSING_FAVICON from '../images/missing_favicon.svg' import MISSING_FAVICON from '../images/missing_favicon.svg'
@ -48,8 +48,7 @@ export class ServerRegistry {
} }
} }
createServerRegistration (serverName) { createServerRegistration (serverId) {
const serverId = this._serverIdsByName[serverName]
const serverData = this._serverDataById[serverId] const serverData = this._serverDataById[serverId]
const serverRegistration = new ServerRegistration(this._app, serverId, serverData) const serverRegistration = new ServerRegistration(this._app, serverId, serverData)
this._registeredServers[serverId] = serverRegistration this._registeredServers[serverId] = serverRegistration
@ -65,23 +64,6 @@ export class ServerRegistry {
} }
} }
getServerRankBy (serverRegistration, x, sort) {
const records = Object.values(this._registeredServers)
.map(x)
.filter(val => val !== undefined)
// Invalidate any results that do not account for all serverRegistrations
if (records.length === this._registeredServers.length) {
records.sort(sort)
// Pull matching data from target serverRegistration
// Assume indexOf cannot be -1 or val undefined since they have been pre-tested in the map call above
const val = x(serverRegistration)
const indexOf = records.indexOf(val)
return indexOf + 1
}
}
getServerRegistrations = () => Object.values(this._registeredServers) getServerRegistrations = () => Object.values(this._registeredServers)
reset () { reset () {
@ -102,7 +84,6 @@ export class ServerRegistration {
isFavorite = false isFavorite = false
rankIndex rankIndex
lastRecordData lastRecordData
lastVersions = []
lastPeakData lastPeakData
constructor (app, serverId, data) { constructor (app, serverId, data) {
@ -139,7 +120,7 @@ export class ServerRegistration {
if (pushToGraph) { if (pushToGraph) {
// Only update graph for successful pings // Only update graph for successful pings
// This intentionally pauses the server graph when pings begin to fail // This intentionally pauses the server graph when pings begin to fail
this._graphData.push([payload.info.timestamp, this.playerCount]) this._graphData.push([payload.timestamp, this.playerCount])
// Trim graphData to within the max length by shifting out the leading elements // Trim graphData to within the max length by shifting out the leading elements
if (this._graphData.length > SERVER_GRAPH_DATA_MAX_LENGTH) { if (this._graphData.length > SERVER_GRAPH_DATA_MAX_LENGTH) {
@ -174,7 +155,7 @@ export class ServerRegistration {
document.getElementById('ranking_' + this.serverId).innerText = '#' + (rankIndex + 1) document.getElementById('ranking_' + this.serverId).innerText = '#' + (rankIndex + 1)
} }
updateServerPeak (time, playerCount) { updateServerPeak (data) {
const peakLabelElement = document.getElementById('peak_' + this.serverId) const peakLabelElement = document.getElementById('peak_' + this.serverId)
// Always set label once any peak data has been received // Always set label once any peak data has been received
@ -182,13 +163,10 @@ export class ServerRegistration {
const peakValueElement = document.getElementById('peak-value_' + this.serverId) const peakValueElement = document.getElementById('peak-value_' + this.serverId)
peakValueElement.innerText = formatNumber(playerCount) peakValueElement.innerText = formatNumber(data.playerCount)
peakLabelElement.title = 'At ' + formatTimestamp(time) peakLabelElement.title = 'At ' + formatTimestamp(data.timestamp)
this.lastPeakData = { this.lastPeakData = data
timestamp: time,
playerCount: playerCount
}
} }
updateServerStatus (ping, isInitialUpdate, minecraftVersions) { updateServerStatus (ping, isInitialUpdate, minecraftVersions) {
@ -196,21 +174,14 @@ export class ServerRegistration {
// Otherwise the ping value is pushed into the graphData when already present // Otherwise the ping value is pushed into the graphData when already present
this.handlePing(ping, !isInitialUpdate) this.handlePing(ping, !isInitialUpdate)
// Compare against a cached value to avoid empty updates if (ping.versions) {
// Allow undefined ping.versions inside the if statement for text reset handling
if (ping.versions && !isArrayEqual(ping.versions, this.lastVersions)) {
this.lastVersions = ping.versions
const versionsElement = document.getElementById('version_' + this.serverId) const versionsElement = document.getElementById('version_' + this.serverId)
versionsElement.style.display = 'block' versionsElement.style.display = 'block'
versionsElement.innerText = formatMinecraftVersions(ping.versions, minecraftVersions[this.data.type]) || '' versionsElement.innerText = formatMinecraftVersions(ping.versions, minecraftVersions[this.data.type]) || ''
} }
// Compare against a cached value to avoid empty updates if (ping.recordData) {
if (ping.recordData !== undefined && !isObjectEqual(ping.recordData, this.lastRecordData, ['playerCount', 'timestamp'])) {
this.lastRecordData = ping.recordData
// Always set label once any record data has been received // Always set label once any record data has been received
const recordLabelElement = document.getElementById('record_' + this.serverId) const recordLabelElement = document.getElementById('record_' + this.serverId)
@ -227,6 +198,12 @@ export class ServerRegistration {
} else { } else {
recordValueElement.innerText = formatNumber(recordData.playerCount) recordValueElement.innerText = formatNumber(recordData.playerCount)
} }
this.lastRecordData = recordData
}
if (ping.graphPeakData) {
this.updateServerPeak(ping.graphPeakData)
} }
const playerCountLabelElement = document.getElementById('player-count_' + this.serverId) const playerCountLabelElement = document.getElementById('player-count_' + this.serverId)

@ -135,20 +135,6 @@ export function isArrayEqual (a, b) {
return true return true
} }
export function isObjectEqual (a, b, props) {
if (typeof a === 'undefined' || typeof a !== typeof b) {
return false
}
for (let i = 0; i < props.length; i++) {
const prop = props[i]
if (typeof a[prop] === 'undefined' || typeof a[prop] !== typeof b[prop] || a[prop] !== b[prop]) {
return false
}
}
return true
}
// From http://detectmobilebrowsers.com/ // From http://detectmobilebrowsers.com/
export function isMobileBrowser () { export function isMobileBrowser () {
var check = false; var check = false;

@ -1,3 +1,8 @@
**5.2.0** *(Apr 29 2020)*
- Updated protocol to use serverIds instead of string names. This will reduce wasted bandwidth when pushing updates.
- Removed "updatePeak", "peaks" and "updateHistoryGraph" socket events. Their behavior has been optimized and merged into "update".
- Removed various legacy code.
**5.1.2** *(Apr 22 2020)* **5.1.2** *(Apr 22 2020)*
- Fixes the historical graph overflowing the maximum graphDuration value. - Fixes the historical graph overflowing the maximum graphDuration value.

@ -39,24 +39,11 @@ class App {
client.on('requestHistoryGraph', () => { client.on('requestHistoryGraph', () => {
// Send historical graphData built from all serverRegistrations // Send historical graphData built from all serverRegistrations
const graphData = {} const graphData = {}
const graphPeaks = {}
this.serverRegistrations.forEach((serverRegistration) => { this.serverRegistrations.forEach((serverRegistration) => {
graphData[serverRegistration.data.name] = serverRegistration.graphData graphData[serverRegistration.serverId] = serverRegistration.graphData
// Send current peak, if any
const graphPeak = serverRegistration.getGraphPeak()
if (graphPeak) {
graphPeaks[serverRegistration.data.name] = graphPeak
}
}) })
// Send current peaks, if any
// Emit peaks first since graphData may take a while to receive
if (Object.keys(graphPeaks).length > 0) {
client.emit('peaks', graphPeaks)
}
client.emit('historyGraph', graphData) client.emit('historyGraph', graphData)
}) })
} }

@ -107,10 +107,10 @@ class PingController {
handlePing (serverRegistration, resp, err, version) { handlePing (serverRegistration, resp, err, version) {
const timestamp = new Date().getTime() const timestamp = new Date().getTime()
this._app.server.broadcast('update', serverRegistration.getUpdate(timestamp, resp, err, version))
serverRegistration.addPing(timestamp, resp) serverRegistration.addPing(timestamp, resp)
let updateHistoryGraph = false
if (config.logToDatabase) { if (config.logToDatabase) {
const playerCount = resp ? resp.players.online : 0 const playerCount = resp ? resp.players.online : 0
@ -118,25 +118,14 @@ class PingController {
this._app.database.insertPing(serverRegistration.data.ip, timestamp, playerCount) this._app.database.insertPing(serverRegistration.data.ip, timestamp, playerCount)
if (serverRegistration.addGraphPoint(resp !== undefined, playerCount, timestamp)) { if (serverRegistration.addGraphPoint(resp !== undefined, playerCount, timestamp)) {
this._app.server.broadcast('updateHistoryGraph', { updateHistoryGraph = true
name: serverRegistration.data.name, }
playerCount: playerCount,
timestamp: timestamp
})
} }
// Update calculated graph peak regardless if the graph is being updated // Generate a combined update payload
// This can cause a (harmless) desync between live and stored data, but it allows it to be more accurate for long surviving processes // This includes any modified fields and flags used by the frontend
if (serverRegistration.findNewGraphPeak()) { // This will not be cached and can contain live metadata
const graphPeak = serverRegistration.getGraphPeak() this._app.server.broadcast('update', serverRegistration.getUpdate(timestamp, resp, err, version, updateHistoryGraph))
this._app.server.broadcast('updatePeak', {
name: serverRegistration.data.name,
playerCount: graphPeak.playerCount,
timestamp: graphPeak.timestamp
})
}
}
} }
} }

@ -2,23 +2,23 @@ const config = require('../config')
const minecraftVersions = require('../minecraft_versions') const minecraftVersions = require('../minecraft_versions')
class ServerRegistration { class ServerRegistration {
serverId
lastFavicon lastFavicon
versions = [] versions = []
recordData recordData
graphData = [] graphData = []
constructor (data) { constructor (serverId, data) {
this.serverId = serverId
this.data = data this.data = data
this._pingHistory = [] this._pingHistory = []
} }
getUpdate (timestamp, resp, err, version) { getUpdate (timestamp, resp, err, version, updateHistoryGraph) {
const update = { const update = {
info: { serverId: this.serverId,
name: this.data.name,
timestamp: timestamp timestamp: timestamp
} }
}
if (resp) { if (resp) {
if (resp.version && this.updateProtocolVersionCompat(resp.version, version.protocolId, version.protocolIndex)) { if (resp.version && this.updateProtocolVersionCompat(resp.version, version.protocolId, version.protocolIndex)) {
@ -48,6 +48,20 @@ class ServerRegistration {
update.result = { update.result = {
players: resp.players players: resp.players
} }
if (config.logToDatabase) {
// Update calculated graph peak regardless if the graph is being updated
// This can cause a (harmless) desync between live and stored data, but it allows it to be more accurate for long surviving processes
if (this.findNewGraphPeak()) {
update.graphPeakData = this.getGraphPeak()
}
// Handled inside logToDatabase to validate logic from #getUpdate call
// Only append when true since an undefined value == false
if (updateHistoryGraph) {
update.updateHistoryGraph = true
}
}
} else if (err) { } else if (err) {
// Append a filtered copy of err // Append a filtered copy of err
// This ensures any unintended data is not leaked // This ensures any unintended data is not leaked
@ -94,15 +108,21 @@ class ServerRegistration {
const lastPing = this._pingHistory[this._pingHistory.length - 1] const lastPing = this._pingHistory[this._pingHistory.length - 1]
const payload = { const payload = {
info: { serverId: this.serverId,
name: this.data.name
},
timestamp: lastPing.timestamp, timestamp: lastPing.timestamp,
versions: this.versions, versions: this.versions,
recordData: this.recordData, recordData: this.recordData,
favicon: this.lastFavicon favicon: this.lastFavicon
} }
// Only append graphPeakData if defined
// The value is lazy computed and conditional that config->logToDatabase == true
const graphPeakData = this.getGraphPeak()
if (graphPeakData) {
payload.graphPeakData = graphPeakData
}
// Conditionally append to avoid defining fields with undefined values // Conditionally append to avoid defining fields with undefined values
if (lastPing.result) { if (lastPing.result) {
payload.result = lastPing.result payload.result = lastPing.result
@ -118,15 +138,14 @@ class ServerRegistration {
} }
return [{ return [{
serverId: this.serverId,
timestamp: new Date().getTime(),
error: { error: {
message: 'Waiting...', message: 'Waiting...',
placeholder: true placeholder: true
}, },
timestamp: new Date().getTime(), recordData: this.recordData,
info: { graphPeakData: this.getGraphPeak()
name: this.data.name
},
recordData: this.recordData
}] }]
} }

@ -8,7 +8,7 @@ const servers = require('./servers')
const app = new App() const app = new App()
servers.forEach(server => { servers.forEach((server, serverId) => {
// Assign a generated color for each servers.json entry if not manually defined // Assign a generated color for each servers.json entry if not manually defined
// These will be passed to the frontend for use in rendering // These will be passed to the frontend for use in rendering
if (!server.color) { if (!server.color) {
@ -22,7 +22,7 @@ servers.forEach(server => {
} }
// Init a ServerRegistration instance of each entry in servers.json // Init a ServerRegistration instance of each entry in servers.json
app.serverRegistrations.push(new ServerRegistration(server)) app.serverRegistrations.push(new ServerRegistration(serverId, server))
}) })
if (!config.logToDatabase) { if (!config.logToDatabase) {

2
package-lock.json generated

@ -1,6 +1,6 @@
{ {
"name": "minetrack", "name": "minetrack",
"version": "5.1.2", "version": "5.2.0",
"lockfileVersion": 1, "lockfileVersion": 1,
"requires": true, "requires": true,
"dependencies": { "dependencies": {

@ -1,6 +1,6 @@
{ {
"name": "minetrack", "name": "minetrack",
"version": "5.1.2", "version": "5.2.0",
"description": "A Minecraft server tracker that lets you focus on the basics.", "description": "A Minecraft server tracker that lets you focus on the basics.",
"main": "main.js", "main": "main.js",
"dependencies": { "dependencies": {