Merge remote-tracking branch 'origin/dev'

This commit is contained in:
Daniel Vestøl committed 2018-12-15 16:48:41 +01:00
commit 2efb628884
44 files changed
+438 -462

No files matched your search

+28
View File
@@ -44,6 +44,7 @@ const bcrypt = require('bcryptjs');
const crypto = require('crypto');
const base64url = require('base64url');
const moment = require("moment");
const request = require("request")
// constants
console.log(`Requiring config from ${args.config || './config'}`)
@@ -644,6 +645,33 @@ app.get("/api/inventoryAsObject", function(req, res) {
res.send(JSON.stringify(db.items));
});
/**
GET endpoint to read the masters inventory as an object with key:value pairs
@memberof clusterioMaster
@instance
@alias api/modData
@returns {object} JSON
*/
// wrap a request in an promise
async function downloadPage(url) {
return new Promise((resolve, reject) => {
request(url, (error, response, body) => {
resolve(body);
});
});
}
app.get("/api/modmeta", async function(req, res) {
endpointHitCounter.labels(req.route.path).inc();
res.header("Access-Control-Allow-Origin", "*");
res.setHeader('Content-Type', 'application/json');
let modData = await downloadPage("https://mods.factorio.com/api/mods/" + req.query.modname);
res.send(modData);
});
/**
POST endpoint for running commands on slaves.
Requires x-access-token header to be set. Find you api token in secret-api-token.txt on the master (after running it once)
+12
View File
@@ -0,0 +1,12 @@
/*!
* Bootswatch v4.1.3
* Homepage: https://bootswatch.com
* Copyright 2012-2018 Thomas Park
* Licensed under MIT
* Based on Bootstrap
*//*!
* Bootstrap v4.1.3 (https://getbootstrap.com/)
* Copyright 2011-2018 The Bootstrap Authors
* Copyright 2011-2018 Twitter, Inc.
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
*/:root{--blue:#007bff;--indigo:#6610f2;--purple:#6f42c1;--pink:#e83e8c;--red:#ee5f5b;--orange:#fd7e14;--yellow:#f89406;--green:#62c462;--teal:#20c997;--cyan:#5bc0de;--white:#fff;--gray:#7A8288;--gray-dark:#3A3F44;--primary:#3A3F44;--secondary:#7A8288;--success:#62c462;--info:#5bc0de;--warning:#f89406;--danger:#ee5f5b;--light:#e9ecef;--dark:#272B30;--breakpoint-xs:0;--breakpoint-sm:576px;--breakpoint-md:768px;--breakpoint-lg:992px;--breakpoint-xl:1200px;--font-family-sans-serif:-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";--font-family-monospace:SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace}*,*::before,*::after{-webkit-box-sizing:border-box;box-sizing:border-box}html{font-family:sans-serif;line-height:1.15;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%;-ms-overflow-style:scrollbar;-webkit-tap-highlight-color:transparent}@-ms-viewport{width:device-width}article,aside,figcaption,figure,footer,header,hgroup,main,nav,section{display:block}body{margin:0;font-family:-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";font-size:0.9375rem;font-weight:400;line-height:1.5;color:#aaa;text-align:left;background-color:#272B30}[tabindex="-1"]:focus{outline:0 !important}hr{-webkit-box-sizing:content-box;box-sizing:content-box;height:0;overflow:visible}h1,h2,h3,h4,h5,h6{margin-top:0;margin-bottom:0.5rem}p{margin-top:0;margin-bottom:1rem}abbr[title],abbr[data-original-title]{text-decoration:underline;-webkit-text-decoration:underline dotted;text-decoration:underline dotted;cursor:help;border-bottom:0}address{margin-bottom:1rem;font-style:normal;line-height:inherit}ol,ul,dl{margin-top:0;margin-bottom:1rem}ol ol,ul ul,ol ul,ul ol{margin-bottom:0}dt{font-weight:700}dd{margin-bottom:.5rem;margin-left:0}blockquote{margin:0 0 1rem}dfn{font-style:italic}b,strong{font-weight:bolder}small{font-size:80%}sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}a{color:#fff;text-decoration:none;background-color:transparent;-webkit-text-decoration-skip:objects}a:hover{color:#d9d9d9;text-decoration:underline}a:not([href]):not([tabindex]){color:inherit;text-decoration:none}a:not([href]):not([tabindex]):hover,a:not([href]):not([tabindex]):focus{color:inherit;text-decoration:none}a:not([href]):not([tabindex]):focus{outline:0}pre,code,kbd,samp{font-family:SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;font-size:1em}pre{margin-top:0;margin-bottom:1rem;overflow:auto;-ms-overflow-style:scrollbar}figure{margin:0 0 1rem}img{vertical-align:middle;border-style:none}svg{overflow:hidden;vertical-align:middle}table{border-collapse:collapse}caption{padding-top:0.75rem;padding-bottom:0.75rem;color:#7A8288;text-align:left;caption-side:bottom}th{text-align:inherit}label{display:inline-block;margin-bottom:0.5rem}button{border-radius:0}button:focus{outline:1px dotted;outline:5px auto -webkit-focus-ring-color}input,button,select,optgroup,textarea{margin:0;font-family:inherit;font-size:inherit;line-height:inherit}button,input{overflow:visible}button,select{text-transform:none}button,html [type="button"],[type="reset"],[type="submit"]{-webkit-appearance:button}button::-moz-focus-inner,[type="button"]::-moz-focus-inner,[type="reset"]::-moz-focus-inner,[type="submit"]::-moz-focus-inner{padding:0;border-style:none}input[type="radio"],input[type="checkbox"]{-webkit-box-sizing:border-box;box-sizing:border-box;padding:0}input[type="date"],input[type="time"],input[type="datetime-local"],input[type="month"]{-webkit-appearance:listbox}textarea{overflow:auto;resize:vertical}fieldset{min-width:0;padding:0;margin:0;border:0}legend{display:block;width:100%;max-width:100%;padding:0;margin-bottom:.5rem;font-size:1.5rem;line-height:inherit;color:inherit;white-space:normal}progress{vertical-align:baseline}[type="number"]::-webkit-inner-spin-button,[type="number"]::-webkit-outer-spin-button{height:auto}[type="search"]{outline-offset:-2px;-webkit-appearance:none}[type="search"]::-webkit-search-cancel-button,[type="search"]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{font:inherit;-webkit-appearance:button}output{display:inline-block}summary{display:list-item;cursor:pointer}template{display:none}[hidden]{display:none !important}h1,h2,h3,h4,h5,h6,.h1,.h2,.h3,.h4,.h5,.h6{margin-bottom:0.5rem;font-family:inherit;font-weight:500;line-height:1.2;color:inherit}h1,.h1{font-size:2.34375rem}h2,.h2{font-size:1.875rem}h3,.h3{font-size:1.640625rem}h4,.h4{font-size:1.40625rem}h5,.h5{font-size:1.171875rem}h6,.h6{font-size:0.9375rem}.lead{font-size:1.171875rem;font-weight:300}.display-1{font-size:6rem;font-weight:300;line-height:1.2}.display-2{font-size:5.5rem;font-weight:300;line-heigLine truncated
+2
View File
@@ -0,0 +1,2 @@
<?xml version="1.0" encoding="utf-8"?>
<browserconfig><msapplication><tile><square70x70logo src="/ms-icon-70x70.png"/><square150x150logo src="/ms-icon-150x150.png"/><square310x310logo src="/ms-icon-310x310.png"/><TileColor>#ffffff</TileColor></tile></msapplication></browserconfig>
Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

+2
View File
@@ -0,0 +1,2 @@
<?xml version="1.0" encoding="utf-8"?>
<browserconfig><msapplication><tile><square70x70logo src="/ms-icon-70x70.png"/><square150x150logo src="/ms-icon-150x150.png"/><square310x310logo src="/ms-icon-310x310.png"/><TileColor>#ffffff</TileColor></tile></msapplication></browserconfig>
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

+41
View File
@@ -0,0 +1,41 @@
{
"name": "App",
"icons": [
{
"src": "\/android-icon-36x36.png",
"sizes": "36x36",
"type": "image\/png",
"density": "0.75"
},
{
"src": "\/android-icon-48x48.png",
"sizes": "48x48",
"type": "image\/png",
"density": "1.0"
},
{
"src": "\/android-icon-72x72.png",
"sizes": "72x72",
"type": "image\/png",
"density": "1.5"
},
{
"src": "\/android-icon-96x96.png",
"sizes": "96x96",
"type": "image\/png",
"density": "2.0"
},
{
"src": "\/android-icon-144x144.png",
"sizes": "144x144",
"type": "image\/png",
"density": "3.0"
},
{
"src": "\/android-icon-192x192.png",
"sizes": "192x192",
"type": "image\/png",
"density": "4.0"
}
]
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.1 KiB

+20 -1
View File
@@ -6,8 +6,27 @@
<script src="/external/moment.min.js"></script>
<script src="/external/mousetrap.min.js"></script>
<script src="/main.js"></script>
<link href="/bootstrap.min.css" rel="stylesheet">
<link href="/external/css/Roboto.css" rel="stylesheet">
<link rel="icon" href="/favicon.ico" type="image/x-icon" />
<link href="https://fonts.googleapis.com/css?family=Orbitron" rel="stylesheet">
<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.6.0/css/all.css" integrity="sha384-aOkxzJ5uQz7WBObEZcHvV5JvRW3TUc2rNPA7pe3AwnsUohiw1Vj2Rgx2KSOkF5+h" crossorigin="anonymous">
<link rel="apple-touch-icon" sizes="57x57" href="/favicon/apple-icon-57x57.png">
<link rel="apple-touch-icon" sizes="60x60" href="/favicon/apple-icon-60x60.png">
<link rel="apple-touch-icon" sizes="72x72" href="/favicon/apple-icon-72x72.png">
<link rel="apple-touch-icon" sizes="76x76" href="/favicon/apple-icon-76x76.png">
<link rel="apple-touch-icon" sizes="114x114" href="/favicon/apple-icon-114x114.png">
<link rel="apple-touch-icon" sizes="120x120" href="/favicon/apple-icon-120x120.png">
<link rel="apple-touch-icon" sizes="144x144" href="/favicon/apple-icon-144x144.png">
<link rel="apple-touch-icon" sizes="152x152" href="/favicon/apple-icon-152x152.png">
<link rel="apple-touch-icon" sizes="180x180" href="/favicon/apple-icon-180x180.png">
<link rel="icon" type="image/png" sizes="192x192" href="/favicon/android-icon-192x192.png">
<link rel="icon" type="image/png" sizes="32x32" href="/favicon/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="96x96" href="/favicon/favicon-96x96.png">
<link rel="icon" type="image/png" sizes="16x16" href="/favicon/favicon-16x16.png">
<link rel="manifest" href="/favicon/manifest.json">
<meta name="msapplication-TileColor" content="#ffffff">
<meta name="msapplication-TileImage" content="/ms-icon-144x144.png">
<meta name="theme-color" content="#ffffff">
<!-- Global site tag (gtag.js) - Google Analytics -->
<script async src="https://www.googletagmanager.com/gtag/js?id=UA-124032859-1"></script>
<script>
+43 -17
View File
@@ -1,23 +1,49 @@
<!doctype HTML>
<head>
<%- include("head.html") %>
<script defer src="storage.js"></script>
<%- include("head.html") %>
<script defer src="storage.js"></script>
</head>
<body>
<%- include("sidebar.html") %>
<div id="body">
<div id="storage">
<h2>Storage</h2>
<input type="text" placeholder="search" id="search">
<div id="contents">
<table><tbody><!-- populated by js --></tbody></table>
</div>
</div>
<div id="graphDisplay">
<div id="storageGraphContainer">
<div id="contentGraph" class="storageGraph" style="width: 100%;"></div>
</div>
</div>
</div>
<%- include("nav.html") %>
<div id="body" class="container">
<div id="storage">
<div class="page-header mt-2 row" id="banner">
<div class="row">
<div class="col-lg-8 col-md-7 col-sm-6">
<h2>Storage</h2>
</div>
</div>
</div>
<div class="form-group mt-3 mb-3 row">
<input type="text" class="form-control" id="search" placeholder="search">
</div>
<div id="contents" class="row w-100">
<table class="table table-striped table-hover">
<thead class="thead-dark">
<tr>
<th scope="col">Icon</th>
<th scope="col">Item ID</th>
<th scope="col">Quantity</th>
</tr>
</thead>
<tbody><!-- populated by js --></tbody>
</table>
</div>
</div>
<div id="graphDisplay">
<div id="storageGraphContainer">
<div id="contentGraph" class="storageGraph" style="width: 100%;"></div>
</div>
</div>
</div>
<script src="https://code.jquery.com/jquery-3.3.1.slim.min.js"
integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo"
crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.3/umd/popper.min.js"
integrity="sha384-ZMP7rVo3mIykV+2+9J3UJ46jBk0WLaUAdn689aCwoqbBJiSnjAK/l8WvCWPIPm49"
crossorigin="anonymous"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/js/bootstrap.min.js"
integrity="sha384-ChfqqxuZUCnJSK3+MXmPNIyE6ZbWh2IMqE241rYiqJxyMiZ6OW/JmZQ5stwEULTy"
crossorigin="anonymous"></script>
</body>
+41
View File
@@ -0,0 +1,41 @@
{
"name": "App",
"icons": [
{
"src": "\/android-icon-36x36.png",
"sizes": "36x36",
"type": "image\/png",
"density": "0.75"
},
{
"src": "\/android-icon-48x48.png",
"sizes": "48x48",
"type": "image\/png",
"density": "1.0"
},
{
"src": "\/android-icon-72x72.png",
"sizes": "72x72",
"type": "image\/png",
"density": "1.5"
},
{
"src": "\/android-icon-96x96.png",
"sizes": "96x96",
"type": "image\/png",
"density": "2.0"
},
{
"src": "\/android-icon-144x144.png",
"sizes": "144x144",
"type": "image\/png",
"density": "3.0"
},
{
"src": "\/android-icon-192x192.png",
"sizes": "192x192",
"type": "image\/png",
"density": "4.0"
}
]
}
+37
View File
@@ -0,0 +1,37 @@
<nav class="navbar nav-pills nav-fill">
<div class="nav-item col-3">
<a class="navbar-brand border-0" href="/">
<img src="/pictures/logo.png" width="50" height="50" class="d-inline-block align-middle " alt=""/>
<h3 class=" ml-3 nav-title">Clusterio</h3>
</a>
</div>
<div class="nav-item mr-1">
<a class="nav-link align-middle" href="/">Storage</a>
</div>
<div class="nav-item mr-1">
<a class="nav-link align-middle" href="/nodes">Slaves</a>
</div>
<%# Very ugly ejs code to hide the Players button if the playerManager plugin isn`t installed %>
<%
sidebarPluginButtonsHtml = "";
for(let o in masterPlugins){
let plug = masterPlugins[o];
if(plug.main
&& plug.main.ui
&& typeof plug.main.ui.sidebar == "object"
&& Array.isArray(plug.main.ui.sidebar)){
let sidebarEntries = plug.main.ui.sidebar;
for(let i in sidebarEntries){
let button = sidebarEntries[i];
if(button.getHtml && typeof button.getHtml == "function"){
sidebarPluginButtonsHtml += button.getHtml();
}
}
}
}
%>
<%- sidebarPluginButtonsHtml %>
<div class="nav-item">
<a class="nav-link align-middle" href="/settings">Settings</a>
</div>
</nav>
+2 -57
View File
@@ -1,58 +1,3 @@
#header .subbar h6 {
display:inline;
font-weight:normal;
color: #BBB
}
#header {
background-color: #222;
color: #DDD;
font-family: 'Roboto', sans-serif;
padding-left:16px;
}
#displayBody {
margin-left: 16px;
}
#hero {
background-color: #444;
height:400px;
}
#hero .subtitle {
margin-left:8px;
}
#header h1 {
margin-left: 10px;
display:inline-block;
}
#header .subbar h6 {
margin-right: 16px;
}
#onlineIndicator {
background-color:red;
height:20px;
width:20px;
display:inline-block;
border-radius:16px;
}
.productionGraph {
background-color:black;
}
#leftHeroContainer {
float:left;
}
ul {
width:300px;
list-style:none;
max-height:400px;
min-height:100px;
margin:0;
overflow:auto;
padding:0;
text-indent:10px;
}
li{
line-height:25px;
}
li:nth-child(even){
background:#ccc;
#node-stats .breadcrumb-item {
height: auto;
}
+2 -3
View File
@@ -7,10 +7,9 @@
</head>
<body>
<%- include("sidebar.html") %>
<div id="body">
<%- include("nav.html") %>
<div id="body" class="container">
<div id="hero">
<h2>Slave details</h2>
<div id="display">
</div>
+102 -49
View File
@@ -1,15 +1,15 @@
// string, object, function(object)
function post(url, data, callback) {
console.log("POST " + url + JSON.stringify(data))
console.log("POST " + url + JSON.stringify(data));
var xhr = new XMLHttpRequest();
xhr.open("POST", url, true);
xhr.setRequestHeader("Content-type", "application/json");
xhr.onreadystatechange = function () {
if (xhr.readyState == 4 && xhr.status == 200) {
if (xhr.readyState === 4 && xhr.status === 200) {
var json = JSON.parse(xhr.responseText);
callback(json);
}
}
};
xhr.send(JSON.stringify(data));
}
// function to sort arrays of objects after a keys value
@@ -38,22 +38,46 @@ var chartsByID = {};
// update the online indicator dot
setInterval(function(){
getJSON("/api/slaves", function(err, slaveData){
let indicator = document.querySelector("#onlineIndicator");
let indicator = document.querySelector("#online-indicator");
$(indicator).removeClass('fa-signal');
$(indicator).removeClass('fa-dizzy');
$(indicator).removeClass('fa-plug');
if(err){
// our request threw an error, most likely master is unavailable so we show yellow indicator
indicator.style.backgroundColor = "yellow";
indicator.style.color = "yellow";
$(indicator).addClass('fa-plug');
indicator.title = "Master server unavailable";
// compare with Math.floor(x/100000) allows us to check if they are within 10s of each other
} else if(Math.floor(slaveData[getParameterByName("instanceID")].time/100000) == Math.floor(Date.now()/100000)){
indicator.style.backgroundColor = "green";
} else if(Math.floor(slaveData[getParameterByName("instanceID")].time/100000) === Math.floor(Date.now()/100000)){
indicator.style.color = "green";
$(indicator).addClass('fa-signal');
indicator.title = "Slave is online";
} else {
indicator.style.backgroundColor = "red";
indicator.style.color = "red";
$(indicator).addClass('fa-dizzy');
indicator.title = "Slave is offline";
}
});
}, 1000);
function regexModDetails(modFileName) {
let m;
let results = [];
const regex = /(.+)_(\d+\.\d+\.\d+)\.zip/gm;
while ((m = regex.exec(modFileName)) !== null) {
// This is necessary to avoid infinite loops with zero-width matches
if (m.index === regex.lastIndex) {
regex.lastIndex++;
}
// The result can be accessed through the `m`-variable.
m.forEach((match, _) => {
results.push(match);
});
}
return results
}
populateSlaveInfo();
function populateSlaveInfo(){
let instanceID = getParameterByName("instanceID");
@@ -86,23 +110,31 @@ function populateSlaveInfo(){
}
*/
let slave = slaveData[instanceID];
let HTML = "<div id='header'><div id='onlineIndicator'></div><h1>Name: " + slave.instanceName+"</h1>"
HTML += "<div class='subbar'><h6>Host: "+slave.mac+" </h6><h6>Unique: "+slave.unique+" </h6>"
if(slave.publicIP != "localhost"){
HTML += "<h6>IP: "+slave.publicIP+":"+slave.serverPort+"</h6>";
let HTML = '<div class="jumbotron mt-5"><div id="header">' +
'<h1 class="display-4"><i id="online-indicator" class="mr-3 fas"></i> ' + slave.instanceName+"</h1>";
HTML += '<div class="stats" id="node-stats">' +
'<nav class="mt-3" aria-label="breadcrumb">\n' +
' <ol class="breadcrumb align-content-center"><li class="breadcrumb-item">Host: '+slave.mac+'</li>' +
'<li class="breadcrumb-item">Unique: '+slave.unique+'</li>';
if(slave.publicIP !== "localhost"){
HTML += '<li class="breadcrumb-item">IP: '+slave.publicIP+':'+slave.serverPort+'</li>';
} else {
HTML += "<h6>This server is not configured for incoming connections</h6>";
HTML += '<li class="breadcrumb-item">This server is not configured for incoming connections</li>';
}
HTML += "</div></div>" // end of header
// left container
HTML += "<div id='leftHeroContainer'>";
HTML += "<div id='displayBody'><p>Last seen: <span id='lastSeenDate'>"+moment(Number(slave.time)).fromNow()+"</span></p>";
HTML += "<p>Online players: "+slave.playerCount+"</p>";
if(slave.meta)
for(let key in slave.meta){
let t = slave.meta[key];
if(typeof t == "string"){
HTML += "<li class=\"breadcrumb-item\">"+key+": "+t+"</li>";
}
}
HTML += '<li class="breadcrumb-item">Last seen: <span id="lastSeenDate">'+moment(Number(slave.time)).fromNow()+"</span></li>";
HTML += '<li class="breadcrumb-item">Online players: '+slave.playerCount+"</li></ol></nav>";
// detect if remoteMap mod is installed, if it is we want to show the link for it
let hasRemoteMap = false;
slave.mods.forEach(mod => {
console.log(mod.modName)
console.log(mod.modName);
if(mod.modName.includes("remoteMap")){
hasRemoteMap = true; // we are still doing the logic on the outside, in case there are multiple instances of remoteMap installed....
}
@@ -111,33 +143,39 @@ function populateSlaveInfo(){
HTML += "<a href='/remoteMap?instanceID="+getParameterByName("instanceID")+"'>Remote map</a>";
}
HTML += "</div>" // end of displayBody
HTML += "</div>"; // end of displayBody
// list mods and other metadata
HTML += "<h2 class='subtitle'>Mods</h2><ul id='modlist'>"
for(let i = 0; i < slave.mods.length; i++){
HTML += "<li>"+slave.mods[i].modName+"</li>"
HTML += '<h2 class="subtitle d-inline">Mods</h2><a id="fetch-mod-data" class="btn btn-primary float-right m-2">Fetch Mod Data</a>' +
'<table id="modlist" class="table table-striped table-hover">' +
' <thead>\n' +
' <tr>\n' +
' <th scope="col">#</th>\n' +
' <th scope="col">Version</th>\n' +
' <th scope="col">Name</th>\n' +
' <th scope="col">Summary</th>\n' +
' </tr>\n' +
' </thead>' +
' <tbody>';
for(let i = 0; i < slave.mods[0].modName.length; i++){
let modRegexResults = regexModDetails(slave.mods[0].modName[i]);
HTML += `<tr>
<th scope="row">${i + 1}</th>
<td>${modRegexResults[2]}</td>
<td>${modRegexResults[1]}</td>
<td></td>
</tr>`
}
HTML += "</ul>"
HTML += "</ul>";
HTML += "</div>" // end of left container
HTML += "</div>"; // end of left container
// chart
HTML += '<div id="' + slave.unique + '" class="productionGraph" style="width: calc(100% - 300px);"></div>';
// terminal
// HTML += '<div id="terminal"></div>';
document.querySelector("#hero").innerHTML = HTML;
// slaveDetails
HTML = "";
if(slave.meta)
for(let key in slave.meta){
let t = slave.meta[key];
if(typeof t == "string"){
HTML += "<p>"+key+": "+t+"</p>";
}
}
document.querySelector("#body > #details").innerHTML = HTML;
@@ -145,8 +183,23 @@ function populateSlaveInfo(){
makeGraph(slave.unique, slave.unique)
// makeTerminal();
$('#fetch-mod-data').on("click", downloadSaveModData);
});
}
function downloadSaveModData() {
let modRows = $("#modlist tbody tr");
for (let i = 0; i < modRows.length; i++) {
let modName = modRows[i].children[2].innerText;
$.getJSON( "/api/modmeta?modname=" + modName, function( data ) {
modRows[i].children[2].innerHTML = data.title;
modRows[i].children[3].innerHTML = data.summary
});
}
}
var slaveLogin = {};
function makeTerminal(){
myTerminal = new Terminal();
@@ -157,7 +210,7 @@ function makeTerminal(){
});
myTerminal.print('Welcome to Clusterio rcon!');
myTerminal.input('', handleTerminalInput);
if(localStorage.terminalMinimized && localStorage.terminalMinimized == "true"){
if(localStorage.terminalMinimized && localStorage.terminalMinimized === "true"){
minimizeTerminal(true);
}
function print(string){
@@ -170,18 +223,18 @@ function makeTerminal(){
lastLine.innerHTML = "> " + lastLine.innerHTML;
argv = inputString.split(' ');
if(argv[0][0] == '/'){
if(argv[0][0] === '/'){
if(slaveLogin && slaveLogin.name && slaveLogin.pass){
print('Running: '+inputString);
} else {
print('Not identified to communicate with any slave! Run "help" for more information.');
}
} else if(argv[0] == 'help'){
if(argv[1] == 'login'){
} else if(argv[0] === 'help'){
if(argv[1] === 'login'){
print('To send commands to a slave, you are required to identify yourself. You can do this with the login command. The name will be the name of the slave as displayed on master. The password is the rcon password of the slave, as per /instances/[name]/config.json');
} else if(argv[1] == "/c"){
} else if(argv[1] === "/c"){
print('To run a command, start with /. You can for example do /c game.print("hello world!")');
} else if(argv[1] == "issues"){
} else if(argv[1] === "issues"){
print("Issues are reported to Danielv123 on github or espernet IRC.");
print(" - http://github.com/Danielv123/factorioClusterio/issues");
print(" - EsperNet #factorio Danielv123");
@@ -191,7 +244,7 @@ function makeTerminal(){
print(' - login [name] [passs] - Connect to remote slave');
print(' - issues - Report an issue');
}
} else if(argv[0] == 'issues'){
} else if(argv[0] === 'issues'){
print("Issues are reported to Danielv123 on github or espernet IRC.");
print(" - http://github.com/Danielv123/factorioClusterio/issues");
print(" - EsperNet #factorio Danielv123");
@@ -244,7 +297,7 @@ function makeGraph(instanceID, selector) {
let chartIgnoreList = [
"water",
"steam"
]
];
post("api/getStats", {instanceID: instanceID}, function(data){
//console.log("Building chart " + instanceID + " with this data:")
//console.log(data)
@@ -272,7 +325,7 @@ function generateLineChartArray(data, nameKey) {
for(let i = 0; i < data.length; i++) {
// only show recent data
if(data[i].timestamp > Date.now() - (24*60*60*1000)){
let y = data[i].data[nameKey]
let y = data[i].data[nameKey];
if(!data[i].data[nameKey]) {
y = 0;
} else if(y < 0) {
@@ -288,7 +341,7 @@ function generateLineChartArray(data, nameKey) {
let xyz = {};
xyz.name = nameKey;
xyz.type = "line";
if(nameKey == "copper-wire"||nameKey == "iron-plate"||nameKey == "copper-plate"||nameKey == "electronic-circuit"||nameKey == "steel-plate"||nameKey == "advanced-circuit"||nameKey == "crude-oil"||nameKey == "petroleum-gas"){
if(nameKey === "copper-wire"||nameKey === "iron-plate"||nameKey === "copper-plate"||nameKey === "electronic-circuit"||nameKey === "steel-plate"||nameKey === "advanced-circuit"||nameKey === "crude-oil"||nameKey === "petroleum-gas"){
xyz.showInLegend = true;
}
xyz.dataPoints = chartData;
@@ -297,7 +350,7 @@ function generateLineChartArray(data, nameKey) {
function drawChart(selector, chartData, title) {
// selector is ID of element, ex "chartContainer" or "-123199123"
console.log(chartData)
console.log(chartData);
chartsByID[selector] = new CanvasJS.Chart(selector, {
title:{
text: title || "Production graph"
+29 -17
View File
@@ -5,10 +5,13 @@
</head>
<body>
<%- include("sidebar.html") %>
<div id="body">
<div id="slaves">
<h2>Slaves</h2>
<%- include("nav.html") %>
<div id="body" class="container">
<div class="page-header mt-2 row" id="banner">
<div class="col-lg-8 col-md-7 col-sm-6">
<h2>Connected Nodes</h2>
</div>
</div>
<div id="display">
<%
function findInArray(key, value, array){
@@ -18,19 +21,23 @@
}
return indexes;
}
html = "<p>Current connections:</p>";
let html = ""
for(let id in slaves){
let slave = slaves[id];
html += `
<div id="${id}" class="slaveBox">
<a href='nodeDetails?instanceID=${id}'>
<h2>${slave.instanceName}</h2>
</a>
<p>ID: ${id}</p>
<p>Last seen: ${moment(Number(slave.time)).format("DD.MM.YY, hh:mm")}</p>
<p>Online players: ${slave.playerCount}</p>
<p>IP: ${slave.publicIP}:${slave.serverPort}</p>
<p>Host: ${slave.mac}</p>
<div id="${id}" class="slaveBox card mb-3">
<div class="card-body">
<h5 class="card-title"><a href='nodeDetails?instanceID=${id}'>
<h2>${slave.instanceName}</h2>
</a></h5>
</div>
<ul class="list-group list-group-flush">
<li class="list-group-item">ID: ${id}</li>
<li class="list-group-item">Last seen: ${moment(Number(slave.time)).format("DD.MM.YY, hh:mm")}</li>
<li class="list-group-item">Online players: ${slave.playerCount}</li>
<li class="list-group-item">IP: ${slave.publicIP}:${slave.serverPort}</li>
<li class="list-group-item">Host: ${slave.mac}</li>
</ul>
${findInArray("modName", "remoteMap", slave.mods).length ? "<a href='/remoteMap?instanceID="+id+"'>Remote map</a>" : ""}
`
for(let o in masterPlugins){
@@ -43,17 +50,22 @@
for(let i in slaveListingEntries){
let button = slaveListingEntries[i];
if(button.getHtml && typeof button.getHtml == "function"){
html += "<div class=\"plugin-link\">"
html += button.getHtml({slave});
html += "</div>"
}
}
}
}
html += `
</div>`
</div>
`
}
%>
<%- html %>
</div>
</div>
</div>
</div>
<script>
$('.plugin-link a').addClass('btn btn-primary')
</script>
</body>
Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

+1 -1
View File
@@ -6,7 +6,7 @@
</head>
<body>
<%- include("sidebar.html") %>
<%- include("nav.html") %>
<div id="body">
<h2>remoteMap</h2>
<div id="remoteMapContainer" style="float:left">
+9 -3
View File
@@ -5,9 +5,15 @@
</head>
<body>
<%- include("sidebar.html") %>
<div id="body">
<h1>Settings</h1>
<%- include("nav.html") %>
<div id="body" class="container">
<div class="page-header mt-2 row" id="banner">
<div class="row">
<div class="col-lg-8 col-md-7 col-sm-6">
<h2>Settings</h2>
</div>
</div>
</div>
</div>
<script src="settings.js"></script>
</body>
+12 -23
View File
@@ -3,6 +3,7 @@ if(localStorage.settings) {
let settings = JSON.parse(localStorage.settings);
let HTML = "";
for(let key in settings) {
HTML += drawSetting(key, settings[key]);
}
@@ -11,36 +12,24 @@ if(localStorage.settings) {
window.onload = function(){
// loop through all settings and add onclick events for them
let thingies = document.querySelectorAll(".settingsBox .switch .slider");
for(let i = 0; i < thingies.length; i++) {
thingies[i].onclick = function() {
let sliders = document.querySelectorAll(".settings-box .switch .slider");
for(let i = 0; i < sliders.length; i++) {
sliders[i].onclick = function() {
let settings = JSON.parse(localStorage.settings);
let selectedSliderParent = $(this.parentElement.parentElement);
// walk through DOM structure created by drawSetting and update localstorage from that
console.log(this.parentElement.parentElement.childNodes[0].innerHTML + " = " + !this.parentElement.childNodes[0].checked);
settings[this.parentElement.parentElement.childNodes[0].innerHTML] = !this.parentElement.childNodes[0].checked;
console.log(selectedSliderParent.find('.settings-text').html() + " = " + (selectedSliderParent.find("label input").val() === 'on'));
settings[selectedSliderParent.find('.settings-text').html()] = (selectedSliderParent.find("label input").val() === 'on');
localStorage.settings = JSON.stringify(settings);
}
}
}
};
function drawSetting(settingText, checked) {
// please don't change these without fixing what it will break in window.onload
let boxPart1 = '<div class="settingsBox"><span class="settingsText">'; // setting text
let boxPart2 = '</span><label class="switch"><input type="checkbox"'; // either checked or nothing
let boxPart3 = '><div class="slider"></div></label></div>'; // comment for balance and looks
let HTML = boxPart1;
if(settingText && typeof settingText == "string") {
HTML += settingText;
} else {
HTML += "unknown";
}
HTML += boxPart2;
if(checked && typeof checked == "boolean") {
HTML += " checked";
}
HTML += boxPart3;
let HTML = '<div class="settings-box">' +
'<label class="switch"><input type="checkbox" checked="' + checked + '"' +
'><div class="slider round"></div></label><span class="settings-text ml-3 align-middle">' + settingText + '</span></div>';
return HTML;
}
+1 -38
View File
@@ -1,38 +1 @@
<div id="header">
</div>
<div id="sidenav">
<div id="logo" style="height:50px;">
<img src="/pictures/clusterio_icon.png" style="width:50px;height:50px; float:left;"></img>
<h1>Clusterio</h1>
</div>
<a href="/"><div id="storage-menu" class="button-black menuItem">
<p>Storage</p>
</div></a>
<a href="/nodes"><div id="slaves-menu" class="button-black menuItem">
<p>Slaves</p>
</div></a>
<%# Very ugly ejs code to hide the Players button if the playerManager plugin isn`t installed %>
<%
sidebarPluginButtonsHtml = "";
for(let o in masterPlugins){
let plug = masterPlugins[o];
if(plug.main
&& plug.main.ui
&& typeof plug.main.ui.sidebar == "object"
&& Array.isArray(plug.main.ui.sidebar)){
let sidebarEntries = plug.main.ui.sidebar;
for(let i in sidebarEntries){
let button = sidebarEntries[i];
if(button.getHtml && typeof button.getHtml == "function"){
sidebarPluginButtonsHtml += button.getHtml();
}
}
}
}
%>
<%- sidebarPluginButtonsHtml %>
<a href="/settings"><div id="settings-menu" class="button-black menuItem">
<p>Settings</p>
</div></a>
</div>
<%- include("nav.html") %>
+54 -253
View File
@@ -1,281 +1,82 @@
* {
margin:0px;
color: #FFF;
}
body {
/* same as http://www.lua-api.factorio.com/latest */
background-color: rgb(38, 38, 38);
}
button {
border-radius: 0;
border: 1px solid transparent;
border-color: #FF7200;
border-width:1px;
cursor: pointer;
}
#logo {
/* using same colors as factorios official ones, taken from
http://www.lua-api.factorio.com */
background-color: #262626;
color: #C8C8C8;
}
#logo > h1 {
padding-top: 6px;
transform: translateX(5%);
}
#storage {
float: left;
}
/* search field */
#storage > input {
background-color:black;
width:300px;
box-sizing: border-box;
}
/* Graphs on the right of storage page */
#body > #graphDisplay {
float:left;
width: calc(100% - 300px);
min-height:100px;
}
/* Navbar on the right */
#sidenav {
position:fixed;
left:0px;
width:200px;
background-color:darkgrey;
height:100%;
}
#sidenav a p {
color: #FF7200;
transition:0.5s;
}
#sidenav a:hover p {
color: #FFF;
}
a {
color: #FF7200;
text-decoration: none;
}
a:hover {
color: #FFF;
}
/* ensure body does not overlap navbar on the right */
#body {
margin-left:200px;
}
#body > #production {
display:block;
}
/* Inventory display table */
#contents {
width:300px;
}
#contents table {
border: 1px solid black;
nav {
background-color: #25272a;
}
#contents td img {
height:32px;
width:32px;
}
#contentGraph {
float:left;
}
.storageGraph {
height:200px;
nav li {
height: 80px;
vertical-align: middle;
}
/* terminal */
.Terminal p div {
color:lime;
}
.Terminal p span {
color:yellow;
}
.Terminal p > span:nth-child(1):before {
content:"> ";
}
.Terminal {
overflow-y: scroll;
position:absolute;
border-left:1px solid darkgrey;
border-bottom:1px solid darkgrey;
height:400px;
transition:height 0.2s linear;
}
#terminal {
width:640px;
height:400px;
position:fixed;
right:10px;
bottom:10px;
left:50%;
top:50%;
}
/* titleBar, the bar above windows where you can minimize, maximize etc. Mostly for the terminal. */
.titleBar {
width:100%;
height:25px;
background-color:white;
}
.titleBar > .title {
float:left;
margin-top: 3px;
margin-left: 5px;
}
.titleBar p {
color:black;
}
.titleBar > .titleButton {
width:50px;
height:100%;
float:right;
background-color:white;
color:black;
transition:0.3s;
}
.titleBar > .titleButton:hover {
background-color: lightgray;
}
.titleBar > .titleButton:before {
margin-left: 18px;
}
.titleBar > .titleButton.close:hover {
background-color: red;
color:white;
}
.titleBar > .titleButton.close:before {
content:"✕";
margin-top:5px;
}
.titleBar > .titleButton.maximize:before {
content:"⬜";
margin-top:5px;
}
.titleBar > .titleButton.minimize:before {
content:"__";
margin-left:17px;
.navbar .nav-title {
font-family: 'Orbitron', sans-serif;
font-size: 25px;
display: inline-block;
vertical-align: bottom;
}
/* Slave stuff */
.slaveBox {
float:left;
width:100%;
/*height:110px;*/
background-color:#444;
/*border-radius: 10px;
margin-left:5px;
margin-right:5px;*/
}
.slaveBox > p {
margin:5px;
}
.slaveBox h2 {
margin:5px;
color: inherit;
transition:0.2s;
}
.slaveBox > button {
background-color: transparent;
color: #FF7200;
transition: 0.2s;
}
.slaveBox > button:hover {
color: #FFF;
}
.productionGraph {
float:right;
height:300px;
}
.button-black {
width:100%;
height:50px;
background-color:black;
color:white;
transition: all 300ms;
}
.button-black:hover {
background-color:#444;
color:yellow;
cursor:pointer;
}
.button-black * {
text-align:center;
position: relative;
top: 50%;
transform: translateY(-50%);
}
.canvasjs-chart-credit {
display:none;
}
/* Styling for settings page */
.settingsText {
margin-left: 10px;
margin-right:10px;
margin-top: -10px;
font-size: 18px;
}
.settingsBox .switch {
position:relative;
top:10px;
}
/* Fancy togglle switch for toggly things stolen from https://www.w3schools.com/howto/howto_css_switch.asp */
/* The switch - the box around the slider */
.switch {
position: relative;
display: inline-block;
width: 60px;
height: 34px;
position: relative;
display: inline-block;
width: 60px;
height: 34px;
}
.settingsBox {
position: relative;
display: block;
}
/* Hide default HTML checkbox */
.switch input {display:none;}
.switch input {
opacity: 0;
width: 0;
height: 0;
}
/* The slider */
.slider {
position: absolute;
cursor: pointer;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: #ccc;
-webkit-transition: .4s;
transition: .4s;
position: absolute;
cursor: pointer;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: #ccc;
-webkit-transition: .4s;
transition: .4s;
}
.slider:before {
position: absolute;
content: "";
height: 26px;
width: 26px;
left: 4px;
bottom: 4px;
background-color: white;
-webkit-transition: .4s;
transition: .4s;
position: absolute;
content: "";
height: 26px;
width: 26px;
left: 4px;
bottom: 4px;
background-color: white;
-webkit-transition: .4s;
transition: .4s;
}
input:checked + .slider {
background-color: #2196F3;
background-color: #ab671e;
}
input:focus + .slider {
box-shadow: 0 0 1px #2196F3;
box-shadow: 0 0 1px #ab671e;
}
input:checked + .slider:before {
-webkit-transform: translateX(26px);
-ms-transform: translateX(26px);
transform: translateX(26px);
-webkit-transform: translateX(26px);
-ms-transform: translateX(26px);
transform: translateX(26px);
}
canvas {
-webkit-transform:translateZ(0)
/* Rounded sliders */
.slider.round {
border-radius: 34px;
}
.slider.round:before {
border-radius: 50%;
}