diff --git a/nodejs/docker/docker.js b/nodejs/docker/docker.js new file mode 100644 index 0000000..a4f0fe8 --- /dev/null +++ b/nodejs/docker/docker.js @@ -0,0 +1,460 @@ +var Docker = function() { + + 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(/[^-_:=\/a-zA-Z0-9]/)) { + if (text.match('"')) { + if (!text.match("'")) return "'"+text+"'"; + else return '"'+text.replace(/"/g, '\\"')+'"'; + } else { + 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.container+'" [label="link: '+l.name+'"];\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.container]) addNodes(ns, peer.container); + }); + 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: [] + }; + if (ns[n].ports) ns[n].ports.forEach(function(p) { + if (p.ip && !p.ip.match(/^([0-9]{1,3}\.){3}[0-9]{1,3}$/)) p.ip = null; + }); + 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') + $("#popup").empty(); + if (n.status.action1) { + $("#popup").append(''); + $("#popup1").click(function() { + emit(n.status.action1, name); + }); + } + $("#popup").append(''); + $("#popup2").click(function() { + if (focused) overview(); else details(name); + }); + if (n.status.action2) { + $("#popup").append(''); + $("#popup3").click(function() { + emit(n.status.action2, name); + }); + } + $("#popup").append('
'); + $("#popup").append(''); + $("#popup4").click(function() { + showLogs(); + emit("logs", name); + }); + if (n.status.bash) { + $("#popup").append(''); + $("#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(''); + $("#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(); + +} + +if (typeof module === 'undefined') module = {}; +module.exports = { + Docker: Docker +} diff --git a/nodejs/docker/index.js b/nodejs/docker/index.js new file mode 100644 index 0000000..9e9bce3 --- /dev/null +++ b/nodejs/docker/index.js @@ -0,0 +1,8 @@ +module.exports = function(app) { + + if (app) app.get('/docker/docker.js', function(req, res) { + res.sendfile('docker.js', {root: __dirname}); + }); + + return require(__dirname+'/docker.js'); +}