new features log and bash

single-host
Marc Wäckerlin 9 years ago
parent 3945afca4c
commit 6a5dad3c93
  1. 9
      nodejs/package.json.in
  2. 228
      nodejs/public/javascripts/servicedock.js
  3. 75
      nodejs/public/stylesheets/servicedock.css
  4. 55
      nodejs/sockets/index.js
  5. 10
      nodejs/views/index.ejs

@ -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",

@ -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('<button id="popup1">'+n.status.action1+'</button>');
$("#popup1").click(function() {
socket.emit(n.status.action1, name);
emit(n.status.action1, name);
});
}
$("#popup").append('<button id="popup2">'+(focused?"overview":"focus")+'</button>');
@ -50,11 +50,27 @@ function DockerContainers() {
if (n.status.action2) {
$("#popup").append('<button id="popup3">'+n.status.action2+'</button>');
$("#popup3").click(function() {
socket.emit(n.status.action2, name);
emit(n.status.action2, name);
});
}
$("#popup").append('<br/>');
$("#popup").append('<button id="popup4">download</button>');
$("#popup").append('<button id="popup4">logs</button>');
$("#popup4").click(function() {
emit("logs", name);
});
if (n.status.bash) {
$("#popup").append('<button id="popup5">bash</button>');
$("#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('<button id="popup6">download</button>');
$("#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 = `
<div id="tabs">
<ul>
<li><a href="#tabs-1">Overview</a></li>
<li><a href="#tabs-2">Logs</a></li>
<li><a href="#tabs-3">Dump</a></li>
</ul>
<div id="tabs-1">
<table class="details docker">
<thead>
<tr>
<th>Name</th>
<th>Ports</th>
<th>Volumes</th>
<th>Links</th>
<th>Environments</th>
<th>Image</th>
<th>Command</th>
</tr>
</thead>
<tbody>
`;
var n = nodes[name];
res += `
</tbody>
</table>
</div>
<div id="tabs-2">
</div>
<div id="tabs-3">
<pre>`;
res += JSON.stringify(containers[nodes[name].id], null, 4);
res += `
</pre>
</div>
</div>
<script>
$(function() {$("#tabs").tabs();});
</script>
`;
return res;
}
function getIps(n, ips) {
n.ports.forEach(function(p) {
if (!ips[p.ip]) ips[p.ip] = [];
@ -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 $('<div/>').text(html).html();
}
function htmldec(data) {
return $('<div/>').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('<span class="'+data.type+'">\nDONE</span>');
} else {
$("#logs").append('<span class="'+data.type+'">'+htmlenc(data.text)+'</span>');
}
}
/** 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, '<span class="bold">'); ++spans; break;
case 2: res = strInsert(res, pos, '<span class="dim">'); ++spans; break;
case 4: res = strInsert(res, pos, '<span class="underlined">'); ++spans; break;
case 5: res = strInsert(res, pos, '<span class="blink">'); ++spans; break;
case 7: res = strInsert(res, pos, '<span class="reverse">'); ++spans; break;
case 8: res = strInsert(res, pos, '<span class="hidden">'); ++spans; break;
// reset
case 0: for (;spans;--spans) res = strInsert(res, pos, "</span>"); break;
case 21: if (spans) --spans; res = strInsert(res, pos, "</span>"); break;
case 22: if (spans) --spans; res = strInsert(res, pos, "</span>"); break;
case 23: if (spans) --spans; res = strInsert(res, pos, "</span>"); break;
case 25: if (spans) --spans; res = strInsert(res, pos, "</span>"); break;
case 27: if (spans) --spans; res = strInsert(res, pos, "</span>"); break;
case 28: if (spans) --spans; res = strInsert(res, pos, "</span>"); break;
// fg colors
case 39: res = strInsert(res, pos, '<span class="fgdefault">'); ++spans; break;
case 30: res = strInsert(res, pos, '<span class="fgblack">'); ++spans; break;
case 31: res = strInsert(res, pos, '<span class="fgred">'); ++spans; break;
case 32: res = strInsert(res, pos, '<span class="fggreen">'); ++spans; break;
case 33: res = strInsert(res, pos, '<span class="fgyellow">'); ++spans; break;
case 34: res = strInsert(res, pos, '<span class="fgblue">'); ++spans; break;
case 35: res = strInsert(res, pos, '<span class="fgmagenta">'); ++spans; break;
case 36: res = strInsert(res, pos, '<span class="fgcyan">'); ++spans; break;
case 37: res = strInsert(res, pos, '<span class="fglightgrey">'); ++spans; break;
case 90: res = strInsert(res, pos, '<span class="fgdarkgrey">'); ++spans; break;
case 91: res = strInsert(res, pos, '<span class="fglightred">'); ++spans; break;
case 92: res = strInsert(res, pos, '<span class="fglightgreen">'); ++spans; break;
case 93: res = strInsert(res, pos, '<span class="fglightyellow">'); ++spans; break;
case 94: res = strInsert(res, pos, '<span class="fglightblue">'); ++spans; break;
case 95: res = strInsert(res, pos, '<span class="fglightmagenta">'); ++spans; break;
case 96: res = strInsert(res, pos, '<span class="fglightcyan">'); ++spans; break;
case 97: res = strInsert(res, pos, '<span class="fgwhite">'); ++spans; break;
// bg colors
case 49: res = strInsert(res, pos, '<span class="bgdefault">'); ++spans; break;
case 40: res = strInsert(res, pos, '<span class="bgblack">'); ++spans; break;
case 41: res = strInsert(res, pos, '<span class="bgred">'); ++spans; break;
case 42: res = strInsert(res, pos, '<span class="bggreen">'); ++spans; break;
case 43: res = strInsert(res, pos, '<span class="bgyellow">'); ++spans; break;
case 44: res = strInsert(res, pos, '<span class="bgblue">'); ++spans; break;
case 45: res = strInsert(res, pos, '<span class="bgmagenta">'); ++spans; break;
case 46: res = strInsert(res, pos, '<span class="bgcyan">'); ++spans; break;
case 47: res = strInsert(res, pos, '<span class="bglightgrey">'); ++spans; break;
case 100: res = strInsert(res, pos, '<span class="bgdarkgrey">'); ++spans; break;
case 101: res = strInsert(res, pos, '<span class="bglightred">'); ++spans; break;
case 102: res = strInsert(res, pos, '<span class="bglightgreen">'); ++spans; break;
case 103: res = strInsert(res, pos, '<span class="bglightyellow">'); ++spans; break;
case 104: res = strInsert(res, pos, '<span class="bglightblue">'); ++spans; break;
case 105: res = strInsert(res, pos, '<span class="bglightmagenta">'); ++spans; break;
case 106: res = strInsert(res, pos, '<span class="bglightcyan">'); ++spans; break;
case 107: res = strInsert(res, pos, '<span class="bgwhite">'); ++spans; break;
}
});
status("<h2>Exception Caught:</h2><p>"+e+"<p><pre>"+res.join("\n")+"</pre>");
} else {
break;
}
}}).fail(function() {
error("offline");
});
}
for (;spans;--spans) res += "</span>";
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('<span class="'+data.type+'">\nDONE</span>');
} else {
$("#screen").append('<span class="'+data.type+'">'+ansifilter(htmlenc(data.text))+'</span>');
}
}
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();
}

@ -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}

@ -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,13 +95,61 @@ 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);
}

@ -44,6 +44,16 @@
</div>
<pre id="logs" style="display: none"> </pre>
<div id="console" style="display: none">
<pre id="screen"></pre>
<form id="bash">
<input placeholder="command" type="text" id="command">
<input type="submit">
</form>
</div>
<div id="status">
<noscript>JavaScript is required for the interface.</noscript>

Loading…
Cancel
Save