This commit is contained in:
Cryptkeeper! 2017-03-14 17:07:58 -05:00 committed by GitHub
parent f1dfe2e21b
commit ac0ea0d5d7
11 changed files with 146 additions and 126 deletions

25
app.js

@ -15,6 +15,7 @@ var currentVersionIndex = {
'PC': 0, 'PC': 0,
'PE': 0 'PE': 0
}; };
var networkVersions = []; var networkVersions = [];
var graphData = []; var graphData = [];
@ -245,6 +246,8 @@ function startServices() {
client.emit('add', [networkHistory[networkHistoryKeys[i]]]); client.emit('add', [networkHistory[networkHistoryKeys[i]]]);
} }
} }
client.emit('syncComplete');
}); });
}); });
@ -260,14 +263,26 @@ if (config.logToDatabase) {
var timestamp = util.getCurrentTimeMs(); var timestamp = util.getCurrentTimeMs();
db.queryPings(config.graphDuration, function(data) { db.queryPings(config.graphDuration, function(data) {
var result = util.convertServerHistory(data); graphData = util.convertServerHistory(data);
completedQueries = 0;
graphData = result.graphData;
highestPlayerCount = result.highestPlayerCount;
logger.log('info', 'Queried and parsed ping history in %sms', util.getCurrentTimeMs() - timestamp); logger.log('info', 'Queried and parsed ping history in %sms', util.getCurrentTimeMs() - timestamp);
startServices(); for (var i = 0; i < servers.length; i++) {
(function(server) {
db.getTotalRecord(server.ip, function(record) {
logger.log('info', 'Completed query for %s', server.ip);
highestPlayerCount[server.ip] = record;
completedQueries += 1;
if (completedQueries === servers.length) {
startServices();
}
});
})(servers[i]);
}
}); });
} else { } else {
logger.log('warn', 'Database logging is not enabled. You can enable it by setting "logToDatabase" to true in config.json. This requires sqlite3 to be installed.'); logger.log('warn', 'Database logging is not enabled. You can enable it by setting "logToDatabase" to true in config.json. This requires sqlite3 to be installed.');

@ -29,8 +29,7 @@ a {
/* Constants */ /* Constants */
#header, #footer, #tagline { #header, #footer, #tagline {
width: 1540px;
margin: 0 auto;
} }
/* Header */ /* Header */
@ -40,6 +39,11 @@ a {
overflow: auto; overflow: auto;
} }
#header-wrapper {
overflow: auto;
min-width: 850px;
}
#header .column { #header .column {
display: inline-block; display: inline-block;
float: left; float: left;
@ -62,8 +66,8 @@ a {
#header a, #footer a { #header a, #footer a {
text-decoration: none; text-decoration: none;
color: inherit; color: inherit;
border-bottom: 1px dashed #3B3738; border-bottom: 1px dashed #3B3738;
} }
#header a:hover, #footer a:hover { #header a:hover, #footer a:hover {
border-bottom: 1px dashed transparent; border-bottom: 1px dashed transparent;
@ -81,57 +85,42 @@ a {
/* Footer */ /* Footer */
#footer { #footer {
font-size: 16px; font-size: 14px;
text-transform: uppercase; text-transform: uppercase;
background: #EBEBEB; background: #EBEBEB;
color: #3B3738; color: #3B3738;
padding: 15px 0; padding: 15px 0;
border-top-right-radius: 2px; min-width: 950px;
border-top-left-radius: 2px; margin-top: 15px;
} }
/* Tagline */ /* Tagline */
#tagline { #tagline-text {
padding: 10px 0; padding-top: 20px;
text-align: center; text-align: center;
border-bottom-right-radius: 2px;
border-bottom-left-radius: 2px;
}
/* Colors used by the Mojang service's status bar */
.status-online {
background: #87D37C;
color: #3B3738;
}
.status-unstable {
background: #E9E581;
color: #3B3738;
}
.status-offline {
background: #e74c3c;
}
.status-connecting {
background: #3498db;
} }
/* Server listing */ /* Server listing */
.server-container { .server-container {
width: 1540px;
margin: 10px auto;
overflow: auto; overflow: auto;
display: flex; flex-wrap: wrap; justify-content: center;
} }
.server { .server {
overflow: auto;
padding: 5px 10px; padding: 5px 10px;
margin: 0 5px; margin: 0 5px;
width: 740px; width: 738px;
border: 1px solid transparent;
display: inline-block; display: inline-block;
} }
/*.server:hover {
background: #282828;
border: 1px solid #444;
cursor: pointer;
border-radius: 2px;
}*/
.server > .column > img { .server > .column > img {
border-radius: 2px; border-radius: 2px;
margin-top: 5px; margin-top: 5px;
@ -180,7 +169,7 @@ a {
padding: 5px; padding: 5px;
border-radius: 3px; border-radius: 3px;
background: rgba(0, 0, 0, 0.65); background: rgba(0, 0, 0, 0.65);
z-index: 999; z-index: 10000;
} }
/* Existing elements */ /* Existing elements */
@ -254,9 +243,7 @@ h3 {
/* Percentage bar */ /* Percentage bar */
#perc-bar { #perc-bar {
margin-top: 6px; height: 35px;
width: 650px;
height: 50px;
position: relative; position: relative;
} }
@ -264,10 +251,25 @@ h3 {
height: 100%; height: 100%;
display: inline-block; display: inline-block;
position: absolute; position: absolute;
transition: 0.1s all;
} }
#perc-bar > .perc-bar-part:hover { #perc-bar > .perc-bar-part:hover {
opacity: 0.75; opacity: 0.75;
margin-top: -5px; }
transition: 0.2s all;
/* Mojang Status */
.mojang-status {
width: 60px;
height: 60px;
display: inline-block;
border-radius: 2px;
text-align: center;
line-height: 20px;
font-size: 12px;
}
.mojang-status > i {
margin-top: 8px;
font-size: 22px;
} }

