From 9e713b7b818503273c852a9956453b75c62c5051 Mon Sep 17 00:00:00 2001 From: Roger Abelenda Date: Fri, 1 May 2015 21:41:10 -0300 Subject: [PATCH 1/2] Add several - Add search box to easily find containers in network. This required an addition to angular-vis which is in a pending pull request: https://github.com/visjs/angular-visjs/pull/22 - Add tooltip to easily visualize container id and image name. - Add options to hide containers (and showAll to reset view) to ease visualization when there are too many containers. --- README.md | 2 +- .../containersNetwork/containersNetwork.html | 18 +- .../containersNetworkController.js | 167 +++++++++++++----- .../glyphicons-halflings-regular.eot | Bin .../glyphicons-halflings-regular.svg | 0 .../glyphicons-halflings-regular.ttf | Bin .../glyphicons-halflings-regular.woff | Bin assets/js/angular-vis.js | 5 +- 8 files changed, 146 insertions(+), 46 deletions(-) rename assets/{font => fonts}/glyphicons-halflings-regular.eot (100%) rename assets/{font => fonts}/glyphicons-halflings-regular.svg (100%) rename assets/{font => fonts}/glyphicons-halflings-regular.ttf (100%) rename assets/{font => fonts}/glyphicons-halflings-regular.woff (100%) diff --git a/README.md b/README.md index 7b1b693a3..178451b16 100644 --- a/README.md +++ b/README.md @@ -41,7 +41,7 @@ DockerUI listens on port 9000 by default. If you run DockerUI inside a container * [Gritter](https://github.com/jboesch/Gritter) * [Spin.js](https://github.com/fgnass/spin.js/) * [Golang](https://golang.org/) -* [Vis.js](http://visjs.org/) +* [Vis.js](http://visjs.org/) - We are using a [patched version](https://github.com/visjs/angular-visjs/pull/22) ### Todo: diff --git a/app/components/containersNetwork/containersNetwork.html b/app/components/containersNetwork/containersNetwork.html index 67fe230bf..fe2eea40f 100644 --- a/app/components/containersNetwork/containersNetwork.html +++ b/app/components/containersNetwork/containersNetwork.html @@ -1,7 +1,21 @@

Containers Network

