angular.module('service', []) .controller('ServiceController', ['$q', '$scope', '$stateParams', '$state', '$location', '$anchorScroll', 'ServiceService', 'Secret', 'SecretHelper', 'Service', 'ServiceHelper', 'TaskService', 'NodeService', 'Notifications', 'Pagination', 'ModalService', 'ControllerDataPipeline', function ($q, $scope, $stateParams, $state, $location, $anchorScroll, ServiceService, Secret, SecretHelper, Service, ServiceHelper, TaskService, NodeService, Notifications, Pagination, ModalService, ControllerDataPipeline) { $scope.state = {}; $scope.state.pagination_count = Pagination.getPaginationCount('service_tasks'); $scope.tasks = []; $scope.sortType = 'Status'; $scope.sortReverse = false; $scope.lastVersion = 0; 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; }; $scope.changeServiceImage = function changeServiceImage(service) { updateServiceAttribute(service, 'Image', service.newServiceImage || service.image); service.EditImage = false; }; $scope.scaleService = function scaleService(service) { var replicas = service.newServiceReplicas === null || isNaN(service.newServiceReplicas) ? service.Replicas : service.newServiceReplicas; updateServiceAttribute(service, 'Replicas', replicas); service.EditReplicas = false; }; $scope.goToItem = function(hash) { $anchorScroll(hash); }; $scope.addEnvironmentVariable = function addEnvironmentVariable(service) { service.EnvironmentVariables.push({ key: '', value: '', originalValue: '' }); updateServiceArray(service, 'EnvironmentVariables', service.EnvironmentVariables); }; $scope.removeEnvironmentVariable = function removeEnvironmentVariable(service, index) { var removedElement = service.EnvironmentVariables.splice(index, 1); if (removedElement !== null) { updateServiceArray(service, 'EnvironmentVariables', service.EnvironmentVariables); } }; $scope.updateEnvironmentVariable = function updateEnvironmentVariable(service, variable) { if (variable.value !== variable.originalValue || variable.key !== variable.originalKey) { updateServiceArray(service, 'EnvironmentVariables', service.EnvironmentVariables); } }; $scope.addSecret = function addSecret(service, secret) { if (secret && service.ServiceSecrets.filter(function(serviceSecret) { return serviceSecret.Id === secret.Id;}).length === 0) { service.ServiceSecrets.push({ Id: secret.Id, Name: secret.Name, FileName: secret.Name, 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) { updateServiceArray(service, 'ServiceSecrets', service.ServiceSecrets); } }; $scope.addLabel = function addLabel(service) { service.ServiceLabels.push({ key: '', value: '', originalValue: '' }); updateServiceArray(service, 'ServiceLabels', service.ServiceLabels); }; $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: '' }); updateServiceArray(service, 'ServiceContainerLabels', service.ServiceContainerLabels); }; $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) { service.ServiceMounts.push({Type: 'volume', Source: '', Target: '', ReadOnly: false }); updateServiceArray(service, 'ServiceMounts', service.ServiceMounts); }; $scope.removeMount = function removeMount(service, index) { var removedElement = service.ServiceMounts.splice(index, 1); if (removedElement !== null) { updateServiceArray(service, 'ServiceMounts', service.ServiceMounts); } }; $scope.updateMount = function updateMount(service, mount) { updateServiceArray(service, 'ServiceMounts', service.ServiceMounts); }; $scope.addPlacementConstraint = function addPlacementConstraint(service) { service.ServiceConstraints.push({ key: '', operator: '==', value: '' }); updateServiceArray(service, 'ServiceConstraints', service.ServiceConstraints); }; $scope.removePlacementConstraint = function removePlacementConstraint(service, index) { var removedElement = service.ServiceConstraints.splice(index, 1); if (removedElement !== null) { updateServiceArray(service, 'ServiceConstraints', service.ServiceConstraints); } }; $scope.updatePlacementConstraint = function updatePlacementConstraint(service, constraint) { updateServiceArray(service, 'ServiceConstraints', service.ServiceConstraints); }; $scope.addPublishedPort = function addPublishedPort(service) { if (!service.Ports) { service.Ports = []; } service.Ports.push({ PublishedPort: '', TargetPort: '', Protocol: 'tcp', PublishMode: 'ingress' }); }; $scope.updatePublishedPort = function updatePublishedPort(service, portMapping) { updateServiceArray(service, 'Ports', service.Ports); }; $scope.removePortPublishedBinding = function removePortPublishedBinding(service, index) { var removedElement = service.Ports.splice(index, 1); if (removedElement !== null) { updateServiceArray(service, 'Ports', service.Ports); } }; $scope.cancelChanges = function cancelChanges(service, keys) { if (keys) { // clean out the keys only from the list of modified keys keys.forEach(function(key) { var index = previousServiceValues.indexOf(key); if (index >= 0) { previousServiceValues.splice(index, 1); } }); } else { // clean out all changes keys = Object.keys(service); previousServiceValues = []; } keys.forEach(function(attribute) { service[attribute] = originalService[attribute]; // reset service values }); service.hasChanges = false; }; $scope.hasChanges = function(service, elements) { var hasChanges = false; elements.forEach(function(key) { hasChanges = hasChanges || (previousServiceValues.indexOf(key) >= 0); }); return hasChanges; }; $scope.updateService = function updateService(service) { $('#loadingViewSpinner').show(); var config = ServiceHelper.serviceToConfig(service.Model); config.Name = service.Name; config.Labels = translateServiceLabelsToLabels(service.ServiceLabels); config.TaskTemplate.ContainerSpec.Env = translateEnvironmentVariablesToEnv(service.EnvironmentVariables); config.TaskTemplate.ContainerSpec.Labels = translateServiceLabelsToLabels(service.ServiceContainerLabels); config.TaskTemplate.ContainerSpec.Image = service.Image; config.TaskTemplate.ContainerSpec.Secrets = service.ServiceSecrets ? service.ServiceSecrets.map(SecretHelper.secretConfig) : []; 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.Resources = { Limits: { NanoCPUs: service.LimitNanoCPUs, MemoryBytes: service.LimitMemoryBytes }, Reservations: { NanoCPUs: service.ReservationNanoCPUs, MemoryBytes: service.ReservationMemoryBytes } }; config.UpdateConfig = { Parallelism: service.UpdateParallelism, Delay: service.UpdateDelay, FailureAction: service.UpdateFailureAction }; config.TaskTemplate.RestartPolicy = { Condition: service.RestartCondition, Delay: service.RestartDelay, MaxAttempts: service.RestartMaxAttempts, Window: service.RestartWindow }; if (service.Ports) { service.Ports.forEach(function (binding) { if (binding.PublishedPort === null || binding.PublishedPort === '') { delete binding.PublishedPort; } }); } config.EndpointSpec = { Mode: config.EndpointSpec.Mode || 'vip', Ports: service.Ports }; Service.update({ id: service.Id, version: service.Version }, config, function (data) { $('#loadingViewSpinner').hide(); Notifications.success('Service successfully updated', 'Service updated'); $scope.cancelChanges({}); initView(); }, function (e) { $('#loadingViewSpinner').hide(); Notifications.error('Failure', e, 'Unable to update service'); }); }; $scope.removeService = function() { ModalService.confirmDeletion( 'Do you want to remove this service? All the containers associated to this service will be removed too.', function onConfirm(confirmed) { if(!confirmed) { return; } removeService(); } ); }; function removeService() { $('#loadingViewSpinner').show(); ServiceService.remove($scope.service) .then(function success(data) { Notifications.success('Service successfully deleted'); $state.go('services', {}); }) .catch(function error(err) { Notifications.error('Failure', err, 'Unable to remove service'); }) .finally(function final() { $('#loadingViewSpinner').hide(); }); } function translateServiceArrays(service) { service.ServiceSecrets = service.Secrets ? service.Secrets.map(SecretHelper.flattenSecret) : []; service.EnvironmentVariables = translateEnvironmentVariables(service.Env); service.ServiceLabels = translateLabelsToServiceLabels(service.Labels); service.ServiceContainerLabels = translateLabelsToServiceLabels(service.ContainerLabels); service.ServiceMounts = angular.copy(service.Mounts); service.ServiceConstraints = translateConstraintsToKeyValue(service.Constraints); } function initView() { $('#loadingViewSpinner').show(); ServiceService.service($stateParams.id) .then(function success(data) { var service = data; $scope.isUpdating = $scope.lastVersion >= service.Version; if (!$scope.isUpdating) { $scope.lastVersion = service.Version; } translateServiceArrays(service); $scope.service = service; ControllerDataPipeline.setAccessControlData('service', $stateParams.id, service.ResourceControl); originalService = angular.copy(service); return $q.all({ tasks: TaskService.serviceTasks(service.Name), nodes: NodeService.nodes(), secrets: Secret.query({}).$promise }); }) .then(function success(data) { $scope.tasks = data.tasks; $scope.nodes = data.nodes; $scope.secrets = data.secrets.map(function (secret) { return new SecretViewModel(secret); }); }) .catch(function error(err) { $scope.secrets = []; Notifications.error('Failure', err, 'Unable to retrieve service details'); }) .finally(function final() { $('#loadingViewSpinner').hide(); }); } function fetchSecrets() { $('#loadSecretsSpinner').show(); Secret.query({}, function (d) { $scope.secrets = d.map(function (secret) { return new SecretViewModel(secret); }); $('#loadSecretsSpinner').hide(); }, function(e) { $('#loadSecretsSpinner').hide(); Notifications.error('Failure', e, 'Unable to retrieve secrets'); $scope.secrets = []; }); } $scope.updateServiceAttribute = function updateServiceAttribute(service, name) { if (service[name] !== originalService[name] || !(name in originalService)) { service.hasChanges = true; } previousServiceValues.push(name); }; function updateServiceArray(service, name) { previousServiceValues.push(name); service.hasChanges = true; } function translateEnvironmentVariables(env) { if (env) { var variables = []; env.forEach(function(variable) { var idx = variable.indexOf('='); var keyValue = [variable.slice(0,idx), variable.slice(idx+1)]; var originalValue = (keyValue.length > 1) ? keyValue[1] : ''; variables.push({ key: keyValue[0], value: originalValue, originalKey: keyValue[0], originalValue: originalValue, added: true}); }); return variables; } return []; } function translateEnvironmentVariablesToEnv(env) { if (env) { var variables = []; env.forEach(function(variable) { if (variable.key && variable.key !== '') { variables.push(variable.key + '=' + variable.value); } }); return variables; } return []; } function translateLabelsToServiceLabels(Labels) { var labels = []; if (Labels) { Object.keys(Labels).forEach(function(key) { labels.push({ key: key, value: Labels[key], originalKey: key, originalValue: Labels[key], added: true}); }); } return labels; } function translateServiceLabelsToLabels(labels) { var Labels = {}; if (labels) { labels.forEach(function(label) { Labels[label.key] = label.value; }); } return Labels; } function translateConstraintsToKeyValue(constraints) { function getOperator(constraint) { var indexEquals = constraint.indexOf('=='); if (indexEquals >= 0) { return [indexEquals, '==']; } return [constraint.indexOf('!='), '!=']; } if (constraints) { var keyValueConstraints = []; constraints.forEach(function(constraint) { var operatorIndices = getOperator(constraint); var key = constraint.slice(0, operatorIndices[0]); var operator = operatorIndices[1]; var value = constraint.slice(operatorIndices[0] + 2); keyValueConstraints.push({ key: key, value: value, operator: operator, originalKey: key, originalValue: value }); }); return keyValueConstraints; } return []; } initView(); }]);