@ -5,6 +5,7 @@
<head> <head>
<link rel="stylesheet" type="text/css" href="css/main.css"> <link rel="stylesheet" type="text/css" href="css/main.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.6.3/css/font-awesome.min.css">
<link rel="icon" type="image/png" href="/images/compass.png"> <link rel="icon" type="image/png" href="/images/compass.png">
@ -20,28 +21,32 @@
<div id="header"> <div id="header">
<div class="column"> <div id="header-wrapper">
<img src="/images/compass.png" width="28" height="28" style="display: inline-block; margin-right: 5px;"> <div class="column">
<h1 style="display: inline-block;" class="text-uppercase">Minetrack</h1>
<p class="subslogan text-uppercase">Watching <span id="stat_totalPlayers">0</span> players on <span id="stat_networks">0</span> networks.</p>
</div> <img src="/images/compass.png" width="28" height="28" style="display: inline-block; margin-right: 5px;">
<h1 style="display: inline-block;" class="text-uppercase">Minetrack</h1>
<p class="subslogan text-uppercase">Watching <span id="stat_totalPlayers">0</span> players on <span id="stat_networks">0</span> networks.</p>
<div class="column" style="float: right;"> </div>
<div id="perc-bar"></div> <div id="mojang-status-list" class="column" style="float: right;">
<div id="perc-bar-text"></div>
<div class="mojang-status" title="Sessions" id="mojang-status_Sessions"><i class="fa fa-gamepad"></i><br />Sessions</div>
<div class="mojang-status" title="Skins" id="mojang-status_Skins"><i class="fa fa-user"></i><br />Skins</div>
<div class="mojang-status" title="Auth" id="mojang-status_Auth"><i class="fa fa-key"></i><br />Auth</div>
<div class="mojang-status" title="API" id="mojang-status_API"><i class="fa fa-wrench"></i><br />API</div>
</div>
</div> </div>
</div> </div>
<div id="tagline" class="status-connecting"> <div id="perc-bar"></div>
<span id="tagline-text" class="text-uppercase">Connecting...</span> <div id="tagline-text">Connecting...</div>
</div>
<div id="big-graph"></div> <div id="big-graph"></div>
@ -76,7 +81,7 @@
<div id="footer"> <div id="footer">
<span style="padding-left: 15px;">Made with <span style="color: #e74c3c;">&#9829;</span> by <a href="http://cryptkpr.me">Cryptkeeper</a>. Read the source code on <a href="https://github.com/Cryptkeeper/Minetrack">Github</a>. Older records may not be reflected due to tracking behavior.</span> <span style="padding-left: 20px;">Made with <span style="color: #e74c3c;">&#9829;</span> by <a href="http://cryptkpr.me">Cryptkeeper</a>. Read the source code on <a href="https://github.com/Cryptkeeper/Minetrack">Github</a>. Older records may not be reflected due to tracking behavior.</span>
</div> </div>

