From a712d5b77e07479e35d034df155bdb15c5878e6f Mon Sep 17 00:00:00 2001 From: Kevan Ahlquist Date: Thu, 17 Dec 2015 23:35:04 -0600 Subject: [PATCH 1/7] Progress on Networks. --- app/app.js | 24 +++++- app/components/masthead/masthead.html | 1 + app/components/networks/networks.html | 80 +++++++++++++++++++ app/components/networks/networksController.js | 50 ++++++++++++ app/components/stats/statsController.js | 16 +--- app/shared/services.js | 10 +++ 6 files changed, 165 insertions(+), 16 deletions(-) create mode 100644 app/components/networks/networks.html create mode 100644 app/components/networks/networksController.js diff --git a/app/app.js b/app/app.js index 222d6decd..acc9c0f7f 100644 --- a/app/app.js +++ b/app/app.js @@ -1,4 +1,26 @@ -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', + 'networks']) .config(['$routeProvider', function ($routeProvider) { 'use strict'; $routeProvider.when('/', { diff --git a/app/components/masthead/masthead.html b/app/components/masthead/masthead.html index c7246c80f..3bb8a0267 100644 --- a/app/components/masthead/masthead.html +++ b/app/components/masthead/masthead.html @@ -5,6 +5,7 @@
  • Containers
  • Containers Network
  • Images
  • +
  • Networks
  • Info
  • diff --git a/app/components/networks/networks.html b/app/components/networks/networks.html new file mode 100644 index 000000000..d6958146a --- /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..c2a10c7e6 --- /dev/null +++ b/app/components/networks/networksController.js @@ -0,0 +1,50 @@ +angular.module('networks', []).config(['$routeProvider', function ($routeProvider) { + $routeProvider.when('/networks', { + templateUrl: 'app/components/networks/networks.html', + controller: 'NetworksController' + }); +}]).controller('NetworksController', ['$scope', 'Network', 'ViewSpinner', 'Messages', + function ($scope, Network, ViewSpinner, Messages) { + $scope.toggle = false; + //$scope.predicate = '-Created'; + + $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", resource.Deleted); + 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.images, function (i) { + i.Checked = $scope.toggle; + }); + }; + + ViewSpinner.spin(); + Network.query({}, function (d) { + $scope.networks = d; + ViewSpinner.stop(); + }, function (e) { + Messages.error("Failure", e.data); + ViewSpinner.stop(); + }); + }]); 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/shared/services.js b/app/shared/services.js index 780726fd5..c19e20304 100644 --- a/app/shared/services.js +++ b/app/shared/services.js @@ -117,6 +117,16 @@ angular.module('dockerui.services', ['ngResource']) get: {method: 'GET'} }); }]) + .factory('Network', ['$resource', 'Settings', function NetworksFactory($resource, Settings) { + 'use strict'; + // http://docs.docker.com/reference/api/docker_remote_api_<%= remoteApiVersion %>/#2-5-networks + return $resource(Settings.url + '/networks/:id/:action', {}, { + query: {method: 'GET', isArray: true}, + get: {method: 'GET'}, + create: {method: 'POST', params: {action: 'create'}}, + remove: {method: 'DELETE', params: {id: '@id'}} + }); + }]) .factory('Settings', ['DOCKER_ENDPOINT', 'DOCKER_PORT', 'DOCKER_API_VERSION', 'UI_VERSION', function SettingsFactory(DOCKER_ENDPOINT, DOCKER_PORT, DOCKER_API_VERSION, UI_VERSION) { 'use strict'; var url = DOCKER_ENDPOINT; From 8a4be8b93a84257af36f9f855355911e0b26c3ff Mon Sep 17 00:00:00 2001 From: Kevan Ahlquist Date: Sun, 20 Dec 2015 19:13:53 -0600 Subject: [PATCH 2/7] Implemented single network view, connect, disconnect, remove. --- app/app.js | 1 + app/components/network/network.html | 110 ++++++++++++++++++ app/components/network/networkController.js | 51 ++++++++ app/shared/services.js | 8 +- .../app/components/networkController.spec.js | 83 +++++++++++++ 5 files changed, 250 insertions(+), 3 deletions(-) create mode 100644 app/components/network/network.html create mode 100644 app/components/network/networkController.js create mode 100644 test/unit/app/components/networkController.spec.js diff --git a/app/app.js b/app/app.js index acc9c0f7f..3f2e9a1fa 100644 --- a/app/app.js +++ b/app/app.js @@ -20,6 +20,7 @@ angular.module('dockerui', [ 'containerTop', 'events', 'stats', + 'network', 'networks']) .config(['$routeProvider', function ($routeProvider) { 'use strict'; 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..eea43950b --- /dev/null +++ b/app/components/network/networkController.js @@ -0,0 +1,51 @@ +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', + function ($scope, Network, ViewSpinner, Messages, $routeParams, $location) { + + $scope.disconnect = function disconnect(networkId, containerId) { + ViewSpinner.spin(); + Network.disconnect({id: $routeParams.id}, {Container: containerId}, function (d) { + ViewSpinner.stop(); + Messages.send("Container disconnected", d); + $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(); + 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/shared/services.js b/app/shared/services.js index c19e20304..e758312ea 100644 --- a/app/shared/services.js +++ b/app/shared/services.js @@ -120,11 +120,13 @@ angular.module('dockerui.services', ['ngResource']) .factory('Network', ['$resource', 'Settings', function NetworksFactory($resource, Settings) { 'use strict'; // http://docs.docker.com/reference/api/docker_remote_api_<%= remoteApiVersion %>/#2-5-networks - return $resource(Settings.url + '/networks/:id/:action', {}, { + 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', params: {id: '@id'}} + remove: {method: 'DELETE'}, + connect: {method: 'POST', params: {action: 'connect'}}, + disconnect: {method: 'POST', params: {action: 'disconnect'}} }); }]) .factory('Settings', ['DOCKER_ENDPOINT', 'DOCKER_PORT', 'DOCKER_API_VERSION', 'UI_VERSION', function SettingsFactory(DOCKER_ENDPOINT, DOCKER_PORT, DOCKER_API_VERSION, UI_VERSION) { @@ -232,7 +234,7 @@ angular.module('dockerui.services', ['ngResource']) labels.push(k); data.push(map[k]); if (map[k] > max) { - max = map[k]; + max = map[k]; } } var dataset = { 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 From 5f4641af67ab2ddeffbb385ee720ab744d7e772f Mon Sep 17 00:00:00 2001 From: Kevan Ahlquist Date: Sun, 20 Dec 2015 20:07:57 -0600 Subject: [PATCH 3/7] Catch plaintext errors for creation and connection, implement network creation, --- app/components/network/networkController.js | 13 ++- app/components/networks/networks.html | 8 +- app/components/networks/networksController.js | 58 +++++++--- .../app/components/networksController.spec.js | 107 ++++++++++++++++++ 4 files changed, 165 insertions(+), 21 deletions(-) create mode 100644 test/unit/app/components/networksController.spec.js diff --git a/app/components/network/networkController.js b/app/components/network/networkController.js index eea43950b..cb3cca7d4 100644 --- a/app/components/network/networkController.js +++ b/app/components/network/networkController.js @@ -3,14 +3,14 @@ angular.module('network', []).config(['$routeProvider', function ($routeProvider templateUrl: 'app/components/network/network.html', controller: 'NetworkController' }); -}]).controller('NetworkController', ['$scope', 'Network', 'ViewSpinner', 'Messages', '$routeParams', '$location', - function ($scope, Network, ViewSpinner, Messages, $routeParams, $location) { +}]).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", d); + Messages.send("Container disconnected", containerId); $location.path('/networks/' + $routeParams.id); // Refresh the current page. }, function (e) { ViewSpinner.stop(); @@ -21,7 +21,12 @@ angular.module('network', []).config(['$routeProvider', function ($routeProvider ViewSpinner.spin(); Network.connect({id: $routeParams.id}, {Container: containerId}, function (d) { ViewSpinner.stop(); - Messages.send("Container connected", d); + 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(); diff --git a/app/components/networks/networks.html b/app/components/networks/networks.html index d6958146a..6cfe2eb85 100644 --- a/app/components/networks/networks.html +++ b/app/components/networks/networks.html @@ -32,7 +32,7 @@ - + {{ network.Name|truncate:20}} {{ network.Id }} {{ network.Scope }} @@ -59,17 +59,17 @@
    + ng-model="createNetworkConfig.IPAM.Config[0].Subnet" class="form-control"/>
    + ng-model="createNetworkConfig.IPAM.Config[0].IPRange" class="form-control"/>
    + ng-model="createNetworkConfig.IPAM.Config[0].Gateway" class="form-control"/>
    + + + \ 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 3f48132b5..e0775de67 100644 --- a/app/shared/services.js +++ b/app/shared/services.js @@ -117,7 +117,7 @@ angular.module('dockerui.services', ['ngResource']) get: {method: 'GET'} }); }]) - .factory('Network', ['$resource', 'Settings', function NetworksFactory($resource, Settings) { + .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'}, { @@ -129,6 +129,16 @@ angular.module('dockerui.services', ['ngResource']) 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; 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 From fe7646e939623eee73eb7f4911c386814bd9df88 Mon Sep 17 00:00:00 2001 From: Kevan Ahlquist Date: Sun, 10 Jan 2016 20:02:04 -0600 Subject: [PATCH 6/7] Use localStorage to only show 'learn more' banner on first load. --- app/components/dashboard/dashboardController.js | 1 + app/shared/services.js | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) 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/shared/services.js b/app/shared/services.js index e3e73a480..f2b9da742 100644 --- a/app/shared/services.js +++ b/app/shared/services.js @@ -145,12 +145,13 @@ angular.module('dockerui.services', ['ngResource']) if (DOCKER_PORT) { url = url + DOCKER_PORT + '\\' + DOCKER_PORT; } + var firstLoad = (localStorage.getItem('firstLoad') || 'true') === 'true'; return { displayAll: false, endpoint: DOCKER_ENDPOINT, uiVersion: UI_VERSION, url: url, - firstLoad: true + firstLoad: firstLoad }; }]) .factory('ViewSpinner', function ViewSpinnerFactory() { From 2683ddb1ee6ce8679d3f15d44c34700b755759c5 Mon Sep 17 00:00:00 2001 From: Kevan Ahlquist Date: Sun, 10 Jan 2016 20:36:22 -0600 Subject: [PATCH 7/7] Add refresh button. --- app/components/masthead/masthead.html | 27 ++++++++++++------- app/components/masthead/mastheadController.js | 4 +++ 2 files changed, 22 insertions(+), 9 deletions(-) diff --git a/app/components/masthead/masthead.html b/app/components/masthead/masthead.html index 7ff8766a0..0e358e7cb 100644 --- a/app/components/masthead/masthead.html +++ b/app/components/masthead/masthead.html @@ -1,12 +1,21 @@

    DockerUI

    - + + +
    + +
    diff --git a/app/components/masthead/mastheadController.js b/app/components/masthead/mastheadController.js index ae375cab4..713ff8bf3 100644 --- a/app/components/masthead/mastheadController.js +++ b/app/components/masthead/mastheadController.js @@ -8,4 +8,8 @@ angular.module('masthead', []) $scope.showNetworksVolumes = true; } }); + + $scope.refresh = function() { + location.reload(); + } }]);