From 512b3c66a569896e6a9d8910e55fca15cd890c2d Mon Sep 17 00:00:00 2001 From: Danielv123 Date: Thu, 13 Jul 2017 11:59:38 +0200 Subject: [PATCH] Added fake terminal to /nodeDetails It doesn't do anything yet, but it should allow sending commands to servers and watching their output. --- static/head.html | 1 + static/lib/terminal.js | 185 ++++++++++++++++++++++++++++++++++++++++ static/nodeDetails.html | 11 +++ static/nodeDetails.js | 97 ++++++++++++++++++++- static/style.css | 73 ++++++++++++++++ 5 files changed, 365 insertions(+), 2 deletions(-) create mode 100644 static/lib/terminal.js diff --git a/static/head.html b/static/head.html index 22eb23a2..208d32a7 100644 --- a/static/head.html +++ b/static/head.html @@ -1,6 +1,7 @@ Clusterio + diff --git a/static/lib/terminal.js b/static/lib/terminal.js new file mode 100644 index 00000000..6e5e0864 --- /dev/null +++ b/static/lib/terminal.js @@ -0,0 +1,185 @@ +/*! terminal.js v2.0 | (c) 2014 Erik Österberg | https://github.com/eosterberg/terminaljs */ + +var Terminal = (function () { + // PROMPT_TYPE + var PROMPT_INPUT = 1, PROMPT_PASSWORD = 2, PROMPT_CONFIRM = 3 + + var fireCursorInterval = function (inputField, terminalObj) { + var cursor = terminalObj._cursor + setTimeout(function () { + if (inputField.parentElement && terminalObj._shouldBlinkCursor) { + cursor.style.visibility = cursor.style.visibility === 'visible' ? 'hidden' : 'visible' + fireCursorInterval(inputField, terminalObj) + } else { + cursor.style.visibility = 'visible' + } + }, 500) + } + + var firstPrompt = true; + promptInput = function (terminalObj, message, PROMPT_TYPE, callback) { + var shouldDisplayInput = (PROMPT_TYPE === PROMPT_INPUT) + var inputField = document.createElement('input') + + inputField.style.position = 'absolute' + inputField.style.zIndex = '-100' + inputField.style.outline = 'none' + inputField.style.border = 'none' + inputField.style.opacity = '0' + inputField.style.fontSize = '0.2em' + + terminalObj._inputLine.textContent = '' + terminalObj._input.style.display = 'block' + terminalObj.html.appendChild(inputField) + fireCursorInterval(inputField, terminalObj) + + if (message.length) terminalObj.print(PROMPT_TYPE === PROMPT_CONFIRM ? message + ' (y/n)' : message) + + inputField.onblur = function () { + terminalObj._cursor.style.display = 'none' + } + + inputField.onfocus = function () { + inputField.value = terminalObj._inputLine.textContent + terminalObj._cursor.style.display = 'inline' + } + + terminalObj.html.onclick = function () { + inputField.focus() + } + + inputField.onkeydown = function (e) { + if (e.which === 37 || e.which === 39 || e.which === 38 || e.which === 40 || e.which === 9) { + e.preventDefault() + } else if (shouldDisplayInput && e.which !== 13) { + setTimeout(function () { + terminalObj._inputLine.textContent = inputField.value + }, 1) + } + } + inputField.onkeyup = function (e) { + if (PROMPT_TYPE === PROMPT_CONFIRM || e.which === 13) { + terminalObj._input.style.display = 'none' + var inputValue = inputField.value + if (shouldDisplayInput) terminalObj.print(inputValue) + terminalObj.html.removeChild(inputField) + if (typeof(callback) === 'function') { + if (PROMPT_TYPE === PROMPT_CONFIRM) { + callback(inputValue.toUpperCase()[0] === 'Y' ? true : false) + } else callback(inputValue) + } + } + } + if (firstPrompt) { + firstPrompt = false + setTimeout(function () { inputField.focus() }, 50) + } else { + inputField.focus() + } + } + + var terminalBeep + + var TerminalConstructor = function (id) { + if (!terminalBeep) { + terminalBeep = document.createElement('audio') + var source = '' + source + 'ogg" type="audio/ogg">' + terminalBeep.volume = 0.05 + } + + this.html = document.createElement('div') + this.html.className = 'Terminal' + if (typeof(id) === 'string') { this.html.id = id } + + this._innerWindow = document.createElement('div') + this._output = document.createElement('p') + this._inputLine = document.createElement('span') //the span element where the users input is put + this._cursor = document.createElement('span') + this._input = document.createElement('p') //the full element administering the user input, including cursor + + this._shouldBlinkCursor = true + + this.beep = function () { + terminalBeep.load() + terminalBeep.play() + } + + this.print = function (message) { + var newLine = document.createElement('div') + newLine.textContent = message + this._output.appendChild(newLine) + } + + this.input = function (message, callback) { + promptInput(this, message, PROMPT_INPUT, callback) + } + + this.password = function (message, callback) { + promptInput(this, message, PROMPT_PASSWORD, callback) + } + + this.confirm = function (message, callback) { + promptInput(this, message, PROMPT_CONFIRM, callback) + } + + this.clear = function () { + this._output.innerHTML = '' + } + + this.sleep = function (milliseconds, callback) { + setTimeout(callback, milliseconds) + } + + this.setTextSize = function (size) { + this._output.style.fontSize = size + this._input.style.fontSize = size + } + + this.setTextColor = function (col) { + this.html.style.color = col + this._cursor.style.background = col + } + + this.setBackgroundColor = function (col) { + this.html.style.background = col + } + + this.setWidth = function (width) { + this.html.style.width = width + } + + this.setHeight = function (height) { + this.html.style.height = height + } + + this.blinkingCursor = function (bool) { + bool = bool.toString().toUpperCase() + this._shouldBlinkCursor = (bool === 'TRUE' || bool === '1' || bool === 'YES') + } + + this._input.appendChild(this._inputLine) + this._input.appendChild(this._cursor) + this._innerWindow.appendChild(this._output) + this._innerWindow.appendChild(this._input) + this.html.appendChild(this._innerWindow) + + this.setBackgroundColor('black') + this.setTextColor('white') + this.setTextSize('1em') + this.setWidth('100%') + this.setHeight('100%') + + this.html.style.fontFamily = 'Monaco, Courier' + this.html.style.margin = '0' + this._innerWindow.style.padding = '10px' + this._input.style.margin = '0' + this._output.style.margin = '0' + this._cursor.style.background = 'white' + this._cursor.innerHTML = 'C' //put something in the cursor.. + this._cursor.style.display = 'none' //then hide it + this._input.style.display = 'none' + } + + return TerminalConstructor +}()) \ No newline at end of file diff --git a/static/nodeDetails.html b/static/nodeDetails.html index df924409..73f88159 100644 --- a/static/nodeDetails.html +++ b/static/nodeDetails.html @@ -18,5 +18,16 @@
+
+
+
+

