mirror of https://github.com/portainer/portainer
feat(webhook): teasers of pull images and webhook for EE EE-1332 (#6278)
* feat(webhook): teasers of pull images and webhook for EEpull/6557/head
parent
fa208c7f2a
commit
37ca62eb06
|
@ -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',
|
||||
|
|
|
@ -65,6 +65,28 @@
|
|||
</por-image-registry>
|
||||
<!-- !image-and-registry -->
|
||||
</div>
|
||||
<!-- create-webhook -->
|
||||
<div ng-if="isAdmin && applicationState.endpoint.type !== 4">
|
||||
<div class="col-sm-12 form-section-title"> Webhooks </div>
|
||||
<div class="form-group">
|
||||
<div class="col-sm-12">
|
||||
<label class="control-label text-left">
|
||||
Create a container webhook
|
||||
<portainer-tooltip
|
||||
position="top"
|
||||
message="Create a webhook (or callback URI) to automate the recreate this container. Sending a POST request to this callback URI (without requiring any authentication) will pull the most up-to-date version of the associated image and recreate this container."
|
||||
></portainer-tooltip>
|
||||
</label>
|
||||
<label class="switch box-selector-item limited business" style="margin-left: 20px">
|
||||
<input type="checkbox" ng-model="formValues.EnableWebhook" disabled="disabled" ng-checked="true" />
|
||||
<i class="orange-icon" aria-hidden="true" style="margin-right: 2px"></i>
|
||||
</label>
|
||||
<be-feature-indicator feature="containerWebhookFeature"></be-feature-indicator>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- !create-webhook -->
|
||||
|
||||
<div class="col-sm-12 form-section-title"> Network ports configuration </div>
|
||||
<!-- publish-exposed-ports -->
|
||||
<div class="form-group">
|
||||
|
|
|
@ -110,6 +110,19 @@
|
|||
<td>Finished</td>
|
||||
<td>{{ container.State.FinishedAt | getisodate }}</td>
|
||||
</tr>
|
||||
<tr ng-if="isAdmin && displayRecreateButton && applicationState.endpoint.type !== 4">
|
||||
<td colspan="1">
|
||||
Container webhook
|
||||
<portainer-tooltip
|
||||
position="top"
|
||||
message="Webhook (or callback URI) used to automate the recreate this container. Sending a POST request to this callback URI (without requiring any authentication) will pull the most up-to-date version of the associated image and recreate this container."
|
||||
></portainer-tooltip>
|
||||
<label class="switch box-selector-item limited business" style="margin-left: 20px">
|
||||
<input disable-authorization="DockerContainerUpdate" type="checkbox" ng-model="WebhookExists" disabled="disabled" ng-checked="true" /><i></i>
|
||||
</label>
|
||||
<be-feature-indicator feature="containerWebhookFeature"></be-feature-indicator>
|
||||
</td>
|
||||
</tr>
|
||||
<tr authorization="DockerContainerLogs, DockerContainerInspect, DockerContainerStats, DockerExecStart">
|
||||
<td colspan="2">
|
||||
<div class="btn-group" role="group" aria-label="...">
|
||||
|
|
|
@ -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(),
|
||||
|
|
|
@ -10,6 +10,7 @@ class GitFormAutoUpdateFieldsetController {
|
|||
this.onChangeInterval = this.onChangeField('RepositoryFetchInterval');
|
||||
|
||||
this.limitedFeature = FeatureId.FORCE_REDEPLOYMENT;
|
||||
this.stackPullImageFeature = FeatureId.STACK_PULL_IMAGE;
|
||||
}
|
||||
|
||||
copyWebhook() {
|
||||
|
|
|
@ -51,6 +51,11 @@
|
|||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group" ng-if="$ctrl.model.RepositoryAutomaticUpdates">
|
||||
<div class="col-sm-12">
|
||||
<por-switch-field name="forcePullImage" feature-id="$ctrl.stackPullImageFeature" checked="$ctrl.model.ForcePullImage" label="'Pull latest image'"> </por-switch-field>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group" ng-if="$ctrl.model.RepositoryAutomaticUpdates">
|
||||
<div class="col-sm-12">
|
||||
<por-switch-field
|
||||
|
|
|
@ -1,16 +1,19 @@
|
|||
import uuidv4 from 'uuid/v4';
|
||||
import { RepositoryMechanismTypes } from 'Kubernetes/models/deploy';
|
||||
import { FeatureId } from 'Portainer/feature-flags/enums';
|
||||
class StackRedeployGitFormController {
|
||||
/* @ngInject */
|
||||
constructor($async, $state, StackService, ModalService, Notifications, WebhookHelper, FormHelper) {
|
||||
constructor($async, $state, $compile, $scope, StackService, ModalService, Notifications, WebhookHelper, FormHelper) {
|
||||
this.$async = $async;
|
||||
this.$state = $state;
|
||||
this.$compile = $compile;
|
||||
this.$scope = $scope;
|
||||
this.StackService = StackService;
|
||||
this.ModalService = ModalService;
|
||||
this.Notifications = Notifications;
|
||||
this.WebhookHelper = WebhookHelper;
|
||||
this.FormHelper = FormHelper;
|
||||
|
||||
$scope.stackPullImageFeature = FeatureId.STACK_PULL_IMAGE;
|
||||
this.state = {
|
||||
inProgress: false,
|
||||
redeployInProgress: false,
|
||||
|
@ -86,27 +89,21 @@ class StackRedeployGitFormController {
|
|||
}
|
||||
|
||||
async submit() {
|
||||
return this.$async(async () => {
|
||||
const tplCrop =
|
||||
'<div>Any changes to this stack or application made locally in Portainer will be overridden, which may cause service interruption.</div>' +
|
||||
'<div"><div style="position: absolute; right: 110px; top: 68px; z-index: 999">' +
|
||||
'<be-feature-indicator feature="stackPullImageFeature"></be-feature-indicator></div></div>';
|
||||
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 {
|
||||
|
|
|
@ -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',
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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';
|
|
@ -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,
|
||||
|
|
|
@ -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<i></i>',
|
||||
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,
|
||||
|
|
|
@ -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: '',
|
||||
|
|
|
@ -157,6 +157,25 @@
|
|||
</editor-description>
|
||||
</web-editor-form>
|
||||
|
||||
<div ng-if="state.Method !== 'repository' && isAdmin && applicationState.endpoint.type !== 4">
|
||||
<div class="col-sm-12 form-section-title"> Webhooks </div>
|
||||
<div class="form-group">
|
||||
<div class="col-sm-12">
|
||||
<label class="control-label text-left">
|
||||
Create a Stack webhook
|
||||
<portainer-tooltip
|
||||
position="top"
|
||||
message="Create a webhook (or callback URI) to automate the update of this stack. Sending a POST request to this callback URI (without requiring any authentication) will pull the most up-to-date version of the associated image and re-deploy this stack."
|
||||
></portainer-tooltip>
|
||||
</label>
|
||||
<label class="switch box-selector-item limited business" style="margin-left: 20px">
|
||||
<input type="checkbox" ng-model="formValues.EnableWebhook" disabled="disabled" ng-checked="true" /><i></i>
|
||||
</label>
|
||||
<be-feature-indicator feature="stackWebhookFeature"></be-feature-indicator>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- environment-variables -->
|
||||
<environment-variables-panel ng-model="formValues.Env" explanation="These values will be used as substitutions in the stack file" on-change="(handleEnvVarChange)">
|
||||
</environment-variables-panel>
|
||||
|
|
|
@ -160,6 +160,26 @@
|
|||
></code-editor>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div ng-if="isAdmin && applicationState.endpoint.type !== 4">
|
||||
<div class="col-sm-12 form-section-title"> Webhooks </div>
|
||||
<div class="form-group">
|
||||
<div class="col-sm-12">
|
||||
<label class="control-label text-left">
|
||||
Create a Stack webhook
|
||||
<portainer-tooltip
|
||||
position="top"
|
||||
message="Create a webhook (or callback URI) to automate the update of this stack. Sending a POST request to this callback URI (without requiring any authentication) will pull the most up-to-date version of the associated image and re-deploy this stack."
|
||||
></portainer-tooltip>
|
||||
</label>
|
||||
<label class="switch box-selector-item limited business" style="margin-left: 20px">
|
||||
<input type="checkbox" ng-model="formValues.EnableWebhook" disabled="disabled" ng-checked="true" /><i></i>
|
||||
</label>
|
||||
<be-feature-indicator feature="stackWebhookFeature"></be-feature-indicator>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- environment-variables -->
|
||||
<div ng-if="stack">
|
||||
<environment-variables-panel
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
import { AccessControlFormData } from 'Portainer/components/accessControlForm/porAccessControlFormModel';
|
||||
import { FeatureId } from 'Portainer/feature-flags/enums';
|
||||
|
||||
angular.module('portainer.app').controller('StackController', [
|
||||
'$async',
|
||||
'$compile',
|
||||
'$q',
|
||||
'$scope',
|
||||
'$state',
|
||||
|
@ -27,6 +29,7 @@ angular.module('portainer.app').controller('StackController', [
|
|||
'endpoint',
|
||||
function (
|
||||
$async,
|
||||
$compile,
|
||||
$q,
|
||||
$scope,
|
||||
$state,
|
||||
|
@ -52,6 +55,9 @@ angular.module('portainer.app').controller('StackController', [
|
|||
endpoint
|
||||
) {
|
||||
$scope.endpoint = endpoint;
|
||||
$scope.isAdmin = Authentication.isAdmin();
|
||||
$scope.stackWebhookFeature = FeatureId.STACK_WEBHOOK;
|
||||
$scope.stackPullImageFeature = FeatureId.STACK_PULL_IMAGE;
|
||||
$scope.state = {
|
||||
actionInProgress: false,
|
||||
migrationInProgress: false,
|
||||
|
@ -216,32 +222,43 @@ angular.module('portainer.app').controller('StackController', [
|
|||
};
|
||||
|
||||
$scope.deployStack = function () {
|
||||
var stackFile = $scope.stackFileContent;
|
||||
var env = FormHelper.removeInvalidEnvVars($scope.formValues.Env);
|
||||
var prune = $scope.formValues.Prune;
|
||||
var stack = $scope.stack;
|
||||
const stack = $scope.stack;
|
||||
const tplCrop =
|
||||
'<div>Do you want to force an update of the stack?</div>' +
|
||||
'<div style="position: absolute; right: 110px; top: 48px; z-index: 999"><be-feature-indicator feature="stackPullImageFeature"></be-feature-indicator></div>';
|
||||
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) {
|
||||
|
|
|
@ -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:
|
||||
|
|
Loading…
Reference in New Issue