Merge pull request #108 from rabelenda/master
Add a containers network tab, visualize links and volumesFrom relations between containers.pull/2/head
|
@ -41,6 +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/)
|
||||
|
||||
|
||||
### Todo:
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
angular.module('dockerui', ['dockerui.templates', 'ngRoute', 'dockerui.services', 'dockerui.filters', 'masthead', 'footer', 'dashboard', 'container', 'containers', 'images', 'image', 'startContainer', 'sidebar', 'info', 'builder', 'containerLogs', 'containerTop'])
|
||||
angular.module('dockerui', ['dockerui.templates', 'ngRoute', 'dockerui.services', 'dockerui.filters', 'masthead', 'footer', 'dashboard', 'container', 'containers', 'containersNetwork', 'images', 'image', 'startContainer', 'sidebar', 'info', 'builder', 'containerLogs', 'containerTop'])
|
||||
.config(['$routeProvider', function ($routeProvider) {
|
||||
'use strict';
|
||||
$routeProvider.when('/', {
|
||||
|
@ -21,6 +21,10 @@ angular.module('dockerui', ['dockerui.templates', 'ngRoute', 'dockerui.services'
|
|||
templateUrl: 'app/components/containerTop/containerTop.html',
|
||||
controller: 'ContainerTopController'
|
||||
});
|
||||
$routeProvider.when('/containers_network', {
|
||||
templateUrl: 'app/components/containersNetwork/containersNetwork.html',
|
||||
controller: 'ContainersNetworkController'
|
||||
});
|
||||
$routeProvider.when('/images/', {
|
||||
templateUrl: 'app/components/images/images.html',
|
||||
controller: 'ImagesController'
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
<div class="detail">
|
||||
<h2>Containers Network</h2>
|
||||
|
||||
<div>
|
||||
<vis-network data="data" options="options" events="events"/>
|
||||
</div>
|
||||
</div>
|
|
@ -0,0 +1,96 @@
|
|||
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
|
||||
}
|
||||
}
|
||||
};
|
||||
$scope.data = new ContainersNetwork();
|
||||
$scope.events = {
|
||||
doubleClick : function(event) {
|
||||
$scope.$apply( function() {
|
||||
$location.path('/containers/' + event.nodes[0]);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
function ContainersNetwork() {
|
||||
this.containers = [];
|
||||
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.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.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'}});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function ContainerNode(data) {
|
||||
this.Id = data.Id;
|
||||
this.Name = data.Name.substring(1, data.Name.length);
|
||||
var dataLinks = data.HostConfig.Links;
|
||||
if (dataLinks != null) {
|
||||
this.Links = [];
|
||||
for (var i = 0; i < dataLinks.length; i++) {
|
||||
// links have the following format: /TargetContainerName:/SourceContainerName/LinkAlias
|
||||
var link = dataLinks[i].split(":");
|
||||
var target = link[0].split("/")[1];
|
||||
var alias = link[1].split("/")[2];
|
||||
// only keep shortest alias
|
||||
if (this.Links[target] == null || alias.length < this.Links[target].length) {
|
||||
this.Links[target] = alias;
|
||||
}
|
||||
}
|
||||
}
|
||||
var dataVolumes = data.HostConfig.VolumesFrom;
|
||||
//converting array into properties for simpler and faster access
|
||||
if (dataVolumes != null) {
|
||||
this.VolumesFrom = [];
|
||||
for (var i = 0; i < dataVolumes.length; i++) {
|
||||
this.VolumesFrom[dataVolumes[i]] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Container.query({all: 0}, function(d) {
|
||||
for (var i = 0; i < d.length; i++) {
|
||||
Container.get({id: d[i].Id}, function(d) {
|
||||
$scope.data.add(d);
|
||||
}, function(e) {
|
||||
Messages.error('Failure', e.data);
|
||||
});
|
||||
|
||||
}
|
||||
});
|
||||
}]);
|
|
@ -3,6 +3,7 @@
|
|||
<ul class="nav well">
|
||||
<li><a href="#">Dashboard</a></li>
|
||||
<li><a href="#/containers/">Containers</a></li>
|
||||
<li><a href="#/containers_network/">Containers Network</a></li>
|
||||
<li><a href="#/images/">Images</a></li>
|
||||
<li><a href="#/info/">Info</a></li>
|
||||
</ul>
|
||||
|
|
After Width: | Height: | Size: 4.4 KiB |
After Width: | Height: | Size: 4.4 KiB |
After Width: | Height: | Size: 4.0 KiB |
After Width: | Height: | Size: 4.2 KiB |
After Width: | Height: | Size: 4.4 KiB |
After Width: | Height: | Size: 4.4 KiB |
After Width: | Height: | Size: 4.4 KiB |
|
@ -0,0 +1,230 @@
|
|||
angular.module('ngVis', [])
|
||||
|
||||
.factory('VisDataSet', function () {
|
||||
'use strict';
|
||||
return function (data, options) {
|
||||
// Create the new dataSets
|
||||
return new vis.DataSet(data, options);
|
||||
};
|
||||
})
|
||||
|
||||
/**
|
||||
* TimeLine directive
|
||||
*/
|
||||
.directive('visTimeline', function () {
|
||||
'use strict';
|
||||
return {
|
||||
restrict: 'EA',
|
||||
transclude: false,
|
||||
scope: {
|
||||
data: '=',
|
||||
options: '=',
|
||||
events: '='
|
||||
},
|
||||
link: function (scope, element, attr) {
|
||||
var timelineEvents = [
|
||||
'rangechange',
|
||||
'rangechanged',
|
||||
'timechange',
|
||||
'timechanged'
|
||||
];
|
||||
|
||||
// Declare the timeline
|
||||
var timeline = null;
|
||||
|
||||
scope.$watch('data', function () {
|
||||
// Sanity check
|
||||
if (scope.data == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If we've actually changed the data set, then recreate the graph
|
||||
// We can always update the data by adding more data to the existing data set
|
||||
if (timeline != null) {
|
||||
timeline.destroy();
|
||||
}
|
||||
|
||||
// Create the timeline object
|
||||
timeline = new vis.Timeline(element[0]);
|
||||
|
||||
// Attach an event handler if defined
|
||||
angular.forEach(scope.events, function (callback, event) {
|
||||
if (timelineEvents.indexOf(String(event)) >= 0) {
|
||||
timeline.on(event, callback);
|
||||
}
|
||||
});
|
||||
|
||||
// Set the options first
|
||||
timeline.setOptions(scope.options);
|
||||
|
||||
// Add groups and items
|
||||
if (scope.data.groups != null) {
|
||||
timeline.setGroups(scope.data.groups);
|
||||
}
|
||||
if (scope.data.items != null) {
|
||||
timeline.setItems(scope.data.items);
|
||||
}
|
||||
|
||||
// onLoad callback
|
||||
if (scope.events != null && scope.events.onload != null && angular.isFunction(scope.events.onload)) {
|
||||
scope.events.onload(timeline);
|
||||
}
|
||||
});
|
||||
|
||||
scope.$watchCollection('options', function (options) {
|
||||
if(timeline == null) {
|
||||
return;
|
||||
}
|
||||
timeline.setOptions(options);
|
||||
});
|
||||
}
|
||||
};
|
||||
})
|
||||
|
||||
|
||||
/**
|
||||
* Directive for network chart.
|
||||
*/
|
||||
.directive('visNetwork', function () {
|
||||
return {
|
||||
restrict: 'EA',
|
||||
transclude: false,
|
||||
scope: {
|
||||
data: '=',
|
||||
options: '=',
|
||||
events: '='
|
||||
},
|
||||
link: function (scope, element, attr) {
|
||||
var networkEvents = [
|
||||
'rangechange',
|
||||
'rangechanged',
|
||||
'timechange',
|
||||
'timechanged'
|
||||
];
|
||||
|
||||
var network = new vis.Network(element[0], scope.data, scope.options);
|
||||
|
||||
scope.$watch('data', function () {
|
||||
// Sanity check
|
||||
if (scope.data == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If we've actually changed the data set, then recreate the graph
|
||||
// We can always update the data by adding more data to the existing data set
|
||||
// if (network !== undefined) {
|
||||
// network.destroy();
|
||||
// }
|
||||
|
||||
// Create the graph2d object
|
||||
network = new vis.Network(element[0]);
|
||||
|
||||
// Attach an event handler if defined
|
||||
angular.forEach(scope.events, function (callback, event) {
|
||||
if (networkEvents.indexOf(String(event)) >= 0) {
|
||||
network.on(event, callback);
|
||||
}
|
||||
});
|
||||
|
||||
// Set the options first
|
||||
network.setOptions(scope.options);
|
||||
network.setData(scope.data);
|
||||
|
||||
|
||||
// onLoad callback
|
||||
// if (scope.events != null && scope.events.onload != null && angular.isFunction(scope.events.onload)) {
|
||||
// scope.events.onload(graph);
|
||||
// }
|
||||
});
|
||||
|
||||
scope.$watchCollection('options', function (options) {
|
||||
if(network == null) {
|
||||
return;
|
||||
}
|
||||
network.setOptions(options);
|
||||
});
|
||||
|
||||
scope.$watch('events', function (events) {
|
||||
angular.forEach(events, function (callback, event) {
|
||||
if (['select', 'click', 'hoverNode', 'doubleClick'].indexOf(String(event)) >= 0) {
|
||||
network.on(event, callback);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
})
|
||||
|
||||
/**
|
||||
* Directive for graph2d.
|
||||
*/
|
||||
.directive('visGraph2d', function () {
|
||||
'use strict';
|
||||
return {
|
||||
restrict: 'EA',
|
||||
transclude: false,
|
||||
scope: {
|
||||
data: '=',
|
||||
options: '=',
|
||||
events: '='
|
||||
},
|
||||
link: function (scope, element, attr) {
|
||||
var graphEvents = [
|
||||
'rangechange',
|
||||
'rangechanged',
|
||||
'timechange',
|
||||
'timechanged'
|
||||
];
|
||||
|
||||
// Create the chart
|
||||
var graph = new vis.Graph2d(element[0]);
|
||||
|
||||
scope.$watch('data', function () {
|
||||
// Sanity check
|
||||
if (scope.data == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If we've actually changed the data set, then recreate the graph
|
||||
// We can always update the data by adding more data to the existing data set
|
||||
if (graph != null) {
|
||||
graph.destroy();
|
||||
}
|
||||
|
||||
// Create the graph2d object
|
||||
graph = new vis.Graph2d(element[0]);
|
||||
|
||||
// Attach an event handler if defined
|
||||
angular.forEach(scope.events, function (callback, event) {
|
||||
if (graphEvents.indexOf(String(event)) >= 0) {
|
||||
graph.on(event, callback);
|
||||
}
|
||||
});
|
||||
|
||||
// Set the options first
|
||||
graph.setOptions(scope.options);
|
||||
|
||||
// Add groups and items
|
||||
if (scope.data.groups != null) {
|
||||
graph.setGroups(scope.data.groups);
|
||||
}
|
||||
if (scope.data.items != null) {
|
||||
graph.setItems(scope.data.items);
|
||||
}
|
||||
|
||||
// onLoad callback
|
||||
if (scope.events != null && scope.events.onload != null && angular.isFunction(scope.events.onload)) {
|
||||
scope.events.onload(graph);
|
||||
}
|
||||
});
|
||||
|
||||
scope.$watchCollection('options', function (options) {
|
||||
if(graph == null) {
|
||||
return;
|
||||
}
|
||||
graph.setOptions(options);
|
||||
});
|
||||
}
|
||||
};
|
||||
})
|
||||
;
|
|
@ -9,6 +9,7 @@
|
|||
|
||||
<link href="assets/css/bootstrap.min.css" rel="stylesheet">
|
||||
<link href="assets/css/jquery.gritter.css" rel="stylesheet">
|
||||
<link href="assets/css/vis.min.css" rel="stylesheet">
|
||||
|
||||
<link href="<%= pkg.name %>.css" rel="stylesheet">
|
||||
|
||||
|
@ -27,6 +28,8 @@
|
|||
<script src="assets/js/jquery.gritter.min.js"></script>
|
||||
<script src="assets/js/Chart.min.js"></script>
|
||||
<script src="assets/js/legend.js"></script>
|
||||
<script src="assets/js/vis.min.js"></script>
|
||||
<script src="assets/js/angular-vis.js"></script>
|
||||
|
||||
<script src="<%= pkg.name %>.js"></script>
|
||||
|
||||
|
|