diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/compileAndRun/index.js | 75 | ||||
-rw-r--r-- | src/compileAndRun/process.js | 41 | ||||
-rw-r--r-- | src/compileAndRun/pythonSupportFiles/LuxcenaNeo/NeoBehaviour.py | 29 | ||||
-rw-r--r-- | src/compileAndRun/pythonSupportFiles/LuxcenaNeo/Strip.py | 6 | ||||
-rw-r--r-- | src/compileAndRun/pythonSupportFiles/LuxcenaNeo/__init__.py | 10 | ||||
-rw-r--r-- | src/compileAndRun/pythonSupportFiles/entry.py | 78 | ||||
-rw-r--r-- | src/domain/middleware.js | 40 | ||||
-rw-r--r-- | src/neoRuntime/index.js | 232 | ||||
-rw-r--r-- | src/public/app.js | 17 | ||||
-rw-r--r-- | src/public/app.scss | 9 | ||||
-rw-r--r-- | src/public/components/sidenav.js | 22 | ||||
-rw-r--r-- | src/public/js/general.js | 10 | ||||
-rw-r--r-- | src/public/js/index.js | 68 | ||||
-rw-r--r-- | src/public/js/logviewer.js | 64 | ||||
-rw-r--r-- | src/public/js/neo_ide.js | 4 | ||||
-rw-r--r-- | src/public/js/scripts.js | 71 | ||||
-rw-r--r-- | src/public/scss/general.scss | 44 | ||||
-rw-r--r-- | src/public/scss/neo_ide.scss | 254 | ||||
-rw-r--r-- | src/public/scss/scripts.scss | 35 | ||||
-rw-r--r-- | src/public/scss/setup.scss | 0 | ||||
-rw-r--r-- | src/public/scss/update.scss | 62 | ||||
-rw-r--r-- | src/runtimeData/index.js | 45 | ||||
-rw-r--r-- | src/versionChecker/index.js | 58 |
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); +}; |