first part of create neew nodes
This commit is contained in:
@@ -11,449 +11,6 @@
|
||||
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.image.name+'\\n'+n.name+'\\ncpu: ????? mem: ?????';
|
||||
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) {
|
||||
var 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);
|
||||
$("#screen").focus();
|
||||
$("#screen").keypress(function(e) {
|
||||
console.log("keypress", e);
|
||||
if (e.keyCode) emit("bash-input", {name: name, text: String.fromCharCode(e.keyCode)});
|
||||
else if (e.charCode) emit("bash-input", {name: name, text: String.fromCharCode(e.charCode)});
|
||||
$("#screen").focus();
|
||||
});
|
||||
// $("#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.configuration(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) {
|
||||
@@ -572,6 +129,30 @@ 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 create() {
|
||||
$("#create form fieldset input.add").click(function() {
|
||||
$(this).siblings("select").append('<option>'+$(this).siblings("input").map(function() {return $(this).text()}).get().join(':'))+'</option>')
|
||||
});
|
||||
//$("#preview").html(Viz("digraph {\nrankdir="+rankdir+";\n"+docker.containers.graph()+"\n}"));
|
||||
}
|
||||
|
||||
var zoomlevel = 0;
|
||||
function zoom(incr = 0) {
|
||||
zoomlevel = (zoomlevel+incr)%2;
|
||||
@@ -619,12 +200,12 @@ function showviz(vizpath, more) {
|
||||
res = "digraph {\n"+" rankdir="+rankdir+";\n"+viz+"\n}";
|
||||
try {
|
||||
status(more?Viz(res)+more:Viz(res));
|
||||
$('a > ellipse + text').attr('font-size', '12');
|
||||
$('a > ellipse + text + text')
|
||||
$('#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)});
|
||||
$('a > ellipse + text + text + text').attr('font-size', '12');
|
||||
$('#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;
|
||||
@@ -672,7 +253,7 @@ function stats(data) {
|
||||
var s = data[name];
|
||||
var o = oldstats[name];
|
||||
if (!o|| !s) continue;
|
||||
$('text + text:contains("'+name+'") + text')
|
||||
$('#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)
|
||||
@@ -700,6 +281,7 @@ function containers(c) {
|
||||
}
|
||||
|
||||
function showImage() {
|
||||
$("#create").hide();
|
||||
$("#logs").hide();
|
||||
$("#console").hide();
|
||||
$("#close").hide();
|
||||
@@ -707,8 +289,19 @@ function showImage() {
|
||||
$("#main").show();
|
||||
}
|
||||
|
||||
function showCreate() {
|
||||
$("#main").hide();
|
||||
$("#logs").hide();
|
||||
$("#console").hide();
|
||||
$("#imagetools").hide();
|
||||
$("#close").show();
|
||||
$("#create").show();
|
||||
create();
|
||||
}
|
||||
|
||||
function showConsole() {
|
||||
$("#main").hide();
|
||||
$("#create").hide();
|
||||
$("#logs").hide();
|
||||
$("#imagetools").hide();
|
||||
$("#console").show();
|
||||
@@ -720,6 +313,7 @@ function showConsole() {
|
||||
|
||||
function showLogs() {
|
||||
$("#main").hide();
|
||||
$("#create").hide();
|
||||
$("#console").hide();
|
||||
$("#imagetools").hide();
|
||||
$("#close").show();
|
||||
|
@@ -30,69 +30,33 @@ svg {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
form {
|
||||
padding: .5em 0 1em 0;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: nowrap;
|
||||
}
|
||||
|
||||
ul li {
|
||||
margin-left: 1em;
|
||||
}
|
||||
|
||||
@media (max-width: 45em) {
|
||||
form {
|
||||
flex-direction: column;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
}
|
||||
|
||||
form > * {
|
||||
flex-grow: 1;
|
||||
}
|
||||
form input[type="submit"] {
|
||||
flex-grow: 0;
|
||||
}
|
||||
form input#msg {
|
||||
flex-grow: 4;
|
||||
}
|
||||
|
||||
.buttongroup {
|
||||
flex-grow: 0;
|
||||
form {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: nowrap;
|
||||
flex-wrap: wrap;
|
||||
justify-content: flex-start;
|
||||
align-items: stretch;
|
||||
align-content: flex-start;
|
||||
}
|
||||
.buttongroup .toolbutton {
|
||||
flex-grow: 1;
|
||||
flex-basis: 0;
|
||||
text-align: center;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
|
||||
form fieldset {
|
||||
flex-grow: 0;
|
||||
flex-shrink: 0;
|
||||
flex-basis: auto; /* or: content */
|
||||
}
|
||||
.toolbutton label img {
|
||||
height: 1.5em;
|
||||
}
|
||||
.toolbutton input {
|
||||
display:none;
|
||||
}
|
||||
.toolbutton.bad:first-line,
|
||||
.toolbutton.good:first-line {
|
||||
font-weight: bold;
|
||||
}
|
||||
.toolbutton {
|
||||
|
||||
fieldset {
|
||||
margin: .5ex;
|
||||
padding: .5ex;
|
||||
border: 1px solid black;
|
||||
background-color: #777;
|
||||
text-decoration: none;
|
||||
margin: 1px;
|
||||
padding: 1px;
|
||||
}
|
||||
.toolbutton.bad {
|
||||
background-color: #f77;
|
||||
}
|
||||
.toolbutton.good {
|
||||
background-color: #7f7;
|
||||
|
||||
.listbutton {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
table {
|
||||
@@ -231,8 +195,14 @@ table.docker li+li {
|
||||
margin-top: 0.5em;
|
||||
padding-top: 0.5em;
|
||||
}
|
||||
#menu li input {
|
||||
display: none;
|
||||
}
|
||||
#menu li:hover {
|
||||
background-color: #777;
|
||||
}
|
||||
|
||||
#main, #logs, #console {
|
||||
#main, #logs, #console, #create {
|
||||
position: fixed;
|
||||
top: 1.5em;
|
||||
left: 0;
|
||||
@@ -244,7 +214,7 @@ table.docker li+li {
|
||||
bottom: 1.5em;
|
||||
}
|
||||
|
||||
#main {
|
||||
#main, #create {
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user