commit 04a8bac3384c5e677a15420a385a3892b964c2db Author: Dorian Niemiec Date: Tue Aug 6 14:49:57 2024 +0200 Initial commit diff --git a/README.txt b/README.txt new file mode 100644 index 0000000..59c9e59 --- /dev/null +++ b/README.txt @@ -0,0 +1,6 @@ +The "backend" folder contains files, that would be copied to /usr/lib/svrjs directory. +The "frontend" folder contains files, that would be copied to /var/www/svrjs directory. +The "sql" folder contains a "database.sql" file, that would be loaded into a database. + +You may need to set "useWebRootServerSideScript" in SVR.JS config.json to "false" and set up HTTP authentication at every URL, expect "/collect.svr" (that includes query strings) +The server-side script require "mysql" and "gnuplot" npm packages. It also requires "gnuplot" command to be installed. diff --git a/backend/dbconfig.json b/backend/dbconfig.json new file mode 100644 index 0000000..7013fc8 --- /dev/null +++ b/backend/dbconfig.json @@ -0,0 +1,6 @@ +{ + "host" : "localhost", + "user" : "statistics", + "password" : "statistics", + "database" : "statistics" +} diff --git a/backend/serverSideScript.js b/backend/serverSideScript.js new file mode 100644 index 0000000..44369a3 --- /dev/null +++ b/backend/serverSideScript.js @@ -0,0 +1,370 @@ +disableEndElseCallbackExecute = true; //Without "var", else it will not work!!! + +var mysql = require("mysql"); +var gnuplot = require("gnuplot"); //There is an OS command injection vulnerability in the "gnuplot" npm package, but since the statistics display part of the application doesn't involve user input, the application isn't affected by it. + +if (!customvar1 && !customvar2) { + try { + customvar1 = JSON.parse(fs.readFileSync(__dirname + "/../dbconfig.json")); + } catch (err) { + customvar2 = err; + } +} + +if (customvar2) { + // customvar2 is a instance of Error + callServerError(500, customvar2); + return; +} + +// customvar1 is a database configuration + +var connection = mysql.createConnection(customvar1); + +function plot(data) { + var dataToFeed = []; + Object.keys(data).sort().forEach(function (key) { + dataToFeed.push(dataToFeed.length + " \"" + key.replace(/"/g, "'") + "\" " + parseFloat(data[key])); + }); + var gnuplotObject = gnuplot().set("terminal png size 800,480").set("tics font \"Poppins,12\"").set("xtics rotate by 45 right").set("boxwidth 0.6").set("style fill solid").set("yrange [0:*]").set("grid ytics mytics").set("grid").plot("'-' using 1:3:xtic(2) notitle lc rgb \"#007000\" with boxes"); + gnuplotObject.end(dataToFeed.join("\n")); + return gnuplotObject; +} + +function antiXSS(string) { + return string.replace(/&/g, "&").replace(//g, ">").replace(/"/g, """).replace(/'/g, "'"); +} + +function getCount(period, callback) { + var query = ""; + if (period == "daily") { + query = "SELECT COUNT(*) AS 'count' FROM entries WHERE time >= DATE_SUB(CURRENT_DATE(), INTERVAL 1 DAY);"; + } else if (period == "weekly") { + query = "SELECT COUNT(*) AS 'count' FROM entries WHERE time >= DATE_SUB(CURRENT_DATE(), INTERVAL 1 WEEK);"; + } else if (period == "monthly") { + query = "SELECT COUNT(*) AS 'count' FROM entries WHERE time >= DATE_SUB(CURRENT_DATE(), INTERVAL 1 MONTH);"; + } else if (period == "yearly" || period == "annual") { + query = "SELECT COUNT(*) AS 'count' FROM entries WHERE time >= DATE_SUB(CURRENT_DATE(), INTERVAL 1 YEAR);"; + } else if (period == "total" || period == "all") { + query = "SELECT COUNT(*) AS 'count' FROM entries;"; + } else { + callback(true, -1); + return; + } + connection.query(query, function (error, results, fields) { + if (error) { + callServerError(500, error); + if (connection.end) connection.end(); + return; + } + callback(false, parseInt(results[0].count)); + }); +} + +function escapeRegExp(string) { + return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); // $& means the whole matched string +} + +function formatTemplate(templateName, templateData, callback) { + readTemplate(templateName, function (data) { + callback(formatTemplateFromReadData(data, templateData)); + }); +} + +function readTemplate(templateName, callback) { + fs.readFile(__dirname + "/../templates/" + templateName + ".template", function (err, data) { + if (err) { + callServerError(500, err); + if (connection.end) connection.end(); + return; + } + callback(data); + }); +} + +function formatTemplateFromReadData(templateFileData, templateData) { + var tD = templateFileData.toString(); + Object.keys(templateData).forEach(function (key) { + tD = tD.replace(new RegExp("\\{\\{" + escapeRegExp(key) + "\\}\\}", "g"), templateData[key]); + }); + return tD; +} + +if (href == "/") { + connection.connect(function (err) { + if (err) { + callServerError(500, err); + if (connection.end) connection.end(); + return; + } + connection.query("SELECT id, ip, time, version, runtime, runtime_version FROM entries ORDER BY id DESC LIMIT 10;", function (error, results, fields) { + if (error) { + callServerError(500, err); + if (connection.end) connection.end(); + return; + } + + function getMods(callback, _id) { + if (!_id) _id = 0; + if (_id == results.length) { + callback(); + return; + } + connection.query("SELECT name, version FROM entries_mods WHERE entry_id = " + mysql.escape(results[_id].id) + ";", function (error, mResults, mFields) { + if (error) { + callServerError(500, err); + if (connection.end) connection.end(); + return; + } + results[_id].mods = mResults; + getMods(callback, _id + 1); + }); + } + getMods(function () { + readTemplate("mods", function (modsData) { + readTemplate("mod", function (modData) { + readTemplate("lateststart", function (latestStartData) { + var latestStarts = ""; + results.forEach(function (row) { + mods = ""; + if (row.mods.length > 0) { + row.mods.forEach(function (mod) { + mods += formatTemplateFromReadData(modData, { + name: mod.name, + version: mod.version + }); + }); + mods = formatTemplateFromReadData(modsData, { + mods: mods + }); + } + latestStarts += formatTemplateFromReadData(latestStartData, { + ipAddress: antiXSS(row.ip), + version: antiXSS(row.version), + runtime: antiXSS(row.runtime), + runtimeVersion: antiXSS(row.runtime_version), + mods: mods + }); + }); + getCount("daily", function (isBadRequest, dailyStarts) { + getCount("monthly", function (isBadRequest, monthlyStarts) { + getCount("yearly", function (isBadRequest, yearlyStarts) { + getCount("all", function (isBadRequest, totalStarts) { + formatTemplate("index", { + dailyStarts: dailyStarts, + monthlyStarts: monthlyStarts, + yearlyStarts: yearlyStarts, + totalStarts: totalStarts, + latestStarts: latestStarts + }, function (data) { + res.writeHead(200, { + "Content-Type": "text/html", + "Refresh": "60" + }); + res.end(data); + if (connection.end) connection.end(); + }); + }); + }); + }); + }); + }); + }); + }); + }); + }); + }); +} else if (href == "/chart.svr") { + connection.connect(function (err) { + if (err) { + callServerError(500, err); + if (connection.end) connection.end(); + return; + } + var query = ""; + if (uobject.query.scope == "versiondistribution") { + query = "SELECT CONCAT('SVR.JS ', version) AS 'key', COUNT(*) AS 'value' FROM entries WHERE time >= DATE_SUB(CURRENT_DATE(), INTERVAL 1 MONTH) GROUP BY version;"; + } else if (uobject.query.scope == "modsdistribution") { + query = "SELECT entries_mods.name AS 'key', COUNT(*) AS 'value' FROM entries_mods INNER JOIN entries ON entries_mods.entry_id = entries.id WHERE entries.time >= DATE_SUB(CURRENT_DATE(), INTERVAL 1 MONTH) GROUP BY entries_mods.name;"; + } else if (uobject.query.scope == "jsruntimedistribution") { + query = "SELECT runtime AS 'key', COUNT(*) AS 'value' FROM entries WHERE time >= DATE_SUB(CURRENT_DATE(), INTERVAL 1 MONTH) GROUP BY runtime;"; + } else if (uobject.query.scope == "jsruntimeversiondistribution") { + query = "SELECT CONCAT(runtime, ' ', runtime_version) AS 'key', COUNT(*) AS 'value' FROM entries WHERE time >= DATE_SUB(CURRENT_DATE(), INTERVAL 1 MONTH) GROUP BY runtime, runtime_version;"; + } else { + callServerError(400); + if (connection.end) connection.end(); + return; + } + connection.query(query, function (error, results, fields) { + if (error) { + callServerError(500, error); + if (connection.end) connection.end(); + return; + } + var data = {}; + results.forEach(function (keyValuePair) { + data[keyValuePair.key] = keyValuePair.value; + }); + try { + var plotInstance = plot(data); + res.writeHead(200, { + "Content-Type": "image/png" + }); + plotInstance.pipe(res); + if (connection.end) connection.end(); + } catch (err) { + callServerError(500, err); + if (connection.end) connection.end(); + return; + } + }); + }); +} else if (href == "/count.svr") { + connection.connect(function (err) { + if (err) { + callServerError(500, err); + if (connection.end) connection.end(); + return; + } + getCount(uobject.query.period, function (isBadRequest, count) { + if (isBadRequest) { + callServerError(400); + if (connection.end) connection.end(); + } else { + res.writeHead(200, { + "Content-Type": "text/plain" + }); + res.end(count.toString()); + if (connection.end) connection.end(); + } + }); + }); +} else if (href == "/collect.svr") { + var headers = { + "Content-Type": "application/json" + }; + if (req.method == "POST") { + if (req.headers["content-type"] && !req.headers["content-type"].match(/^application\/json(?:$|;)/)) { + headers["Accept"] = "application/json"; + res.writeHead(415, headers); + res.end(JSON.stringify({ + "status": 415, + "message": "Only JSON is supported." + })); + return; + } + var jsonData = ""; + req.on("data", function (chunk) { + jsonData += chunk.toString(); + }); + req.on("end", function () { + try { + var parsedJsonData = {}; + try { + parsedJsonData = JSON.parse(jsonData); + } catch (err) { + res.writeHead(400, headers); + res.end(JSON.stringify({ + "status": 400, + "message": "JSON parse error." + })); + return; + } + var isValid = true; + if (!parsedJsonData.version || !parsedJsonData.runtime || !parsedJsonData.runtimeVersion || !parsedJsonData.mods || typeof parsedJsonData.version != "string" || typeof parsedJsonData.runtime != "string" || typeof parsedJsonData.runtimeVersion != "string" || !Array.isArray(parsedJsonData.mods)) { + isValid = false; + } else if (["Node.js", "Bun"].indexOf(parsedJsonData.runtime) == -1) { + isValid = false; + } else { + parsedJsonData.mods.every(function (element) { + if (!element.version || typeof element.name != "string" || typeof element.version != "string") { + isValid = false; + return false; + } else { + return true; + } + }); + } + if (!isValid) { + res.writeHead(400, headers); + res.end(JSON.stringify({ + "status": 400, + "message": "Invalid data." + })); + return; + } + connection.connect(function (err) { + if (err) { + serverconsole.errmessage("There was an error while processing the request!"); + serverconsole.errmessage("Stack:"); + serverconsole.errmessage(err.stack); + res.writeHead(500, headers); + res.end(JSON.stringify({ + "status": 500, + "message": "An unexpected error occurred." + })); + if (connection.end) connection.end(); + return; + } + var requestIP = (req.socket.realRemoteAddress ? req.socket.realRemoteAddress : req.socket.remoteAddress).replace(/^::ffff:/i, ""); + connection.query("INSERT INTO entries (ip, time, version, runtime, runtime_version) VALUES (" + mysql.escape(requestIP) + ", NOW(), " + mysql.escape(parsedJsonData.version) + ", " + mysql.escape(parsedJsonData.runtime) + ", " + mysql.escape(parsedJsonData.runtimeVersion) + ");", function (error, results, fields) { + if (error) { + serverconsole.errmessage("There was an error while processing the request!"); + serverconsole.errmessage("Stack:"); + serverconsole.errmessage(error.stack); + res.writeHead(500, headers); + res.end(JSON.stringify({ + "status": 500, + "message": "An unexpected error occurred." + })); + if (connection.end) connection.end(); + return; + } + var entriesToInsert = []; + parsedJsonData.mods.forEach(function (mod) { + entriesToInsert.push("(" + mysql.escape(results.insertId) + ", " + mysql.escape(mod.name) + ", " + mysql.escape(mod.version) + ")"); + }); + connection.query("INSERT INTO entries_mods (entry_id, name, version) VALUES " + entriesToInsert.join(", ") + ";", function (error, results, fields) { + if (error) { + serverconsole.errmessage("There was an error while processing the request!"); + serverconsole.errmessage("Stack:"); + serverconsole.errmessage(error.stack); + res.writeHead(500, headers); + res.end(JSON.stringify({ + "status": 500, + "message": "An unexpected error occurred." + })); + if (connection.end) connection.end(); + return; + } + res.writeHead(200, headers); + res.end(JSON.stringify({ + "status": 200, + "message": "The statistics are added successfully." + })); + if (connection.end) connection.end(); + }); + }); + }); + } catch (err) { + serverconsole.errmessage("There was an error while processing the request!"); + serverconsole.errmessage("Stack:"); + serverconsole.errmessage(err.stack); + res.writeHead(500, headers); + res.end(JSON.stringify({ + "status": 500, + "message": "An unexpected error occurred." + })); + } + }); + } else { + headers["Allow"] = "POST"; + res.writeHead(405, headers); + res.end(JSON.stringify({ + "status": 405, + "message": "Invalid HTTP method." + })); + } +} else { + elseCallback(); +} diff --git a/backend/templates/index.template b/backend/templates/index.template new file mode 100644 index 0000000..43ffe46 --- /dev/null +++ b/backend/templates/index.template @@ -0,0 +1,71 @@ + + + + + + + SVR.JS statistics + + +
+