@ -102,8 +102,7 @@ function sortServers() {
}); });
for (var x = 0; x < keys.length; x++) { for (var x = 0; x < keys.length; x++) {
$('#' + safeName(keys[x])).appendTo('#server-container-' + categories[i]); $('#container_' + safeName(keys[x])).appendTo('#server-container-' + categories[i]);
$('#ranking_' + safeName(keys[x])).text('#' + (x + 1)); $('#ranking_' + safeName(keys[x])).text('#' + (x + 1));
} }
} }
@ -121,8 +120,7 @@ function sortServers() {
}); });
for (var i = 0; i < serverNames.length; i++) { for (var i = 0; i < serverNames.length; i++) {
$('#' + safeName(serverNames[i])).appendTo('#server-container-all'); $('#container_' + safeName(serverNames[i])).appendTo('#server-container-all');
$('#ranking_' + safeName(serverNames[i])).text('#' + (i + 1)); $('#ranking_' + safeName(serverNames[i])).text('#' + (i + 1));
} }
} }
@ -132,7 +130,7 @@ function renderPercentageBarText(server) {
var totalPlayers = getCurrentTotalPlayers(); var totalPlayers = getCurrentTotalPlayers();
var playerCount = lastPlayerEntries[server]; var playerCount = lastPlayerEntries[server];
$('#perc-bar-text').html('<strong>' + server + '</strong><br />' + roundToPoint(playerCount / totalPlayers * 100, 10) + '% of ' + formatNumber(totalPlayers) + ' tracked players.'); showCaption('<strong>' + server + '</strong>: ' + formatNumber(playerCount) + ' online. <strong>' + roundToPoint(playerCount / totalPlayers * 100, 10) + '%</strong> of ' + formatNumber(totalPlayers) + ' tracked players.');
} }
function updatePercentageBar() { function updatePercentageBar() {
@ -167,17 +165,11 @@ function updatePercentageBar() {
div.mouseover(function(e) { div.mouseover(function(e) {
renderPercentageBarText(server); renderPercentageBarText(server);
var text = $('#perc-bar-text');
text.stop(true, false);
text.slideDown(100);
currentServerHover = server; currentServerHover = server;
}); });
div.mouseout(function(e) { div.mouseout(function(e) {
$('#perc-bar-text').slideUp(100); hideCaption();
currentServerHover = undefined; currentServerHover = undefined;
}); });
} }
@ -187,11 +179,7 @@ function updatePercentageBar() {
div.css({ div.css({
width: width + 'px', width: width + 'px',
left: leftPadding + 'px', left: leftPadding + 'px'
'border-top-left-radius': (pos === 0 ? '2px' : 0),
'border-bottom-left-radius': (pos === 0 ? '2px' : 0),
'border-top-right-radius': (pos === keys.length - 1 ? '2px' : 0),
'border-bottom-right-radius': (pos === keys.length ? '2px' : 0)
}); });
leftPadding += width; leftPadding += width;
@ -268,8 +256,7 @@ function validateBootTime(bootTime, socket) {
if (xhr.status === 200) { if (xhr.status === 200) {
validateBootTime(publicConfig.bootTime, socket); validateBootTime(publicConfig.bootTime, socket);
} else { } else {
$('#tagline').attr('class', 'status-offline'); showCaption('Failed to update! Refresh?');
$('#tagline-text').text('Failed to update! Refresh?');
} }
}); });
} }
@ -300,8 +287,7 @@ $(document).ready(function() {
lastMojangServiceUpdate = undefined; lastMojangServiceUpdate = undefined;
$('#tagline').attr('class', 'status-offline'); showCaption('Disconnected! Refresh?');
$('#tagline-text').text('Disconnected! Refresh?');
lastPlayerEntries = {}; lastPlayerEntries = {};
graphs = {}; graphs = {};
@ -428,10 +414,11 @@ $(document).ready(function() {
var safeNameCopy = safeName(info.name); var safeNameCopy = safeName(info.name);
$('<div/>', { $('<div/>', {
id: safeNameCopy, id: 'container_' + safeNameCopy,
class: 'server', class: 'server',
'server-id': safeNameCopy,
html: '<div id="server-' + safeNameCopy + '" class="column" style="width: 80px;">\ html: '<div id="server-' + safeNameCopy + '" class="column" style="width: 80px;">\
<img id="favicon_' + safeNameCopy + '" title="' + info.ip + printPort(info.port) + '">\ <img id="favicon_' + safeNameCopy + '" title="' + info.name + '\n' + info.ip + printPort(info.port) + '">\
<br />\ <br />\
<p class="text-center-align rank" id="ranking_' + safeNameCopy + '"></p>\ <p class="text-center-align rank" id="ranking_' + safeNameCopy + '"></p>\
</div>\ </div>\
@ -503,6 +490,14 @@ $(document).ready(function() {
} }
}); });
socket.on('syncComplete', function(data) {
$('#tagline-text').slideUp(100);
$(document).on('click', '.server', function(e) {
var serverId = $(this).attr('server-id');
});
});
$(document).on('click', '.graph-control', function(e) { $(document).on('click', '.graph-control', function(e) {
var serverIp = $(this).attr('data-target-network'); var serverIp = $(this).attr('data-target-network');
var checked = $(this).attr('checked'); var checked = $(this).attr('checked');
@ -531,4 +526,8 @@ $(document).ready(function() {
saveGraphControls(Object.keys(displayedGraphData)); saveGraphControls(Object.keys(displayedGraphData));
} }
}); });
$(window).on('resize', function() {
updatePercentageBar();
});
}); });

