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.

554 lines
19 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 = null;
var docker = null;
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. */
9 years ago
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);
$("#main").show();
$("form input:first-child").focus();
}
function emit(signal, data) {
console.log("<-snd "+signal, data);
socket.emit(signal, data);
}
function connect() {
$("#server").html($("#usernamefield").val()+'@'+window.location.hostname)
console.log("server connect");
$("#connectionstatus #bad").hide();
$("#connectionstatus #authentication").show();
$("#connectionstatus #good").hide();
success("login to server");
socket.emit('authentication', {
username: $("#usernamefield").val(),
password: $("#passwordfield").val()
});
}
function authenticated() {
$("#server").html($("#usernamefield").val()+'@'+window.location.hostname)
console.log("server authenticated");
$("#connectionstatus #bad").hide();
$("#connectionstatus #authentication").hide();
$("#connectionstatus #good").show();
success("server connected");
start();
}
function unauthorized() {
$("#server").html($("#usernamefield").val()+'@'+window.location.hostname)
console.log("authentication failed");
$("#connectionstatus #good").hide();
$("#connectionstatus #authentication").hide();
$("#connectionstatus #bad").show();
error("authentication failed", true);
}
function disconnected() {
$("#server").html($("#usernamefield").val()+'@'+window.location.hostname)
console.log("server disconnected");
$("#connectionstatus #good").hide();
$("#connectionstatus #authentication").hide();
$("#connectionstatus #bad").show();
error("server disconnected", true);
}
/// 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 …");
tobecreated = JSON.parse(evt.target.result);
showCreate();
}
reader.readAsText(file);
}
}
9 years ago
var tobecreated = {};
9 years ago
function previewCreate() {
var name = $('#name').val();
var nodes = Object.create(tobecreated);
if (name != '') {
nodes[name] = {
status: docker.containers.Status.Preview,
id: null,
name: name,
image: {
name: $('#image').val(),
id: null
},
ports: $('#createports option').map(function() {return $(this).data();}).get(),
env: $('#createvars option').map(function() {return $(this).val();}).get(),
volumes: $('#createvolumes option').map(function() {return $(this).data();}).get(),
volumesfrom: $('#createvolumefroms option').map(function() {return $(this).val();}).get(),
links: $('#createlinks option').map(function() {return $(this).data();}).get(),
entrypoint: $('#createentrypoints option').map(function() {return $(this).val();}).get(),
cmd: $('#createcommands option').map(function() {return $(this).val();}).get(),
};
$('#doappend').unbind().click(function() {
tobecreated[name] = Object.create(nodes[name]);
tobecreated[name].status = docker.containers.Status.Prepared;
$('#createpreview').append('<option>'+name+'</option>');
$('#create form input[type=text]').val('');
$('#create form select.collect option').remove();
previewCreate();
});
$('#doremove').unbind().click(function() {
$(this).siblings('#createpreview').children('option:selected').each(function(a,b,c) {
delete tobecreated[b.innerHTML];
}).remove();
previewCreate();
});
$('#dosend').unbind().click(function() {
if (Object.keys(tobecreated).length>0) {
emit("docker.container.create", docker.containers.configuration(tobecreated));
tobecreated = {};
showImage();
}
});
}
if ($('#image').val()!='') {
var img = docker.images.get($('#image').val());
if (img) {
$('#portintdata').empty().append(img.ports.map(function(i) {
var option = document.createElement('option');
option.value = i.replace(/\/.*/g, '');
return option;
}));
$('#varnamedata').empty().append(img.env.map(function(i) {
var option = document.createElement('option');
option.value = i.replace(/=.*/g, '');
return option;
}));
$('#varvaluedata').empty().append(img.env.map(function(i) {
var option = document.createElement('option');
option.value = i.replace(/^[^=]*=/g, '');
return option;
}));
$('#volumeextdata').empty();
$('#volumeintdata').empty();
for (i in img.volumes) {
var option = document.createElement('option');
option.value = i;
$('#volumeextdata').append(option);
$('#volumeintdata').append(option);
}
$('#volumesfromdata').empty().append(docker.containers.names(tobecreated).map(function(i) {
var option = document.createElement('option');
option.value = i;
return option;
}));
$('#linkcontainerdata').empty().append(docker.containers.names(tobecreated).map(function(i) {
var option = document.createElement('option');
option.value = i;
return option;
}));
9 years ago
}
}
if (name=='' && nodes) for (name in nodes) break;
if (name && name!='') $('#preview').html(Viz("digraph {\n"+" rankdir="+rankdir+";\n"
+docker.containers.subgraph(name, nodes)
+"\n}"));
else $('#preview').html('');
9 years ago
}
function create() {
}
var zoomlevel = 0;
function zoom(incr = 0) {
zoomlevel = (zoomlevel+incr)%2;
switch (zoomlevel) {
case 0: {
$("#main svg, #preview svg").css("width", "auto");
$("#main svg, #preview svg").css("height", "auto");
$("#main svg, #preview svg").css("max-width", "100%");
$("#main svg, #preview svg").css("max-height", "100%");
} break;
case 1: {
$("#main svg, #preview svg").css("width", "100%");
$("#main svg, #preview svg").css("height", "auto");
$("#main svg, #preview svg").css("max-width", "100%");
$("#main svg, #preview svg").css("max-height", "none");
} break;
case 2: {
$("#main.svg, #preview svg").css("width", "auto");
$("#main.svg, #preview svg").css("height", "100%");
$("#main.svg, #preview svg").css("max-width", "none");
$("#main.svg, #preview svg").css("max-height", "100%");
} break;
}
}
function showLogin() {
$("#close").hide();
$("#console").hide();
$("#create").hide();
$("#imagetools").hide();
$("#login").show();
$("#logs").hide();
$("#main").hide();
}
function showImage() {
$("#close").hide();
$("#console").hide();
$("#create").hide();
$("#imagetools").show();
$("#login").hide();
$("#logs").hide();
$("#main").show();
}
function showCreate() {
$("#close").show();
$("#console").hide();
$("#create").show();
$("#imagetools").show();
$("#login").hide();
$("#logs").hide();
$("#main").hide();
previewCreate();
}
function showConsole() {
$("#close").show();
$("#console").show();
$("#create").hide();
$("#imagetools").hide();
$("#login").hide();
$("#logs").hide();
$("#main").hide();
// $("#command").focus();
// $("#command").val("");
// if ($("#screen").val()!="") $("#screen").append("\n");
}
function showLogs() {
$("#close").show();
$("#console").hide();
$("#create").hide();
$("#imagetools").hide();
$("#login").hide();
$("#logs").show();
$("#main").hide();
}
function images(i) {
console.log("->rcv images");
docker.images.set(i);
$('#imagedata').empty().append(docker.images.tags().map(function(i) {
var option = document.createElement('option');
option.value = i;
return option;
}));
}
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();
}
9 years ago
/// Initial Function: Startup
/** To be called after login */
function start() {
$("#imagetools").hide();
$("#close").hide();
$("#popup").hide();
$("#menu").hide();
try {
status("Starting up ...");
emit("docker.images");
showImage();
} catch (m) {
error(m);
}
}
9 years ago
function initForms() {
$('#create form *').change(previewCreate);
$("#create form fieldset .add").unbind().click(function() {
$(this).siblings("select.collect")
.append('<option '+$(this).siblings("input")
.map(function() {
if (this.hasAttribute('data-name')) {
var res = "";
var obj = this;
this.getAttribute('data-name').split(' ').forEach(function(n) {
res += 'data-'+n+'="'+
(obj.type!='checkbox'||obj.checked
?obj.value:obj.getAttribute('data-false'))
.replace(/&/g, '&amp;')
.replace(/"/g, '&quot;')
+'"';
})
return res;
} 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[type=text]").val('');
previewCreate();
});
$("#create form fieldset .remove").unbind().click(function() {
$(this).siblings('select.collect').children('option:selected').remove();
previewCreate();
});
9 years ago
}
function init() {
socket = io.connect();
docker = new Docker(socket, '#main', error);
$("#server").html($("#usernamefield").value+'@'+window.location.hostname)
initForms();
showLogin();
socket
.io
.on("connect", connect)
.on("reconnect", connect)
.on("disconnect", disconnected)
.on("error", disconnected);
socket
.on("authenticated", authenticated)
.on("unauthorized", unauthorized)
.on("docker.images", images)
.on("docker.container.logs", logs)
.on("docker.container.bash.data", bash_data);
}
/// On Load, Call @ref start
/*
$(window.onbeforeunload = function() {
return "Are you sure you want to navigate away?";
});
*/
$(init);