aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/compileAndRun/index.js75
-rw-r--r--src/compileAndRun/process.js41
-rw-r--r--src/compileAndRun/pythonSupportFiles/LuxcenaNeo/NeoBehaviour.py29
-rw-r--r--src/compileAndRun/pythonSupportFiles/LuxcenaNeo/Strip.py6
-rw-r--r--src/compileAndRun/pythonSupportFiles/LuxcenaNeo/__init__.py10
-rw-r--r--src/compileAndRun/pythonSupportFiles/entry.py78
-rw-r--r--src/domain/middleware.js40
-rw-r--r--src/neoRuntime/index.js232
-rw-r--r--src/public/app.js17
-rw-r--r--src/public/app.scss9
-rw-r--r--src/public/components/sidenav.js22
-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
23 files changed, 1274 insertions, 0 deletions
diff --git a/src/compileAndRun/index.js b/src/compileAndRun/index.js
new file mode 100644
index 0000000..6f89e44
--- /dev/null
+++ b/src/compileAndRun/index.js
@@ -0,0 +1,75 @@
+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
new file mode 100644
index 0000000..67ff546
--- /dev/null
+++ b/src/compileAndRun/process.js
@@ -0,0 +1,41 @@
+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/NeoBehaviour.py b/src/compileAndRun/pythonSupportFiles/LuxcenaNeo/NeoBehaviour.py
new file mode 100644
index 0000000..b0238e7
--- /dev/null
+++ b/src/compileAndRun/pythonSupportFiles/LuxcenaNeo/NeoBehaviour.py
@@ -0,0 +1,29 @@
+# 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
new file mode 100644
index 0000000..c4ac8c9
--- /dev/null
+++ b/src/compileAndRun/pythonSupportFiles/LuxcenaNeo/Strip.py
@@ -0,0 +1,6 @@
+class Strip:
+
+ def __init__(self, segments, segmentConfiguration):
+ self.segments = segments
+ self.segmentConfiguration = segmentConfiguration
+ return
diff --git a/src/compileAndRun/pythonSupportFiles/LuxcenaNeo/__init__.py b/src/compileAndRun/pythonSupportFiles/LuxcenaNeo/__init__.py
new file mode 100644
index 0000000..40b8e2d
--- /dev/null
+++ b/src/compileAndRun/pythonSupportFiles/LuxcenaNeo/__init__.py
@@ -0,0 +1,10 @@
+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
new file mode 100644
index 0000000..4ba1031
--- /dev/null
+++ b/src/compileAndRun/pythonSupportFiles/entry.py
@@ -0,0 +1,78 @@
+# 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)
+ segments = stripConf["segments"]
+ segmentConfiguration = stripConf["segmentConfiguration"]
+
+ print ("> Initializing script...")
+ moduleSc = importlib.import_module("script")
+
+ if ("LuxcenaNeo" in dir(moduleSc)):
+ moduleSc.LuxcenaNeo.strip = moduleSc.LuxcenaNeo.Strip(segments, segmentConfiguration)
+ elif ("neo" in dir(moduleSc)):
+ moduleSc.neo.strip = moduleSc.neo.Strip(segments, segmentConfiguration)
+ 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
new file mode 100644
index 0000000..ebd00f8
--- /dev/null
+++ b/src/domain/middleware.js
@@ -0,0 +1,40 @@
+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
new file mode 100644
index 0000000..d34f4b4
--- /dev/null
+++ b/src/neoRuntime/index.js
@@ -0,0 +1,232 @@
+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
new file mode 100644
index 0000000..5e6b22b
--- /dev/null
+++ b/src/public/app.js
@@ -0,0 +1,17 @@
+// 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");
diff --git a/src/public/app.scss b/src/public/app.scss
new file mode 100644
index 0000000..845d36a
--- /dev/null
+++ b/src/public/app.scss
@@ -0,0 +1,9 @@
+// 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
new file mode 100644
index 0000000..a024750
--- /dev/null
+++ b/src/public/components/sidenav.js
@@ -0,0 +1,22 @@
+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>
+</ul>
+`; \ No newline at end of file
diff --git a/src/public/js/general.js b/src/public/js/general.js
new file mode 100644
index 0000000..b0b04d2
--- /dev/null
+++ b/src/public/js/general.js
@@ -0,0 +1,10 @@
+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
new file mode 100644
index 0000000..5b67de2
--- /dev/null
+++ b/src/public/js/index.js
@@ -0,0 +1,68 @@
+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
new file mode 100644
index 0000000..d376196
--- /dev/null
+++ b/src/public/js/logviewer.js
@@ -0,0 +1,64 @@
+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
new file mode 100644
index 0000000..6108be4
--- /dev/null
+++ b/src/public/js/neo_ide.js
@@ -0,0 +1,4 @@
+module.exports = () => {
+
+
+}; \ No newline at end of file
diff --git a/src/public/js/scripts.js b/src/public/js/scripts.js
new file mode 100644
index 0000000..ccad3cf
--- /dev/null
+++ b/src/public/js/scripts.js
@@ -0,0 +1,71 @@
+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
new file mode 100644
index 0000000..cbbc654
--- /dev/null
+++ b/src/public/scss/general.scss
@@ -0,0 +1,44 @@
+.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
new file mode 100644
index 0000000..fb10885
--- /dev/null
+++ b/src/public/scss/neo_ide.scss
@@ -0,0 +1,254 @@
+.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
new file mode 100644
index 0000000..400c5f1
--- /dev/null
+++ b/src/public/scss/scripts.scss
@@ -0,0 +1,35 @@
+#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
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/src/public/scss/setup.scss
diff --git a/src/public/scss/update.scss b/src/public/scss/update.scss
new file mode 100644
index 0000000..8e9d2f2
--- /dev/null
+++ b/src/public/scss/update.scss
@@ -0,0 +1,62 @@
+#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
new file mode 100644
index 0000000..8253140
--- /dev/null
+++ b/src/runtimeData/index.js
@@ -0,0 +1,45 @@
+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
new file mode 100644
index 0000000..69bf848
--- /dev/null
+++ b/src/versionChecker/index.js
@@ -0,0 +1,58 @@
+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"] * 10 * 10; // 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);
+};