SVR.JS statistics

+

The page will be refreshed every minute.

+

Insights

+ +
+

10 latest SVR.JS starts

+
    + {{latestStarts}} +
+
+ + diff --git a/backend/templates/lateststart.template b/backend/templates/lateststart.template new file mode 100644 index 0000000..65b67ae --- /dev/null +++ b/backend/templates/lateststart.template @@ -0,0 +1,5 @@ +
  • + {{ipAddress}}
    + SVR.JS version: {{version}} | JavaScript runtime: {{runtime}} | JavaScript runtime version: {{runtimeVersion}}
    + {{mods}} +
  • diff --git a/backend/templates/mod.template b/backend/templates/mod.template new file mode 100644 index 0000000..4dba6fe --- /dev/null +++ b/backend/templates/mod.template @@ -0,0 +1 @@ +
  • {{name}} {{version}}
  • diff --git a/backend/templates/mods.template b/backend/templates/mods.template new file mode 100644 index 0000000..325cd8a --- /dev/null +++ b/backend/templates/mods.template @@ -0,0 +1,4 @@ +Installed mods: + diff --git a/frontend/css/main.css b/frontend/css/main.css new file mode 100644 index 0000000..55b670d --- /dev/null +++ b/frontend/css/main.css @@ -0,0 +1,492 @@ +/* latin-ext */ +@font-face { + font-family: 'Poppins'; + font-style: italic; + font-weight: 100; + font-display: swap; + src: url(../fonts/pxiAyp8kv8JHgFVrJJLmE0tMMPKzSQ.woff2) format('woff2'); + unicode-range: U+0100-02AF, U+0304, U+0308, U+0329, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF; +} +/* latin */ +@font-face { + font-family: 'Poppins'; + font-style: italic; + font-weight: 100; + font-display: swap; + src: url(../fonts/pxiAyp8kv8JHgFVrJJLmE0tCMPI.woff2) format('woff2'); + unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; +} +/* latin-ext */ +@font-face { + font-family: 'Poppins'; + font-style: italic; + font-weight: 200; + font-display: swap; + src: url(../fonts/pxiDyp8kv8JHgFVrJJLmv1pVGdeOcEg.woff2) format('woff2'); + unicode-range: U+0100-02AF, U+0304, U+0308, U+0329, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF; +} +/* latin */ +@font-face { + font-family: 'Poppins'; + font-style: italic; + font-weight: 200; + font-display: swap; + src: url(../fonts/pxiDyp8kv8JHgFVrJJLmv1pVF9eO.woff2) format('woff2'); + unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; +} +/* latin-ext */ +@font-face { + font-family: 'Poppins'; + font-style: italic; + font-weight: 300; + font-display: swap; + src: url(../fonts/pxiDyp8kv8JHgFVrJJLm21lVGdeOcEg.woff2) format('woff2'); + unicode-range: U+0100-02AF, U+0304, U+0308, U+0329, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF; +} +/* latin */ +@font-face { + font-family: 'Poppins'; + font-style: italic; + font-weight: 300; + font-display: swap; + src: url(../fonts/pxiDyp8kv8JHgFVrJJLm21lVF9eO.woff2) format('woff2'); + unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; +} +/* latin-ext */ +@font-face { + font-family: 'Poppins'; + font-style: italic; + font-weight: 400; + font-display: swap; + src: url(../fonts/pxiGyp8kv8JHgFVrJJLufntAKPY.woff2) format('woff2'); + unicode-range: U+0100-02AF, U+0304, U+0308, U+0329, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF; +} +/* latin */ +@font-face { + font-family: 'Poppins'; + font-style: italic; + font-weight: 400; + font-display: swap; + src: url(../fonts/pxiGyp8kv8JHgFVrJJLucHtA.woff2) format('woff2'); + unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; +} +/* latin-ext */ +@font-face { + font-family: 'Poppins'; + font-style: italic; + font-weight: 500; + font-display: swap; + src: url(../fonts/pxiDyp8kv8JHgFVrJJLmg1hVGdeOcEg.woff2) format('woff2'); + unicode-range: U+0100-02AF, U+0304, U+0308, U+0329, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF; +} +/* latin */ +@font-face { + font-family: 'Poppins'; + font-style: italic; + font-weight: 500; + font-display: swap; + src: url(../fonts/pxiDyp8kv8JHgFVrJJLmg1hVF9eO.woff2) format('woff2'); + unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; +} +/* latin-ext */ +@font-face { + font-family: 'Poppins'; + font-style: italic; + font-weight: 600; + font-display: swap; + src: url(../fonts/pxiDyp8kv8JHgFVrJJLmr19VGdeOcEg.woff2) format('woff2'); + unicode-range: U+0100-02AF, U+0304, U+0308, U+0329, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF; +} +/* latin */ +@font-face { + font-family: 'Poppins'; + font-style: italic; + font-weight: 600; + font-display: swap; + src: url(../fonts/pxiDyp8kv8JHgFVrJJLmr19VF9eO.woff2) format('woff2'); + unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; +} +/* latin-ext */ +@font-face { + font-family: 'Poppins'; + font-style: italic; + font-weight: 700; + font-display: swap; + src: url(../fonts/pxiDyp8kv8JHgFVrJJLmy15VGdeOcEg.woff2) format('woff2'); + unicode-range: U+0100-02AF, U+0304, U+0308, U+0329, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF; +} +/* latin */ +@font-face { + font-family: 'Poppins'; + font-style: italic; + font-weight: 700; + font-display: swap; + src: url(../fonts/pxiDyp8kv8JHgFVrJJLmy15VF9eO.woff2) format('woff2'); + unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; +} +/* latin-ext */ +@font-face { + font-family: 'Poppins'; + font-style: italic; + font-weight: 800; + font-display: swap; + src: url(../fonts/pxiDyp8kv8JHgFVrJJLm111VGdeOcEg.woff2) format('woff2'); + unicode-range: U+0100-02AF, U+0304, U+0308, U+0329, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF; +} +/* latin */ +@font-face { + font-family: 'Poppins'; + font-style: italic; + font-weight: 800; + font-display: swap; + src: url(../fonts/pxiDyp8kv8JHgFVrJJLm111VF9eO.woff2) format('woff2'); + unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; +} +/* latin-ext */ +@font-face { + font-family: 'Poppins'; + font-style: italic; + font-weight: 900; + font-display: swap; + src: url(../fonts/pxiDyp8kv8JHgFVrJJLm81xVGdeOcEg.woff2) format('woff2'); + unicode-range: U+0100-02AF, U+0304, U+0308, U+0329, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF; +} +/* latin */ +@font-face { + font-family: 'Poppins'; + font-style: italic; + font-weight: 900; + font-display: swap; + src: url(../fonts/pxiDyp8kv8JHgFVrJJLm81xVF9eO.woff2) format('woff2'); + unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; +} +/* latin-ext */ +@font-face { + font-family: 'Poppins'; + font-style: normal; + font-weight: 100; + font-display: swap; + src: url(../fonts/pxiGyp8kv8JHgFVrLPTufntAKPY.woff2) format('woff2'); + unicode-range: U+0100-02AF, U+0304, U+0308, U+0329, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF; +} +/* latin */ +@font-face { + font-family: 'Poppins'; + font-style: normal; + font-weight: 100; + font-display: swap; + src: url(../fonts/pxiGyp8kv8JHgFVrLPTucHtA.woff2) format('woff2'); + unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; +} +/* latin-ext */ +@font-face { + font-family: 'Poppins'; + font-style: normal; + font-weight: 200; + font-display: swap; + src: url(../fonts/pxiByp8kv8JHgFVrLFj_Z1JlFc-K.woff2) format('woff2'); + unicode-range: U+0100-02AF, U+0304, U+0308, U+0329, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF; +} +/* latin */ +@font-face { + font-family: 'Poppins'; + font-style: normal; + font-weight: 200; + font-display: swap; + src: url(../fonts/pxiByp8kv8JHgFVrLFj_Z1xlFQ.woff2) format('woff2'); + unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; +} +/* latin-ext */ +@font-face { + font-family: 'Poppins'; + font-style: normal; + font-weight: 300; + font-display: swap; + src: url(../fonts/pxiByp8kv8JHgFVrLDz8Z1JlFc-K.woff2) format('woff2'); + unicode-range: U+0100-02AF, U+0304, U+0308, U+0329, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF; +} +/* latin */ +@font-face { + font-family: 'Poppins'; + font-style: normal; + font-weight: 300; + font-display: swap; + src: url(../fonts/pxiByp8kv8JHgFVrLDz8Z1xlFQ.woff2) format('woff2'); + unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; +} +/* latin-ext */ +@font-face { + font-family: 'Poppins'; + font-style: normal; + font-weight: 400; + font-display: swap; + src: url(../fonts/pxiEyp8kv8JHgFVrJJnecmNE.woff2) format('woff2'); + unicode-range: U+0100-02AF, U+0304, U+0308, U+0329, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF; +} +/* latin */ +@font-face { + font-family: 'Poppins'; + font-style: normal; + font-weight: 400; + font-display: swap; + src: url(../fonts/pxiEyp8kv8JHgFVrJJfecg.woff2) format('woff2'); + unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; +} +/* latin-ext */ +@font-face { + font-family: 'Poppins'; + font-style: normal; + font-weight: 500; + font-display: swap; + src: url(../fonts/pxiByp8kv8JHgFVrLGT9Z1JlFc-K.woff2) format('woff2'); + unicode-range: U+0100-02AF, U+0304, U+0308, U+0329, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF; +} +/* latin */ +@font-face { + font-family: 'Poppins'; + font-style: normal; + font-weight: 500; + font-display: swap; + src: url(../fonts/pxiByp8kv8JHgFVrLGT9Z1xlFQ.woff2) format('woff2'); + unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; +} +/* latin-ext */ +@font-face { + font-family: 'Poppins'; + font-style: normal; + font-weight: 600; + font-display: swap; + src: url(../fonts/pxiByp8kv8JHgFVrLEj6Z1JlFc-K.woff2) format('woff2'); + unicode-range: U+0100-02AF, U+0304, U+0308, U+0329, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF; +} +/* latin */ +@font-face { + font-family: 'Poppins'; + font-style: normal; + font-weight: 600; + font-display: swap; + src: url(../fonts/pxiByp8kv8JHgFVrLEj6Z1xlFQ.woff2) format('woff2'); + unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; +} +/* latin-ext */ +@font-face { + font-family: 'Poppins'; + font-style: normal; + font-weight: 700; + font-display: swap; + src: url(../fonts/pxiByp8kv8JHgFVrLCz7Z1JlFc-K.woff2) format('woff2'); + unicode-range: U+0100-02AF, U+0304, U+0308, U+0329, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF; +} +/* latin */ +@font-face { + font-family: 'Poppins'; + font-style: normal; + font-weight: 700; + font-display: swap; + src: url(../fonts/pxiByp8kv8JHgFVrLCz7Z1xlFQ.woff2) format('woff2'); + unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; +} +/* latin-ext */ +@font-face { + font-family: 'Poppins'; + font-style: normal; + font-weight: 800; + font-display: swap; + src: url(../fonts/pxiByp8kv8JHgFVrLDD4Z1JlFc-K.woff2) format('woff2'); + unicode-range: U+0100-02AF, U+0304, U+0308, U+0329, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF; +} +/* latin */ +@font-face { + font-family: 'Poppins'; + font-style: normal; + font-weight: 800; + font-display: swap; + src: url(../fonts/pxiByp8kv8JHgFVrLDD4Z1xlFQ.woff2) format('woff2'); + unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; +} +/* latin-ext */ +@font-face { + font-family: 'Poppins'; + font-style: normal; + font-weight: 900; + font-display: swap; + src: url(../fonts/pxiByp8kv8JHgFVrLBT5Z1JlFc-K.woff2) format('woff2'); + unicode-range: U+0100-02AF, U+0304, U+0308, U+0329, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF; +} +/* latin */ +@font-face { + font-family: 'Poppins'; + font-style: normal; + font-weight: 900; + font-display: swap; + src: url(../fonts/pxiByp8kv8JHgFVrLBT5Z1xlFQ.woff2) format('woff2'); + unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; +} + +body { + font-family: Poppins, sans-serif; + font-size: 1.25em; + background-color: #ffffff; + color: #000000; +} + +main { + width: 100%; + max-width: 1024px; + margin: 3em auto; + overflow-wrap: break-word; +} + +h1 { + font-size: 3em; + margin-top: 0.3em; + margin-bottom: 0.3em; +} + +h2 { + margin-top: 0.75em; + margin-bottom: 0.75em; +} + +.latest { + list-style-type: none; + padding: 0; +} + +.latest li { + margin: 0.6em; + padding: 0.6em; + font-size: 0.8em; + background-color: #f9f9fa; + border: 1px solid #e4e4e7; + border-radius: 10px; +} + +.ipaddress { + font-size: 1.75em; + font-weight: bold; +} + +.mods { + font-size: 1.25em; + font-weight: bold; +} + +.modlist { + border: 1px solid #e4e4e7; + border-radius: 10px; + padding: 0; + list-style-type: none; + background-color: #ffffff; +} + +.modlist li { + border-radius: 0px; + border: none; + border-bottom: 1px solid #e4e4e7; + background: none; + font-size: 1em; + margin: 0; + padding: 0.4em; +} + +.modlist li:last-child { + border-bottom: none; +} + +.insights { + list-style-type: none; + padding: 0; +} + +.insights:after { + clear: both; + display: table; + content: ''; +} + +.insights li { + width: 50%; + float: left; +} + +.insightwrapper { + margin: 0.6em; + padding: 0.6em; + font-size: 0.8em; + background-color: #f9f9fa; + border: 1px solid #e4e4e7; + border-radius: 10px; +} + +.insightheading { + display: block; + font-weight: bold; + font-size: 1.25em; +} + +.insightvalue { + font-size: 3em; +} + +.insightimage { + width: 100%; + border-radius: 15px; +} + +.clearfix { + display: table; + clear: both; +} + +@media screen and (prefers-color-scheme: dark) { + body { + background-color: #0c0a09; + color: #ffffff; + } + + a { + color: #ffffff; + } + + .latest li { + border-color: #27272a; + background-color: #191817; + } + + .modlist { + border-color: #27272a; + background-color: #0c0a09; + } + + .modlist li { + border-color: #27272a; + background: none; + } + + .insightwrapper { + border-color: #27272a; + background-color: #191817; + } +} + +@media screen and (max-width: 800px) { + body { + font-size: 1.1em; + } +} + +@media screen and (max-width: 600px) { + body { + font-size: 1em; + } + + main { + margin: 2em auto; + } + + .insights li { + width: 100%; + float: none; + } +} diff --git a/frontend/favicon.ico b/frontend/favicon.ico new file mode 100644 index 0000000..c5d08a7 Binary files /dev/null and b/frontend/favicon.ico differ diff --git a/frontend/fonts/pxiAyp8kv8JHgFVrJJLmE0tCMPI.woff2 b/frontend/fonts/pxiAyp8kv8JHgFVrJJLmE0tCMPI.woff2 new file mode 100644 index 0000000..f43fd41 Binary files /dev/null and b/frontend/fonts/pxiAyp8kv8JHgFVrJJLmE0tCMPI.woff2 differ diff --git a/frontend/fonts/pxiAyp8kv8JHgFVrJJLmE0tMMPKzSQ.woff2 b/frontend/fonts/pxiAyp8kv8JHgFVrJJLmE0tMMPKzSQ.woff2 new file mode 100644 index 0000000..a5767e1 Binary files /dev/null and b/frontend/fonts/pxiAyp8kv8JHgFVrJJLmE0tMMPKzSQ.woff2 differ diff --git a/frontend/fonts/pxiByp8kv8JHgFVrLBT5Z1JlFc-K.woff2 b/frontend/fonts/pxiByp8kv8JHgFVrLBT5Z1JlFc-K.woff2 new file mode 100644 index 0000000..e7ed736 Binary files /dev/null and b/frontend/fonts/pxiByp8kv8JHgFVrLBT5Z1JlFc-K.woff2 differ diff --git a/frontend/fonts/pxiByp8kv8JHgFVrLBT5Z1xlFQ.woff2 b/frontend/fonts/pxiByp8kv8JHgFVrLBT5Z1xlFQ.woff2 new file mode 100644 index 0000000..71f96de Binary files /dev/null and b/frontend/fonts/pxiByp8kv8JHgFVrLBT5Z1xlFQ.woff2 differ diff --git a/frontend/fonts/pxiByp8kv8JHgFVrLCz7Z1JlFc-K.woff2 b/frontend/fonts/pxiByp8kv8JHgFVrLCz7Z1JlFc-K.woff2 new file mode 100644 index 0000000..4c0521e Binary files /dev/null and b/frontend/fonts/pxiByp8kv8JHgFVrLCz7Z1JlFc-K.woff2 differ diff --git a/frontend/fonts/pxiByp8kv8JHgFVrLCz7Z1xlFQ.woff2 b/frontend/fonts/pxiByp8kv8JHgFVrLCz7Z1xlFQ.woff2 new file mode 100644 index 0000000..bf022fc Binary files /dev/null and b/frontend/fonts/pxiByp8kv8JHgFVrLCz7Z1xlFQ.woff2 differ diff --git a/frontend/fonts/pxiByp8kv8JHgFVrLDD4Z1JlFc-K.woff2 b/frontend/fonts/pxiByp8kv8JHgFVrLDD4Z1JlFc-K.woff2 new file mode 100644 index 0000000..a381efc Binary files /dev/null and b/frontend/fonts/pxiByp8kv8JHgFVrLDD4Z1JlFc-K.woff2 differ diff --git a/frontend/fonts/pxiByp8kv8JHgFVrLDD4Z1xlFQ.woff2 b/frontend/fonts/pxiByp8kv8JHgFVrLDD4Z1xlFQ.woff2 new file mode 100644 index 0000000..f107b36 Binary files /dev/null and b/frontend/fonts/pxiByp8kv8JHgFVrLDD4Z1xlFQ.woff2 differ diff --git a/frontend/fonts/pxiByp8kv8JHgFVrLDz8Z1JlFc-K.woff2 b/frontend/fonts/pxiByp8kv8JHgFVrLDz8Z1JlFc-K.woff2 new file mode 100644 index 0000000..1066be4 Binary files /dev/null and b/frontend/fonts/pxiByp8kv8JHgFVrLDz8Z1JlFc-K.woff2 differ diff --git a/frontend/fonts/pxiByp8kv8JHgFVrLDz8Z1xlFQ.woff2 b/frontend/fonts/pxiByp8kv8JHgFVrLDz8Z1xlFQ.woff2 new file mode 100644 index 0000000..962b734 Binary files /dev/null and b/frontend/fonts/pxiByp8kv8JHgFVrLDz8Z1xlFQ.woff2 differ diff --git a/frontend/fonts/pxiByp8kv8JHgFVrLEj6Z1JlFc-K.woff2 b/frontend/fonts/pxiByp8kv8JHgFVrLEj6Z1JlFc-K.woff2 new file mode 100644 index 0000000..ea95e83 Binary files /dev/null and b/frontend/fonts/pxiByp8kv8JHgFVrLEj6Z1JlFc-K.woff2 differ diff --git a/frontend/fonts/pxiByp8kv8JHgFVrLEj6Z1xlFQ.woff2 b/frontend/fonts/pxiByp8kv8JHgFVrLEj6Z1xlFQ.woff2 new file mode 100644 index 0000000..921e962 Binary files /dev/null and b/frontend/fonts/pxiByp8kv8JHgFVrLEj6Z1xlFQ.woff2 differ diff --git a/frontend/fonts/pxiByp8kv8JHgFVrLFj_Z1JlFc-K.woff2 b/frontend/fonts/pxiByp8kv8JHgFVrLFj_Z1JlFc-K.woff2 new file mode 100644 index 0000000..4114612 Binary files /dev/null and b/frontend/fonts/pxiByp8kv8JHgFVrLFj_Z1JlFc-K.woff2 differ diff --git a/frontend/fonts/pxiByp8kv8JHgFVrLFj_Z1xlFQ.woff2 b/frontend/fonts/pxiByp8kv8JHgFVrLFj_Z1xlFQ.woff2 new file mode 100644 index 0000000..eeacdfe Binary files /dev/null and b/frontend/fonts/pxiByp8kv8JHgFVrLFj_Z1xlFQ.woff2 differ diff --git a/frontend/fonts/pxiByp8kv8JHgFVrLGT9Z1JlFc-K.woff2 b/frontend/fonts/pxiByp8kv8JHgFVrLGT9Z1JlFc-K.woff2 new file mode 100644 index 0000000..1ac43d7 Binary files /dev/null and b/frontend/fonts/pxiByp8kv8JHgFVrLGT9Z1JlFc-K.woff2 differ diff --git a/frontend/fonts/pxiByp8kv8JHgFVrLGT9Z1xlFQ.woff2 b/frontend/fonts/pxiByp8kv8JHgFVrLGT9Z1xlFQ.woff2 new file mode 100644 index 0000000..c660336 Binary files /dev/null and b/frontend/fonts/pxiByp8kv8JHgFVrLGT9Z1xlFQ.woff2 differ diff --git a/frontend/fonts/pxiDyp8kv8JHgFVrJJLm111VF9eO.woff2 b/frontend/fonts/pxiDyp8kv8JHgFVrJJLm111VF9eO.woff2 new file mode 100644 index 0000000..d956b75 Binary files /dev/null and b/frontend/fonts/pxiDyp8kv8JHgFVrJJLm111VF9eO.woff2 differ diff --git a/frontend/fonts/pxiDyp8kv8JHgFVrJJLm111VGdeOcEg.woff2 b/frontend/fonts/pxiDyp8kv8JHgFVrJJLm111VGdeOcEg.woff2 new file mode 100644 index 0000000..fd3bf91 Binary files /dev/null and b/frontend/fonts/pxiDyp8kv8JHgFVrJJLm111VGdeOcEg.woff2 differ diff --git a/frontend/fonts/pxiDyp8kv8JHgFVrJJLm21lVF9eO.woff2 b/frontend/fonts/pxiDyp8kv8JHgFVrJJLm21lVF9eO.woff2 new file mode 100644 index 0000000..0e9e94d Binary files /dev/null and b/frontend/fonts/pxiDyp8kv8JHgFVrJJLm21lVF9eO.woff2 differ diff --git a/frontend/fonts/pxiDyp8kv8JHgFVrJJLm21lVGdeOcEg.woff2 b/frontend/fonts/pxiDyp8kv8JHgFVrJJLm21lVGdeOcEg.woff2 new file mode 100644 index 0000000..e1cf6c8 Binary files /dev/null and b/frontend/fonts/pxiDyp8kv8JHgFVrJJLm21lVGdeOcEg.woff2 differ diff --git a/frontend/fonts/pxiDyp8kv8JHgFVrJJLm81xVF9eO.woff2 b/frontend/fonts/pxiDyp8kv8JHgFVrJJLm81xVF9eO.woff2 new file mode 100644 index 0000000..b00cf81 Binary files /dev/null and b/frontend/fonts/pxiDyp8kv8JHgFVrJJLm81xVF9eO.woff2 differ diff --git a/frontend/fonts/pxiDyp8kv8JHgFVrJJLm81xVGdeOcEg.woff2 b/frontend/fonts/pxiDyp8kv8JHgFVrJJLm81xVGdeOcEg.woff2 new file mode 100644 index 0000000..ca42990 Binary files /dev/null and b/frontend/fonts/pxiDyp8kv8JHgFVrJJLm81xVGdeOcEg.woff2 differ diff --git a/frontend/fonts/pxiDyp8kv8JHgFVrJJLmg1hVF9eO.woff2 b/frontend/fonts/pxiDyp8kv8JHgFVrJJLmg1hVF9eO.woff2 new file mode 100644 index 0000000..b995ebb Binary files /dev/null and b/frontend/fonts/pxiDyp8kv8JHgFVrJJLmg1hVF9eO.woff2 differ diff --git a/frontend/fonts/pxiDyp8kv8JHgFVrJJLmg1hVGdeOcEg.woff2 b/frontend/fonts/pxiDyp8kv8JHgFVrJJLmg1hVGdeOcEg.woff2 new file mode 100644 index 0000000..2484b8a Binary files /dev/null and b/frontend/fonts/pxiDyp8kv8JHgFVrJJLmg1hVGdeOcEg.woff2 differ diff --git a/frontend/fonts/pxiDyp8kv8JHgFVrJJLmr19VF9eO.woff2 b/frontend/fonts/pxiDyp8kv8JHgFVrJJLmr19VF9eO.woff2 new file mode 100644 index 0000000..27331a9 Binary files /dev/null and b/frontend/fonts/pxiDyp8kv8JHgFVrJJLmr19VF9eO.woff2 differ diff --git a/frontend/fonts/pxiDyp8kv8JHgFVrJJLmr19VGdeOcEg.woff2 b/frontend/fonts/pxiDyp8kv8JHgFVrJJLmr19VGdeOcEg.woff2 new file mode 100644 index 0000000..7d4eb76 Binary files /dev/null and b/frontend/fonts/pxiDyp8kv8JHgFVrJJLmr19VGdeOcEg.woff2 differ diff --git a/frontend/fonts/pxiDyp8kv8JHgFVrJJLmv1pVF9eO.woff2 b/frontend/fonts/pxiDyp8kv8JHgFVrJJLmv1pVF9eO.woff2 new file mode 100644 index 0000000..33f40ad Binary files /dev/null and b/frontend/fonts/pxiDyp8kv8JHgFVrJJLmv1pVF9eO.woff2 differ diff --git a/frontend/fonts/pxiDyp8kv8JHgFVrJJLmv1pVGdeOcEg.woff2 b/frontend/fonts/pxiDyp8kv8JHgFVrJJLmv1pVGdeOcEg.woff2 new file mode 100644 index 0000000..3d6a836 Binary files /dev/null and b/frontend/fonts/pxiDyp8kv8JHgFVrJJLmv1pVGdeOcEg.woff2 differ diff --git a/frontend/fonts/pxiDyp8kv8JHgFVrJJLmy15VF9eO.woff2 b/frontend/fonts/pxiDyp8kv8JHgFVrJJLmy15VF9eO.woff2 new file mode 100644 index 0000000..70974d8 Binary files /dev/null and b/frontend/fonts/pxiDyp8kv8JHgFVrJJLmy15VF9eO.woff2 differ diff --git a/frontend/fonts/pxiDyp8kv8JHgFVrJJLmy15VGdeOcEg.woff2 b/frontend/fonts/pxiDyp8kv8JHgFVrJJLmy15VGdeOcEg.woff2 new file mode 100644 index 0000000..b055595 Binary files /dev/null and b/frontend/fonts/pxiDyp8kv8JHgFVrJJLmy15VGdeOcEg.woff2 differ diff --git a/frontend/fonts/pxiEyp8kv8JHgFVrJJfecg.woff2 b/frontend/fonts/pxiEyp8kv8JHgFVrJJfecg.woff2 new file mode 100644 index 0000000..b69e009 Binary files /dev/null and b/frontend/fonts/pxiEyp8kv8JHgFVrJJfecg.woff2 differ diff --git a/frontend/fonts/pxiEyp8kv8JHgFVrJJnecmNE.woff2 b/frontend/fonts/pxiEyp8kv8JHgFVrJJnecmNE.woff2 new file mode 100644 index 0000000..8f4fcbf Binary files /dev/null and b/frontend/fonts/pxiEyp8kv8JHgFVrJJnecmNE.woff2 differ diff --git a/frontend/fonts/pxiGyp8kv8JHgFVrJJLucHtA.woff2 b/frontend/fonts/pxiGyp8kv8JHgFVrJJLucHtA.woff2 new file mode 100644 index 0000000..1112336 Binary files /dev/null and b/frontend/fonts/pxiGyp8kv8JHgFVrJJLucHtA.woff2 differ diff --git a/frontend/fonts/pxiGyp8kv8JHgFVrJJLufntAKPY.woff2 b/frontend/fonts/pxiGyp8kv8JHgFVrJJLufntAKPY.woff2 new file mode 100644 index 0000000..31652ed Binary files /dev/null and b/frontend/fonts/pxiGyp8kv8JHgFVrJJLufntAKPY.woff2 differ diff --git a/frontend/fonts/pxiGyp8kv8JHgFVrLPTucHtA.woff2 b/frontend/fonts/pxiGyp8kv8JHgFVrLPTucHtA.woff2 new file mode 100644 index 0000000..e59b153 Binary files /dev/null and b/frontend/fonts/pxiGyp8kv8JHgFVrLPTucHtA.woff2 differ diff --git a/frontend/fonts/pxiGyp8kv8JHgFVrLPTufntAKPY.woff2 b/frontend/fonts/pxiGyp8kv8JHgFVrLPTufntAKPY.woff2 new file mode 100644 index 0000000..db44598 Binary files /dev/null and b/frontend/fonts/pxiGyp8kv8JHgFVrLPTufntAKPY.woff2 differ diff --git a/sql/database.sql b/sql/database.sql new file mode 100644 index 0000000..22c322f --- /dev/null +++ b/sql/database.sql @@ -0,0 +1,63 @@ +/*!999999\- enable the sandbox mode */ +-- MariaDB dump 10.19 Distrib 10.6.18-MariaDB, for debian-linux-gnu (x86_64) +-- +-- Host: localhost Database: statistics +-- ------------------------------------------------------ +-- Server version 10.6.18-MariaDB-0ubuntu0.22.04.1 + +/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; +/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; +/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */; +/*!40101 SET NAMES utf8mb4 */; +/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */; +/*!40103 SET TIME_ZONE='+00:00' */; +/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */; +/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */; +/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */; +/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */; + +-- +-- Table structure for table `entries` +-- + +DROP TABLE IF EXISTS `entries`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `entries` ( + `id` int(10) NOT NULL AUTO_INCREMENT, + `ip` varchar(100) NOT NULL, + `time` datetime NOT NULL, + `version` varchar(100) NOT NULL, + `runtime` varchar(100) NOT NULL, + `runtime_version` varchar(100) NOT NULL, + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `entries_mods` +-- + +DROP TABLE IF EXISTS `entries_mods`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `entries_mods` ( + `id` int(10) NOT NULL AUTO_INCREMENT, + `entry_id` int(10) NOT NULL, + `name` varchar(100) NOT NULL, + `version` varchar(100) NOT NULL, + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */; + +/*!40101 SET SQL_MODE=@OLD_SQL_MODE */; +/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */; +/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */; +/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; +/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */; +/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; +/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */; + +-- Dump completed on 2024-08-06 14:32:28