2020-04-10 21:54:53 +00:00
require ( './includes/configs.html' ) ;
require ( './includes/constraints.html' ) ;
require ( './includes/container-specs.html' ) ;
require ( './includes/containerlabels.html' ) ;
require ( './includes/environmentvariables.html' ) ;
require ( './includes/hosts.html' ) ;
require ( './includes/image.html' ) ;
require ( './includes/logging.html' ) ;
require ( './includes/mounts.html' ) ;
require ( './includes/networks.html' ) ;
require ( './includes/placementPreferences.html' ) ;
require ( './includes/ports.html' ) ;
require ( './includes/resources.html' ) ;
require ( './includes/restart.html' ) ;
require ( './includes/secrets.html' ) ;
require ( './includes/servicelabels.html' ) ;
require ( './includes/tasks.html' ) ;
require ( './includes/updateconfig.html' ) ;
2019-03-21 05:46:49 +00:00
2020-08-06 23:11:47 +00:00
import _ from 'lodash-es' ;
2021-06-14 06:59:07 +00:00
2023-05-31 03:08:41 +00:00
import * as envVarsUtils from '@/react/components/form-components/EnvironmentVariablesFieldset/utils' ;
2019-11-27 22:36:39 +00:00
import { PorImageRegistryModel } from 'Docker/models/porImageRegistry' ;
2022-09-07 04:25:00 +00:00
import { ResourceControlType } from '@/react/portainer/access-control/types' ;
2023-02-14 08:19:41 +00:00
import { confirmServiceForceUpdate } from '@/react/docker/services/common/update-service-modal' ;
import { confirm , confirmDelete } from '@@/modals/confirm' ;
import { ModalType } from '@@/modals' ;
import { buildConfirmButton } from '@@/modals/utils' ;
2019-11-27 22:36:39 +00:00
2020-04-10 21:54:53 +00:00
angular . module ( 'portainer.docker' ) . controller ( 'ServiceController' , [
'$q' ,
'$scope' ,
'$transition$' ,
'$state' ,
'$location' ,
'$timeout' ,
'$anchorScroll' ,
'ServiceService' ,
'ConfigService' ,
'ConfigHelper' ,
'SecretService' ,
'ImageService' ,
'SecretHelper' ,
'ServiceHelper' ,
'LabelHelper' ,
'TaskService' ,
'NodeService' ,
'ContainerService' ,
'TaskHelper' ,
'Notifications' ,
'PluginService' ,
'Authentication' ,
'VolumeService' ,
'ImageHelper' ,
'WebhookService' ,
'clipboard' ,
'WebhookHelper' ,
2020-08-06 23:11:47 +00:00
'NetworkService' ,
2021-10-14 12:42:10 +00:00
'RegistryService' ,
2021-02-09 08:09:06 +00:00
'endpoint' ,
2020-04-10 21:54:53 +00:00
function (
$q ,
$scope ,
$transition$ ,
$state ,
$location ,
$timeout ,
$anchorScroll ,
ServiceService ,
ConfigService ,
ConfigHelper ,
SecretService ,
ImageService ,
SecretHelper ,
ServiceHelper ,
LabelHelper ,
TaskService ,
NodeService ,
ContainerService ,
TaskHelper ,
Notifications ,
PluginService ,
Authentication ,
VolumeService ,
ImageHelper ,
WebhookService ,
clipboard ,
2020-08-06 23:11:47 +00:00
WebhookHelper ,
2021-02-09 08:09:06 +00:00
NetworkService ,
2021-10-14 12:42:10 +00:00
RegistryService ,
2021-02-09 08:09:06 +00:00
endpoint
2020-04-10 21:54:53 +00:00
) {
2022-03-16 06:35:32 +00:00
$scope . resourceType = ResourceControlType . Service ;
$scope . onUpdateResourceControlSuccess = function ( ) {
$state . reload ( ) ;
} ;
2021-03-24 18:27:32 +00:00
$scope . endpoint = endpoint ;
2020-04-10 21:54:53 +00:00
$scope . state = {
updateInProgress : false ,
deletionInProgress : false ,
rollbackInProgress : false ,
} ;
2018-01-09 09:40:30 +00:00
2020-04-10 21:54:53 +00:00
$scope . formValues = {
RegistryModel : new PorImageRegistryModel ( ) ,
} ;
2019-11-27 22:36:39 +00:00
2020-04-10 21:54:53 +00:00
$scope . tasks = [ ] ;
$scope . availableImages = [ ] ;
2016-09-23 04:54:58 +00:00
2020-04-10 21:54:53 +00:00
$scope . lastVersion = 0 ;
2017-03-20 20:28:09 +00:00
2020-04-10 21:54:53 +00:00
var originalService = { } ;
var previousServiceValues = [ ] ;
2016-11-09 00:23:56 +00:00
2020-04-10 21:54:53 +00:00
$scope . goToItem = function ( hash ) {
2017-06-20 10:54:27 +00:00
if ( $location . hash ( ) !== hash ) {
$location . hash ( hash ) ;
} else {
$anchorScroll ( ) ;
}
2020-04-10 21:54:53 +00:00
} ;
2017-03-20 20:28:09 +00:00
2020-04-10 21:54:53 +00:00
$scope . addEnvironmentVariable = function addEnvironmentVariable ( service ) {
2023-05-31 03:08:41 +00:00
$scope . $evalAsync ( ( ) => {
service . EnvironmentVariables = service . EnvironmentVariables . concat ( { name : '' , value : '' } ) ;
updateServiceArray ( service , 'EnvironmentVariables' , service . EnvironmentVariables ) ;
} ) ;
2020-04-10 21:54:53 +00:00
} ;
2021-06-14 06:59:07 +00:00
$scope . onChangeEnvVars = onChangeEnvVars ;
function onChangeEnvVars ( env ) {
const service = $scope . service ;
const orgEnv = service . EnvironmentVariables ;
service . EnvironmentVariables = env . map ( ( v ) => {
const orgVar = orgEnv . find ( ( { name } ) => v . name === name ) ;
const added = orgVar && orgVar . added ;
return { ... v , added } ;
} ) ;
updateServiceArray ( service , 'EnvironmentVariables' , service . EnvironmentVariables ) ;
}
2020-04-10 21:54:53 +00:00
$scope . addConfig = function addConfig ( service , config ) {
if (
config &&
service . ServiceConfigs . filter ( function ( serviceConfig ) {
return serviceConfig . Id === config . Id ;
} ) . length === 0
) {
service . ServiceConfigs . push ( { Id : config . Id , Name : config . Name , FileName : config . Name , Uid : '0' , Gid : '0' , Mode : 292 } ) ;
updateServiceArray ( service , 'ServiceConfigs' , service . ServiceConfigs ) ;
}
} ;
$scope . removeConfig = function removeSecret ( service , index ) {
var removedElement = service . ServiceConfigs . splice ( index , 1 ) ;
if ( removedElement !== null ) {
updateServiceArray ( service , 'ServiceConfigs' , service . ServiceConfigs ) ;
}
} ;
$scope . updateConfig = function updateConfig ( service ) {
2017-11-06 08:47:31 +00:00
updateServiceArray ( service , 'ServiceConfigs' , service . ServiceConfigs ) ;
2020-04-10 21:54:53 +00:00
} ;
$scope . addSecret = function addSecret ( service , newSecret ) {
if ( newSecret . secret ) {
var filename = newSecret . secret . Name ;
if ( newSecret . override ) {
filename = newSecret . target ;
}
if (
service . ServiceSecrets . filter ( function ( serviceSecret ) {
return serviceSecret . Id === newSecret . secret . Id && serviceSecret . FileName === filename ;
} ) . length === 0
) {
service . ServiceSecrets . push ( { Id : newSecret . secret . Id , Name : newSecret . secret . Name , FileName : filename , Uid : '0' , Gid : '0' , Mode : 444 } ) ;
updateServiceArray ( service , 'ServiceSecrets' , service . ServiceSecrets ) ;
}
}
} ;
$scope . removeSecret = function removeSecret ( service , index ) {
var removedElement = service . ServiceSecrets . splice ( index , 1 ) ;
if ( removedElement !== null ) {
2017-11-20 13:44:23 +00:00
updateServiceArray ( service , 'ServiceSecrets' , service . ServiceSecrets ) ;
}
2020-04-10 21:54:53 +00:00
} ;
$scope . addLabel = function addLabel ( service ) {
service . ServiceLabels . push ( { key : '' , value : '' , originalValue : '' } ) ;
2017-03-20 20:28:09 +00:00
updateServiceArray ( service , 'ServiceLabels' , service . ServiceLabels ) ;
2020-04-10 21:54:53 +00:00
} ;
$scope . removeLabel = function removeLabel ( service , index ) {
var removedElement = service . ServiceLabels . splice ( index , 1 ) ;
if ( removedElement !== null ) {
updateServiceArray ( service , 'ServiceLabels' , service . ServiceLabels ) ;
}
} ;
$scope . updateLabel = function updateLabel ( service , label ) {
if ( label . value !== label . originalValue || label . key !== label . originalKey ) {
updateServiceArray ( service , 'ServiceLabels' , service . ServiceLabels ) ;
}
} ;
$scope . addContainerLabel = function addContainerLabel ( service ) {
service . ServiceContainerLabels . push ( { key : '' , value : '' , originalValue : '' } ) ;
2017-03-20 20:28:09 +00:00
updateServiceArray ( service , 'ServiceContainerLabels' , service . ServiceContainerLabels ) ;
2020-04-10 21:54:53 +00:00
} ;
$scope . removeContainerLabel = function removeLabel ( service , index ) {
var removedElement = service . ServiceContainerLabels . splice ( index , 1 ) ;
if ( removedElement !== null ) {
updateServiceArray ( service , 'ServiceContainerLabels' , service . ServiceContainerLabels ) ;
}
} ;
$scope . updateContainerLabel = function updateLabel ( service , label ) {
if ( label . value !== label . originalValue || label . key !== label . originalKey ) {
updateServiceArray ( service , 'ServiceContainerLabels' , service . ServiceContainerLabels ) ;
}
} ;
$scope . addMount = function addMount ( service ) {
2020-08-20 01:02:25 +00:00
service . ServiceMounts . push ( { Type : 'volume' , Source : null , Target : '' , ReadOnly : false } ) ;
2017-03-20 20:28:09 +00:00
updateServiceArray ( service , 'ServiceMounts' , service . ServiceMounts ) ;
2020-04-10 21:54:53 +00:00
} ;
$scope . removeMount = function removeMount ( service , index ) {
var removedElement = service . ServiceMounts . splice ( index , 1 ) ;
if ( removedElement !== null ) {
updateServiceArray ( service , 'ServiceMounts' , service . ServiceMounts ) ;
}
} ;
2021-03-02 22:10:34 +00:00
$scope . onChangeMountType = function onChangeMountType ( service , mount ) {
mount . Source = null ;
$scope . updateMount ( service , mount ) ;
} ;
2020-04-10 21:54:53 +00:00
$scope . updateMount = function updateMount ( service ) {
updateServiceArray ( service , 'ServiceMounts' , service . ServiceMounts ) ;
} ;
2020-08-06 23:11:47 +00:00
2023-05-25 03:59:32 +00:00
$scope . toggleMountReadOnly = function toggleMountReadOnly ( isReadOnly , index ) {
$scope . $evalAsync ( function ( ) {
updateServiceArray ( $scope . service , 'ServiceMounts' , $scope . service . ServiceMounts ) ;
$scope . service . ServiceMounts [ index ] . ReadOnly = isReadOnly ;
} ) ;
} ;
2020-08-06 23:11:47 +00:00
$scope . addNetwork = function addNetwork ( service ) {
if ( ! service . Networks ) {
service . Networks = [ ] ;
}
service . Networks . push ( { Editable : true } ) ;
} ;
$scope . removeNetwork = function removeNetwork ( service , index ) {
var removedElement = service . Networks . splice ( index , 1 ) ;
if ( removedElement && removedElement . length && removedElement [ 0 ] . Id ) {
updateServiceArray ( service , 'Networks' , service . Networks ) ;
}
} ;
$scope . updateNetwork = function updateNetwork ( service ) {
updateServiceArray ( service , 'Networks' , service . Networks ) ;
} ;
2020-04-10 21:54:53 +00:00
$scope . addPlacementConstraint = function addPlacementConstraint ( service ) {
service . ServiceConstraints . push ( { key : '' , operator : '==' , value : '' } ) ;
2017-03-20 20:28:09 +00:00
updateServiceArray ( service , 'ServiceConstraints' , service . ServiceConstraints ) ;
2020-04-10 21:54:53 +00:00
} ;
$scope . removePlacementConstraint = function removePlacementConstraint ( service , index ) {
var removedElement = service . ServiceConstraints . splice ( index , 1 ) ;
if ( removedElement !== null ) {
updateServiceArray ( service , 'ServiceConstraints' , service . ServiceConstraints ) ;
}
} ;
$scope . updatePlacementConstraint = function ( service ) {
updateServiceArray ( service , 'ServiceConstraints' , service . ServiceConstraints ) ;
} ;
$scope . addPlacementPreference = function ( service ) {
service . ServicePreferences . push ( { strategy : 'spread' , value : '' } ) ;
2017-07-10 07:33:09 +00:00
updateServiceArray ( service , 'ServicePreferences' , service . ServicePreferences ) ;
2020-04-10 21:54:53 +00:00
} ;
$scope . removePlacementPreference = function ( service , index ) {
var removedElement = service . ServicePreferences . splice ( index , 1 ) ;
if ( removedElement !== null ) {
updateServiceArray ( service , 'ServicePreferences' , service . ServicePreferences ) ;
}
} ;
$scope . updatePlacementPreference = function ( service ) {
updateServiceArray ( service , 'ServicePreferences' , service . ServicePreferences ) ;
} ;
$scope . addPublishedPort = function addPublishedPort ( service ) {
if ( ! service . Ports ) {
service . Ports = [ ] ;
}
service . Ports . push ( { PublishedPort : '' , TargetPort : '' , Protocol : 'tcp' , PublishMode : 'ingress' } ) ;
} ;
$scope . updatePublishedPort = function updatePublishedPort ( service ) {
2017-03-20 20:28:09 +00:00
updateServiceArray ( service , 'Ports' , service . Ports ) ;
2020-04-10 21:54:53 +00:00
} ;
$scope . removePortPublishedBinding = function removePortPublishedBinding ( service , index ) {
var removedElement = service . Ports . splice ( index , 1 ) ;
if ( removedElement !== null ) {
updateServiceArray ( service , 'Ports' , service . Ports ) ;
}
} ;
$scope . addLogDriverOpt = function addLogDriverOpt ( service ) {
service . LogDriverOpts . push ( { key : '' , value : '' , originalValue : '' } ) ;
2017-12-22 09:05:31 +00:00
updateServiceArray ( service , 'LogDriverOpts' , service . LogDriverOpts ) ;
2020-04-10 21:54:53 +00:00
} ;
$scope . removeLogDriverOpt = function removeLogDriverOpt ( service , index ) {
var removedElement = service . LogDriverOpts . splice ( index , 1 ) ;
if ( removedElement !== null ) {
updateServiceArray ( service , 'LogDriverOpts' , service . LogDriverOpts ) ;
}
} ;
$scope . updateLogDriverOpt = function updateLogDriverOpt ( service , variable ) {
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 . addHostsEntry = function ( service ) {
if ( ! service . Hosts ) {
service . Hosts = [ ] ;
}
service . Hosts . push ( { hostname : '' , ip : '' } ) ;
} ;
$scope . removeHostsEntry = function ( service , index ) {
var removedElement = service . Hosts . splice ( index , 1 ) ;
if ( removedElement !== null ) {
updateServiceArray ( service , 'Hosts' , service . Hosts ) ;
}
} ;
$scope . updateHostsEntry = function ( service ) {
2017-12-21 08:53:34 +00:00
updateServiceArray ( service , 'Hosts' , service . Hosts ) ;
2020-04-10 21:54:53 +00:00
} ;
2022-07-20 00:39:44 +00:00
$scope . onWebhookChange = function ( enabled ) {
2023-05-25 03:59:32 +00:00
enabled = enabled | '' ;
2022-07-20 00:39:44 +00:00
$scope . $evalAsync ( ( ) => {
$scope . updateWebhook ( $scope . service ) ;
$scope . WebhookExists = enabled ;
2023-05-25 03:59:32 +00:00
updateServiceAttribute ( $scope . service , 'Webhooks' , enabled ) ;
2022-07-20 00:39:44 +00:00
} ) ;
} ;
2020-04-10 21:54:53 +00:00
$scope . updateWebhook = function updateWebhook ( service ) {
if ( $scope . WebhookExists ) {
WebhookService . deleteWebhook ( $scope . webhookID )
. then ( function success ( ) {
$scope . webhookURL = null ;
$scope . webhookID = null ;
$scope . WebhookExists = false ;
} )
. catch ( function error ( err ) {
Notifications . error ( 'Failure' , err , 'Unable to delete webhook' ) ;
} ) ;
} else {
2021-12-06 20:11:44 +00:00
WebhookService . createServiceWebhook ( service . Id , endpoint . Id , $scope . initialRegistryID )
2020-04-10 21:54:53 +00:00
. then ( function success ( data ) {
$scope . WebhookExists = true ;
$scope . webhookID = data . Id ;
$scope . webhookURL = WebhookHelper . returnWebhookUrl ( data . Token ) ;
} )
. catch ( function error ( err ) {
Notifications . error ( 'Failure' , err , 'Unable to create webhook' ) ;
} ) ;
}
} ;
2021-12-06 20:11:44 +00:00
$scope . updateWebhookRegistryId = function ( ) {
const newRegistryID = _ . get ( $scope . formValues . RegistryModel , 'Registry.Id' , 0 ) ;
const registryChanged = $scope . initialRegistryID != newRegistryID ;
if ( $scope . WebhookExists && registryChanged ) {
2021-12-16 18:52:54 +00:00
WebhookService . updateServiceWebhook ( $scope . webhookID , newRegistryID ) . catch ( function error ( err ) {
Notifications . error ( 'Failure' , err , 'Unable to update webhook' ) ;
} ) ;
2021-12-06 20:11:44 +00:00
}
} ;
2020-04-10 21:54:53 +00:00
$scope . copyWebhook = function copyWebhook ( ) {
clipboard . copyText ( $scope . webhookURL ) ;
$ ( '#copyNotification' ) . show ( ) ;
$ ( '#copyNotification' ) . fadeOut ( 2000 ) ;
} ;
2018-09-03 10:08:03 +00:00
2021-10-14 12:42:10 +00:00
$scope . cancelChanges = async function cancelChanges ( service , keys ) {
2020-04-10 21:54:53 +00:00
if ( keys ) {
// clean out the keys only from the list of modified keys
2021-10-14 12:42:10 +00:00
for ( const key of keys ) {
2020-04-10 21:54:53 +00:00
if ( key === 'Image' ) {
2021-10-14 12:42:10 +00:00
$scope . formValues . RegistryModel = await RegistryService . retrievePorRegistryModelFromRepository ( originalService . Image , endpoint . Id ) ;
2020-04-10 21:54:53 +00:00
} else {
var index = previousServiceValues . indexOf ( key ) ;
if ( index >= 0 ) {
previousServiceValues . splice ( index , 1 ) ;
}
}
2021-10-14 12:42:10 +00:00
}
2020-04-10 21:54:53 +00:00
} else {
// clean out all changes
2021-10-14 12:42:10 +00:00
$scope . formValues . RegistryModel = await RegistryService . retrievePorRegistryModelFromRepository ( originalService . Image , endpoint . Id ) ;
2020-04-10 21:54:53 +00:00
keys = Object . keys ( service ) ;
previousServiceValues = [ ] ;
}
keys . forEach ( function ( attribute ) {
service [ attribute ] = originalService [ attribute ] ; // reset service values
} ) ;
service . hasChanges = false ;
} ;
2018-09-03 10:08:03 +00:00
2020-04-10 21:54:53 +00:00
$scope . hasChanges = function ( service , elements ) {
var hasChanges = false ;
elements . forEach ( function ( key ) {
2019-11-27 22:36:39 +00:00
if ( key === 'Image' ) {
2021-10-14 12:42:10 +00:00
const originalImage = service ? service . Model . Spec . TaskTemplate . ContainerSpec . Image : null ;
const currentImage = ImageHelper . createImageConfigForContainer ( $scope . formValues . RegistryModel ) . fromImage ;
hasChanges = hasChanges || originalImage !== currentImage ;
2019-11-27 22:36:39 +00:00
} else {
2020-04-10 21:54:53 +00:00
hasChanges = hasChanges || previousServiceValues . indexOf ( key ) >= 0 ;
2017-03-20 20:28:09 +00:00
}
} ) ;
2020-04-10 21:54:53 +00:00
return hasChanges ;
} ;
2020-12-14 03:27:05 +00:00
$scope . mountsAreValid = mountsAreValid ;
function mountsAreValid ( ) {
const mounts = $scope . service . ServiceMounts ;
return mounts . every ( ( mount ) => mount . Source && mount . Target ) ;
}
2020-04-10 21:54:53 +00:00
function buildChanges ( service ) {
var config = ServiceHelper . serviceToConfig ( service . Model ) ;
config . Name = service . Name ;
config . Labels = LabelHelper . fromKeyValueToLabelHash ( service . ServiceLabels ) ;
2021-06-14 06:59:07 +00:00
config . TaskTemplate . ContainerSpec . Env = envVarsUtils . convertToArrayOfStrings ( service . EnvironmentVariables ) ;
2020-04-10 21:54:53 +00:00
config . TaskTemplate . ContainerSpec . Labels = LabelHelper . fromKeyValueToLabelHash ( service . ServiceContainerLabels ) ;
if ( $scope . hasChanges ( service , [ 'Image' ] ) ) {
const image = ImageHelper . createImageConfigForContainer ( $scope . formValues . RegistryModel ) ;
config . TaskTemplate . ContainerSpec . Image = image . fromImage ;
2022-03-10 05:35:11 +00:00
config . registryId = $scope . formValues . RegistryModel . Registry . Id ;
2019-11-27 22:36:39 +00:00
} else {
2020-04-10 21:54:53 +00:00
config . TaskTemplate . ContainerSpec . Image = service . Image ;
}
2019-11-27 22:36:39 +00:00
2020-08-06 23:11:47 +00:00
if ( $scope . hasChanges ( service , [ 'Networks' ] ) ) {
config . Networks = _ . map (
_ . filter ( service . Networks , ( item ) => item . Id && item . Editable ) ,
( item ) => ( { Target : item . Id } )
) ;
config . TaskTemplate . Networks = config . Networks ;
}
2020-04-10 21:54:53 +00:00
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 ) : [ ] ;
2018-01-08 21:06:56 +00:00
2020-04-10 21:54:53 +00:00
if ( service . Mode === 'replicated' ) {
config . Mode . Replicated . Replicas = service . Replicas ;
}
config . TaskTemplate . ContainerSpec . Mounts = service . ServiceMounts ;
if ( typeof config . TaskTemplate . Placement === 'undefined' ) {
config . TaskTemplate . Placement = { } ;
}
config . TaskTemplate . Placement . Constraints = ServiceHelper . translateKeyValueToPlacementConstraints ( service . ServiceConstraints ) ;
config . TaskTemplate . Placement . Preferences = ServiceHelper . translateKeyValueToPlacementPreferences ( service . ServicePreferences ) ;
if ( $scope . hasChanges ( service , [ 'LimitNanoCPUs' , 'LimitMemoryBytes' , 'ReservationNanoCPUs' , 'ReservationMemoryBytes' ] ) ) {
// Round memory values to 0.125 and convert MB to B
var memoryLimit = ( Math . round ( service . LimitMemoryBytes * 8 ) / 8 ) . toFixed ( 3 ) ;
memoryLimit *= 1024 * 1024 ;
var memoryReservation = ( Math . round ( service . ReservationMemoryBytes * 8 ) / 8 ) . toFixed ( 3 ) ;
memoryReservation *= 1024 * 1024 ;
config . TaskTemplate . Resources = {
Limits : {
NanoCPUs : service . LimitNanoCPUs * 1000000000 ,
MemoryBytes : memoryLimit ,
} ,
Reservations : {
NanoCPUs : service . ReservationNanoCPUs * 1000000000 ,
MemoryBytes : memoryReservation ,
} ,
} ;
}
if ( $scope . hasChanges ( service , [ 'UpdateFailureAction' , 'UpdateDelay' , 'UpdateParallelism' , 'UpdateOrder' ] ) ) {
config . UpdateConfig = {
Parallelism : service . UpdateParallelism ,
Delay : ServiceHelper . translateHumanDurationToNanos ( service . UpdateDelay ) || 0 ,
FailureAction : service . UpdateFailureAction ,
Order : service . UpdateOrder ,
} ;
}
if ( $scope . hasChanges ( service , [ 'RestartCondition' , 'RestartDelay' , 'RestartMaxAttempts' , 'RestartWindow' ] ) ) {
config . TaskTemplate . RestartPolicy = {
Condition : service . RestartCondition ,
Delay : ServiceHelper . translateHumanDurationToNanos ( service . RestartDelay ) || 5000000000 ,
MaxAttempts : service . RestartMaxAttempts ,
Window : ServiceHelper . translateHumanDurationToNanos ( service . RestartWindow ) || 0 ,
} ;
}
config . TaskTemplate . LogDriver = null ;
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 ;
}
2018-05-10 07:54:22 +00:00
}
2020-04-10 21:54:53 +00:00
}
2016-11-09 00:23:56 +00:00
2020-04-10 21:54:53 +00:00
if ( service . Ports ) {
service . Ports . forEach ( function ( binding ) {
if ( binding . PublishedPort === null || binding . PublishedPort === '' ) {
delete binding . PublishedPort ;
}
} ) ;
}
config . EndpointSpec = {
Mode : ( config . EndpointSpec && config . EndpointSpec . Mode ) || 'vip' ,
Ports : service . Ports ,
2018-05-10 07:54:22 +00:00
} ;
2020-04-10 21:54:53 +00:00
return service , config ;
2018-05-10 07:54:22 +00:00
}
2017-08-09 13:30:50 +00:00
2020-04-10 21:54:53 +00:00
function rollbackService ( service ) {
$scope . state . rollbackInProgress = true ;
let config = { } ;
service , ( config = buildChanges ( service ) ) ;
ServiceService . update ( service , config , 'previous' )
. then ( function ( data ) {
if ( data . message && data . message . match ( /^rpc error:/ ) ) {
2021-10-11 21:37:07 +00:00
Notifications . error ( 'Failure' , data , 'Error' ) ;
2020-04-10 21:54:53 +00:00
} else {
Notifications . success ( 'Success' , 'Service successfully rolled back' ) ;
$scope . cancelChanges ( { } ) ;
initView ( ) ;
}
} )
. catch ( function ( e ) {
if ( e . data . message && e . data . message . includes ( 'does not have a previous spec' ) ) {
Notifications . error ( 'Failure' , { message : 'No previous config to rollback to.' } ) ;
} else {
Notifications . error ( 'Failure' , e , 'Unable to rollback service' ) ;
}
} )
. finally ( function ( ) {
$scope . state . rollbackInProgress = false ;
} ) ;
2018-01-23 09:06:58 +00:00
}
2018-01-08 21:06:56 +00:00
2020-04-10 21:54:53 +00:00
$scope . rollbackService = function ( service ) {
2023-02-14 08:19:41 +00:00
confirm ( {
2020-04-10 21:54:53 +00:00
title : 'Rollback service' ,
message : 'Are you sure you want to rollback?' ,
2023-02-14 08:19:41 +00:00
modalType : ModalType . Warn ,
confirmButton : buildConfirmButton ( 'Yes' , 'danger' ) ,
} ) . then ( ( confirmed ) => {
if ( ! confirmed ) {
return ;
}
rollbackService ( service ) ;
2020-04-10 21:54:53 +00:00
} ) ;
} ;
2021-03-24 18:27:32 +00:00
$scope . setPullImageValidity = setPullImageValidity ;
function setPullImageValidity ( validity ) {
$scope . state . pullImageValidity = validity ;
}
2020-04-10 21:54:53 +00:00
$scope . updateService = function updateService ( service ) {
let config = { } ;
service , ( config = buildChanges ( service ) ) ;
ServiceService . update ( service , config ) . then (
function ( data ) {
if ( data . message && data . message . match ( /^rpc error:/ ) ) {
2021-10-11 21:37:07 +00:00
Notifications . error ( 'Failure' , data , 'Error' ) ;
2020-04-10 21:54:53 +00:00
} else {
Notifications . success ( 'Service successfully updated' , 'Service updated' ) ;
2021-12-06 20:11:44 +00:00
$scope . updateWebhookRegistryId ( ) ;
2020-04-10 21:54:53 +00:00
}
$scope . cancelChanges ( { } ) ;
initView ( ) ;
} ,
function ( e ) {
Notifications . error ( 'Failure' , e , 'Unable to update service' ) ;
2017-12-22 09:05:31 +00:00
}
2020-04-10 21:54:53 +00:00
) ;
} ;
2017-03-30 10:00:16 +00:00
2020-04-10 21:54:53 +00:00
$scope . removeService = function ( ) {
2023-02-14 08:19:41 +00:00
confirmDelete ( 'Do you want to remove this service? All the containers associated to this service will be removed too.' ) . then ( ( confirmed ) => {
2020-04-10 21:54:53 +00:00
if ( ! confirmed ) {
return ;
2017-04-06 08:35:01 +00:00
}
2020-04-10 21:54:53 +00:00
removeService ( ) ;
2017-04-06 08:35:01 +00:00
} ) ;
2020-04-10 21:54:53 +00:00
} ;
function removeService ( ) {
$scope . state . deletionInProgress = true ;
ServiceService . remove ( $scope . service )
. then ( function success ( ) {
return $q . when ( $scope . webhookID && WebhookService . deleteWebhook ( $scope . webhookID ) ) ;
} )
. then ( function success ( ) {
2022-08-10 05:07:35 +00:00
Notifications . success ( 'Success' , 'Service successfully deleted' ) ;
2020-04-10 21:54:53 +00:00
$state . go ( 'docker.services' , { } ) ;
} )
. catch ( function error ( err ) {
Notifications . error ( 'Failure' , err , 'Unable to remove service' ) ;
} )
. finally ( function final ( ) {
$scope . state . deletionInProgress = false ;
} ) ;
2017-04-06 08:35:01 +00:00
}
2017-03-30 10:00:16 +00:00
2020-04-10 21:54:53 +00:00
$scope . forceUpdateService = function ( service ) {
2023-02-14 08:19:41 +00:00
confirmServiceForceUpdate ( 'Do you want to force an update of the service? All the tasks associated to the service will be recreated.' ) . then ( function ( result ) {
2020-04-10 21:54:53 +00:00
if ( ! result ) {
return ;
2019-09-09 22:56:57 +00:00
}
2023-02-14 08:19:41 +00:00
forceUpdateService ( service , result . pullLatest ) ;
2020-04-10 21:54:53 +00:00
} ) ;
} ;
function forceUpdateService ( service , pullImage ) {
var config = ServiceHelper . serviceToConfig ( service . Model ) ;
if ( pullImage ) {
config . TaskTemplate . ContainerSpec . Image = ImageHelper . removeDigestFromRepository ( config . TaskTemplate . ContainerSpec . Image ) ;
2018-01-08 21:06:56 +00:00
}
2020-04-10 21:54:53 +00:00
// 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 ++ ;
$scope . state . updateInProgress = true ;
ServiceService . update ( service , config )
. then ( function success ( ) {
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 ) ;
} )
. finally ( function final ( ) {
$scope . state . updateInProgress = false ;
} ) ;
2018-06-20 13:53:58 +00:00
}
2020-04-10 21:54:53 +00:00
function translateServiceArrays ( service ) {
service . ServiceSecrets = service . Secrets ? service . Secrets . map ( SecretHelper . flattenSecret ) : [ ] ;
service . ServiceConfigs = service . Configs ? service . Configs . map ( ConfigHelper . flattenConfig ) : [ ] ;
2021-06-14 06:59:07 +00:00
service . EnvironmentVariables = envVarsUtils
. parseArrayOfStrings ( service . Env )
. map ( ( v ) => ( { ... v , added : true } ) )
. sort ( ( v1 , v2 ) => ( v1 . name > v2 . name ? 1 : - 1 ) ) ;
2020-04-10 21:54:53 +00:00
service . LogDriverOpts = ServiceHelper . translateLogDriverOptsToKeyValue ( service . LogDriverOpts ) ;
service . ServiceLabels = LabelHelper . fromLabelHashToKeyValue ( service . Labels ) ;
service . ServiceContainerLabels = LabelHelper . fromLabelHashToKeyValue ( service . ContainerLabels ) ;
service . ServiceMounts = angular . copy ( service . Mounts ) ;
service . ServiceConstraints = ServiceHelper . translateConstraintsToKeyValue ( service . Constraints ) ;
service . ServicePreferences = ServiceHelper . translatePreferencesToKeyValue ( service . Preferences ) ;
service . Hosts = ServiceHelper . translateHostsEntriesToHostnameIP ( service . Hosts ) ;
}
2018-05-06 07:15:57 +00:00
2020-04-10 21:54:53 +00:00
function transformResources ( service ) {
service . LimitNanoCPUs = service . LimitNanoCPUs / 1000000000 || 0 ;
service . ReservationNanoCPUs = service . ReservationNanoCPUs / 1000000000 || 0 ;
service . LimitMemoryBytes = service . LimitMemoryBytes / 1024 / 1024 || 0 ;
service . ReservationMemoryBytes = service . ReservationMemoryBytes / 1024 / 1024 || 0 ;
}
2018-05-10 15:17:53 +00:00
2020-04-10 21:54:53 +00:00
function transformDurations ( service ) {
service . RestartDelay = ServiceHelper . translateNanosToHumanDuration ( service . RestartDelay ) || '5s' ;
service . RestartWindow = ServiceHelper . translateNanosToHumanDuration ( service . RestartWindow ) || '0s' ;
service . UpdateDelay = ServiceHelper . translateNanosToHumanDuration ( service . UpdateDelay ) || '0s' ;
service . StopGracePeriod = service . StopGracePeriod ? ServiceHelper . translateNanosToHumanDuration ( service . StopGracePeriod ) : '' ;
}
2018-05-06 07:15:57 +00:00
2020-04-10 21:54:53 +00:00
function initView ( ) {
var apiVersion = $scope . applicationState . endpoint . apiVersion ;
var agentProxy = $scope . applicationState . endpoint . mode . agentProxy ;
var service = null ;
ServiceService . service ( $transition$ . params ( ) . id )
. then ( function success ( data ) {
service = data ;
$scope . isUpdating = $scope . lastVersion >= service . Version ;
if ( ! $scope . isUpdating ) {
$scope . lastVersion = service . Version ;
}
2018-05-06 07:15:57 +00:00
2020-04-10 21:54:53 +00:00
transformResources ( service ) ;
translateServiceArrays ( service ) ;
transformDurations ( service ) ;
$scope . service = service ;
originalService = angular . copy ( service ) ;
return $q . all ( {
volumes : VolumeService . volumes ( ) ,
tasks : TaskService . tasks ( { service : [ service . Name ] } ) ,
containers : agentProxy ? ContainerService . containers ( ) : [ ] ,
nodes : NodeService . nodes ( ) ,
secrets : apiVersion >= 1.25 ? SecretService . secrets ( ) : [ ] ,
configs : apiVersion >= 1.3 ? ConfigService . configs ( ) : [ ] ,
availableImages : ImageService . images ( ) ,
availableLoggingDrivers : PluginService . loggingPlugins ( apiVersion < 1.25 ) ,
2020-08-06 23:11:47 +00:00
availableNetworks : NetworkService . networks ( true , true , apiVersion >= 1.25 ) ,
2021-07-14 09:15:21 +00:00
webhooks : WebhookService . webhooks ( service . Id , endpoint . Id ) ,
2020-04-10 21:54:53 +00:00
} ) ;
} )
2020-08-06 23:11:47 +00:00
. then ( async function success ( data ) {
2020-04-10 21:54:53 +00:00
$scope . nodes = data . nodes ;
$scope . configs = data . configs ;
$scope . secrets = data . secrets ;
$scope . availableImages = ImageService . getUniqueTagListFromImages ( data . availableImages ) ;
$scope . availableLoggingDrivers = data . availableLoggingDrivers ;
$scope . availableVolumes = data . volumes ;
2021-02-09 08:09:06 +00:00
$scope . allowBindMounts = endpoint . SecuritySettings . allowBindMountsForRegularUsers ;
2020-04-10 21:54:53 +00:00
$scope . isAdmin = Authentication . isAdmin ( ) ;
2020-08-06 23:11:47 +00:00
$scope . availableNetworks = data . availableNetworks ;
$scope . swarmNetworks = _ . filter ( $scope . availableNetworks , ( network ) => network . Scope === 'swarm' ) ;
2023-05-25 03:59:32 +00:00
$scope . WebhookExists = false ;
2020-08-06 23:11:47 +00:00
const serviceNetworks = _ . uniqBy ( _ . concat ( $scope . service . Model . Spec . Networks || [ ] , $scope . service . Model . Spec . TaskTemplate . Networks || [ ] ) , 'Target' ) ;
const networks = _ . filter (
_ . map ( serviceNetworks , ( { Target } ) => _ . find ( data . availableNetworks , { Id : Target } ) ) ,
Boolean
) ;
if ( _ . some ( $scope . service . Ports , ( port ) => port . PublishMode === 'ingress' ) ) {
const ingressNetwork = _ . find ( $scope . availableNetworks , ( network ) => network . Ingress ) ;
if ( ingressNetwork ) {
networks . unshift ( ingressNetwork ) ;
}
}
$scope . service . Networks = await Promise . all (
_ . map ( networks , async ( item ) => {
let addr = '' ;
if ( item . IPAM . Config . length ) {
addr = item . IPAM . Config [ 0 ] . Subnet ;
} else {
const network = await NetworkService . network ( item . Id ) ;
addr = ( network && network . IPAM && network . IPAM . Config && network . IPAM . Config . length && network . IPAM . Config [ 0 ] . Subnet ) || '' ;
}
return { Id : item . Id , Name : item . Name , Addr : addr , Editable : ! item . Ingress } ;
} )
) ;
originalService . Networks = angular . copy ( $scope . service . Networks ) ;
2020-04-10 21:54:53 +00:00
if ( data . webhooks . length > 0 ) {
var webhook = data . webhooks [ 0 ] ;
$scope . WebhookExists = true ;
$scope . webhookID = webhook . Id ;
$scope . webhookURL = WebhookHelper . returnWebhookUrl ( webhook . Token ) ;
}
2017-09-20 06:32:19 +00:00
2020-04-10 21:54:53 +00:00
var tasks = data . tasks ;
2017-11-20 13:44:23 +00:00
2020-04-10 21:54:53 +00:00
if ( agentProxy ) {
var containers = data . containers ;
for ( var i = 0 ; i < tasks . length ; i ++ ) {
var task = tasks [ i ] ;
TaskHelper . associateContainerToTask ( task , containers ) ;
}
}
ServiceHelper . associateTasksToService ( service , tasks ) ;
$scope . tasks = data . tasks ;
// Set max cpu value
var maxCpus = 0 ;
for ( var n in data . nodes ) {
if ( data . nodes [ n ] . CPUs && data . nodes [ n ] . CPUs > maxCpus ) {
maxCpus = data . nodes [ n ] . CPUs ;
}
}
if ( maxCpus > 0 ) {
$scope . state . sliderMaxCpu = maxCpus / 1000000000 ;
} else {
$scope . state . sliderMaxCpu = 32 ;
}
2021-10-14 12:42:10 +00:00
const image = $scope . service . Model . Spec . TaskTemplate . ContainerSpec . Image ;
RegistryService . retrievePorRegistryModelFromRepository ( image , endpoint . Id ) . then ( ( model ) => {
$scope . formValues . RegistryModel = model ;
2021-12-06 20:11:44 +00:00
$scope . initialRegistryID = _ . get ( model , 'Registry.Id' , 0 ) ;
2021-10-14 12:42:10 +00:00
} ) ;
2020-04-10 21:54:53 +00:00
// Default values
$scope . state . addSecret = { override : false } ;
$timeout ( function ( ) {
$anchorScroll ( ) ;
} ) ;
} )
. catch ( function error ( err ) {
$scope . secrets = [ ] ;
$scope . configs = [ ] ;
Notifications . error ( 'Failure' , err , 'Unable to retrieve service details' ) ;
} ) ;
2016-11-09 00:23:56 +00:00
}
2017-03-20 20:28:09 +00:00
2020-04-10 21:54:53 +00:00
$scope . updateServiceAttribute = updateServiceAttribute ;
function updateServiceAttribute ( service , name ) {
if ( service [ name ] !== originalService [ name ] || ! ( name in originalService ) ) {
service . hasChanges = true ;
}
previousServiceValues . push ( name ) ;
}
2020-08-06 23:11:47 +00:00
$scope . filterNetworks = filterNetworks ;
function filterNetworks ( networks , current ) {
return networks . filter ( ( network ) => ! network . Ingress && ( network . Id === current . Id || $scope . service . Networks . every ( ( serviceNetwork ) => network . Id !== serviceNetwork . Id ) ) ) ;
}
2020-04-10 21:54:53 +00:00
function updateServiceArray ( service , name ) {
previousServiceValues . push ( name ) ;
service . hasChanges = true ;
}
2016-11-09 00:23:56 +00:00
2020-04-10 21:54:53 +00:00
initView ( ) ;
} ,
] ) ;