diff --git a/app/app.js b/app/app.js index 222d6decd..8ec6554ba 100644 --- a/app/app.js +++ b/app/app.js @@ -1,4 +1,28 @@ -angular.module('dockerui', ['dockerui.templates', 'ngRoute', 'dockerui.services', 'dockerui.filters', 'masthead', 'footer', 'dashboard', 'container', 'containers', 'containersNetwork', 'images', 'image', 'pullImage', 'startContainer', 'sidebar', 'info', 'builder', 'containerLogs', 'containerTop', 'events', 'stats']) +angular.module('dockerui', [ + 'dockerui.templates', + 'ngRoute', + 'dockerui.services', + 'dockerui.filters', + 'masthead', + 'footer', + 'dashboard', + 'container', + 'containers', + 'containersNetwork', + 'images', + 'image', + 'pullImage', + 'startContainer', + 'sidebar', + 'info', + 'builder', + 'containerLogs', + 'containerTop', + 'events', + 'stats', + 'network', + 'networks', + 'volumes']) .config(['$routeProvider', function ($routeProvider) { 'use strict'; $routeProvider.when('/', { @@ -48,5 +72,4 @@ angular.module('dockerui', ['dockerui.templates', 'ngRoute', 'dockerui.services' // You need to set this to the api endpoint without the port i.e. http://192.168.1.9 .constant('DOCKER_ENDPOINT', 'dockerapi') .constant('DOCKER_PORT', '') // Docker port, leave as an empty string if no port is requred. If you have a port, prefix it with a ':' i.e. :4243 - .constant('UI_VERSION', 'v0.9.0-beta') - .constant('DOCKER_API_VERSION', 'v1.20'); + .constant('UI_VERSION', 'v0.9.0-beta'); diff --git a/app/components/builder/builderController.js b/app/components/builder/builderController.js index 5455aae4a..f89ce4e05 100644 --- a/app/components/builder/builderController.js +++ b/app/components/builder/builderController.js @@ -1,5 +1,5 @@ angular.module('builder', []) - .controller('BuilderController', ['$scope', 'Dockerfile', 'Messages', - function ($scope, Dockerfile, Messages) { + .controller('BuilderController', ['$scope', + function ($scope) { $scope.template = 'app/components/builder/builder.html'; }]); diff --git a/app/components/dashboard/dashboardController.js b/app/components/dashboard/dashboardController.js index 07646c007..e5863dc59 100644 --- a/app/components/dashboard/dashboardController.js +++ b/app/components/dashboard/dashboardController.js @@ -21,6 +21,7 @@ angular.module('dashboard', []) if (Settings.firstLoad) { opts.animation = true; Settings.firstLoad = false; + localStorage.setItem('firstLoad', false); $('#masthead').show(); setTimeout(function () { diff --git a/app/components/footer/footerController.js b/app/components/footer/footerController.js index 021ab5721..3e7cd2532 100644 --- a/app/components/footer/footerController.js +++ b/app/components/footer/footerController.js @@ -1,9 +1,9 @@ angular.module('footer', []) - .controller('FooterController', ['$scope', 'Settings', 'Docker', function ($scope, Settings, Docker) { + .controller('FooterController', ['$scope', 'Settings', 'Version', function ($scope, Settings, Version) { $scope.template = 'app/components/footer/statusbar.html'; $scope.uiVersion = Settings.uiVersion; - Docker.get({}, function (d) { + Version.get({}, function (d) { $scope.apiVersion = d.ApiVersion; }); }]); diff --git a/app/components/info/infoController.js b/app/components/info/infoController.js index 9cbf9fb07..794e03e13 100644 --- a/app/components/info/infoController.js +++ b/app/components/info/infoController.js @@ -1,15 +1,14 @@ angular.module('info', []) - .controller('InfoController', ['$scope', 'System', 'Docker', 'Settings', 'Messages', - function ($scope, System, Docker, Settings, Messages) { + .controller('InfoController', ['$scope', 'Info', 'Version', 'Settings', + function ($scope, Info, Version, Settings) { $scope.info = {}; $scope.docker = {}; $scope.endpoint = Settings.endpoint; - $scope.apiVersion = Settings.version; - Docker.get({}, function (d) { + Version.get({}, function (d) { $scope.docker = d; }); - System.get({}, function (d) { + Info.get({}, function (d) { $scope.info = d; }); }]); diff --git a/app/components/masthead/masthead.html b/app/components/masthead/masthead.html index c7246c80f..0e358e7cb 100644 --- a/app/components/masthead/masthead.html +++ b/app/components/masthead/masthead.html @@ -1,10 +1,21 @@

DockerUI

- + +
+ +
+
+ +
diff --git a/app/components/masthead/mastheadController.js b/app/components/masthead/mastheadController.js index 9d12cba42..713ff8bf3 100644 --- a/app/components/masthead/mastheadController.js +++ b/app/components/masthead/mastheadController.js @@ -1,4 +1,15 @@ angular.module('masthead', []) - .controller('MastheadController', ['$scope', function ($scope) { + .controller('MastheadController', ['$scope', 'Version', function ($scope, Version) { $scope.template = 'app/components/masthead/masthead.html'; + $scope.showNetworksVolumes = false; + + Version.get(function(d) { + if (d.ApiVersion >= 1.21) { + $scope.showNetworksVolumes = true; + } + }); + + $scope.refresh = function() { + location.reload(); + } }]); diff --git a/app/components/network/network.html b/app/components/network/network.html new file mode 100644 index 000000000..46412b329 --- /dev/null +++ b/app/components/network/network.html @@ -0,0 +1,110 @@ +
+ +

Network: {{ network.Name }}

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Name:{{ network.Name }}
Id:{{ network.Id }}
Scope:{{ network.Scope }}
Driver:{{ network.Driver }}
IPAM: + + + + + + + + + + + + + +
Driver:{{ network.IPAM.Driver }}
Subnet:{{ network.IPAM.Config[0].Subnet }}
Gateway:{{ network.IPAM.Config[0].Gateway }}
+
Containers: + + + + + + + + + + + + + + + + + + + + + + +
Id:{{ Id }} + +
EndpointID:{{ container.EndpointID}}
MacAddress:{{ container.MacAddress}}
IPv4Address:{{ container.IPv4Address}}
IPv6Address:{{ container.IPv6Address}}
+
+
+ +
+ +
+
Options: + + + + + + + + + +
KeyValue
{{ k }}{{ v }}
+
+ + +
+ + +
+ +
+
\ No newline at end of file diff --git a/app/components/network/networkController.js b/app/components/network/networkController.js new file mode 100644 index 000000000..cb3cca7d4 --- /dev/null +++ b/app/components/network/networkController.js @@ -0,0 +1,56 @@ +angular.module('network', []).config(['$routeProvider', function ($routeProvider) { + $routeProvider.when('/networks/:id/', { + templateUrl: 'app/components/network/network.html', + controller: 'NetworkController' + }); +}]).controller('NetworkController', ['$scope', 'Network', 'ViewSpinner', 'Messages', '$routeParams', '$location', 'errorMsgFilter', + function ($scope, Network, ViewSpinner, Messages, $routeParams, $location, errorMsgFilter) { + + $scope.disconnect = function disconnect(networkId, containerId) { + ViewSpinner.spin(); + Network.disconnect({id: $routeParams.id}, {Container: containerId}, function (d) { + ViewSpinner.stop(); + Messages.send("Container disconnected", containerId); + $location.path('/networks/' + $routeParams.id); // Refresh the current page. + }, function (e) { + ViewSpinner.stop(); + Messages.error("Failure", e.data); + }); + }; + $scope.connect = function connect(networkId, containerId) { + ViewSpinner.spin(); + Network.connect({id: $routeParams.id}, {Container: containerId}, function (d) { + ViewSpinner.stop(); + var errmsg = errorMsgFilter(d); + if (errmsg) { + Messages.error('Error', errmsg); + } else { + Messages.send("Container connected", d); + } + $location.path('/networks/' + $routeParams.id); // Refresh the current page. + }, function (e) { + ViewSpinner.stop(); + Messages.error("Failure", e.data); + }); + }; + $scope.remove = function remove(networkId) { + ViewSpinner.spin(); + Network.remove({id: $routeParams.id}, function (d) { + ViewSpinner.stop(); + Messages.send("Network removed", d); + $location.path('/networks'); // Go to the networks page + }, function (e) { + ViewSpinner.stop(); + Messages.error("Failure", e.data); + }); + }; + + ViewSpinner.spin(); + Network.get({id: $routeParams.id}, function (d) { + $scope.network = d; + ViewSpinner.stop(); + }, function (e) { + Messages.error("Failure", e.data); + ViewSpinner.stop(); + }); + }]); diff --git a/app/components/networks/networks.html b/app/components/networks/networks.html new file mode 100644 index 000000000..6cfe2eb85 --- /dev/null +++ b/app/components/networks/networks.html @@ -0,0 +1,80 @@ +

Networks:

+ +
+ + +
+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + +
SelectNameIdScopeDriverIPAM DriverIPAM SubnetIPAM Gateway
{{ network.Name|truncate:20}}{{ network.Id }}{{ network.Scope }}{{ network.Driver }}{{ network.IPAM.Driver }}{{ network.IPAM.Config[0].Subnet }}{{ network.IPAM.Config[0].Gateway }}
+
+
+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+ +
+
+
\ No newline at end of file diff --git a/app/components/networks/networksController.js b/app/components/networks/networksController.js new file mode 100644 index 000000000..fa8551cb9 --- /dev/null +++ b/app/components/networks/networksController.js @@ -0,0 +1,82 @@ +angular.module('networks', []).config(['$routeProvider', function ($routeProvider) { + $routeProvider.when('/networks', { + templateUrl: 'app/components/networks/networks.html', + controller: 'NetworksController' + }); +}]).controller('NetworksController', ['$scope', 'Network', 'ViewSpinner', 'Messages', '$route', 'errorMsgFilter', + function ($scope, Network, ViewSpinner, Messages, $route, errorMsgFilter) { + $scope.toggle = false; + $scope.predicate = '-Created'; + $scope.createNetworkConfig = { + "Name": '', + "Driver": '', + "IPAM": { + "Config": [{ + "Subnet": '', + "IPRange": '', + "Gateway": '' + }] + } + }; + + + + $scope.removeAction = function () { + ViewSpinner.spin(); + var counter = 0; + var complete = function () { + counter = counter - 1; + if (counter === 0) { + ViewSpinner.stop(); + } + }; + angular.forEach($scope.networks, function (network) { + if (network.Checked) { + counter = counter + 1; + Network.remove({id: network.Id}, function (d) { + Messages.send("Network deleted", network.Id); + var index = $scope.networks.indexOf(network); + $scope.networks.splice(index, 1); + complete(); + }, function (e) { + Messages.error("Failure", e.data); + complete(); + }); + } + }); + }; + + $scope.toggleSelectAll = function () { + angular.forEach($scope.networks, function (i) { + i.Checked = $scope.toggle; + }); + }; + + $scope.addNetwork = function addNetwork(createNetworkConfig) { + ViewSpinner.spin(); + Network.create(createNetworkConfig, function (d) { + if (d.Id) { + Messages.send("Network created", d.Id); + } else { + Messages.error('Failure', errorMsgFilter(d)); + } + ViewSpinner.stop(); + fetchNetworks(); + }, function (e) { + Messages.error("Failure", e.data); + ViewSpinner.stop(); + }); + }; + + function fetchNetworks() { + ViewSpinner.spin(); + Network.query({}, function (d) { + $scope.networks = d; + ViewSpinner.stop(); + }, function (e) { + Messages.error("Failure", e.data); + ViewSpinner.stop(); + }); + } + fetchNetworks(); + }]); diff --git a/app/components/pullImage/pullImageController.js b/app/components/pullImage/pullImageController.js index 48e05f8c4..d53fe9c2c 100644 --- a/app/components/pullImage/pullImageController.js +++ b/app/components/pullImage/pullImageController.js @@ -1,6 +1,6 @@ angular.module('pullImage', []) - .controller('PullImageController', ['$scope', '$log', 'Dockerfile', 'Messages', 'Image', 'ViewSpinner', - function ($scope, $log, Dockerfile, Messages, Image, ViewSpinner) { + .controller('PullImageController', ['$scope', '$log', 'Messages', 'Image', 'ViewSpinner', + function ($scope, $log, Messages, Image, ViewSpinner) { $scope.template = 'app/components/pullImage/pullImage.html'; $scope.init = function () { diff --git a/app/components/stats/statsController.js b/app/components/stats/statsController.js index 272ed6939..40d0b4159 100644 --- a/app/components/stats/statsController.js +++ b/app/components/stats/statsController.js @@ -1,21 +1,7 @@ angular.module('stats', []) .controller('StatsController', ['Settings', '$scope', 'Messages', '$timeout', 'Container', '$routeParams', 'humansizeFilter', '$sce', function (Settings, $scope, Messages, $timeout, Container, $routeParams, humansizeFilter, $sce) { - // TODO: Implement memory chart, force scale to 0-100 for cpu, 0 to limit for memory, fix charts on dashboard, + // TODO: Force scale to 0-100 for cpu, fix charts on dashboard, // TODO: Force memory scale to 0 - max memory - //var initialStats = {}; // Used to set scale of memory graph. - // - //Container.stats({id: $routeParams.id}, function (d) { - // var arr = Object.keys(d).map(function (key) { - // return d[key]; - // }); - // if (arr.join('').indexOf('no such id') !== -1) { - // Messages.error('Unable to retrieve stats', 'Is this container running?'); - // return; - // } - // initialStats = d; - //}, function () { - // Messages.error('Unable to retrieve stats', 'Is this container running?'); - //}); var cpuLabels = []; var cpuData = []; diff --git a/app/components/volumes/volumes.html b/app/components/volumes/volumes.html new file mode 100644 index 000000000..af8921776 --- /dev/null +++ b/app/components/volumes/volumes.html @@ -0,0 +1,56 @@ +

Volumes:

+ +
+ + +
+ +
+
+ + + + + + + + + + + + + + + + + +
SelectNameDriverMountpoint
{{ volume.Name|truncate:20 }}{{ volume.Driver }}{{ volume.Mountpoint }}
+
+
+
+
+ + +
+
+ + +
+ +
+
+
\ No newline at end of file diff --git a/app/components/volumes/volumesController.js b/app/components/volumes/volumesController.js new file mode 100644 index 000000000..bed59316a --- /dev/null +++ b/app/components/volumes/volumesController.js @@ -0,0 +1,75 @@ +angular.module('volumes', []).config(['$routeProvider', function ($routeProvider) { + $routeProvider.when('/volumes', { + templateUrl: 'app/components/volumes/volumes.html', + controller: 'VolumesController' + }); +}]).controller('VolumesController', ['$scope', 'Volume', 'ViewSpinner', 'Messages', '$route', 'errorMsgFilter', + function ($scope, Volume, ViewSpinner, Messages, $route, errorMsgFilter) { + $scope.toggle = false; + $scope.predicate = '-Created'; + $scope.createVolumeConfig = { + "Name": "", + "Driver": "" + }; + + + + $scope.removeAction = function () { + ViewSpinner.spin(); + var counter = 0; + var complete = function () { + counter = counter - 1; + if (counter === 0) { + ViewSpinner.stop(); + } + }; + angular.forEach($scope.volumes, function (volume) { + if (volume.Checked) { + counter = counter + 1; + Volume.remove({name: volume.Name}, function (d) { + Messages.send("Volume deleted", volume.Name); + var index = $scope.volumes.indexOf(volume); + $scope.volumes.splice(index, 1); + complete(); + }, function (e) { + Messages.error("Failure", e.data); + complete(); + }); + } + }); + }; + + $scope.toggleSelectAll = function () { + angular.forEach($scope.volumes, function (i) { + i.Checked = $scope.toggle; + }); + }; + + $scope.addVolume = function addVolume(createVolumeConfig) { + ViewSpinner.spin(); + Volume.create(createVolumeConfig, function (d) { + if (d.Name) { + Messages.send("Volume created", d.Name); + } else { + Messages.error('Failure', errorMsgFilter(d)); + } + ViewSpinner.stop(); + fetchVolumes(); + }, function (e) { + Messages.error("Failure", e.data); + ViewSpinner.stop(); + }); + }; + + function fetchVolumes() { + ViewSpinner.spin(); + Volume.query({}, function (d) { + $scope.volumes = d.Volumes; + ViewSpinner.stop(); + }, function (e) { + Messages.error("Failure", e.data); + ViewSpinner.stop(); + }); + } + fetchVolumes(); + }]); diff --git a/app/shared/services.js b/app/shared/services.js index 805f1e399..f2b9da742 100644 --- a/app/shared/services.js +++ b/app/shared/services.js @@ -95,7 +95,7 @@ angular.module('dockerui.services', ['ngResource']) remove: {method: 'DELETE', params: {id: '@id'}, isArray: true} }); }]) - .factory('Docker', ['$resource', 'Settings', function DockerFactory($resource, Settings) { + .factory('Version', ['$resource', 'Settings', function VersionFactory($resource, Settings) { 'use strict'; // http://docs.docker.com/reference/api/docker_remote_api_<%= remoteApiVersion %>/#show-the-docker-version-information return $resource(Settings.url + '/version', {}, { @@ -110,27 +110,48 @@ angular.module('dockerui.services', ['ngResource']) update: {method: 'POST'} }); }]) - .factory('System', ['$resource', 'Settings', function SystemFactory($resource, Settings) { + .factory('Info', ['$resource', 'Settings', function InfoFactory($resource, Settings) { 'use strict'; // http://docs.docker.com/reference/api/docker_remote_api_<%= remoteApiVersion %>/#display-system-wide-information return $resource(Settings.url + '/info', {}, { get: {method: 'GET'} }); }]) - .factory('Settings', ['DOCKER_ENDPOINT', 'DOCKER_PORT', 'DOCKER_API_VERSION', 'UI_VERSION', function SettingsFactory(DOCKER_ENDPOINT, DOCKER_PORT, DOCKER_API_VERSION, UI_VERSION) { + .factory('Network', ['$resource', 'Settings', function NetworkFactory($resource, Settings) { + 'use strict'; + // http://docs.docker.com/reference/api/docker_remote_api_<%= remoteApiVersion %>/#2-5-networks + return $resource(Settings.url + '/networks/:id/:action', {id: '@id'}, { + query: {method: 'GET', isArray: true}, + get: {method: 'GET'}, + create: {method: 'POST', params: {action: 'create'}}, + remove: {method: 'DELETE'}, + connect: {method: 'POST', params: {action: 'connect'}}, + disconnect: {method: 'POST', params: {action: 'disconnect'}} + }); + }]) + .factory('Volume', ['$resource', 'Settings', function VolumeFactory($resource, Settings) { + 'use strict'; + // http://docs.docker.com/reference/api/docker_remote_api_<%= remoteApiVersion %>/#2-5-networks + return $resource(Settings.url + '/volumes/:name/:action', {name: '@name'}, { + query: {method: 'GET'}, + get: {method: 'GET'}, + create: {method: 'POST', params: {action: 'create'}}, + remove: {method: 'DELETE'} + }); + }]) + .factory('Settings', ['DOCKER_ENDPOINT', 'DOCKER_PORT', 'UI_VERSION', function SettingsFactory(DOCKER_ENDPOINT, DOCKER_PORT, UI_VERSION) { 'use strict'; var url = DOCKER_ENDPOINT; if (DOCKER_PORT) { url = url + DOCKER_PORT + '\\' + DOCKER_PORT; } + var firstLoad = (localStorage.getItem('firstLoad') || 'true') === 'true'; return { displayAll: false, endpoint: DOCKER_ENDPOINT, - version: DOCKER_API_VERSION, - rawUrl: DOCKER_ENDPOINT + DOCKER_PORT + '/' + DOCKER_API_VERSION, uiVersion: UI_VERSION, url: url, - firstLoad: true + firstLoad: firstLoad }; }]) .factory('ViewSpinner', function ViewSpinnerFactory() { @@ -176,23 +197,6 @@ angular.module('dockerui.services', ['ngResource']) } }; }]) - .factory('Dockerfile', ['Settings', function DockerfileFactory(Settings) { - 'use strict'; - // http://docs.docker.com/reference/api/docker_remote_api_<%= remoteApiVersion %>/#build-image-from-a-dockerfile - var url = Settings.rawUrl + '/build'; - return { - build: function (file, callback) { - var data = new FormData(); - var dockerfile = new Blob([file], {type: 'text/text'}); - data.append('Dockerfile', dockerfile); - - var request = new XMLHttpRequest(); - request.onload = callback; - request.open('POST', url); - request.send(data); - } - }; - }]) .factory('LineChart', ['Settings', function LineChartFactory(Settings) { 'use strict'; return { @@ -222,7 +226,7 @@ angular.module('dockerui.services', ['ngResource']) labels.push(k); data.push(map[k]); if (map[k] > max) { - max = map[k]; + max = map[k]; } } var steps = Math.min(max, 10); diff --git a/gruntFile.js b/gruntFile.js index f184ed9c7..605539f4d 100644 --- a/gruntFile.js +++ b/gruntFile.js @@ -14,8 +14,27 @@ module.exports = function (grunt) { // Default task. grunt.registerTask('default', ['jshint', 'build', 'karma:unit']); - grunt.registerTask('build', ['clean:app', 'if:binaryNotExist', 'html2js', 'concat', 'clean:tmpl', 'recess:build', 'copy']); - grunt.registerTask('release', ['clean:all', 'if:binaryNotExist', 'html2js', 'uglify', 'clean:tmpl', 'jshint', 'karma:unit', 'concat:index', 'recess:min', 'copy']); + grunt.registerTask('build', [ + 'clean:app', + 'if:binaryNotExist', + 'html2js', + 'concat', + 'clean:tmpl', + 'recess:build', + 'copy' + ]); + grunt.registerTask('release', [ + 'clean:all', + 'if:binaryNotExist', + 'html2js', + 'uglify', + 'clean:tmpl', + 'jshint', + 'karma:unit', + 'concat:index', + 'recess:min', + 'copy' + ]); grunt.registerTask('test-watch', ['karma:watch']); grunt.registerTask('run', ['if:binaryNotExist', 'build', 'shell:buildImage', 'shell:run']); grunt.registerTask('run-dev', ['if:binaryNotExist', 'shell:buildImage', 'shell:run', 'watch:build']); diff --git a/test/unit/app/components/networkController.spec.js b/test/unit/app/components/networkController.spec.js new file mode 100644 index 000000000..649a66630 --- /dev/null +++ b/test/unit/app/components/networkController.spec.js @@ -0,0 +1,83 @@ +describe('NetworkController', function () { + var $scope, $httpBackend, $routeParams; + + beforeEach(module('dockerui')); + beforeEach(inject(function (_$httpBackend_, $controller, _$routeParams_) { + $scope = {}; + $httpBackend = _$httpBackend_; + $routeParams = _$routeParams_; + $routeParams.id = 'f1e1ce1613ccd374a75caf5e2c3ab35520d1944f91498c1974ec86fb4019c79b'; + $controller('NetworkController', { + '$scope': $scope, + '$routeParams': $routeParams + }); + })); + + it('initializes correctly', function () { + expectGetNetwork(); + $httpBackend.flush(); + }); + + it('issues a correct connect call to the remote API', function () { + expectGetNetwork(); + $httpBackend.expectPOST('dockerapi/networks/f1e1ce1613ccd374a75caf5e2c3ab35520d1944f91498c1974ec86fb4019c79b/connect', {'Container': 'containerId'}).respond(200); + $scope.connect($routeParams.id, 'containerId'); + $httpBackend.flush(); + }); + it('issues a correct disconnect call to the remote API', function () { + expectGetNetwork(); + $httpBackend.expectPOST('dockerapi/networks/f1e1ce1613ccd374a75caf5e2c3ab35520d1944f91498c1974ec86fb4019c79b/disconnect', {'Container': 'containerId'}).respond(200); + $scope.disconnect($routeParams.id, 'containerId'); + $httpBackend.flush(); + }); + it('issues a correct remove call to the remote API', function () { + expectGetNetwork(); + $httpBackend.expectDELETE('dockerapi/networks/f1e1ce1613ccd374a75caf5e2c3ab35520d1944f91498c1974ec86fb4019c79b').respond(204); + $scope.remove($routeParams.id); + $httpBackend.flush(); + }); + + function expectGetNetwork() { + $httpBackend.expectGET('dockerapi/networks/f1e1ce1613ccd374a75caf5e2c3ab35520d1944f91498c1974ec86fb4019c79b').respond({ + "Name": "bridge", + "Id": "f1e1ce1613ccd374a75caf5e2c3ab35520d1944f91498c1974ec86fb4019c79b", + "Scope": "local", + "Driver": "bridge", + "IPAM": { + "Driver": "default", + "Config": [{ + "Subnet": "172.17.0.1/16", + "Gateway": "172.17.0.1" + }] + }, + "Containers": { + "727fe76cd0bd65033baab3045508784a166fbc67d177e91c1874b6b29eae946a": { + "EndpointID": "c17ec80e2cfc8eaedc7737b7bb6f954adff439767197ef89c4a5b4127d07b267", + "MacAddress": "02:42:ac:11:00:03", + "IPv4Address": "172.17.0.3/16", + "IPv6Address": "" + }, + "8c32c2446c3dfe0defac2dc8b5fd927cd394f15e08051c677a681bf36877175b": { + "EndpointID": "cf7e795c978ab194d1af4a3efdc177d84c075582ba30a7cff414c7d516236af1", + "MacAddress": "02:42:ac:11:00:04", + "IPv4Address": "172.17.0.4/16", + "IPv6Address": "" + }, + "cfe81fc97b1f857fdb3061fe487a064b8b57d8f112910954ac16910400d2e058": { + "EndpointID": "611929ffcff2ced1db8e88f77e009c4fb4a4736395251cd97553b242e2e23bf1", + "MacAddress": "02:42:ac:11:00:02", + "IPv4Address": "172.17.0.2/16", + "IPv6Address": "" + } + }, + "Options": { + "com.docker.network.bridge.default_bridge": "true", + "com.docker.network.bridge.enable_icc": "true", + "com.docker.network.bridge.enable_ip_masquerade": "true", + "com.docker.network.bridge.host_binding_ipv4": "0.0.0.0", + "com.docker.network.bridge.name": "docker0", + "com.docker.network.driver.mtu": "1500" + } + }); + } +}); \ No newline at end of file diff --git a/test/unit/app/components/networksController.spec.js b/test/unit/app/components/networksController.spec.js new file mode 100644 index 000000000..0f3ce3280 --- /dev/null +++ b/test/unit/app/components/networksController.spec.js @@ -0,0 +1,107 @@ +describe('NetworksController', function () { + var $scope, $httpBackend, $routeParams; + + beforeEach(module('dockerui')); + beforeEach(inject(function (_$httpBackend_, $controller, _$routeParams_) { + $scope = {}; + $httpBackend = _$httpBackend_; + $routeParams = _$routeParams_; + $controller('NetworksController', { + '$scope': $scope, + '$routeParams': $routeParams + }); + })); + + it('initializes correctly', function () { + expectGetNetwork(); + $httpBackend.flush(); + }); + + + it('issues correct remove calls to the remote API', function () { + expectGetNetwork(); + $httpBackend.flush(); + $scope.networks[0].Checked = true; + $scope.networks[2].Checked = true; + $httpBackend.expectDELETE('dockerapi/networks/f2de39df4171b0dc801e8002d1d999b77256983dfc63041c0f34030aa3977566').respond(204); + $httpBackend.expectDELETE('dockerapi/networks/13e871235c677f196c4e1ecebb9dc733b9b2d2ab589e30c539efeda84a24215e').respond(204); + $scope.removeAction(); + $httpBackend.flush(); + }); + it('issues a correct network creation call to the remote API', function () { + expectGetNetwork(); + var createBody = { + "Name":"isolated_nw", + "Driver":"bridge", + "IPAM":{ + "Config":[{ + "Subnet":"172.20.0.0/16", + "IPRange":"172.20.10.0/24", + "Gateway":"172.20.10.11" + }] + }}; + $httpBackend.expectPOST('dockerapi/networks/create', createBody).respond(201); + expectGetNetwork(); + $scope.addNetwork(createBody); + $httpBackend.flush(); + }); + + function expectGetNetwork() { + $httpBackend.expectGET('dockerapi/networks').respond([ + { + "Name": "bridge", + "Id": "f2de39df4171b0dc801e8002d1d999b77256983dfc63041c0f34030aa3977566", + "Scope": "local", + "Driver": "bridge", + "IPAM": { + "Driver": "default", + "Config": [ + { + "Subnet": "172.17.0.0/16" + } + ] + }, + "Containers": { + "39b69226f9d79f5634485fb236a23b2fe4e96a0a94128390a7fbbcc167065867": { + "EndpointID": "ed2419a97c1d9954d05b46e462e7002ea552f216e9b136b80a7db8d98b442eda", + "MacAddress": "02:42:ac:11:00:02", + "IPv4Address": "172.17.0.2/16", + "IPv6Address": "" + } + }, + "Options": { + "com.docker.network.bridge.default_bridge": "true", + "com.docker.network.bridge.enable_icc": "true", + "com.docker.network.bridge.enable_ip_masquerade": "true", + "com.docker.network.bridge.host_binding_ipv4": "0.0.0.0", + "com.docker.network.bridge.name": "docker0", + "com.docker.network.driver.mtu": "1500" + } + }, + { + "Name": "none", + "Id": "e086a3893b05ab69242d3c44e49483a3bbbd3a26b46baa8f61ab797c1088d794", + "Scope": "local", + "Driver": "null", + "IPAM": { + "Driver": "default", + "Config": [] + }, + "Containers": {}, + "Options": {} + }, + { + "Name": "host", + "Id": "13e871235c677f196c4e1ecebb9dc733b9b2d2ab589e30c539efeda84a24215e", + "Scope": "local", + "Driver": "host", + "IPAM": { + "Driver": "default", + "Config": [] + }, + "Containers": {}, + "Options": {} + } + ]); + } +}); \ No newline at end of file diff --git a/test/unit/app/components/volumesController.spec.js b/test/unit/app/components/volumesController.spec.js new file mode 100644 index 000000000..ef567c07a --- /dev/null +++ b/test/unit/app/components/volumesController.spec.js @@ -0,0 +1,64 @@ +describe('VolumesController', function () { + var $scope, $httpBackend, $routeParams; + + beforeEach(module('dockerui')); + beforeEach(inject(function (_$httpBackend_, $controller, _$routeParams_) { + $scope = {}; + $httpBackend = _$httpBackend_; + $routeParams = _$routeParams_; + $controller('VolumesController', { + '$scope': $scope, + '$routeParams': $routeParams + }); + })); + + it('initializes correctly', function () { + expectGetVolumes(); + $httpBackend.flush(); + }); + + + it('issues correct remove calls to the remote API', function () { + expectGetVolumes(); + $httpBackend.flush(); + $scope.volumes[0].Checked = true; + $scope.volumes[2].Checked = true; + $httpBackend.expectDELETE('dockerapi/volumes/tardis').respond(200); + $httpBackend.expectDELETE('dockerapi/volumes/bar').respond(200); + $scope.removeAction(); + $httpBackend.flush(); + }); + it('issues a correct volume creation call to the remote API', function () { + expectGetVolumes(); + var createBody = { + "Name": "tardis", + "Driver": "local" + }; + $httpBackend.expectPOST('dockerapi/volumes/create', createBody).respond(201); + expectGetVolumes(); + $scope.addVolume(createBody); + $httpBackend.flush(); + }); + + function expectGetVolumes() { + $httpBackend.expectGET('dockerapi/volumes').respond({ + "Volumes": [ + { + "Name": "tardis", + "Driver": "local", + "Mountpoint": "/var/lib/docker/volumes/tardis" + }, + { + "Name": "foo", + "Driver": "local", + "Mountpoint": "/var/lib/docker/volumes/foo" + }, + { + "Name": "bar", + "Driver": "local", + "Mountpoint": "/var/lib/docker/volumes/bar" + } + ] + }); + } +}); \ No newline at end of file