Remove old web interface
No files matched your search
@@ -27,13 +27,11 @@
|
||||
"@clusterio/slave": "file:./packages/slave",
|
||||
"eslint": "^7.2.0",
|
||||
"fs-extra": "^8.1.0",
|
||||
"html5-validator": "^1.2.1",
|
||||
"jsdoc": "^3.6.3",
|
||||
"jsonwebtoken": "^8.2.1",
|
||||
"jszip": "^3.2.2",
|
||||
"lerna": "^3.22.1",
|
||||
"mocha": "^6.2.0",
|
||||
"mocha.parallel": "^0.15.5",
|
||||
"nyc": "^14.1.1",
|
||||
"phin": "^3.4.1"
|
||||
}
|
||||
|
||||
@@ -23,8 +23,6 @@ const path = require("path");
|
||||
const fs = require("fs-extra");
|
||||
const jwt = require("jsonwebtoken");
|
||||
const crypto = require("crypto");
|
||||
const moment = require("moment");
|
||||
const request = require("request");
|
||||
const setBlocking = require("set-blocking");
|
||||
const events = require("events");
|
||||
const yargs = require("yargs");
|
||||
@@ -72,23 +70,6 @@ app.use(bodyParser.urlencoded({
|
||||
app.use(fileUpload());
|
||||
app.use(compression());
|
||||
|
||||
// dynamic HTML generations with EJS
|
||||
app.set("view engine", "ejs");
|
||||
app.set("views", ["views", "plugins"]);
|
||||
|
||||
// give ejs access to some interesting information
|
||||
app.use(function(req, res, next){
|
||||
let externalAddress = masterConfig.get("master.external_address");
|
||||
res.locals.root = externalAddress ? new URL(externalAddress).pathname : "/";
|
||||
res.locals.res = res;
|
||||
res.locals.req = req;
|
||||
res.locals.masterPlugins = masterPlugins;
|
||||
res.locals.slaves = db.slaves;
|
||||
res.locals.moment = moment;
|
||||
next();
|
||||
});
|
||||
|
||||
require("./src/routes")(app);
|
||||
// Set folder to serve static content from (the website)
|
||||
app.use(express.static(path.join(__dirname, "static")));
|
||||
app.use(express.static("static")); // Used for data export files
|
||||
@@ -438,24 +419,6 @@ async function shutdown() {
|
||||
}
|
||||
}
|
||||
|
||||
// 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);
|
||||
});
|
||||
|
||||
|
||||
/**
|
||||
* Base class for master server connections
|
||||
*
|
||||
|
||||
@@ -1,106 +0,0 @@
|
||||
"use strict";
|
||||
var http = require("http");
|
||||
var https = require("https");
|
||||
var fs = require("fs-extra");
|
||||
|
||||
|
||||
function makeDirectory(path){
|
||||
return new Promise((resolve, reject) => {
|
||||
fs.mkdirp(path, function(err){
|
||||
if(err){
|
||||
console.error("Unable to mkdirp "+path);
|
||||
console.error(err);
|
||||
resolve(false);
|
||||
} else {
|
||||
console.log("mkdirp "+path);
|
||||
resolve(true);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
function download(url, file, callback){
|
||||
return new Promise((resolve, reject) => {
|
||||
let pathArray = file.split("/");
|
||||
pathArray.pop(); // remove filename from the end
|
||||
if(pathArray.length >= 1){
|
||||
// this might be an invalid path!
|
||||
fs.stat(pathArray.join("/"), (err, stat) => {
|
||||
if(err){
|
||||
makeDirectory(pathArray.join("/")).then(() => {
|
||||
doDownload(url, file, callback);
|
||||
});
|
||||
} else {
|
||||
doDownload(url, file, callback);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
doDownload(url, file, callback);
|
||||
}
|
||||
function doDownload(url, file, callback){
|
||||
var writeStream = fs.createWriteStream(file);
|
||||
let protocol = http;
|
||||
if(url.split(":")[0] == "https") protocol = https;
|
||||
var request = protocol.get(url, function(response) {
|
||||
response.pipe(writeStream);
|
||||
response.on("end", () => {
|
||||
if(callback && typeof callback == "function") callback(file);
|
||||
console.log("Downloaded "+file+" from "+url);
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
var directories = [
|
||||
"sharedMods",
|
||||
"instances",
|
||||
];
|
||||
let downloadList = [
|
||||
{
|
||||
url: "https://code.jquery.com/jquery.js",
|
||||
file: "static/external/jquery.js",
|
||||
},
|
||||
{
|
||||
url: "https://code.jquery.com/ui/1.12.1/jquery-ui.js",
|
||||
file: "static/external/jquery-ui.js",
|
||||
},
|
||||
{
|
||||
url: "https://momentjs.com/downloads/moment.min.js",
|
||||
file: "static/external/moment.min.js",
|
||||
},
|
||||
{
|
||||
url: "https://craig.global.ssl.fastly.net/js/mousetrap/mousetrap.min.js",
|
||||
file: "static/external/mousetrap.min.js",
|
||||
},
|
||||
{
|
||||
url: "https://fonts.googleapis.com/css?family=Roboto",
|
||||
file: "static/external/css/Roboto.css",
|
||||
},
|
||||
];
|
||||
|
||||
// initiate folder creations and downloads
|
||||
function processMkdirRecursive(callback){
|
||||
let item = directories.shift();
|
||||
makeDirectory(item).then(() => {
|
||||
if(directories.length == 0){
|
||||
if(callback) callback();
|
||||
} else {
|
||||
processMkdirRecursive(callback);
|
||||
}
|
||||
});
|
||||
}
|
||||
function processDownloadRecursive(callback){
|
||||
let item = downloadList.shift();
|
||||
download(item.url, item.file, item.callback).then(() => {
|
||||
if(downloadList.length == 0){
|
||||
if(callback) callback();
|
||||
} else {
|
||||
processDownloadRecursive(callback);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (module === require.main) {
|
||||
processMkdirRecursive(processDownloadRecursive);
|
||||
}
|
||||
@@ -6,8 +6,7 @@
|
||||
"version": "2.0.0-alpha0",
|
||||
"main": "master.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1",
|
||||
"install": "node npm_post_install"
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"bin": {
|
||||
"clusteriomaster": "./master.js"
|
||||
@@ -23,16 +22,13 @@
|
||||
"body-parser": "^1.18.2",
|
||||
"compression": "^1.7.2",
|
||||
"cookie-parser": "^1.4.3",
|
||||
"ejs": "^2.5.7",
|
||||
"express": "^4.16.2",
|
||||
"express-fileupload": "1.2.0",
|
||||
"fs-extra": "^8.1.0",
|
||||
"jsonwebtoken": "^8.2.1",
|
||||
"jszip": "^3.2.2",
|
||||
"moment": "^2.22.2",
|
||||
"node-factorio-api": "^0.3.8",
|
||||
"node-forge": "^0.10.0",
|
||||
"request": "^2.83.0",
|
||||
"set-blocking": "^2.0.0",
|
||||
"ws": "^7.2.3",
|
||||
"yargs": "^14.2.0"
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
"use strict";
|
||||
module.exports = function(app) {
|
||||
app.get("/", function(req, res) {
|
||||
res.render("index");
|
||||
});
|
||||
app.get("/nodes", function(req, res) {
|
||||
res.render("nodes");
|
||||
});
|
||||
app.get("/settings", function(req, res) {
|
||||
res.render("settings");
|
||||
});
|
||||
app.get("/nodeDetails", function(req, res) {
|
||||
res.render("nodeDetails");
|
||||
});
|
||||
};
|
||||
@@ -1,12 +0,0 @@
|
||||
/*!
|
||||
* 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
|
||||
@@ -1,11 +0,0 @@
|
||||
<?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>
|
||||
|
Before Width: | Height: | Size: 3.2 KiB |
|
Before Width: | Height: | Size: 22 KiB |
|
Before Width: | Height: | Size: 34 KiB |
|
Before Width: | Height: | Size: 2.8 KiB |
|
Before Width: | Height: | Size: 4.0 KiB |
|
Before Width: | Height: | Size: 7.3 KiB |
|
Before Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 22 KiB |
|
Before Width: | Height: | Size: 24 KiB |
|
Before Width: | Height: | Size: 32 KiB |
|
Before Width: | Height: | Size: 5.2 KiB |
|
Before Width: | Height: | Size: 5.6 KiB |
|
Before Width: | Height: | Size: 7.3 KiB |
|
Before Width: | Height: | Size: 8.0 KiB |
|
Before Width: | Height: | Size: 35 KiB |
|
Before Width: | Height: | Size: 35 KiB |
@@ -1,2 +0,0 @@
|
||||
<?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>
|
||||
|
Before Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 2.4 KiB |
|
Before Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 1.1 KiB |
@@ -1,41 +0,0 @@
|
||||
{
|
||||
"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"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
Before Width: | Height: | Size: 22 KiB |
|
Before Width: | Height: | Size: 24 KiB |
|
Before Width: | Height: | Size: 74 KiB |
|
Before Width: | Height: | Size: 7.1 KiB |
@@ -1,185 +0,0 @@
|
||||
/*! terminal.js v2.0 | (c) 2014 Erik Österberg | https://github.com/eosterberg/terminaljs */
|
||||
|
||||
var Terminal = (function () {
|
||||
// PROMPT_TYPE
|
||||
var PROMPT_INPUT = 1, PROMPT_PASSWORD = 2, PROMPT_CONFIRM = 3
|
||||
|
||||
var fireCursorInterval = function (inputField, terminalObj) {
|
||||
var cursor = terminalObj._cursor
|
||||
setTimeout(function () {
|
||||
if (inputField.parentElement && terminalObj._shouldBlinkCursor) {
|
||||
cursor.style.visibility = cursor.style.visibility === 'visible' ? 'hidden' : 'visible'
|
||||
fireCursorInterval(inputField, terminalObj)
|
||||
} else {
|
||||
cursor.style.visibility = 'visible'
|
||||
}
|
||||
}, 500)
|
||||
}
|
||||
|
||||
var firstPrompt = true;
|
||||
promptInput = function (terminalObj, message, PROMPT_TYPE, callback) {
|
||||
var shouldDisplayInput = (PROMPT_TYPE === PROMPT_INPUT)
|
||||
var inputField = document.createElement('input')
|
||||
|
||||
inputField.style.position = 'absolute'
|
||||
inputField.style.zIndex = '-100'
|
||||
inputField.style.outline = 'none'
|
||||
inputField.style.border = 'none'
|
||||
inputField.style.opacity = '0'
|
||||
inputField.style.fontSize = '0.2em'
|
||||
|
||||
terminalObj._inputLine.textContent = ''
|
||||
terminalObj._input.style.display = 'block'
|
||||
terminalObj.html.appendChild(inputField)
|
||||
fireCursorInterval(inputField, terminalObj)
|
||||
|
||||
if (message.length) terminalObj.print(PROMPT_TYPE === PROMPT_CONFIRM ? message + ' (y/n)' : message)
|
||||
|
||||
inputField.onblur = function () {
|
||||
terminalObj._cursor.style.display = 'none'
|
||||
}
|
||||
|
||||
inputField.onfocus = function () {
|
||||
inputField.value = terminalObj._inputLine.textContent
|
||||
terminalObj._cursor.style.display = 'inline'
|
||||
}
|
||||
|
||||
terminalObj.html.onclick = function () {
|
||||
inputField.focus()
|
||||
}
|
||||
|
||||
inputField.onkeydown = function (e) {
|
||||
if (e.which === 37 || e.which === 39 || e.which === 38 || e.which === 40 || e.which === 9) {
|
||||
e.preventDefault()
|
||||
} else if (shouldDisplayInput && e.which !== 13) {
|
||||
setTimeout(function () {
|
||||
terminalObj._inputLine.textContent = inputField.value
|
||||
}, 1)
|
||||
}
|
||||
}
|
||||
inputField.onkeyup = function (e) {
|
||||
if (PROMPT_TYPE === PROMPT_CONFIRM || e.which === 13) {
|
||||
terminalObj._input.style.display = 'none'
|
||||
var inputValue = inputField.value
|
||||
if (shouldDisplayInput) terminalObj.print(inputValue)
|
||||
terminalObj.html.removeChild(inputField)
|
||||
if (typeof(callback) === 'function') {
|
||||
if (PROMPT_TYPE === PROMPT_CONFIRM) {
|
||||
callback(inputValue.toUpperCase()[0] === 'Y' ? true : false)
|
||||
} else callback(inputValue)
|
||||
}
|
||||
}
|
||||
}
|
||||
if (firstPrompt) {
|
||||
firstPrompt = false
|
||||
setTimeout(function () { inputField.focus() }, 50)
|
||||
} else {
|
||||
inputField.focus()
|
||||
}
|
||||
}
|
||||
|
||||
var terminalBeep
|
||||
|
||||
var TerminalConstructor = function (id) {
|
||||
if (!terminalBeep) {
|
||||
terminalBeep = document.createElement('audio')
|
||||
var source = '<source src="http://www.erikosterberg.com/terminaljs/beep.'
|
||||
terminalBeep.innerHTML = source + 'mp3" type="audio/mpeg">' + source + 'ogg" type="audio/ogg">'
|
||||
terminalBeep.volume = 0.05
|
||||
}
|
||||
|
||||
this.html = document.createElement('div')
|
||||
this.html.className = 'Terminal'
|
||||
if (typeof(id) === 'string') { this.html.id = id }
|
||||
|
||||
this._innerWindow = document.createElement('div')
|
||||
this._output = document.createElement('p')
|
||||
this._inputLine = document.createElement('span') //the span element where the users input is put
|
||||
this._cursor = document.createElement('span')
|
||||
this._input = document.createElement('p') //the full element administering the user input, including cursor
|
||||
|
||||
this._shouldBlinkCursor = true
|
||||
|
||||
this.beep = function () {
|
||||
terminalBeep.load()
|
||||
terminalBeep.play()
|
||||
}
|
||||
|
||||
this.print = function (message) {
|
||||
var newLine = document.createElement('div')
|
||||
newLine.textContent = message
|
||||
this._output.appendChild(newLine)
|
||||
}
|
||||
|
||||
this.input = function (message, callback) {
|
||||
promptInput(this, message, PROMPT_INPUT, callback)
|
||||
}
|
||||
|
||||
this.password = function (message, callback) {
|
||||
promptInput(this, message, PROMPT_PASSWORD, callback)
|
||||
}
|
||||
|
||||
this.confirm = function (message, callback) {
|
||||
promptInput(this, message, PROMPT_CONFIRM, callback)
|
||||
}
|
||||
|
||||
this.clear = function () {
|
||||
this._output.innerHTML = ''
|
||||
}
|
||||
|
||||
this.sleep = function (milliseconds, callback) {
|
||||
setTimeout(callback, milliseconds)
|
||||
}
|
||||
|
||||
this.setTextSize = function (size) {
|
||||
this._output.style.fontSize = size
|
||||
this._input.style.fontSize = size
|
||||
}
|
||||
|
||||
this.setTextColor = function (col) {
|
||||
this.html.style.color = col
|
||||
this._cursor.style.background = col
|
||||
}
|
||||
|
||||
this.setBackgroundColor = function (col) {
|
||||
this.html.style.background = col
|
||||
}
|
||||
|
||||
this.setWidth = function (width) {
|
||||
this.html.style.width = width
|
||||
}
|
||||
|
||||
this.setHeight = function (height) {
|
||||
this.html.style.height = height
|
||||
}
|
||||
|
||||
this.blinkingCursor = function (bool) {
|
||||
bool = bool.toString().toUpperCase()
|
||||
this._shouldBlinkCursor = (bool === 'TRUE' || bool === '1' || bool === 'YES')
|
||||
}
|
||||
|
||||
this._input.appendChild(this._inputLine)
|
||||
this._input.appendChild(this._cursor)
|
||||
this._innerWindow.appendChild(this._output)
|
||||
this._innerWindow.appendChild(this._input)
|
||||
this.html.appendChild(this._innerWindow)
|
||||
|
||||
this.setBackgroundColor('black')
|
||||
this.setTextColor('white')
|
||||
this.setTextSize('1em')
|
||||
this.setWidth('100%')
|
||||
this.setHeight('100%')
|
||||
|
||||
this.html.style.fontFamily = 'Monaco, Courier'
|
||||
this.html.style.margin = '0'
|
||||
this._innerWindow.style.padding = '10px'
|
||||
this._input.style.margin = '0'
|
||||
this._output.style.margin = '0'
|
||||
this._cursor.style.background = 'white'
|
||||
this._cursor.innerHTML = 'C' //put something in the cursor..
|
||||
this._cursor.style.display = 'none' //then hide it
|
||||
this._input.style.display = 'none'
|
||||
}
|
||||
|
||||
return TerminalConstructor
|
||||
}())
|
||||
@@ -1,9 +0,0 @@
|
||||
export function getParameterByName(name, url) {
|
||||
if(!url) url = window.location.href;
|
||||
name = name.replace(/[\[\]]/g, "\\$&");
|
||||
var regex = new RegExp("[?&]" + name + "(=([^&#]*)|&|#|$)"),
|
||||
results = regex.exec(url);
|
||||
if(!results) return null;
|
||||
if(!results[2]) return '';
|
||||
return decodeURIComponent(results[2].replace(/\+/g, " "));
|
||||
}
|
||||
@@ -1,218 +0,0 @@
|
||||
|
||||
(function (){
|
||||
ensureSetting("Periodically update storage screen", "boolean", true);
|
||||
}())
|
||||
|
||||
function ensureSetting(setting, type, defaultSetting) {
|
||||
let settings
|
||||
try {
|
||||
settings = JSON.parse(localStorage.settings);
|
||||
} catch (e) {
|
||||
|
||||
}
|
||||
if(!settings) settings = {};
|
||||
if(typeof settings[setting] != type) {
|
||||
settings[setting] = defaultSetting;
|
||||
console.log("Fixed setting: " + setting + " | " + defaultSetting)
|
||||
}
|
||||
localStorage.settings = JSON.stringify(settings)
|
||||
}
|
||||
|
||||
// debug tools
|
||||
|
||||
debugTools = {
|
||||
addItem: function(name, count){
|
||||
console.log(`Attempting to add ${count} ${name}`)
|
||||
postJSON("api/place", {
|
||||
instanceName: "unknown",
|
||||
instanceID: "unknown",
|
||||
unique: "unknown",
|
||||
name: name,
|
||||
count: count,
|
||||
}, function(data){
|
||||
console.log(data);
|
||||
});
|
||||
},
|
||||
removeItem: function(name, count){
|
||||
console.log(`Attempting to remove ${count} ${name}`)
|
||||
postJSON("api/remove", {
|
||||
instanceName: "unknown",
|
||||
instanceID: "unknown",
|
||||
unique: "unknown",
|
||||
name: name,
|
||||
count: count,
|
||||
}, function(data){
|
||||
console.log(data);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
let cachedInstanceNames = {};
|
||||
async function getInstanceName(instanceID){
|
||||
let instance = cachedInstanceNames[instanceID];
|
||||
if(!instance){
|
||||
let slaves = await getJSON("/api/slaves");
|
||||
try {
|
||||
for(id in slaves){
|
||||
cachedInstanceNames[id] = slaves[id].instanceName;
|
||||
}
|
||||
} catch(e){
|
||||
console.log("Error while looking for instance name!");
|
||||
console.log(e);
|
||||
}
|
||||
return cachedInstanceNames[instanceID] || instanceID;
|
||||
} else {
|
||||
return cachedInstanceNames[instanceID];
|
||||
}
|
||||
}
|
||||
var g = {}
|
||||
contents = {
|
||||
"iron-plate":100,
|
||||
"copper-plate":7312,
|
||||
}
|
||||
// nice functions
|
||||
// hash a string to a hash
|
||||
function djb2(str){
|
||||
var hash = 5381;
|
||||
for (var i = 0; i < str.length; i++) {
|
||||
hash = ((hash << 5) + hash) + str.charCodeAt(i); /* hash * 33 + c */
|
||||
}
|
||||
return hash;
|
||||
}
|
||||
|
||||
// get queryString parameters
|
||||
function getParameterByName(name, url) {
|
||||
if (!url) url = window.location.href;
|
||||
name = name.replace(/[\[\]]/g, "\\$&");
|
||||
var regex = new RegExp("[?&]" + name + "(=([^&#]*)|&|#|$)"),
|
||||
results = regex.exec(url);
|
||||
if (!results) return null;
|
||||
if (!results[2]) return '';
|
||||
return decodeURIComponent(results[2].replace(/\+/g, " "));
|
||||
}
|
||||
|
||||
// callback(err, json)
|
||||
function getJSON(url, callback) {
|
||||
return new Promise((resolve, reject) => {
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.open('GET', url, true);
|
||||
xhr.responseType = 'json';
|
||||
xhr.onload = function() {
|
||||
var status = xhr.status;
|
||||
if (status == 200) {
|
||||
if(callback) callback(null, xhr.response);
|
||||
resolve(xhr.response);
|
||||
} else {
|
||||
if(callback) callback(status);
|
||||
reject(status);
|
||||
}
|
||||
};
|
||||
// triggers if connection is refused
|
||||
xhr.onerror = function(e){
|
||||
if(callback) callback(e);
|
||||
reject(e);
|
||||
};
|
||||
xhr.send();
|
||||
});
|
||||
};
|
||||
// callback(err, json)
|
||||
function postJSON(url, data, callback) {
|
||||
return new Promise((resolve, reject) => {
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.open('POST', url, true);
|
||||
xhr.setRequestHeader('Content-Type', 'application/json; charset=UTF-8');
|
||||
xhr.responseType = 'json';
|
||||
xhr.onload = function() {
|
||||
var status = xhr.status;
|
||||
if (status == 200) {
|
||||
if(callback) callback(null, xhr.response);
|
||||
resolve(xhr.response);
|
||||
} else {
|
||||
if(callback) callback(status);
|
||||
reject(status);
|
||||
}
|
||||
};
|
||||
// triggers if connection is refused
|
||||
xhr.onerror = function(e){
|
||||
if(callback) callback(e);
|
||||
reject(e);
|
||||
};
|
||||
xhr.send(JSON.stringify(data));
|
||||
});
|
||||
};
|
||||
// return Boolean
|
||||
function isJSON(string){
|
||||
let stringIsJson = false;
|
||||
let x
|
||||
try {
|
||||
x = JSON.parse(string);
|
||||
} catch (e){
|
||||
return false;
|
||||
}
|
||||
if(typeof x == "object" || typeof string == "object"){
|
||||
stringIsJson = true;
|
||||
}
|
||||
return stringIsJson;
|
||||
}
|
||||
// hash a string to a color
|
||||
function hashColor(str) {
|
||||
var hash = djb2(str);
|
||||
var r = (hash & 0xFF0000) >> 16;
|
||||
var g = (hash & 0x00FF00) >> 8;
|
||||
var b = hash & 0x0000FF;
|
||||
return "#" + ("0" + r.toString(16)).substr(-2) + ("0" + g.toString(16)).substr(-2) + ("0" + b.toString(16)).substr(-2);
|
||||
}
|
||||
// exactly what you would expect it to, returns String
|
||||
function capitalizeFirstLetter(string) {
|
||||
return string.charAt(0).toUpperCase() + string.slice(1);
|
||||
}
|
||||
|
||||
// hide the HTML object passed as argument #1
|
||||
function hideThis(object) {
|
||||
object.style.visibility = "hidden";
|
||||
}
|
||||
|
||||
Date.prototype.yyyymmdd = function(time) { // http://stackoverflow.com/questions/3066586/get-string-in-yyyymmdd-format-from-js-date-object
|
||||
var mm = this.getMonth() + 1; // getMonth() is zero-based
|
||||
var dd = this.getDate();
|
||||
if(mm<10)mm = "0"+mm
|
||||
if(dd<10)dd = "0"+dd
|
||||
console.log(this.getFullYear())
|
||||
return this.getFullYear()+""+mm+""+dd+ ""; // padding
|
||||
};
|
||||
var date = new Date();
|
||||
function replaceAll(target, search, replacement) {
|
||||
return target.split(search).join(replacement);
|
||||
};
|
||||
|
||||
function getItemIconClass(name) {
|
||||
if (factorioItemMetadata.has(name)) {
|
||||
return `item-${name}`;
|
||||
} else {
|
||||
return "item-unknown-item";
|
||||
}
|
||||
}
|
||||
window.factorioLocale = new Map();
|
||||
$.getJSON(`${root}export/locale.json`, (locale) => {
|
||||
window.factorioLocale = new Map(locale);
|
||||
});
|
||||
window.factorioItemMetadata = new Map();
|
||||
$.getJSON(`${root}export/item-metadata.json`, (itemMetadata) => {
|
||||
window.factorioItemMetadata = new Map(itemMetadata);
|
||||
console.log(factorioItemMetadata.get("electronic-circuit"));
|
||||
let style = document.createElement("style");
|
||||
style.type = "text/css";
|
||||
document.head.appendChild(style);
|
||||
|
||||
for (let [name, meta] of itemMetadata) {
|
||||
style.sheet.insertRule([
|
||||
`.item-${name} {`,
|
||||
` background-image: url("${root}export/item-spritesheet.png");`,
|
||||
" background-repeat: no-repeat;",
|
||||
` background-position: -${meta.x}px -${meta.y}px;`,
|
||||
` height: ${meta.size}px;`,
|
||||
` width: ${meta.size}px;`,
|
||||
"}\n",
|
||||
].join(""));
|
||||
}
|
||||
});
|
||||
@@ -1,41 +0,0 @@
|
||||
{
|
||||
"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"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
#node-stats .breadcrumb-item {
|
||||
height: auto;
|
||||
}
|
||||
@@ -1,255 +0,0 @@
|
||||
function getParameterByName(name, url) {
|
||||
if (!url) url = window.location.href;
|
||||
name = name.replace(/[\[\]]/g, "\\$&");
|
||||
var regex = new RegExp("[?&]" + name + "(=([^&#]*)|&|#|$)"),
|
||||
results = regex.exec(url);
|
||||
if (!results) return null;
|
||||
if (!results[2]) return '';
|
||||
return decodeURIComponent(results[2].replace(/\+/g, " "));
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/// code below here
|
||||
// declaring globals
|
||||
var chartsByID = {};
|
||||
// update the online indicator dot
|
||||
setInterval(function(){
|
||||
getJSON("/api/slaves", function(err, slaveData){
|
||||
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.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.color = "green";
|
||||
$(indicator).addClass('fa-signal');
|
||||
indicator.title = "Slave is online";
|
||||
} else {
|
||||
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");
|
||||
if(!instanceID){
|
||||
throw "We need instanceID! Also, *funny joke* but it doesn't work after I standardized on calling it instanceID";
|
||||
}
|
||||
getJSON("/api/slaves", function(err, slaveData){
|
||||
/*
|
||||
{"-1530507550":{
|
||||
"time":"1495475926375",
|
||||
"rconPort":"8714",
|
||||
"rconPassword":"hidden",
|
||||
"serverPort":"14345",
|
||||
"unique":"-1530507550",
|
||||
"publicIP":"localhost",
|
||||
"mods":[
|
||||
{
|
||||
"modName":"clusterio_1.0.0.zip",
|
||||
"hash":"341feb6c60918c83f7f3a6a14a4a308aef18dda6"
|
||||
},{
|
||||
"modName":"clusterio_1.0.1.zip",
|
||||
"hash":"aee7a4b67ece19bddf236312fe408a5d4b67248d"
|
||||
},{
|
||||
"modName":"clusterio_1.1.0.zip",
|
||||
"hash":"18e2f7e402d716e3dd1c1458b7fab2d0bb29eccf"
|
||||
}],
|
||||
"playerCount":"0",
|
||||
"instanceName":"t",
|
||||
"mac":"44-85-00-D2-7E-D6"}
|
||||
}
|
||||
*/
|
||||
let slave = slaveData[instanceID];
|
||||
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 += '<li class="breadcrumb-item">This server is not configured for incoming connections</li>';
|
||||
}
|
||||
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>";
|
||||
|
||||
HTML += "</div>"; // end of displayBody
|
||||
|
||||
// list mods and other metadata
|
||||
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 += "</div>"; // end of left container
|
||||
|
||||
// terminal
|
||||
// HTML += '<div id="terminal"></div>';
|
||||
|
||||
|
||||
|
||||
document.querySelector("#body > #details").innerHTML = HTML;
|
||||
|
||||
// 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();
|
||||
document.querySelector("#terminal").appendChild(myTerminal.html);
|
||||
// use jQuery to allow the user to drag the window around
|
||||
$("#terminal").draggable({
|
||||
handle:".titleBar",
|
||||
});
|
||||
myTerminal.print('Welcome to Clusterio rcon!');
|
||||
myTerminal.input('', handleTerminalInput);
|
||||
if(localStorage.terminalMinimized && localStorage.terminalMinimized === "true"){
|
||||
minimizeTerminal(true);
|
||||
}
|
||||
function print(string){
|
||||
myTerminal.print(string);
|
||||
}
|
||||
|
||||
function handleTerminalInput(inputString) {
|
||||
// show clearly that it was a command that was typed, not terminal output
|
||||
let lastLine = document.querySelector("#terminal div > p:nth-child(1) > div:last-child");
|
||||
lastLine.innerHTML = "> " + lastLine.innerHTML;
|
||||
|
||||
argv = inputString.split(' ');
|
||||
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'){
|
||||
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"){
|
||||
print('To run a command, start with /. You can for example do /c game.print("hello world!")');
|
||||
} else if(argv[1] === "issues"){
|
||||
print("Issues are reported to clusterio on github or espernet IRC.");
|
||||
print(" - http://github.com/clusterio/factorioClusterio/issues");
|
||||
print(" - EsperNet #factorio Danielv123");
|
||||
} else {
|
||||
print('To identify yourself to a slave, use "login [name] [password]"');
|
||||
print(' - /c [command] - Run command');
|
||||
print(' - login [name] [passs] - Connect to remote slave');
|
||||
print(' - issues - Report an issue');
|
||||
}
|
||||
} else if(argv[0] === 'issues'){
|
||||
print("Issues are reported to clusterio on github or espernet IRC.");
|
||||
print(" - http://github.com/clusterio/factorioClusterio/issues");
|
||||
print(" - EsperNet #factorio Danielv123");
|
||||
} else {
|
||||
print('Invalid command!');
|
||||
}
|
||||
|
||||
// keep handling input (unless something is seriously wrong)
|
||||
myTerminal.input('',handleTerminalInput);
|
||||
}
|
||||
}
|
||||
function minimizeTerminal(instant){
|
||||
localStorage.terminalMinimized = true;
|
||||
$('#terminal').draggable('disable');
|
||||
if(!instant){
|
||||
$('#terminal')[0].style.transition = 'left 0.2s linear, top 0.2s linear';
|
||||
}
|
||||
document.querySelector('.Terminal').style.height = 0;
|
||||
let ter = $('#terminal')[0];
|
||||
// if the window just spawned in and we are doing the first minimize, our position won't be set yet. We also don't want to save this blank position.
|
||||
if(ter.style.left && ter.style.top){
|
||||
localStorage.oldTerminalPosition = JSON.stringify({x:ter.style.left, y:ter.style.top});
|
||||
}
|
||||
ter.style.left = (window.innerWidth - ter.offsetWidth)+'px';
|
||||
ter.style.top = (window.innerHeight - 25)+'px';
|
||||
|
||||
// if we did it instant we still want to set this property for after.
|
||||
if(instant){
|
||||
setTimeout(() => $('#terminal')[0].style.transition = 'left 0.2s linear, top 0.2s linear',200);
|
||||
}
|
||||
}
|
||||
function maximizeTerminal(instant){
|
||||
localStorage.terminalMinimized = false;
|
||||
document.querySelector('.Terminal').style.height = '400px';
|
||||
let ter = $('#terminal')[0];
|
||||
|
||||
// recover our old saved position
|
||||
let oldTerminalPosition = JSON.parse(localStorage.oldTerminalPosition);
|
||||
ter.style.left = oldTerminalPosition.x;
|
||||
ter.style.top = oldTerminalPosition.y;
|
||||
|
||||
// remove our animation once its finished
|
||||
// since instant is a true/false value, Number will convert it to 1 or 0. Since 0 is falsy, || will turn it into 200.
|
||||
setTimeout(() => $('#terminal')[0].style.transition = 'none', (Number(instant) || 200));
|
||||
$('#terminal').draggable('enable');
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
|
||||
if(localStorage.settings) {
|
||||
let settings = JSON.parse(localStorage.settings);
|
||||
|
||||
let HTML = "";
|
||||
|
||||
for(let key in settings) {
|
||||
HTML += drawSetting(key, settings[key]);
|
||||
}
|
||||
document.querySelector("#body").innerHTML = document.querySelector("#body").innerHTML + HTML;
|
||||
}
|
||||
|
||||
window.onload = function(){
|
||||
// loop through all settings and add onclick events for them
|
||||
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(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) {
|
||||
|
||||
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,88 +0,0 @@
|
||||
nav {
|
||||
background-color: #25272a;
|
||||
}
|
||||
|
||||
nav li {
|
||||
height: 80px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.navbar .nav-title {
|
||||
font-family: 'Orbitron', sans-serif;
|
||||
font-size: 25px;
|
||||
display: inline-block;
|
||||
vertical-align: bottom;
|
||||
}
|
||||
|
||||
/* The switch - the box around the slider */
|
||||
.switch {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
width: 60px;
|
||||
height: 34px;
|
||||
}
|
||||
.settingsBox {
|
||||
position: relative;
|
||||
display: block;
|
||||
}
|
||||
|
||||
/* Hide default HTML checkbox */
|
||||
.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;
|
||||
}
|
||||
|
||||
.slider:before {
|
||||
position: absolute;
|
||||
content: "";
|
||||
height: 26px;
|
||||
width: 26px;
|
||||
left: 4px;
|
||||
bottom: 4px;
|
||||
background-color: white;
|
||||
-webkit-transition: .4s;
|
||||
transition: .4s;
|
||||
}
|
||||
|
||||
input:checked + .slider {
|
||||
background-color: #ab671e;
|
||||
}
|
||||
|
||||
input:focus + .slider {
|
||||
box-shadow: 0 0 1px #ab671e;
|
||||
}
|
||||
|
||||
input:checked + .slider:before {
|
||||
-webkit-transform: translateX(26px);
|
||||
-ms-transform: translateX(26px);
|
||||
transform: translateX(26px);
|
||||
}
|
||||
|
||||
/* Rounded sliders */
|
||||
.slider.round {
|
||||
border-radius: 34px;
|
||||
}
|
||||
|
||||
.slider.round:before {
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.item-unknown-item {
|
||||
background: url("pictures/unknown-item.png");
|
||||
height: 32px;
|
||||
width: 32px;
|
||||
}
|
||||
@@ -30,19 +30,6 @@ const masterInventoryGauge = new prometheus.Gauge(
|
||||
class MasterPlugin extends plugin.BaseMasterPlugin {
|
||||
async init() {
|
||||
|
||||
let externalAddress = this.master.config.get("master.external_address");
|
||||
let root = externalAddress ? new URL(externalAddress).pathname : "/";
|
||||
this.ui = {
|
||||
sidebar: [
|
||||
{
|
||||
getHtml: () => `
|
||||
<div class="nav-item mr-1">
|
||||
<a class="nav-link align-middle" href="${root}subspace_storage/storage">Storage</a>
|
||||
</div>`,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
this.items = await loadDatabase(this.master.config);
|
||||
this.itemsLastUpdate = new Map(this.items._items.entries());
|
||||
this.autosaveId = setInterval(() => {
|
||||
@@ -61,7 +48,6 @@ class MasterPlugin extends plugin.BaseMasterPlugin {
|
||||
}, 1000);
|
||||
|
||||
routes.addApiRoutes(this.master.app, this.items, this.metrics.endpointHitCounter);
|
||||
routes.addWebRoutes(this.master.app);
|
||||
}
|
||||
|
||||
updateStorage() {
|
||||
|
||||
@@ -5,11 +5,6 @@ const path = require("path");
|
||||
const prometheus = require("@clusterio/lib/prometheus");
|
||||
|
||||
|
||||
function addWebRoutes(app) {
|
||||
app.use("/subspace_storage", express.static(path.join(__dirname, "static")));
|
||||
app.get("/subspace_storage/storage", (req, res) => { res.render("subspace_storage/views/storage"); });
|
||||
}
|
||||
|
||||
function addApiRoutes(app, items, endpointHitCounter) {
|
||||
|
||||
/**
|
||||
@@ -52,6 +47,5 @@ function addApiRoutes(app, items, endpointHitCounter) {
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
addWebRoutes,
|
||||
addApiRoutes,
|
||||
};
|
||||
@@ -1,99 +0,0 @@
|
||||
// function to draw data we recieve from ajax requests
|
||||
"use strict";
|
||||
var _lastData = [];
|
||||
function drawcontents(data) {
|
||||
data = data || _lastData; //Cache data so we can drawcontents without waiting for the server, for the search box.
|
||||
_lastData = data;
|
||||
|
||||
// allow searching for multiple criteria separated by space
|
||||
var searchArgs = document.querySelector("#search").value;
|
||||
searchArgs = searchArgs.trim();
|
||||
searchArgs = "(" + searchArgs.replace(/ +/g,")|(") + ")";
|
||||
const search = new RegExp(searchArgs, "i");
|
||||
data = data.filter(function(item) {
|
||||
return search.test(item.name);
|
||||
});
|
||||
|
||||
sortByKey(data, "count");
|
||||
|
||||
const table = document.querySelector("#contents tbody"); //tables have tbody inserted automatically
|
||||
const rows = table.children;
|
||||
|
||||
//update existing rows or create new ones
|
||||
data.forEach(function(item, i) {
|
||||
// format count to have .s in it for large number readability (1,000,000)
|
||||
item.count = item.count.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");
|
||||
|
||||
let row = rows[i];
|
||||
if(!row) {
|
||||
row = document.createElement("tr");
|
||||
row.innerHTML = "<td><div></div></td><td class=name></td><td class=count></td>";
|
||||
table.appendChild(row);
|
||||
}
|
||||
|
||||
const itemIcon = row.querySelector("div");
|
||||
const iconClass = getItemIconClass(item.name);
|
||||
if (itemIcon.getAttribute("class") !== iconClass) {
|
||||
itemIcon.setAttribute("class", iconClass);
|
||||
}
|
||||
|
||||
let localeName = item.name;
|
||||
let meta = factorioItemMetadata.get(item.name);
|
||||
if (meta && meta.localised_name) {
|
||||
// TODO implement the locale to name conversion.
|
||||
localeName = factorioLocale.get(meta.localised_name[0]);
|
||||
} else {
|
||||
for (let section of ["item-name", "entity-name", "fluid-name", "equipment-name"]) {
|
||||
let name = factorioLocale.get(`${section}.${item.name}`);
|
||||
if (name) {
|
||||
localeName = name;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const name = row.querySelector(".name");
|
||||
if(name.textContent !== localeName) {
|
||||
name.textContent = localeName;
|
||||
}
|
||||
|
||||
const count = row.querySelector(".count");
|
||||
if(count.textContent !== String(item.count)) {
|
||||
count.textContent = item.count;
|
||||
}
|
||||
});
|
||||
|
||||
//remove excess rows, for example, after filtering
|
||||
while (data.length < rows.length) {
|
||||
table.removeChild(rows[data.length]);
|
||||
}
|
||||
}
|
||||
|
||||
// get cluster inventory from master
|
||||
function updateInventory() {
|
||||
var xmlhttp = new XMLHttpRequest();
|
||||
xmlhttp.onreadystatechange = function() {
|
||||
if(xmlhttp.readyState == 4 && xmlhttp.status == 200) {
|
||||
let data = JSON.parse(xmlhttp.responseText);
|
||||
drawcontents(data);
|
||||
}
|
||||
};
|
||||
xmlhttp.open("GET", `${root}api/inventory`, true);
|
||||
xmlhttp.send();
|
||||
}
|
||||
if(JSON.parse(localStorage.settings)["Periodically update storage screen"]) {
|
||||
setInterval(updateInventory, 500);
|
||||
} else {
|
||||
updateInventory();
|
||||
}
|
||||
|
||||
// function to sort arrays of objects after a keys value
|
||||
function sortByKey(array, key) {
|
||||
array.sort(function(a, b) {
|
||||
return b[key] - a[key];
|
||||
});
|
||||
}
|
||||
|
||||
document.querySelector("#search").addEventListener("input", function() {
|
||||
drawcontents();
|
||||
});
|
||||
@@ -1,41 +0,0 @@
|
||||
<!doctype HTML>
|
||||
<head>
|
||||
<%- include("head") %>
|
||||
<script defer src="<%= locals.root %>subspace_storage/storage.js"></script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<%- include("nav") %>
|
||||
<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">Resource</th>
|
||||
<th scope="col">Quantity</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody><!-- populated by js --></tbody>
|
||||
</table>
|
||||
</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://stackpath.bootstrapcdn.com/bootstrap/4.1.3/js/bootstrap.min.js"
|
||||
integrity="sha384-ChfqqxuZUCnJSK3+MXmPNIyE6ZbWh2IMqE241rYiqJxyMiZ6OW/JmZQ5stwEULTy"
|
||||
crossorigin="anonymous"></script>
|
||||
</body>
|
||||
@@ -2,8 +2,6 @@
|
||||
const assert = require("assert").strict;
|
||||
const fs = require("fs-extra");
|
||||
const path = require("path");
|
||||
const validateHTML = require("html5-validator");
|
||||
const parallel = require("mocha.parallel");
|
||||
|
||||
const link = require("@clusterio/lib/link");
|
||||
|
||||
@@ -11,24 +9,6 @@ const { slowTest, get, execCtl, sendRcon, getControl, instancesDir } = require("
|
||||
|
||||
|
||||
describe("Integration of Clusterio", function() {
|
||||
parallel("master web interface", function() {
|
||||
this.timeout(6000);
|
||||
|
||||
let paths = ["/", "/nodes", "/settings", "/nodeDetails"];
|
||||
for (let path of paths) {
|
||||
it(`sends some HTML when accessing ${path}`, async function() {
|
||||
let res = await get(path);
|
||||
let validation = await validateHTML(res.body.toString());
|
||||
let filtered = validation.messages.filter(msg => msg.type !== "info");
|
||||
assert(
|
||||
filtered.length === 0,
|
||||
"there are HTML errors on the page, please fix: "+JSON.stringify(validation.messages, null, 4)
|
||||
);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
describe("clusterioctl", function() {
|
||||
describe("slave list", function() {
|
||||
it("runs", async function() {
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
// These are libraries that does not have any units test, they are
|
||||
// required here in order to track coverage for them.
|
||||
|
||||
"use strict";
|
||||
require("@clusterio/master/npm_post_install.js");
|
||||
@@ -1,29 +0,0 @@
|
||||
<title>Clusterio</title>
|
||||
<link rel="stylesheet" type="text/css" href="<%= locals.root %>style.css">
|
||||
<script>root = "<%= locals.root %>";</script>
|
||||
<script src="<%= locals.root %>external/jquery.js"></script>
|
||||
<script src="<%= locals.root %>external/jquery-ui.js"></script>
|
||||
<script src="<%= locals.root %>external/moment.min.js"></script>
|
||||
<script src="<%= locals.root %>external/mousetrap.min.js"></script>
|
||||
<script src="<%= locals.root %>main.js"></script>
|
||||
<link href="<%= locals.root %>bootstrap.min.css" rel="stylesheet">
|
||||
<link href="<%= locals.root %>external/css/Roboto.css" rel="stylesheet">
|
||||
<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">
|
||||
@@ -1,17 +0,0 @@
|
||||
<!doctype HTML>
|
||||
<head>
|
||||
<%- include("head") %>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<%- include("nav") %>
|
||||
<div id="body" class="container">
|
||||
<div id="storage">
|
||||
<div class="page-header mt-2 row" id="banner">
|
||||
<div class="row">
|
||||
<h2>Work in progress interface</h2>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
@@ -1,29 +0,0 @@
|
||||
<nav class="navbar nav-pills nav-fill">
|
||||
<div class="nav-item col-3">
|
||||
<a class="navbar-brand border-0" href="/">
|
||||
<img src="<%= locals.root %>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="<%= locals.root %>nodes">Slaves</a>
|
||||
</div><%
|
||||
for (let plug of locals.masterPlugins.values()) {
|
||||
if (
|
||||
plug
|
||||
&& plug.ui
|
||||
&& typeof plug.ui.sidebar == "object"
|
||||
&& Array.isArray(plug.ui.sidebar)
|
||||
) {
|
||||
let sidebarEntries = plug.ui.sidebar;
|
||||
for (let button of sidebarEntries) {
|
||||
if (button.getHtml && typeof button.getHtml == "function") { %>
|
||||
<%- button.getHtml() %><%
|
||||
}
|
||||
}
|
||||
}
|
||||
} %>
|
||||
<div class="nav-item">
|
||||
<a class="nav-link align-middle" href="<%= locals.root %>settings">Settings</a>
|
||||
</div>
|
||||
</nav>
|
||||
@@ -1,32 +0,0 @@
|
||||
<!doctype HTML>
|
||||
<head>
|
||||
<%- include("head") %>
|
||||
<script src="lib/terminal.js"></script>
|
||||
<script src="nodeDetails.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="nodeDetails.css">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<%- include("nav") %>
|
||||
<div id="body" class="container">
|
||||
<div id="hero">
|
||||
<div id="display">
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div id="details">
|
||||
|
||||
</div>
|
||||
<div id="terminal">
|
||||
<div class="titleBar" style="display:none;">
|
||||
<div class="title">
|
||||
<p>Clusterio RCON shell</p>
|
||||
</div>
|
||||
<div class="titleButton close" onclick="minimizeTerminal();"></div>
|
||||
<div class="titleButton maximize" onclick="maximizeTerminal();"></div>
|
||||
<div class="titleButton minimize" onclick="minimizeTerminal();"></div>
|
||||
</div>
|
||||
<!-- JS generates a div called .Terminal that appears here with the display and typing things -->
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
@@ -1,47 +0,0 @@
|
||||
<!doctype HTML>
|
||||
<head>
|
||||
<%- include("head") %>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<%- include("nav") %>
|
||||
<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"><%
|
||||
for (let [id, slave] of locals.slaves) { %>
|
||||
<div id="<%= id %>" class="slaveBox card mb-3">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title"><a href='nodeDetails?instanceID=<%=id%>'>
|
||||
<%= slave.name %>
|
||||
</a></h5>
|
||||
</div>
|
||||
<ul class="list-group list-group-flush">
|
||||
<li class="list-group-item">ID: <%= id %></li>
|
||||
</ul><%
|
||||
for (let plug of locals.masterPlugins.values()) {
|
||||
if (
|
||||
plug
|
||||
&& plug.ui
|
||||
&& typeof plug.ui.slaveListing == "object"
|
||||
&& Array.isArray(plug.ui.slaveListing)
|
||||
) {
|
||||
let slaveListingEntries = plug.ui.slaveListing;
|
||||
for (let button of slaveListingEntries) {
|
||||
if (button.getHtml && typeof button.getHtml == "function") { %>
|
||||
<div class="plugin-link"><%- button.getHtml({slave}) %></div><%
|
||||
}
|
||||
}
|
||||
}
|
||||
} %>
|
||||
</div><%
|
||||
} %>
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
$('.plugin-link a').addClass('btn btn-primary')
|
||||
</script>
|
||||
</body>
|
||||
@@ -1,19 +0,0 @@
|
||||
<!doctype HTML>
|
||||
<head>
|
||||
<%- include("head") %>
|
||||
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<%- include("nav") %>
|
||||
<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>
|
||||
@@ -1 +0,0 @@
|
||||
<%- include("nav") %>
|
||||