diff --git a/app/docker/views/containers/create/createContainerController.js b/app/docker/views/containers/create/createContainerController.js index 850282d62..ab146d249 100644 --- a/app/docker/views/containers/create/createContainerController.js +++ b/app/docker/views/containers/create/createContainerController.js @@ -3,6 +3,7 @@ import _ from 'lodash-es'; import { PorImageRegistryModel } from 'Docker/models/porImageRegistry'; import * as envVarsUtils from '@/portainer/helpers/env-vars'; +import { FeatureId } from 'Portainer/feature-flags/enums'; import { ContainerCapabilities, ContainerCapability } from '../../../models/containerCapabilities'; import { AccessControlFormData } from '../../../../portainer/components/accessControlForm/porAccessControlFormModel'; import { ContainerDetailsViewModel } from '../../../models/container'; @@ -65,7 +66,7 @@ angular.module('portainer.docker').controller('CreateContainerController', [ $scope.create = create; $scope.update = update; $scope.endpoint = endpoint; - + $scope.containerWebhookFeature = FeatureId.CONTAINER_WEBHOOK; $scope.formValues = { alwaysPull: true, Console: 'none', diff --git a/app/docker/views/containers/create/createcontainer.html b/app/docker/views/containers/create/createcontainer.html index 0df48fd3e..33ffd0a0b 100644 --- a/app/docker/views/containers/create/createcontainer.html +++ b/app/docker/views/containers/create/createcontainer.html @@ -65,6 +65,28 @@ + +
+
Webhooks
+
+
+ + + +
+
+
+ +
Network ports configuration
diff --git a/app/docker/views/containers/edit/container.html b/app/docker/views/containers/edit/container.html index 2d5be1c34..c00d2ea69 100644 --- a/app/docker/views/containers/edit/container.html +++ b/app/docker/views/containers/edit/container.html @@ -110,6 +110,19 @@ Finished {{ container.State.FinishedAt | getisodate }} + + + Container webhook + + + + +
diff --git a/app/docker/views/containers/edit/containerController.js b/app/docker/views/containers/edit/containerController.js index 53c53a5ae..070450011 100644 --- a/app/docker/views/containers/edit/containerController.js +++ b/app/docker/views/containers/edit/containerController.js @@ -2,6 +2,7 @@ import moment from 'moment'; import _ from 'lodash-es'; import { PorImageRegistryModel } from 'Docker/models/porImageRegistry'; import { confirmContainerDeletion } from '@/portainer/services/modal.service/prompt'; +import { FeatureId } from 'Portainer/feature-flags/enums'; angular.module('portainer.docker').controller('ContainerController', [ '$q', @@ -49,6 +50,7 @@ angular.module('portainer.docker').controller('ContainerController', [ $scope.activityTime = 0; $scope.portBindings = []; $scope.displayRecreateButton = false; + $scope.containerWebhookFeature = FeatureId.CONTAINER_WEBHOOK; $scope.config = { RegistryModel: new PorImageRegistryModel(), diff --git a/app/portainer/components/forms/git-form/git-form-auto-update-fieldset/git-form-auto-update-fieldset.controller.js b/app/portainer/components/forms/git-form/git-form-auto-update-fieldset/git-form-auto-update-fieldset.controller.js index 01af3076c..76f654425 100644 --- a/app/portainer/components/forms/git-form/git-form-auto-update-fieldset/git-form-auto-update-fieldset.controller.js +++ b/app/portainer/components/forms/git-form/git-form-auto-update-fieldset/git-form-auto-update-fieldset.controller.js @@ -10,6 +10,7 @@ class GitFormAutoUpdateFieldsetController { this.onChangeInterval = this.onChangeField('RepositoryFetchInterval'); this.limitedFeature = FeatureId.FORCE_REDEPLOYMENT; + this.stackPullImageFeature = FeatureId.STACK_PULL_IMAGE; } copyWebhook() { diff --git a/app/portainer/components/forms/git-form/git-form-auto-update-fieldset/git-form-auto-update-fieldset.html b/app/portainer/components/forms/git-form/git-form-auto-update-fieldset/git-form-auto-update-fieldset.html index 6b064808c..4a3e09d9f 100644 --- a/app/portainer/components/forms/git-form/git-form-auto-update-fieldset/git-form-auto-update-fieldset.html +++ b/app/portainer/components/forms/git-form/git-form-auto-update-fieldset/git-form-auto-update-fieldset.html @@ -51,6 +51,11 @@ />
+
+
+ +
+
{ + const tplCrop = + '
Any changes to this stack or application made locally in Portainer will be overridden, which may cause service interruption.
' + + '
' + + '
'; + const template = angular.element(tplCrop); + const html = this.$compile(template)(this.$scope); + this.ModalService.confirmStackUpdate(html, true, true, 'btn-warning', function (result) { + if (!result) { + return; + } try { - const confirmed = await this.ModalService.confirmAsync({ - title: 'Are you sure?', - message: 'Any changes to this stack or application made locally in Portainer will be overridden, which may cause service interruption.', - buttons: { - confirm: { - label: 'Update', - className: 'btn-warning', - }, - }, - }); - if (!confirmed) { - return; - } - this.state.redeployInProgress = true; - - await this.StackService.updateGit(this.stack.Id, this.stack.EndpointId, this.FormHelper.removeInvalidEnvVars(this.formValues.Env), false, this.formValues); + this.StackService.updateGit(this.stack.Id, this.stack.EndpointId, this.FormHelper.removeInvalidEnvVars(this.formValues.Env), false, this.formValues); this.Notifications.success('Pulled and redeployed stack successfully'); - await this.$state.reload(); + this.$state.reload(); } catch (err) { this.Notifications.error('Failure', err, 'Failed redeploying stack'); } finally { diff --git a/app/portainer/feature-flags/enums.ts b/app/portainer/feature-flags/enums.ts index 784a1b516..73b61b7f3 100644 --- a/app/portainer/feature-flags/enums.ts +++ b/app/portainer/feature-flags/enums.ts @@ -23,4 +23,7 @@ export enum FeatureId { ACTIVITY_AUDIT = 'activity-audit', FORCE_REDEPLOYMENT = 'force-redeployment', HIDE_AUTO_UPDATE_WINDOW = 'hide-auto-update-window', + STACK_PULL_IMAGE = 'stack-pull-image', + STACK_WEBHOOK = 'stack-webhook', + CONTAINER_WEBHOOK = 'container-webhook', } diff --git a/app/portainer/feature-flags/feature-flags.service.ts b/app/portainer/feature-flags/feature-flags.service.ts index e072d561b..388c53310 100644 --- a/app/portainer/feature-flags/feature-flags.service.ts +++ b/app/portainer/feature-flags/feature-flags.service.ts @@ -27,6 +27,9 @@ export async function init(edition: Edition) { [FeatureId.TEAM_MEMBERSHIP]: Edition.BE, [FeatureId.FORCE_REDEPLOYMENT]: Edition.BE, [FeatureId.HIDE_AUTO_UPDATE_WINDOW]: Edition.BE, + [FeatureId.STACK_PULL_IMAGE]: Edition.BE, + [FeatureId.STACK_WEBHOOK]: Edition.BE, + [FeatureId.CONTAINER_WEBHOOK]: Edition.BE, }; state.currentEdition = currentEdition; diff --git a/app/portainer/feature-flags/feature-ids.js b/app/portainer/feature-flags/feature-ids.js new file mode 100644 index 000000000..c9aa5180b --- /dev/null +++ b/app/portainer/feature-flags/feature-ids.js @@ -0,0 +1,16 @@ +export const K8S_RESOURCE_POOL_LB_QUOTA = 'k8s-resourcepool-Ibquota'; +export const K8S_RESOURCE_POOL_STORAGE_QUOTA = 'k8s-resourcepool-storagequota'; +export const RBAC_ROLES = 'rbac-roles'; +export const REGISTRY_MANAGEMENT = 'registry-management'; +export const K8S_SETUP_DEFAULT = 'k8s-setup-default'; +export const S3_BACKUP_SETTING = 's3-backup-setting'; +export const HIDE_INTERNAL_AUTHENTICATION_PROMPT = 'hide-internal-authentication-prompt'; +export const TEAM_MEMBERSHIP = 'team-membership'; +export const HIDE_INTERNAL_AUTH = 'hide-internal-auth'; +export const EXTERNAL_AUTH_LDAP = 'external-auth-ldap'; +export const ACTIVITY_AUDIT = 'activity-audit'; +export const HIDE_AUTO_UPDATE_WINDOW = 'hide-auto-update-window'; +export const FORCE_REDEPLOYMENT = 'force-redeployment'; +export const STACK_PULL_IMAGE = 'stack-pull-image'; +export const STACK_WEBHOOK = 'stack-webhook'; +export const CONTAINER_WEBHOOK = 'container-webhook'; diff --git a/app/portainer/services/modal.service/index.ts b/app/portainer/services/modal.service/index.ts index 8653e3f68..1f70e610a 100644 --- a/app/portainer/services/modal.service/index.ts +++ b/app/portainer/services/modal.service/index.ts @@ -22,6 +22,7 @@ import { confirmContainerDeletion, confirmContainerRecreation, confirmServiceForceUpdate, + confirmStackUpdate, confirmKubeconfigSelection, selectRegistry, } from './prompt'; @@ -57,6 +58,7 @@ export function ModalServiceAngular() { confirmChangePassword, confirmImageExport, confirmServiceForceUpdate, + confirmStackUpdate, selectRegistry, confirmContainerDeletion, confirmKubeconfigSelection, diff --git a/app/portainer/services/modal.service/prompt.ts b/app/portainer/services/modal.service/prompt.ts index 3521f7ae9..e9a6382d0 100644 --- a/app/portainer/services/modal.service/prompt.ts +++ b/app/portainer/services/modal.service/prompt.ts @@ -1,5 +1,6 @@ import sanitize from 'sanitize-html'; import bootbox from 'bootbox'; +import '@/portainer/components/BoxSelector/BoxSelectorItem.css'; import { applyBoxCSS, ButtonsOptions, confirmButtons } from './utils'; @@ -136,6 +137,46 @@ export function confirmServiceForceUpdate( customizeCheckboxPrompt(box, sanitizedMessage); } +export function confirmStackUpdate( + message: string, + defaultDisabled: boolean, + defaultToggle: boolean, + confirmButtonClassName: string | undefined, + callback: PromptCallback +) { + const box = prompt({ + title: 'Are you sure?', + inputType: 'checkbox', + inputOptions: [ + { + text: 'Pull latest image version', + value: '1', + }, + ], + buttons: { + confirm: { + label: 'Update', + className: confirmButtonClassName || 'btn-primary', + }, + }, + callback, + }); + box.find('.bootbox-body').prepend(message); + const checkbox = box.find('.bootbox-input-checkbox'); + checkbox.prop('checked', defaultToggle); + checkbox.prop('disabled', defaultDisabled); + const checkboxDiv = box.find('.checkbox'); + checkboxDiv.removeClass('checkbox'); + checkboxDiv.prop( + 'style', + 'position: relative; display: block; margin-top: 10px; margin-bottom: 10px;' + ); + const checkboxLabel = box.find('.form-check-label'); + checkboxLabel.addClass('switch box-selector-item limited business'); + const switchEle = checkboxLabel.find('i'); + switchEle.prop('style', 'margin-left:20px'); +} + export function confirmKubeconfigSelection( options: InputOption[], expiryMessage: string, diff --git a/app/portainer/views/stacks/create/createStackController.js b/app/portainer/views/stacks/create/createStackController.js index 31f4e4244..65c300cb4 100644 --- a/app/portainer/views/stacks/create/createStackController.js +++ b/app/portainer/views/stacks/create/createStackController.js @@ -4,6 +4,7 @@ import uuidv4 from 'uuid/v4'; import { AccessControlFormData } from '@/portainer/components/accessControlForm/porAccessControlFormModel'; import { STACK_NAME_VALIDATION_REGEX } from '@/constants'; import { RepositoryMechanismTypes } from '@/kubernetes/models/deploy'; +import { FeatureId } from 'Portainer/feature-flags/enums'; angular .module('portainer.app') @@ -31,8 +32,9 @@ angular ) { $scope.onChangeTemplateId = onChangeTemplateId; $scope.buildAnalyticsProperties = buildAnalyticsProperties; - + $scope.stackWebhookFeature = FeatureId.STACK_WEBHOOK; $scope.STACK_NAME_VALIDATION_REGEX = STACK_NAME_VALIDATION_REGEX; + $scope.isAdmin = Authentication.isAdmin(); $scope.formValues = { Name: '', diff --git a/app/portainer/views/stacks/create/createstack.html b/app/portainer/views/stacks/create/createstack.html index 04f4a8dc3..5331cf340 100644 --- a/app/portainer/views/stacks/create/createstack.html +++ b/app/portainer/views/stacks/create/createstack.html @@ -157,6 +157,25 @@ +
+
Webhooks
+
+
+ + + +
+
+
+ diff --git a/app/portainer/views/stacks/edit/stack.html b/app/portainer/views/stacks/edit/stack.html index 3467c03bd..6271495d4 100644 --- a/app/portainer/views/stacks/edit/stack.html +++ b/app/portainer/views/stacks/edit/stack.html @@ -160,6 +160,26 @@ >
+ +
+
Webhooks
+
+
+ + + +
+
+
+
Do you want to force an update of the stack?
' + + '
'; + const template = angular.element(tplCrop); + const html = $compile(template)($scope); + // 'Do you want to force an update of the stack?' + ModalService.confirmStackUpdate(html, true, true, null, function (result) { + if (!result) { + return; + } + var stackFile = $scope.stackFileContent; + var env = FormHelper.removeInvalidEnvVars($scope.formValues.Env); + var prune = $scope.formValues.Prune; - // TODO: this is a work-around for stacks created with Portainer version >= 1.17.1 - // The EndpointID property is not available for these stacks, we can pass - // the current endpoint identifier as a part of the update request. It will be used if - // the EndpointID property is not defined on the stack. - if (stack.EndpointId === 0) { - stack.EndpointId = endpoint.Id; - } + // TODO: this is a work-around for stacks created with Portainer version >= 1.17.1 + // The EndpointID property is not available for these stacks, we can pass + // the current endpoint identifier as a part of the update request. It will be used if + // the EndpointID property is not defined on the stack. + if (stack.EndpointId === 0) { + stack.EndpointId = endpoint.Id; + } - $scope.state.actionInProgress = true; - StackService.updateStack(stack, stackFile, env, prune) - .then(function success() { - Notifications.success('Stack successfully deployed'); - $scope.state.isEditorDirty = false; - $state.reload(); - }) - .catch(function error(err) { - Notifications.error('Failure', err, 'Unable to create stack'); - }) - .finally(function final() { - $scope.state.actionInProgress = false; - }); + $scope.state.actionInProgress = true; + StackService.updateStack(stack, stackFile, env, prune) + .then(function success() { + Notifications.success('Stack successfully deployed'); + $scope.state.isEditorDirty = false; + $state.reload(); + }) + .catch(function error(err) { + Notifications.error('Failure', err, 'Unable to create stack'); + }) + .finally(function final() { + $scope.state.actionInProgress = false; + }); + }); }; $scope.editorUpdate = function (cm) { diff --git a/yarn.lock b/yarn.lock index 9321c5f4c..92da6bc18 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4547,7 +4547,6 @@ angular-moment-picker@^0.10.2: dependencies: angular-mocks "1.6.1" angular-sanitize "1.6.1" - lodash-es "^4.17.15" angular-resource@1.8.2: version "1.8.2" @@ -12598,9 +12597,9 @@ locate-path@^6.0.0: dependencies: p-locate "^5.0.0" -lodash-es@^4.17.21: +lodash-es@^4.17.15, lodash-es@^4.17.21: version "4.17.21" - resolved "https://registry.yarnpkg.com/lodash-es/-/lodash-es-4.17.21.tgz#43e626c46e6591b7750beb2b50117390c609e3ee" + resolved "https://registry.npmmirror.com/lodash-es/-/lodash-es-4.17.21.tgz#43e626c46e6591b7750beb2b50117390c609e3ee" integrity sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw== lodash-webpack-plugin@^0.11.6: