diff --git a/README.md b/README.md index 58c6a787c..c84a7cd1e 100644 --- a/README.md +++ b/README.md @@ -26,6 +26,14 @@ You can try out the public demo instance: http://demo.portainer.io/ (login with Please note that the public demo cluster is **reset every 15min**. +Alternatively, you can deploy a copy of the demo stack inside a [play-with-docker (PWD)](https://labs.play-with-docker.com) playground: + +- Browse [PWD/?stack=portainer-demo/play-with-docker/docker-stack.yml](http://play-with-docker.com/?stack=https://raw.githubusercontent.com/portainer/portainer-demo/master/play-with-docker/docker-stack.yml) +- Sign in with your [Docker ID](https://docs.docker.com/docker-id) +- Follow [these](https://github.com/portainer/portainer-demo/blob/master/play-with-docker/docker-stack.yml#L5-L8) steps. + +Unlike the public demo, the playground sessions are deleted after 4 hours. Apart from that, all the settings are same, including default credentials. + ## Getting started * [Deploy Portainer](https://portainer.readthedocs.io/en/latest/deployment.html) diff --git a/api/bolt/migrate_dbversion6.go b/api/bolt/migrate_dbversion6.go new file mode 100644 index 000000000..95d53af61 --- /dev/null +++ b/api/bolt/migrate_dbversion6.go @@ -0,0 +1,16 @@ +package bolt + +func (m *Migrator) updateSettingsToVersion7() error { + legacySettings, err := m.SettingsService.Settings() + if err != nil { + return err + } + legacySettings.DisplayDonationHeader = true + + err = m.SettingsService.StoreSettings(legacySettings) + if err != nil { + return err + } + + return nil +} diff --git a/api/bolt/migrator.go b/api/bolt/migrator.go index 8a42cedc6..f74a29b34 100644 --- a/api/bolt/migrator.go +++ b/api/bolt/migrator.go @@ -81,6 +81,14 @@ func (m *Migrator) Migrate() error { } } + // https://github.com/portainer/portainer/issues/1449 + if m.CurrentDBVersion < 7 { + err := m.updateSettingsToVersion7() + if err != nil { + return err + } + } + err := m.VersionService.StoreDBVersion(portainer.DBVersion) if err != nil { return err diff --git a/api/http/handler/handler.go b/api/http/handler/handler.go index 5128a125a..24367f360 100644 --- a/api/http/handler/handler.go +++ b/api/http/handler/handler.go @@ -48,7 +48,7 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { case strings.HasPrefix(r.URL.Path, "/api/dockerhub"): http.StripPrefix("/api", h.DockerHubHandler).ServeHTTP(w, r) case strings.HasPrefix(r.URL.Path, "/api/endpoints"): - if strings.Contains(r.URL.Path, "/docker") { + if strings.Contains(r.URL.Path, "/docker/") { http.StripPrefix("/api/endpoints", h.DockerHandler).ServeHTTP(w, r) } else if strings.Contains(r.URL.Path, "/stacks") { http.StripPrefix("/api/endpoints", h.StackHandler).ServeHTTP(w, r) diff --git a/api/portainer.go b/api/portainer.go index 751043810..8fc8588ef 100644 --- a/api/portainer.go +++ b/api/portainer.go @@ -390,9 +390,9 @@ type ( const ( // APIVersion is the version number of the Portainer API. - APIVersion = "1.15.3" + APIVersion = "1.15.4" // DBVersion is the version number of the Portainer database. - DBVersion = 6 + DBVersion = 7 // DefaultTemplatesURL represents the default URL for the templates definitions. DefaultTemplatesURL = "https://raw.githubusercontent.com/portainer/templates/master/templates.json" ) diff --git a/api/swagger.yaml b/api/swagger.yaml index 28504caf4..f2646abb4 100644 --- a/api/swagger.yaml +++ b/api/swagger.yaml @@ -56,7 +56,7 @@ info: **NOTE**: You can find more information on how to query the Docker API in the [Docker official documentation](https://docs.docker.com/engine/api/v1.30/) as well as in [this Portainer example](https://gist.github.com/deviantony/77026d402366b4b43fa5918d41bc42f8). - version: "1.15.3" + version: "1.15.4" title: "Portainer API" contact: email: "info@portainer.io" @@ -1869,7 +1869,7 @@ definitions: description: "Is analytics enabled" Version: type: "string" - example: "1.15.3" + example: "1.15.4" description: "Portainer API version" PublicSettingsInspectResponse: type: "object" diff --git a/app/__module.js b/app/__module.js index 54ab077ce..9e02e45c8 100644 --- a/app/__module.js +++ b/app/__module.js @@ -9,6 +9,7 @@ angular.module('portainer', [ 'LocalStorageModule', 'angular-jwt', 'angular-google-analytics', + 'ui', 'angular-loading-bar', 'portainer.templates', 'portainer.filters', diff --git a/app/components/configs/configs.html b/app/components/configs/configs.html index 07093c63d..ab2c0cea4 100644 --- a/app/components/configs/configs.html +++ b/app/components/configs/configs.html @@ -8,73 +8,13 @@
-
- - - - -
- - Add config -
-
- -
-
- -
- - - - - - - - - - - - - - - - - - - - - -
- - - - Name - - - - - - Created at - - - - - - Ownership - - - -
{{ config.Name }}{{ config.CreatedAt | getisodate }} - - - {{ config.ResourceControl.Ownership ? config.ResourceControl.Ownership : config.ResourceControl.Ownership = 'public' }} - -
Loading...
No configs available.
-
- -
-
-
- +
+
diff --git a/app/components/configs/configsController.js b/app/components/configs/configsController.js index d777aaf61..908a59002 100644 --- a/app/components/configs/configsController.js +++ b/app/components/configs/configsController.js @@ -1,47 +1,25 @@ angular.module('configs', []) -.controller('ConfigsController', ['$scope', '$stateParams', '$state', 'ConfigService', 'Notifications', 'Pagination', -function ($scope, $stateParams, $state, ConfigService, Notifications, Pagination) { - $scope.state = {}; - $scope.state.selectedItemCount = 0; - $scope.state.pagination_count = Pagination.getPaginationCount('configs'); - $scope.sortType = 'Name'; - $scope.sortReverse = false; - - $scope.order = function (sortType) { - $scope.sortReverse = ($scope.sortType === sortType) ? !$scope.sortReverse : false; - $scope.sortType = sortType; - }; - - $scope.selectItems = function (allSelected) { - angular.forEach($scope.state.filteredConfigs, function (config) { - if (config.Checked !== allSelected) { - config.Checked = allSelected; - $scope.selectItem(config); - } - }); - }; - - $scope.selectItem = function (item) { - if (item.Checked) { - $scope.state.selectedItemCount++; - } else { - $scope.state.selectedItemCount--; - } - }; - - $scope.removeAction = function () { - angular.forEach($scope.configs, function (config) { - if (config.Checked) { - ConfigService.remove(config.Id) - .then(function success() { - Notifications.success('Config deleted', config.Id); - var index = $scope.configs.indexOf(config); - $scope.configs.splice(index, 1); - }) - .catch(function error(err) { - Notifications.error('Failure', err, 'Unable to remove config'); - }); - } +.controller('ConfigsController', ['$scope', '$state', 'ConfigService', 'Notifications', +function ($scope, $state, ConfigService, Notifications) { + + $scope.removeAction = function (selectedItems) { + var actionCount = selectedItems.length; + angular.forEach(selectedItems, function (config) { + ConfigService.remove(config.Id) + .then(function success() { + Notifications.success('Config successfully removed', config.Name); + var index = $scope.configs.indexOf(config); + $scope.configs.splice(index, 1); + }) + .catch(function error(err) { + Notifications.error('Failure', err, 'Unable to remove config'); + }) + .finally(function final() { + --actionCount; + if (actionCount === 0) { + $state.reload(); + } + }); }); }; diff --git a/app/components/container/container.html b/app/components/container/container.html index f3f19fa93..08843a9f7 100644 --- a/app/components/container/container.html +++ b/app/components/container/container.html @@ -12,17 +12,20 @@
- - - - - - - + + + + + + +
- - + +
@@ -81,10 +84,10 @@
- Stats - Logs - Console - Inspect + Stats + Logs + Console + Inspect
@@ -267,70 +270,16 @@
-
- - -
- Items per page: - -
-
- - - - - - - - - - - - - - - - - - - - - -
Network NameIP AddressGatewayMacAddressActions
{{ key }}{{ value.IPAddress || '-' }}{{ value.Gateway || '-' }}{{ value.MacAddress || '-' }} - -
No networks connected.
-
- -
-
-
- -
- -
- -
-
- -
-
-
-
-
+
+
diff --git a/app/components/container/containerController.js b/app/components/container/containerController.js index c75f6894a..3df165542 100644 --- a/app/components/container/containerController.js +++ b/app/components/container/containerController.js @@ -1,20 +1,18 @@ angular.module('container', []) -.controller('ContainerController', ['$q', '$scope', '$state','$transition$', '$filter', 'Container', 'ContainerCommit', 'ContainerHelper', 'ContainerService', 'ImageHelper', 'Network', 'NetworkService', 'Notifications', 'Pagination', 'ModalService', 'ResourceControlService', 'RegistryService', 'ImageService', -function ($q, $scope, $state, $transition$, $filter, Container, ContainerCommit, ContainerHelper, ContainerService, ImageHelper, Network, NetworkService, Notifications, Pagination, ModalService, ResourceControlService, RegistryService, ImageService) { +.controller('ContainerController', ['$q', '$scope', '$state','$transition$', '$filter', 'Container', 'ContainerCommit', 'ContainerHelper', 'ContainerService', 'ImageHelper', 'Network', 'NetworkService', 'Notifications', 'ModalService', 'ResourceControlService', 'RegistryService', 'ImageService', +function ($q, $scope, $state, $transition$, $filter, Container, ContainerCommit, ContainerHelper, ContainerService, ImageHelper, Network, NetworkService, Notifications, ModalService, ResourceControlService, RegistryService, ImageService) { $scope.activityTime = 0; $scope.portBindings = []; + $scope.config = { Image: '', Registry: '' }; - $scope.state = { - joinNetworkInProgress: false, - leaveNetworkInProgress: false, - pagination_count: Pagination.getPaginationCount('container_networks') - }; - $scope.changePaginationCount = function() { - Pagination.setPaginationCount('container_networks', $scope.state.pagination_count); + $scope.state = { + recreateContainerInProgress: false, + joinNetworkInProgress: false, + leaveNetworkInProgress: false }; var update = function () { @@ -207,6 +205,7 @@ function ($q, $scope, $state, $transition$, $filter, Container, ContainerCommit, function recreateContainer(pullImage) { var container = $scope.container; var config = ContainerHelper.configFromContainer(container.Model); + $scope.state.recreateContainerInProgress = true; ContainerService.remove(container, true) .then(function success() { return RegistryService.retrieveRegistryFromRepository(container.Config.Image); @@ -239,6 +238,7 @@ function ($q, $scope, $state, $transition$, $filter, Container, ContainerCommit, }) .catch(function error(err) { Notifications.error('Failure', err, 'Unable to re-create container'); + $scope.state.recreateContainerInProgress = false; }); } diff --git a/app/components/containerStats/containerStats.html b/app/components/containerStats/containerStats.html index c95dcfafa..f6702d680 100644 --- a/app/components/containerStats/containerStats.html +++ b/app/components/containerStats/containerStats.html @@ -51,7 +51,6 @@
- @@ -81,49 +80,13 @@
+
- - -
- Items per page: - -
-
- - - - - - - - - - - - - - - - - - -
- - {{ title }} - - - -
{{ procInfo }}
Loading...
No processes available.
-
- -
-
-
+
diff --git a/app/components/containerStats/containerStatsController.js b/app/components/containerStats/containerStatsController.js index 53f324c3d..9faf4751b 100644 --- a/app/components/containerStats/containerStatsController.js +++ b/app/components/containerStats/containerStatsController.js @@ -1,25 +1,12 @@ angular.module('containerStats', []) -.controller('ContainerStatsController', ['$q', '$scope', '$transition$', '$document', '$interval', 'ContainerService', 'ChartService', 'Notifications', 'Pagination', -function ($q, $scope, $transition$, $document, $interval, ContainerService, ChartService, Notifications, Pagination) { +.controller('ContainerStatsController', ['$q', '$scope', '$transition$', '$document', '$interval', 'ContainerService', 'ChartService', 'Notifications', +function ($q, $scope, $transition$, $document, $interval, ContainerService, ChartService, Notifications) { $scope.state = { refreshRate: '5', networkStatsUnavailable: false }; - $scope.state.pagination_count = Pagination.getPaginationCount('stats_processes'); - $scope.sortType = 'CMD'; - $scope.sortReverse = false; - - $scope.order = function (sortType) { - $scope.sortReverse = ($scope.sortType === sortType) ? !$scope.sortReverse : false; - $scope.sortType = sortType; - }; - - $scope.changePaginationCount = function() { - Pagination.setPaginationCount('stats_processes', $scope.state.pagination_count); - }; - $scope.$on('$destroy', function() { stopRepeater(); }); diff --git a/app/components/containers/containers.html b/app/components/containers/containers.html index d8bfcd681..da72b72de 100644 --- a/app/components/containers/containers.html +++ b/app/components/containers/containers.html @@ -7,145 +7,23 @@ Containers -
- - -
- Items per page: - -
-
- -
-
- - - - - - - -
- Add container -
-
- - -
-
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - State - - - - - - Name - - - - - - - - - Stack - - - - - - Image - - - - - - IP Address - - - - - - Host IP - - - - - - Published Ports - - - - - - Ownership - - - -
- {{ container.Status }} - {{ container.Status }} - {{ container|swarmcontainername|truncate: truncate_size}}{{ container|containername|truncate: truncate_size}}{{ container.StackName ? container.StackName : '-' }}{{ container.Image | hideshasum }}{{ container.IP ? container.IP : '-' }}{{ container.hostIP }} - - {{p.public}}:{{ p.private }} - - - - - - - {{ container.ResourceControl.Ownership ? container.ResourceControl.Ownership : container.ResourceControl.Ownership = 'public' }} - -
Loading...
No containers available.
-
- -
-
-
-
+
+
+ +
diff --git a/app/components/containers/containersController.js b/app/components/containers/containersController.js index 420eedc1d..988aaa991 100644 --- a/app/components/containers/containersController.js +++ b/app/components/containers/containersController.js @@ -1,219 +1,108 @@ angular.module('containers', []) - .controller('ContainersController', ['$q', '$scope', '$state', '$filter', 'Container', 'ContainerService', 'ContainerHelper', 'SystemService', 'Notifications', 'Pagination', 'EntityListService', 'ModalService', 'ResourceControlService', 'EndpointProvider', 'LocalStorage', - function ($q, $scope, $state, $filter, Container, ContainerService, ContainerHelper, SystemService, Notifications, Pagination, EntityListService, ModalService, ResourceControlService, EndpointProvider, LocalStorage) { - $scope.state = {}; - $scope.state.pagination_count = Pagination.getPaginationCount('containers'); - $scope.state.displayAll = LocalStorage.getFilterContainerShowAll(); - $scope.state.displayIP = false; - $scope.sortType = 'State'; - $scope.sortReverse = false; - $scope.state.selectedItemCount = 0; - $scope.truncate_size = 40; - $scope.showMore = true; - - $scope.order = function (sortType) { - $scope.sortReverse = ($scope.sortType === sortType) ? !$scope.sortReverse : false; - $scope.sortType = sortType; - }; - $scope.PublicURL = EndpointProvider.endpointPublicURL(); - - $scope.changePaginationCount = function() { - Pagination.setPaginationCount('containers', $scope.state.pagination_count); + .controller('ContainersController', ['$q', '$scope', '$state', '$filter', '$transition$', 'ContainerService', 'SystemService', 'Notifications', 'ModalService', 'EndpointProvider', + function ($q, $scope, $state, $filter, $transition$, ContainerService, SystemService, Notifications, ModalService, EndpointProvider) { + $scope.state = { + publicURL: EndpointProvider.endpointPublicURL() }; - $scope.cleanAssociatedVolumes = false; - - var update = function (data) { - $scope.state.selectedItemCount = 0; - Container.query(data, function (d) { - var containers = d; - $scope.containers = containers.map(function (container) { - var model = new ContainerViewModel(container); - model.Status = $filter('containerstatus')(model.Status); - - EntityListService.rememberPreviousSelection($scope.containers, model, function onSelect(model){ - $scope.selectItem(model); - }); - - if (model.IP) { - $scope.state.displayIP = true; - } - if ($scope.applicationState.endpoint.mode.provider === 'DOCKER_SWARM') { - model.hostIP = $scope.swarm_hosts[_.split(container.Names[0], '/')[1]]; - } - return model; - }); - updateSelectionFlags(); - }, function (e) { - Notifications.error('Failure', e, 'Unable to retrieve containers'); - $scope.containers = []; - }); + $scope.startAction = function(selectedItems) { + var successMessage = 'Container successfully started'; + var errorMessage = 'Unable to start container'; + executeActionOnContainerList(selectedItems, ContainerService.startContainer, successMessage, errorMessage); }; - var batch = function (items, action, msg) { - var counter = 0; - var complete = function () { - counter = counter - 1; - if (counter === 0) { - update({all: $scope.state.displayAll ? 1 : 0}); - } - }; - angular.forEach(items, function (c) { - if (c.Checked) { - counter = counter + 1; - if (action === Container.start) { - action({id: c.Id}, {}, function (d) { - Notifications.success('Container ' + msg, c.Id); - complete(); - }, function (e) { - Notifications.error('Failure', e, 'Unable to start container'); - complete(); - }); - } - else if (action === Container.remove) { - ContainerService.remove(c, $scope.cleanAssociatedVolumes) - .then(function success() { - var index = items.indexOf(c); - items.splice(index, 1); - Notifications.success('Container successfully removed'); - complete(); - }) - .catch(function error(err) { - Notifications.error('Failure', err, 'Unable to remove container'); - complete(); - }); - } - else if (action === Container.pause) { - action({id: c.Id}, function (d) { - if (d.message) { - Notifications.success('Container is already paused', c.Id); - } else { - Notifications.success('Container ' + msg, c.Id); - } - complete(); - }, function (e) { - Notifications.error('Failure', e, 'Unable to pause container'); - complete(); - }); - } - else { - action({id: c.Id}, function (d) { - Notifications.success('Container ' + msg, c.Id); - complete(); - }, function (e) { - Notifications.error('Failure', e, 'An error occured'); - complete(); - }); - } - } - }); + $scope.stopAction = function(selectedItems) { + var successMessage = 'Container successfully stopped'; + var errorMessage = 'Unable to stop container'; + executeActionOnContainerList(selectedItems, ContainerService.stopContainer, successMessage, errorMessage); }; - $scope.selectItems = function (allSelected) { - angular.forEach($scope.state.filteredContainers, function (container) { - if (container.Checked !== allSelected) { - container.Checked = allSelected; - toggleItemSelection(container); - } - }); - updateSelectionFlags(); + $scope.restartAction = function(selectedItems) { + var successMessage = 'Container successfully restarted'; + var errorMessage = 'Unable to restart container'; + executeActionOnContainerList(selectedItems, ContainerService.restartContainer, successMessage, errorMessage); }; - $scope.selectItem = function (item) { - toggleItemSelection(item); - updateSelectionFlags(); + $scope.killAction = function(selectedItems) { + var successMessage = 'Container successfully killed'; + var errorMessage = 'Unable to kill container'; + executeActionOnContainerList(selectedItems, ContainerService.killContainer, successMessage, errorMessage); }; - $scope.toggleGetAll = function () { - LocalStorage.storeFilterContainerShowAll($scope.state.displayAll); - update({all: $scope.state.displayAll ? 1 : 0}); + $scope.pauseAction = function(selectedItems) { + var successMessage = 'Container successfully paused'; + var errorMessage = 'Unable to pause container'; + executeActionOnContainerList(selectedItems, ContainerService.pauseContainer, successMessage, errorMessage); }; - $scope.startAction = function () { - batch($scope.containers, Container.start, 'Started'); + $scope.resumeAction = function(selectedItems) { + var successMessage = 'Container successfully resumed'; + var errorMessage = 'Unable to resume container'; + executeActionOnContainerList(selectedItems, ContainerService.resumeContainer, successMessage, errorMessage); }; - $scope.stopAction = function () { - batch($scope.containers, Container.stop, 'Stopped'); - }; - - $scope.restartAction = function () { - batch($scope.containers, Container.restart, 'Restarted'); - }; - - $scope.killAction = function () { - batch($scope.containers, Container.kill, 'Killed'); - }; - - $scope.pauseAction = function () { - batch($scope.containers, Container.pause, 'Paused'); - }; - - $scope.unpauseAction = function () { - batch($scope.containers, Container.unpause, 'Unpaused'); - }; - - $scope.removeAction = function () { - batch($scope.containers, Container.remove, 'Removed'); - }; - - - $scope.truncateMore = function(size) { - $scope.truncate_size = 80; - $scope.showMore = false; - }; - - $scope.confirmRemoveAction = function () { + $scope.confirmRemoveAction = function(selectedItems) { var isOneContainerRunning = false; - angular.forEach($scope.containers, function (c) { - if (c.Checked && c.State === 'running') { + for (var i = 0; i < selectedItems.length; i++) { + var container = selectedItems[i]; + if (container.State === 'running') { isOneContainerRunning = true; - return; + break; } - }); + } + var title = 'You are about to remove one or more container.'; if (isOneContainerRunning) { - title = 'You are about to remove one or more running containers.'; + title = 'You are about to remove one or more running container.'; } - ModalService.confirmContainerDeletion( - title, - function (result) { + + ModalService.confirmContainerDeletion(title, function (result) { if(!result) { return; } - $scope.cleanAssociatedVolumes = false; + var cleanVolumes = false; if (result[0]) { - $scope.cleanAssociatedVolumes = true; + cleanVolumes = true; } - $scope.removeAction(); + removeAction(selectedItems, cleanVolumes); } ); }; - function toggleItemSelection(item) { - if (item.Checked) { - $scope.state.selectedItemCount++; - } else { - $scope.state.selectedItemCount--; - } + function executeActionOnContainerList(containers, action, successMessage, errorMessage) { + var actionCount = containers.length; + angular.forEach(containers, function (container) { + action(container.Id) + .then(function success() { + Notifications.success(successMessage, container.Names[0]); + }) + .catch(function error(err) { + Notifications.error('Failure', err, errorMessage); + }) + .finally(function final() { + --actionCount; + if (actionCount === 0) { + $state.transitionTo($state.current, { selectedContainers: containers }, { reload: true }); + } + }); + }); } - function updateSelectionFlags() { - $scope.state.noStoppedItemsSelected = true; - $scope.state.noRunningItemsSelected = true; - $scope.state.noPausedItemsSelected = true; - $scope.containers.forEach(function(container) { - if(!container.Checked) { - return; - } - - if(container.Status === 'paused') { - $scope.state.noPausedItemsSelected = false; - } else if(container.Status === 'stopped' || - container.Status === 'created') { - $scope.state.noStoppedItemsSelected = false; - } else if(container.Status === 'running') { - $scope.state.noRunningItemsSelected = false; - } - } ); + function removeAction(containers, cleanVolumes) { + var actionCount = containers.length; + angular.forEach(containers, function (container) { + ContainerService.remove(container, cleanVolumes) + .then(function success() { + Notifications.success('Container successfully removed', container.Names[0]); + }) + .catch(function error(err) { + Notifications.error('Failure', err, 'Unable to remove container'); + }) + .finally(function final() { + --actionCount; + if (actionCount === 0) { + $state.reload(); + } + }); + }); } function retrieveSwarmHostsInfo(data) { @@ -231,17 +120,42 @@ angular.module('containers', []) return swarm_hosts; } + function assignContainers(containers, provider) { + var previouslySelectedContainers = $transition$.params().selectedContainers || []; + $scope.containers = containers.map(function (container) { + container.Status = $filter('containerstatus')(container.Status); + if (provider === 'DOCKER_SWARM') { + container.hostIP = $scope.swarm_hosts[_.split(container.Names[0], '/')[1]]; + } + + var previousContainer = _.find(previouslySelectedContainers, function(item) { + return item.Id === container.Id; + }); + + if (previousContainer && previousContainer.Checked) { + container.Checked = true; + } + + return container; + }); + } + function initView() { var provider = $scope.applicationState.endpoint.mode.provider; - $q.when(provider !== 'DOCKER_SWARM' || SystemService.info()) + + $q.all({ + swarm: provider !== 'DOCKER_SWARM' || SystemService.info(), + containers: ContainerService.containers(1) + }) .then(function success(data) { if (provider === 'DOCKER_SWARM') { - $scope.swarm_hosts = retrieveSwarmHostsInfo(data); + $scope.swarm_hosts = retrieveSwarmHostsInfo(data.swarm); } - update({all: $scope.state.displayAll ? 1 : 0}); + assignContainers(data.containers, provider); }) .catch(function error(err) { - Notifications.error('Failure', err, 'Unable to retrieve cluster information'); + Notifications.error('Failure', err, 'Unable to retrieve containers'); + $scope.containers = []; }); } diff --git a/app/components/createContainer/createcontainer.html b/app/components/createContainer/createcontainer.html index 32b329c94..8c9098368 100644 --- a/app/components/createContainer/createcontainer.html +++ b/app/components/createContainer/createcontainer.html @@ -523,7 +523,7 @@ Memory reservation
- +
@@ -541,7 +541,7 @@ Memory limit
- +
@@ -559,7 +559,7 @@ CPU limit
- +

diff --git a/app/components/createService/createServiceController.js b/app/components/createService/createServiceController.js index 09ff3805a..ab85f14ed 100644 --- a/app/components/createService/createServiceController.js +++ b/app/components/createService/createServiceController.js @@ -24,7 +24,7 @@ function ($q, $scope, $state, $timeout, Service, ServiceHelper, ConfigService, C Parallelism: 1, PlacementConstraints: [], PlacementPreferences: [], - UpdateDelay: 0, + UpdateDelay: '0s', UpdateOrder: 'stop-first', FailureAction: 'pause', Secrets: [], @@ -35,7 +35,11 @@ function ($q, $scope, $state, $timeout, Service, ServiceHelper, ConfigService, C MemoryLimit: 0, MemoryReservation: 0, MemoryLimitUnit: 'MB', - MemoryReservationUnit: 'MB' + MemoryReservationUnit: 'MB', + RestartCondition: 'any', + RestartDelay: '5s', + RestartMaxAttempts: 0, + RestartWindow: '0s' }; $scope.state = { @@ -243,12 +247,21 @@ function ($q, $scope, $state, $timeout, Service, ServiceHelper, ConfigService, C function prepareUpdateConfig(config, input) { config.UpdateConfig = { Parallelism: input.Parallelism || 0, - Delay: input.UpdateDelay * 1000000000 || 0, + Delay: ServiceHelper.translateHumanDurationToNanos(input.UpdateDelay) || 0, FailureAction: input.FailureAction, Order: input.UpdateOrder }; } + function prepareRestartPolicy(config, input) { + config.TaskTemplate.RestartPolicy = { + Condition: input.RestartCondition || 'any', + Delay: ServiceHelper.translateHumanDurationToNanos(input.RestartDelay) || 5000000000, + MaxAttempts: input.RestartMaxAttempts || 0, + Window: ServiceHelper.translateHumanDurationToNanos(input.RestartWindow) || 0 + }; + } + function preparePlacementConfig(config, input) { config.TaskTemplate.Placement.Constraints = ServiceHelper.translateKeyValueToPlacementConstraints(input.PlacementConstraints); config.TaskTemplate.Placement.Preferences = ServiceHelper.translateKeyValueToPlacementPreferences(input.PlacementPreferences); @@ -348,6 +361,7 @@ function ($q, $scope, $state, $timeout, Service, ServiceHelper, ConfigService, C preparePlacementConfig(config, input); prepareResourcesCpuConfig(config, input); prepareResourcesMemoryConfig(config, input); + prepareRestartPolicy(config, input); return config; } diff --git a/app/components/createService/createservice.html b/app/components/createService/createservice.html index 196e97506..9a86ee73c 100644 --- a/app/components/createService/createservice.html +++ b/app/components/createService/createservice.html @@ -130,7 +130,7 @@

  • Volumes
  • Network
  • Labels
  • -
  • Update config
  • +
  • Update config & Restart
  • Secrets
  • Configs
  • Resources & Placement
  • @@ -372,71 +372,7 @@
    -
    -
    - -
    - -
    - -
    -
    -

    - Maximum number of tasks to be updated simultaneously (0 to update all at once). -

    -
    -
    - - -
    - -
    - -
    -
    -

    - Amount of time between updates. Time in seconds. -

    -
    -
    - - -
    - -
    -
    - - -
    -
    -
    -

    - Action taken on failure to start after update. -

    -
    - -
    - - -
    - - -
    -
    - - -
    -
    -
    -

    - Operation order on failure. -

    -
    - -
    - -
    -
    +
    diff --git a/app/components/createService/includes/resources-placement.html b/app/components/createService/includes/resources-placement.html index 17fc9b6a9..8ff522dc3 100644 --- a/app/components/createService/includes/resources-placement.html +++ b/app/components/createService/includes/resources-placement.html @@ -8,7 +8,7 @@ Memory reservation
    - +
    @@ -26,7 +26,7 @@ Memory limit
    - +
    @@ -44,7 +44,7 @@ CPU reservation
    - +

    @@ -59,7 +59,7 @@ CPU limit

    - +

    diff --git a/app/components/createService/includes/update-restart.html b/app/components/createService/includes/update-restart.html new file mode 100644 index 000000000..da1529b20 --- /dev/null +++ b/app/components/createService/includes/update-restart.html @@ -0,0 +1,132 @@ +

    +
    + Update config +
    + +
    + +
    + +
    +
    +

    + Maximum number of tasks to be updated simultaneously (0 to update all at once). +

    +
    +
    + + +
    + +
    + +
    +
    +

    + Amount of time between updates expressed by a number followed by unit (ns|us|ms|s|m|h). Default value is 0s, 0 seconds. +

    +
    +
    + + +
    + +
    +
    + + +
    +
    +
    +

    + Action taken on failure to start after update. +

    +
    +
    + + +
    + +
    +
    + + +
    +
    +
    +

    + Operation order on failure. +

    +
    +
    + + +
    + Restart policy +
    + +
    + +
    +
    + + + +
    +
    +
    +

    + Restart when condition is met (default condition "any"). +

    +
    +
    + + +
    + +
    + +
    +
    +

    + Delay between restart attempts expressed by a number followed by unit (ns|us|ms|s|m|h). Default value is 5s, 5 seconds. +

    +
    +
    + + +
    + +
    + +
    +
    +

    + Maximum attempts to restart a given task before giving up (default value is 0, which means unlimited). +

    +
    +
    + + +
    + +
    + +
    +
    +

    + Time window to evaluate restart attempts expressed by a number followed by unit (ns|us|ms|s|m|h). Default value is 0 seconds, which is unbounded. +

    +
    +
    + +
    \ No newline at end of file diff --git a/app/components/dashboard/dashboard.html b/app/components/dashboard/dashboard.html index 98abf59a2..b961e1266 100644 --- a/app/components/dashboard/dashboard.html +++ b/app/components/dashboard/dashboard.html @@ -75,6 +75,13 @@ Nodes in the cluster {{ infoData.Swarm.Nodes }} + + + + + diff --git a/app/components/endpoints/endpoints.html b/app/components/endpoints/endpoints.html index 921a00397..743befc91 100644 --- a/app/components/endpoints/endpoints.html +++ b/app/components/endpoints/endpoints.html @@ -67,93 +67,26 @@
    - +
    -
    - - - - -
    -
    - -
    -
    - - -
    - Items per page: - -
    -
    - -
    - -
    -
    - -
    -
    - -
    - - - - - - - - - - - - - - - - - - - - - - - -
    - - - - Name - - - - - - URL - - - -
    {{ endpoint.Name }}{{ endpoint.URL | stripprotocol }} - - Edit - - - Manage access - -
    Loading...
    No endpoints available.
    -
    - -
    -
    + +
    + +
    +
    + +
    +
    diff --git a/app/components/endpoints/endpointsController.js b/app/components/endpoints/endpointsController.js index 8a33f34b4..01c205663 100644 --- a/app/components/endpoints/endpointsController.js +++ b/app/components/endpoints/endpointsController.js @@ -1,14 +1,10 @@ angular.module('endpoints', []) -.controller('EndpointsController', ['$scope', '$state', '$filter', 'EndpointService', 'EndpointProvider', 'Notifications', 'Pagination', -function ($scope, $state, $filter, EndpointService, EndpointProvider, Notifications, Pagination) { +.controller('EndpointsController', ['$scope', '$state', '$filter', 'EndpointService', 'Notifications', +function ($scope, $state, $filter, EndpointService, Notifications) { $scope.state = { uploadInProgress: false, - selectedItemCount: 0, - pagination_count: Pagination.getPaginationCount('endpoints'), actionInProgress: false }; - $scope.sortType = 'Name'; - $scope.sortReverse = true; $scope.formValues = { Name: '', @@ -17,32 +13,6 @@ function ($scope, $state, $filter, EndpointService, EndpointProvider, Notificati SecurityFormData: new EndpointSecurityFormData() }; - $scope.order = function(sortType) { - $scope.sortReverse = ($scope.sortType === sortType) ? !$scope.sortReverse : false; - $scope.sortType = sortType; - }; - - $scope.changePaginationCount = function() { - Pagination.setPaginationCount('endpoints', $scope.state.pagination_count); - }; - - $scope.selectItems = function (allSelected) { - angular.forEach($scope.state.filteredEndpoints, function (endpoint) { - if (endpoint.Checked !== allSelected) { - endpoint.Checked = allSelected; - $scope.selectItem(endpoint); - } - }); - }; - - $scope.selectItem = function (item) { - if (item.Checked) { - $scope.state.selectedItemCount++; - } else { - $scope.state.selectedItemCount--; - } - }; - $scope.addEndpoint = function() { var name = $scope.formValues.Name; var URL = $filter('stripprotocol')($scope.formValues.URL); @@ -75,17 +45,24 @@ function ($scope, $state, $filter, EndpointService, EndpointProvider, Notificati }); }; - $scope.removeAction = function () { - angular.forEach($scope.endpoints, function (endpoint) { - if (endpoint.Checked) { - EndpointService.deleteEndpoint(endpoint.Id).then(function success(data) { - Notifications.success('Endpoint deleted', endpoint.Name); - var index = $scope.endpoints.indexOf(endpoint); - $scope.endpoints.splice(index, 1); - }, function error(err) { - Notifications.error('Failure', err, 'Unable to remove endpoint'); - }); - } + $scope.removeAction = function (selectedItems) { + var actionCount = selectedItems.length; + angular.forEach(selectedItems, function (endpoint) { + EndpointService.deleteEndpoint(endpoint.Id) + .then(function success() { + Notifications.success('Endpoint successfully removed', endpoint.Name); + var index = $scope.endpoints.indexOf(endpoint); + $scope.endpoints.splice(index, 1); + }) + .catch(function error(err) { + Notifications.error('Failure', err, 'Unable to remove endpoint'); + }) + .finally(function final() { + --actionCount; + if (actionCount === 0) { + $state.reload(); + } + }); }); }; diff --git a/app/components/events/events.html b/app/components/events/events.html index 1170728be..415a89500 100644 --- a/app/components/events/events.html +++ b/app/components/events/events.html @@ -8,66 +8,12 @@
    -
    - - -
    - Items per page: - -
    -
    - -
    - -
    -
    - -
    - - - - - - - - - - - - - - - -
    - - Date - - - - - - Category - - - - - - Details - - - -
    {{ event.Time|getisodatefromtimestamp }}{{ event.Type }}{{ event.Details }}
    -
    - -
    -
    -
    -
    +
    +
    diff --git a/app/components/events/eventsController.js b/app/components/events/eventsController.js index dc2f2ac31..997624c08 100644 --- a/app/components/events/eventsController.js +++ b/app/components/events/eventsController.js @@ -1,19 +1,6 @@ angular.module('events', []) -.controller('EventsController', ['$scope', 'Notifications', 'SystemService', 'Pagination', -function ($scope, Notifications, SystemService, Pagination) { - $scope.state = {}; - $scope.state.pagination_count = Pagination.getPaginationCount('events'); - $scope.sortType = 'Time'; - $scope.sortReverse = true; - - $scope.order = function(sortType) { - $scope.sortReverse = ($scope.sortType === sortType) ? !$scope.sortReverse : false; - $scope.sortType = sortType; - }; - - $scope.changePaginationCount = function() { - Pagination.setPaginationCount('events', $scope.state.pagination_count); - }; +.controller('EventsController', ['$scope', 'Notifications', 'SystemService', +function ($scope, Notifications, SystemService) { function initView() { var from = moment().subtract(24, 'hour').unix(); diff --git a/app/components/images/images.html b/app/components/images/images.html index 793da3767..942bdbe4a 100644 --- a/app/components/images/images.html +++ b/app/components/images/images.html @@ -41,112 +41,13 @@
    -
    - - -
    - Items per page: - -
    -
    - -
    -
    - - - -
    -
    -
    - -
    - - - - - -
    - -
    - - - - - - - - - - - - - - - - - - - - - - - - - -
    - - - - Id - - - - - - Tags - - - - - - Size - - - - - - Created - - - -
    - {{ image.Id|truncate:20}} - Unused - - {{ tag }} - {{ image.VirtualSize|humansize }}{{ image.Created|getisodatefromtimestamp }}
    Loading...
    No images available.
    -
    - -
    -
    -
    -
    +
    +
    diff --git a/app/components/images/imagesController.js b/app/components/images/imagesController.js index 496393a50..2ae3b4324 100644 --- a/app/components/images/imagesController.js +++ b/app/components/images/imagesController.js @@ -1,46 +1,15 @@ angular.module('images', []) -.controller('ImagesController', ['$scope', '$state', 'ImageService', 'Notifications', 'Pagination', 'ModalService', -function ($scope, $state, ImageService, Notifications, Pagination, ModalService) { +.controller('ImagesController', ['$scope', '$state', 'ImageService', 'Notifications', 'ModalService', +function ($scope, $state, ImageService, Notifications, ModalService) { $scope.state = { - pagination_count: Pagination.getPaginationCount('images'), - actionInProgress: false, - selectedItemCount: 0 + actionInProgress: false }; - $scope.sortType = 'RepoTags'; - $scope.sortReverse = true; - $scope.formValues = { Image: '', Registry: '' }; - $scope.order = function(sortType) { - $scope.sortReverse = ($scope.sortType === sortType) ? !$scope.sortReverse : false; - $scope.sortType = sortType; - }; - - $scope.changePaginationCount = function() { - Pagination.setPaginationCount('images', $scope.state.pagination_count); - }; - - $scope.selectItems = function (allSelected) { - angular.forEach($scope.state.filteredImages, function (image) { - if (image.Checked !== allSelected) { - image.Checked = allSelected; - $scope.selectItem(image); - } - }); - }; - - $scope.selectItem = function (item) { - if (item.Checked) { - $scope.state.selectedItemCount++; - } else { - $scope.state.selectedItemCount--; - } - }; - $scope.pullImage = function() { var image = $scope.formValues.Image; var registry = $scope.formValues.Registry; @@ -59,33 +28,38 @@ function ($scope, $state, ImageService, Notifications, Pagination, ModalService) }); }; - $scope.confirmRemovalAction = function (force) { + $scope.confirmRemovalAction = function (selectedItems, force) { ModalService.confirmImageForceRemoval(function (confirmed) { if(!confirmed) { return; } - $scope.removeAction(force); + $scope.removeAction(selectedItems, force); }); }; - $scope.removeAction = function (force) { - force = !!force; - angular.forEach($scope.images, function (i) { - if (i.Checked) { - ImageService.deleteImage(i.Id, force) - .then(function success(data) { - Notifications.success('Image deleted', i.Id); - var index = $scope.images.indexOf(i); - $scope.images.splice(index, 1); - }) - .catch(function error(err) { - Notifications.error('Failure', err, 'Unable to remove image'); - }); - } + $scope.removeAction = function (selectedItems, force) { + var actionCount = selectedItems.length; + angular.forEach(selectedItems, function (image) { + ImageService.deleteImage(image.Id, force) + .then(function success() { + Notifications.success('Image successfully removed', image.Id); + var index = $scope.images.indexOf(image); + $scope.images.splice(index, 1); + }) + .catch(function error(err) { + Notifications.error('Failure', err, 'Unable to remove image'); + }) + .finally(function final() { + --actionCount; + if (actionCount === 0) { + $state.reload(); + } + }); }); }; - function fetchImages() { + function initView() { var endpointProvider = $scope.applicationState.endpoint.mode.provider; var apiVersion = $scope.applicationState.endpoint.apiVersion; + ImageService.images(true) .then(function success(data) { $scope.images = data; @@ -96,5 +70,5 @@ function ($scope, $state, ImageService, Notifications, Pagination, ModalService) }); } - fetchImages(); + initView(); }]); diff --git a/app/components/networks/networks.html b/app/components/networks/networks.html index 0c30c2d4b..932c080e6 100644 --- a/app/components/networks/networks.html +++ b/app/components/networks/networks.html @@ -8,6 +8,18 @@
    +
    + +
    +
    + + diff --git a/app/components/networks/networksController.js b/app/components/networks/networksController.js index d3a95b33a..2d7ef9bd1 100644 --- a/app/components/networks/networksController.js +++ b/app/components/networks/networksController.js @@ -1,54 +1,25 @@ angular.module('networks', []) -.controller('NetworksController', ['$scope', '$state', 'Network', 'NetworkService', 'Notifications', 'Pagination', -function ($scope, $state, Network, NetworkService, Notifications, Pagination) { - $scope.state = {}; - $scope.state.pagination_count = Pagination.getPaginationCount('networks'); - $scope.state.selectedItemCount = 0; - $scope.state.advancedSettings = false; - $scope.sortType = 'Name'; - $scope.sortReverse = false; +.controller('NetworksController', ['$scope', '$state', 'NetworkService', 'Notifications', +function ($scope, $state, NetworkService, Notifications) { - $scope.changePaginationCount = function() { - Pagination.setPaginationCount('networks', $scope.state.pagination_count); - }; - - $scope.order = function(sortType) { - $scope.sortReverse = ($scope.sortType === sortType) ? !$scope.sortReverse : false; - $scope.sortType = sortType; - }; - - $scope.selectItems = function(allSelected) { - angular.forEach($scope.state.filteredNetworks, function (network) { - if (network.Checked !== allSelected) { - network.Checked = allSelected; - $scope.selectItem(network); - } - }); - }; - - $scope.selectItem = function (item) { - if (item.Checked) { - $scope.state.selectedItemCount++; - } else { - $scope.state.selectedItemCount--; - } - }; - - $scope.removeAction = function () { - angular.forEach($scope.networks, function (network) { - if (network.Checked) { - Network.remove({id: network.Id}, function (d) { - if (d.message) { - Notifications.error('Error', d, 'Unable to remove network'); - } else { - Notifications.success('Network removed', network.Id); - var index = $scope.networks.indexOf(network); - $scope.networks.splice(index, 1); - } - }, function (e) { - Notifications.error('Failure', e, 'Unable to remove network'); - }); - } + $scope.removeAction = function (selectedItems) { + var actionCount = selectedItems.length; + angular.forEach(selectedItems, function (network) { + NetworkService.remove(network.Id) + .then(function success() { + Notifications.success('Network successfully removed', network.Name); + var index = $scope.networks.indexOf(network); + $scope.networks.splice(index, 1); + }) + .catch(function error(err) { + Notifications.error('Failure', err, 'Unable to remove network'); + }) + .finally(function final() { + --actionCount; + if (actionCount === 0) { + $state.reload(); + } + }); }); }; diff --git a/app/components/node/node.html b/app/components/node/node.html index 4b9f87910..909013aab 100644 --- a/app/components/node/node.html +++ b/app/components/node/node.html @@ -143,6 +143,33 @@
    +
    +
    + + + +

    There are no engine labels for this node.

    +
    + + + + + + + + + + + + + + +
    LabelValue
    {{ engineLabel.key }}{{ engineLabel.value }}
    +
    +
    +
    +
    +
    @@ -205,69 +232,12 @@
    -
    - - -
    - Items per page: - -
    -
    - - - - - - - - - - - - - - - - - - - - -
    Id - - Status - - - - - - Slot - - - - - - Image - - - - - - Last update - - - -
    {{ task.Id }}{{ task.Status.State }}{{ task.Slot ? task.Slot : '-' }}{{ task.Spec.ContainerSpec.Image | hideshasum }}{{ task.Updated | getisodate }}
    -
    - -
    -
    -
    +
    +
    diff --git a/app/components/node/nodeController.js b/app/components/node/nodeController.js index e572219a1..cc43cb764 100644 --- a/app/components/node/nodeController.js +++ b/app/components/node/nodeController.js @@ -1,28 +1,15 @@ // @@OLD_SERVICE_CONTROLLER: this service should be rewritten to use services. // See app/components/templates/templatesController.js as a reference. angular.module('node', []) -.controller('NodeController', ['$scope', '$state', '$transition$', 'LabelHelper', 'Node', 'NodeHelper', 'Task', 'Pagination', 'Notifications', -function ($scope, $state, $transition$, LabelHelper, Node, NodeHelper, Task, Pagination, Notifications) { +.controller('NodeController', ['$scope', '$state', '$transition$', 'LabelHelper', 'Node', 'NodeHelper', 'Task', 'Notifications', +function ($scope, $state, $transition$, LabelHelper, Node, NodeHelper, Task, Notifications) { - $scope.state = {}; - $scope.state.pagination_count = Pagination.getPaginationCount('node_tasks'); $scope.loading = true; $scope.tasks = []; - $scope.sortType = 'Status'; - $scope.sortReverse = false; var originalNode = {}; var editedKeys = []; - $scope.order = function(sortType) { - $scope.sortReverse = ($scope.sortType === sortType) ? !$scope.sortReverse : false; - $scope.sortType = sortType; - }; - - $scope.changePaginationCount = function() { - Pagination.setPaginationCount('node_tasks', $scope.state.pagination_count); - }; - $scope.updateNodeAttribute = function updateNodeAttribute(node, key) { editedKeys.push(key); }; diff --git a/app/components/registries/registries.html b/app/components/registries/registries.html index caa3935db..946f84d4a 100644 --- a/app/components/registries/registries.html +++ b/app/components/registries/registries.html @@ -70,82 +70,12 @@
    - - -
    - Items per page: - -
    -
    - -
    - - Add registry -
    -
    - -
    -
    - -
    - - - - - - - - - - - - - - - - - - - - - - - -
    - - - - Name - - - - - - URL - - - -
    - {{ registry.Name }} - - - - {{ registry.URL }} - - Manage access - -
    Loading...
    No registries available.
    -
    - -
    -
    -
    -
    +
    diff --git a/app/components/registries/registriesController.js b/app/components/registries/registriesController.js index 4357cf972..b5b65f5c1 100644 --- a/app/components/registries/registriesController.js +++ b/app/components/registries/registriesController.js @@ -1,14 +1,10 @@ angular.module('registries', []) -.controller('RegistriesController', ['$q', '$scope', '$state', 'RegistryService', 'DockerHubService', 'ModalService', 'Notifications', 'Pagination', -function ($q, $scope, $state, RegistryService, DockerHubService, ModalService, Notifications, Pagination) { +.controller('RegistriesController', ['$q', '$scope', '$state', 'RegistryService', 'DockerHubService', 'ModalService', 'Notifications', 'PaginationService', +function ($q, $scope, $state, RegistryService, DockerHubService, ModalService, Notifications, PaginationService) { $scope.state = { - selectedItemCount: 0, - pagination_count: Pagination.getPaginationCount('registries'), actionInProgress: false }; - $scope.sortType = 'Name'; - $scope.sortReverse = true; $scope.updateDockerHub = function() { var dockerhub = $scope.dockerhub; @@ -25,56 +21,34 @@ function ($q, $scope, $state, RegistryService, DockerHubService, ModalService, N }); }; - $scope.order = function(sortType) { - $scope.sortReverse = ($scope.sortType === sortType) ? !$scope.sortReverse : false; - $scope.sortType = sortType; - }; - - $scope.changePaginationCount = function() { - Pagination.setPaginationCount('endpoints', $scope.state.pagination_count); - }; - - $scope.selectItems = function (allSelected) { - angular.forEach($scope.state.filteredRegistries, function (registry) { - if (registry.Checked !== allSelected) { - registry.Checked = allSelected; - $scope.selectItem(registry); - } - }); - }; - - $scope.selectItem = function (item) { - if (item.Checked) { - $scope.state.selectedItemCount++; - } else { - $scope.state.selectedItemCount--; - } - }; - - $scope.removeAction = function() { + $scope.removeAction = function(selectedItems) { ModalService.confirmDeletion( 'Do you want to remove the selected registries?', function onConfirm(confirmed) { if(!confirmed) { return; } - removeRegistries(); + deleteSelectedRegistries(selectedItems); } ); }; - function removeRegistries() { - var registries = $scope.registries; - angular.forEach(registries, function (registry) { - if (registry.Checked) { - RegistryService.deleteRegistry(registry.Id) - .then(function success(data) { - var index = registries.indexOf(registry); - registries.splice(index, 1); - Notifications.success('Registry deleted', registry.Name); - }) - .catch(function error(err) { - Notifications.error('Failure', err, 'Unable to remove registry'); - }); - } + function deleteSelectedRegistries(selectedItems) { + var actionCount = selectedItems.length; + angular.forEach(selectedItems, function (registry) { + RegistryService.deleteRegistry(registry.Id) + .then(function success() { + Notifications.success('Registry successfully removed', registry.Name); + var index = $scope.registries.indexOf(registry); + $scope.registries.splice(index, 1); + }) + .catch(function error(err) { + Notifications.error('Failure', err, 'Unable to remove registry'); + }) + .finally(function final() { + --actionCount; + if (actionCount === 0) { + $state.reload(); + } + }); }); } diff --git a/app/components/secrets/secrets.html b/app/components/secrets/secrets.html index 3586364af..6ac1313b8 100644 --- a/app/components/secrets/secrets.html +++ b/app/components/secrets/secrets.html @@ -8,73 +8,13 @@
    -
    - - - - -
    - - Add secret -
    -
    - -
    -
    - -
    - - - - - - - - - - - - - - - - - - - - - -
    - - - - Name - - - - - - Created at - - - - - - Ownership - - - -
    {{ secret.Name }}{{ secret.CreatedAt | getisodate }} - - - {{ secret.ResourceControl.Ownership ? secret.ResourceControl.Ownership : secret.ResourceControl.Ownership = 'public' }} - -
    Loading...
    No secrets available.
    -
    - -
    -
    -
    -
    +
    +
    diff --git a/app/components/secrets/secretsController.js b/app/components/secrets/secretsController.js index 7c176c741..6bc5ca81d 100644 --- a/app/components/secrets/secretsController.js +++ b/app/components/secrets/secretsController.js @@ -1,47 +1,25 @@ angular.module('secrets', []) -.controller('SecretsController', ['$scope', '$state', 'SecretService', 'Notifications', 'Pagination', -function ($scope, $state, SecretService, Notifications, Pagination) { - $scope.state = {}; - $scope.state.selectedItemCount = 0; - $scope.state.pagination_count = Pagination.getPaginationCount('secrets'); - $scope.sortType = 'Name'; - $scope.sortReverse = false; +.controller('SecretsController', ['$scope', '$state', 'SecretService', 'Notifications', +function ($scope, $state, SecretService, Notifications) { - $scope.order = function (sortType) { - $scope.sortReverse = ($scope.sortType === sortType) ? !$scope.sortReverse : false; - $scope.sortType = sortType; - }; - - $scope.selectItems = function (allSelected) { - angular.forEach($scope.state.filteredSecrets, function (secret) { - if (secret.Checked !== allSelected) { - secret.Checked = allSelected; - $scope.selectItem(secret); - } - }); - }; - - $scope.selectItem = function (item) { - if (item.Checked) { - $scope.state.selectedItemCount++; - } else { - $scope.state.selectedItemCount--; - } - }; - - $scope.removeAction = function () { - angular.forEach($scope.secrets, function (secret) { - if (secret.Checked) { - SecretService.remove(secret.Id) - .then(function success() { - Notifications.success('Secret deleted', secret.Id); - var index = $scope.secrets.indexOf(secret); - $scope.secrets.splice(index, 1); - }) - .catch(function error(err) { - Notifications.error('Failure', err, 'Unable to remove secret'); - }); - } + $scope.removeAction = function (selectedItems) { + var actionCount = selectedItems.length; + angular.forEach(selectedItems, function (secret) { + SecretService.remove(secret.Id) + .then(function success() { + Notifications.success('Secret successfully removed', secret.Name); + var index = $scope.secrets.indexOf(secret); + $scope.secrets.splice(index, 1); + }) + .catch(function error(err) { + Notifications.error('Failure', err, 'Unable to remove secret'); + }) + .finally(function final() { + --actionCount; + if (actionCount === 0) { + $state.reload(); + } + }); }); }; diff --git a/app/components/service/includes/resources.html b/app/components/service/includes/resources.html index e24de75b5..9d7f610f6 100644 --- a/app/components/service/includes/resources.html +++ b/app/components/service/includes/resources.html @@ -38,7 +38,7 @@
    - +

    @@ -53,7 +53,7 @@

    - +

    diff --git a/app/components/service/includes/restart.html b/app/components/service/includes/restart.html index 1e5293bda..e42de0173 100644 --- a/app/components/service/includes/restart.html +++ b/app/components/service/includes/restart.html @@ -25,11 +25,11 @@ Restart delay - +

    - Delay between restart attempts. Time in seconds. + Delay between restart attempts expressed by a number followed by unit (ns|us|ms|s|m|h). Default value is 5s, 5 seconds.

    @@ -40,18 +40,18 @@

    - Maximum attempts to restart a given container before giving up (default value is 0, which is ignored). + Maximum attempts to restart a given task before giving up (default value is 0, which means unlimited).

    Restart window - +

    - The time window used to evaluate the restart policy (default value is 0, which is unbounded). Time in seconds. + Time window to evaluate restart attempts expressed by a number followed by unit (ns|us|ms|s|m|h). Default value is 0 seconds, which is unbounded.

    diff --git a/app/components/service/includes/tasks.html b/app/components/service/includes/tasks.html index 3dd131b9e..2cf7fdbaf 100644 --- a/app/components/service/includes/tasks.html +++ b/app/components/service/includes/tasks.html @@ -1,71 +1,10 @@
    - - -
    - Items per page: - -
    -
    - -
    - -
    -
    - - - - - - - - - - - - - - - - - - - - -
    Id - - Status - - - - - - Slot - - - - - - Node - - - - - - Last update - - - -
    {{ task.Id }}{{ task.Status.State }}{{ task.Slot }}{{ task.NodeId | tasknodename: nodes }}{{ task.Updated | getisodate }}
    -
    - -
    -
    -
    +
    - diff --git a/app/components/service/includes/updateconfig.html b/app/components/service/includes/updateconfig.html index 92b6ff34b..3a1d7801b 100644 --- a/app/components/service/includes/updateconfig.html +++ b/app/components/service/includes/updateconfig.html @@ -19,11 +19,11 @@ Update Delay - +

    - Amount of time between updates. Time in seconds. + Amount of time between updates expressed by a number followed by unit (ns|us|ms|s|m|h). Example: 1m.

    diff --git a/app/components/service/service.html b/app/components/service/service.html index b5d1a8d90..4715f5323 100644 --- a/app/components/service/service.html +++ b/app/components/service/service.html @@ -75,7 +75,7 @@
    - Logs + Logs
    diff --git a/app/components/service/serviceController.js b/app/components/service/serviceController.js index 761c7f708..45518e02b 100644 --- a/app/components/service/serviceController.js +++ b/app/components/service/serviceController.js @@ -1,12 +1,9 @@ angular.module('service', []) -.controller('ServiceController', ['$q', '$scope', '$transition$', '$state', '$location', '$timeout', '$anchorScroll', 'ServiceService', 'ConfigService', 'ConfigHelper', 'SecretService', 'ImageService', 'SecretHelper', 'Service', 'ServiceHelper', 'LabelHelper', 'TaskService', 'NodeService', 'Notifications', 'Pagination', 'ModalService', -function ($q, $scope, $transition$, $state, $location, $timeout, $anchorScroll, ServiceService, ConfigService, ConfigHelper, SecretService, ImageService, SecretHelper, Service, ServiceHelper, LabelHelper, TaskService, NodeService, Notifications, Pagination, ModalService) { +.controller('ServiceController', ['$q', '$scope', '$transition$', '$state', '$location', '$timeout', '$anchorScroll', 'ServiceService', 'ConfigService', 'ConfigHelper', 'SecretService', 'ImageService', 'SecretHelper', 'Service', 'ServiceHelper', 'LabelHelper', 'TaskService', 'NodeService', 'Notifications', 'ModalService', +function ($q, $scope, $transition$, $state, $location, $timeout, $anchorScroll, ServiceService, ConfigService, ConfigHelper, SecretService, ImageService, SecretHelper, Service, ServiceHelper, LabelHelper, TaskService, NodeService, Notifications, ModalService) { $scope.state = {}; - $scope.state.pagination_count = Pagination.getPaginationCount('service_tasks'); $scope.tasks = []; - $scope.sortType = 'Updated'; - $scope.sortReverse = true; $scope.availableImages = []; $scope.lastVersion = 0; @@ -14,15 +11,6 @@ function ($q, $scope, $transition$, $state, $location, $timeout, $anchorScroll, var originalService = {}; var previousServiceValues = []; - $scope.order = function (sortType) { - $scope.sortReverse = ($scope.sortType === sortType) ? !$scope.sortReverse : false; - $scope.sortType = sortType; - }; - - $scope.changePaginationCount = function() { - Pagination.setPaginationCount('service_tasks', $scope.state.pagination_count); - }; - $scope.renameService = function renameService(service) { updateServiceAttribute(service, 'Name', service.newServiceName || service.name); service.EditName = false; @@ -244,16 +232,16 @@ function ($q, $scope, $transition$, $state, $location, $timeout, $anchorScroll, config.UpdateConfig = { Parallelism: service.UpdateParallelism, - Delay: service.UpdateDelay * 1000000000, + Delay: ServiceHelper.translateHumanDurationToNanos(service.UpdateDelay) || 0, FailureAction: service.UpdateFailureAction, Order: service.UpdateOrder }; config.TaskTemplate.RestartPolicy = { Condition: service.RestartCondition, - Delay: service.RestartDelay * 1000000000, + Delay: ServiceHelper.translateHumanDurationToNanos(service.RestartDelay) || 5000000000, MaxAttempts: service.RestartMaxAttempts, - Window: service.RestartWindow * 1000000000 + Window: ServiceHelper.translateHumanDurationToNanos(service.RestartWindow) || 0 }; if (service.Ports) { @@ -320,11 +308,11 @@ function ($q, $scope, $transition$, $state, $location, $timeout, $anchorScroll, service.LimitMemoryBytes = service.LimitMemoryBytes / 1024 / 1024 || 0; service.ReservationMemoryBytes = service.ReservationMemoryBytes / 1024 / 1024 || 0; } - + function transformDurations(service) { - service.RestartDelay = service.RestartDelay / 1000000000 || 5; - service.RestartWindow = service.RestartWindow / 1000000000 || 0; - service.UpdateDelay = service.UpdateDelay / 1000000000 || 0; + service.RestartDelay = ServiceHelper.translateNanosToHumanDuration(service.RestartDelay) || '5s'; + service.RestartWindow = ServiceHelper.translateNanosToHumanDuration(service.RestartWindow) || '0s'; + service.UpdateDelay = ServiceHelper.translateNanosToHumanDuration(service.UpdateDelay) || '0s'; } function initView() { diff --git a/app/components/services/services.html b/app/components/services/services.html index 662f6b625..3c23ac6cd 100644 --- a/app/components/services/services.html +++ b/app/components/services/services.html @@ -8,133 +8,15 @@
    -
    - - -
    - Items per page: - -
    -
    - -
    - - Add service -
    -
    - -
    -
    - -
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    - - Name - - - - - - Stack - - - - - - Image - - - - - - Scheduling mode - - - - - - Published Ports - - - - - - Updated at - - - - - - Ownership - - - -
    {{ service.Name }}{{ service.StackName ? service.StackName : '-' }}{{ service.Image | hideshasum }} - {{ service.Mode }} - {{ service.Running }} - / - {{ service.Replicas }} - - Scale - - - - - - - - - {{ p.PublishedPort }}:{{ p.TargetPort }} - - - - - {{ service.UpdatedAt|getisodate }} - - - - {{ service.ResourceControl.Ownership ? service.ResourceControl.Ownership : service.ResourceControl.Ownership = 'public' }} - -
    Loading...
    No services available.
    -
    - -
    -
    -
    -
    +
    +
    diff --git a/app/components/services/servicesController.js b/app/components/services/servicesController.js index f3c6a82ca..e2223da48 100644 --- a/app/components/services/servicesController.js +++ b/app/components/services/servicesController.js @@ -1,80 +1,50 @@ angular.module('services', []) -.controller('ServicesController', ['$q', '$scope', '$transition$', '$state', 'Service', 'ServiceService', 'ServiceHelper', 'Notifications', 'Pagination', 'Task', 'Node', 'NodeHelper', 'ModalService', 'ResourceControlService', -function ($q, $scope, $transition$, $state, Service, ServiceService, ServiceHelper, Notifications, Pagination, Task, Node, NodeHelper, ModalService, ResourceControlService) { - $scope.state = {}; - $scope.state.selectedItemCount = 0; - $scope.state.pagination_count = Pagination.getPaginationCount('services'); - $scope.sortType = 'Name'; - $scope.sortReverse = false; +.controller('ServicesController', ['$q', '$scope', '$state', 'Service', 'ServiceService', 'ServiceHelper', 'Notifications', 'Task', 'Node', 'NodeHelper', 'ModalService', +function ($q, $scope, $state, Service, ServiceService, ServiceHelper, Notifications, Task, Node, NodeHelper, ModalService) { - $scope.changePaginationCount = function() { - Pagination.setPaginationCount('services', $scope.state.pagination_count); - }; - - $scope.order = function (sortType) { - $scope.sortReverse = ($scope.sortType === sortType) ? !$scope.sortReverse : false; - $scope.sortType = sortType; - }; - - $scope.selectItem = function (item) { - if (item.Checked) { - $scope.state.selectedItemCount++; - } else { - $scope.state.selectedItemCount--; - } - }; - - $scope.scaleService = function scaleService(service) { + $scope.scaleAction = function scaleService(service) { var config = ServiceHelper.serviceToConfig(service.Model); config.Mode.Replicated.Replicas = service.Replicas; - Service.update({ id: service.Id, version: service.Version }, config, function (data) { + ServiceService.update(service, config) + .then(function success(data) { Notifications.success('Service successfully scaled', 'New replica count: ' + service.Replicas); $state.reload(); - }, function (e) { + }) + .catch(function error(err) { + Notifications.error('Failure', err, 'Unable to scale service'); service.Scale = false; service.Replicas = service.ReplicaCount; - Notifications.error('Failure', e, 'Unable to scale service'); }); }; - $scope.removeAction = function() { + $scope.removeAction = function(selectedItems) { ModalService.confirmDeletion( 'Do you want to remove the selected service(s)? All the containers associated to the selected service(s) will be removed too.', function onConfirm(confirmed) { if(!confirmed) { return; } - removeServices(); + removeServices(selectedItems); } ); }; - function removeServices() { - angular.forEach($scope.services, function (service) { - if (service.Checked) { - ServiceService.remove(service) - .then(function success(data) { - Notifications.success('Service successfully deleted'); - var index = $scope.services.indexOf(service); - $scope.services.splice(index, 1); - }) - .catch(function error(err) { - Notifications.error('Failure', err, 'Unable to remove service'); - }); - } - }); - } - - function mapUsersToServices(users) { - angular.forEach($scope.services, function (service) { - if (service.Metadata) { - var serviceRC = service.Metadata.ResourceControl; - if (serviceRC && serviceRC.OwnerId !== $scope.user.ID) { - angular.forEach(users, function (user) { - if (serviceRC.OwnerId === user.Id) { - service.Owner = user.Username; - } - }); + function removeServices(services) { + var actionCount = services.length; + angular.forEach(services, function (service) { + ServiceService.remove(service) + .then(function success() { + Notifications.success('Service successfully removed', service.Name); + var index = $scope.services.indexOf(service); + $scope.services.splice(index, 1); + }) + .catch(function error(err) { + Notifications.error('Failure', err, 'Unable to remove service'); + }) + .finally(function final() { + --actionCount; + if (actionCount === 0) { + $state.reload(); } - } + }); }); } diff --git a/app/components/settingsAuthentication/settingsAuthenticationController.js b/app/components/settingsAuthentication/settingsAuthenticationController.js index 36c266dc7..bf282f155 100644 --- a/app/components/settingsAuthentication/settingsAuthenticationController.js +++ b/app/components/settingsAuthentication/settingsAuthenticationController.js @@ -22,8 +22,8 @@ function ($q, $scope, Notifications, SettingsService, FileUploadService) { $scope.LDAPSettings.SearchSettings.splice(index, 1); }; - $scope.LDAPConnectivityCheck = function() { - var settings = $scope.settings; + $scope.LDAPConnectivityCheck = function() { + var settings = $scope.settings; var TLSCAFile = $scope.formValues.TLSCACert !== settings.LDAPSettings.TLSConfig.TLSCACert ? $scope.formValues.TLSCACert : null; var uploadRequired = ($scope.LDAPSettings.TLSConfig.TLS || $scope.LDAPSettings.StartTLS) && !$scope.LDAPSettings.TLSConfig.TLSSkipVerify; @@ -32,6 +32,7 @@ function ($q, $scope, Notifications, SettingsService, FileUploadService) { $scope.state.connectivityCheckInProgress = true; $q.when(!uploadRequired || FileUploadService.uploadLDAPTLSFiles(TLSCAFile, null, null)) .then(function success(data) { + addLDAPDefaultPort(settings, $scope.LDAPSettings.TLSConfig.TLS); return SettingsService.checkLDAPConnectivity(settings); }) .then(function success(data) { @@ -60,6 +61,7 @@ function ($q, $scope, Notifications, SettingsService, FileUploadService) { $scope.state.actionInProgress = true; $q.when(!uploadRequired || FileUploadService.uploadLDAPTLSFiles(TLSCAFile, null, null)) .then(function success(data) { + addLDAPDefaultPort(settings, $scope.LDAPSettings.TLSConfig.TLS); return SettingsService.update(settings); }) .then(function success(data) { @@ -74,6 +76,13 @@ function ($q, $scope, Notifications, SettingsService, FileUploadService) { }); }; + // Add default port if :port is not defined in URL + function addLDAPDefaultPort(settings, tlsEnabled) { + if (settings.LDAPSettings.URL.indexOf(':') === -1) { + settings.LDAPSettings.URL += tlsEnabled ? ':636' : ':389'; + } + } + function initView() { SettingsService.settings() .then(function success(data) { diff --git a/app/components/sidebar/sidebar.html b/app/components/sidebar/sidebar.html index dd1b0d293..7cfde6625 100644 --- a/app/components/sidebar/sidebar.html +++ b/app/components/sidebar/sidebar.html @@ -48,7 +48,7 @@ -