Servicedock: Webgui for Docker Swarm. Manage Docker Swarm a a Service.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

398 lines
12 KiB

/*! @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();
function DockerContainers() {
var Status = Object.freeze({
Error: "red",
Terminated: "yellow",
Restarting: "lightblue",
Paused: "lightgrey",
Running: "lightgreen"
});
var containers = [];
var nodes = [];
this.graph = function() {
var res = "";
console.log("nodes["+nodes.length+"]=", nodes);
for (name in nodes) {
var n = nodes[name];
var label = n.name+'\\n'+n.image;
res += '"'+n.name+'"'
+' [label="'+label
+'",URL="details('+"'"+n.name+"'"
+')",style=filled,fillcolor='+n.status+"];\n";
}
res += "{rank=same;\n";
for (name in nodes) {
var n = nodes[name];
n.volumes.forEach(function(v) {
res += '"'+v.id+'" [label="'+v.inside+'",shape=box];\n';
});
}
res+="}\n";
res += "{rank=same;\n";
for (name in nodes) {
var n = nodes[name];
n.volumes.forEach(function(v) {
if (v.host)
res += '"'+v.outside+'" [label="'+v.host+'",shape=box];\n';
});
}
res+="}\n";
for (name in nodes) {
var n = nodes[name];
n.volumes.forEach(function(v) {
if (v.host)
res += '"'+v.id+'" -> "'+v.outside+'"\n';
});
}
for (name in nodes) {
var n = nodes[name];
n.volumes.forEach(function(v) {
res += '"'+n.name+'" -> "'+v.id+'"\n';
});
}
return res;
}
function setup() {
delete nodes; nodes=[];
containers.forEach(function(c) {
var name = c.Name.replace(/^\//, "");
nodes[name] = {};
console.log("container: "+name);
nodes[name].name = name;
nodes[name].image = c.Config.Image;
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=="127.0.0.1"||ip=="0.0.0.0") ip=null;
nodes[name].ports.push({
internal: port,
external: ports[port][expose].HostPort,
ip: ip
});
}
if (c.State.Running) nodes[name].status = Status.Running;
else if (c.State.Paused) nodes[name].status = Status.Paused;
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 outside = (typeof volumes[volume]=="string")?volumes[volume]:null;
nodes[name].volumes.push({
id: volume+':'+(outside?outside:name),
inside: volume,
outside: outside,
host: outside && !outside.match(/^\/var\/lib\/docker/)
? volumes[volume] : null
});
}
nodes[name].volumesfrom = c.VolumesFrom;
nodes[name].links = [];
if (c.HostConfig && c.HostConfig.Links)
c.HostConfig.Links.forEach(function(l) {
nodes[name].links.push({
to: l.replace(/^\/?([^:]*).*$/, "$1"),
link: l.replace(new RegExp("^.*:/?"+name+"/"), "")
});
});
console.log(nodes[name]);
});
}
this.setContainers = function(c) {
if (typeof c == "string") c = JSON.parse(c);
if (typeof c != "object") throw "wrong format: "+(typeof c);
containers = c;
setup();
}
}
var dc = new DockerContainers();
/// 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, stay) {
$("#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: "+JSON.stringify(data));
}
} else {
$("#status").html('error');
console.log("error");
}
$("#status").fadeIn("slow");
if (!stay) setTimeout(start, 5000);
});
}
/// 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").fadeOut("slow", function() {
$("#main").html(text);
if (msg) success(msg);
else setTimeout("$('#status').fadeOut('slow')", 5000);
$("#main").fadeIn("slow", function() {
$("form input:first-child").focus();
})
});
}
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 rankdir = "LR";
function rotateviz() {
if (!viz) return;
if (rankdir == "LR")
rankdir = "TB";
else
rankdir = "LR";
showviz(viz);
}
function showviz(vizpath) {
$("#imagetools").show();
viz = vizpath;
console.log("DRAW: "+viz);
res = "digraph {\n"+" rankdir="+rankdir+";\n"+viz+"\n}";
try {
zoomlevel = 0;
status(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(c) {
$("#imagetools").hide();
$.ajax({url: "details.php?container="+c, success: function(res) {
try {
status(res);
} catch(e) {
status("<pre>"+res+"</pre>");
error("Exception Caught: "+e);
}
}}).fail(function() {
error("offline");
});
}
function action(container, action) {
$("#imagetools").hide();
$.ajax({url: "action.php?container="+container+"&action="+action, success: function(res) {
success(res);
manage();
}}).fail(function() {
error("offline");
});
}
/** Manage Docker Services */
function manage() {
$("#imagetools").hide();
$.ajax({url: "manage.php", success: function(res) {
status(res);
}}).fail(function() {
error("offline");
});
}
/** Show an Overview of all Docker Services */
function overview() {
$("#imagetools").hide();
}
/** 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;
});
status("<h2>Exception Caught:</h2><p>"+e+"<p><pre>"+res.join("\n")+"</pre>");
}
}}).fail(function() {
error("offline");
});
}
function containers(c) {
console.log("->rcv containers");
dc.setContainers(c);
showviz(dc.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("containers");
} catch (m) {
error(m);
}
}
function init() {
socket.io.on("connect", connected);
socket.io.on("reconnect", connected);
socket.io.on("disconnect", disconnected);
socket.io.on("error", disconnected);
socket.on("containers", containers);
start();
}
/// On Load, Call @ref start
/*
$(window.onbeforeunload = function() {
return "Are you sure you want to navigate away?";
});
*/
$(init);