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.
 
 
 
 
 

530 lines
18 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();
var focused = null;
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);
if (msg) success(msg);
else setTimeout("$('#status').fadeOut('slow')", 5000);
zoom(0);
stats();
$("#main").show();
$("form input:first-child").focus();
docker.containers.contextmenu("#main");
}
function emit(signal, data) {
console.log("<-snd "+signal, data);
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();
}
/// Upload a configuration and send it to server
function upload(evt) {
if (!window.FileReader)
return error("your browser does not support file upload", true);
for (var i=0, f; f=evt.target.files[i]; ++i) {
var file = f;
var reader = new FileReader();
reader.onload = function(evt) {
if (evt.target.error) return error("error reading file", true);
if (evt.target.readyState==0) return notice("waiting for data …");
if (evt.target.readyState==1) return notice("loading data …");
emit("create", evt.target.result);
}
reader.readAsText(file);
}
}
function previewCreate() {
var name = $('#name').val();
var nodes = {};
nodes[name] = {
status: docker.containers.Status.Preview,
id: null,
name: name,
image: {
name: $('#image').val(),
id: null
},
links: [],
volumesfrom: [],
};
$('#preview').html(Viz("digraph {\n"+" rankdir="+rankdir+";\n"+docker.containers.subgraph(name, nodes)+"\n}"));
}
function create() {
}
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) {
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));
$('#main a > ellipse + text').attr('font-size', '12');
$('#main a > ellipse + text + text')
.attr('font-weight', 'bold')
.attr('font-size', '16')
.each(function() {$(this).attr('y', parseFloat($(this).attr('y'))+1.0)});
$('#main a > ellipse + text + text + text').attr('font-size', '12');
} 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));
}
/// Convert number of bytes to readable text
function size(num) {
if (num>0.6*1024) {
if (num>0.6*1024*1024) {
if (num>0.6*1024*1024*1024) {
if (num>0.6*1024*1024*1024*1024) {
return Math.round(num/1024/1024/1024/1024)+"TB";
} else {
return Math.round(num/1024/1024/1024)+"GB";
}
} else {
return Math.round(num/1024/1024)+"MB";
}
} else {
return Math.round(num/1024)+"kB";
}
} else {
return num+"B";
}
}
var oldoldstats = null;
var oldstats = null;
function stats(data) {
console.log("->rcv stats");
if (!data && oldstats && oldoldstats) {
data = oldstats;
oldstats = oldoldstats;
}
if (oldstats) for (name in data) {
var s = data[name];
var o = oldstats[name];
if (!o|| !s) continue;
$('#main text + text:contains("'+name+'") + text')
.html('cpu: '
+(Math.round((s.cpuacct.usage.data-o.cpuacct.usage.data)
/(s.cpuacct.usage.date-o.cpuacct.usage.date)
/100)
/100)
+'% mem: '
+size(s.memory.usage_in_bytes.data));
}
oldoldstats = oldstats;
oldstats = data;
}
function images(i) {
console.log("->rcv images");
docker.images.set(i);
}
function containers(c) {
console.log("->rcv containers");
docker.containers.set(c);
if (focused && docker.containers.exists(focused))
details(focused);
else
overview();
}
function showImage() {
$("#create").hide();
$("#logs").hide();
$("#console").hide();
$("#close").hide();
$("#imagetools").show();
$("#main").show();
}
function showCreate() {
$("#main").hide();
$("#logs").hide();
$("#console").hide();
$("#imagetools").show();
$("#close").show();
$("#create").show();
previewCreate();
}
function showConsole() {
$("#main").hide();
$("#create").hide();
$("#logs").hide();
$("#imagetools").hide();
$("#console").show();
$("#close").show();
// $("#command").focus();
// $("#command").val("");
// if ($("#screen").val()!="") $("#screen").append("\n");
}
function showLogs() {
$("#main").hide();
$("#create").hide();
$("#console").hide();
$("#imagetools").hide();
$("#close").show();
$("#logs").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 ascii(txt) {
var res = "";
for (i=0; i<txt.length; ++i) {
if (res) res += ",";
res += txt.charCodeAt(i).toString();
}
return res;
}
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 {
var buff = "";
console.log("ASCII: ", ascii(data.text));
for (var i=0; i<data.text.length; ++i) {
switch (data.text.charCodeAt(i)) {
case 7:
case 8:
if (buff.length) $("#screen").append(ansifilter(htmlenc(buff)));
buff = "";
$('#screen').html(function (_,txt) {
return txt.slice(0, -1);
});
break;
default: buff += data.text[i];
}
}
if (buff.length) $("#screen").append(ansifilter(htmlenc(buff)));
}
$("#screen").animate({
scrollTop: $("#screen").prop('scrollHeight')
}, 500);
$("#screen").focus();
}
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();
$("#close").hide();
$("#popup").hide();
$("#menu").hide();
$("#username").html(window.location.hostname)
try {
status("Starting up ...");
emit("images");
emit("containers");
showImage();
} catch (m) {
error(m);
}
}
function initForms() {
$('#create form *').change(previewCreate);
$("#create form fieldset .add").click(function() {
$(this).siblings("select.collect")
.append('<option '+$(this).siblings("input")
.map(function() {
if (this.hasAttribute('data-name')) {
return this.getAttribute('data-name')+'="'+this.value.replace(/"/g, '&quot;')+'"';
} else return '';
}).get().join(' ')+'>'+$(this).siblings("input")
.map(function() {
var val = this.value;
var sep = this.getAttribute('data-separator') || '';
if (this.type=='checkbox' && !this.checked) {
val=''; sep=''
}
return sep+val;
}).get().join('')+'</option>')
$(this).siblings("input").value();
});
//$("#preview").html(Viz("digraph {\nrankdir="+rankdir+";\n"+docker.containers.graph()+"\n}"));
}
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("stats", stats)
.on("logs", logs)
.on("bash-data", bash_data);
initForms();
start();
}
/// On Load, Call @ref start
/*
$(window.onbeforeunload = function() {
return "Are you sure you want to navigate away?";
});
*/
$(init);