portainer/app/docker/views/services/edit/serviceController.js

843 lines
32 KiB
JavaScript
Raw Normal View History

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');
refactor(app): introduce webpack and babel (#2407) * feat(agent): add new host page * feat(agent): convert volume-browser to files-datatable * fix(agent): browse folders in file-datatable * feat(engine-details): replace engine view with host view * feat(engine-details): remove old panels * feat(engine-details): add basic engine-details-panel component * feat(engine-details): pass details to the different components * feat(engine-details): replace host-view with host-overview * feat(engine-details): add commaseperated filter * feat(engine-details): add host-view container component * feat(engine-details): add host-details component * feat(engine-details): build host details object * feat(engine-details): format engine version * feat(engine-details): get details for one node * feat(engine-details): pass is-agent from view * feat(engine-details): replace old node view with a new component * feat(engine-details): add swarm-node-details component * feat(engine-details): remove isSwarm binding * feat(engine-details): remove node-details and include in parent * feat(engine-details): add labels-table component * feat(engine-details): add update node service * feat(engine-details): add update label functionality * style(engine-details): remove whitespaces * feat(engine-details): remove old node page * feat(engine-details): pass is agent to host details * feat(host-details): hide missing info * feat(host-details): update node availability * style(host-details): remove obsolete event object * feat(host-details): fix labels not sending * feat(host-details): remove flags for hiding data * feat(host-details): create mock call to server for agent host info * style(host-details): fix spelling mistake in filter's name * feat(host-details): get info from agent * feat(host-details): hide engine labels when empty * feat(node-details): move labels table and save button * feat(host-info): add different urls for refresh * feat(host-details): show disk/devices info for agent * feat(host-view): add loading indicator to devices-panel * feat(host-details): add loading indicator to disks panel * feat(agent): fix browse volume * feat(agent): browse files * feat(agent): enable rename * feat(agent): download file * fix(agent): download file from root * feat(agent): delete file * style(agent): remove whitespaces * fix(agent): fix link on node browser * feat(agent): basic file uploader * feat(agent): add basic file upload * fix(volume-browser): move volume id to query params * feat(node-browser): moved uploader into browser * feat(node-browser): add upload spinner * feat(agent): browse files relative to root * feat(build): add webpack build config * feat(build): add missing imports * feat(webpack): add missing imports * feat(build): enable eslint on build * feat(build): add webpack notifier * feat(build): clean terminal on build * feat(build): import all globals * feat(build): add angular import * feat(build): fix styles * feat(build): load favicons * feat(build): load css before script * feat(webpack): split vendors css and js to a different bundle * feat(webpack): import angular in all files * feat(webpack): remove eslint global config * feat(webpack): add webpack clean dist * feat(webpack): fix styling issues * refactor(webpack): remove empty controllers * refactor(webpack): optimize moment * refactor(webpack): add bundle analyzer * feat(webpack): add babel * refactor(webpack): optimize lodash * refactor(toastr): update toastr * feat(webpack): create basic production and dev config * fix(webpack): fix production config * fix(webpack): fix html templates url * refactor(webpack): remove angular imports * refactor(webpack): remove more angular imports * refactor(webpack): return angular to entry file * style(webpack): remove comments from config * fix(hosts): remove browse button * fix(webpack): import lodash * fix(webpack): import missing htmls * feat(webpack): reduce lodash size * feat(webpack): config grunt to use webpack * feat(webpack): add postcss * chore(codeclimate): use eslint-5 channel * feat(deps): upgrade from lodash to lodash-es * fix(webpack): fix bug with lodash * chore(build): add build client script * fix(webpack): fix missing jsyaml reference * refactor(webpack): seperate builds of img files * chore(build): add a way to check times of webpack build * feat(webpack): add dev server * fix(webpack): fix css output name * chore(webpack): optimize images * chore(webpack): add node env * fix(build): copy templates on release * chore(webpack): set env NODE_ENV * feat(webpack): set NODE_ENV on production builds * fix(extensions): set image path * refactor(css): move vendor css to js import * style(app): remove whitespaces * fix(build-system): allow DevOps pipeline to leverage webpack (#2670) * Update devopsbuild task to use webpack & remove AppVeyor environment var * Added -Force to replace the existing dist folder * Removed Test-Path * dep(build-system): add angularjs-annotate to webpack + fix on imports * Merge branch 'develop' into webpack * refactor(app): webpack aliases for imports + async / await dep + start refactor * style(extensions): use develop version of the view * fix(app): fix several issues introduced by webpack migration * fix(webpack): fix ng-include not loading templates with webpack * Fix Windows CI with Webpack (#2782) * fix(configs): refactor broke configs creation and list views * fix(build-system): update build_binary_devops for Windows
2019-03-21 05:46:49 +00:00
import _ from 'lodash-es';
feat(UX): introduce new env variables UI (#4175) * feat(app): introduce new env vars ui feat(app): introduce new env vars ui feat(UX): WIP new env variables UI feat(UX): update button and placeholder feat(UX): mention .env file in message feat(UX): allow add/remove value & load correctly feat(UX): restrict filesize to 1MB feat(UX): vertical align error message feat(UX): fill UI from file & when switching modes feat(UX): strip un-needed newline character feat(UX): introduce component to other views feat(UX): fix title alignment feat(UX): only populate editor on mode switch when key exists feat(UX): prevent trimming of whitespace on values feat(UX): change editor to async feat(UX): add message describing use feat(UX): Refactor variable text to editorText refactor(app): rename env vars controller refactor(app): move env var explanation to parent refactor(app): order env var panels refactor(app): move simple env vars mode to component refactor(app): parse env vars refactor(app): move styles to css refactor(app): rename functions refactor(container): parse env vars refactor(env-vars): move utils to helper module refactor(env-vars): use util function for parse dot env file fix(env-vars): ignore comments refactor(services): use env vars utils refactor(env-vars): rename files refactor(env-panel): use utils style(stack): revert EnvContent to Env style(service): revert EnvContent to Env style(container): revert EnvContent to Env refactor(env-vars): support default value refactor(service): use new env var component refactor(env-var): use one way data flow refactor(containers): remove unused function * fix(env-vars): prevent using non .env files * refactor(env-vars): move env vars items to a component * feat(app): fixed env vars form validation in Stack * feat(services): disable env form submit if invalid * fix(app): show key pairs correctly * fix(env-var): use the same validation as with kubernetes * fix(env-vars): parse env var Co-authored-by: Chaim Lev-Ari <chiptus@gmail.com> Co-authored-by: Felix Han <felix.han@portainer.io>
2021-06-14 06:59:07 +00:00
import * as envVarsUtils from '@/react/components/form-components/EnvironmentVariablesFieldset/utils';
fix(app): registry push-pull features overhaul (#3393) * feat(registry): registry or direct url selector * feat(app): push pull container creation * feat(app): push pull container duplicate * feat(app): push pull container details recreate * feat(app): push pull container details commit * feat(app): push pull images * feat(app): push pull image tag * feat(app): push pull image push * feat(app): push pull image pull * feat(app): push pull service creation * feat(app): push pull templates create container * feat(app): push pull templates create stacks * feat(app): push pull template edit * feat(app): push pull service details update * fix(app): refactor registry selector + registry auto select * feat(app): remove autocomplete on registry selector * style(image-registry): reword simple/advanced mode * Revert "feat(app): remove autocomplete on registry selector" This reverts commit 97ec2ddd62715405c4089bd2c0cdda4028263d94. * refactor(registry-selector): reverse registry and image fields * feat(app): autocomplete on registry selector * feat(registry-selector): change gitlab registry autocomplete * feat(registry-selector): autocomplete for dockerhub * feat(registry-selector): gitlab url based on locked value instead of name * fix(registry-selector): gitlab registries URL are not modified anymore * fix(registry-selector): change gitlab image autofill on duplicate * fix(registry-selector): gitlab registries now only suggest their own images and not all from gitlab * fix(registry-selector): psuh pull issues with gitlab registries * fix(registry-selector): dockerhub registry selection on duplicate for dockerhub images * fix(templates): registry retrieval for template * feat(images): add autocomplete on image pull panel * fix(registry-selector): add latest tag when no tag is specified * fix(registry-selector): latest tag now applied for non gitlab registries
2019-11-27 22:36:39 +00:00
import { PorImageRegistryModel } from 'Docker/models/porImageRegistry';
import { ResourceControlType } from '@/react/portainer/access-control/types';
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';
fix(app): registry push-pull features overhaul (#3393) * feat(registry): registry or direct url selector * feat(app): push pull container creation * feat(app): push pull container duplicate * feat(app): push pull container details recreate * feat(app): push pull container details commit * feat(app): push pull images * feat(app): push pull image tag * feat(app): push pull image push * feat(app): push pull image pull * feat(app): push pull service creation * feat(app): push pull templates create container * feat(app): push pull templates create stacks * feat(app): push pull template edit * feat(app): push pull service details update * fix(app): refactor registry selector + registry auto select * feat(app): remove autocomplete on registry selector * style(image-registry): reword simple/advanced mode * Revert "feat(app): remove autocomplete on registry selector" This reverts commit 97ec2ddd62715405c4089bd2c0cdda4028263d94. * refactor(registry-selector): reverse registry and image fields * feat(app): autocomplete on registry selector * feat(registry-selector): change gitlab registry autocomplete * feat(registry-selector): autocomplete for dockerhub * feat(registry-selector): gitlab url based on locked value instead of name * fix(registry-selector): gitlab registries URL are not modified anymore * fix(registry-selector): change gitlab image autofill on duplicate * fix(registry-selector): gitlab registries now only suggest their own images and not all from gitlab * fix(registry-selector): psuh pull issues with gitlab registries * fix(registry-selector): dockerhub registry selection on duplicate for dockerhub images * fix(templates): registry retrieval for template * feat(images): add autocomplete on image pull panel * fix(registry-selector): add latest tag when no tag is specified * fix(registry-selector): latest tag now applied for non gitlab registries
2019-11-27 22:36:39 +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',
'NetworkService',
'RegistryService',
'endpoint',
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,
WebhookHelper,
NetworkService,
RegistryService,
endpoint
) {
$scope.resourceType = ResourceControlType.Service;
$scope.onUpdateResourceControlSuccess = function () {
$state.reload();
};
$scope.endpoint = endpoint;
$scope.state = {
updateInProgress: false,
deletionInProgress: false,
rollbackInProgress: false,
};
$scope.formValues = {
RegistryModel: new PorImageRegistryModel(),
};
fix(app): registry push-pull features overhaul (#3393) * feat(registry): registry or direct url selector * feat(app): push pull container creation * feat(app): push pull container duplicate * feat(app): push pull container details recreate * feat(app): push pull container details commit * feat(app): push pull images * feat(app): push pull image tag * feat(app): push pull image push * feat(app): push pull image pull * feat(app): push pull service creation * feat(app): push pull templates create container * feat(app): push pull templates create stacks * feat(app): push pull template edit * feat(app): push pull service details update * fix(app): refactor registry selector + registry auto select * feat(app): remove autocomplete on registry selector * style(image-registry): reword simple/advanced mode * Revert "feat(app): remove autocomplete on registry selector" This reverts commit 97ec2ddd62715405c4089bd2c0cdda4028263d94. * refactor(registry-selector): reverse registry and image fields * feat(app): autocomplete on registry selector * feat(registry-selector): change gitlab registry autocomplete * feat(registry-selector): autocomplete for dockerhub * feat(registry-selector): gitlab url based on locked value instead of name * fix(registry-selector): gitlab registries URL are not modified anymore * fix(registry-selector): change gitlab image autofill on duplicate * fix(registry-selector): gitlab registries now only suggest their own images and not all from gitlab * fix(registry-selector): psuh pull issues with gitlab registries * fix(registry-selector): dockerhub registry selection on duplicate for dockerhub images * fix(templates): registry retrieval for template * feat(images): add autocomplete on image pull panel * fix(registry-selector): add latest tag when no tag is specified * fix(registry-selector): latest tag now applied for non gitlab registries
2019-11-27 22:36:39 +00:00
$scope.tasks = [];
$scope.availableImages = [];
$scope.lastVersion = 0;
var originalService = {};
var previousServiceValues = [];
$scope.goToItem = function (hash) {
if ($location.hash() !== hash) {
$location.hash(hash);
} else {
$anchorScroll();
}
};
$scope.addEnvironmentVariable = function addEnvironmentVariable(service) {
$scope.$evalAsync(() => {
service.EnvironmentVariables = service.EnvironmentVariables.concat({ name: '', value: '' });
updateServiceArray(service, 'EnvironmentVariables', service.EnvironmentVariables);
});
};
feat(UX): introduce new env variables UI (#4175) * feat(app): introduce new env vars ui feat(app): introduce new env vars ui feat(UX): WIP new env variables UI feat(UX): update button and placeholder feat(UX): mention .env file in message feat(UX): allow add/remove value & load correctly feat(UX): restrict filesize to 1MB feat(UX): vertical align error message feat(UX): fill UI from file & when switching modes feat(UX): strip un-needed newline character feat(UX): introduce component to other views feat(UX): fix title alignment feat(UX): only populate editor on mode switch when key exists feat(UX): prevent trimming of whitespace on values feat(UX): change editor to async feat(UX): add message describing use feat(UX): Refactor variable text to editorText refactor(app): rename env vars controller refactor(app): move env var explanation to parent refactor(app): order env var panels refactor(app): move simple env vars mode to component refactor(app): parse env vars refactor(app): move styles to css refactor(app): rename functions refactor(container): parse env vars refactor(env-vars): move utils to helper module refactor(env-vars): use util function for parse dot env file fix(env-vars): ignore comments refactor(services): use env vars utils refactor(env-vars): rename files refactor(env-panel): use utils style(stack): revert EnvContent to Env style(service): revert EnvContent to Env style(container): revert EnvContent to Env refactor(env-vars): support default value refactor(service): use new env var component refactor(env-var): use one way data flow refactor(containers): remove unused function * fix(env-vars): prevent using non .env files * refactor(env-vars): move env vars items to a component * feat(app): fixed env vars form validation in Stack * feat(services): disable env form submit if invalid * fix(app): show key pairs correctly * fix(env-var): use the same validation as with kubernetes * fix(env-vars): parse env var Co-authored-by: Chaim Lev-Ari <chiptus@gmail.com> Co-authored-by: Felix Han <felix.han@portainer.io>
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);
}
$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) {
updateServiceArray(service, 'ServiceConfigs', service.ServiceConfigs);
};
$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) {
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: null, 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.onChangeMountType = function onChangeMountType(service, mount) {
mount.Source = null;
$scope.updateMount(service, mount);
};
$scope.updateMount = function updateMount(service) {
updateServiceArray(service, 'ServiceMounts', service.ServiceMounts);
};
$scope.toggleMountReadOnly = function toggleMountReadOnly(isReadOnly, index) {
$scope.$evalAsync(function () {
updateServiceArray($scope.service, 'ServiceMounts', $scope.service.ServiceMounts);
$scope.service.ServiceMounts[index].ReadOnly = isReadOnly;
});
};
$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);
};
$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 (service) {
updateServiceArray(service, 'ServiceConstraints', service.ServiceConstraints);
};
$scope.addPlacementPreference = function (service) {
service.ServicePreferences.push({ strategy: 'spread', value: '' });
updateServiceArray(service, 'ServicePreferences', service.ServicePreferences);
};
$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) {
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.addLogDriverOpt = function addLogDriverOpt(service) {
service.LogDriverOpts.push({ key: '', value: '', originalValue: '' });
updateServiceArray(service, 'LogDriverOpts', service.LogDriverOpts);
};
$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) {
updateServiceArray(service, 'Hosts', service.Hosts);
};
$scope.onWebhookChange = function (enabled) {
enabled = enabled | '';
$scope.$evalAsync(() => {
$scope.updateWebhook($scope.service);
$scope.WebhookExists = enabled;
updateServiceAttribute($scope.service, 'Webhooks', enabled);
});
};
$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 {
WebhookService.createServiceWebhook(service.Id, endpoint.Id, $scope.initialRegistryID)
.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');
});
}
};
$scope.updateWebhookRegistryId = function () {
const newRegistryID = _.get($scope.formValues.RegistryModel, 'Registry.Id', 0);
const registryChanged = $scope.initialRegistryID != newRegistryID;
if ($scope.WebhookExists && registryChanged) {
WebhookService.updateServiceWebhook($scope.webhookID, newRegistryID).catch(function error(err) {
Notifications.error('Failure', err, 'Unable to update webhook');
});
}
};
$scope.copyWebhook = function copyWebhook() {
clipboard.copyText($scope.webhookURL);
$('#copyNotification').show();
$('#copyNotification').fadeOut(2000);
};
feat(webhooks): add support for service update webhooks (#2161) * Initial pass at adding webhook controller and routes * Moving some objects around * Cleaning up comments * Fixing syntax, switching to using the docker sdk over building an http client * Adding delete and list functionality * Updating the handler to use the correct permissions. Updating some comments * Fixing some comments * Code cleanup per pull request comments * Cleanup per PR feedback. Syntax error fix * Initial creation of webhook app code * Moving ClientFactory creation out of handler code and instead using the one created by the main process. Removing webhookInspect method and updating the list function to use json filters * Delete now works on the webhook ID vs service ID * WIP - Service creates a webhook. Display will show an existing webhook URL. * Adding the webhook field to the service view. There is now the ability to add or remove a webhook from a service * Moving all api calls to be webhooks vs webhook * Code cleanup. Moving all api calls to be webhooks vs webhook * More conversion of webhook to webhooks? * Moving UI elements around. Starting function for copying to clipboard * Finalizing function for copying to clipboard. Adding button that calls function and copies webhook to clipboard. * Fixing UI issues. Hiding field entirely when there is no webhook * Moving URL crafting to a helper method. The edit pane for service now creates/deletes webhooks immidiately. * style(service-details): update webhook line * feat(api): strip sha when updating an image via the update webhook * Fixing up some copy. Only displying the port if it is not http or https * Fixing tooltip copy. Setting the forceupdate to be true to require an update to occur * Fixing code climate errors * Adding WebhookType field and setting to ServiceWebhook for new webhooks. Renaming ServiceID to resourceID so future work can add new types of webhooks in other resource areas. * Adding the webhook type to the payload to support more types of webhooks in the future. Setting the type correctly when creating one for a service * feat(webhooks): changes related to webhook management * API code cleanup, removing unneeded functions, and updating validation logic * Incorrectly ignoring the error that the webhook did not exist * Re-adding missing error handling. Changing error response to be a 404 vs 500 when token can't find an object * fix(webhooks): close Docker client after service webhook execution
2018-09-03 10:08:03 +00:00
$scope.cancelChanges = async function cancelChanges(service, keys) {
if (keys) {
// clean out the keys only from the list of modified keys
for (const key of keys) {
if (key === 'Image') {
$scope.formValues.RegistryModel = await RegistryService.retrievePorRegistryModelFromRepository(originalService.Image, endpoint.Id);
} else {
var index = previousServiceValues.indexOf(key);
if (index >= 0) {
previousServiceValues.splice(index, 1);
}
}
}
} else {
// clean out all changes
$scope.formValues.RegistryModel = await RegistryService.retrievePorRegistryModelFromRepository(originalService.Image, endpoint.Id);
keys = Object.keys(service);
previousServiceValues = [];
}
keys.forEach(function (attribute) {
service[attribute] = originalService[attribute]; // reset service values
});
service.hasChanges = false;
};
feat(webhooks): add support for service update webhooks (#2161) * Initial pass at adding webhook controller and routes * Moving some objects around * Cleaning up comments * Fixing syntax, switching to using the docker sdk over building an http client * Adding delete and list functionality * Updating the handler to use the correct permissions. Updating some comments * Fixing some comments * Code cleanup per pull request comments * Cleanup per PR feedback. Syntax error fix * Initial creation of webhook app code * Moving ClientFactory creation out of handler code and instead using the one created by the main process. Removing webhookInspect method and updating the list function to use json filters * Delete now works on the webhook ID vs service ID * WIP - Service creates a webhook. Display will show an existing webhook URL. * Adding the webhook field to the service view. There is now the ability to add or remove a webhook from a service * Moving all api calls to be webhooks vs webhook * Code cleanup. Moving all api calls to be webhooks vs webhook * More conversion of webhook to webhooks? * Moving UI elements around. Starting function for copying to clipboard * Finalizing function for copying to clipboard. Adding button that calls function and copies webhook to clipboard. * Fixing UI issues. Hiding field entirely when there is no webhook * Moving URL crafting to a helper method. The edit pane for service now creates/deletes webhooks immidiately. * style(service-details): update webhook line * feat(api): strip sha when updating an image via the update webhook * Fixing up some copy. Only displying the port if it is not http or https * Fixing tooltip copy. Setting the forceupdate to be true to require an update to occur * Fixing code climate errors * Adding WebhookType field and setting to ServiceWebhook for new webhooks. Renaming ServiceID to resourceID so future work can add new types of webhooks in other resource areas. * Adding the webhook type to the payload to support more types of webhooks in the future. Setting the type correctly when creating one for a service * feat(webhooks): changes related to webhook management * API code cleanup, removing unneeded functions, and updating validation logic * Incorrectly ignoring the error that the webhook did not exist * Re-adding missing error handling. Changing error response to be a 404 vs 500 when token can't find an object * fix(webhooks): close Docker client after service webhook execution
2018-09-03 10:08:03 +00:00
$scope.hasChanges = function (service, elements) {
var hasChanges = false;
elements.forEach(function (key) {
fix(app): registry push-pull features overhaul (#3393) * feat(registry): registry or direct url selector * feat(app): push pull container creation * feat(app): push pull container duplicate * feat(app): push pull container details recreate * feat(app): push pull container details commit * feat(app): push pull images * feat(app): push pull image tag * feat(app): push pull image push * feat(app): push pull image pull * feat(app): push pull service creation * feat(app): push pull templates create container * feat(app): push pull templates create stacks * feat(app): push pull template edit * feat(app): push pull service details update * fix(app): refactor registry selector + registry auto select * feat(app): remove autocomplete on registry selector * style(image-registry): reword simple/advanced mode * Revert "feat(app): remove autocomplete on registry selector" This reverts commit 97ec2ddd62715405c4089bd2c0cdda4028263d94. * refactor(registry-selector): reverse registry and image fields * feat(app): autocomplete on registry selector * feat(registry-selector): change gitlab registry autocomplete * feat(registry-selector): autocomplete for dockerhub * feat(registry-selector): gitlab url based on locked value instead of name * fix(registry-selector): gitlab registries URL are not modified anymore * fix(registry-selector): change gitlab image autofill on duplicate * fix(registry-selector): gitlab registries now only suggest their own images and not all from gitlab * fix(registry-selector): psuh pull issues with gitlab registries * fix(registry-selector): dockerhub registry selection on duplicate for dockerhub images * fix(templates): registry retrieval for template * feat(images): add autocomplete on image pull panel * fix(registry-selector): add latest tag when no tag is specified * fix(registry-selector): latest tag now applied for non gitlab registries
2019-11-27 22:36:39 +00:00
if (key === 'Image') {
const originalImage = service ? service.Model.Spec.TaskTemplate.ContainerSpec.Image : null;
const currentImage = ImageHelper.createImageConfigForContainer($scope.formValues.RegistryModel).fromImage;
hasChanges = hasChanges || originalImage !== currentImage;
fix(app): registry push-pull features overhaul (#3393) * feat(registry): registry or direct url selector * feat(app): push pull container creation * feat(app): push pull container duplicate * feat(app): push pull container details recreate * feat(app): push pull container details commit * feat(app): push pull images * feat(app): push pull image tag * feat(app): push pull image push * feat(app): push pull image pull * feat(app): push pull service creation * feat(app): push pull templates create container * feat(app): push pull templates create stacks * feat(app): push pull template edit * feat(app): push pull service details update * fix(app): refactor registry selector + registry auto select * feat(app): remove autocomplete on registry selector * style(image-registry): reword simple/advanced mode * Revert "feat(app): remove autocomplete on registry selector" This reverts commit 97ec2ddd62715405c4089bd2c0cdda4028263d94. * refactor(registry-selector): reverse registry and image fields * feat(app): autocomplete on registry selector * feat(registry-selector): change gitlab registry autocomplete * feat(registry-selector): autocomplete for dockerhub * feat(registry-selector): gitlab url based on locked value instead of name * fix(registry-selector): gitlab registries URL are not modified anymore * fix(registry-selector): change gitlab image autofill on duplicate * fix(registry-selector): gitlab registries now only suggest their own images and not all from gitlab * fix(registry-selector): psuh pull issues with gitlab registries * fix(registry-selector): dockerhub registry selection on duplicate for dockerhub images * fix(templates): registry retrieval for template * feat(images): add autocomplete on image pull panel * fix(registry-selector): add latest tag when no tag is specified * fix(registry-selector): latest tag now applied for non gitlab registries
2019-11-27 22:36:39 +00:00
} else {
hasChanges = hasChanges || previousServiceValues.indexOf(key) >= 0;
}
});
return hasChanges;
};
$scope.mountsAreValid = mountsAreValid;
function mountsAreValid() {
const mounts = $scope.service.ServiceMounts;
return mounts.every((mount) => mount.Source && mount.Target);
}
function buildChanges(service) {
var config = ServiceHelper.serviceToConfig(service.Model);
config.Name = service.Name;
config.Labels = LabelHelper.fromKeyValueToLabelHash(service.ServiceLabels);
feat(UX): introduce new env variables UI (#4175) * feat(app): introduce new env vars ui feat(app): introduce new env vars ui feat(UX): WIP new env variables UI feat(UX): update button and placeholder feat(UX): mention .env file in message feat(UX): allow add/remove value & load correctly feat(UX): restrict filesize to 1MB feat(UX): vertical align error message feat(UX): fill UI from file & when switching modes feat(UX): strip un-needed newline character feat(UX): introduce component to other views feat(UX): fix title alignment feat(UX): only populate editor on mode switch when key exists feat(UX): prevent trimming of whitespace on values feat(UX): change editor to async feat(UX): add message describing use feat(UX): Refactor variable text to editorText refactor(app): rename env vars controller refactor(app): move env var explanation to parent refactor(app): order env var panels refactor(app): move simple env vars mode to component refactor(app): parse env vars refactor(app): move styles to css refactor(app): rename functions refactor(container): parse env vars refactor(env-vars): move utils to helper module refactor(env-vars): use util function for parse dot env file fix(env-vars): ignore comments refactor(services): use env vars utils refactor(env-vars): rename files refactor(env-panel): use utils style(stack): revert EnvContent to Env style(service): revert EnvContent to Env style(container): revert EnvContent to Env refactor(env-vars): support default value refactor(service): use new env var component refactor(env-var): use one way data flow refactor(containers): remove unused function * fix(env-vars): prevent using non .env files * refactor(env-vars): move env vars items to a component * feat(app): fixed env vars form validation in Stack * feat(services): disable env form submit if invalid * fix(app): show key pairs correctly * fix(env-var): use the same validation as with kubernetes * fix(env-vars): parse env var Co-authored-by: Chaim Lev-Ari <chiptus@gmail.com> Co-authored-by: Felix Han <felix.han@portainer.io>
2021-06-14 06:59:07 +00:00
config.TaskTemplate.ContainerSpec.Env = envVarsUtils.convertToArrayOfStrings(service.EnvironmentVariables);
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;
config.registryId = $scope.formValues.RegistryModel.Registry.Id;
fix(app): registry push-pull features overhaul (#3393) * feat(registry): registry or direct url selector * feat(app): push pull container creation * feat(app): push pull container duplicate * feat(app): push pull container details recreate * feat(app): push pull container details commit * feat(app): push pull images * feat(app): push pull image tag * feat(app): push pull image push * feat(app): push pull image pull * feat(app): push pull service creation * feat(app): push pull templates create container * feat(app): push pull templates create stacks * feat(app): push pull template edit * feat(app): push pull service details update * fix(app): refactor registry selector + registry auto select * feat(app): remove autocomplete on registry selector * style(image-registry): reword simple/advanced mode * Revert "feat(app): remove autocomplete on registry selector" This reverts commit 97ec2ddd62715405c4089bd2c0cdda4028263d94. * refactor(registry-selector): reverse registry and image fields * feat(app): autocomplete on registry selector * feat(registry-selector): change gitlab registry autocomplete * feat(registry-selector): autocomplete for dockerhub * feat(registry-selector): gitlab url based on locked value instead of name * fix(registry-selector): gitlab registries URL are not modified anymore * fix(registry-selector): change gitlab image autofill on duplicate * fix(registry-selector): gitlab registries now only suggest their own images and not all from gitlab * fix(registry-selector): psuh pull issues with gitlab registries * fix(registry-selector): dockerhub registry selection on duplicate for dockerhub images * fix(templates): registry retrieval for template * feat(images): add autocomplete on image pull panel * fix(registry-selector): add latest tag when no tag is specified * fix(registry-selector): latest tag now applied for non gitlab registries
2019-11-27 22:36:39 +00:00
} else {
config.TaskTemplate.ContainerSpec.Image = service.Image;
}
fix(app): registry push-pull features overhaul (#3393) * feat(registry): registry or direct url selector * feat(app): push pull container creation * feat(app): push pull container duplicate * feat(app): push pull container details recreate * feat(app): push pull container details commit * feat(app): push pull images * feat(app): push pull image tag * feat(app): push pull image push * feat(app): push pull image pull * feat(app): push pull service creation * feat(app): push pull templates create container * feat(app): push pull templates create stacks * feat(app): push pull template edit * feat(app): push pull service details update * fix(app): refactor registry selector + registry auto select * feat(app): remove autocomplete on registry selector * style(image-registry): reword simple/advanced mode * Revert "feat(app): remove autocomplete on registry selector" This reverts commit 97ec2ddd62715405c4089bd2c0cdda4028263d94. * refactor(registry-selector): reverse registry and image fields * feat(app): autocomplete on registry selector * feat(registry-selector): change gitlab registry autocomplete * feat(registry-selector): autocomplete for dockerhub * feat(registry-selector): gitlab url based on locked value instead of name * fix(registry-selector): gitlab registries URL are not modified anymore * fix(registry-selector): change gitlab image autofill on duplicate * fix(registry-selector): gitlab registries now only suggest their own images and not all from gitlab * fix(registry-selector): psuh pull issues with gitlab registries * fix(registry-selector): dockerhub registry selection on duplicate for dockerhub images * fix(templates): registry retrieval for template * feat(images): add autocomplete on image pull panel * fix(registry-selector): add latest tag when no tag is specified * fix(registry-selector): latest tag now applied for non gitlab registries
2019-11-27 22:36:39 +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;
}
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;
}
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;
}
}
}
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,
};
return service, config;
}
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:/)) {
Notifications.error('Failure', data, 'Error');
} 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;
});
}
$scope.rollbackService = function (service) {
confirm({
title: 'Rollback service',
message: 'Are you sure you want to rollback?',
modalType: ModalType.Warn,
confirmButton: buildConfirmButton('Yes', 'danger'),
}).then((confirmed) => {
if (!confirmed) {
return;
}
rollbackService(service);
});
};
$scope.setPullImageValidity = setPullImageValidity;
function setPullImageValidity(validity) {
$scope.state.pullImageValidity = validity;
}
$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:/)) {
Notifications.error('Failure', data, 'Error');
} else {
Notifications.success('Service successfully updated', 'Service updated');
$scope.updateWebhookRegistryId();
}
$scope.cancelChanges({});
initView();
},
function (e) {
Notifications.error('Failure', e, 'Unable to update service');
}
);
};
$scope.removeService = function () {
confirmDelete('Do you want to remove this service? All the containers associated to this service will be removed too.').then((confirmed) => {
if (!confirmed) {
return;
}
removeService();
});
};
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() {
Notifications.success('Success', 'Service successfully deleted');
$state.go('docker.services', {});
})
.catch(function error(err) {
Notifications.error('Failure', err, 'Unable to remove service');
})
.finally(function final() {
$scope.state.deletionInProgress = false;
});
}
$scope.forceUpdateService = function (service) {
confirmServiceForceUpdate('Do you want to force an update of the service? All the tasks associated to the service will be recreated.').then(function (result) {
if (!result) {
return;
}
forceUpdateService(service, result.pullLatest);
});
};
function forceUpdateService(service, pullImage) {
var config = ServiceHelper.serviceToConfig(service.Model);
if (pullImage) {
config.TaskTemplate.ContainerSpec.Image = ImageHelper.removeDigestFromRepository(config.TaskTemplate.ContainerSpec.Image);
}
// 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;
});
}
function translateServiceArrays(service) {
service.ServiceSecrets = service.Secrets ? service.Secrets.map(SecretHelper.flattenSecret) : [];
service.ServiceConfigs = service.Configs ? service.Configs.map(ConfigHelper.flattenConfig) : [];
feat(UX): introduce new env variables UI (#4175) * feat(app): introduce new env vars ui feat(app): introduce new env vars ui feat(UX): WIP new env variables UI feat(UX): update button and placeholder feat(UX): mention .env file in message feat(UX): allow add/remove value & load correctly feat(UX): restrict filesize to 1MB feat(UX): vertical align error message feat(UX): fill UI from file & when switching modes feat(UX): strip un-needed newline character feat(UX): introduce component to other views feat(UX): fix title alignment feat(UX): only populate editor on mode switch when key exists feat(UX): prevent trimming of whitespace on values feat(UX): change editor to async feat(UX): add message describing use feat(UX): Refactor variable text to editorText refactor(app): rename env vars controller refactor(app): move env var explanation to parent refactor(app): order env var panels refactor(app): move simple env vars mode to component refactor(app): parse env vars refactor(app): move styles to css refactor(app): rename functions refactor(container): parse env vars refactor(env-vars): move utils to helper module refactor(env-vars): use util function for parse dot env file fix(env-vars): ignore comments refactor(services): use env vars utils refactor(env-vars): rename files refactor(env-panel): use utils style(stack): revert EnvContent to Env style(service): revert EnvContent to Env style(container): revert EnvContent to Env refactor(env-vars): support default value refactor(service): use new env var component refactor(env-var): use one way data flow refactor(containers): remove unused function * fix(env-vars): prevent using non .env files * refactor(env-vars): move env vars items to a component * feat(app): fixed env vars form validation in Stack * feat(services): disable env form submit if invalid * fix(app): show key pairs correctly * fix(env-var): use the same validation as with kubernetes * fix(env-vars): parse env var Co-authored-by: Chaim Lev-Ari <chiptus@gmail.com> Co-authored-by: Felix Han <felix.han@portainer.io>
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));
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
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;
}
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
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
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),
availableNetworks: NetworkService.networks(true, true, apiVersion >= 1.25),
feat(app): rework private registries and support private registries in kubernetes EE-30 (#5131) * feat(app): rework private registries and support private registries in kubernetes [EE-30] feat(api): backport private registries backend changes (#5072) * feat(api/bolt): backport bolt changes * feat(api/exec): backport exec changes * feat(api/http): backport http/handler/dockerhub changes * feat(api/http): backport http/handler/endpoints changes * feat(api/http): backport http/handler/registries changes * feat(api/http): backport http/handler/stacks changes * feat(api/http): backport http/handler changes * feat(api/http): backport http/proxy/factory/azure changes * feat(api/http): backport http/proxy/factory/docker changes * feat(api/http): backport http/proxy/factory/utils changes * feat(api/http): backport http/proxy/factory/kubernetes changes * feat(api/http): backport http/proxy/factory changes * feat(api/http): backport http/security changes * feat(api/http): backport http changes * feat(api/internal): backport internal changes * feat(api): backport api changes * feat(api/kubernetes): backport kubernetes changes * fix(api/http): changes on backend following backport feat(app): backport private registries frontend changes (#5056) * feat(app/docker): backport docker/components changes * feat(app/docker): backport docker/helpers changes * feat(app/docker): backport docker/views/container changes * feat(app/docker): backport docker/views/images changes * feat(app/docker): backport docker/views/registries changes * feat(app/docker): backport docker/views/services changes * feat(app/docker): backport docker changes * feat(app/kubernetes): backport kubernetes/components changes * feat(app/kubernetes): backport kubernetes/converters changes * feat(app/kubernetes): backport kubernetes/models changes * feat(app/kubernetes): backport kubernetes/registries changes * feat(app/kubernetes): backport kubernetes/services changes * feat(app/kubernetes): backport kubernetes/views/applications changes * feat(app/kubernetes): backport kubernetes/views/configurations changes * feat(app/kubernetes): backport kubernetes/views/configure changes * feat(app/kubernetes): backport kubernetes/views/resource-pools changes * feat(app/kubernetes): backport kubernetes/views changes * feat(app/portainer): backport portainer/components/accessManagement changes * feat(app/portainer): backport portainer/components/datatables changes * feat(app/portainer): backport portainer/components/forms changes * feat(app/portainer): backport portainer/components/registry-details changes * feat(app/portainer): backport portainer/models changes * feat(app/portainer): backport portainer/rest changes * feat(app/portainer): backport portainer/services changes * feat(app/portainer): backport portainer/views changes * feat(app/portainer): backport portainer changes * feat(app): backport app changes * config(project): gitignore + jsconfig changes gitignore all files under api/cmd/portainer but main.go and enable Code Editor autocomplete on import ... from '@/...' fix(app): fix pull rate limit checker fix(app/registries): sidebar menus and registry accesses users filtering fix(api): add missing kube client factory fix(kube): fetch dockerhub pull limits (#5133) fix(app): pre review fixes (#5142) * fix(app/registries): remove checkbox for endpointRegistries view * fix(endpoints): allow access to default namespace * fix(docker): fetch pull limits * fix(kube/ns): show selected registries for non admin Co-authored-by: Chaim Lev-Ari <chiptus@gmail.com> chore(webpack): ignore missing sourcemaps fix(registries): fetch registry config from url feat(kube/registries): ignore not found when deleting secret feat(db): move migration to db 31 fix(registries): fix bugs in PR EE-869 (#5169) * fix(registries): hide role * fix(endpoints): set empty access policy to edge endpoint * fix(registry): remove double arguments * fix(admin): ignore warning * feat(kube/configurations): tag registry secrets (#5157) * feat(kube/configurations): tag registry secrets * feat(kube/secrets): show registry secrets for admins * fix(registries): move dockerhub to beginning * refactor(registries): use endpoint scoped registries feat(registries): filter by namespace if supplied feat(access-managment): filter users for registry (#5191) * refactor(access-manage): move users selector to component * feat(access-managment): filter users for registry refactor(registries): sync code with CE (#5200) * refactor(registry): add inspect handler under endpoints * refactor(endpoint): sync endpoint_registries_list * refactor(endpoints): sync registry_access * fix(db): rename migration functions * fix(registries): show accesses for admin * fix(kube): set token on transport * refactor(kube): move secret help to bottom * fix(kuberentes): remove shouldLog parameter * style(auth): add description of security.IsAdmin * feat(security): allow admin access to registry * feat(edge): connect to edge endpoint when creating client * style(portainer): change deprecation version * refactor(sidebar): hide manage * refactor(containers): revert changes * style(container): remove whitespace * fix(endpoint): add handler to registy on endpointService * refactor(image): use endpointService.registries * fix(kueb/namespaces): rename resource pool to namespace * fix(kube/namespace): move selected registries * fix(api/registries): hide accesses on registry creation Co-authored-by: LP B <xAt0mZ@users.noreply.github.com> refactor(api): remove code duplication after rebase fix(app/registries): replace last registry api usage by endpoint registry api fix(api/endpoints): update registry access policies on endpoint deletion (#5226) [EE-1027] fix(db): update db version * fix(dockerhub): fetch rate limits * fix(registry/tests): supply restricred context * fix(registries): show proget registry only when selected * fix(registry): create dockerhub registry * feat(db): move migrations to db 32 Co-authored-by: Chaim Lev-Ari <chiptus@gmail.com>
2021-07-14 09:15:21 +00:00
webhooks: WebhookService.webhooks(service.Id, endpoint.Id),
});
})
.then(async function success(data) {
$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;
$scope.allowBindMounts = endpoint.SecuritySettings.allowBindMountsForRegularUsers;
$scope.isAdmin = Authentication.isAdmin();
$scope.availableNetworks = data.availableNetworks;
$scope.swarmNetworks = _.filter($scope.availableNetworks, (network) => network.Scope === 'swarm');
$scope.WebhookExists = false;
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);
if (data.webhooks.length > 0) {
var webhook = data.webhooks[0];
$scope.WebhookExists = true;
$scope.webhookID = webhook.Id;
$scope.webhookURL = WebhookHelper.returnWebhookUrl(webhook.Token);
}
var tasks = data.tasks;
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;
}
const image = $scope.service.Model.Spec.TaskTemplate.ContainerSpec.Image;
RegistryService.retrievePorRegistryModelFromRepository(image, endpoint.Id).then((model) => {
$scope.formValues.RegistryModel = model;
$scope.initialRegistryID = _.get(model, 'Registry.Id', 0);
});
// 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');
});
}
$scope.updateServiceAttribute = updateServiceAttribute;
function updateServiceAttribute(service, name) {
if (service[name] !== originalService[name] || !(name in originalService)) {
service.hasChanges = true;
}
previousServiceValues.push(name);
}
$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)));
}
function updateServiceArray(service, name) {
previousServiceValues.push(name);
service.hasChanges = true;
}
initView();
},
]);