mirror of https://github.com/portainer/portainer
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.pull/2/head
parent
008884ec59
commit
9e713b7b81
|
@ -41,7 +41,7 @@ DockerUI listens on port 9000 by default. If you run DockerUI inside a container
|
||||||
* [Gritter](https://github.com/jboesch/Gritter)
|
* [Gritter](https://github.com/jboesch/Gritter)
|
||||||
* [Spin.js](https://github.com/fgnass/spin.js/)
|
* [Spin.js](https://github.com/fgnass/spin.js/)
|
||||||
* [Golang](https://golang.org/)
|
* [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:
|
### Todo:
|
||||||
|
|
|
@ -1,7 +1,21 @@
|
||||||
<div class="detail">
|
<div class="detail">
|
||||||
<h2>Containers Network</h2>
|
<h2>Containers Network</h2>
|
||||||
|
|
||||||
<div>
|
<div class="row">
|
||||||
<vis-network data="data" options="options" events="events"/>
|
<div class="input-group">
|
||||||
|
<input type="text" ng-model="query" autofocus="true" class="form-control"
|
||||||
|
placeholder="Search" ng-change="network.selectContainers(query)"/>
|
||||||
|
<span class="input-group-addon"><span class="glyphicon glyphicon-search"/></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="btn-group">
|
||||||
|
<button class="btn btn-warning" ng-click="network.hideSelected()">Hide Selected</button>
|
||||||
|
<button class="btn btn-success" ng-click="network.showAll()">Show All</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<vis-network data="network.data" options="network.options" events="network.events"
|
||||||
|
component="network.component"/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,26 +1,11 @@
|
||||||
angular.module('containersNetwork', ['ngVis'])
|
angular.module('containersNetwork', ['ngVis'])
|
||||||
.controller('ContainersNetworkController', ['$scope', '$location', 'Container', 'Messages', 'VisDataSet', function($scope, $location, Container, Messages, VisDataSet) {
|
.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) {
|
function ContainerNode(data) {
|
||||||
this.Id = data.Id;
|
this.Id = data.Id;
|
||||||
// names have the following format: /Name
|
// names have the following format: /Name
|
||||||
this.Name = data.Name.substring(1);
|
this.Name = data.Name.substring(1);
|
||||||
|
this.Image = data.Config.Image;
|
||||||
var dataLinks = data.HostConfig.Links;
|
var dataLinks = data.HostConfig.Links;
|
||||||
if (dataLinks != null) {
|
if (dataLinks != null) {
|
||||||
this.Links = [];
|
this.Links = [];
|
||||||
|
@ -45,57 +30,155 @@ angular.module('containersNetwork', ['ngVis'])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function ContainersNetwork() {
|
function ContainersNetworkData() {
|
||||||
this.containers = [];
|
|
||||||
this.nodes = new VisDataSet();
|
this.nodes = new VisDataSet();
|
||||||
this.edges = new VisDataSet();
|
this.edges = new VisDataSet();
|
||||||
|
|
||||||
this.add = function(data) {
|
this.addContainerNode = function(container) {
|
||||||
var container = new ContainerNode(data);
|
this.nodes.add({
|
||||||
this.containers.push(container);
|
id: container.Id,
|
||||||
this.nodes.add({id: container.Id, label: container.Name});
|
label: container.Name,
|
||||||
for (var i = 0; i < this.containers.length; i++) {
|
title: "<ul style=\"list-style-type:none; padding: 0px; margin: 0px\">" +
|
||||||
var otherContainer = this.containers[i];
|
"<li><strong>ID:</strong> " + container.Id + "</li>" +
|
||||||
this.addLinkEdgeIfExists(container, otherContainer);
|
"<li><strong>Image:</strong> " + container.Image + "</li>" +
|
||||||
this.addLinkEdgeIfExists(otherContainer, container);
|
"</ul>"});
|
||||||
this.addVolumeEdgeIfExists(container, otherContainer);
|
|
||||||
this.addVolumeEdgeIfExists(otherContainer, container);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
this.addLinkEdgeIfExists = function(from, to) {
|
this.addLinkEdgeIfExists = function(from, to) {
|
||||||
if (from.Links != null && from.Links[to.Name] != null) {
|
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) {
|
this.addVolumeEdgeIfExists = function(from, to) {
|
||||||
if (from.VolumesFrom != null && from.VolumesFrom[to.Id] != null) {
|
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();
|
function ContainersNetwork() {
|
||||||
$scope.events = {
|
this.data = new ContainersNetworkData();
|
||||||
doubleClick : function(event) {
|
this.containers = [];
|
||||||
$scope.$apply( function() {
|
this.selectedContainers = [];
|
||||||
$location.path('/containers/' + event.nodes[0]);
|
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) {
|
var showFailure = function (event) {
|
||||||
Messages.error('Failure', e.data);
|
Messages.error('Failure', e.data);
|
||||||
};
|
};
|
||||||
|
|
||||||
var addContainer = function (container) {
|
var addContainer = function (container) {
|
||||||
$scope.data.add(container);
|
$scope.network.addContainer(container);
|
||||||
};
|
};
|
||||||
|
|
||||||
Container.query({all: 0}, function(d) {
|
Container.query({all: 0}, function(d) {
|
||||||
for (var i = 0; i < d.length; i++) {
|
for (var i = 0; i < d.length; i++) {
|
||||||
Container.get({id: d[i].Id}, addContainer, showFailure);
|
Container.get({id: d[i].Id}, addContainer, showFailure);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
}]);
|
}]);
|
||||||
|
|
Before Width: | Height: | Size: 62 KiB After Width: | Height: | Size: 62 KiB |
|
@ -92,7 +92,8 @@ angular.module('ngVis', [])
|
||||||
scope: {
|
scope: {
|
||||||
data: '=',
|
data: '=',
|
||||||
options: '=',
|
options: '=',
|
||||||
events: '='
|
events: '=',
|
||||||
|
component: '='
|
||||||
},
|
},
|
||||||
link: function (scope, element, attr) {
|
link: function (scope, element, attr) {
|
||||||
var networkEvents = [
|
var networkEvents = [
|
||||||
|
@ -103,6 +104,7 @@ angular.module('ngVis', [])
|
||||||
];
|
];
|
||||||
|
|
||||||
var network = new vis.Network(element[0], scope.data, scope.options);
|
var network = new vis.Network(element[0], scope.data, scope.options);
|
||||||
|
scope.component = network;
|
||||||
|
|
||||||
scope.$watch('data', function () {
|
scope.$watch('data', function () {
|
||||||
// Sanity check
|
// Sanity check
|
||||||
|
@ -118,6 +120,7 @@ angular.module('ngVis', [])
|
||||||
|
|
||||||
// Create the graph2d object
|
// Create the graph2d object
|
||||||
network = new vis.Network(element[0]);
|
network = new vis.Network(element[0]);
|
||||||
|
scope.component = network;
|
||||||
|
|
||||||
// Attach an event handler if defined
|
// Attach an event handler if defined
|
||||||
angular.forEach(scope.events, function (callback, event) {
|
angular.forEach(scope.events, function (callback, event) {
|
||||||
|
|
Loading…
Reference in New Issue