aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorJakob Stendahl <14180120+JakobST1n@users.noreply.github.com>2021-10-11 20:02:04 +0200
committerGitHub <noreply@github.com>2021-10-11 20:02:04 +0200
commitc67531161e56488166a33232f87566309ba8676e (patch)
tree846e59a020e80bea48557d5a06af5728e44961ff /src
parente6880cd8ccf82d993f222cb14b4860581654acb8 (diff)
parentc1b6eec770b885a9829e1f62bad5cc99389ca429 (diff)
downloadLuxcena-Neo-c67531161e56488166a33232f87566309ba8676e.tar.gz
Luxcena-Neo-c67531161e56488166a33232f87566309ba8676e.zip
Merge pull request #24 from JakobST1n/rebuild
v1.0.0
Diffstat (limited to 'src')
-rw-r--r--src/Logger/index.js79
-rw-r--r--src/NeoRuntimeManager/IPC.js184
-rw-r--r--src/NeoRuntimeManager/RuntimeProcess.js103
-rw-r--r--src/NeoRuntimeManager/index.js311
-rw-r--r--src/SSLCert/index.js144
-rw-r--r--src/SelfUpdater/index.js68
-rw-r--r--src/SocketIO/index.js355
-rw-r--r--src/UserData/index.js333
-rw-r--r--src/compileAndRun/index.js75
-rw-r--r--src/compileAndRun/process.js41
-rw-r--r--src/compileAndRun/pythonSupportFiles/LuxcenaNeo/Matrix.py73
-rw-r--r--src/compileAndRun/pythonSupportFiles/LuxcenaNeo/NeoBehaviour.py29
-rw-r--r--src/compileAndRun/pythonSupportFiles/LuxcenaNeo/Strip.py121
-rw-r--r--src/compileAndRun/pythonSupportFiles/LuxcenaNeo/__init__.py10
-rw-r--r--src/compileAndRun/pythonSupportFiles/entry.py76
-rw-r--r--src/domain/middleware.js40
-rw-r--r--src/neoRuntime/index.js232
-rw-r--r--src/public/app.js19
-rw-r--r--src/public/app.scss9
-rw-r--r--src/public/components/sidenav.js24
-rw-r--r--src/public/js/general.js10
-rw-r--r--src/public/js/index.js68
-rw-r--r--src/public/js/logviewer.js64
-rw-r--r--src/public/js/neo_ide.js4
-rw-r--r--src/public/js/scripts.js71
-rw-r--r--src/public/scss/general.scss44
-rw-r--r--src/public/scss/neo_ide.scss254
-rw-r--r--src/public/scss/scripts.scss35
-rw-r--r--src/public/scss/setup.scss0
-rw-r--r--src/public/scss/update.scss62
-rw-r--r--src/runtimeData/index.js45
-rw-r--r--src/versionChecker/index.js58
32 files changed, 1577 insertions, 1464 deletions
diff --git a/src/Logger/index.js b/src/Logger/index.js
new file mode 100644
index 0000000..2ee216a
--- /dev/null
+++ b/src/Logger/index.js
@@ -0,0 +1,79 @@
+let fse = require("fs-extra");
+
+const level = {
+ EMERG: "EMERGENCY",
+ ALERT: "ALERT",
+ CRIT: "CRITICAL",
+ ERROR: "ERROR",
+ WARNING: "WARNING",
+ NOTICE: "NOTICE",
+ INFO: "INFO",
+ DEBUG: "DEBUG",
+
+ ACCESS: ""
+};
+
+
+Object.defineProperty(String.prototype, "lPad", {
+ value: function lPad(len, chr="0") {
+ str = this;
+ var i = -1;
+ if (!chr && chr !== 0) chr = ' ';
+ len = len - this.length;
+ while (++i < len) {
+ str = chr + str;
+ }
+ return str;
+ },
+ writeable: true,
+ configurable: true
+});
+
+
+function getTimeStamp() {
+ let CDate = new Date();
+ let day = CDate.getDate().toString().lPad(2);
+ let month = (CDate.getMonth() + 1).toString().lPad(2); // +1 because js starts to count at 0
+ let year = CDate.getFullYear();
+ let hour = CDate.getHours().toString().lPad(2);
+ let min = CDate.getMinutes().toString().lPad(2);
+ let sec = CDate.getSeconds().toString().lPad(2);
+ let ms = Math.round(CDate.getMilliseconds() / 10).toString().lPad(2); // divide by 10 to make the last digit decimal, then round.
+
+ return `${day}.${month}.${year}-${hour}:${min}:${sec}.${ms}`;
+}
+
+
+function log(object, logLevel=level.DEBUG, file="/lux-neo.log") {
+ fse.ensureFileSync(__logdir + file);
+
+ let formattedLogString = `[${getTimeStamp()}] ${logLevel} ${object}`;
+ console.log(formattedLogString); // @TODO: This should probably be removed, used for dev currently
+
+
+ fse.appendFile(
+ __logdir + "/lux-neo.log",
+ formattedLogString + '\n'
+ ).catch(err => {
+ console.log("EMERGENCY Could not write to log-file 'lux-neo.log'...");
+ console.log("DEBUG FileWriteError: " + err)
+ });
+
+ if (__event != undefined) {
+ __event.emit("logger", logLevel, object);
+ }
+}
+
+module.exports = {
+ level,
+ log,
+ emerg: (object) => { log(object, level.EMERG); },
+ alert: (object) => { log(object, level.ALERT); },
+ crit: (object) => { log(object, level.CRIT); },
+ error: (object) => { log(object, level.ERROR); },
+ warning: (object) => { log(object, level.WARNING); },
+ notice: (object) => { log(object, level.NOTICE); },
+ info: (object) => { log(object, level.INFO); },
+ debug: (object) => { log(object, level.DEBUG); },
+ access: (object) => { log(object, level.ACCESS, file="/access.log"); }
+};
diff --git a/src/NeoRuntimeManager/IPC.js b/src/NeoRuntimeManager/IPC.js
new file mode 100644
index 0000000..79ce7ea
--- /dev/null
+++ b/src/NeoRuntimeManager/IPC.js
@@ -0,0 +1,184 @@
+/**
+ * This module is used to communicate with a python NeoRuntime instance.
+ *
+ * @author jakobst1n.
+ * @since 3.10.2021
+ */
+
+const net = require("net");
+let logger = require(__appdir + "/src/Logger");
+
+/** @type {int} How long wait between each reconnection attempt */
+const RECONNECT_INTERVAL = 1000;
+/** @type {Object} ENUM-ish for command that can be sent to neoruntime */
+const COMMAND = Object.freeze({SET_GLOB : 0,
+ SET_VAR : 1,
+ SET_SEND_STRIP_BUF: 2});
+/** @type {Object} ENUM-ish for globvars */
+const GLOBVAR = Object.freeze({POWER_ON : 0,
+ BRIGHTNESS: 1});
+/** @type {Object} ENUM-ish for what type of data neoruntime sends */
+const DATATYPE = Object.freeze({STATES : 1,
+ STRIP_BUF: 2});
+
+/**
+ * class that will keep a active connection to a socket if possible, and
+ * automatically reconnect. It will emit events when data is received,
+ * and it will send commands to the process. */
+class IPC {
+
+ constructor(_socketFile, _eventEmitter) {
+ this.socketFile = _socketFile;
+ this.eventEmitter = _eventEmitter;
+
+ this.client;
+ this.connected = false;
+ this.reconnectInterval = false;
+
+ this.globvars = {};
+ this.variables = {};
+
+ this.reconnect();
+ }
+
+ /**
+ * If we are not already attempting to reconnect, this will start a
+ * interval that tries to reconnect. */
+ reconnect() {
+ if (this.reconnectInterval === false) {
+ this.reconnectInterval = setInterval(this.tryOpenSocketConnection.bind(this), RECONNECT_INTERVAL);
+ }
+ }
+
+ /**
+ * This will attempt to connect to the socket, and then setup all listeners
+ * if it succedes. */
+ tryOpenSocketConnection() {
+ // logger.info("Attempting to start IPC");
+
+ this.client = net.createConnection(this.socketFile)
+ .on('connect', () => {
+ clearInterval(this.reconnectInterval);
+ this.reconnectInterval = false;
+ // logger.info("IPC Connected.");
+ })
+ .on("ready", () => {
+ this.connected = true;
+ })
+ .on('data', (data) => {
+ switch (data[0]) {
+ case DATATYPE.STATES:
+ let json_data;
+ try {
+ json_data = JSON.parse(data.toString("ascii", 1));
+ } catch (e) {
+ logger.warning("Could not parse json data from neoruntime");
+ return;
+ }
+
+ if (json_data.hasOwnProperty("globvars")) {
+ forEachDiff(json_data["globvars"], this.globvars, (key, newVal) => {
+ this.eventEmitter.emit("change", key, newVal);
+ });
+ this.globvars = json_data["globvars"];
+ }
+ if (json_data.hasOwnProperty("variables")) {
+ forEachDiff(json_data["variables"], this.variables, (key, newVal) => {
+ this.eventEmitter.emit("change", `variable/${key}`, newVal);
+ });
+ this.variables = json_data["variables"];
+ }
+ break;
+
+ default:
+ logger.info(data);
+ }
+
+ })
+ .on("timeout", () => {
+ logger.info("IPC Timeout");
+ })
+ .on("close", (hadError) => {
+ // logger.info("IPC Close, hadError: ", hadError);
+ this.connected = false;
+ this.reconnect();
+ })
+ .on("end", () => {
+ // logger.info("IPC End");
+ this.connected = false;
+ })
+ .on('error', (data) => {
+ // logger.info('IPC Server not active.');
+ this.connected = false;
+ this.reconnect();
+ })
+ ;
+ }
+
+ /**
+ * Will send a command to the socket if we have a active connection,
+ * if not it will just drop the command. there is no queue implemented
+ * for such events. */
+ sendCommand(commandType, name, value) {
+ if (this.connected) {
+ let buf;
+
+ switch (commandType) {
+ case (COMMAND.SET_GLOB):
+ buf = Buffer.allocUnsafe(3);
+ buf[1] = name;
+ buf[2] = value;
+ break;
+
+ case (COMMAND.SET_VAR):
+ if (name.length > 32) { return {success: false, reason: "name too long", detail: "max size of name is 32 bytes"}; }
+ if (value.length > 93) { return {success: false, reason: "value too long", detail: "max size of value is 93 bytes"}; }
+ buf = Buffer.allocUnsafe(3 + name.length + value.length);
+ buf[1] = name.length;
+ buf[2] = value.length;
+ buf.write(name, 3, name.length, "ascii");
+ buf.write(value, 3+name.length, value.length, "ascii");
+ break;
+
+ case (COMMAND.SET_SEND_STRIP_BUF):
+ buf = Buffer.allocUnsafe(2);
+ buf[1] = (name) ? 1 : 0;
+ break;
+
+ default:
+ logger.warning(`IPC UNKNOWN COMMANDTYPE ${commandType}`)
+ return {success: false, reason: "ipc command unknown", detail: commandType};
+ }
+
+ buf[0] = commandType;
+ this.client.write(buf);
+ return {success: true}
+ }
+ return {success: false, reason: "socket not connected", detail: "This usually means the python script is not running"};
+ }
+
+}
+
+const isObject = v => v && typeof v === 'object';
+
+/**
+ * Will call callback on all the differences between the dicts
+ */
+function forEachDiff(dict1, dict2, callback) {
+ for (const key of new Set([...Object.keys(dict1), ...Object.keys(dict2)])) {
+ if (isObject(dict1[key]) && isObject(dict2[key])) {
+ if (dict1[key].value !== dict2[key].value) {
+ callback(key, dict1[key]);
+ }
+ } else if (dict1[key] !== dict2[key]) {
+ if (isObject(dict2[key]) && (dict1[key] == null)) {
+ dict2[key].value = null;
+ callback(key, dict2[key])
+ } else {
+ callback(key, dict1[key]);
+ }
+ }
+ }
+}
+
+module.exports = {IPC, COMMAND, GLOBVAR};
diff --git a/src/NeoRuntimeManager/RuntimeProcess.js b/src/NeoRuntimeManager/RuntimeProcess.js
new file mode 100644
index 0000000..c5c4749
--- /dev/null
+++ b/src/NeoRuntimeManager/RuntimeProcess.js
@@ -0,0 +1,103 @@
+let fs = require("fs-extra");
+let spawn = require("child_process");
+
+class RuntimeProcess {
+
+ constructor(_modePath, _eventEmitter) {
+ this.modePath = _modePath;
+ this.logfile = `${this.modePath}/mode.log`;
+ this.errfile = `${this.modePath}/mode.error`;
+
+ this.stdout = "";
+ this.stderr = "";
+
+ this.fl = false;
+ this.proc = null;
+
+ this.isRunning = false;
+ this.exitCode = null;
+
+ this.eventEmitter = _eventEmitter;
+ }
+
+ start() {
+ if (this.isRunning) {
+ console.log("PROCESS ALREADY RUNNING");
+ return;
+ }
+ this.isRunning = true;
+ this.proc = spawn.spawn(
+ `${__appdir}/NeoRuntime/Runtime/venv/bin/python`,
+ //"python",
+ [
+ "-u", // This makes us able to get real-time output
+ `${__appdir}/NeoRuntime/Runtime/neo_runtime.py`,
+ `--strip-config="${__configdir}/strip.ini"`,
+ `--mode-path="${this.modePath}"`,
+ `--mode-entry=script`
+ ]
+ );
+
+ this.proc.on('error', (err) => {
+ console.log(err);
+ });
+
+ fs.ensureFileSync(this.logfile);
+ fs.ensureFileSync(this.errfile);
+ this.eventEmitter.emit("proc:start");
+
+ this.proc.stdout.on('data', (_stdout) => {
+ let stdout_str = _stdout.toString();
+ fs.appendFile(this.logfile, `[${timestamp()}]: ` + stdout_str);
+ this.eventEmitter.emit("proc:stdout", stdout_str);
+ });
+
+ this.proc.stdout.on('end', () => {
+ fs.appendFile(this.logfile, "\n");
+ });
+
+ this.proc.stderr.on('data', (_stderr) => {
+ let stderr_str = _stderr.toString();
+ fs.appendFile(this.errfile, `[${timestamp()}]: ` + stderr_str);
+ this.eventEmitter.emit("proc:stderr", stderr_str);
+ });
+
+ this.proc.stderr.on('end', () => {
+ fs.appendFile(this.logfile, "\n");
+ });
+
+ this.proc.on('close', (code) => {
+ if (code) {
+ fs.appendFile(this.logfile, `[${timestamp()}]: ` + "Script exited with code " + code.toString());
+ }
+ this.eventEmitter.emit("proc:exit", 0);
+ this.isRunning = false;
+ this.exitCode = code;
+ });
+
+ }
+
+ stop(restart=false) {
+ try {
+ if (restart) {
+ this.proc.once("close", () => {
+ setTimeout(() => this.start(), 500);
+ });
+ }
+ this.proc.kill("SIGINT");
+ } catch (err) {
+ console.log(err);
+ }
+ }
+}
+
+/**
+ * Creates and returns a timestamp that can be used in logfiles.
+ *
+ * @return {string} timestamp
+ */
+function timestamp() {
+ return (new Date()).toISOString();
+}
+
+module.exports = RuntimeProcess;
diff --git a/src/NeoRuntimeManager/index.js b/src/NeoRuntimeManager/index.js
new file mode 100644
index 0000000..4377b8a
--- /dev/null
+++ b/src/NeoRuntimeManager/index.js
@@ -0,0 +1,311 @@
+/**
+ * This module is used to execute and communicate with a python NeoRuntime instance.
+ *
+ * @author jakobst1n.
+ * @since 19.12.2019
+ */
+
+const fs = require("fs");
+const fsPromises = fs.promises;
+const RuntimeProcess = require("./RuntimeProcess");
+const IPC = require("./IPC");
+const logger = require(__appdir + "/src/Logger");
+const EventEmitter = require('events');
+
+/** @type {object} this should be a pointer to a object referencing all neoModules (see app.js) */
+let neoModules;
+
+/** @type {string} Currently active mode */
+let modeId = null;
+/** @type {int} Last exit code of a mode */
+let modeExitCode = 0;
+/** @type {RuntimeProcess} This is the current RuntimeProcess instance */
+let runtimeProcess = null;
+/** @type {IPC} The IPC instance, used to communicate with the script */
+let ipc = null;
+/** @type {EventEmitter} This is used to emit events when things change */
+const eventEmitter = new EventEmitter();
+/** @type {boolean} If this is true, we will not do things the usual way */
+let modeDebuggerActive = false;
+/** @type {string} Should be the modeId the debugger is attached to */
+let modeDebuggerId = null;
+
+eventEmitter.on("proc:exit", (code) => modeExitCode = code);
+
+/**
+ * Check if a path id actually a mode (if it is a folder with a script.py file)
+ *
+ * @param {string} path - Path to check.
+ *
+ * @return {boolean} wether the path points to a valid mode.
+ */
+function isMode(path) {
+ if (!fs.existsSync(path)) { return false; }
+ let folderStat = fs.statSync(path);
+ if (!folderStat.isDirectory()) { return false; }
+ if (!fs.existsSync(path + "/script.py")) { return false; }
+ return true;
+}
+
+/**
+ * Get all ids of modes that can be set.
+ *
+ * @returns {array} All modeids
+ */
+function listModes() {
+ let modeDirs = [
+ ["builtin/", fs.readdirSync(__appdir + "/NeoRuntime/builtin")],
+ ["remote/", fs.readdirSync(__datadir + "/remoteCode")],
+ ["user/", fs.readdirSync(__datadir + "/userCode")]
+ ]
+ let validModes = [];
+ for (modeDir of modeDirs) {
+ for (modeName of modeDir[1]) {
+ let modeId = `${modeDir[0]}${modeName}`;
+ if (isMode(getModePath(modeId))) {
+ validModes.push(modeId);
+ }
+ }
+ }
+ return validModes;
+}
+
+/**
+ * Change mode, stop the old one and start the new one.
+ *
+ * @param {string} _modeId - Id of the mode to change to.
+ *
+ * @return {object} A standardform return object.
+ */
+function setMode(_modeId) {
+ if (modeDebuggerActive && (_modeId != modeDebuggerId)) {
+ return {success: false, reason: "debugger active", detail: "Cannot change mode when debugger is active."}
+ }
+ if (!isMode(getModePath(_modeId))) {
+ console.log(`Invalid mode "${_modeId}".`);
+ return {success: false, reason: "unknown modeId"};
+ }
+ logger.info(`Changing mode to "${_modeId}".`);
+
+ stopMode();
+
+ modeId = _modeId;
+ neoModules.userData.config.activeMode = modeId;
+ eventEmitter.emit("change", "mode", modeId);
+
+ runtimeProcess = new RuntimeProcess(getModePath(_modeId), eventEmitter);
+ startMode();
+
+ return {success: true}
+};
+
+/**
+ * Get current mode
+ *
+ * @return {string} current modeId
+ */
+function currentMode() {
+ return modeId;
+}
+
+/**
+ * Will attempt to stop current mode
+ *
+ * @return {object} A standardform return object.
+ */
+function stopMode(restart=false) {
+ if (modeRunning()) {
+ runtimeProcess.stop(restart);
+ }
+ return {success: true}
+};
+
+/**
+ * Will attempt to start current mode
+ *
+ * @return {object} A standardform return object.
+ */
+function startMode() {
+ if (runtimeProcess === null) { return {success: false, reason: "no runtimeprocess", detail: "Runtimeprocess not set, did you mean to call setMode?"}; }
+ runtimeProcess.start();
+ return {success: true}
+};
+
+/**
+ * Will attempt to restart current mode
+ *
+ * @return {object} A standardform return object.
+ */
+function restartMode() {
+ return stopMode(true);
+};
+
+/**
+ * Checks if mode is running currently
+ *
+ * @return {boolean} if mode is running
+ */
+function modeRunning() {
+ if (runtimeProcess === null) { return false; }
+ return runtimeProcess.isRunning;
+};
+
+/**
+ * Get the full system path to a mode
+ *
+ * @param {string} modeId
+ *
+ * @return {string} Full path of mode
+ */
+function getModePath(modeId) {
+ let path = modeId.split("/");
+ let location = path.splice(0, 1).toString();
+ if (location === "user") { path = __datadir + "/userCode/" + path.join("/"); }
+ if (location === "remote") { path = __datadir + "/remoteCode/" + path.join("/"); }
+ if (location === "builtin") { path = __appdir + "/NeoRuntime/builtin/" + path.join("/"); }
+ return path;
+}
+
+/**
+ * This should be called by RuntimeProcess when a variable changes in the mode
+ *
+ * @param {string} location - This is globvars/variables
+ * @param {string} name - Name of the variable
+ * @param {any} newValue - The new value of the variable
+ */
+function onVariableChange(location, name, newValue) {
+ if (location == "variables") {
+ eventEmitter.emit("change", `variable/${name}`, newValue)
+ } else if (location == "globvars") {
+ eventEmitter.emit("change", `${name}`, newValue)
+ }
+}
+
+/**
+ * Function that returns all globvars (brightness, power_on) as the values they
+ * had last time we heard from the python script.
+ *
+ * @return {object}
+ */
+function getGlobvars() {
+ if (!modeRunning()) { return {}; }
+ return ipc.globvars;
+}
+
+/**
+ * Sets value of a globvar power_on/brightness.
+ *
+ * @param {string} name - Name of the variable power_on/brightness
+ * @param {any} value - The value the variable should be set to
+ *
+ * @return {object} Standardform return object
+ */
+function setGlobvar(name, value) {
+ if (!modeRunning()) { return; }
+
+ switch(name) {
+ case "power_on":
+ return ipc.sendCommand(IPC.COMMAND.SET_GLOB, IPC.GLOBVAR.POWER_ON, (value) ? 1 : 0);
+ case "brightness":
+ return ipc.sendCommand(IPC.COMMAND.SET_GLOB, IPC.GLOBVAR.BRIGHTNESS, value);
+ default:
+ return {success: false, reason: "unknown globvar", detail: name};
+ }
+}
+
+/**
+ * Get all variables declared in mode
+ *
+ * @return {object}
+ */
+function getVariables() {
+ if (!modeRunning()) { return {}; }
+ return ipc.variables;
+}
+
+/**
+ * Sets value of a variable
+ *
+ * @param {string} name - Name of the variable
+ * @param {any} value - The value the variable should be set to
+ *
+ * @return {object} Standardform return object
+ */
+function setVariable(name, value) {
+ if (!modeRunning()) { return; }
+ return ipc.sendCommand(IPC.COMMAND.SET_VAR, name, value);
+}
+
+/**
+ * Start debugger for a mode
+ *
+ * @param {string} modeId - The mode to debug
+ *
+ * @return {object} Standardform return object
+ */
+function startDebugger(debuggerModeId) {
+ if (debuggerModeId.substr(0, 5) !== "user/") { return {success: false, reason: "not user mode"}; }
+ if (!isMode(getModePath(debuggerModeId))) { return {success: false, reason: "unknown modeId"}; }
+ if (modeDebuggerActive) { return {success: false, reason: "debugger already active"}; }
+ logger.info(`Starting debugger for ${debuggerModeId}`);
+ modeDebuggerActive = true;
+ modeDebuggerId = debuggerModeId;
+ if (debuggerModeId != modeId) {
+ setMode(debuggerModeId);
+ } else {
+ restartMode();
+ }
+ return {success: true, code: fs.readFileSync(getModePath(debuggerModeId) + "/script.py").toString()}
+}
+
+/**
+ * Save mode
+ */
+function saveModeCode(_modeId, code) {
+ if (!modeDebuggerActive) { return {success: false, reason: "debugger not active"}; };
+ if (_modeId != modeDebuggerId) { return {success: false, reason: "modeid not the same as debuggermodeid"}; };
+ fs.writeFileSync(getModePath(`${modeDebuggerId}/script.py`), code);
+ return {success: true};
+}
+
+/**
+ * Stop the active debugger
+ *
+ * @return {object} Standardform return object
+ */
+function stopDebugger() {
+ if (!modeDebuggerActive) { return {success: true, detail: "No debugger active"} }
+ logger.info(`Stopping debugger`);
+ modeDebuggerActive = false;
+ return {success: true}
+}
+
+module.exports = (_neoModules) => {
+ neoModules = _neoModules;
+ ipc = new IPC.IPC(neoModules.userData.config.neoRuntimeIPC.socketFile, eventEmitter);
+ return {
+ event: eventEmitter,
+ modes: listModes,
+ mode: {
+ current: currentMode,
+ set: (modeId) => setMode(modeId),
+ status: {
+ modeRunning: modeRunning(),
+ modeExitCode: modeExitCode
+ },
+ globvars: {
+ get: getGlobvars,
+ set: setGlobvar
+ },
+ variables: {
+ get: getVariables,
+ set: setVariable
+ }
+ },
+ getModePath,
+ isMode,
+ modeRunning,
+ startDebugger, stopDebugger, saveModeCode,
+ startMode, stopMode, restartMode
+ }
+};
diff --git a/src/SSLCert/index.js b/src/SSLCert/index.js
new file mode 100644
index 0000000..d235c9b
--- /dev/null
+++ b/src/SSLCert/index.js
@@ -0,0 +1,144 @@
+/**
+ * Module that exports an instance of CertMon
+ * see class definition to see what it does.
+ *
+ * Requires global var '__datadir' to be set.
+ *
+ * @author jakobst1n.
+ * @since 14.16.2019
+ */
+ let logger = require(__appdir + "/src/Logger");
+ const fs = require("fs");
+ const { execSync } = require("child_process");
+
+var neoModules;
+
+ /**
+ * This checks if the server has a valid certificate, if not,
+ * it will generate one.
+ */
+ class CertMon {
+
+ constructor(configPath, certPath, httpsConfig) {
+ this.certPath = __configdir + "/certs/";
+
+ let valid = this.checkValidity();
+ if (!valid) {
+ logger.notice("No valid certificate found, creating one now.");
+ this.generateCert();
+ }
+
+ let interval = setInterval(() => {
+ let certIsValid = this.checkValidity();
+ if (!valid) {
+ logger.crit("Certificate no longer valid, server should reboot to make a new one.");
+ }
+ }, 1440000); // Run once every day
+ }
+
+ checkValidity() {
+ let sslConfig = this.getConfig();
+ if (!sslConfig["certMade"]) {
+ logger.debug("'certMade' in config is false, assuming no valid certificate");
+ return false;
+ }
+ let expire = ((sslConfig["certExpire"] - Date.now()) / 86400000).toFixed(2);
+ if (expire > 0) {
+ logger.debug(`Certificate should be valid for ${expire} more days.`);
+ } else {
+ expire = Math.abs(expire);
+ logger.debug(`Certificate expired ${expire} days ago`);
+ return false;
+ }
+ return true;
+ }
+
+ getConfig() {
+ return neoModules.userData.config.SSLCert;
+ }
+
+ updateConfig(parameters) {
+ neoModules.userData.config.set(parameters);
+ }
+
+ generateCert() {
+ let certPath = this.certPath;
+ let config = this.getConfig();
+
+
+ // Create Root Certificate Autority
+ let res = openssl(
+ `genrsa ` +
+ `-out "${certPath}/root-CA.key.pem" ` +
+ `2048`
+ );
+
+ // Self sign the Root Certificate Autority
+ res = openssl(
+ `req ` +
+ `-x509 ` +
+ `-new ` +
+ `-nodes ` +
+ `-key "${certPath}/root-CA.key.pem" ` +
+ `-days 1024 ` +
+ `-out "${certPath}/root-CA.crt.pem" ` +
+ `-subj "/C=NO/ST=Oslo/L=Oslo/O=Luxcena Neo Self-Signing Authority/CN=${config.CN}"`
+ );
+
+ // Create a Device Certificate for each domain,
+ // such as example.com, *.example.com, awesome.example.com
+ // NOTE: You MUST match CN to the domain name or ip address you want to use
+ res = openssl(
+ `genrsa ` +
+ `-out "${certPath}/privkey.pem" ` +
+ `2048`
+ );
+
+ // Create a request from your Device, which your Root CA will sign
+ res = openssl(
+ `req ` +
+ `-new ` +
+ `-key "${certPath}/privkey.pem" ` +
+ `-out "${certPath}/csr.pem" ` +
+ `-subj "/C=NO/ST=Oslo/L=Oslo/O=Luxcena Neo Self-Signing Autohity/CN=${config.CN}"`
+ );
+
+ // Sign the request from Device with your Root CA
+ // -CAserial certs/ca/my-root-ca.srl
+ res = openssl(
+ `x509 ` +
+ `-req ` +
+ `-in "${certPath}/csr.pem" ` +
+ `-CA "${certPath}/root-CA.crt.pem" ` +
+ `-CAkey "${certPath}/root-CA.key.pem" ` +
+ `-CAcreateserial ` +
+ `-out "${certPath}/cert.pem" ` +
+ `-days 500`
+ );
+
+ let creationDate = Date.now();
+ config.certMade = true;
+ config.certDate = creationDate;
+ config.certExpire = creationDate + (500*86400000);
+ config.certCN = config.CN;
+
+ logger.info("Self-signed certificate created.");
+
+ }
+
+ }
+
+function openssl(command) {
+ try {
+ let stdout = execSync("openssl " + command);
+ return true
+ } catch (e) {
+ return false
+ }
+ }
+
+module.exports = (_neoModules) => {
+ neoModules = _neoModules;
+ return new CertMon();
+};
+
diff --git a/src/SelfUpdater/index.js b/src/SelfUpdater/index.js
new file mode 100644
index 0000000..5a9baa3
--- /dev/null
+++ b/src/SelfUpdater/index.js
@@ -0,0 +1,68 @@
+let fs = require("fs-extra");
+let url = require("url");
+let request = require('request');
+let exec = require("child_process").exec;
+let logger = require(__appdir + "/src/Logger");
+
+let neoModules;
+
+class VersionChecker {
+
+ constructor() {
+ this.CPackageJson = JSON.parse(fs.readFileSync(__appdir + "/package.json"));
+ this.version = this.CPackageJson["version"];
+ this.repoLink = this.CPackageJson["repository"]["url"];
+
+ this.checkFrequency = neoModules.userData.config.SelfUpdater.checkVersionInterval * 86400000; // Takes in days.
+ this.repoBranch = neoModules.userData.config.SelfUpdater.branch;
+
+ this.remotePackageJSON = "https://raw.githubusercontent.com" + url.parse(this.repoLink).pathname + "/" + this.repoBranch + "/package.json";
+
+ this.newVersion = false;
+ this.newestVersion = this.checkVersion(this.remotePackageJSON);
+
+ this.updateChecker = setInterval(() => {
+ let newVersion = this.checkVersion(this.remotePackageJSON);
+ }, this.checkFrequency);
+
+ }
+
+ checkVersion() {
+ request.get(this.remotePackageJSON, (error, response, body) => {
+ if (!error && response.statusCode === 200) {
+ let remotePackageJSON = JSON.parse(body);
+ let newestVersion = remotePackageJSON["version"];
+ if (this.VersionIsNewerThan(newestVersion, this.version)) {
+ logger.notice("A new version is available on \"" + this.repoBranch + "\" (v" + this.version + ")");
+ this.newVersion = true;
+ } else {
+ logger.info(`Running newest version (${newestVersion})`);
+ this.newVersion = false;
+ }
+ this.newestVersion = newestVersion;
+ } else {
+ logger.notice("Could not find latest version! Please check you internet connection.");
+ }
+ });
+ }
+
+ VersionIsNewerThan(check, current) {
+ let checkParts = check.split(".");
+ let currentParts = current.split(".");
+ let lCheckParts = checkParts.length;
+ let lCurrentParts = currentParts.length;
+
+ for (let i = 0; i < lCheckParts; i++) {
+ if (i >= lCurrentParts) { return true; }
+ if (Number (checkParts[i]) > Number (currentParts[i])) { return true; }
+ if (Number (checkParts[i]) < Number (currentParts[i])) { return false; }
+ }
+ return false;
+ }
+
+}
+ 
+module.exports = (_neoModules) => {
+ neoModules = _neoModules;
+ return new VersionChecker();
+};
diff --git a/src/SocketIO/index.js b/src/SocketIO/index.js
new file mode 100644
index 0000000..1803845
--- /dev/null
+++ b/src/SocketIO/index.js
@@ -0,0 +1,355 @@
+/**
+ * This module contains code for handling socketIO clients.
+ *
+ * There are two classes, one is a SocketIO controller module.
+ * The other one is a authorizedclient.
+ *
+ * @author jakobst1n.
+ * @since 19.12.2019
+ */
+
+let logger = require(__appdir + "/src/Logger");
+var exec = require('child_process').exec;
+var CryptoJS = require("crypto-js");
+let fs = require("fs");
+
+let neoModules;
+
+const sanitizePath = (path) => path.match(/(user|remote|builtin\/[a-zA-Z0-9-_\/]{1,200})(\.[a-zA-Z0-9]{1,10})?/)[0];
+
+/**
+ * Create the open socketio namespace and setup all listeners.
+ *
+ * @param {io} socketio
+ */
+function createOpenSocketNamespace(io) {
+ const openNamespace = io.of("/open")
+
+ openNamespace.on("connection", (socket) => {
+ logger.access(`SOCKET:open Client (${socket.id}@${socket.handshake.headers.host}) connected.`);
+
+ socket.on("name:get", () => {
+ socket.emit("name", neoModules.userData.config.instanceName);
+ });
+ socket.on("mode:set", (modeId) => {
+ neoModules.neoRuntimeManager.mode.set(modeId);
+ });
+ socket.on("mode:get", () => {
+ socket.emit("mode", neoModules.neoRuntimeManager.mode.current());
+ });
+ socket.on("modelist:get", () => {
+ socket.emit("modelist", neoModules.neoRuntimeManager.modes())
+ });
+ socket.on("brightness:set", (brightness) => {
+ neoModules.neoRuntimeManager.mode.globvars.set("brightness", brightness);
+ });
+ socket.on("brightness:get", () => {
+ socket.emit("brightness", neoModules.neoRuntimeManager.mode.globvars.get().brightness);
+ });
+ socket.on("power:set", (power) => {
+ neoModules.neoRuntimeManager.mode.globvars.set("power_on", power);
+ });
+ socket.on("power:get", () => {
+ socket.emit("power", neoModules.neoRuntimeManager.mode.globvars.get().power_on);
+ });
+ socket.on("var:set", (name, value) => {
+ neoModules.neoRuntimeManager.mode.variables.set(name, value);
+ });
+ socket.on("vars:get", () => {
+ socket.emit("vars", neoModules.neoRuntimeManager.mode.variables.get());
+ });
+ socket.on("modeinfo:get", () => {
+ socket.emit("modeinfo", {
+ mode: neoModules.neoRuntimeManager.mode.current(),
+ brightness: neoModules.neoRuntimeManager.mode.globvars.get().brightness,
+ power: neoModules.neoRuntimeManager.mode.globvars.get().power_on,
+ vars: neoModules.neoRuntimeManager.mode.variables.get()
+ });
+ });
+ socket.on("authenticate:user", (username, password, callback) => {
+ let user = neoModules.userData.user.get(username);
+ if (user == null) {
+ callback({success: false, reason: "Invalid username/password"})
+ logger.access(`SOCKET:open Client (${socket.id}@${socket.handshake.headers.host}) tried to log in as '${username}', wrong username and/or password.`);
+ return;
+ }
+
+ let providedHash = hashPassword(password, user.salt);
+ if (providedHash.hash == user.password) {
+ let token = createToken(socket);
+ while (session_tokens.hasOwnProperty(token)) {
+ token = createToken(socket);
+ }
+
+ session_tokens[token] = {
+ expire: (~~Date.now())+(86400),
+ host: socket.handshake.headers.host,
+ user: {username: user.username}
+ };
+
+ callback({success: true, user: {username: username}, token: token})
+ logger.access(`SOCKET:open Client (${socket.id}@${socket.handshake.headers.host}) authenticated as user '${username}'`);
+ return;
+ }
+
+ callback({success: false, reason: "Invalid username/password"})
+ logger.access(`SOCKET:open Client (${socket.id}@${socket.handshake.headers.host}) tried to log in as '${username}', wrong username and/or password.`);
+ });
+
+ socket.on("disconnect", () => {
+ logger.access(`SOCKET:open Client (${socket.id}@${socket.handshake.headers.host}) disconnected.`);
+ });
+ });
+
+ neoModules.neoRuntimeManager.event.on("change", (name, value) => {
+ if (name == "modelist") {
+ openNamespace.emit("modelist", neoModules.neoRuntimeManager.modes());
+ } else if (["mode", "power_on", "brightness"].includes(name)) {
+ if (name == "power_on") { name = "power"; }
+ openNamespace.emit(name, value);
+ } else {
+ openNamespace.emit("var", name, value);
+ }
+ });
+}
+
+/**
+ * @type {object} This is the collection of valid session tokens.
+ */
+let session_tokens = {};
+
+/**
+ * Middleware that will stop the request if the client does not have a valid
+ * session token.
+ *
+ * @param {object} socket - The socket instance of the connected client
+ * @param {function} next - The callback to continue the middleware chain
+ */
+function authorize_middleware(socket, next) {
+ const token = socket.handshake.auth.token;
+
+ if (session_tokens.hasOwnProperty(token) &&
+ session_tokens[token].host === socket.handshake.headers.host &&
+ session_tokens[token].expire > (~~(Date.now()))) {
+ socket.data.user = session_tokens[token].user;
+ next();
+ } else {
+ const err = new Error("not authorized");
+ err.data = { content: "invalid session token" };
+ next(err);
+ }
+}
+
+/**
+ * Create the open socketio namespace and setup all listeners.
+ * A valid session token is required to connect to this namespace.
+ *
+ * @param {io} socetio
+ */
+function createAuthorizedNamespace(io) {
+ const authorizedNamespace = io.of("/authed");
+ authorizedNamespace.use(authorize_middleware);
+ authorizedNamespace.on("connection", (socket) => {
+ logger.access(`SOCKET:authed Client (${socket.id}@${socket.handshake.headers.host}) connected.`);
+ let debuggerOpen = false;
+
+ socket.emit("user", socket.data.user);
+
+ /* InstanceName */
+ socket.on("name:set", (name, fn) => {
+ neoModules.userData.config.instanceName = name;
+ fn({success: true});
+ io.emit("name", neoModules.userData.config.instanceName);
+ });
+
+ /* UserData */
+ socket.on("mode:create", (name, template, fn) => {
+ fn(neoModules.userData.mode.create(name, template));
+ });
+ socket.on("mode:delete", (modeid, fn) => {
+ fn(neoModules.userData.mode.delete(modeid));
+ });
+
+ /* LED Config */
+ socket.on("led_config:get", () => {
+ socket.emit("led_config", neoModules.userData.strip.get());
+ });
+ socket.on("led_config:set", (config) => {
+ neoModules.userData.strip.set(config);
+ });
+
+ /* SelfUpdater */
+ socket.on("version:current_number", () => {
+ socket.emit("version:current_number", neoModules.selfUpdater.version);
+ });
+ socket.on("version:branch", (fn) => {
+ socket.emit("version:branch", neoModules.selfUpdater.repoBranch);
+ });
+ socket.on("version:newest_number", (fn) => {
+ socket.emit("version:newest_number", neoModules.selfUpdater.newestVersion);
+ });
+ socket.on("version:check_for_update", (fn) => {
+ neoModules.selfUpdater.checkVersion();
+ socket.emit("version:newest_number", neoModules.selfUpdater.newestVersion);
+ fn({success: true});
+ });
+ socket.on("system:update_version", () => {
+ let p = exec('luxcena-neo-cli.sh update');
+ p.unref();
+ });
+
+ /* SSLCert */
+ socket.on("sslcert:info", (fn) => {
+ socket.emit("sslcert:info", {...neoModules.SSLCert.getConfig(), "isValid": neoModules.SSLCert.checkValidity()});
+ });
+ socket.on("sslcert:generate_new", (fn) => {
+ neoModules.SSLCert.generateCert();
+ fn({success: true});
+ });
+
+ /* System actions */
+ socket.on("restart:system", () => {
+ exec('shutdown -r now', function(error, stdout, stderr){ callback(stdout); });
+ });
+ socket.on("restart:service", () => {
+ let p = exec('systemctl restart luxcena-neo');
+ p.unref();
+ });
+
+ /* Users */
+ socket.on("users:get", () => {
+ socket.emit("users", neoModules.userData.users())
+ });
+ socket.on("user:delete", (username, fn) => {
+ if (username == socket.data.user.username) { fn({success: false, reason: "cannot delete logged in account"}); return; }
+ fn(neoModules.userData.user.delete(username));
+ socket.emit("users", neoModules.userData.users())
+ });
+ socket.on("user:changeusername", (oldusername, newusername, fn) => {
+ if (oldusername == socket.data.user.username) { fn({success: false, reason: "cannot change username of logged in account"}); return; }
+ let user = neoModules.userData.user.get(oldUserName);
+ if (user == null) { fn({success: false, reason: "unknown username", detail: oldusername}); return; }
+ user.username = newusername;
+ let res = neoModules.userData.user.save(user);
+ if (!res.success) { fn(res); return; }
+ res = neoModules.userData.user.delete(oldusername)
+ if (!res.success) { fn(res); return; }
+ fn({success: true});
+ socket.emit("users", neoModules.userData.users())
+ });
+ socket.on("user:newpassword", (username, newPassword, fn) => {
+ let user = neoModules.userData.user.get(username);
+ if (user == null) { fn({success: false, reason: "unknown username", detail: username}); return; }
+ let newHash = hashPassword(newPassword);
+ fn(neoModules.userData.user.save(username, newHash.salt, newHash.hash));
+ socket.emit("users", neoModules.userData.users())
+ });
+ socket.on("user:create", (username, newPassword, fn) => {
+ let user = neoModules.userData.user.get(username);
+ if (user != null) { fn({success: false, reason: "user already exists", detail: username}); return; }
+ if (username.length < 1) { fn({success: false, reason: "no username provided"}); return; }
+ let newHash = hashPassword(newPassword);
+ fn(neoModules.userData.user.save(username, newHash.salt, newHash.hash));
+ socket.emit("users", neoModules.userData.users())
+ });
+
+ /* Editor/debugger */
+ let onProcStart = () => socket.emit("editor:proc:start");
+ let onProcStop = (code) => socket.emit("editor:proc:exit", code);
+ let onProcStdout = (stdout) => socket.emit("editor:proc:stdout", stdout);
+ let onProcStderr = (stderr) => socket.emit("editor:proc:stderr", stderr);
+ let closeDebugger = () => {
+ debuggerOpen = false;
+ neoModules.neoRuntimeManager.event.removeListener("proc:start", onProcStart);
+ neoModules.neoRuntimeManager.event.removeListener("proc:stop", onProcStop);
+ neoModules.neoRuntimeManager.event.removeListener("proc:stdout", onProcStdout);
+ neoModules.neoRuntimeManager.event.removeListener("proc:stderr", onProcStderr);
+ return neoModules.neoRuntimeManager.stopDebugger();
+ };
+ socket.on("editor:open", (modeId, fn) => {
+ let res = neoModules.neoRuntimeManager.startDebugger(modeId);
+ if (!res.success) { fn(res); return; }
+ logger.info(`Starting debugger for ${modeId}.`)
+ debuggerOpen = true;
+ fn({success: true})
+ socket.emit("editor:code", modeId, res.code);
+
+ neoModules.neoRuntimeManager.event.on("proc:start", onProcStart);
+ neoModules.neoRuntimeManager.event.on("proc:exit", onProcStop);
+ neoModules.neoRuntimeManager.event.on("proc:stdout", onProcStdout);
+ neoModules.neoRuntimeManager.event.on("proc:stderr", onProcStderr);
+ if (neoModules.neoRuntimeManager.modeRunning()) {
+ socket.emit("editor:proc:start");
+ }
+ });
+ socket.on("editor:save", (modeId, code, fn) => {
+ if (!debuggerOpen) { fn({success: false, reason: "Debugger not open"}); return; };
+ fn(neoModules.neoRuntimeManager.saveModeCode(modeId, code));
+ });
+ socket.on("editor:startmode", (fn) => {
+ fn(neoModules.neoRuntimeManager.startMode());
+ });
+ socket.on("editor:stopmode", (fn) => {
+ fn(neoModules.neoRuntimeManager.stopMode());
+ });
+ socket.on("editor:restartmode", (fn) => {
+ fn(neoModules.neoRuntimeManager.restartMode());
+ });
+ socket.on("editor:close", (fn) => {
+ fn(closeDebugger());
+ logger.info("Stopped debugger");
+ });
+
+ socket.on("disconnect", () => {
+ logger.access(`SOCKET:authed Client (${socket.id}@${socket.handshake.headers.host}) disconnected.`);
+ if (debuggerOpen) {
+ closeDebugger();
+ logger.info("Stopped debugger because client disconnected")
+ }
+ });
+ });
+}
+
+/**
+ * Creates an access-token from the clients host-name and the current EPOCH.
+ *
+ * @param {client}
+ *
+ * @return {string} - The access-token.
+ */
+ function createToken(client) {
+ let time = Date.now().toString();
+ let host = client.handshake.headers.host;
+ return (CryptoJS.SHA256(`${host}${time}`).toString());
+}
+
+/**
+ * Create a new salt and hash from a password.
+ *
+ * @param {string} password - The password to hash.
+ * @param {string} salt - If set, this salt will be used, else a new salt is generated.
+ *
+ * @return {object} A object containing a password and a salt property.
+ */
+function hashPassword(password, salt = null) {
+ if (salt == null) {
+ salt = CryptoJS.lib.WordArray.random(128 / 2);
+ } else {
+ salt = CryptoJS.enc.Hex.parse(salt);
+ }
+ let hash = CryptoJS.PBKDF2(password, salt, {
+ keySize: 512 / 32,
+ iterations: 1000,
+ hasher: CryptoJS.algo.SHA512
+ });
+ return {hash: hash.toString(), salt: salt.toString()}
+}
+
+module.exports = (_neoModules, io) => {
+ neoModules = _neoModules;
+ return {
+ openNamespace: createOpenSocketNamespace(io),
+ authorizedNamespace: createAuthorizedNamespace(io)
+ }
+};
+
diff --git a/src/UserData/index.js b/src/UserData/index.js
new file mode 100644
index 0000000..751c265
--- /dev/null
+++ b/src/UserData/index.js
@@ -0,0 +1,333 @@
+/**
+ * This module is the entry of UserData. This will ensure the user-dirs and all config-files.
+ * Also, it will
+ *
+ * @author jakobst1n.
+ * @since 19.12.2019
+ */
+
+let logger = require(__appdir + "/src/Logger");
+let fse = require("fs-extra");
+let ini = require('ini');
+
+let neoModules;
+
+/**
+ * This method will ensure that all required fields are in config.ini
+ */
+function ensureMainConfig() {
+ var config = ini.decode(fse.readFileSync(__configdir + "/config.ini", 'utf-8'))
+
+ if (config.instanceName == null) { config.instanceName = "neoStrip"; }
+ if (config.activeMode == null) { config.activeMode = "builtin/static"; }
+
+ if (config.HTTP == null) { config.HTTP = {}; }
+ if (config.HTTP.port == null) { config.HTTP.port = 443; }
+
+ if (config.SelfUpdater == null) { config.SelfUpdater = {}; }
+ if (config.SelfUpdater.branch == null) { config.SelfUpdater.branch = "master"; }
+ if (config.SelfUpdater.checkVersionInterval == null) { config.SelfUpdater.checkVersionInterval = 1; }
+ if (config.SelfUpdater.automaticUpdate == null) { config.SelfUpdater.automaticUpdate = false; }
+
+ if (config.SSLCert == null) { config.SSLCert = {}; }
+ if (config.SSLCert.CN == null) { config.SSLCert.CN = "localhost"; }
+ if (config.SSLCert.certMade == null) { config.SSLCert.certMade = false; }
+ if (config.SSLCert.certDate == null) { config.SSLCert.certDate = 0; }
+ if (config.SSLCert.certExpire == null) { config.SSLCert.certExpire = 0; }
+ if (config.SSLCert.certCN == null) { config.SSLCert.certCN = ""; }
+
+ if (config.DiscoveryServer == null) { config.DiscoveryServer = {}; }
+ if (config.DiscoveryServer.address == null) { config.DiscoveryServer.address = "https://erj46s.deta.dev"; }
+ if (config.DiscoveryServer.broadcastSelf == null) { config.DiscoveryServer.broadcastSelf = false; }
+
+ if (config.neoRuntimeIPC == null) { config.neoRuntimeIPC = {}; }
+ if (config.neoRuntimeIPC.socketFile == null) { config.neoRuntimeIPC.socketFile = "/tmp/neo_runtime.sock"; }
+
+ fse.writeFileSync(__configdir + "/config.ini", ini.encode(config))
+}
+
+/**
+ * This method will ensure that all required fields are in config.ini
+ */
+function ensureStripConfig() {
+ var config = ini.decode(fse.readFileSync(__configdir + "/strip.ini", 'utf-8'))
+
+ if (config.DEFAULT == null) { config.DEFAULT = {}; }
+ if (config.DEFAULT.led_pin == null) { config.DEFAULT.led_pin = 18; }
+ if (config.DEFAULT.led_freq_hz == null) { config.DEFAULT.led_freq_hz = 80000; }
+ if (config.DEFAULT.led_dma == null) { config.DEFAULT.led_dma = 10; }
+ if (config.DEFAULT.led_invert == null) { config.DEFAULT.led_invert = false; }
+ if (config.DEFAULT.led_channel == null) { config.DEFAULT.led_channel = 0 }
+ if (config.DEFAULT.segments == null) { config.DEFAULT.segments = ""; }
+ if (config.DEFAULT.matrix == null) { config.DEFAULT.matrix = ""; }
+
+ fse.writeFileSync(__configdir + "/strip.ini", ini.encode(config))
+}
+
+/**
+ * This method will make sure all files and folders needed for the app exists,
+ * it will also make sure all files contain all needed data.
+ */
+function init() {
+ // Generate all user-folders
+ logger.info("Ensuring all folder in UserDir exists...");
+
+ fse.ensureDirSync(__datadir + "/");
+ fse.ensureDirSync(__configdir);
+ fse.ensureDirSync(__configdir + "/certs");
+ fse.ensureDirSync(__datadir + "/userCode/");
+ fse.ensureDirSync(__datadir + "/remoteCode/");
+
+ // Generate config-files
+ if (!fse.existsSync(__configdir + "/config.ini")) {
+ fse.closeSync(fse.openSync(__configdir + "/config.ini", 'w'));
+ }
+ ensureMainConfig();
+
+ if (!fse.existsSync(__configdir + "/strip.ini")) {
+ fse.closeSync(fse.openSync(__configdir + "/strip.ini", 'w'));
+ }
+ ensureStripConfig();
+
+ if (!fse.existsSync(__configdir + "/users.ini")) {
+ fse.writeFileSync(__configdir + "/users.ini", ini.encode({
+ "neo": {
+ "password": "5adbc90fb4716fff62d9cf634837e22f29b011803ba29cee51f921b920fa941651737bd15d00dc72e4cbeee5e64e06ec99cc50ea917285a029797a98740cce0f",
+ "salt": "59b6de1040f3ae3c63de984ca5d61ef46f41dc6ecead3a9d5dab69f0bb3636aa49017e179b74dbcdb407f62bc139a7d55aa78fe2bbdd5327609ea124b2fa03b1"
+ }
+ }))
+ }
+};
+
+/**
+ * Recursive function which adds setters and getters to all properties
+ * in a nested object. This will make us able to save config values
+ * directly without doing anything other that `prop = value`.
+ *
+ * @param {object} config - The full config object.
+ * @param {string} configFile - The path of the configfile.
+ *
+ * @return {object} The config object with setters for values.
+ */
+ function withSetters(config, configFile) {
+ let outConfig = {};
+ function iter(inNode, outNode) {
+ for (const key of Object.keys(inNode)) {
+ if (typeof(inNode[key]) === "object") {
+ outNode[key] = {};
+ iter(inNode[key], outNode[key]);
+ } else {
+ outNode[`_${key}`] = inNode[key];
+ Object.defineProperty(outNode, key, {
+ get: function() { return this[`_${key}`]; },
+ set: function(value) {
+ this[`_${key}`] = value;
+ saveConfig(configFile, outConfig);
+ },
+ enumerable: true
+ });
+ }
+ }
+ }
+ iter(config, outConfig);
+ return outConfig
+}
+
+/**
+ * Returns a object with only the actual values and not setters, this is the
+ * inverse of withSetters.
+ *
+ * @param {object} config - The full config object.
+ *
+ * @return {object} The config object without setters.
+ */
+function withoutSetters(config) {
+ let out = {};
+ function iter(inNode, outNode) {
+ for (const key of Object.keys(inNode).filter(k => (k.substr(0, 1) != "_"))) {
+ if (typeof(inNode[key]) === "object") {
+ outNode[key] = {};
+ iter(inNode[key], outNode[key], out);
+ } else {
+ outNode[key] = inNode[`_${key}`];
+ }
+ }
+ }
+ iter(config, out);
+ return out;
+}
+
+/**
+ * Save config object, this will run stripSetters on the object it saves.
+ *
+ * @param {string} file - filename to save the config object to.
+ * @param {object} object - the config object to save.
+ */
+function saveConfig(file, object, removeSetters=true) {
+ if (removeSetters) {
+ object = withoutSetters(object);
+ }
+ fse.writeFileSync(file, ini.encode(object));
+}
+
+/**
+ * Reads a ini file and add setters to all properties
+ *
+ * @param {string} file - filename of file to read.
+ *
+ * @return {object} The config in the file.
+ */
+function getFullConfig(file, addSetters=true) {
+ let fullConfig = ini.decode(fse.readFileSync(file, "utf-8"));
+ if (addSetters) {
+ fullConfig = withSetters(fullConfig, file);
+ }
+ return fullConfig;
+}
+
+/**
+ * Save a user the user config file, this will append if a new user, and
+ * overwrite if it is a existsing user.
+ *
+ * @param {string} username - The username, case-insensitive.
+ * @param {string} salt - Salt used for password-checking.
+ * @param {string} password - Hashed password.
+ *
+ * @return {object} Standardform return object
+ */
+ function saveUser(username, salt, password) {
+ let config = ini.decode(fse.readFileSync(__configdir + "/users.ini", 'utf-8'))
+ config[username] = {}
+ config[username].salt = salt
+ config[username].password = password
+ fse.writeFileSync(__configdir + "/users.ini", ini.encode(config))
+ return {success: true}
+}
+
+/**
+ * Get a user, this will return null if no user is found.
+ *
+ * @return {object} with username, salt and hash properties.
+ */
+function getUser(username) {
+ let config = ini.decode(fse.readFileSync(__configdir + "/users.ini", 'utf-8'))
+ if (Object.prototype.hasOwnProperty.call(config, username)) {
+ return {...config[username], username: username}
+ }
+ return null;
+}
+
+/**
+ * Get all users
+ *
+ * @return {array} usernames
+ */
+function getUsers() {
+ let config = ini.decode(fse.readFileSync(__configdir + "/users.ini", "utf-8"));
+ let users = [];
+ for (const username of Object.keys(config)) {
+ users.push(username);
+ }
+ return users;
+}
+
+/**
+ * Delete a user
+ *
+ * @return {object} Standardform success object.
+ */
+function deleteUser(username) {
+ let config = ini.decode(fse.readFileSync(__configdir + "/users.ini", 'utf-8'))
+ if (config.length <= 1) { return {success: false, reason: "cannot delete only user"}; }
+ if (!Object.prototype.hasOwnProperty.call(config, username)) { return {success: false, reason: "user not found", detail: username}; }
+ delete config[username];
+ fse.writeFileSync(__configdir + "/users.ini", ini.encode(config));
+ return {success: true}
+}
+
+/**
+ * Create a new mode in the user directory.
+ *
+ * @param {string} name - The name of the file to use, a trailing number will
+ * be added if there are any conflicts.
+ * @param {string} template - Id of the template, builtin/static, template/base etc...
+ *
+ * @return {object} a standard convention result object.
+ */
+function createNewUserMode(name, template) {
+ source_script = null;
+ if ((template === "template/base") || (template === "") || (template == null)) {
+ source_script = __appdir + "/NeoRuntime/special/template_base/";
+ } else {
+ source_script = neoModules.neoRuntimeManager.getModePath(template);
+ }
+ if (!neoModules.neoRuntimeManager.isMode(source_script)) {
+ return {success: false, reason: "Source script not found"};
+ }
+
+ let newModeName = neoModules.neoRuntimeManager.getModePath(`user/${name}`);
+ let counter = 0;
+ while (neoModules.neoRuntimeManager.isMode(newModeName)) {
+ counter += 1;
+ newModeName = neoModules.neoRuntimeManager.getModePath(`user/${name}_${counter}`);
+ }
+
+ fse.ensureDirSync(newModeName);
+ fse.copySync(`${source_script}/script.py`, `${newModeName}/script.py`)
+ neoModules.neoRuntimeManager.event.emit("change", "modelist");
+ return {success: true};
+}
+
+/**
+ * Delete a user created mode
+ *
+ * @param {string} modeid - modeid to delete
+ *
+ * @return {object} a standard convention result object.
+ */
+function deleteUserMode(modeid) {
+ if (modeid.substr(0, 5) !== "user/") {
+ return {success: false, reason: "Not user mode"}
+ }
+ let modePath = neoModules.neoRuntimeManager.getModePath(modeid);
+ if (!neoModules.neoRuntimeManager.isMode(modePath)) {
+ return {success: false, reason: "Mode does not found"}
+ }
+ if (modeid === neoModules.neoRuntimeManager.mode.current()) {
+ return {success: false, reason: "Cannot delete currently active mode"}
+ }
+ fse.removeSync(modePath);
+ neoModules.neoRuntimeManager.event.emit("change", "modelist");
+ return {success: true}
+}
+
+module.exports = (_neoModules) => {
+ neoModules = _neoModules;
+ init();
+ return {
+ users: getUsers,
+ user: {
+ save: saveUser,
+ get: getUser,
+ delete: deleteUser
+ },
+ strip: {
+ get: () => {
+ let c = getFullConfig(`${__configdir}/strip.ini`, addSetters=false);
+ c.DEFAULT.matrix = JSON.parse(c.DEFAULT.matrix);
+ c.DEFAULT.segments = c.DEFAULT.segments.split(" ");
+ return c.DEFAULT;
+ },
+ set: (c) => {
+ c.segments = c.segments.join(" ");
+ c.matrix = JSON.stringify(c.matrix);
+ return saveConfig(`${__configdir}/strip.ini`, {DEFAULT: c}, removeSetters=false);
+ },
+ },
+ config: getFullConfig(`${__configdir}/config.ini`),
+ mode: {
+ create: createNewUserMode,
+ delete: deleteUserMode
+ }
+ }
+};
diff --git a/src/compileAndRun/index.js b/src/compileAndRun/index.js
deleted file mode 100644
index 6f89e44..0000000
--- a/src/compileAndRun/index.js
+++ /dev/null
@@ -1,75 +0,0 @@
-let EventEmitter = require('events');
-let fse = require("fs-extra");
-let Process = require("./process");
-let pythonSupportFiles = __dirname + "/pythonSupportFiles/";
-
-class Python extends EventEmitter {
-
- constructor(profileFolder, usrDataFolder) {
- super();
- this.profileFolder = profileFolder;
- this.usrDataFolder = usrDataFolder;
-
- this.stdout = "";
- this.stderr = "";
- }
-
- status() {
- return {};
- }
-
- compile() {
- try {
- fse.removeSync(this.profileFolder + "/build");
- fse.copySync(pythonSupportFiles, this.profileFolder + "/build/");
- fse.copySync(this.profileFolder + "/script.py", this.profileFolder + "/build/script.py");
- } catch (err) {
- console.log(err);
- }
- }
-
- run() {
- // Setup paths and commands
- let entryPath = this.profileFolder + "/build/entry.py";
-
- // Spawn the new process
- this.pyProc = new Process("python", [
- "-u", // This makes us able to get real-time output
- entryPath, // This is where the entry-point is located
- this.usrDataFolder // This is required for the python-script to now where our config-files are
- ]);
- this.pyProc.start();
-
- this.pyProc.proc.stdout.on("data", (_stdout) => {
- this.emit("stdout::data", "" + _stdout);
- });
- this.pyProc.proc.stdout.on("end", () => {
- this.emit("stdout::end");
- });
- this.pyProc.proc.stderr.on("data", (_stderr) => {
- this.emit("stderr::data", _stderr);
- });
- this.pyProc.proc.stderr.on("end", () => {
- this.emit("stderr:end");
- });
- this.pyProc.proc.on("close", (_code) => {
- this.emit("close", _code);
- });
-
- }
-
- compileAndRun() {
- this.compile();
- this.run();
- }
-
- stop() {
- this.pyProc.proc.kill("SIGINT");
- }
-
-}
-
-module.exports = {
- Python: Python,
- Process: Process
-};
diff --git a/src/compileAndRun/process.js b/src/compileAndRun/process.js
deleted file mode 100644
index 67ff546..0000000
--- a/src/compileAndRun/process.js
+++ /dev/null
@@ -1,41 +0,0 @@
-let spawn = require("child_process");
-
-class Process {
-
- constructor(command, args = []) {
- this.command = command;
- this.args = args;
- this.stdout = "";
- this.stderr = "";
- this.fl = false;
- }
-
- start() {
- this.proc = spawn.spawn(this.command, this.args);
-
- this.proc.on('error', (err) => {
- console.log(err);
- });
-
- this.proc.stdout.on('data', (_stdout) => {
- this.stdout += _stdout;
- });
-
- this.proc.stdout.on('end', () => {
- });
-
- this.proc.stderr.on('data', (_stderr) => {
- this.stderr += _stderr;
- });
-
- this.proc.stderr.on('end', () => {
- });
-
- this.proc.on('close', (code) => {
- });
-
- }
-
-}
-
-module.exports = Process; \ No newline at end of file
diff --git a/src/compileAndRun/pythonSupportFiles/LuxcenaNeo/Matrix.py b/src/compileAndRun/pythonSupportFiles/LuxcenaNeo/Matrix.py
deleted file mode 100644
index 320da02..0000000
--- a/src/compileAndRun/pythonSupportFiles/LuxcenaNeo/Matrix.py
+++ /dev/null
@@ -1,73 +0,0 @@
-def getSegmentRange(segments, n):
- """ Return a list of all the actual led-numbers in a segment """
- # Sum all the segments prior to the one we are looking for
- i = 0
- start = 0
- while True:
- if i >= n: break
- start += segments[i] # Add number of leds in this segment to the start-index
- i += 1
-
- # Add all numbers in the segment we are looking for to a list
- i = start
- breakPoint = i + segments[n]
- range = []
- while True:
- range.append(i)
- i += 1
- if i >= breakPoint: break
- return range
-
-
-class Matrix:
-
- def __init__(self, segments, matrix):
- self.matrix = [] # Holds the matrix
- self.xLen = 0 # The width of the matrix
- self.yLen = len(matrix) # The heigth of the matrix
-
- for yPos in range(len(matrix)):
- yVal = matrix[yPos]
-
- thisY = []
- for xPos in range(len(yVal)):
- # This gets the range of segment n
- segmentRange = getSegmentRange(segments, matrix[yPos][xPos][0])
-
- # This adds the range to the current row's list
- # if in the config [<segment_num>, <reversed>]
- # reversed == true, revese the list before adding it
- thisY += reversed(segmentRange) if matrix[yPos][xPos][1] else segmentRange
-
- # This just finds the longest row in the matrix
- if (len(thisY) > self.xLen):
- self.xLen = len(thisY)
-
- self.matrix.append(thisY)
-
- def get(self, x, y):
- """ Return the value of a place in the matrix given x and y coordinates """
- return self.matrix[y][x]
-
- def dump(self):
- nSpacers = (self.xLen*6) // 2 - 6
- print( ("=" * nSpacers) + "Matrix dump" + ("=" * nSpacers) )
-
- for y in self.matrix:
- thisYLine = ""
- for x in y:
- thisYLine += ( ' ' * (5 - len(str(x))) ) + str(x) + ' '
- print(thisYLine)
-
- print("=" * (self.xLen*6))
-
-
-if __name__ == "__main__":
- testMatrix = Matrix(
- [2, 2, 2, 2, 2, 2, 2, 2, 2],
- [
- [[0, False], [1, True], [2, False]],
- [[3, True], [4, False], [5, True]],
- [[6, False], [7, True], [8, False]]
- ]
- )
diff --git a/src/compileAndRun/pythonSupportFiles/LuxcenaNeo/NeoBehaviour.py b/src/compileAndRun/pythonSupportFiles/LuxcenaNeo/NeoBehaviour.py
deleted file mode 100644
index b0238e7..0000000
--- a/src/compileAndRun/pythonSupportFiles/LuxcenaNeo/NeoBehaviour.py
+++ /dev/null
@@ -1,29 +0,0 @@
-# This is the base-class "main" should inherit from!
-# All methods are blocking :) This means that you could potentially loose a "tick"
-# For example, if "eachSecond" is taking up the thread, and the clock goes from 11:58 to 12:02, "eachHour", will not be called.
-class NeoBehaviour:
-
- # THIS METHOD SHOULD NOT BE OVERIDDEN! Use onStart if you want a setup-method!!!
- # Contains basic setup
- def __init__(self):
- return
-
- # This method will be run right after __init__
- def onStart(self):
- return
-
- # This method is called every second (on the clock), given that the thread is open
- def eachSecond(self):
- return
-
- # This method is called every mintue (on the clock), given that the thread is open
- def eachMinute(self):
- return
-
- # This method is called every whole hour (on the clock), given that the thread is open
- def eachHour(self):
- return
-
- # This method is called every day at noon, given that the thread is open
- def eachDay(self):
- return
diff --git a/src/compileAndRun/pythonSupportFiles/LuxcenaNeo/Strip.py b/src/compileAndRun/pythonSupportFiles/LuxcenaNeo/Strip.py
deleted file mode 100644
index c3a913f..0000000
--- a/src/compileAndRun/pythonSupportFiles/LuxcenaNeo/Strip.py
+++ /dev/null
@@ -1,121 +0,0 @@
-from neopixel import *
-from Matrix import Matrix, getSegmentRange
-
-class Strip:
-
- def __init__(self, stripConf):
- self.SEGMENTS = stripConf["segments"]
- self.SEGMENT_CONFIG = stripConf["segment_config"]
-
- self.LED_FREQ_HZ = stripConf["led_freq_hz"] # LED signal frequency in hertz (usually 800khz)
- self.LED_CHANNEL = stripConf["led_channel"] # Set to '1' for GPIOs 13, 19, 41, 45, 53
- self.LED_INVERT = stripConf["led_invert"] # True to invert the signal, (when using NPN transistor level shift)
- self.LED_PIN = stripConf["led_pin"] # 18 uses PWM, 10 uses SPI /dev/spidev0.0
- self.LED_DMA = stripConf["led_dma"] # DMA channel for generating the signal, on the newer ones, try 10
- self.LED_COUNT = sum(self.SEGMENTS) # Number of LEDs in strip
-
-
- self.LED_BRIGHTNESS = 255
-
- self.strip = Adafruit_NeoPixel(
- self.LED_COUNT,
- self.LED_PIN,
- self.LED_FREQ_HZ,
- self.LED_DMA,
- self.LED_INVERT,
- self.LED_BRIGHTNESS,
- self.LED_CHANNEL
- )
-
- self.strip.begin()
-
- # Blank out all the LEDs
- i = 0
- while True:
- self.strip.setPixelColor(i, 0)
- i += 1
- if (i > self.LED_COUNT): break
- self.strip.show()
-
- # Setup matrix
- print(" * Generating matrix")
- #try:
- self.pixelMatrix = Matrix(self.SEGMENTS, stripConf["matrix"])
- self.pixelMatrix.dump()
- #except:
- # print("Something went wrong while setting up your self-defined matrix.")
-
- def show(self):
- """Update the display with the data from the LED buffer."""
- self.strip.show()
-
- def setPixelColor(self, n, color):
- """Set LED at position n to the provided 24-bit color value (in RGB order).
- """
- self.strip.setPixelColor(n, color)
-
- def setPixelColorXY(self, x, y, color):
- """Set LED at position n to the provided 24-bit color value (in RGB order).
- """
- self.strip.setPixelColor(self.pixelMatrix.get(x, y), color)
-
- def setPixelColorRGB(self, n, red, green, blue, white = 0):
- """Set LED at position n to the provided red, green, and blue color.
- Each color component should be a value from 0 to 255 (where 0 is the
- lowest intensity and 255 is the highest intensity).
- """
- self.strip.setPixelColor(n, Color(red, green, blue, white))
-
- def setPixelColorXYRGB(self, x, y, red, green, blue, white = 0):
- """Set LED at position n to the provided red, green, and blue color.
- Each color component should be a value from 0 to 255 (where 0 is the
- lowest intensity and 255 is the highest intensity).
- """
- self.strip.setPixelColor(self.pixelMatrix.get(x, y), Color(red, green, blue, white))
-
- def setSegmentColorRGB(self, segment, red, green, blue, white = 0):
- """Set a whole segment to the provided red, green and blue color.
- Each color component should be a value from 0 to 255 (where 0 is the
- lowest intensity and 255 is the highest intensity)."""
- for n in getSegmentRange(self.SEGMENTS, segment):
- self.strip.setPixelColor(n, Color(red, green, blue, white))
-
- def setBrightness(self, brightness):
- """Scale each LED in the buffer by the provided brightness. A brightness
- of 0 is the darkest and 255 is the brightest.
- """
- self.strip.setBrightness(brightness)
-
- def getBrightness(self):
- """Get the brightness value for each LED in the buffer. A brightness
- of 0 is the darkest and 255 is the brightest.
- """
- return self.strip.getBrightness()
-
- def getPixels(self):
- """Return an object which allows access to the LED display data as if
- it were a sequence of 24-bit RGB values.
- """
- return self.strip.getPixels()
-
- def numPixels(self):
- """Return the number of pixels in the display."""
- return self.LED_COUNT
-
- def getPixelColor(self, n):
- """Get the 24-bit RGB color value for the LED at position n."""
- return self.strip.getPixelColor(n)
-
-
-def Color(red, green, blue, white = 0):
- """Convert the provided red, green, blue color to a 24-bit color value.
- Each color component should be a value 0-255 where 0 is the lowest intensity
- and 255 is the highest intensity.
- """
- return (white << 24) | (red << 16)| (green << 8) | blue
-
-def hexColor(value):
- value = value.lstrip('#')
- lv = len(value)
- rgb = tuple(int(value[i:i+lv/3], 16) for i in range(0, lv, lv/3))
- return (0 << 24) | (rgb[1] << 16) | (rgb[0] << 8) | rgb[2]
diff --git a/src/compileAndRun/pythonSupportFiles/LuxcenaNeo/__init__.py b/src/compileAndRun/pythonSupportFiles/LuxcenaNeo/__init__.py
deleted file mode 100644
index 40b8e2d..0000000
--- a/src/compileAndRun/pythonSupportFiles/LuxcenaNeo/__init__.py
+++ /dev/null
@@ -1,10 +0,0 @@
-from NeoBehaviour import *
-from Strip import *
-
-strip = None # The strip object, should be set in entry.py, and is then accessible in the script-file
-forceStop = False # When set to true, execution of the script will halt
-
-# A function that could be used to halt the script
-def stop():
- global forceStop
- forceStop = True
diff --git a/src/compileAndRun/pythonSupportFiles/entry.py b/src/compileAndRun/pythonSupportFiles/entry.py
deleted file mode 100644
index 45de822..0000000
--- a/src/compileAndRun/pythonSupportFiles/entry.py
+++ /dev/null
@@ -1,76 +0,0 @@
-# This is the entry-point for all Luxcena-Neo python-scripts
-# The script should be in the same folder as this, and be named "script.py"
-# In the future you could possibly have more files and stuff alongside the "script.py"-file as well
-import sys
-import json
-import importlib
-import datetime
-
-def runSync(moduleSc, sc):
- timeNow = datetime.datetime.now()
- lastDay = timeNow.day
- lastHour = timeNow.hour
- lastMinute = timeNow.minute
- lastSecond = timeNow.second
-
- while True:
- timeNow = datetime.datetime.now()
-
- if ("LuxcenaNeo" in dir(moduleSc)):
- if moduleSc.LuxcenaNeo.forceStop: break
- elif ("neo" in dir(moduleSc)):
- if moduleSc.neo.forceStop == True: break
-
- if (timeNow.second != lastSecond):
- lastSecond = timeNow.second
- sc.eachSecond()
-
- if (timeNow.minute != lastMinute):
- lastMinute = timeNow.minute
- sc.eachMinute()
-
- if (timeNow.hour != lastHour):
- lastHour = timeNow.hour
- sc.eachHour()
-
- if (timeNow.day != lastDay):
- lastDay = timeNow.lastDay
- sc.eachDay()
-
-def runAsync(moduleSc, sc):
- return
-
-def main():
- print ("Starting script named \"{0}\"".format("test"))
-
- root_dir = sys.argv[1]
- config_dir = root_dir + "/config/"
-
- print ("> Loading pixel-configuration...")
- with open(config_dir + "strip.json", "r") as rawStripConf:
- stripConf = json.load(rawStripConf)
-
- print ("> Initializing script...")
- moduleSc = importlib.import_module("script")
-
- if ("LuxcenaNeo" in dir(moduleSc)):
- moduleSc.LuxcenaNeo.strip = moduleSc.LuxcenaNeo.Strip(stripConf)
- elif ("neo" in dir(moduleSc)):
- moduleSc.neo.strip = moduleSc.neo.Strip(stripConf)
- else:
- raise Exception("Neither LuxcenaNeo nor neo found in script, check docs!")
-
- sc = moduleSc.Main()
-
- print ("> Running the script...")
- sc.onStart()
-
- if (("async" in dir(moduleSc)) and (moduleSc.async == True)):
- runAsync(moduleSc, sc)
- else:
- runSync(moduleSc, sc)
-
- print ("> Script exited...")
-
-if __name__ == "__main__":
- main()
diff --git a/src/domain/middleware.js b/src/domain/middleware.js
deleted file mode 100644
index ebd00f8..0000000
--- a/src/domain/middleware.js
+++ /dev/null
@@ -1,40 +0,0 @@
-module.exports = function(options) {
- return function(req, res, next) {
- let dirPublic = options.srcDir + "/public/";
-
- if (global.runtimeData.get("updaterRunning") === true) {
- res.sendFile(dirPublic + "/update/index.html");
- return;
- }
-
- switch (req.path) {
-
- case "/":
- res.sendFile(dirPublic + "index.html");
- return;
-
- case "/scripts":
- res.sendFile(dirPublic + "scripts.html");
- return;
-
- case "/strip_setup":
- res.sendFile(dirPublic + "strip_setup.html");
- return;
-
- case "/neo_ide":
- res.sendFile(dirPublic + "neo_ide.html");
- return;
-
- case "/logviewer":
- res.sendFile(dirPublic + "logviewer.html");
- return;
-
- case "/settings":
- res.sendFile(dirPublic + "settings.html");
- return;
-
- }
-
- next()
- }
-}
diff --git a/src/neoRuntime/index.js b/src/neoRuntime/index.js
deleted file mode 100644
index d34f4b4..0000000
--- a/src/neoRuntime/index.js
+++ /dev/null
@@ -1,232 +0,0 @@
-let fs = require("fs-extra");
-let path = require("path");
-let compileRun = require("../compileAndRun");
-
-let listDirsInDir = p => fs.readdirSync(p).filter(f => fs.statSync(path.join(p, f)).isDirectory());
-
-class neoRuntime {
-
- constructor(dirUsrData) {
- this._proc = undefined;
- this._cScript = "None";
- this._cScriptHasExited = false;
- this._dirUsrData = dirUsrData;
- }
-
- status() {
- return {
- "currentScript": this._cScript,
- "scriptIsExited": this._cScriptHasExited,
- "uptime": process.uptime()
- };
- }
-
- listScripts() {
- let localScripts = listDirsInDir(this._dirUsrData + "/usrCode/");
- let remoteScripts = listDirsInDir(this._dirUsrData + "/remoteCode/");
- let scriptList = [];
-
- for (let i = 0; i < localScripts.length; i++) {
- if (fs.existsSync(this._dirUsrData + "/usrCode/" + localScripts[i] + "/script.py")) {
- scriptList.push({
- "name": localScripts[i],
- "loc": "local",
- "path": "local/" + localScripts[i],
- "lang": "python"
- });
- }
- }
-
- for (let i = 0; i < remoteScripts.length; i++) {
- if (fs.existsSync(this._dirUsrData + "/remoteCode/" + remoteScripts[i] + "/script.py")) {
- scriptList.push({
- "name": remoteScripts[i],
- "loc": "remote",
- "path": "remote/" + remoteScripts[i],
- "lang": "python"
- });
- }
- }
-
- return scriptList;
- }
-
- selectScript(path) {
- global.log(`Selecting script \"${path}\"`, "event");
-
- try {
- this._proc.stop();
- } catch (err) {
- global.log("Could not kill process: " + err, "error");
- }
-
- this._cScript = path;
- path = path.split("/");
- let location = path.splice(0, 1).toString();
- if (location === "local") { path = this._dirUsrData + "/usrCode/" + path.join("/"); }
- if (location === "remote") { path = this._dirUsrData + "/remoteCode/" + path.join("/"); }
- //if (location === "base") { path = this._dirUsrData + "/src/compileAndRun/scripts/" + path.join("/"); } // TODO make this do something
-
- if (!fs.existsSync(path + "/script.py")) {
- global.log(`No script file found when selecting script with real path \"${path}\"`, "ERROR");
- return;
- }
-
- fs.removeSync(path + "/build/logs/log");
-
- this._cScriptHasExited = false;
- this._proc = new compileRun.Python(path, this._dirUsrData);
- this._proc.compileAndRun();
-
- fs.ensureFileSync(path + "/build/logs/log");
- this._proc.on("stdout::data", (_stdout) => {
- fs.appendFile(path + "/build/logs/log", "\n====stdout====\n" + _stdout.toString().replace(/\n$/, ""));
- });
- this._proc.on("stderr::data", (_stderr) => {
- fs.appendFile(path + "/build/logs/log", "\n====stderr====\n" + _stderr.toString().replace(/\n$/, ""));
- });
- this._proc.on("stderr::end", () => {
- fs.appendFile(path + "/build/logs/log", "\n");
- });
- this._proc.on("stdout::end", () => {
- fs.appendFile(path + "/build/logs/log", "\n");
- });
- this._proc.on("close", (_code) => {
- fs.appendFile(path + "/build/logs/log","\n====close====\nScript exited with code " + _code.toString());
- this._cScriptHasExited = true;
- });
-
- }
-
- stopScript() {
- try {
- this._proc.stop();
- return {
- success: true
- }
- } catch (err) {
- return {
- success: false,
- error: {reason: err}
- };
- }
- }
-
- deleteScript(path) {
- global.log(`Deleting script \"${path}\"`, "DEBUG");
-
- let sPath = path.split("/");
- let location = sPath.splice(0, 1).toString();
- if (location === "remote") {
- global.log(`Cannot delete remote script ${path}`, "DEBUG");
- return;
- }
- let absPath = this._dirUsrData + "/usrCode/" + sPath.join("/");
-
- if (this._cScriptPath == path) {
- try {
- this._proc.stop();
- } catch (err) {
- global.log("Could not kill process: " + err, "error");
- }
- }
-
- fs.removeSync(absPath);
-
- }
-
- createEmptyScript(name) {
- global.log(`Creating script with name \"${name}/"`, "DEBUG");
-
- let scriptFolderPath = this._dirUsrData + "/usrCode/" + name;
- if (fs.existsSync(scriptFolderPath)) {
- global.log(`A Script with the name \"${name}\" already exists`, "ERROR");
- return;
- }
-
- fs.ensureDirSync(scriptFolderPath);
- fs.writeFile(scriptFolderPath + "/script.py",
- "import LuxcenaNeo as neo # Can be imported as LuxcenaNeo as well. but anything else and it will fail...\n\nclass Main(neo.NeoBehaviour):\n\n def onStart(self):\n print (\"Script started\")\n\n def eachSecond(self):\n print (\"A second has passed\")",
- (err) => {
- if (err) {
- global.log("Could not create script.py for profile \"" + name + "\"", "ERROR");
- } else {
- global.log("Script \"" + name + "\" created.", "SUCCESS");
- }
- }
- );
-
- }
-
- getScript(scriptPath) {
- let sPath = scriptPath.split("/");
- let location = sPath.splice(0, 1).toString();
- if (location === "remote") {
- global.log(`Cannot edit remote script ${path}`, "DEBUG");
- return;
- }
- let absPath = this._dirUsrData + "/usrCode/" + sPath.join("/");
-
- if (!fs.existsSync(absPath + "/script.py")) {
- return false;
- }
-
- return {
- name : sPath[sPath.length - 1],
- files : {
- main : fs.readFileSync(absPath + "/script.py", "utf8")
- }
- };
- }
-
- saveScript(script, callback) {
- if (!fs.existsSync(this._dirUsrData + "/usrCode/" + script.name )) {
- callback({success: false, error: {reason: "Script file not found"}});
- }
- fs.writeFile(this._dirUsrData + "/usrCode/" + script.name + "/script.py", script.files.main, (err) => {
- if (err) {
- callback({success: false, error: {reason: err}});
- } else {
- callback({success: true});
- }
- });
- }
-
- getScriptOutput(scriptPath) {
- if (scriptPath != this._cScript) {
- return {
- success: false,
- error: {reason: "This is not the current running script"}
- }
- }
-
- let path = scriptPath.split("/");
- let location = path.splice(0, 1).toString();
- if (location === "local") { path = this._dirUsrData + "/usrCode/" + path.join("/"); }
- if (location === "remote") { path = this._dirUsrData + "/remoteCode/" + path.join("/"); }
-
- if (!fs.existsSync(path + "/build/logs/log")){
- return {
- success: false,
- error: {reason: "No log file found"}
- }
- }
-
- try {
- let output = fs.readFileSync(path + "/build/logs/log", "utf8");
- return {
- success: true,
- output: output
- }
- } catch (err) {
- return {
- success: false,
- error: {reason: err}
- };
- }
- }
-}
-
-module.exports = (dirUsrData) => {
- return new neoRuntime(dirUsrData);
-};
diff --git a/src/public/app.js b/src/public/app.js
deleted file mode 100644
index 58af98a..0000000
--- a/src/public/app.js
+++ /dev/null
@@ -1,19 +0,0 @@
-// General JavaScript
-require ("./js/general")();
-
-// Page-specific JavaScript
-const pageName = document.getElementsByTagName("body")[0].id;
-try {
- require("./js/" + pageName)();
-} catch (error) {
- console.log(
- "Something went wrong when loading the js for this page...\n" +
- "The pageName is \"" + pageName + "\".\n" +
- "If it was excpected to get js for this page, please check the filename, and recompile webpack."
- );
-}
-
-// Require all styles
-require("./app.scss");
-// Require font awesome
-require("fontawesome");
diff --git a/src/public/app.scss b/src/public/app.scss
deleted file mode 100644
index 845d36a..0000000
--- a/src/public/app.scss
+++ /dev/null
@@ -1,9 +0,0 @@
-// This file is probably not needed anymore, as all the files are loaded directly by our sass-loader
-// General scss
-@import "./scss/general.scss";
-
-// Page-specific css
-@import "./scss/scripts.scss";
-@import "./scss/neo_ide.scss";
-@import "./scss/setup.scss";
-@import "./scss/update.scss";
diff --git a/src/public/components/sidenav.js b/src/public/components/sidenav.js
deleted file mode 100644
index a69aac5..0000000
--- a/src/public/components/sidenav.js
+++ /dev/null
@@ -1,24 +0,0 @@
-module.exports = `
-<ul id="slide-out" class="sidenav sidenav-fixed">
- <li>
- <div class="user-view">
- <div class="background">
- <img src="./logo/150h/Icon.png">
- </div>
- <!--<a href="#!user"><img class="circle" src="https://picsum.photos/200/300/?random"></a>
- <a href="#!name"><span class="white-text name">John Doe</span></a>
- <a href="#!email"><span class="white-text email">Guest</span></a>-->
- </div>
- </li>
- <li><div class="divider"></div></li>
- <li><a class="waves-effect" href="/"><i class="material-icons">dashboard</i> Dashboard</a></li>
- <li><a class="waves-effect" href="/scripts"><i class="material-icons">cloud</i> Scripts</a></li>
- <li><a class="waves-effect" href="/logviewer"><i class="material-icons">timeline</i> LogViewer</a></li>
- <li><div class="divider"></div></li>
- <li><a class="subheader">Settings</a></li>
- <li><a class="waves-effect" href="strip_setup"><i class="material-icons">straighten</i> Strip setup</a></li>
- <li><a class="waves-effect" href="/settings"><i class="material-icons">settings</i> Settings</a></li>
- <li><div class="divider"></div></li>
- <li><a class="waves-effect" href="/docs"><i class="material-icons">book</i> Docs</a></li>
-</ul>
-`;
diff --git a/src/public/js/general.js b/src/public/js/general.js
deleted file mode 100644
index b0b04d2..0000000
--- a/src/public/js/general.js
+++ /dev/null
@@ -1,10 +0,0 @@
-let sidenav = require("../components/sidenav");
-
-module.exports = () => {
- const pageName = document.getElementsByTagName("body")[0].id;
- if (pageName == "neo_ide") { return; }
-
- document.getElementById("sidenav").innerHTML = sidenav;
-
- M.AutoInit();
-};
diff --git a/src/public/js/index.js b/src/public/js/index.js
deleted file mode 100644
index 5b67de2..0000000
--- a/src/public/js/index.js
+++ /dev/null
@@ -1,68 +0,0 @@
-let socket = io();
-
-module.exports = () => {
- M.AutoInit();
- setupSocket();
-
- setInterval(() => {
- socket.emit("GetGeneralInfo");
- }, 500);
-
- socket.emit("GetLog", {filter: "success error event", entryN: 10});
-};
-
-function setupSocket() {
-
- socket.on("lastLogEntries", (entries) => {
- let list = "";
- entries.forEach((entry) => {
- list += "<tr><td>" + prettifyType(entry.type) + "</td><td>" + entry.time + "</td><td>" + entry.details + "</td></tr>";
- });
-
- document.getElementById("log-table-body").innerHTML = list;
- M.AutoInit();
- });
-
- socket.on("newLogEntry", (entry) => {
- // Start with parsing the new entry, no reason to select the DOM-element and stuff, if we are filtering out the entry anyway.
- let type = entry.type;
- if ( (type.toUpperCase() !== "SUCCESS") && (type.toUpperCase() !== "ERROR") && (type.toUpperCase() !== "EVENT") && (type.toUpperCase() !== "INFO")) {
- return;
- }
-
- let logTable = document.getElementById("log-table-body");
-
- let LTable = logTable.rows.length;
- logTable.deleteRow(LTable - 1); // Since length outputs a 1-based number
-
- let newEntry = logTable.insertRow(0);
- newEntry.insertCell(0).innerHTML = prettifyType(entry.type);
- newEntry.insertCell(1).innerHTML = entry.time;
- newEntry.insertCell(2).innerHTML = entry.details;
- M.AutoInit();
- newEntry.className = "newLogEntry";
- });
-
- socket.on("generalInfo", (info) => {
- if (info["scriptIsExited"]) {
- document.getElementById("currentScript").innerHTML = info["currentScript"] + " (exited)";
- } else {
- document.getElementById("currentScript").innerHTML = info["currentScript"];
- }
- document.getElementById("uptime").innerHTML = info["uptime"] + " seconds";
- });
-
-}
-
-function prettifyType(type) {
- let prettyTable = {
- "DEBUG": `<span class="tooltipped" data-position="top" data-tooltip="Debug-log">😸</span>`,
- "INFO": `<span class="tooltipped" data-position="top" data-tooltip="Just some information">â„šī¸</span>`,
- "WARNING": `<span class="tooltipped" data-position="top" data-tooltip="A warning">âš ī¸</span>`,
- "EVENT": `<span class="tooltipped" data-position="top" data-tooltip="Event">âšĄī¸</span>`,
- "SUCCESS": `<span class="tooltipped" data-position="top" data-tooltip="Something exited successfully">✅</span>`,
- "ERROR": `<span class="tooltipped" data-position="top" data-tooltip="Error">🔴</span>`,
- "PYTHON": `<span class="tooltipped" data-position="top" data-tooltip="Output from user-script">🐍</span>`
- };
- return prettyTable[type];
-}
diff --git a/src/public/js/logviewer.js b/src/public/js/logviewer.js
deleted file mode 100644
index d376196..0000000
--- a/src/public/js/logviewer.js
+++ /dev/null
@@ -1,64 +0,0 @@
-let socket = io();
-
-module.exports = () => {
- M.AutoInit();
-
- socket.emit("GetLog", {filter: "success error event debug python info warning", entryN: 1000});
- socket.on("lastLogEntries", (entries) => {
- M.toast({html: "Loading log-files..."});
- console.log("Log-entries received: " + entries.length);
- let HTMLBasicTable = "";
- let HTMLAdvancedTable = "";
- let HTMLScriptTable = "";
- let HTMLRAWTable = "";
-
- entries.forEach((entry) => {
- let strHTML = "<tr><td>" + prettifyType(entry.type) + "</td><td>" + entry.time + "</td><td>" + entry.details + "</td></tr>";
-
- if (entry.type === "SUCCESS") { HTMLBasicTable += strHTML; }
- if (entry.type === "ERROR") { HTMLBasicTable += strHTML; }
- if (entry.type === "EVENT") { HTMLBasicTable += strHTML; }
- if (entry.type === "PYTHON") { HTMLScriptTable += strHTML; }
- if (entry.type !== "PYTHON") { HTMLAdvancedTable += strHTML; }
-
- //HTMLRAWTable += entry.join(" ");
- });
-
- document.getElementById("log-table-basic").innerHTML = HTMLBasicTable;
- document.getElementById("log-table-script").innerHTML = HTMLScriptTable;
- document.getElementById("log-table-advanced").innerHTML = HTMLAdvancedTable;
- //document.getElementById("log-table-raw").innerHTML = HTMLRAWTable;
-
- });
-
- socket.on("newLogEntry", (entry) => {
- if (entry.type === "SUCCESS") { appendEntryToTable("log-table-basic", entry); }
- if (entry.type === "ERROR") { appendEntryToTable("log-table-basic", entry); }
- if (entry.type === "EVENT") { appendEntryToTable("log-table-basic", entry); }
- if (entry.type === "PYTHON") { appendEntryToTable("log-table-script", entry); }
- if (entry.type !== "PYTHON") { appendEntryToTable("log-table-advanced", entry); }
- });
-
-};
-
-function appendEntryToTable(tableName, entry) {
- let newEntry = document.getElementById(tableName).insertRow(0);
- newEntry.insertCell(0).innerHTML = prettifyType(entry.type);
- newEntry.insertCell(1).innerHTML = entry.time;
- newEntry.insertCell(2).innerHTML = entry.details;
- M.AutoInit();
- newEntry.className = "newLogEntry";
-}
-
-function prettifyType(type) {
- let prettyTable = {
- "DEBUG": `<span class="tooltipped" data-position="top" data-tooltip="Debug-log">😸</span>`,
- "INFO": `<span class="tooltipped" data-position="top" data-tooltip="Just some information">â„šī¸</span>`,
- "WARNING": `<span class="tooltipped" data-position="top" data-tooltip="A warning">âš ī¸</span>`,
- "EVENT": `<span class="tooltipped" data-position="top" data-tooltip="Event">âšĄī¸</span>`,
- "SUCCESS": `<span class="tooltipped" data-position="top" data-tooltip="Something exited successfully">✅</span>`,
- "ERROR": `<span class="tooltipped" data-position="top" data-tooltip="Error">🔴</span>`,
- "PYTHON": `<span class="tooltipped" data-position="top" data-tooltip="Output from user-script">🐍</span>`
- };
- return prettyTable[type];
-} \ No newline at end of file
diff --git a/src/public/js/neo_ide.js b/src/public/js/neo_ide.js
deleted file mode 100644
index 6108be4..0000000
--- a/src/public/js/neo_ide.js
+++ /dev/null
@@ -1,4 +0,0 @@
-module.exports = () => {
-
-
-}; \ No newline at end of file
diff --git a/src/public/js/scripts.js b/src/public/js/scripts.js
deleted file mode 100644
index ccad3cf..0000000
--- a/src/public/js/scripts.js
+++ /dev/null
@@ -1,71 +0,0 @@
-module.exports = () => {
- let socket = io();
- socket.emit("GetScripts", {});
-
- socket.on("updatedScriptList", (scriptList) => {
- let localScriptsHTML = "";
- let remoteScriptsHTML = "";
-
- for (let i = 0; i < scriptList.length; i++) {
- if (scriptList[i].loc !== "local") { continue; }
- let HTMLElem = "<li><div class=\"col s12 m4\"><div class=\"card blue darken-1\"><div class=\"card-content white-text\"><p class=\"card-title\">{{script_name}}</p><p>{{badges}}</p></div><div class=\"card-action white\">{{buttons}}</div></div></div></li>";
- if (scriptList[i].loc === "local") {
- HTMLElem = HTMLElem.replace("{{badges}}", "");
- HTMLElem = HTMLElem.replace("{{script_name}}", scriptList[i].name);
- HTMLElem = HTMLElem.replace("{{buttons}}",
- "<a class=\"selectScript\" data-path=" + scriptList[i].path + "><i class=\"material-icons\">play_arrow</i></a>" +
- "<a class=\"editScript\" data-path=" + scriptList[i].path + "><i class=\"material-icons\">edit</i></a>" +
- "<a class=\"deleteScript\" data-path=" + scriptList[i].path + "><i class=\"material-icons\">delete_forever</i></a>"
- );
- localScriptsHTML += HTMLElem;
- } else if (scriptList[i].loc === "remote") {
- HTMLElem = HTMLElem.replace("{{badges}}", "<span class=\"badge yellow darken-1 white-text\">GitHub</span>");
- HTMLElem = HTMLElem.replace("{{script_name}}", scriptList[i].name);
- remoteScriptsHTML += HTMLElem;
- }
- }
-
- document.getElementById("local-scripts").innerHTML = localScriptsHTML;
- document.getElementById("remote-scripts").innerHTML = remoteScriptsHTML;
-
- });
-
- /*
- The delays here with settimeout, is set to a second deliberately, because, rather than making a whole checking-thing.
- We just wait a second, and assume, that if it worked, the change should show now. Else, check the logViewer.
- */
- function clickHandler(event) {
- let element = event.target.parentElement;
-
- if (element.className === "selectScript") {
- M.toast({html: "Now selecting script: " + element.dataset.path});
- socket.emit("SelectScript", {"scriptPath": element.dataset.path});
-
- } else if (element.className === "editScript") {
- window.location.href = (
- "http://" + window.location.hostname + ":" + window.location.port +
- "/neo_ide?scriptName=" + btoa(element.dataset.path)
- );
-
- } else if (element.className === "deleteScript") {
- if (confirm("Do you really want to delete this script?\n" + element.dataset.path + "\n\nYou can not undo this action, and the script will be lost forever...")) {
- M.toast({html: "Trying to create script. If no change after a second. Check the logViewer."});
- socket.emit("DeleteScript", {"scriptPath": element.dataset.path});
- setTimeout(() => {socket.emit("GetScripts", {})}, 1000);
- }
-
- } else if (element.id === "createEmptyScript") {
- var scriptName = prompt("Please enter the name of the new script:");
- if (scriptName != null || scriptName != "") {
- M.toast({html: "Trying to create script. If no change after a second. Check the logViewer."});
- socket.emit("CreateEmptyScript", {"scriptName": scriptName});
- setTimeout(() => {socket.emit("GetScripts", {})}, 1000);
- }
-
- }
-
- }
-
- addEventListener("click", clickHandler, false);
-
-};
diff --git a/src/public/scss/general.scss b/src/public/scss/general.scss
deleted file mode 100644
index cbbc654..0000000
--- a/src/public/scss/general.scss
+++ /dev/null
@@ -1,44 +0,0 @@
-.general {
-
- header, main, footer {
- padding-left: 300px;
- }
-
- header .brand-logo {
- padding-left: 15px;
- }
-
- @media only screen and (max-width : 992px) {
- header, main, footer {
- padding-left: 0;
- }
- }
-
- .user-view {
- margin-top: 5px;
- height: 150px;
- }
-
- .user-view .background {
- margin-left: 85px;
- }
-
- .log-table {
-
-
-
-
- }
-
- @keyframes highlightNew {
- 0% {background-color:#ffc107;}
- 100% {background-color:white;}
- }
-
- .newLogEntry {
- -webkit-animation-name: highlightNew; /* Safari 4.0 - 8.0 */
- -webkit-animation-duration: 4s; /* Safari 4.0 - 8.0 */
- animation-name: highlightNew;
- animation-duration: 4s;
- }
-}
diff --git a/src/public/scss/neo_ide.scss b/src/public/scss/neo_ide.scss
deleted file mode 100644
index fb10885..0000000
--- a/src/public/scss/neo_ide.scss
+++ /dev/null
@@ -1,254 +0,0 @@
-.neo_ide {
-
- html,
- body {
- height: 100%;
- font-family: 'Trebuchet MS', 'Lucida Sans Unicode', 'Lucida Grande', 'Lucida Sans', Arial, sans-serif;
- padding: 0;
- margin: 0;
- /*margin-top: -20px;*/
- overflow: auto;
- }
-
- #editor {
- width: 100%;
- height: 100%;
- margin: -10px;
- }
-
- .page-container {
- display: flex;
- flex-direction: column;
- justify-content: center;
- }
-
- .nav-container {
- width: 100%;
- height: 45px;
- background-color: #333333;
-
- ul {
- display: flex;
- flex-direction: row;
- justify-content: space-between;
- flex: 1;
- align-self: center;
- width: 100%;
- list-style: none;
- }
-
- ul .left {
- color: white;
- padding: 15px;
- margin-left: -40px;
- margin-top: -19px;
- font-size: 20px;
- }
-
- ul .center {
- color: white;
- }
-
- ul .right {
- width: 80px;
- margin-right: 174px;
- color: white;
- }
-
- ul .right .button {
- padding: 15px;
- margin-top: -15px;
- }
-
- .fileName {
- color: grey;
- }
-
- ul .button {
-
- }
-
- ul .button:hover {
- background-color: black;
- }
- }
-
- @keyframes menuBarIn {
- from {height: 0;}
- to {height: 25px;}
- }
-
- @keyframes menuBarOut {
- from {height: 25px;}
- to {height: 0;}
- }
-
- .menuBarIn {
- animation-name: menuBarIn;
- animation-duration: 0.5s;
- height: 25px !important;
- }
-
- .menuBarOut {
- animation-name: menuBarOut;
- animation-duration: 0.5s;
- height: 0px !important;
- }
-
- .menubar-container {
- background-color: #4e4e4e;
- height: 0;
-
- ul {
- display: flex;
- flex-direction: row;
- flex: 1;
- width: 100%;
- list-style: none;
- margin-top: 2px;
- margin-left: -30px;
- }
-
- .button {
- background-color: #3e3e3e;
- color: white;
- padding: 3px 10px 3px 10px;
- margin-right: 4px;
- font-size: 13px;
- border-radius: 7px;
- }
-
- .button:hover {
- background-color: black;
- }
-
- }
-
- .panel-container {
- display: flex;
- flex-direction: column;
- flex: 1;
- overflow: hidden;
-
- .panel-top {
- flex: 0 0 auto;
- padding: 10px;
- height: 80%;
- width: 100%;
- white-space: nowrap;
- background: #838383;
- color: white;
- }
-
- .splitter {
- flex: 0 0 auto;
- margin-top: -20px;
- height: 25px;
- background: url(https://raw.githubusercontent.com/RickStrahl/jquery-resizable/master/assets/hsizegrip.png) center center no-repeat #535353;
- cursor: row-resize;
- }
-
- .splitter .text {
- margin-left: 10px;
- margin-top: 4px;
- color: #cccccc;
-
- span {
- margin-left: 5px;
- }
- }
-
- .panel-bottom {
- flex: 1 1 auto;
- padding: 10px;
- /*min-height: 200px;*/
- background: black;
- color: white;
-
- overflow-y: scroll;
- }
-
- .panel-bottom pre {
- margin-top: -10px;
-
- .stdout {
- color: white;
- }
-
- .stderr {
- color: red;
- }
-
- .close {
- color: yellow;
- }
- }
- }
-
- label {
- font-size: 1.2em;
- display: block;
- font-weight: bold;
- margin: 30px 0 10px;
- }
-
-
-
-
-
- .page-loader {
- position: absolute;
- margin: 0;
- padding: 0;
- border: 0;
- width: 100vw;
- height: 100vh;
- background-color: #1fa2ed;
- color: #fff;
-
- // LET THE LOADING BEGIN
- .loader {
- display: flex;
- justify-content: center;
- flex-flow: nowrap column;
- align-items: center;
- min-height: 100vh;
- }
- .loading {
- display: flex;
- margin: 24px 0;
- }
- .loading__element {
- font: normal 100 2rem/1 Roboto;
- letter-spacing: .5em;
- }
- [class*="el"] {
- animation: bouncing 3s infinite ease;
- }
-
- @for $i from 1 through 19 {
- $delay: percentage($i);
- .el#{$i} {
- animation-delay: $delay / 1000% + s;
- }
- }
-
- @keyframes bouncing {
- 0%, 100% {
- transform: scale3d(1,1,1);
- }
- 50% {
- transform: scale3d(0,0,1);
- }
- }
-
- .current-event {
- color: rgba(255, 255, 255, 0.53);
- font: normal 100 1rem/1 Roboto;
- letter-spacing: .1em;
- }
-
- }
-
-
-}
diff --git a/src/public/scss/scripts.scss b/src/public/scss/scripts.scss
deleted file mode 100644
index 400c5f1..0000000
--- a/src/public/scss/scripts.scss
+++ /dev/null
@@ -1,35 +0,0 @@
-#scripts {
-
- .script-list .badge {
- margin-left: 0;
- float: none;
- }
-
- .card {
- /*margin-bottom: 60px;*/
- }
-
- @media only screen and (max-width: 599px) {
- .card-action {
- border-top: 0 !important;
- padding: 0 !important;
- height: 0;
- transition: padding 0.5s ease 0s,
- height 0.5s ease 0s;
- }
-
- .card-action a i {
- transform: scale(0);
- transition: transform 0.1s ease 0.1s;
- }
- .card:hover > .card-action {
- height: 55px;
- padding: 16px 24px !important;
- }
-
- .card:hover > .card-action > a > i {
- transform: scale(1);
- }
- }
-
-} \ No newline at end of file
diff --git a/src/public/scss/setup.scss b/src/public/scss/setup.scss
deleted file mode 100644
index e69de29..0000000
--- a/src/public/scss/setup.scss
+++ /dev/null
diff --git a/src/public/scss/update.scss b/src/public/scss/update.scss
deleted file mode 100644
index 8e9d2f2..0000000
--- a/src/public/scss/update.scss
+++ /dev/null
@@ -1,62 +0,0 @@
-#update {
- * {
- margin: 0;
- padding: 0;
- border: 0;
- box-sizing: border-box;
- &:before, &:after {
- box-sizing: inherit;
- }
- }
- html {
- width: 100vw;
- height: 100vh;
- }
- body {
- background-color: #1fa2ed;
- color: #fff;
- }
-
- // LET THE LOADING BEGIN
- .start-screen {
- display: flex;
- justify-content: center;
- flex-flow: nowrap column;
- align-items: center;
- min-height: 100vh;
- }
- .loading {
- display: flex;
- margin: 24px 0;
- }
- .loading__element {
- font: normal 100 2rem/1 Roboto;
- letter-spacing: .5em;
- }
- [class*="el"] {
- animation: bouncing 3s infinite ease;
- }
-
- @for $i from 1 through 19 {
- $delay: percentage($i);
- .el#{$i} {
- animation-delay: $delay / 1000% + s;
- }
- }
-
- @keyframes bouncing {
- 0%, 100% {
- transform: scale3d(1,1,1);
- }
- 50% {
- transform: scale3d(0,0,1);
- }
- }
-
- .current-event {
- color: rgba(255, 255, 255, 0.53);
- font: normal 100 1rem/1 Roboto;
- letter-spacing: .1em;
- }
-
-}
diff --git a/src/runtimeData/index.js b/src/runtimeData/index.js
deleted file mode 100644
index 8253140..0000000
--- a/src/runtimeData/index.js
+++ /dev/null
@@ -1,45 +0,0 @@
-let fse = require("fs-extra");
-
-class RuntimeData {
-
- constructor (DirUserData) {
- this.lockFile = DirUserData + "/config/runtime.json";
- this.runtimeVars = {};
-
- this.readFile();
- this.saveFile();
- };
-
- saveFile() {
- fse.outputJsonSync(this.lockFile, this.runtimeVars);
- }
-
- readFile() {
- try {
- this.runtimeVars = fse.readJsonSync(this.lockFile);
- } catch (err) {
- this.runtimeVars = {};
- }
- }
-
- set (name, value) {
- this.runtimeVars[name] = value;
- this.saveFile();
- };
-
- get (name) {
- this.readFile();
- if (typeof (this.runtimeVars[name]) != undefined) {
- return this.runtimeVars[name];
- } else {
- return false;
- }
- }
-
- unset (name) {
- delete this.runtimeVars[name];
- }
-
-}
-
-module.exports = RuntimeData;
diff --git a/src/versionChecker/index.js b/src/versionChecker/index.js
deleted file mode 100644
index cee1486..0000000
--- a/src/versionChecker/index.js
+++ /dev/null
@@ -1,58 +0,0 @@
-let fs = require("fs-extra");
-let url = require("url");
-let request = require('request');
-let exec = require("child_process").exec;
-
-class versionChecker {
-
- constructor(configJson, packageJson) {
- this.config = JSON.parse(fs.readFileSync(configJson));
- this.CPackageJson = JSON.parse(fs.readFileSync(packageJson));
-
- this.version = this.CPackageJson["version"];
- this.repoVersion = this.version;
- this.checkFrequency = this.config["checkInterval"] * 100 * 10 * 60 * 60; // takes in hours
- this.repoLink = this.CPackageJson["repository"]["url"];
- this.repoBranch = this.config["branch"];
-
- this.checkVersion(this); // Because we have to send the reference in our interval, we have to to it here as well
- this.updateChecker = setInterval(this.checkVersion, this.checkFrequency, this); // We have to send a reference to this class
- }
-
- checkVersion(parent) {
- if (typeof(parent) == "undefined") { parent = this; }
- let path = url.parse(parent.repoLink);
- let link = "https://raw.githubusercontent.com" + path.pathname + '/' + parent.repoBranch + "/package.json";
-
- request.get(link, (error, response, body) => {
- if (!error && response.statusCode === 200) {
- let remotePackageJSON = JSON.parse(body);
- this.repoVersion = remotePackageJSON["version"];
- if (parent.VersionIsNewerThan(this.repoVersion, parent.version)) {
- global.log("A new version is available on \"" + parent.repoBranch + "\" (v" + this.repoVersion + ")", "INFO");
- }
- } else {
- global.log("Could not find latest version! Please check you internet connection.", "ERROR");
- }
- });
- }
-
- VersionIsNewerThan(check, current) {
- let checkParts = check.split(".");
- let currentParts = current.split(".");
- let lCheckParts = checkParts.length;
- let lCurrentParts = currentParts.length;
-
- for (let i = 0; i < lCheckParts; i++) {
- if (i >= lCurrentParts) { return true; }
- if (Number (checkParts[i]) > Number (currentParts[i])) { return true; }
- if (Number (checkParts[i]) < Number (currentParts[i])) { return false; }
- }
- return false;
- }
-
-}
- 
-module.exports = (configJson, packageJson) => {
- return new versionChecker(configJson, packageJson);
-};