From f875361bc72d51a49aebb20e99d87af503d0f0c1 Mon Sep 17 00:00:00 2001 From: Nick Krecklow Date: Sun, 19 Apr 2020 19:27:59 -0500 Subject: [PATCH] Minetrack 5 (#143) * remove unused #getServer methods, inline #roundToPoint * replace #safeName regex with incremental ids * remove legacy #setInterval based #updateMojangServices handling * add Tooltip class, move faviconSize to css instead of js * move server id assignment to ServerRegistry * move printPort logic to formatMinecraftServerAddress, add MINECRAFT_DEFAULT_PORTS * simplify ping tracking * rework perc-bar tooltip to not use mousemove event * begin moving graphing logic to GraphDisplayManager * begin merge graph point tracking into graphDisplayManager * centralizing graphing logic into GraphDisplayManager * properly reset GraphDisplayManager when handling disconnects * move individual server graph data into ServerGraph class * constantly run sortServers loop to simplify logic * inline #updateMojangServices method * resize performance improvements * remove legacy bootTime refresh behavior, require manual user refresh * move class defs to core.js * remove unused #isGraphDataVisible arg * remove #toggleControlsDrawer * dont call #updatePercentageBar in #updateServerStatus calls * centralize caption handling * inline #msToTime * remove hackish seconds handling for timestamps * reduce #forEach calls with filter/map * safely fallback to errorMessage if errno/description does not match * Add /images/missing_favicon.png path instead of putting base64 in js * remove debug * cleanup mojang status handling * move historyPlot instance into GraphDisplayManager * cleanup checkbox html generation * cleanup #updateServerStatus * fix up tooltip styling * move jquery code out of core.js * fix add server race condition when initially pinging servers * send error.placeholder=true for pending pings so the frontend can discard later * filter placeholder pings sent by the backend * del assets/images/logo_2014.png * move graph code into graph.js * merge pingTracker into ServerRegistry+ServerGraph * remove todos * simplify getVisibleGraphData * fix potential sortServers race condition when adding * use #show instead of #fadeIn(0) * remove publicConfig.json, send over socket * update docs/CHANGELOG.md * getOrAssign -> getOrCreateId * dont delete graph controls when disconnected * early work cleaning up HTML+CSS structures * cleanup server css elements * cleanup graph control css elements * move base CSS color values into @media(prefers-color-scheme: light) * move CSS magic colors to vars * reduce duplicated CSS color rules * inline body text color CSS * WIP replacing jQuery calls with vanilla JS * WIP replacing jQuery calls with vanilla JS * replace getElementsByClass with querySelectorAll * typeMarker -> serverTypeHTML * use jQuery slim for remaining flot.js dependency * merge setAllGraphVisibility into GraphDisplayManager * break apart element update and redraw logic * add eslint + parcel bundler * auto lint assets/js when building * statically serve favicons/ for faviconOverrides outside of dist/ * only send favicons when changed * move faviconOverride behavior into entry in servers.json * add warning to backend server files * remove .server-favicon-missing class * add Minetrack 5 migration guide * add npm run build step to install.sh * adjust package.json version to 5.0.0 * remove js references from index.html * move logic and behavior out of site.js * cleanup ServerRegistry methods * prevent multiple history graph redraws * add comments * cleanup #addServer usage, move to App * move graph control bindings into GraphDisplayManager * site.js -> main.js, core.js -> servers.js * move Tooltip/Caption into util.js * spacing tweak * format index.html * ensure the frontend does not handling updateHistoryGraph events * prevent versions/record updates if the same value * avoid empty percbar updates, ensure versions are sorted * only include main.js ref in index.html * serve minified copy of font awesome directly * bundle icons.css into main.css, remove Open Sans 400 * add new SVG logo * update docs/CHANGELOG.md * new design, server version grouping * remove start.sh call from install.sh * move graph controls into header with new button * move #handleSettingsToggle back to graph * fix legacy code behavior of currentVersionIndex applying globally * fix header text color in light mode * fix mojang status text color in light mode * fix toggle settings and checkbox colors * tweak button hover color * tweak button hover color * add new status-overlay to avoid complicated DOM management during loading * fix initial graph rendering bug * add comments * update default graph tick sizes * prevent #tooltip from overflowing page * remove localhost spec * prevent minor connection errors from reshuffling layout * update CHANGELOG.md * add message/button for manually loading historical graph on mobile devices * send isGraphVisible to frontend to prevent alert if logToDatabase: false * send timestamp data with record * update docs/CHANGELOG.md * remove clock icon * remove 24h peak timestamp * Only check favicon if present * safely handle undefined/empty knownVersions in #formatMinecraftVersions * merge config.versions and minecraft.json into minecraft_versions.json, simplify index matching behavior * remove localhost url in socket.io config * stub methods/linkage for FocusManager * add #isObjectEqual hack, add event proxying to FocusManager * wip extended stats box * remove server-type badging * tweak mojang unstable color * serve socket.io-client using parcel * fix incorrect mojang status colors * remove legacy capitalization design * redesign focus boxes * update docs/CHANGELOG.md * remove localhost ref * color clock icon * use background-color for hover effect, remove unused var * improve stats focus box icons * change mojang sessions icon to globe * Add favorites system * remove focus boxes * update docs/CHANGELOG.md * remove focus icons from font * simplify graph related event binding * Add Sort By button * store current sortOption in localStorage * update docs/CHANGELOG.md * move magic 0 sortOption to SORT_OPTION_INDEX_DEFAULT * remove localhost ref * merge #settings-toggle, #sort-by and .mojang-status CSS * remove .focus-box CSS * use sortedServerIds for _lastSortedServers * tweak --color-blue * new missing_favicon design to match logo * edit footer CSS/text, remove github icon * replace player count diff counter with GROWTH sort option * italize non-default sort options * add Only Favorites button to auto sync favorites to the visible graph data * add icons to graph control buttons * update docs/CHANGELOG.md * use * to denote non-default sort option instead * remove localhost url in socket.io config * add value highlighting to make sort by easier to read * remove last remaining uppercase text * remove serverTypesVisible from config.json * simplify header CSS, fix spacing with logToDatabase=false * fix inverted text color on highlighted values * remove localhost url in socket.io config * break header into rows on mobile devices Co-authored-by: Hugo Manrique --- .babelrc | 5 + .eslintrc.json | 20 + .gitignore | 4 +- README.md | 3 + app.js | 223 +- assets/css/icons.css | 59 + assets/css/main.css | 532 +- assets/fonts/icomoon.svg | 20 + assets/fonts/icomoon.ttf | Bin 0 -> 5520 bytes assets/fonts/icomoon.woff | Bin 0 -> 5596 bytes assets/html/index.html | 143 +- assets/images/compass.png | Bin 6403 -> 0 bytes assets/images/logo.svg | 8 + assets/images/logo_2014.png | Bin 30848 -> 0 bytes assets/images/missing_favicon.svg | 8 + assets/js/app.js | 140 + assets/js/favorites.js | 69 + assets/js/graph.js | 468 +- assets/js/main.js | 159 + assets/js/mojang.js | 20 + assets/js/percbar.js | 72 + assets/js/servers.js | 304 ++ assets/js/site.js | 538 -- assets/js/sort.js | 185 + assets/js/util.js | 305 +- config.json | 30 +- docs/CHANGELOG.md | 35 + docs/CREDITS.md | 1 - docs/MIGRATING.md | 40 + lib/database.js | 9 +- lib/logger.js | 5 + lib/mojang_services.js | 5 + lib/ping.js | 10 +- lib/server.js | 90 +- lib/util.js | 17 +- minecraft.json | 16 - minecraft_versions.json | 44 + package-lock.json | 8010 ++++++++++++++++++++++++++++- package.json | 25 +- scripts/install.sh | 2 +- servers.json | 10 + 41 files changed, 10304 insertions(+), 1330 deletions(-) create mode 100644 .babelrc create mode 100644 .eslintrc.json create mode 100644 assets/css/icons.css create mode 100644 assets/fonts/icomoon.svg create mode 100644 assets/fonts/icomoon.ttf create mode 100644 assets/fonts/icomoon.woff delete mode 100644 assets/images/compass.png create mode 100644 assets/images/logo.svg delete mode 100644 assets/images/logo_2014.png create mode 100644 assets/images/missing_favicon.svg create mode 100644 assets/js/app.js create mode 100644 assets/js/favorites.js create mode 100644 assets/js/main.js create mode 100644 assets/js/mojang.js create mode 100644 assets/js/percbar.js create mode 100644 assets/js/servers.js delete mode 100644 assets/js/site.js create mode 100644 assets/js/sort.js delete mode 100644 docs/CREDITS.md create mode 100644 docs/MIGRATING.md delete mode 100644 minecraft.json create mode 100644 minecraft_versions.json diff --git a/.babelrc b/.babelrc new file mode 100644 index 0000000..7b5554a --- /dev/null +++ b/.babelrc @@ -0,0 +1,5 @@ +{ + "plugins": [ + "@babel/plugin-proposal-class-properties" + ] +} diff --git a/.eslintrc.json b/.eslintrc.json new file mode 100644 index 0000000..0ec52d3 --- /dev/null +++ b/.eslintrc.json @@ -0,0 +1,20 @@ +{ + "env": { + "browser": true, + "es6": true, + "jquery": true + }, + "extends": [ + "standard" + ], + "globals": { + "Atomics": "readonly", + "SharedArrayBuffer": "readonly" + }, + "parserOptions": { + "ecmaVersion": 2018 + }, + "rules": { + }, + "parser": "babel-eslint" +} diff --git a/.gitignore b/.gitignore index e0f3e0d..5985360 100644 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,6 @@ minetrack.log .idea/ database.sql database.sql-journal -.DS_Store \ No newline at end of file +.DS_Store +dist/ +.cache/ diff --git a/README.md b/README.md index 3824a6a..d2be1ef 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,9 @@ Minetrack is a Minecraft PC/PE server tracker that lets you focus on what's happening *now*. Built to be lightweight and durable, you can easily adapt it to monitor BungeeCord or server instances. +#### Migrating to Minetrack 5 +See our Minetrack 5 [migration guide](docs/MIGRATING.md). + #### This project is not actively maintained! This project and the offical website are not actively maintained anymore, but you are welcome to run your own instances of Minetrack. I will however review and accept pull-requests, so please share any improvements you are making so everybody can benefit from them. diff --git a/app.js b/app.js index fdaac82..674f0c7 100644 --- a/app.js +++ b/app.js @@ -1,3 +1,8 @@ +/** + * THIS IS LEGACY, UNMAINTAINED CODE + * IT MAY (AND LIKELY DOES) CONTAIN BUGS + * USAGE IS NOT RECOMMENDED + */ var server = require('./lib/server'); var ping = require('./lib/ping'); var logger = require('./lib/logger'); @@ -7,22 +12,44 @@ var db = require('./lib/database'); var config = require('./config.json'); var servers = require('./servers.json'); +var minecraftVersions = require('./minecraft_versions.json'); var networkHistory = []; var connectedClients = 0; -var currentVersionIndex = { - 'PC': 0, - 'PE': 0 -}; - var networkVersions = []; +const lastFavicons = []; + var graphData = []; var highestPlayerCount = {}; var lastGraphPush = []; var graphPeaks = {}; +const serverProtocolVersionIndexes = [] + +function getNextProtocolVersion (server) { + // Minecraft Bedrock Edition does not have protocol versions + if (server.type === 'PE') { + return { + protocolId: 0, + protocolIndex: 0 + } + } + const protocolVersions = minecraftVersions[server.type] + let nextProtocolVersion = serverProtocolVersionIndexes[server.name] + if (typeof nextProtocolVersion === 'undefined' || nextProtocolVersion + 1 >= protocolVersions.length) { + nextProtocolVersion = 0 + } else { + nextProtocolVersion++ + } + serverProtocolVersionIndexes[server.name] = nextProtocolVersion + return { + protocolId: protocolVersions[nextProtocolVersion].protocolId, + protocolIndex: nextProtocolVersion + } +} + function pingAll() { for (var i = 0; i < servers.length; i++) { // Make sure we lock our scope. @@ -32,7 +59,7 @@ function pingAll() { network.color = util.stringToColor(network.name); } - var attemptedVersion = config.versions[network.type][currentVersionIndex[network.type]]; + const attemptedVersion = getNextProtocolVersion(network) ping.ping(network.ip, network.port, network.type, config.rates.connectTimeout, function(err, res) { // Handle our ping results, if it succeeded. if (err) { @@ -40,27 +67,14 @@ function pingAll() { } // If we have favicon override specified, use it. - if (res && config.faviconOverride && config.faviconOverride[network.name]) { - res.favicon = config.faviconOverride[network.name]; + if (network.favicon) { + res.favicon = network.favicon } handlePing(network, res, err, attemptedVersion); - }, attemptedVersion); + }, attemptedVersion.protocolId); })(servers[i]); } - - currentVersionIndex['PC']++; - currentVersionIndex['PE']++; - - if (currentVersionIndex['PC'] >= config.versions['PC'].length) { - // Loop around - currentVersionIndex['PC'] = 0; - } - - if (currentVersionIndex['PE'] >= config.versions['PE'].length) { - // Loop around - currentVersionIndex['PE'] = 0; - } } // This is where the result of a ping is feed. @@ -76,18 +90,31 @@ function handlePing(network, res, err, attemptedVersion) { networkVersions[network.name] = []; } + const serverVersionHistory = networkVersions[network.name] + // If the result version matches the attempted version, the version is supported - var _networkVersions = networkVersions[network.name]; + if (res && res.version !== undefined) { + const indexOf = serverVersionHistory.indexOf(attemptedVersion.protocolIndex) + + // Test indexOf to avoid inserting previously recorded protocolIndex values + if (res.version === attemptedVersion.protocolId && indexOf === -1) { + serverVersionHistory.push(attemptedVersion.protocolIndex) + } else if (res.version !== attemptedVersion.protocolId && indexOf >= 0) { + serverVersionHistory.splice(indexOf, 1) + } + } + + const timestamp = util.getCurrentTimeMs() + if (res) { - if (res.version == attemptedVersion) { - if (_networkVersions.indexOf(res.version) == -1) { - _networkVersions.push(res.version); - } - } else { - // Mismatch, so remove the version from the supported version list - var index = _networkVersions.indexOf(attemptedVersion); - if (index != -1) { - _networkVersions.splice(index, 1); + const recordData = highestPlayerCount[network.ip] + + // Validate that we have logToDatabase enabled otherwise in memory pings + // will create a record that's only valid for the runtime duration. + if (config.logToDatabase && (!recordData || res.players.online > recordData.playerCount)) { + highestPlayerCount[network.ip] = { + playerCount: res.players.online, + timestamp: timestamp } } } @@ -96,20 +123,25 @@ function handlePing(network, res, err, attemptedVersion) { var networkSnapshot = { info: { name: network.name, - timestamp: util.getCurrentTimeMs(), + timestamp: timestamp, type: network.type }, - versions: _networkVersions, - record: highestPlayerCount[network.ip] + versions: serverVersionHistory, + recordData: highestPlayerCount[network.ip] }; if (res) { networkSnapshot.result = res; - // Validate that we have logToDatabase enabled otherwise in memory pings - // will create a record that's only valid for the runtime duration. - if (config.logToDatabase && res.players.online > highestPlayerCount[network.ip]) { - highestPlayerCount[network.ip] = res.players.online; + // Only emit updated favicons + // Favicons will otherwise be explicitly emitted during the handshake process + if (res.favicon) { + const lastFavicon = lastFavicons[network.name] + if (lastFavicon !== res.favicon) { + lastFavicons[network.name] = res.favicon + networkSnapshot.favicon = res.favicon // Send updated favicon directly on object + } + delete res.favicon // Never store favicons in memory outside lastFavicons } } else if (err) { networkSnapshot.error = err; @@ -121,18 +153,15 @@ function handlePing(network, res, err, attemptedVersion) { // Remove our previous data that we don't need anymore. for (var i = 0; i < _networkHistory.length; i++) { + delete _networkHistory[i].versions delete _networkHistory[i].info; - - if (_networkHistory[i].result) { - delete _networkHistory[i].result.favicon; - } } _networkHistory.push({ error: err, result: res, - versions: _networkVersions, - timestamp: util.getCurrentTimeMs(), + versions: serverVersionHistory, + timestamp: timestamp, info: { ip: network.ip, port: network.port, @@ -148,18 +177,16 @@ function handlePing(network, res, err, attemptedVersion) { // Log it to the database if needed. if (config.logToDatabase) { - db.log(network.ip, util.getCurrentTimeMs(), res ? res.players.online : 0); + db.log(network.ip, timestamp, res ? res.players.online : 0); } - // Push it to our graphs. - var timeMs = util.getCurrentTimeMs(); // The same mechanic from trimUselessPings is seen here. // If we dropped the ping, then to avoid destroying the graph, ignore it. // However if it's been too long since the last successful ping, we'll send it anyways. if (config.logToDatabase) { - if (!lastGraphPush[network.ip] || (timeMs - lastGraphPush[network.ip] >= 60 * 1000 && res) || timeMs - lastGraphPush[network.ip] >= 70 * 1000) { - lastGraphPush[network.ip] = timeMs; + if (!lastGraphPush[network.ip] || (timestamp - lastGraphPush[network.ip] >= 60 * 1000 && res) || timestamp - lastGraphPush[network.ip] >= 70 * 1000) { + lastGraphPush[network.ip] = timestamp; // Don't have too much data! util.trimOldPings(graphData); @@ -168,14 +195,14 @@ function handlePing(network, res, err, attemptedVersion) { graphData[network.name] = []; } - graphData[network.name].push([timeMs, res ? res.players.online : 0]); + graphData[network.name].push([timestamp, res ? res.players.online : 0]); // Send the update. server.io.sockets.emit('updateHistoryGraph', { ip: network.ip, name: network.name, players: (res ? res.players.online : 0), - timestamp: timeMs + timestamp: timestamp }); } @@ -234,10 +261,6 @@ function startServices() { logger.log('info', '%s connected, total clients: %d', util.getRemoteAddr(client.request), connectedClients); - // We send the boot time (also sent in publicConfig.json) to the frontend to validate they have the same config. - // If so, they'll send back "requestListing" event, otherwise they will pull the new config and retry. - client.emit('bootTime', util.getBootTime()); - // Attach our listeners. client.on('disconnect', function() { connectedClients -= 1; @@ -257,41 +280,54 @@ function startServices() { } }); - client.on('requestListing', function() { - // Send them our previous data, so they have somewhere to start. - client.emit('updateMojangServices', mojang.toMessage()); + const minecraftVersionNames = {} + Object.keys(minecraftVersions).forEach(function (key) { + minecraftVersionNames[key] = minecraftVersions[key].map(version => version.name) + }) - // Remap our associative array into just an array. - var networkHistoryKeys = Object.keys(networkHistory); - - networkHistoryKeys.sort(); - - // Send each individually, this should look cleaner than waiting for one big array to transfer. - for (var i = 0; i < servers.length; i++) { - var server = servers[i]; - - if (!(server.name in networkHistory) || networkHistory[server.name].length < 1) { - // This server hasn't been ping'd yet. Send a hacky placeholder. - client.emit('add', [[{ - error: { - description: 'Waiting' - }, - result: null, - timestamp: util.getCurrentTimeMs(), - info: { - ip: server.ip, - port: server.port, - type: server.type, - name: server.name - } - }]]); - } else { - client.emit('add', [networkHistory[networkHistoryKeys[i]]]); - } - } - - client.emit('syncComplete'); + // Send configuration data for rendering the page + client.emit('setPublicConfig', { + graphDuration: config.graphDuration, + servers: servers, + minecraftVersions: minecraftVersionNames, + isGraphVisible: config.logToDatabase }); + + // Send them our previous data, so they have somewhere to start. + client.emit('updateMojangServices', mojang.toMessage()); + + // Send each individually, this should look cleaner than waiting for one big array to transfer. + for (var i = 0; i < servers.length; i++) { + var server = servers[i]; + + if (!(server.name in networkHistory) || networkHistory[server.name].length < 1) { + // This server hasn't been ping'd yet. Send a hacky placeholder. + client.emit('add', [[{ + error: { + description: 'Waiting...', + placeholder: true + }, + result: null, + timestamp: util.getCurrentTimeMs(), + info: { + ip: server.ip, + port: server.port, + type: server.type, + name: server.name + } + }]]); + } else { + // Append the lastFavicon to the last ping entry + const serverHistory = networkHistory[server.name]; + const lastFavicon = lastFavicons[server.name]; + if (lastFavicon) { + serverHistory[serverHistory.length - 1].favicon = lastFavicon + } + client.emit('add', [serverHistory]) + } + } + + client.emit('syncComplete'); }); startMainLoop(); @@ -332,10 +368,13 @@ if (config.logToDatabase) { } (function(server) { - db.getTotalRecord(server.ip, function(record) { - logger.log('info', 'Computed total record %s (%d)', server.ip, record); + db.getTotalRecord(server.ip, function(playerCount, timestamp) { + logger.log('info', 'Computed total record %s (%d) @ %d', server.ip, playerCount, timestamp); - highestPlayerCount[server.ip] = record; + highestPlayerCount[server.ip] = { + playerCount: playerCount, + timestamp: timestamp + }; completedQueries += 1; diff --git a/assets/css/icons.css b/assets/css/icons.css new file mode 100644 index 0000000..a9b0e38 --- /dev/null +++ b/assets/css/icons.css @@ -0,0 +1,59 @@ +@font-face { + font-family: 'icomoon'; + src: + url('../fonts/icomoon.ttf?gn52nv') format('truetype'), + url('../fonts/icomoon.woff?gn52nv') format('woff'), + url('../fonts/icomoon.svg?gn52nv#icomoon') format('svg'); + font-weight: normal; + font-style: normal; + font-display: block; +} + +[class^="icon-"], [class*=" icon-"] { + /* use !important to prevent issues with browser extensions that change fonts */ + font-family: 'icomoon' !important; + speak: none; + font-style: normal; + font-weight: normal; + font-variant: normal; + text-transform: none; + line-height: 1; + + /* Better Font Rendering =========== */ + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +.icon-star:before { + content: "\f005"; +} +.icon-star-o:before { + content: "\f006"; +} +.icon-lock:before { + content: "\f023"; +} +.icon-eye:before { + content: "\f06e"; +} +.icon-eye-slash:before { + content: "\f070"; +} +.icon-cogs:before { + content: "\f085"; +} +.icon-gears:before { + content: "\f085"; +} +.icon-globe:before { + content: "\f0ac"; +} +.icon-code:before { + content: "\f121"; +} +.icon-sort-amount-desc:before { + content: "\f161"; +} +.icon-street-view:before { + content: "\f21d"; +} diff --git a/assets/css/main.css b/assets/css/main.css index 076bccc..d802598 100644 --- a/assets/css/main.css +++ b/assets/css/main.css @@ -1,16 +1,62 @@ -@import url(https://fonts.googleapis.com/css?family=Open+Sans:700,300,400); +@import url(https://fonts.googleapis.com/css?family=Open+Sans:700,300); +@import url(../css/icons.css); * { margin: 0; padding: 0; } -body { +:root { + --color-dark-gray: #A3A3A3; + --color-gold: #FFD700; + --color-dark-purple: #6c5ce7; + --color-light-purple: #a29bfe; + --color-dark-blue: #0984e3; + --color-light-blue: #74b9ff; + + --theme-color-dark: #3B3738; + --theme-color-light: #EBEBEB; + + --border-radius: 1px; +} + +@media (prefers-color-scheme: light) { + :root { + --color-purple: var(--color-light-purple); + --color-blue: var(--color-light-blue); + --background-color: var(--theme-color-light); + --text-decoration-color: var(--theme-color-dark); + --text-color: #000; + --text-color-inverted: #FFF; + } + + body { background: #212021; color: #FFF; - font-family: "Open Sans", sans-serif; - font-size: 18px; - font-weight: 300 !important; + } +} + +@media (prefers-color-scheme: dark) { + :root { + --color-purple: var(--color-dark-purple); + --color-blue: var(--color-dark-blue); + --background-color: var(--theme-color-dark); + --text-decoration-color: var(--theme-color-light); + --text-color: #FFF; + --text-color-inverted: #000; + } + + body { + background: #1c1b1c; + color: #FFF; + } +} + +body { + font-family: "Open Sans", sans-serif; + font-size: 18px; + font-weight: 300; + min-width: 800px; } /* Page layout */ @@ -19,195 +65,287 @@ html, body { } a { - cursor: pointer; + cursor: pointer; + color: inherit; + text-decoration: none; } #push { - position: relative; - min-height: 100%; + display: none; + position: relative; + min-height: 100%; } strong { font-weight: 700; } +/* Logo */ +.logo-text { + letter-spacing: -3px; +} + /* Header */ -#header { - background: #EBEBEB; - color: #3B3738; - overflow: auto; +header { + overflow: auto; + padding: 20px; } -#header-wrapper { - overflow: auto; - min-width: 850px; +header .column-left { + float: left; } -#header .column { - display: inline-block; - float: left; +header .column-right { + float: right; } -#header .column h1 { - margin: -6px 0; +header .logo-image { + --fixed-logo-image-size: 36px; + width: var(--fixed-logo-image-size); + height: var(--fixed-logo-image-size); + margin-right: 5px; + display: inline-block; } -#header .slogan { - font-size: 20px; - text-align: left; +header .logo-text { + font-size: 48px; + margin: -6px 0; + display: inline-block; } -#header .subslogan { - font-size: 19px; +header .logo-status { + font-size: 21px; } -#header a, #footer a { - text-decoration: none; - color: inherit; - border-bottom: 1px dashed #3B3738; +header a { + border-bottom: 1px dashed var(--text-decoration-color); } -#header a:hover, #footer a:hover { - border-bottom: 1px dashed transparent; +header a:hover { + border-bottom: 1px dashed transparent; } -#header > h1 { - font-size: 42px; +header .header-button { + color: var(--text-color); + width: 83px; + height: 83px; + text-align: center; + line-height: 20px; + font-size: 14px; + margin-left: -5px; } -#header > #column-center { - width: 1480px; - margin: 0 auto; - text-align: center; +header .header-button > span:first-of-type { + display: block; + margin-top: 10px; + font-size: 22px; +} + +header .header-button-group { + display: inline-block; +} + +header .header-button-group:first-of-type { + border-top-left-radius: var(--border-radius); + border-bottom-left-radius: var(--border-radius); +} + +header .header-button-group:last-of-type { + border-top-right-radius: var(--border-radius); + border-bottom-right-radius: var(--border-radius); +} + +header .header-button-single { + display: none; + cursor: pointer; + border-radius: var(--border-radius); + margin-right: 20px; +} + +header .header-button-single:hover { + background: var(--background-color) !important; } /* Footer */ -#footer { - font-size: 16px; - text-transform: uppercase; - background: #EBEBEB; - color: #3B3738; - padding: 15px 0; - min-width: 950px; - margin-top: 15px; +footer { + display: none; + background: var(--background-color); + color: var(--text-color); + padding: 10px 0 15px 0; + text-align: center; + margin-top: 15px; } -#footer a { - font-weight: 700; - border-bottom: none !important; +footer a { + border-bottom: 1px dashed var(--text-decoration-color); } -#footer a:hover { - border-bottom: 1px dashed #000 !important; +footer a:hover { + border-bottom: 1px dashed transparent; } -/* Tagline */ -#tagline-text { - padding-top: 20px; - text-align: center; +/* Status overly */ +#status-overlay { + padding: 20px 0; + background: var(--background-color); + color: var(--text-color); + border-radius: var(--border-radius); + text-align: center; + height: 150px; + width: 350px; + position: fixed; + top: 50%; + left: 50%; + margin-top: -95px; + margin-left: -175px; +} + +#status-overlay .logo-image { + --fixed-logo-image-size: 72px; + width: var(--fixed-logo-image-size); + height: var(--fixed-logo-image-size); +} + +/* Floating tooltip */ +#tooltip { + display: none; + position: absolute; + padding: 5px 8px; + border-radius: var(--border-radius); + background: var(--background-color); + color: var(--text-color); + z-index: 10000; } /* Server listing */ -.server-container { - overflow: auto; - display: flex; - flex-wrap: wrap; - justify-content: center; +#server-list { + overflow: auto; + display: flex; + flex-wrap: wrap; + justify-content: center; } .server { - padding: 5px 10px; - margin: 0 5px; - width: 800px; - border: 1px solid transparent; - display: inline-block; + padding: 5px 10px; + margin: 0 5px; + width: 800px; + display: inline-block; } -.version { - font-size: 12px; +.server .column { + float: left; } -/*.server:hover { - background: #282828; - border: 1px solid #444; - cursor: pointer; - border-radius: 2px; -}*/ - -.server > .column > img { - border-radius: 2px; - margin-top: 5px; +.server .column-favicon { + width: 80px; } -.server > .column { - float: left; - display: inline-block; +.server .column-favicon .server-favicon { + --fixed-server-favicon-size: 64px; + width: var(--fixed-server-favicon-size); + height: var(--fixed-server-favicon-size); + border-radius: var(--border-radius); + margin-top: 5px; } -.server > .column > .rank { - width: 64px; - padding-top: 4px; +.server .column-favicon .server-rank { + display: block; + width: 64px; + text-align: center; } -.server > .column > h3 > .type { - padding: 1px 5px; - border-radius: 2px; - border: 1px solid #A09E9E; - font-size: 14px; - margin-bottom: 2px; +.server .column-status { + width: 282px; } -.server-meta { - font-size: 16px !important; +.server .column-status .server-name { + display: inline-block; } -/* Charts */ -.chart { - height: 100px; - width: 400px; - margin-right: -3px; - margin-bottom: 5px; +.server .column-status .server-is-favorite { + cursor: pointer; + color: var(--color-gold); } -#tooltip { - display: none; - position: absolute; - padding: 5px; - border-radius: 3px; - background: rgba(0, 0, 0, 0.65); - z-index: 10000; +.server .column-status .server-is-favorite:hover::before { + content: "\f006"; } -/* Existing elements */ -h3 { - text-transform: uppercase; +.server .column-status .server-is-not-favorite { + cursor: pointer; + color: var(--background-color); } -/* Basic classes used randomly */ -.color-gray { - color: #C4C4C4; +.server .column-status .server-is-not-favorite:hover { + color: var(--color-gold); } -.color-dark-gray { - color: #A3A3A3; +.server .column-status .server-error { + display: none; + color: #e74c3c; } -.color-red { - color: #e74c3c; +.server .column-status .server-label { + color: var(--color-dark-gray); + font-size: 16px; + display: none; } -.text-uppercase { - text-transform: uppercase; +.server .column-status .server-value { + color: var(--color-dark-gray); + font-size: 16px; } -.text-center-align { - text-align: center; +.server .column-graph { + float: right; + height: 100px; + width: 400px; + margin-right: -3px; + margin-bottom: 5px; +} + +/* Highlighted values */ +.server-highlighted-label { + font-size: 18px; +} + +.server-highlighted-value { + font-size: 18px; + font-weight: 700; +} + +/* Global stats */ +.global-stat { + font-weight: 700; +} + +/* Sort by */ +#sort-by { + background: var(--color-purple); +} + +/* Settings toggle */ +#settings-toggle { + background: var(--color-blue); +} + +/* Historical graph */ +#big-graph-mobile-load-request { + background: var(--background-color); + color: var(--text-color); + padding: 10px 0; + text-align: center; + display: none; + width: 100%; + margin-bottom: 10px; +} + +#big-graph-mobile-load-request a { + display: inline-block; } -/* The big graph */ #big-graph, #big-graph-controls, #big-graph-checkboxes { width: 90%; - margin: 15px auto 0 auto; } #big-graph-checkboxes > table { @@ -215,119 +353,105 @@ h3 { } #big-graph { - margin-top: 20px; + margin: 0 auto; } #big-graph-controls { - margin: 10px auto; + margin: 10px auto; + display: none; } -#big-graph-controls a { - text-decoration: none; - color: inherit; - text-transform: uppercase; - border-bottom: 1px dashed #FFF; - font-size: 16px; +#big-graph-controls .icon-star { + color: var(--color-gold); } -#big-graph-controls a:hover { - border-bottom: 1px dashed transparent; +#big-graph-controls-drawer { + background: var(--background-color); + color: var(--text-color); + border-radius: var(--border-radius); + padding-bottom: 10px; + overflow: auto; + display: none; +} + +#big-graph-controls-drawer .graph-controls-setall { + text-align: center; + display: block; + margin: 15px 0; +} + +#big-graph-checkboxes { + margin: 15px auto 0 auto; } /* Basic elements */ .button { - background: #3498db; - border-radius: 2px; - text-shadow: 0 0 0 #000; - width: 85px; - font-size: 16px; - padding: 5px 10px; - margin: 0 auto; + background: var(--color-blue); + border-radius: var(--border-radius); + font-size: 16px; + padding: 5px 10px; } .button:hover { - background: #ecf0f1; - color: #3498db; - cursor: pointer; + background: var(--text-color); + color: var(--text-color-inverted); } /* Percentage bar */ #perc-bar { - height: 35px; - position: relative; + height: 35px; + position: relative; + overflow-x: hidden; } -#perc-bar > .perc-bar-part { - height: 100%; - display: inline-block; - position: absolute; - transition: 0.1s all; -} - -#perc-bar > .perc-bar-part:hover { - opacity: 0.75; -} - -/* Mojang Status */ -.mojang-status { - width: 85px; - height: 106px; - display: inline-block; - text-align: center; - line-height: 20px; - font-size: 14px; -} - -.mojang-status > strong { - text-transform: uppercase; -} - -.mojang-status > i { - margin-top: 20px; - font-size: 22px; +#perc-bar .perc-bar-part { + height: 100%; + display: inline-block; + position: absolute; } /* Mojang status colors */ -.mojang-status-online { - background: #87D37C; +@media (prefers-color-scheme: light) { + .mojang-status-online { + background: #87D37C; + } + + .mojang-status-unstable { + background: #f1c40f; + } + + .mojang-status-offline { + background: #DE5749; + } } -.mojang-status-unstable { - background: #f1c40f; -} - -.mojang-status-offline { - background: #DE5749; -} - -/* Dark color scheme overrides */ @media (prefers-color-scheme: dark) { - body { - background: #1c1b1c; - } + .mojang-status-online { + background: #66aa5a; + } - #header, #footer { - background: #3B3738; - color: #FFF; - } + .mojang-status-unstable { + background: #cc8a4f; + } - #footer a, #header a:hover { - border-bottom: 1px dashed transparent !important; - } + .mojang-status-offline { + background: #A6453B; + } +} - #footer a:hover, #header a { - border-bottom: 1px dashed #EBEBEB !important; - } +/* Header rows */ +@media only screen and (max-width: 1050px) { + header { + padding: 0 !important; + } - .mojang-status-online { - background: #6b9963; - } + .header-possible-row-break { + padding-top: 20px; + width: 100%; + text-align: center; + } - .mojang-status-unstable { - background: #a87b4b; - } - - .mojang-status-offline { - background: #A6453B; - } -} \ No newline at end of file + .header-possible-row-break:last-of-type { + margin-bottom: 20px; + } +} diff --git a/assets/fonts/icomoon.svg b/assets/fonts/icomoon.svg new file mode 100644 index 0000000..041aa3d --- /dev/null +++ b/assets/fonts/icomoon.svg @@ -0,0 +1,20 @@ + + + +Generated by IcoMoon + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/assets/fonts/icomoon.ttf b/assets/fonts/icomoon.ttf new file mode 100644 index 0000000000000000000000000000000000000000..f8d62d2d7dcf33740b7aa46a1495a659a4800d4c GIT binary patch literal 5520 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 VuqW{D!~?iD8G!xUhEe{->rdJnpW*-j literal 0 HcmV?d00001 diff --git a/assets/fonts/icomoon.woff b/assets/fonts/icomoon.woff new file mode 100644 index 0000000000000000000000000000000000000000..fd37104cb7e3c36dcbdfaafd64e93a6a4c16c646 GIT binary patch literal 5596 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 literal 0 HcmV?d00001 diff --git a/assets/html/index.html b/assets/html/index.html index 4d2eb9b..6ec540c 100644 --- a/assets/html/index.html +++ b/assets/html/index.html @@ -2,98 +2,97 @@ - + - - + - + - Minetrack + - + Minetrack - + -
+ -
+
-