diff --git a/app/components/service/service.html b/app/components/service/service.html
index 16245f8e2..ba298a3d9 100644
--- a/app/components/service/service.html
+++ b/app/components/service/service.html
@@ -38,6 +38,7 @@
ID |
{{ service.Id }}
+
|
diff --git a/app/components/service/serviceController.js b/app/components/service/serviceController.js
index 74c72a02a..2c4a5602e 100644
--- a/app/components/service/serviceController.js
+++ b/app/components/service/serviceController.js
@@ -168,7 +168,7 @@ function ($q, $scope, $transition$, $state, $location, $timeout, $anchorScroll,
}
};
- $scope.addLogDriverOpt = function addLogDriverOpt(service) {
+ $scope.addLogDriverOpt = function addLogDriverOpt(service) {
service.LogDriverOpts.push({ key: '', value: '', originalValue: '' });
updateServiceArray(service, 'LogDriverOpts', service.LogDriverOpts);
};
@@ -182,16 +182,16 @@ function ($q, $scope, $transition$, $state, $location, $timeout, $anchorScroll,
if (variable.value !== variable.originalValue || variable.key !== variable.originalKey) {
updateServiceArray(service, 'LogDriverOpts', service.LogDriverOpts);
}
- };
- $scope.updateLogDriverName = function updateLogDriverName(service) {
- updateServiceArray(service, 'LogDriverName', service.LogDriverName);
- };
+ };
+ $scope.updateLogDriverName = function updateLogDriverName(service) {
+ updateServiceArray(service, 'LogDriverName', service.LogDriverName);
+ };
$scope.addHostsEntry = function (service) {
if (!service.Hosts) {
service.Hosts = [];
}
- service.Hosts.push({ hostname: '', ip: '' });
+ service.Hosts.push({ hostname: '', ip: '' });
};
$scope.removeHostsEntry = function(service, index) {
var removedElement = service.Hosts.splice(index, 1);
@@ -199,9 +199,9 @@ function ($q, $scope, $transition$, $state, $location, $timeout, $anchorScroll,
updateServiceArray(service, 'Hosts', service.Hosts);
}
};
- $scope.updateHostsEntry = function(service, entry) {
+ $scope.updateHostsEntry = function(service, entry) {
updateServiceArray(service, 'Hosts', service.Hosts);
- };
+ };
$scope.cancelChanges = function cancelChanges(service, keys) {
if (keys) { // clean out the keys only from the list of modified keys
@@ -239,7 +239,7 @@ function ($q, $scope, $transition$, $state, $location, $timeout, $anchorScroll,
config.TaskTemplate.ContainerSpec.Secrets = service.ServiceSecrets ? service.ServiceSecrets.map(SecretHelper.secretConfig) : [];
config.TaskTemplate.ContainerSpec.Configs = service.ServiceConfigs ? service.ServiceConfigs.map(ConfigHelper.configConfig) : [];
config.TaskTemplate.ContainerSpec.Hosts = service.Hosts ? ServiceHelper.translateHostnameIPToHostsEntries(service.Hosts) : [];
-
+
if (service.Mode === 'replicated') {
config.Mode.Replicated.Replicas = service.Replicas;
}
@@ -279,17 +279,17 @@ function ($q, $scope, $transition$, $state, $location, $timeout, $anchorScroll,
MaxAttempts: service.RestartMaxAttempts,
Window: ServiceHelper.translateHumanDurationToNanos(service.RestartWindow) || 0
};
-
+
config.TaskTemplate.LogDriver = null;
- if (service.LogDriverName) {
+ if (service.LogDriverName) {
config.TaskTemplate.LogDriver = { Name: service.LogDriverName };
if (service.LogDriverName !== 'none') {
var logOpts = ServiceHelper.translateKeyValueToLogDriverOpts(service.LogDriverOpts);
if (Object.keys(logOpts).length !== 0 && logOpts.constructor === Object) {
config.TaskTemplate.LogDriver.Options = logOpts;
}
- }
- }
+ }
+ }
if (service.Ports) {
service.Ports.forEach(function (binding) {
@@ -338,6 +338,32 @@ function ($q, $scope, $transition$, $state, $location, $timeout, $anchorScroll,
});
}
+ $scope.forceUpdateService = function(service) {
+ ModalService.confirmServiceForceUpdate(
+ 'Do you want to force update this service? All the tasks associated to the selected service(s) will be recreated.',
+ function onConfirm(confirmed) {
+ if(!confirmed) { return; }
+ forceUpdateService(service);
+ }
+ );
+ };
+
+ function forceUpdateService(service) {
+ var config = ServiceHelper.serviceToConfig(service.Model);
+ // As explained in https://github.com/docker/swarmkit/issues/2364 ForceUpdate can accept a random
+ // value or an increment of the counter value to force an update.
+ config.TaskTemplate.ForceUpdate++;
+ ServiceService.update(service, config)
+ .then(function success(data) {
+ Notifications.success('Service successfully updated', service.Name);
+ $scope.cancelChanges({});
+ initView();
+ })
+ .catch(function error(err) {
+ Notifications.error('Failure', err, 'Unable to force update service', service.Name);
+ });
+ }
+
function translateServiceArrays(service) {
service.ServiceSecrets = service.Secrets ? service.Secrets.map(SecretHelper.flattenSecret) : [];
service.ServiceConfigs = service.Configs ? service.Configs.map(ConfigHelper.flattenConfig) : [];
@@ -365,7 +391,7 @@ function ($q, $scope, $transition$, $state, $location, $timeout, $anchorScroll,
}
function initView() {
- var apiVersion = $scope.applicationState.endpoint.apiVersion;
+ var apiVersion = $scope.applicationState.endpoint.apiVersion;
ServiceService.service($transition$.params().id)
.then(function success(data) {
diff --git a/app/components/services/services.html b/app/components/services/services.html
index 3c23ac6cd..48eebed2e 100644
--- a/app/components/services/services.html
+++ b/app/components/services/services.html
@@ -16,7 +16,9 @@
show-ownership-column="applicationState.application.authentication"
remove-action="removeAction"
scale-action="scaleAction"
+ force-update-action="forceUpdateAction"
swarm-manager-ip="swarmManagerIP"
+ show-force-update-button="applicationState.endpoint.apiVersion >= 1.25"
>
diff --git a/app/components/services/servicesController.js b/app/components/services/servicesController.js
index e2223da48..8937fa392 100644
--- a/app/components/services/servicesController.js
+++ b/app/components/services/servicesController.js
@@ -17,6 +17,39 @@ function ($q, $scope, $state, Service, ServiceService, ServiceHelper, Notificati
});
};
+ $scope.forceUpdateAction = function(selectedItems) {
+ ModalService.confirmServiceForceUpdate(
+ 'Do you want to force update of selected service(s)? All the tasks associated to the selected service(s) will be recreated.',
+ function onConfirm(confirmed) {
+ if(!confirmed) { return; }
+ forceUpdateServices(selectedItems);
+ }
+ );
+ };
+
+ function forceUpdateServices(services) {
+ var actionCount = services.length;
+ angular.forEach(services, function (service) {
+ var config = ServiceHelper.serviceToConfig(service.Model);
+ // As explained in https://github.com/docker/swarmkit/issues/2364 ForceUpdate can accept a random
+ // value or an increment of the counter value to force an update.
+ config.TaskTemplate.ForceUpdate++;
+ ServiceService.update(service, config)
+ .then(function success(data) {
+ Notifications.success('Service successfully updated', service.Name);
+ })
+ .catch(function error(err) {
+ Notifications.error('Failure', err, 'Unable to force update service', service.Name);
+ })
+ .finally(function final() {
+ --actionCount;
+ if (actionCount === 0) {
+ $state.reload();
+ }
+ });
+ });
+ }
+
$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.',
diff --git a/app/directives/ui/datatables/services-datatable/servicesDatatable.html b/app/directives/ui/datatables/services-datatable/servicesDatatable.html
index 60afade12..bd458edd2 100644
--- a/app/directives/ui/datatables/services-datatable/servicesDatatable.html
+++ b/app/directives/ui/datatables/services-datatable/servicesDatatable.html
@@ -16,6 +16,10 @@
ng-disabled="$ctrl.state.selectedItemCount === 0" ng-click="$ctrl.removeAction($ctrl.state.selectedItems)">
Remove
+
diff --git a/app/directives/ui/datatables/services-datatable/servicesDatatable.js b/app/directives/ui/datatables/services-datatable/servicesDatatable.js
index 14ae8954c..9d33fea66 100644
--- a/app/directives/ui/datatables/services-datatable/servicesDatatable.js
+++ b/app/directives/ui/datatables/services-datatable/servicesDatatable.js
@@ -12,6 +12,8 @@ angular.module('ui').component('servicesDatatable', {
showOwnershipColumn: '<',
removeAction: '<',
scaleAction: '<',
- swarmManagerIp: '<'
+ swarmManagerIp: '<',
+ forceUpdateAction: '<',
+ showForceUpdateButton: '<'
}
});
diff --git a/app/services/modalService.js b/app/services/modalService.js
index 81b0b84a9..4c81bdcc6 100644
--- a/app/services/modalService.js
+++ b/app/services/modalService.js
@@ -156,5 +156,19 @@ angular.module('portainer.services')
});
};
+ service.confirmServiceForceUpdate = function(message, callback) {
+ service.confirm({
+ title: 'Are you sure ?',
+ message: message,
+ buttons: {
+ confirm: {
+ label: 'Update',
+ className: 'btn-primary'
+ }
+ },
+ callback: callback
+ });
+ };
+
return service;
}]);