@ -8,6 +8,25 @@ var publicConfig;
var createdCategories = false; var createdCategories = false;
var categoriesVisible; var categoriesVisible;
var colorsByStatus = {
'Online': '#87D37C',
'Unstable': '#f1c40f',
'Offline': '#DE5749'
};
function showCaption(html) {
var tagline = $('#tagline-text');
tagline.stop(true, false);
tagline.html(html);
tagline.slideDown(100);
}
function hideCaption() {
var tagline = $('#tagline-text');
tagline.stop(true, false);
tagline.slideUp(100);
}
function setPublicConfig(json) { function setPublicConfig(json) {
publicConfig = json; publicConfig = json;
@ -79,43 +98,15 @@ function updateMojangServices(currentUpdate) {
return; return;
} }
var keys = Object.keys(lastMojangServiceUpdate); var keys = Object.keys(lastMojangServiceUpdate);
var newStatus = 'Mojang Services: ';
var serviceCountByType = {
Online: 0,
Unstable: 0,
Offline: 0
};
for (var i = 0; i < keys.length; i++) { for (var i = 0; i < keys.length; i++) {
var entry = lastMojangServiceUpdate[keys[i]]; var key = keys[i];
var status = lastMojangServiceUpdate[key];
serviceCountByType[entry.title] += 1; var div = $('#mojang-status_' + status.name);
div.css({background: colorsByStatus[status.title]});
} }
if (serviceCountByType['Online'] === keys.length) {
$('#tagline').attr('class', 'status-online');
newStatus += 'All systems operational.';
} else {
if (serviceCountByType['Unstable'] > serviceCountByType['Offline']) {
$('#tagline').attr('class', 'status-unstable');
} else {
$('#tagline').attr('class', 'status-offline');
}
for (var i = 0; i < keys.length; i++) {
var entry = lastMojangServiceUpdate[keys[i]];
if (entry.startTime) {
newStatus += entry.name + ' ' + entry.title.toLowerCase() + ' for ' + msToTime((new Date()).getTime() - entry.startTime);
if (i < keys.length - 1) newStatus += ', ';
}
}
}
$('#tagline-text').text(newStatus);
} }
function findErrorMessage(error) { function findErrorMessage(error) {

@ -1,3 +1,8 @@
**3.1.0** *(Mar 14 2017)*
- Updated design. More flexible!
- Automatically builds indexes on database.
- Fixes issue with record query.
**3.0.0** *(Mar 11 2017)* **3.0.0** *(Mar 11 2017)*
- Adds player count records. - Adds player count records.
- Adds "serverTypesVisible" to hide PC/PE badges. - Adds "serverTypesVisible" to hide PC/PE badges.

@ -7,6 +7,8 @@ exports.setup = function() {
db.serialize(function() { db.serialize(function() {
db.run('CREATE TABLE IF NOT EXISTS pings (timestamp BIGINT NOT NULL, ip TINYTEXT, playerCount MEDIUMINT)'); db.run('CREATE TABLE IF NOT EXISTS pings (timestamp BIGINT NOT NULL, ip TINYTEXT, playerCount MEDIUMINT)');
db.run('CREATE INDEX IF NOT EXISTS ip_index ON pings (ip, playerCount)');
db.run('CREATE INDEX IF NOT EXISTS timestamp_index on PINGS (timestamp)');
}); });
exports.log = function(ip, timestamp, playerCount) { exports.log = function(ip, timestamp, playerCount) {
@ -19,6 +21,14 @@ exports.setup = function() {
insertStatement.finalize(); insertStatement.finalize();
}; };
exports.getTotalRecord = function(ip, callback) {
db.all("SELECT MAX(playerCount) FROM pings WHERE ip = ?", [
ip
], function(err, data) {
callback(data[0]['MAX(playerCount)']);
});
};
exports.queryPings = function(duration, callback) { exports.queryPings = function(duration, callback) {
var currentTime = util.getCurrentTimeMs(); var currentTime = util.getCurrentTimeMs();

@ -44,7 +44,7 @@ function handleRequest(req, res) {
var categories = config.serverCategories; var categories = config.serverCategories;
// Legacy support for people without categories configured. // Legacy support for people without categories configured.
if (!categories) { if (!categories || Object.keys(categories).length === 0) {
categories = { categories = {
'default': 'All Networks' 'default': 'All Networks'
}; };

@ -111,7 +111,6 @@ exports.setIntervalNoDelay = function(func, delay) {
exports.convertServerHistory = function(sqlData) { exports.convertServerHistory = function(sqlData) {
var serverIps = getServerIps(); var serverIps = getServerIps();
var graphData = {}; var graphData = {};
var highestPlayerCount = {};
var startTime = exports.getCurrentTimeMs(); var startTime = exports.getCurrentTimeMs();
@ -124,9 +123,6 @@ exports.convertServerHistory = function(sqlData) {
if (!graphData[name]) graphData[name] = []; if (!graphData[name]) graphData[name] = [];
graphData[name].push([entry.timestamp, entry.playerCount]); graphData[name].push([entry.timestamp, entry.playerCount]);
if (!highestPlayerCount[entry.ip]) highestPlayerCount[entry.ip] = 0;
if (entry.playerCount > highestPlayerCount[entry.ip]) highestPlayerCount[entry.ip] = entry.playerCount;
} }
// Break it into minutes. // Break it into minutes.
@ -137,10 +133,7 @@ exports.convertServerHistory = function(sqlData) {
logger.info('Parsed ' + sqlData.length + ' ping records in ' + (exports.getCurrentTimeMs() - startTime) + 'ms'); logger.info('Parsed ' + sqlData.length + ' ping records in ' + (exports.getCurrentTimeMs() - startTime) + 'ms');
return { return graphData;
graphData: graphData,
highestPlayerCount: highestPlayerCount
};
}; };
exports.getBootTime = function() { exports.getBootTime = function() {

@ -1,6 +1,6 @@
{ {
"name": "minetrack", "name": "minetrack",
"version": "3.0.0", "version": "3.1.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": "app.js", "main": "app.js",
"dependencies": { "dependencies": {