diff options
Diffstat (limited to 'src')
31 files changed, 1433 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/RuntimeProcess.js b/src/NeoRuntimeManager/RuntimeProcess.js new file mode 100644 index 0000000..60f6a28 --- /dev/null +++ b/src/NeoRuntimeManager/RuntimeProcess.js @@ -0,0 +1,150 @@ +let fs = require("fs-extra"); +let spawn = require("child_process"); + +class RuntimeProcess { + + constructor(_modePath, _onVarChange, _eventEmitter) { + this.modePath = _modePath; + this.logfile = `${this.modePath}/mode.log`; + + this.stdout = ""; + this.stderr = ""; + + this.fl = false; + this.proc = null; + + this.isRunning = false; + this.exitCode = null; + + this.variables = {}; + this.globvars = {}; + this.onVarChange = _onVarChange; + this.eventEmitter = _eventEmitter; + } + + start() { + if (this.isRunning) { + console.log("PROCESS ALREADY RUNNING"); + return; + } + this.isRunning = true; + this.proc = spawn.spawn( + "python3", + [ + "-u", // This makes us able to get real-time output + `${__basedir}/NeoRuntime/Runtime/neo_runtime.py`, + `--strip-config="${__datadir}/config/strip.ini"`, + `--mode-path="${this.modePath}"`, + `--mode-entry=script` + ] + ); + + this.proc.on('error', (err) => { + console.log(err); + }); + + fs.ensureFileSync(this.logfile); + this.eventEmitter.emit("proc:start"); + + this.proc.stdout.on('data', (_stdout) => { + let stdout_str = _stdout.toString(); + + let regex = /{ ":::data:": { (.*) } }/gi; + let data = stdout_str.match(regex); + stdout_str = stdout_str.replace(regex, () => ""); + + if ((data != null) && (data.length > 0)) { + try { + this.processVarData(data) + } catch {} + } + + if (stdout_str.replace("\n", "").replace(" ", "") == "") { + // In this case, we want to ignore the data. + } else { + // stdout_str = stdout_str.replace(/\n$/, "") + fs.appendFile(this.logfile, "\n====stdout====\n" + 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().replace(/\n$/, "") + let stderr_str = _stderr.toString() + fs.appendFile(this.logfile, "\n====stderr====\n" + 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, "\n====close====\nScript 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); + } + } + + processVarData(data) { + data = JSON.parse(data)[":::data:"]; + if (data.hasOwnProperty("globvars")) { + forEachDiff(data["globvars"], this.globvars, (key, newVal) => { + this.onVarChange("globvars", key, newVal); + }); + this.globvars = data["globvars"]; + } + if (data.hasOwnProperty("variables")) { + forEachDiff(data["variables"], this.variables, (key, newVal) => { + this.onVarChange("variables", key, newVal); + }); + this.variables = data["variables"]; + } + } + +} + +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 = RuntimeProcess; diff --git a/src/NeoRuntimeManager/index.js b/src/NeoRuntimeManager/index.js new file mode 100644 index 0000000..62acb8a --- /dev/null +++ b/src/NeoRuntimeManager/index.js @@ -0,0 +1,309 @@ +/** + * 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"); +let logger = require(__basedir + "/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 {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(__basedir + "/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}".`); + + let globvarsTmp = {}; + let variablesTmp = {}; + if (runtimeProcess != null) { + globvarsTmp = runtimeProcess.globvars; + variablesTmp = runtimeProcess.variables; + } + + stopMode(); + + modeId = _modeId; + neoModules.userData.config.activeMode = modeId; + eventEmitter.emit("change", "mode", modeId); + + runtimeProcess = new RuntimeProcess(getModePath(_modeId), onVariableChange, eventEmitter); + runtimeProcess.globvars = globvarsTmp; + runtimeProcess.variables = variablesTmp; + 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 = __basedir + "/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 runtimeProcess.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; } + runtimeProcess.proc.stdin.write(`:::setglob: ${name}:${value}\n`); + return {success: true} +} + +/** + * Get all variables declared in mode + * + * @return {object} + */ +function getVariables() { + if (!modeRunning()) { return {}; } + return runtimeProcess.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; } + runtimeProcess.proc.stdin.write(`:::setvar: ${name}:${value}\n`); + return {success: true} +} + +/** + * 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; + 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 + } +};
\ No newline at end of file diff --git a/src/SSLCert/index.js b/src/SSLCert/index.js new file mode 100644 index 0000000..6dad579 --- /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(__basedir + "/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 = __datadir + "/config/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(); +}; +
\ No newline at end of file diff --git a/src/SelfUpdater/index.js b/src/SelfUpdater/index.js new file mode 100644 index 0000000..3c5546b --- /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(__basedir + "/src/logger"); + +let neoModules; + +class VersionChecker { + + constructor() { + this.CPackageJson = JSON.parse(fs.readFileSync(__basedir + "/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..fdaad56 --- /dev/null +++ b/src/SocketIO/index.js @@ -0,0 +1,353 @@ +/** + * 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(__basedir + "/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()) / 1000)+(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", () => { + }); + + /* 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) + } +}; +
\ No newline at end of file diff --git a/src/UserData/index.js b/src/UserData/index.js new file mode 100644 index 0000000..e5318c9 --- /dev/null +++ b/src/UserData/index.js @@ -0,0 +1,330 @@ +/** + * 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(__basedir + "/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(__datadir + "/config/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; } + + fse.writeFileSync(__datadir + "/config/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(__datadir + "/config/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(__datadir + "/config/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(__datadir + "/config/"); + fse.ensureDirSync(__datadir + "/config/certs"); + fse.ensureDirSync(__datadir + "/userCode/"); + fse.ensureDirSync(__datadir + "/remoteCode/"); + + // Generate config-files + if (!fse.existsSync(__datadir + "/config/config.ini")) { + fse.closeSync(fse.openSync(__datadir + "/config/config.ini", 'w')); + } + ensureMainConfig(); + + if (!fse.existsSync(__datadir + "/config/strip.ini")) { + fse.closeSync(fse.openSync(__datadir + "/config/strip.ini", 'w')); + } + ensureStripConfig(); + + if (!fse.existsSync(__datadir + "/config/users.ini")) { + fse.writeFileSync(__datadir + "/config/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(__datadir + "/config/users.ini", 'utf-8')) + config[username] = {} + config[username].salt = salt + config[username].password = password + fse.writeFileSync(__datadir + "/config/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(__datadir + "/config/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(__datadir + "/config/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(__datadir + "/config/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(__datadir + "/config/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 = __basedir + "/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(`${__datadir}/config/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(`${__datadir}/config/strip.ini`, {DEFAULT: c}, removeSetters=false); + }, + }, + config: getFullConfig(`${__datadir}/config/config.ini`), + mode: { + create: createNewUserMode, + delete: deleteUserMode + } + } +};
\ No newline at end of file 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); -}; |