mirror of https://github.com/portainer/portainer
feat(service): add force update in service list/detail (#1536)
parent
35892525ff
commit
0e28aebd65
|
@ -38,6 +38,7 @@
|
||||||
<td>ID</td>
|
<td>ID</td>
|
||||||
<td>
|
<td>
|
||||||
{{ service.Id }}
|
{{ service.Id }}
|
||||||
|
<button class="btn btn-xs btn-primary" ng-click="forceUpdateService(service)"><i class="fa fa-refresh space-right" aria-hidden="true" ng-disabled="isUpdating" ng-if="applicationState.endpoint.apiVersion >= 1.25"></i>Force update this service</button>
|
||||||
<button class="btn btn-xs btn-danger" ng-click="removeService()"><i class="fa fa-trash space-right" aria-hidden="true" ng-disabled="isUpdating"></i>Delete this service</button>
|
<button class="btn btn-xs btn-danger" ng-click="removeService()"><i class="fa fa-trash space-right" aria-hidden="true" ng-disabled="isUpdating"></i>Delete this service</button>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
|
@ -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: '' });
|
service.LogDriverOpts.push({ key: '', value: '', originalValue: '' });
|
||||||
updateServiceArray(service, 'LogDriverOpts', service.LogDriverOpts);
|
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) {
|
if (variable.value !== variable.originalValue || variable.key !== variable.originalKey) {
|
||||||
updateServiceArray(service, 'LogDriverOpts', service.LogDriverOpts);
|
updateServiceArray(service, 'LogDriverOpts', service.LogDriverOpts);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
$scope.updateLogDriverName = function updateLogDriverName(service) {
|
$scope.updateLogDriverName = function updateLogDriverName(service) {
|
||||||
updateServiceArray(service, 'LogDriverName', service.LogDriverName);
|
updateServiceArray(service, 'LogDriverName', service.LogDriverName);
|
||||||
};
|
};
|
||||||
|
|
||||||
$scope.addHostsEntry = function (service) {
|
$scope.addHostsEntry = function (service) {
|
||||||
if (!service.Hosts) {
|
if (!service.Hosts) {
|
||||||
service.Hosts = [];
|
service.Hosts = [];
|
||||||
}
|
}
|
||||||
service.Hosts.push({ hostname: '', ip: '' });
|
service.Hosts.push({ hostname: '', ip: '' });
|
||||||
};
|
};
|
||||||
$scope.removeHostsEntry = function(service, index) {
|
$scope.removeHostsEntry = function(service, index) {
|
||||||
var removedElement = service.Hosts.splice(index, 1);
|
var removedElement = service.Hosts.splice(index, 1);
|
||||||
|
@ -199,9 +199,9 @@ function ($q, $scope, $transition$, $state, $location, $timeout, $anchorScroll,
|
||||||
updateServiceArray(service, 'Hosts', service.Hosts);
|
updateServiceArray(service, 'Hosts', service.Hosts);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
$scope.updateHostsEntry = function(service, entry) {
|
$scope.updateHostsEntry = function(service, entry) {
|
||||||
updateServiceArray(service, 'Hosts', service.Hosts);
|
updateServiceArray(service, 'Hosts', service.Hosts);
|
||||||
};
|
};
|
||||||
|
|
||||||
$scope.cancelChanges = function cancelChanges(service, keys) {
|
$scope.cancelChanges = function cancelChanges(service, keys) {
|
||||||
if (keys) { // clean out the keys only from the list of modified 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.Secrets = service.ServiceSecrets ? service.ServiceSecrets.map(SecretHelper.secretConfig) : [];
|
||||||
config.TaskTemplate.ContainerSpec.Configs = service.ServiceConfigs ? service.ServiceConfigs.map(ConfigHelper.configConfig) : [];
|
config.TaskTemplate.ContainerSpec.Configs = service.ServiceConfigs ? service.ServiceConfigs.map(ConfigHelper.configConfig) : [];
|
||||||
config.TaskTemplate.ContainerSpec.Hosts = service.Hosts ? ServiceHelper.translateHostnameIPToHostsEntries(service.Hosts) : [];
|
config.TaskTemplate.ContainerSpec.Hosts = service.Hosts ? ServiceHelper.translateHostnameIPToHostsEntries(service.Hosts) : [];
|
||||||
|
|
||||||
if (service.Mode === 'replicated') {
|
if (service.Mode === 'replicated') {
|
||||||
config.Mode.Replicated.Replicas = service.Replicas;
|
config.Mode.Replicated.Replicas = service.Replicas;
|
||||||
}
|
}
|
||||||
|
@ -279,17 +279,17 @@ function ($q, $scope, $transition$, $state, $location, $timeout, $anchorScroll,
|
||||||
MaxAttempts: service.RestartMaxAttempts,
|
MaxAttempts: service.RestartMaxAttempts,
|
||||||
Window: ServiceHelper.translateHumanDurationToNanos(service.RestartWindow) || 0
|
Window: ServiceHelper.translateHumanDurationToNanos(service.RestartWindow) || 0
|
||||||
};
|
};
|
||||||
|
|
||||||
config.TaskTemplate.LogDriver = null;
|
config.TaskTemplate.LogDriver = null;
|
||||||
if (service.LogDriverName) {
|
if (service.LogDriverName) {
|
||||||
config.TaskTemplate.LogDriver = { Name: service.LogDriverName };
|
config.TaskTemplate.LogDriver = { Name: service.LogDriverName };
|
||||||
if (service.LogDriverName !== 'none') {
|
if (service.LogDriverName !== 'none') {
|
||||||
var logOpts = ServiceHelper.translateKeyValueToLogDriverOpts(service.LogDriverOpts);
|
var logOpts = ServiceHelper.translateKeyValueToLogDriverOpts(service.LogDriverOpts);
|
||||||
if (Object.keys(logOpts).length !== 0 && logOpts.constructor === Object) {
|
if (Object.keys(logOpts).length !== 0 && logOpts.constructor === Object) {
|
||||||
config.TaskTemplate.LogDriver.Options = logOpts;
|
config.TaskTemplate.LogDriver.Options = logOpts;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (service.Ports) {
|
if (service.Ports) {
|
||||||
service.Ports.forEach(function (binding) {
|
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) {
|
function translateServiceArrays(service) {
|
||||||
service.ServiceSecrets = service.Secrets ? service.Secrets.map(SecretHelper.flattenSecret) : [];
|
service.ServiceSecrets = service.Secrets ? service.Secrets.map(SecretHelper.flattenSecret) : [];
|
||||||
service.ServiceConfigs = service.Configs ? service.Configs.map(ConfigHelper.flattenConfig) : [];
|
service.ServiceConfigs = service.Configs ? service.Configs.map(ConfigHelper.flattenConfig) : [];
|
||||||
|
@ -365,7 +391,7 @@ function ($q, $scope, $transition$, $state, $location, $timeout, $anchorScroll,
|
||||||
}
|
}
|
||||||
|
|
||||||
function initView() {
|
function initView() {
|
||||||
var apiVersion = $scope.applicationState.endpoint.apiVersion;
|
var apiVersion = $scope.applicationState.endpoint.apiVersion;
|
||||||
|
|
||||||
ServiceService.service($transition$.params().id)
|
ServiceService.service($transition$.params().id)
|
||||||
.then(function success(data) {
|
.then(function success(data) {
|
||||||
|
|
|
@ -16,7 +16,9 @@
|
||||||
show-ownership-column="applicationState.application.authentication"
|
show-ownership-column="applicationState.application.authentication"
|
||||||
remove-action="removeAction"
|
remove-action="removeAction"
|
||||||
scale-action="scaleAction"
|
scale-action="scaleAction"
|
||||||
|
force-update-action="forceUpdateAction"
|
||||||
swarm-manager-ip="swarmManagerIP"
|
swarm-manager-ip="swarmManagerIP"
|
||||||
|
show-force-update-button="applicationState.endpoint.apiVersion >= 1.25"
|
||||||
></services-datatable>
|
></services-datatable>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -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) {
|
$scope.removeAction = function(selectedItems) {
|
||||||
ModalService.confirmDeletion(
|
ModalService.confirmDeletion(
|
||||||
'Do you want to remove the selected service(s)? All the containers associated to the selected service(s) will be removed too.',
|
'Do you want to remove the selected service(s)? All the containers associated to the selected service(s) will be removed too.',
|
||||||
|
|
|
@ -16,6 +16,10 @@
|
||||||
ng-disabled="$ctrl.state.selectedItemCount === 0" ng-click="$ctrl.removeAction($ctrl.state.selectedItems)">
|
ng-disabled="$ctrl.state.selectedItemCount === 0" ng-click="$ctrl.removeAction($ctrl.state.selectedItems)">
|
||||||
<i class="fa fa-trash space-right" aria-hidden="true"></i>Remove
|
<i class="fa fa-trash space-right" aria-hidden="true"></i>Remove
|
||||||
</button>
|
</button>
|
||||||
|
<button ng-if="$ctrl.showForceUpdateButton" type="button" class="btn btn-sm btn-primary"
|
||||||
|
ng-disabled="$ctrl.state.selectedItemCount === 0" ng-click="$ctrl.forceUpdateAction($ctrl.state.selectedItems)">
|
||||||
|
<i class="fa fa-refresh space-right" aria-hidden="true"></i>Force update
|
||||||
|
</button>
|
||||||
<button type="button" class="btn btn-sm btn-primary" ui-sref="actions.create.service">
|
<button type="button" class="btn btn-sm btn-primary" ui-sref="actions.create.service">
|
||||||
<i class="fa fa-plus space-right" aria-hidden="true"></i>Add service
|
<i class="fa fa-plus space-right" aria-hidden="true"></i>Add service
|
||||||
</button>
|
</button>
|
||||||
|
|
|
@ -12,6 +12,8 @@ angular.module('ui').component('servicesDatatable', {
|
||||||
showOwnershipColumn: '<',
|
showOwnershipColumn: '<',
|
||||||
removeAction: '<',
|
removeAction: '<',
|
||||||
scaleAction: '<',
|
scaleAction: '<',
|
||||||
swarmManagerIp: '<'
|
swarmManagerIp: '<',
|
||||||
|
forceUpdateAction: '<',
|
||||||
|
showForceUpdateButton: '<'
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -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;
|
return service;
|
||||||
}]);
|
}]);
|
||||||
|
|
Loading…
Reference in New Issue