Clusterio RCON shell

+
+
+
+
+
+ +
\ No newline at end of file diff --git a/static/nodeDetails.js b/static/nodeDetails.js index 7bfaad78..3d01473e 100644 --- a/static/nodeDetails.js +++ b/static/nodeDetails.js @@ -126,7 +126,9 @@ function populateSlaveInfo(){ HTML += "" // end of left container // chart - HTML += '
' + HTML += '
'; + // terminal + // HTML += '
'; document.querySelector("#hero").innerHTML = HTML; @@ -145,9 +147,100 @@ function populateSlaveInfo(){ // make production graph makeGraph(slave.unique, slave.unique) + makeTerminal(); }); } - +var slaveLogin = {}; +function makeTerminal(){ + myTerminal = new Terminal(); + document.querySelector("#terminal").appendChild(myTerminal.html); + // use jQuery to allow the user to drag the window around + $("#terminal").draggable({ + handle:".titleBar", + }); + myTerminal.print('Welcome to Clusterio rcon!'); + myTerminal.input('', handleTerminalInput); + if(localStorage.terminalMinimized && localStorage.terminalMinimized == "true"){ + minimizeTerminal(true); + } + function print(string){ + myTerminal.print(string); + } + + function handleTerminalInput(inputString) { + // show clearly that it was a command that was typed, not terminal output + let lastLine = document.querySelector("#terminal div > p:nth-child(1) > div:last-child"); + lastLine.innerHTML = "> " + lastLine.innerHTML; + + argv = inputString.split(' '); + if(argv[0][0] == '/'){ + if(slaveLogin && slaveLogin.name && slaveLogin.pass){ + print('Running: '+inputString); + } else { + print('Not identified to communicate with any slave! Run "help" for more information.'); + } + } else if(argv[0] == 'help'){ + if(argv[1] == 'login'){ + print('To send commands to a slave, you are required to identify yourself. You can do this with the login command. The name will be the name of the slave as displayed on master. The password is the rcon password of the slave, as per /instances/[name]/config.json'); + } else if(argv[1] == "/c"){ + print('To run a command, start with /. You can for example do /c game.print("hello world!")'); + } else if(argv[1] == "issues"){ + print("Issues are reported to Danielv123 on github or espernet IRC."); + print(" - http://github.com/Danielv123/factorioClusterio/issues"); + print(" - EsperNet #factorio Danielv123"); + } else { + print('To identify yourself to a slave, use "login [name] [password]"'); + print(' - /c [command] - Run command'); + print(' - login [name] [passs] - Connect to remote slave'); + print(' - issues - Report an issue'); + } + } else if(argv[0] == 'issues'){ + print("Issues are reported to Danielv123 on github or espernet IRC."); + print(" - http://github.com/Danielv123/factorioClusterio/issues"); + print(" - EsperNet #factorio Danielv123"); + } else { + print('Invalid command!'); + } + + // keep handling input (unless something is seriously wrong) + myTerminal.input('',handleTerminalInput); + } +} +function minimizeTerminal(instant){ + localStorage.terminalMinimized = true; + $('#terminal').draggable('disable'); + if(!instant){ + $('#terminal')[0].style.transition = 'left 0.2s linear, top 0.2s linear'; + } + document.querySelector('.Terminal').style.height = 0; + let ter = $('#terminal')[0]; + // if the window just spawned in and we are doing the first minimize, our position won't be set yet. We also don't want to save this blank position. + if(ter.style.left && ter.style.top){ + localStorage.oldTerminalPosition = JSON.stringify({x:ter.style.left, y:ter.style.top}); + } + ter.style.left = (window.innerWidth - ter.offsetWidth)+'px'; + ter.style.top = (window.innerHeight - 25)+'px'; + + // if we did it instant we still want to set this property for after. + if(instant){ + setTimeout(() => $('#terminal')[0].style.transition = 'left 0.2s linear, top 0.2s linear',200); + } +} +function maximizeTerminal(instant){ + localStorage.terminalMinimized = false; + document.querySelector('.Terminal').style.height = '400px'; + let ter = $('#terminal')[0]; + + // recover our old saved position + let oldTerminalPosition = JSON.parse(localStorage.oldTerminalPosition); + ter.style.left = oldTerminalPosition.x; + ter.style.top = oldTerminalPosition.y; + + // remove our animation once its finished + // since instant is a true/false value, Number will convert it to 1 or 0. Since 0 is falsy, || will turn it into 200. + setTimeout(() => $('#terminal')[0].style.transition = 'none', (Number(instant) || 200)); + $('#terminal').draggable('enable'); +} populateSlaveInfo(); // ID of slave, ID of canvasjs div without # diff --git a/static/style.css b/static/style.css index 5156f4f4..557d66b8 100644 --- a/static/style.css +++ b/static/style.css @@ -68,6 +68,79 @@ a:hover { #contentGraph { float:left; } + +/* terminal */ +.Terminal p div { + color:lime; +} +.Terminal p span { + color:yellow; +} +.Terminal p > span:nth-child(1):before { + content:"> "; +} +.Terminal { + overflow-y: scroll; + position:absolute; + border-left:1px solid darkgrey; + border-bottom:1px solid darkgrey; + height:400px; + transition:height 0.2s linear; +} +#terminal { + width:640px; + height:400px; + position:fixed; + right:10px; + bottom:10px; + left:50%; + top:50%; +} +/* titleBar, the bar above windows where you can minimize, maximize etc. Mostly for the terminal. */ +.titleBar { + width:100%; + height:25px; + background-color:white; +} +.titleBar > .title { + float:left; + margin-top: 3px; + margin-left: 5px; +} +.titleBar p { + color:black; +} +.titleBar > .titleButton { + width:50px; + height:100%; + float:right; + background-color:white; + color:black; + transition:0.3s; +} +.titleBar > .titleButton:hover { + background-color: lightgray; +} +.titleBar > .titleButton:before { + margin-left: 18px; +} +.titleBar > .titleButton.close:hover { + background-color: red; + color:white; +} +.titleBar > .titleButton.close:before { + content:"✕"; + margin-top:5px; +} +.titleBar > .titleButton.maximize:before { + content:"⬜"; + margin-top:5px; +} +.titleBar > .titleButton.minimize:before { + content:"__"; + margin-left:17px; +} + /* Slave stuff */ .slaveBox { float:left;