From 6a5dad3c93211a14ea2a93afc844b0336869e5c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc=20W=C3=A4ckerlin?= Date: Tue, 19 Jan 2016 16:20:28 +0000 Subject: [PATCH] new features log and bash --- nodejs/package.json.in | 9 +- nodejs/public/javascripts/servicedock.js | 230 +++++++++++++--------- nodejs/public/stylesheets/servicedock.css | 75 ++++++- nodejs/sockets/index.js | 57 +++++- nodejs/views/index.ejs | 10 + 5 files changed, 277 insertions(+), 104 deletions(-) diff --git a/nodejs/package.json.in b/nodejs/package.json.in index 9d546cb..d9fbcc9 100644 --- a/nodejs/package.json.in +++ b/nodejs/package.json.in @@ -3,10 +3,11 @@ "version": "@PACKAGE_VERSION@", "private": true, "dependencies": { - "express": "~2.5.8", - "stylus": "~0.53.0", - "ejs": ">= 0.0.1", - "socket.io": "~1.4.4" + "express": "~2.5.8", + "stylus": "~0.53.0", + "ejs": ">= 0.0.1", + "socket.io": "~1.4.4", + "pty.js": "~0.3.0" }, "description": "Docker as a Service", "main": "servicedock.js", diff --git a/nodejs/public/javascripts/servicedock.js b/nodejs/public/javascripts/servicedock.js index ad5f0cb..1c6b707 100644 --- a/nodejs/public/javascripts/servicedock.js +++ b/nodejs/public/javascripts/servicedock.js @@ -13,11 +13,11 @@ var focused = null; function DockerContainers() { var Status = Object.freeze({ - Error: {color: "red", action1: "start", action2: "remove"}, - Terminated: {color: "yellow", action1: "start", action2: "remove"}, - Restarting: {color: "lightblue", action1: "start", action2: "remove"}, - Paused: {color: "lightgrey", action1: "unpause", action2: null}, - Running: {color: "lightgreen", action1: "pause", action2: "stop"} + Error: {color: "red", action1: "start", action2: "remove", bash: false}, + Terminated: {color: "yellow", action1: "start", action2: "remove", bash: false}, + Restarting: {color: "lightblue", action1: "start", action2: "remove", bash: false}, + Paused: {color: "lightgrey", action1: "unpause", action2: null, bash: false}, + Running: {color: "lightgreen", action1: "pause", action2: "stop", bash: true} }); var containers = []; var nodes = []; @@ -40,7 +40,7 @@ function DockerContainers() { if (n.status.action1) { $("#popup").append(''); $("#popup1").click(function() { - socket.emit(n.status.action1, name); + emit(n.status.action1, name); }); } $("#popup").append(''); @@ -50,11 +50,27 @@ function DockerContainers() { if (n.status.action2) { $("#popup").append(''); $("#popup3").click(function() { - socket.emit(n.status.action2, name); + emit(n.status.action2, name); }); } $("#popup").append('
'); - $("#popup").append(''); + $("#popup").append(''); + $("#popup4").click(function() { + emit("logs", name); + }); + if (n.status.bash) { + $("#popup").append(''); + $("#popup5").click(function() { + emit("bash-start", name); + $("#console").show(); + $("#main").hide(); + $("#bash").submit(function() { + emit("bash-input", {name: name, text: $("#command").val()+"\n"}); + $("#command").val(""); + }) + }); + } + $("#popup").append(''); $("#popup").css("position", "fixed"); $("#popup").css("top", e.pageY-$("#popup").height()/4); $("#popup").css("left", e.pageX-$("#popup").width()/2); @@ -66,49 +82,6 @@ function DockerContainers() { $("#popup").show(); }) } - this.details = function(name) { - var res = ` -
- -
- - - - - - - - - - - - - - `; - var n = nodes[name]; - res += ` - -
NamePortsVolumesLinksEnvironmentsImageCommand
-
-
-
-
-
`;
-        res += JSON.stringify(containers[nodes[name].id], null, 4);
-        res += `
-                    
-
-
- - `; - return res; - } function getIps(n, ips) { n.ports.forEach(function(p) { if (!ips[p.ip]) ips[p.ip] = []; @@ -185,7 +158,7 @@ function DockerContainers() { n = n || nodes; for (name in n) getIps(n[name], ips); res += graphIpClusters(ips); - for (name in n) res += graphNode(n[name]); + for (name in n) res += graphNode(n[name]); res += "{rank=same;\n"; for (name in n) res += graphVolumesInside(n[name]); res+="}\n"; @@ -243,7 +216,6 @@ function DockerContainers() { else if (c.State.Restarting) nodes[name].status = Status.Restarting; else if (c.State.ExitCode == 0) nodes[name].status = Status.Terminated; else nodes[name].status = Status.Error; - console.log("STATUS", name, c.State, nodes[name].status); nodes[name].volumes = []; var volumes = c.Volumes || c.Config.Volumes; nodes[name].volumes = []; @@ -316,6 +288,14 @@ function DockerContainers() { var dc = new DockerContainers(); +function htmlenc(html) { + return $('
').text(html).html(); +} + +function htmldec(data) { + return $('
').html(data).text(); +} + /// Show error messsage /** Fades in an error message and logs to console. @param data (optional) The error can be a string or any structure. @@ -389,6 +369,7 @@ function status(text, msg) { $("#popup").hide(); if (msg) success(msg); else setTimeout("$('#status').fadeOut('slow')", 5000); + zoom(0); $("#main").show(); $("form input:first-child").focus(); dc.contextmenu("#main"); @@ -488,7 +469,6 @@ function showviz(vizpath, more) { } res = "digraph {\n"+" rankdir="+rankdir+";\n"+viz+"\n}"; try { - zoomlevel = 0; status(more?Viz(res)+more:Viz(res)); } catch(e) { (res = res.split("\n")).forEach(function(v, i, a) { @@ -504,50 +484,116 @@ function details(name) { showviz(dc.subgraph(focused)); } -function action(container, action) { - $("#imagetools").hide(); - $.ajax({url: "action.php?container="+container+"&action="+action, success: function(res) { - success(res); - manage(); - }}).fail(function() { - error("offline"); - }); +function containers(c) { + console.log("->rcv containers"); + dc.setContainers(c); + if (focused && dc.exists(focused)) + details(focused); + else + overview(); } -/** Manage Docker Services */ -function manage() { - $("#imagetools").hide(); - $.ajax({url: "manage.php", success: function(res) { - status(res); - }}).fail(function() { - error("offline"); - }); +function logs(data) { + console.log("->rcv logs("+data.name+")"); + $("#main").hide(); + $("#console").hide(); + $("#logs").show(); + if (data.type=='done') { + $("#logs").append('\nDONE'); + } else { + $("#logs").append(''+htmlenc(data.text)+''); + } } -/** Show an Overview of all Docker Images */ -function imgs() { - $("#imagetools").hide(); - $.ajax({url: "images.php", success: function(res) { - try { - showviz(res); - } catch(e) { - (res = res.split("\n")).forEach(function(v, i, a) { - a[i] = ("000"+(i+1)).slice(-3)+": "+v; +function strInsert(str, pos, txt) { + return str.slice(0, pos)+txt+str.slice(pos); +} + +function ansifilter(data) { + console.log("ansifilter"); + var res = data; + var pos = -1; + var spans = 0; + while ((pos=res.indexOf("\x1B[")) >= 0) { + var end = res.indexOf("m", pos+2); + if (end>0) { + var control= res.slice(pos+2, end); + res = res.slice(0, pos)+res.slice(end+1); + control.split(';').forEach(function(c) { + switch (parseInt(c)) { + // set + case 1: res = strInsert(res, pos, ''); ++spans; break; + case 2: res = strInsert(res, pos, ''); ++spans; break; + case 4: res = strInsert(res, pos, ''); ++spans; break; + case 5: res = strInsert(res, pos, ''); ++spans; break; + case 7: res = strInsert(res, pos, ''); ++spans; break; + case 8: res = strInsert(res, pos, '"); break; + case 21: if (spans) --spans; res = strInsert(res, pos, ""); break; + case 22: if (spans) --spans; res = strInsert(res, pos, ""); break; + case 23: if (spans) --spans; res = strInsert(res, pos, ""); break; + case 25: if (spans) --spans; res = strInsert(res, pos, ""); break; + case 27: if (spans) --spans; res = strInsert(res, pos, ""); break; + case 28: if (spans) --spans; res = strInsert(res, pos, ""); break; + // fg colors + case 39: res = strInsert(res, pos, ''); ++spans; break; + case 30: res = strInsert(res, pos, ''); ++spans; break; + case 31: res = strInsert(res, pos, ''); ++spans; break; + case 32: res = strInsert(res, pos, ''); ++spans; break; + case 33: res = strInsert(res, pos, ''); ++spans; break; + case 34: res = strInsert(res, pos, ''); ++spans; break; + case 35: res = strInsert(res, pos, ''); ++spans; break; + case 36: res = strInsert(res, pos, ''); ++spans; break; + case 37: res = strInsert(res, pos, ''); ++spans; break; + case 90: res = strInsert(res, pos, ''); ++spans; break; + case 91: res = strInsert(res, pos, ''); ++spans; break; + case 92: res = strInsert(res, pos, ''); ++spans; break; + case 93: res = strInsert(res, pos, ''); ++spans; break; + case 94: res = strInsert(res, pos, ''); ++spans; break; + case 95: res = strInsert(res, pos, ''); ++spans; break; + case 96: res = strInsert(res, pos, ''); ++spans; break; + case 97: res = strInsert(res, pos, ''); ++spans; break; + // bg colors + case 49: res = strInsert(res, pos, ''); ++spans; break; + case 40: res = strInsert(res, pos, ''); ++spans; break; + case 41: res = strInsert(res, pos, ''); ++spans; break; + case 42: res = strInsert(res, pos, ''); ++spans; break; + case 43: res = strInsert(res, pos, ''); ++spans; break; + case 44: res = strInsert(res, pos, ''); ++spans; break; + case 45: res = strInsert(res, pos, ''); ++spans; break; + case 46: res = strInsert(res, pos, ''); ++spans; break; + case 47: res = strInsert(res, pos, ''); ++spans; break; + case 100: res = strInsert(res, pos, ''); ++spans; break; + case 101: res = strInsert(res, pos, ''); ++spans; break; + case 102: res = strInsert(res, pos, ''); ++spans; break; + case 103: res = strInsert(res, pos, ''); ++spans; break; + case 104: res = strInsert(res, pos, ''); ++spans; break; + case 105: res = strInsert(res, pos, ''); ++spans; break; + case 106: res = strInsert(res, pos, ''); ++spans; break; + case 107: res = strInsert(res, pos, ''); ++spans; break; + + } }); - status("

Exception Caught:

"+e+"

"+res.join("\n")+"
"); + } else { + break; } - }}).fail(function() { - error("offline"); - }); + } + for (;spans;--spans) res += "
"; + console.log(res); + return res.replace(/\r\r\n/g, '\n'); } -function containers(c) { - console.log("->rcv containers"); - dc.setContainers(c); - if (focused && dc.exists(focused)) - details(focused); - else - overview(); +function bash_data(data) { + console.log("->rcv bash-data("+data.name+")", data); + $("#main").hide(); + $("#logs").hide(); + $("#console").show(); + if (data.type=='done') { + $("#screen").append('\nDONE'); + } else { + $("#screen").append(''+ansifilter(htmlenc(data.text))+''); + } } function overview() { @@ -577,7 +623,9 @@ function init() { .on("error", disconnected); socket .on("fail", error) - .on("containers", containers); + .on("containers", containers) + .on("logs", logs) + .on("bash-data", bash_data); start(); } diff --git a/nodejs/public/stylesheets/servicedock.css b/nodejs/public/stylesheets/servicedock.css index d126f0e..2aff43a 100644 --- a/nodejs/public/stylesheets/servicedock.css +++ b/nodejs/public/stylesheets/servicedock.css @@ -3,6 +3,10 @@ padding: 0; } +body { + background-color: blue; +} + @media (min-resolution: 120dpi) { html { font-size: 120%; @@ -228,18 +232,22 @@ table.docker li+li { padding-top: 0.5em; } -#main { +#main, #logs, #console { position: fixed; - top: 2em; + top: 1.5em; left: 0; right: 0; - bottom: 2em; - padding: 0em 1em 0em 1em; + bottom: 1.5em; + padding: 1em 1em 1em 1em; clear: both; overflow: auto; z-index: 0; } +#main { + background-color: white; +} + #popup { position: fixed; background-color: lightblue; @@ -308,3 +316,62 @@ table.docker li+li { #preview img { height: 4em; } + +#logs { + background-color: lightgray; +} +#logs .stdout { + color: black; +} +.stderr { + color: red; +} +.done { + color: green; +} + +#console { + background-color: black; + color: white +} +.bold {font-weight: bold} +.dim {color: grey} +.underline {text-decoration: underline} +.blink {animation: blinker 1s linear infinite} +@keyframes blinker {50% { opacity: 0.0;}} +.reverse {color: black; background-color: white} +.hidden {color: black} +.fgdefault {color: white} +.fgblack {color: black} +.fgred {color: red} +.fggreen {color: green} +.fgyellow {color: yellow} +.fgblue {color: blue} +.fgmagenta {color: magenta} +.fgcyan {color: cyan} +.fglightgrey {color: lightgrey} +.fgdarkgrey {color: darkgrey} +.fglightred {color: lightred} +.fglightgreen {color: lightgreen} +.fglightyellow {color: lightyellow} +.fglightblue {color: lightblue} +.fglightmagenta {color: lightmagenta} +.fglightcyan {color: lightcyan} +.fgwhite {color: white} +.bgdefault {background-color: black} +.bgblack {background-color: black} +.bgred {background-color: red} +.bggreen {background-color: green} +.bgyellow {background-color: yellow} +.bgblue {background-color: blue} +.bgmagenta {background-color: magenta} +.bgcyan {background-color: cyan} +.bglightgrey {background-color: lightgrey} +.bgdarkgrey {background-color: darkgrey} +.bglightred {background-color: lightred} +.bglightgreen {background-color: lightgreen} +.bglightyellow {background-color: lightyellow} +.bglightblue {background-color: lightblue} +.bglightmagenta {background-color: lightmagenta} +.bglightcyan {background-color: lightcyan} +.bgwhite {background-color: white} diff --git a/nodejs/sockets/index.js b/nodejs/sockets/index.js index a5670e3..b6c4c19 100644 --- a/nodejs/sockets/index.js +++ b/nodejs/sockets/index.js @@ -5,6 +5,7 @@ module.exports = function() { module.connection = function(socket) { //var sys = require('sys'); + var pty = require('pty.js'); var proc = require('child_process'); console.log("new client"); @@ -60,9 +61,7 @@ module.exports = function() { function modify(cmd, name) { if (!name.match(/^[a-z0-9][-_:.+a-z0-9]*$/i)) - return fail("illegal instance name", { - error: error, stderr: stderr, stdout: stdout - }); + return fail("illegal instance name"); exec("docker "+cmd+" "+name, updatecontainers); } @@ -96,14 +95,62 @@ module.exports = function() { modify("rm", name); } + function logs(name) { + console.log("-> logs("+name+")"); + var l = proc.spawn("docker", ["logs", "-f", name]) + .on('close', function(code) { + emit('logs', {name: name, type: 'done'}); + }); + l.stdout.on('data', function(data) { + emit('logs', {name: name, type: 'stdout', text: data.toString()}); + }); + l.stderr.on('data', function(data) { + emit('logs', {name: name, type: 'stderr', text: data.toString()}); + }); + } + + var bash_connections = {}; + + function bash_start(name) { + console.log("-> bash-start("+name+")"); + if (!name.match(/^[a-z0-9][-_:.+a-z0-9]*$/i)) + return fail("illegal instance name"); + if (bash_connections[name]) return fail("bash already open"); + bash_connections[name] = + pty.spawn("docker", ["exec", "-it", name, "bash", "-i"]); + bash_connections[name].stdout.on('data', function(data) { + emit('bash-data', {name: name, type: 'stdout', text: data.toString()}); + }); + // bash_connections[name].stderr.on('data', function(data) { + // emit('bash-data', {name: name, type: 'stdout', text: data.toString()}); + // }); + } + + function bash_input(data) { + console.log("-> bash-input("+data.name+", "+data.text+")"); + if (!bash_connections[data.name]) return fail("bash not open"); + bash_connections[data.name].stdin.resume(); + bash_connections[data.name].stdin.write(data.text); + } + + // function bash_end(name, text) { + // console.log("-> bash-end("+name+")"); + // if (!bash_connections[name]) return fail("bash not open"); + // bash_connections[name].stdin.close(); + // } + socket .on("containers", containers) .on("start", start) .on("stop", stop) .on("pause", pause) .on("unpause", unpause) - .on("remove", remove); - + .on("remove", remove) + .on('logs', logs) + .on('bash-start', bash_start) + .on('bash-input', bash_input); + //.on('bash-end', bash_end); + } return module; diff --git a/nodejs/views/index.ejs b/nodejs/views/index.ejs index 5934c35..7e38332 100644 --- a/nodejs/views/index.ejs +++ b/nodejs/views/index.ejs @@ -44,6 +44,16 @@
+ + + +