mirror of
https://github.com/clusterio/clusterio.git
synced 2026-11-13 07:04:53 +09:00
Added fake terminal to /nodeDetails
It doesn't do anything yet, but it should allow sending commands to servers and watching their output.
This commit is contained in:
1 parent
e3b8926e35
commit
512b3c66a5
5 files changed
+365
-2
No files matched your search
@@ -1,6 +1,7 @@
|
||||
<title>Clusterio</title>
|
||||
<link rel="stylesheet" type="text/css" href="style.css">
|
||||
<script src="https://code.jquery.com/jquery.js"></script>
|
||||
<script src="https://code.jquery.com/ui/1.12.1/jquery-ui.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/canvasjs/1.7.0/canvasjs.js" type="text/javascript"></script>
|
||||
<script src="http://momentjs.com/downloads/moment.min.js" type="text/javascript"></script>
|
||||
<script src="main.js"></script>
|
||||
|
||||
@@ -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 src="http://www.erikosterberg.com/terminaljs/beep.'
|
||||
terminalBeep.innerHTML = source + 'mp3" type="audio/mpeg">' + 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
|
||||
}())
|
||||
@@ -18,5 +18,16 @@
|
||||
<div id="details">
|
||||
|
||||
</div>
|
||||
<div id="terminal">
|
||||
<div class="titleBar">
|
||||
<div class="title">
|
||||
<p>Clusterio RCON shell</p>
|
||||
</div>
|
||||
<div class="titleButton close" onclick="minimizeTerminal();"></div>
|
||||
<div class="titleButton maximize" onclick="maximizeTerminal();"></div>
|
||||
<div class="titleButton minimize" onclick="minimizeTerminal();"></div>
|
||||
</div>
|
||||
<!-- JS generates a div called .Terminal that appears here with the display and typing things -->
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
+95
-2
@@ -126,7 +126,9 @@ function populateSlaveInfo(){
|
||||
HTML += "</div>" // end of left container
|
||||
|
||||
// chart
|
||||
HTML += '<div id="' + slave.unique + '" class="productionGraph" style="width: calc(100% - 300px);"></div>'
|
||||
HTML += '<div id="' + slave.unique + '" class="productionGraph" style="width: calc(100% - 300px);"></div>';
|
||||
// terminal
|
||||
// HTML += '<div id="terminal"></div>';
|
||||
|
||||
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 #
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in new issue
Block a user