|
|
|
/*! @file
|
|
|
|
|
|
|
|
@id $Id$
|
|
|
|
|
|
|
|
This is the main application as it is fully run in the user's browser.
|
|
|
|
|
|
|
|
*/
|
|
|
|
// 1 2 3 4 5 6 7 8
|
|
|
|
// 45678901234567890123456789012345678901234567890123456789012345678901234567890
|
|
|
|
|
|
|
|
var socket = io.connect();
|
|
|
|
var focused = null;
|
|
|
|
|
|
|
|
function Docker() {
|
|
|
|
|
|
|
|
function same(array1, array2) {
|
|
|
|
if (!array1 && !array2) return true;
|
|
|
|
if (!array1 || !array2) return false;
|
|
|
|
return (array1.length == array2.length)
|
|
|
|
&& array1.every(function(element, index) {
|
|
|
|
return element === array2[index];
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
function quote(text) {
|
|
|
|
if (text.match('"')) {
|
|
|
|
if (!text.match("'")) return "'"+text+"'";
|
|
|
|
else return '"'+text.replace(/"/g, '\\"')+'"';
|
|
|
|
} else {
|
|
|
|
if (text.match(' ')) return '"'+text+'"';
|
|
|
|
else return text;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
var _docker = this;
|
|
|
|
|
|
|
|
this.Images = function() {
|
|
|
|
|
|
|
|
var _images = this;
|
|
|
|
var images = [];
|
|
|
|
var nodes = [];
|
|
|
|
|
|
|
|
function setup() {
|
|
|
|
delete nodes; nodes = [];
|
|
|
|
images.forEach(function(c, i) {
|
|
|
|
if (!nodes[c.Id]) nodes[c.Id] = {};
|
|
|
|
nodes[c.Id].id = c.Id;
|
|
|
|
nodes[c.Id].tags = c.RepoTags;
|
|
|
|
nodes[c.Id].created = c.Created;
|
|
|
|
nodes[c.Id].author = c.Author;
|
|
|
|
nodes[c.Id].os = c.Os+"/"+c.Architecture;
|
|
|
|
nodes[c.Id].parent = c.Parent;
|
|
|
|
nodes[c.Id].env = c.Config.Env;
|
|
|
|
nodes[c.Id].cmd = c.Config.Cmd;
|
|
|
|
nodes[c.Id].entrypoint = c.Config.Entrypoint;
|
|
|
|
nodes[c.Id].ports = c.Config.ExposedPorts;
|
|
|
|
nodes[c.Id].volumes = c.Config.Volumes;
|
|
|
|
if (c.Parent) {
|
|
|
|
if (!nodes[c.Parent]) nodes[c.Parent] = {};
|
|
|
|
if (!nodes[c.Parent].children) nodes[c.Parent].children = [];
|
|
|
|
nodes[c.Parent].children.push(c.Id);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
this.cleanup = function(id, instance) {
|
|
|
|
if (!nodes[id]) return
|
|
|
|
nodes[id].env.forEach(function(e) {
|
|
|
|
if ((pos=instance.env.indexOf(e))>-1) instance.env.splice(pos, 1)
|
|
|
|
})
|
|
|
|
if (same(nodes[id].cmd, instance.cmd)) instance.cmd = null
|
|
|
|
if (same(nodes[id].entrypoint, instance.entrypoint)) instance.entrypoint = null
|
|
|
|
}
|
|
|
|
this.set = function(c) {
|
|
|
|
if (typeof c == "string") c = JSON.parse(c);
|
|
|
|
if (typeof c != "object") throw "wrong format: "+(typeof c);
|
|
|
|
images = c;
|
|
|
|
setup();
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
this.Containers = function() {
|
|
|
|
|
|
|
|
var _containers = this;
|
|
|
|
|
|
|
|
var Status = Object.freeze({
|
|
|
|
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 = [];
|
|
|
|
function protocol(port) {
|
|
|
|
if (port.toString().match("443")) return "https://";
|
|
|
|
if (port.toString().match("3304")) return "mysql://";
|
|
|
|
if (port.toString().match("22")) return "ssh://";
|
|
|
|
return "http://";
|
|
|
|
}
|
|
|
|
this.exists = function(name) {
|
|
|
|
if (nodes[name]) return true;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
function getIps(n, ips) {
|
|
|
|
n.ports.forEach(function(p) {
|
|
|
|
if (!ips[p.ip]) ips[p.ip] = [];
|
|
|
|
ips[p.ip].push(p);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
function graphIpClusters(ips) {
|
|
|
|
var res = "newrank=true;\n";
|
|
|
|
var i = 0;
|
|
|
|
for (ip in ips) {
|
|
|
|
res += "subgraph clusterIp"+(++i)+' {\nlabel="'+ip+'";\n';
|
|
|
|
ips[ip].forEach(function(p) {
|
|
|
|
res += '"'+p.ip+":"+p.external
|
|
|
|
+'" [label="'+p.external+'",URL="'
|
|
|
|
+protocol(p.internal)+p.ip+':'+p.external+'",shape=box];\n';
|
|
|
|
});
|
|
|
|
res+="}\n";
|
|
|
|
}
|
|
|
|
res += "{rank=same;\n";
|
|
|
|
for (ip in ips) {
|
|
|
|
ips[ip].forEach(function(p) {
|
|
|
|
res += '"'+p.ip+":"+p.external+'";\n';
|
|
|
|
});
|
|
|
|
}
|
|
|
|
res+="}\n";
|
|
|
|
return res;
|
|
|
|
}
|
|
|
|
function graphNode(n) {
|
|
|
|
var res = "";
|
|
|
|
var label = n.name+'\\n'+n.image.name;
|
|
|
|
res += '"'+n.name+'"'
|
|
|
|
+' [label="'+label
|
|
|
|
+'",URL="#'+n.name
|
|
|
|
+'",style=filled,fillcolor='+n.status.color+"];\n";
|
|
|
|
n.ports.forEach(function(p) {
|
|
|
|
res += '"'+(p.ip?p.ip+":":"")+p.external+'" -> "'+n.name
|
|
|
|
+'" [label="'+p.internal+'"];\n';
|
|
|
|
});
|
|
|
|
n.links.forEach(function(l) {
|
|
|
|
res += '"'+n.name+'" -> "'+l.to+'" [label="link: '+l.link+'"];\n'
|
|
|
|
});
|
|
|
|
return res;
|
|
|
|
}
|
|
|
|
function graphVolumesInside(n) {
|
|
|
|
var res = "";
|
|
|
|
n.volumes.forEach(function(v) {
|
|
|
|
res += '"'+v.id+'" [label="'+v.inside+'",shape=box];\n';
|
|
|
|
});
|
|
|
|
return res;
|
|
|
|
}
|
|
|
|
function graphVolumesOutside(n) {
|
|
|
|
var res = "";
|
|
|
|
n.volumes.forEach(function(v) {
|
|
|
|
if (v.host)
|
|
|
|
res += '"'+v.outside+'" [label="'+v.host+'",shape=box];\n';
|
|
|
|
});
|
|
|
|
return res;
|
|
|
|
}
|
|
|
|
function graphVolumesConnections(n) {
|
|
|
|
var res = "";
|
|
|
|
n.volumes.forEach(function(v) {
|
|
|
|
if (v.host)
|
|
|
|
res += '"'+v.id+'" -> "'+v.outside+'" [label="mounted from"]\n';
|
|
|
|
res += '"'+n.name+'" -> "'+v.id+'" [label="volume/'+v.rw+'"]\n';
|
|
|
|
});
|
|
|
|
n.volumesfrom.forEach(function(o) {
|
|
|
|
res += '"'+n.name+'" -> "'+nodes[o].name+'" [label="volumes from"]\n';
|
|
|
|
});
|
|
|
|
return res;
|
|
|
|
}
|
|
|
|
this.graph = function(n) {
|
|
|
|
var res = "";
|
|
|
|
var ips = [];
|
|
|
|
n = n || nodes;
|
|
|
|
for (name in n) getIps(n[name], ips);
|
|
|
|
res += graphIpClusters(ips);
|
|
|
|
for (name in n) res += graphNode(n[name]);
|
|
|
|
res += "{rank=same;\n";
|
|
|
|
for (name in n) res += graphVolumesInside(n[name]);
|
|
|
|
res+="}\n";
|
|
|
|
res += "{rank=same;\n";
|
|
|
|
for (name in n) res += graphVolumesOutside(n[name]);
|
|
|
|
res+="}\n";
|
|
|
|
for (name in n) res += graphVolumesConnections(n[name]);
|
|
|
|
return res;
|
|
|
|
}
|
|
|
|
function addNodes(ns, name) {
|
|
|
|
var n = nodes[name];
|
|
|
|
ns[name] = n;
|
|
|
|
n.links.forEach(function(peer) {
|
|
|
|
if (!ns[peer.to]) addNodes(ns, peer.to);
|
|
|
|
});
|
|
|
|
n.usedby.forEach(function(peer) {
|
|
|
|
if (!ns[peer]) addNodes(ns, peer);
|
|
|
|
});
|
|
|
|
n.volumesfrom.forEach(function(peer) {
|
|
|
|
if (!ns[peer]) addNodes(ns, peer);
|
|
|
|
});
|
|
|
|
n.volumesto.forEach(function(peer) {
|
|
|
|
if (!ns[peer]) addNodes(ns, peer);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
this.subnet = function(name) {
|
|
|
|
var ns = {};
|
|
|
|
addNodes(ns, name);
|
|
|
|
return ns;
|
|
|
|
}
|
|
|
|
this.subgraph = function(name) {
|
|
|
|
return this.graph(this.subnet(name));
|
|
|
|
}
|
|
|
|
this.configuration = function(name) {
|
|
|
|
var ns = this.subnet(name);
|
|
|
|
var creates = [];
|
|
|
|
for (n in ns) {
|
|
|
|
var instance = {
|
|
|
|
name: ns[n].name,
|
|
|
|
image: ns[n].image.name,
|
|
|
|
ports: ns[n].ports,
|
|
|
|
env: ns[n].env,
|
|
|
|
cmd: ns[n].cmd,
|
|
|
|
entrypoint: ns[n].entrypoint,
|
|
|
|
volumesfrom: ns[n].volumesfrom,
|
|
|
|
links: ns[n].links,
|
|
|
|
volumes: []
|
|
|
|
};
|
|
|
|
ns[n].volumes.forEach(function(v) {
|
|
|
|
if (v.host) instance.volumes.push({
|
|
|
|
inside: v.inside,
|
|
|
|
outside: v.host
|
|
|
|
});
|
|
|
|
});
|
|
|
|
_docker.images.cleanup(ns[n].image.id, instance);
|
|
|
|
creates.push(instance);
|
|
|
|
}
|
|
|
|
creates.sort(function(a, b) {
|
|
|
|
if (a.volumesfrom.indexOf(b.name)>=0) return 1; // a after b
|
|
|
|
if (b.volumesfrom.indexOf(a.name)>=0) return -1; // a before b
|
|
|
|
for (var i=0; i<a.links.length; ++i) if (a.links[i].to == b.name) return 1; // a after b;
|
|
|
|
for (var i=0; i<b.links.length; ++i) if (b.links[i].to == a.name) return -1; // a before b;
|
|
|
|
if ((b.volumesfrom.length || b.links.length) && !(a.volumesfrom.length || a.links.length)) return 1; // a after b;
|
|
|
|
if ((a.volumesfrom.length || a.links.length) && !(b.volumesfrom.length || b.links.length)) return 1; // a after b;
|
|
|
|
return 0; // a and b do not depend on each other
|
|
|
|
});
|
|
|
|
return creates;
|
|
|
|
}
|
|
|
|
this.creation = function(name) {
|
|
|
|
var res = [];
|
|
|
|
this.configuration(name).forEach(function(n) {
|
|
|
|
var cmd = "docker create -d --name "+quote(n.name);
|
|
|
|
n.ports.forEach(function(p) {
|
|
|
|
cmd += " --publish "+(p.ip?quote(p.ip)+":":"")+p.external+":"+p.internal;
|
|
|
|
});
|
|
|
|
n.env.forEach(function(e) {
|
|
|
|
cmd += ' --env '+quote(e);
|
|
|
|
});
|
|
|
|
n.volumes.forEach(function(v) {
|
|
|
|
cmd += ' --volume '+quote(v.outside)+':'+quote(v.inside);
|
|
|
|
});
|
|
|
|
n.volumesfrom.forEach(function(v) {
|
|
|
|
cmd += ' --volumes-from '+quote(v);
|
|
|
|
})
|
|
|
|
n.links.forEach(function(l) {
|
|
|
|
cmd += ' --link '+quote(l.link)+':'+quote(l.to);
|
|
|
|
});
|
|
|
|
if (n.entrypoint && n.entrypoint.length) cmd += ' --entrypoint '+quote(n.entrypoint.join(" "));
|
|
|
|
cmd += " "+n.image;
|
|
|
|
if (n.cmd) n.cmd.forEach(function(c) {
|
|
|
|
cmd+= ' '+quote(c);
|
|
|
|
});
|
|
|
|
res.push(cmd);
|
|
|
|
})
|
|
|
|
return res;
|
|
|
|
}
|
|
|
|
function setup() {
|
|
|
|
delete nodes; nodes = [];
|
|
|
|
containers.forEach(function(c, i) {
|
|
|
|
var name = c.Name.replace(/^\//, "");
|
|
|
|
if (!nodes[name]) nodes[name] = {};
|
|
|
|
nodes[name].id = c.Id;
|
|
|
|
nodes[name].name = name;
|
|
|
|
nodes[name].image = {
|
|
|
|
name: c.Config.Image,
|
|
|
|
id: c.Image
|
|
|
|
};
|
|
|
|
nodes[name].env = c.Config.Env;
|
|
|
|
nodes[name].cmd = c.Config.Cmd;
|
|
|
|
nodes[name].entrypoint = c.Entrypoint;
|
|
|
|
nodes[name].ports = [];
|
|
|
|
var ports = c.NetworkSettings.Ports || c.NetworkSettings.PortBindings;
|
|
|
|
if (ports)
|
|
|
|
for (var port in ports)
|
|
|
|
if (ports[port])
|
|
|
|
for (var expose in ports[port]) {
|
|
|
|
var ip = ports[port][expose].HostIp;
|
|
|
|
if (!ip||ip==""||ip=="0.0.0.0"||ip==0) ip=window.location.hostname;
|
|
|
|
nodes[name].ports.push({
|
|
|
|
internal: port,
|
|
|
|
external: ports[port][expose].HostPort,
|
|
|
|
ip: ip
|
|
|
|
});
|
|
|
|
}
|
|
|
|
if (c.State.Paused) nodes[name].status = Status.Paused;
|
|
|
|
else if (c.State.Running) nodes[name].status = Status.Running;
|
|
|
|
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;
|
|
|
|
nodes[name].volumes = [];
|
|
|
|
var volumes = c.Volumes || c.Config.Volumes;
|
|
|
|
nodes[name].volumes = [];
|
|
|
|
if (volumes)
|
|
|
|
for (var volume in volumes) {
|
|
|
|
var rw = "rw";
|
|
|
|
var outside = (typeof volumes[volume]=="string")?volumes[volume]:null;
|
|
|
|
if (c.Mounts) c.Mounts.forEach(function(mnt) {
|
|
|
|
if (mnt.Destination==volume) {
|
|
|
|
outside = mnt.Source;
|
|
|
|
rw = mnt.RW ? "rw" : "ro";
|
|
|
|
}
|
|
|
|
});
|
|
|
|
nodes[name].volumes.push({
|
|
|
|
id: volume+':'+(outside?outside:name),
|
|
|
|
rw:rw,
|
|
|
|
inside: volume,
|
|
|
|
outside: outside,
|
|
|
|
host: outside && !outside.match(/^\/var\/lib\/docker/)
|
|
|
|
? outside : null
|
|
|
|
});
|
|
|
|
}
|
|
|
|
nodes[name].volumesfrom = [];
|
|
|
|
if (!nodes[name].volumesto) nodes[name].volumesto = [];
|
|
|
|
if (c.HostConfig.VolumesFrom) c.HostConfig.VolumesFrom.forEach(function(id) {
|
|
|
|
containers.forEach(function(c) {
|
|
|
|
if (c.Id == id || c.Name == "/"+id || c.Name == id) {
|
|
|
|
var src = c.Name.replace(/^\//, "");
|
|
|
|
nodes[name].volumesfrom.push(src);
|
|
|
|
if (!nodes[src]) nodes[src] = {};
|
|
|
|
if (!nodes[src].volumesto) nodes[src].volumesto = [];
|
|
|
|
nodes[src].volumesto.push(name);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
});
|
|
|
|
nodes[name].links = [];
|
|
|
|
if (!nodes[name].usedby) nodes[name].usedby = [];
|
|
|
|
if (c.HostConfig && c.HostConfig.Links)
|
|
|
|
c.HostConfig.Links.forEach(function(l) {
|
|
|
|
var target = {
|
|
|
|
to: l.replace(/^\/?([^:]*).*$/, "$1"),
|
|
|
|
link: l.replace(new RegExp("^.*:/?"+name+"/"), "")
|
|
|
|
};
|
|
|
|
nodes[name].links.push(target);
|
|
|
|
if (!nodes[target.to]) nodes[target.to] = {};
|
|
|
|
if (!nodes[target.to].usedby) nodes[target.to].usedby = [];
|
|
|
|
nodes[target.to].usedby.push(name);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
for (name in nodes) { // cleanup duplicate links to volumes when using volumes-from
|
|
|
|
var n = nodes[name];
|
|
|
|
n.volumesfrom.forEach(function(other) {
|
|
|
|
var o = nodes[other];
|
|
|
|
o.volumes.forEach(function(ovol) {
|
|
|
|
n.volumes.reduceRight(function(x, nvol, i, arr) {
|
|
|
|
if (nvol.id == ovol.id)
|
|
|
|
arr.splice(i, 1);
|
|
|
|
}, [])
|
|
|
|
})
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
this.contextmenu = function(selector) {
|
|
|
|
$('a[xlink\\:href^=#]').click(function(e) {
|
|
|
|
name = $(this).attr("xlink:href").replace(/^#/, "");
|
|
|
|
var n = nodes[name];
|
|
|
|
$(selector).prepend('<div id="popup"></div>')
|
|
|
|
$("#popup").empty();
|
|
|
|
if (n.status.action1) {
|
|
|
|
$("#popup").append('<button id="popup1">'+n.status.action1+'</button>');
|
|
|
|
$("#popup1").click(function() {
|
|
|
|
emit(n.status.action1, name);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
$("#popup").append('<button id="popup2">'+(focused?"overview":"focus")+'</button>');
|
|
|
|
$("#popup2").click(function() {
|
|
|
|
if (focused) overview(); else details(name);
|
|
|
|
});
|
|
|
|
if (n.status.action2) {
|
|
|
|
$("#popup").append('<button id="popup3">'+n.status.action2+'</button>');
|
|
|
|
$("#popup3").click(function() {
|
|
|
|
emit(n.status.action2, name);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
$("#popup").append('<br/>');
|
|
|
|
$("#popup").append('<button id="popup4">logs</button>');
|
|
|
|
$("#popup4").click(function() {
|
|
|
|
showLogs();
|
|
|
|
emit("logs", name);
|
|
|
|
});
|
|
|
|
if (n.status.bash) {
|
|
|
|
$("#popup").append('<button id="popup5">bash</button>');
|
|
|
|
$("#popup5").click(function() {
|
|
|
|
showConsole();
|
|
|
|
emit("bash-start", name);
|
|
|
|
$("#bash").submit(function() {
|
|
|
|
emit("bash-input", {name: name, text: $("#command").val()+"\n"});
|
|
|
|
$("#command").val("");
|
|
|
|
})
|
|
|
|
});
|
|
|
|
}
|
|
|
|
$("#popup").append('<button id="popup6">download</button>');
|
|
|
|
$("#popup6").click(function() {
|
|
|
|
var download = document.createElement('a');
|
|
|
|
download.href = 'data:application/json,'
|
|
|
|
+ encodeURI(JSON.stringify(_containers.creation(name), null, 2));
|
|
|
|
download.target = '_blank';
|
|
|
|
download.download = name+'.json';
|
|
|
|
var clickEvent = new MouseEvent("click", {
|
|
|
|
"view": window,
|
|
|
|
"bubbles": true,
|
|
|
|
"cancelable": false
|
|
|
|
});
|
|
|
|
download.dispatchEvent(clickEvent);
|
|
|
|
});
|
|
|
|
$("#popup").css("position", "fixed");
|
|
|
|
$("#popup").css("top", e.pageY-$("#popup").height()/4);
|
|
|
|
$("#popup").css("left", e.pageX-$("#popup").width()/2);
|
|
|
|
$("#popup").mouseleave(function() {
|
|
|
|
$("#popup").hide();
|
|
|
|
}).click(function() {
|
|
|
|
$("#popup").hide();
|
|
|
|
});
|
|
|
|
$("#popup").show();
|
|
|
|
})
|
|
|
|
}
|
|
|
|
this.set = function(c) {
|
|
|
|
if (typeof c == "string") c = JSON.parse(c);
|
|
|
|
if (typeof c != "object") throw "wrong format: "+(typeof c);
|
|
|
|
containers = c;
|
|
|
|
setup();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
this.images = new this.Images();
|
|
|
|
this.containers = new this.Containers();
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
var docker = new Docker();
|
|
|
|
|
|
|
|
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.
|
|
|
|
Strings are shown to the user, structures are logged only.
|
|
|
|
@param stay (optional) If not given as @c true, reloads page after 5s. */
|
|
|
|
function error(data) {
|
|
|
|
$("#status").fadeOut("slow", function() {
|
|
|
|
$("#status").addClass("error")
|
|
|
|
$("#status").removeClass("notice")
|
|
|
|
$("#status").removeClass("success")
|
|
|
|
if (data) {
|
|
|
|
if (typeof data == 'string') {
|
|
|
|
$("#status").html(data);
|
|
|
|
console.log("error: "+data);
|
|
|
|
} else {
|
|
|
|
$("#status").html('unknown error: '+JSON.stringify(data));
|
|
|
|
console.log("error: ", data);
|
|
|
|
console.log((new Error('stacktrace')));
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
$("#status").html('error');
|
|
|
|
console.log("error");
|
|
|
|
}
|
|
|
|
$("#status").fadeIn("slow");
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Show notice messsage
|
|
|
|
/** Fades in an notice message and logs to console.
|
|
|
|
@param text (optional) The data is a string. */
|
|
|
|
function notice(text) {
|
|
|
|
$("#status").fadeOut("slow", function() {
|
|
|
|
$("#status").addClass("notice")
|
|
|
|
$("#status").removeClass("error")
|
|
|
|
$("#status").removeClass("success")
|
|
|
|
if (text) {
|
|
|
|
$("#status").html(text);
|
|
|
|
console.log("notice: "+text);
|
|
|
|
} else {
|
|
|
|
$("#status").html('');
|
|
|
|
console.log("notice");
|
|
|
|
}
|
|
|
|
$("#status").fadeIn("slow");
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Show notice messsage
|
|
|
|
/** Fades in an success message and logs to console.
|
|
|
|
@param text (optional) The data is a string. */
|
|
|
|
function success(text) {
|
|
|
|
$("#status").fadeOut("slow", function() {
|
|
|
|
$("#status").addClass("success")
|
|
|
|
$("#status").removeClass("error")
|
|
|
|
$("#status").removeClass("notice")
|
|
|
|
if (text) {
|
|
|
|
$("#status").html(text);
|
|
|
|
console.log("success: "+text);
|
|
|
|
} else {
|
|
|
|
$("#status").html('');
|
|
|
|
console.log("success");
|
|
|
|
}
|
|
|
|
$("#status").fadeIn("slow");
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Show status message in the main screen area
|
|
|
|
/** @param text Text is a message or some complex HTML from the server.
|
|
|
|
@param msg The success message text */
|
|
|
|
function status(text, msg) {
|
|
|
|
$("#main").hide();
|
|
|
|
$("#main").html(text);
|
|
|
|
$("#popup").hide();
|
|
|
|
if (msg) success(msg);
|
|
|
|
else setTimeout("$('#status').fadeOut('slow')", 5000);
|
|
|
|
zoom(0);
|
|
|
|
$("#main").show();
|
|
|
|
$("form input:first-child").focus();
|
|
|
|
docker.containers.contextmenu("#main");
|
|
|
|
}
|
|
|
|
|
|
|
|
function emit(signal, data) {
|
|
|
|
console.log("<-snd "+signal);
|
|
|
|
socket.emit(signal, data);
|
|
|
|
}
|
|
|
|
|
|
|
|
function connected() {
|
|
|
|
console.log("server connected");
|
|
|
|
$("#connectionstatus #bad").hide();
|
|
|
|
$("#connectionstatus #good").show();
|
|
|
|
success("server connected");
|
|
|
|
}
|
|
|
|
|
|
|
|
function disconnected() {
|
|
|
|
console.log("server disconnected");
|
|
|
|
$("#connectionstatus #good").hide();
|
|
|
|
$("#connectionstatus #bad").show();
|
|
|
|
error("server disconnected", true);
|
|
|
|
}
|
|
|
|
|
|
|
|
function connectionstatus() {
|
|
|
|
if (socket.connected) connected(); else disconnected();
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Toggle Menu Display
|
|
|
|
function togglemenu() {
|
|
|
|
$("#menu").toggle();
|
|
|
|
}
|
|
|
|
|
|
|
|
function about(c) {
|
|
|
|
$("#imagetools").hide();
|
|
|
|
$.ajax({url: "about.php", success: function(res) {
|
|
|
|
try {
|
|
|
|
var a = JSON.parse(res);
|
|
|
|
status("<h2>"+a.description+"</h2>"+
|
|
|
|
"<p>"+a.project+"-"+a.version+"</p>"+
|
|
|
|
"<p>"+a.docker+"</p>"+
|
|
|
|
"<h3>README</h3>"+
|
|
|
|
"<pre>"+a.readme+"</pre>");
|
|
|
|
} catch(e) {
|
|
|
|
status("<pre>"+res+"</pre>");
|
|
|
|
error("Exception Caught: "+e);
|
|
|
|
}
|
|
|
|
}}).fail(function() {
|
|
|
|
error("offline");
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
var zoomlevel = 0;
|
|
|
|
function zoom(incr = 0) {
|
|
|
|
zoomlevel = (zoomlevel+incr)%2;
|
|
|
|
switch (zoomlevel) {
|
|
|
|
case 0: {
|
|
|
|
$("#main svg").css("width", "auto");
|
|
|
|
$("#main svg").css("height", "auto");
|
|
|
|
$("#main svg").css("max-width", "100%");
|
|
|
|
$("#main svg").css("max-height", "100%");
|
|
|
|
} break;
|
|
|
|
case 1: {
|
|
|
|
$("#main svg").css("width", "100%");
|
|
|
|
$("#main svg").css("height", "auto");
|
|
|
|
$("#main svg").css("max-width", "100%");
|
|
|
|
$("#main svg").css("max-height", "none");
|
|
|
|
} break;
|
|
|
|
case 2: {
|
|
|
|
$("#main.svg").css("width", "auto");
|
|
|
|
$("#main.svg").css("height", "100%");
|
|
|
|
$("#main.svg").css("max-width", "none");
|
|
|
|
$("#main.svg").css("max-height", "100%");
|
|
|
|
} break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
var viz = null;
|
|
|
|
var vizmore = null;
|
|
|
|
var rankdir = "LR";
|
|
|
|
function rotateviz() {
|
|
|
|
if (!viz) return;
|
|
|
|
if (rankdir == "LR")
|
|
|
|
rankdir = "TB";
|
|
|
|
else
|
|
|
|
rankdir = "LR";
|
|
|
|
showviz();
|
|
|
|
}
|
|
|
|
function showviz(vizpath, more) {
|
|
|
|
$("#imagetools").show();
|
|
|
|
if (!vizpath) {
|
|
|
|
vizpath = viz;
|
|
|
|
more = vizmore;
|
|
|
|
} else {
|
|
|
|
viz = vizpath;
|
|
|
|
vizmore = more;
|
|
|
|
}
|
|
|
|
res = "digraph {\n"+" rankdir="+rankdir+";\n"+viz+"\n}";
|
|
|
|
try {
|
|
|
|
status(more?Viz(res)+more:Viz(res));
|
|
|
|
} catch(e) {
|
|
|
|
(res = res.split("\n")).forEach(function(v, i, a) {
|
|
|
|
a[i] = ("000"+(i+1)).slice(-3)+": "+v;
|
|
|
|
});
|
|
|
|
status("<h2>Exception Caught:</h2><p>"+e+"<p><pre>"+res.join("\n")+"</pre>");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function details(name) {
|
|
|
|
if (name) focused = name;
|
|
|
|
else if (!focused) return overview();
|
|
|
|
showviz(docker.containers.subgraph(focused));
|
|
|
|
}
|
|
|
|
|
|
|
|
function images(i) {
|
|
|
|
console.log("->rcv images");
|
|
|
|
docker.images.set(i);
|
|
|
|
}
|
|
|
|
|
|
|
|
function containers(c) {
|
|
|
|
console.log("->rcv containers");
|
|
|
|
docker.containers.set(c);
|
|
|
|
showImage();
|
|
|
|
if (focused && docker.containers.exists(focused))
|
|
|
|
details(focused);
|
|
|
|
else
|
|
|
|
overview();
|
|
|
|
}
|
|
|
|
|
|
|
|
function showImage() {
|
|
|
|
$("#close").hide();
|
|
|
|
$("#logs").hide();
|
|
|
|
$("#console").hide();
|
|
|
|
$("#main").show();
|
|
|
|
}
|
|
|
|
|
|
|
|
function showConsole() {
|
|
|
|
$("#main").hide();
|
|
|
|
$("#logs").hide();
|
|
|
|
$("#console").show();
|
|
|
|
$("#close").show();
|
|
|
|
$("#command").focus();
|
|
|
|
$("#command").val("");
|
|
|
|
if ($("#screen").val()!="") $("#screen").append("\n");
|
|
|
|
}
|
|
|
|
|
|
|
|
function showLogs() {
|
|
|
|
$("#main").hide();
|
|
|
|
$("#logs").show();
|
|
|
|
$("#console").hide();
|
|
|
|
$("#close").show();
|
|
|
|
}
|
|
|
|
|
|
|
|
function logs(data) {
|
|
|
|
console.log("->rcv logs("+data.name+")");
|
|
|
|
if (data.type=='done') {
|
|
|
|
$("#logs").append('<span class="'+data.type+'">\nDONE</span>');
|
|
|
|
} else {
|
|
|
|
$("#logs").append('<span class="'+data.type+'">'+htmlenc(data.text)+'</span>');
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
|
|
|
}
|
|
|
|
});
|
|
|
|
} else {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
for (;spans;--spans) res += "</span>";
|
|
|
|
console.log(res);
|
|
|
|
return res.replace(/\r\r\n/g, '\n');
|
|
|
|
}
|
|
|
|
|
|
|
|
function bash_data(data) {
|
|
|
|
console.log("->rcv bash-data("+data.name+")", data);
|
|
|
|
if (data.type=='done') {
|
|
|
|
$("#screen").append('<span class="'+data.type+'">\nDONE</span>');
|
|
|
|
} else {
|
|
|
|
$("#screen").append('<span class="'+data.type+'">'+ansifilter(htmlenc(data.text))+'</span>');
|
|
|
|
}
|
|
|
|
$("#screen").animate({
|
|
|
|
scrollTop: $("#screen").scrollHeight()
|
|
|
|
}, 500);}
|
|
|
|
|
|
|
|
function overview() {
|
|
|
|
focused = null;
|
|
|
|
showviz(docker.containers.graph());
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Initial Function: Startup
|
|
|
|
/** Decide whether to login or to create a new user */
|
|
|
|
function start() {
|
|
|
|
$("#imagetools").hide();
|
|
|
|
$("#menu").hide();
|
|
|
|
$("#username").html(window.location.hostname)
|
|
|
|
try {
|
|
|
|
status("Starting up ...");
|
|
|
|
emit("images");
|
|
|
|
emit("containers");
|
|
|
|
} catch (m) {
|
|
|
|
error(m);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function init() {
|
|
|
|
socket.io
|
|
|
|
.on("connect", connected)
|
|
|
|
.on("reconnect", connected)
|
|
|
|
.on("disconnect", disconnected)
|
|
|
|
.on("error", disconnected);
|
|
|
|
socket
|
|
|
|
.on("fail", error)
|
|
|
|
.on("containers", containers)
|
|
|
|
.on("images", images)
|
|
|
|
.on("logs", logs)
|
|
|
|
.on("bash-data", bash_data);
|
|
|
|
start();
|
|
|
|
}
|
|
|
|
|
|
|
|
/// On Load, Call @ref start
|
|
|
|
/*
|
|
|
|
$(window.onbeforeunload = function() {
|
|
|
|
return "Are you sure you want to navigate away?";
|
|
|
|
});
|
|
|
|
*/
|
|
|
|
$(init);
|