-
- +
+
+ + +
+
+
+
+ + +
+
+
+
diff --git a/app/components/containersNetwork/containersNetworkController.js b/app/components/containersNetwork/containersNetworkController.js index b015488f9..d2537dc7b 100644 --- a/app/components/containersNetwork/containersNetworkController.js +++ b/app/components/containersNetwork/containersNetworkController.js @@ -1,26 +1,11 @@ angular.module('containersNetwork', ['ngVis']) .controller('ContainersNetworkController', ['$scope', '$location', 'Container', 'Messages', 'VisDataSet', function($scope, $location, Container, Messages, VisDataSet) { - $scope.options = { - navigation: true, - keyboard: true, - height: '500px', width: '700px', - nodes: { - shape: 'box' - }, - edges: { - style: 'arrow' - }, - physics: { - barnesHut : { - springLength: 200 - } - } - }; function ContainerNode(data) { this.Id = data.Id; // names have the following format: /Name this.Name = data.Name.substring(1); + this.Image = data.Config.Image; var dataLinks = data.HostConfig.Links; if (dataLinks != null) { this.Links = []; @@ -45,57 +30,155 @@ angular.module('containersNetwork', ['ngVis']) } } - function ContainersNetwork() { - this.containers = []; + function ContainersNetworkData() { this.nodes = new VisDataSet(); this.edges = new VisDataSet(); - this.add = function(data) { - var container = new ContainerNode(data); - this.containers.push(container); - this.nodes.add({id: container.Id, label: container.Name}); - for (var i = 0; i < this.containers.length; i++) { - var otherContainer = this.containers[i]; - this.addLinkEdgeIfExists(container, otherContainer); - this.addLinkEdgeIfExists(otherContainer, container); - this.addVolumeEdgeIfExists(container, otherContainer); - this.addVolumeEdgeIfExists(otherContainer, container); - } + this.addContainerNode = function(container) { + this.nodes.add({ + id: container.Id, + label: container.Name, + title: ""}); }; this.addLinkEdgeIfExists = function(from, to) { if (from.Links != null && from.Links[to.Name] != null) { - this.edges.add({ from: from.Id, to: to.Id, label: from.Links[to.Name] }); + this.edges.add({ + from: from.Id, + to: to.Id, + label: from.Links[to.Name] }); } }; this.addVolumeEdgeIfExists = function(from, to) { if (from.VolumesFrom != null && from.VolumesFrom[to.Id] != null) { - this.edges.add({ from: from.Id, to: to.Id, color: { color: '#A0A0A0', highlight: '#A0A0A0', hover: '#848484'}}); + this.edges.add({ + from: from.Id, + to: to.Id, + color: { color: '#A0A0A0', highlight: '#A0A0A0', hover: '#848484'}}); } }; + + this.removeContainersNodes = function(containersIds) { + this.nodes.remove(containersIds); + }; } - $scope.data = new ContainersNetwork(); - $scope.events = { - doubleClick : function(event) { - $scope.$apply( function() { - $location.path('/containers/' + event.nodes[0]); - }); - } - }; + function ContainersNetwork() { + this.data = new ContainersNetworkData(); + this.containers = []; + this.selectedContainers = []; + this.shownContainers = []; + this.events = { + select : function(event) { + $scope.network.selectedContainers = event.nodes; + $scope.$apply( function() { + $scope.query = ''; + }); + }, + doubleClick : function(event) { + $scope.$apply( function() { + $location.path('/containers/' + event.nodes[0]); + }); + } + }; + this.options = { + navigation: true, + keyboard: true, + height: '500px', width: '700px', + nodes: { + shape: 'box' + }, + edges: { + style: 'arrow' + }, + physics: { + barnesHut : { + springLength: 200 + } + } + }; + + this.addContainer = function(data) { + var container = new ContainerNode(data); + this.containers.push(container); + this.shownContainers.push(container); + this.data.addContainerNode(container); + for (var i = 0; i < this.containers.length; i++) { + var otherContainer = this.containers[i]; + this.data.addLinkEdgeIfExists(container, otherContainer); + this.data.addLinkEdgeIfExists(otherContainer, container); + this.data.addVolumeEdgeIfExists(container, otherContainer); + this.data.addVolumeEdgeIfExists(otherContainer, container); + } + }; + + this.selectContainers = function(query) { + if (this.component != null) { + this.selectedContainers = this.searchContainers(query); + this.component.selectNodes(this.selectedContainers); + } + }; + + this.searchContainers = function(query) { + if (query.trim() === "") { + return []; + } + var selectedContainers = []; + for (var i=0; i < this.shownContainers.length; i++) { + var container = this.shownContainers[i]; + if (container.Name.indexOf(query) > -1 || + container.Image.indexOf(query) > -1 || + container.Id.indexOf(query) > -1) { + selectedContainers.push(container.Id); + } + } + return selectedContainers; + }; + + this.hideSelected = function() { + var i=0; + while ( i < this.shownContainers.length ) { + if (this.selectedContainers.indexOf(this.shownContainers[i].Id) > -1) { + this.shownContainers.splice(i, 1); + } else { + i++; + } + } + this.data.removeContainersNodes(this.selectedContainers); + $scope.query = ''; + this.selectedContainers = []; + }; + + this.showAll = function() { + for (var i=0; i < this.containers.length; i++) { + var container = this.containers[i]; + if (this.shownContainers.indexOf(container) === -1) { + this.data.addContainerNode(container); + this.shownContainers.push(container); + } + } + }; + + } + + $scope.network = new ContainersNetwork(); var showFailure = function (event) { - Messages.error('Failure', e.data); + Messages.error('Failure', e.data); }; var addContainer = function (container) { - $scope.data.add(container); + $scope.network.addContainer(container); }; Container.query({all: 0}, function(d) { for (var i = 0; i < d.length; i++) { Container.get({id: d[i].Id}, addContainer, showFailure); } - }); + }); + }]); diff --git a/assets/font/glyphicons-halflings-regular.eot b/assets/fonts/glyphicons-halflings-regular.eot similarity index 100% rename from assets/font/glyphicons-halflings-regular.eot rename to assets/fonts/glyphicons-halflings-regular.eot diff --git a/assets/font/glyphicons-halflings-regular.svg b/assets/fonts/glyphicons-halflings-regular.svg similarity index 100% rename from assets/font/glyphicons-halflings-regular.svg rename to assets/fonts/glyphicons-halflings-regular.svg diff --git a/assets/font/glyphicons-halflings-regular.ttf b/assets/fonts/glyphicons-halflings-regular.ttf similarity index 100% rename from assets/font/glyphicons-halflings-regular.ttf rename to assets/fonts/glyphicons-halflings-regular.ttf diff --git a/assets/font/glyphicons-halflings-regular.woff b/assets/fonts/glyphicons-halflings-regular.woff similarity index 100% rename from assets/font/glyphicons-halflings-regular.woff rename to assets/fonts/glyphicons-halflings-regular.woff diff --git a/assets/js/angular-vis.js b/assets/js/angular-vis.js index 71b967867..a0347fb5d 100755 --- a/assets/js/angular-vis.js +++ b/assets/js/angular-vis.js @@ -92,7 +92,8 @@ angular.module('ngVis', []) scope: { data: '=', options: '=', - events: '=' + events: '=', + component: '=' }, link: function (scope, element, attr) { var networkEvents = [ @@ -103,6 +104,7 @@ angular.module('ngVis', []) ]; var network = new vis.Network(element[0], scope.data, scope.options); + scope.component = network; scope.$watch('data', function () { // Sanity check @@ -118,6 +120,7 @@ angular.module('ngVis', []) // Create the graph2d object network = new vis.Network(element[0]); + scope.component = network; // Attach an event handler if defined angular.forEach(scope.events, function (callback, event) { From edd6a41d6e029c99439a7d18e4a4a45fb40a6d3c Mon Sep 17 00:00:00 2001 From: Roger Abelenda Date: Sat, 2 May 2015 12:04:31 -0300 Subject: [PATCH 2/2] Add actions to show upstream and downstream dependencies of a container for easy visualization. Additionally updated angular-vis to use version 0.0.4 which already includes the changes of https://github.com/visjs/angular-visjs/pull/22. --- README.md | 2 +- .../containersNetwork/containersNetwork.html | 2 + .../containersNetworkController.js | 119 ++++++++++++++---- assets/js/angular-vis.js | 9 +- 4 files changed, 102 insertions(+), 30 deletions(-) diff --git a/README.md b/README.md index 178451b16..7b1b693a3 100644 --- a/README.md +++ b/README.md @@ -41,7 +41,7 @@ DockerUI listens on port 9000 by default. If you run DockerUI inside a container * [Gritter](https://github.com/jboesch/Gritter) * [Spin.js](https://github.com/fgnass/spin.js/) * [Golang](https://golang.org/) -* [Vis.js](http://visjs.org/) - We are using a [patched version](https://github.com/visjs/angular-visjs/pull/22) +* [Vis.js](http://visjs.org/) ### Todo: diff --git a/app/components/containersNetwork/containersNetwork.html b/app/components/containersNetwork/containersNetwork.html index fe2eea40f..e9bce03de 100644 --- a/app/components/containersNetwork/containersNetwork.html +++ b/app/components/containersNetwork/containersNetwork.html @@ -11,6 +11,8 @@
+ +
diff --git a/app/components/containersNetwork/containersNetworkController.js b/app/components/containersNetwork/containersNetworkController.js index d2537dc7b..3219a0951 100644 --- a/app/components/containersNetwork/containersNetworkController.js +++ b/app/components/containersNetwork/containersNetworkController.js @@ -8,7 +8,7 @@ angular.module('containersNetwork', ['ngVis']) this.Image = data.Config.Image; var dataLinks = data.HostConfig.Links; if (dataLinks != null) { - this.Links = []; + this.Links = {}; for (var i = 0; i < dataLinks.length; i++) { // links have the following format: /TargetContainerName:/SourceContainerName/LinkAlias var link = dataLinks[i].split(":"); @@ -23,7 +23,7 @@ angular.module('containersNetwork', ['ngVis']) var dataVolumes = data.HostConfig.VolumesFrom; //converting array into properties for simpler and faster access if (dataVolumes != null) { - this.VolumesFrom = []; + this.VolumesFrom = {}; for (var j = 0; j < dataVolumes.length; j++) { this.VolumesFrom[dataVolumes[j]] = true; } @@ -69,12 +69,12 @@ angular.module('containersNetwork', ['ngVis']) function ContainersNetwork() { this.data = new ContainersNetworkData(); - this.containers = []; - this.selectedContainers = []; - this.shownContainers = []; + this.containers = {}; + this.selectedContainersIds = []; + this.shownContainersIds = []; this.events = { select : function(event) { - $scope.network.selectedContainers = event.nodes; + $scope.network.selectedContainersIds = event.nodes; $scope.$apply( function() { $scope.query = ''; }); @@ -104,11 +104,11 @@ angular.module('containersNetwork', ['ngVis']) this.addContainer = function(data) { var container = new ContainerNode(data); - this.containers.push(container); - this.shownContainers.push(container); + this.containers[container.Id] = container; + this.shownContainersIds.push(container.Id); this.data.addContainerNode(container); - for (var i = 0; i < this.containers.length; i++) { - var otherContainer = this.containers[i]; + for (var otherContainerId in this.containers) { + var otherContainer = this.containers[otherContainerId]; this.data.addLinkEdgeIfExists(container, otherContainer); this.data.addLinkEdgeIfExists(otherContainer, container); this.data.addVolumeEdgeIfExists(container, otherContainer); @@ -118,8 +118,8 @@ angular.module('containersNetwork', ['ngVis']) this.selectContainers = function(query) { if (this.component != null) { - this.selectedContainers = this.searchContainers(query); - this.component.selectNodes(this.selectedContainers); + this.selectedContainersIds = this.searchContainers(query); + this.component.selectNodes(this.selectedContainersIds); } }; @@ -127,38 +127,103 @@ angular.module('containersNetwork', ['ngVis']) if (query.trim() === "") { return []; } - var selectedContainers = []; - for (var i=0; i < this.shownContainers.length; i++) { - var container = this.shownContainers[i]; + var selectedContainersIds = []; + for (var i=0; i < this.shownContainersIds.length; i++) { + var container = this.containers[this.shownContainersIds[i]]; if (container.Name.indexOf(query) > -1 || container.Image.indexOf(query) > -1 || container.Id.indexOf(query) > -1) { - selectedContainers.push(container.Id); + selectedContainersIds.push(container.Id); } } - return selectedContainers; + return selectedContainersIds; }; this.hideSelected = function() { var i=0; - while ( i < this.shownContainers.length ) { - if (this.selectedContainers.indexOf(this.shownContainers[i].Id) > -1) { - this.shownContainers.splice(i, 1); + while ( i < this.shownContainersIds.length ) { + if (this.selectedContainersIds.indexOf(this.shownContainersIds[i]) > -1) { + this.shownContainersIds.splice(i, 1); } else { i++; } } - this.data.removeContainersNodes(this.selectedContainers); + this.data.removeContainersNodes(this.selectedContainersIds); $scope.query = ''; - this.selectedContainers = []; + this.selectedContainersIds = []; + }; + + this.searchDownstream = function(containerId, downstreamContainersIds) { + if (downstreamContainersIds.indexOf(containerId) > -1) { + return; + } + downstreamContainersIds.push(containerId); + var container = this.containers[containerId]; + if (container.Links == null && container.VolumesFrom == null) { + return; + } + for (var otherContainerId in this.containers) { + var otherContainer = this.containers[otherContainerId]; + if (container.Links != null && container.Links[otherContainer.Name] != null) { + this.searchDownstream(otherContainer.Id, downstreamContainersIds); + } else if (container.VolumesFrom != null && + container.VolumesFrom[otherContainer.Id] != null) { + this.searchDownstream(otherContainer.Id, downstreamContainersIds); + } + } + }; + + this.updateShownContainers = function(newShownContainersIds) { + for (var containerId in this.containers) { + if (newShownContainersIds.indexOf(containerId) > -1 && + this.shownContainersIds.indexOf(containerId) === -1) { + this.data.addContainerNode(this.containers[containerId]); + } else if (newShownContainersIds.indexOf(containerId) === -1 && + this.shownContainersIds.indexOf(containerId) > -1) { + this.data.removeContainersNodes(containerId); + } + } + this.shownContainersIds = newShownContainersIds; + }; + + this.showSelectedDownstream = function() { + var downstreamContainersIds = []; + for (var i=0; i < this.selectedContainersIds.length; i++) { + this.searchDownstream(this.selectedContainersIds[i], downstreamContainersIds); + } + this.updateShownContainers(downstreamContainersIds); + }; + + this.searchUpstream = function(containerId, upstreamContainersIds) { + if (upstreamContainersIds.indexOf(containerId) > -1) { + return; + } + upstreamContainersIds.push(containerId); + var container = this.containers[containerId]; + for (var otherContainerId in this.containers) { + var otherContainer = this.containers[otherContainerId]; + if (otherContainer.Links != null && otherContainer.Links[container.Name] != null) { + this.searchUpstream(otherContainer.Id, upstreamContainersIds); + } else if (otherContainer.VolumesFrom != null && + otherContainer.VolumesFrom[container.Id] != null) { + this.searchUpstream(otherContainer.Id, upstreamContainersIds); + } + } + }; + + this.showSelectedUpstream = function() { + var upstreamContainersIds = []; + for (var i=0; i < this.selectedContainersIds.length; i++) { + this.searchUpstream(this.selectedContainersIds[i], upstreamContainersIds); + } + this.updateShownContainers(upstreamContainersIds); }; this.showAll = function() { - for (var i=0; i < this.containers.length; i++) { - var container = this.containers[i]; - if (this.shownContainers.indexOf(container) === -1) { - this.data.addContainerNode(container); - this.shownContainers.push(container); + for (var containerId in this.containers) { + if (this.shownContainersIds.indexOf(containerId) === -1) { + this.data.addContainerNode(this.containers[containerId]); + this.shownContainersIds.push(containerId); } } }; diff --git a/assets/js/angular-vis.js b/assets/js/angular-vis.js index a0347fb5d..4d594a00b 100755 --- a/assets/js/angular-vis.js +++ b/assets/js/angular-vis.js @@ -19,7 +19,8 @@ angular.module('ngVis', []) scope: { data: '=', options: '=', - events: '=' + events: '=', + component: '=' }, link: function (scope, element, attr) { var timelineEvents = [ @@ -46,6 +47,7 @@ angular.module('ngVis', []) // Create the timeline object timeline = new vis.Timeline(element[0]); + scope.component = timeline; // Attach an event handler if defined angular.forEach(scope.events, function (callback, event) { @@ -169,7 +171,8 @@ angular.module('ngVis', []) scope: { data: '=', options: '=', - events: '=' + events: '=', + component: '=' }, link: function (scope, element, attr) { var graphEvents = [ @@ -181,6 +184,7 @@ angular.module('ngVis', []) // Create the chart var graph = new vis.Graph2d(element[0]); + scope.component = graph; scope.$watch('data', function () { // Sanity check @@ -196,6 +200,7 @@ angular.module('ngVis', []) // Create the graph2d object graph = new vis.Graph2d(element[0]); + scope.component = graph; // Attach an event handler if defined angular.forEach(scope.events, function (callback, event) {