1
0
Fork 0
mirror of https://github.com/portainer/portainer.git synced 2025-08-08 07:15:23 +02:00

refactor(ui/modals): replace bootbox with react solution [EE-4541] (#8010)

This commit is contained in:
Chaim Lev-Ari 2023-02-14 13:49:41 +05:30 committed by GitHub
parent 392c7f74b8
commit e66dea44e3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
111 changed files with 1330 additions and 1562 deletions

View file

@ -1,10 +1,13 @@
import { ModalType } from '@@/modals';
import { confirm } from '@@/modals/confirm';
import { buildConfirmButton } from '@@/modals/utils';
class KubernetesAppGitFormController {
/* @ngInject */
constructor($async, $state, StackService, ModalService, Notifications) {
constructor($async, $state, StackService, Notifications) {
this.$async = $async;
this.$state = $state;
this.StackService = StackService;
this.ModalService = ModalService;
this.Notifications = Notifications;
this.state = {
@ -39,19 +42,16 @@ class KubernetesAppGitFormController {
async pullAndRedeployApplication() {
return this.$async(async () => {
try {
const confirmed = await this.ModalService.confirmAsync({
const confirmed = await confirm({
title: 'Are you sure?',
message: 'Any changes to this application will be overridden by the definition in git and may cause a service interruption. Do you wish to continue?',
buttons: {
confirm: {
label: 'Update',
className: 'btn-warning',
},
},
confirmButton: buildConfirmButton('Update', 'warning'),
modalType: ModalType.Warn,
});
if (!confirmed) {
return;
}
this.state.redeployInProgress = true;
await this.StackService.updateKubeGit(this.stack.Id, this.stack.EndpointId, this.namespace, this.formValues);
this.Notifications.success('Success', 'Pulled and redeployed stack successfully');

View file

@ -1,12 +1,14 @@
import uuidv4 from 'uuid/v4';
import { RepositoryMechanismTypes } from 'Kubernetes/models/deploy';
import { confirm } from '@@/modals/confirm';
import { buildConfirmButton } from '@@/modals/utils';
import { ModalType } from '@@/modals';
class KubernetesRedeployAppGitFormController {
/* @ngInject */
constructor($async, $state, StackService, ModalService, Notifications, WebhookHelper) {
constructor($async, $state, StackService, Notifications, WebhookHelper) {
this.$async = $async;
this.$state = $state;
this.StackService = StackService;
this.ModalService = ModalService;
this.Notifications = Notifications;
this.WebhookHelper = WebhookHelper;
@ -80,19 +82,16 @@ class KubernetesRedeployAppGitFormController {
async pullAndRedeployApplication() {
return this.$async(async () => {
try {
const confirmed = await this.ModalService.confirmAsync({
const confirmed = await confirm({
title: 'Are you sure?',
message: 'Any changes to this application will be overridden by the definition in git and may cause a service interruption. Do you wish to continue?',
buttons: {
confirm: {
label: 'Update',
className: 'btn-warning',
},
},
confirmButton: buildConfirmButton('Update', 'warning'),
modalType: ModalType.Warn,
});
if (!confirmed) {
return;
}
this.state.redeployInProgress = true;
await this.StackService.updateKubeGit(this.stack.Id, this.stack.EndpointId, this.namespace, this.formValues);
this.Notifications.success('Success', 'Pulled and redeployed stack successfully');

View file

@ -1,15 +1,16 @@
import uuidv4 from 'uuid/v4';
import { RepositoryMechanismTypes } from 'Kubernetes/models/deploy';
import { FeatureId } from '@/react/portainer/feature-flags/enums';
import { confirmStackUpdate } from '@/react/docker/stacks/common/confirm-stack-update';
class StackRedeployGitFormController {
/* @ngInject */
constructor($async, $state, $compile, $scope, StackService, ModalService, Notifications, WebhookHelper, FormHelper) {
constructor($async, $state, $compile, $scope, StackService, 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;
@ -105,33 +106,32 @@ class StackRedeployGitFormController {
async submit() {
const isSwarmStack = this.stack.Type === 1;
const that = this;
this.ModalService.confirmStackUpdate(
confirmStackUpdate(
'Any changes to this stack or application made locally in Portainer will be overridden, which may cause service interruption. Do you wish to continue?',
isSwarmStack,
'btn-warning',
async function (result) {
if (!result) {
return;
}
try {
that.state.redeployInProgress = true;
await that.StackService.updateGit(
that.stack.Id,
that.stack.EndpointId,
that.FormHelper.removeInvalidEnvVars(that.formValues.Env),
that.formValues.Option.Prune,
that.formValues,
!!result[0]
);
that.Notifications.success('Success', 'Pulled and redeployed stack successfully');
that.$state.reload();
} catch (err) {
that.Notifications.error('Failure', err, 'Failed redeploying stack');
} finally {
that.state.redeployInProgress = false;
}
isSwarmStack
).then(async function (result) {
if (!result) {
return;
}
);
try {
that.state.redeployInProgress = true;
await that.StackService.updateGit(
that.stack.Id,
that.stack.EndpointId,
that.FormHelper.removeInvalidEnvVars(that.formValues.Env),
that.formValues.Option.Prune,
that.formValues,
result.pullImage
);
that.Notifications.success('Success', 'Pulled and redeployed stack successfully');
that.$state.reload();
} catch (err) {
that.Notifications.error('Failure', err, 'Failed redeploying stack');
} finally {
that.state.redeployInProgress = false;
}
});
}
async saveGitSettings() {

View file

@ -2,13 +2,11 @@ import angular from 'angular';
import { apiServicesModule } from './api';
import { Notifications } from './notifications';
import { ModalServiceAngular } from './modal.service';
import { HttpRequestHelperAngular } from './http-request.helper';
import { EndpointProvider } from './endpointProvider';
export default angular
.module('portainer.app.services', [apiServicesModule])
.factory('Notifications', Notifications)
.factory('ModalService', ModalServiceAngular)
.factory('EndpointProvider', EndpointProvider)
.factory('HttpRequestHelper', HttpRequestHelperAngular).name;

View file

@ -1,266 +0,0 @@
import sanitize from 'sanitize-html';
import bootbox from 'bootbox';
import {
applyBoxCSS,
ButtonsOptions,
confirmButtons,
buildTitle,
ModalTypeIcon,
} from './utils';
type ConfirmCallback = (confirmed: boolean) => void;
interface ConfirmAsyncOptions {
title: string;
message: string;
buttons: ButtonsOptions;
}
interface ConfirmOptions extends ConfirmAsyncOptions {
callback: ConfirmCallback;
}
export function confirmWebEditorDiscard() {
const options = {
title: buildTitle('Are you sure?'),
message:
'You currently have unsaved changes in the editor. Are you sure you want to leave?',
buttons: {
confirm: {
label: 'Yes',
className: 'btn-danger',
},
},
};
return new Promise((resolve) => {
confirm({
...options,
callback: (confirmed) => resolve(confirmed),
});
});
}
export function confirmAsync(options: ConfirmAsyncOptions) {
return new Promise((resolve) => {
confirm({
...options,
title: buildTitle(options.title),
callback: (confirmed) => resolve(confirmed),
});
});
}
export function confirmDestructiveAsync(options: ConfirmAsyncOptions) {
return new Promise((resolve) => {
confirm({
...options,
title: buildTitle(options.title, ModalTypeIcon.Destructive),
callback: (confirmed) => resolve(confirmed),
});
});
}
export function confirm(options: ConfirmOptions) {
const box = bootbox.confirm({
title: options.title,
message: options.message,
buttons: confirmButtons(options.buttons),
callback: options.callback,
});
applyBoxCSS(box);
}
export function confirmWarn(options: ConfirmOptions) {
confirm({ ...options, title: buildTitle(options.title, ModalTypeIcon.Warn) });
}
export function confirmDestructive(options: ConfirmOptions) {
confirm({
...options,
title: buildTitle(options.title, ModalTypeIcon.Destructive),
});
}
export function confirmImageForceRemoval(callback: ConfirmCallback) {
confirm({
title: buildTitle('Are you sure?', ModalTypeIcon.Destructive),
message:
'Forcing the removal of the image will remove the image even if it has multiple tags or if it is used by stopped containers.',
buttons: {
confirm: {
label: 'Remove the image',
className: 'btn-danger',
},
},
callback,
});
}
export function cancelRegistryRepositoryAction(callback: ConfirmCallback) {
confirm({
title: buildTitle('Are you sure?', ModalTypeIcon.Destructive),
message:
'WARNING: interrupting this operation before it has finished will result in the loss of all tags. Are you sure you want to do this?',
buttons: {
confirm: {
label: 'Stop',
className: 'btn-danger',
},
},
callback,
});
}
export function confirmDeletion(message: string, callback: ConfirmCallback) {
const messageSanitized = sanitize(message);
confirm({
title: buildTitle('Are you sure?', ModalTypeIcon.Destructive),
message: messageSanitized,
buttons: {
confirm: {
label: 'Remove',
className: 'btn-danger',
},
},
callback,
});
}
export function confirmWithTitle(
title: string,
message: string,
callback: ConfirmCallback
) {
const messageSanitized = sanitize(message);
confirm({
title: buildTitle(title, ModalTypeIcon.Destructive),
message: messageSanitized,
buttons: {
confirm: {
label: 'Remove',
className: 'btn-danger',
},
},
callback,
});
}
export function confirmDetachment(message: string, callback: ConfirmCallback) {
const messageSanitized = sanitize(message);
confirm({
title: buildTitle('Are you sure?'),
message: messageSanitized,
buttons: {
confirm: {
label: 'Detach',
className: 'btn-primary',
},
},
callback,
});
}
export function confirmDisassociate(callback: ConfirmCallback) {
const message =
'<p>Disassociating this Edge environment will mark it as non associated and will clear the registered Edge ID.</p>' +
'<p>Any agent started with the Edge key associated to this environment will be able to re-associate with this environment.</p>' +
'<p>You can re-use the Edge ID and Edge key that you used to deploy the existing Edge agent to associate a new Edge device to this environment.</p>';
confirm({
title: buildTitle('About disassociating'),
message: sanitize(message),
buttons: {
confirm: {
label: 'Disassociate',
className: 'btn-primary',
},
},
callback,
});
}
export function confirmUpdate(message: string, callback: ConfirmCallback) {
const messageSanitized = sanitize(message);
confirm({
title: buildTitle('Are you sure?'),
message: messageSanitized,
buttons: {
confirm: {
label: 'Update',
className: 'btn-primary',
},
},
callback,
});
}
export function confirmRedeploy(message: string, callback: ConfirmCallback) {
const messageSanitized = sanitize(message);
confirm({
title: '',
message: messageSanitized,
buttons: {
confirm: {
label: 'Redeploy the applications',
className: 'btn-primary',
},
cancel: {
label: "I'll do it later",
},
},
callback,
});
}
export function confirmDeletionAsync(message: string) {
return new Promise((resolve) => {
confirmDeletion(message, (confirmed) => resolve(confirmed));
});
}
export function confirmImageExport(callback: ConfirmCallback) {
confirm({
title: buildTitle('Caution'),
message:
'The export may take several minutes, do not navigate away whilst the export is in progress.',
buttons: {
confirm: {
label: 'Continue',
className: 'btn-primary',
},
},
callback,
});
}
export function confirmChangePassword() {
return confirmAsync({
title: buildTitle('Are you sure?'),
message:
'You will be logged out after the password change. Do you want to change your password?',
buttons: {
confirm: {
label: 'Change',
className: 'btn-primary',
},
},
});
}
export function confirmForceChangePassword() {
const box = bootbox.dialog({
message:
'Please update your password to a stronger password to continue using Portainer',
buttons: {
confirm: {
label: 'OK',
className: 'btn-primary',
},
},
});
applyBoxCSS(box);
}

View file

@ -1,70 +0,0 @@
import sanitize from 'sanitize-html';
import bootbox from 'bootbox';
import {
cancelRegistryRepositoryAction,
confirmAsync,
confirmWarn,
confirmDestructive,
confirmDestructiveAsync,
confirmDisassociate,
confirmDeletion,
confirmDetachment,
confirmDeletionAsync,
confirmChangePassword,
confirmImageExport,
confirmImageForceRemoval,
confirmRedeploy,
confirmUpdate,
confirmWebEditorDiscard,
confirm,
confirmForceChangePassword,
confirmWithTitle,
} from './confirm';
import {
confirmContainerDeletion,
confirmContainerRecreation,
confirmServiceForceUpdate,
confirmStackUpdate,
selectRegistry,
} from './prompt';
export function enlargeImage(imageUrl: string) {
const imageSanitized = sanitize(imageUrl);
bootbox.dialog({
message: `<img src="${imageSanitized}" style="width:100%" />`,
className: 'image-zoom-modal',
onEscape: true,
});
}
/* @ngInject */
export function ModalServiceAngular() {
return {
enlargeImage,
confirmWebEditorDiscard,
confirmAsync,
confirmWarn,
confirmDestructive,
confirmDestructiveAsync,
confirm,
confirmImageForceRemoval,
cancelRegistryRepositoryAction,
confirmDeletion,
confirmDetachment,
confirmDisassociate,
confirmUpdate,
confirmRedeploy,
confirmDeletionAsync,
confirmContainerRecreation,
confirmChangePassword,
confirmImageExport,
confirmServiceForceUpdate,
confirmStackUpdate,
selectRegistry,
confirmContainerDeletion,
confirmForceChangePassword,
confirmWithTitle,
};
}

View file

@ -1,235 +0,0 @@
import sanitize from 'sanitize-html';
import bootbox from 'bootbox';
import {
applyBoxCSS,
ButtonsOptions,
confirmButtons,
buildTitle,
ModalTypeIcon,
} from './utils';
type PromptCallback = ((value: string) => void) | ((value: string[]) => void);
interface InputOption {
text: string;
value: string;
}
interface PromptOptions {
title: string;
message?: string;
inputType?:
| 'text'
| 'textarea'
| 'email'
| 'select'
| 'checkbox'
| 'date'
| 'time'
| 'number'
| 'password'
| 'radio'
| 'range';
inputOptions: InputOption[];
buttons: ButtonsOptions;
value?: string;
callback: PromptCallback;
}
export async function promptAsync(options: Omit<PromptOptions, 'callback'>) {
return new Promise((resolve) => {
prompt({
...options,
callback: (result: string | string[]) => resolve(result),
});
});
}
// the ts-ignore is required because the bootbox typings are not up to date
// remove the ts-ignore when the typings are updated in
export function prompt(options: PromptOptions) {
const box = bootbox.prompt({
title: options.title,
message: options.message || '',
inputType: options.inputType,
inputOptions: options.inputOptions,
buttons: options.buttons ? confirmButtons(options.buttons) : undefined,
// casting is done because ts definition expects string=>any, but library code can emit different values, based on inputType
callback: options.callback as (value: string) => void,
value: options.value,
});
applyBoxCSS(box);
return box;
}
export function confirmContainerDeletion(
title: string,
callback: PromptCallback
) {
prompt({
title: buildTitle(title, ModalTypeIcon.Destructive),
inputType: 'checkbox',
inputOptions: [
{
text: 'Automatically remove non-persistent volumes<i></i>',
value: '1',
},
],
buttons: {
confirm: {
label: 'Remove',
className: 'btn-danger',
},
},
callback,
});
}
export function confirmUpdateAppIngress(
title: string,
message: string,
inputText: string,
callback: PromptCallback
) {
prompt({
title: buildTitle(title),
inputType: 'checkbox',
message,
inputOptions: [
{
text: `${inputText}<i></i>`,
value: '1',
},
],
buttons: {
confirm: {
label: 'Update',
className: 'btn-primary',
},
},
callback,
});
}
export function selectRegistry(options: PromptOptions) {
prompt(options);
}
export function confirmContainerRecreation(
cannotPullImage: boolean | null,
callback: PromptCallback
) {
const box = prompt({
title: buildTitle('Are you sure?', ModalTypeIcon.Destructive),
inputType: 'checkbox',
inputOptions: [
{
text: 'Re-pull image<i></i>',
value: '1',
},
],
buttons: {
confirm: {
label: 'Recreate',
className: 'btn-danger',
},
},
callback,
});
const message = `You're about to recreate this container and any non-persisted data will be lost. This container will be removed and another one will be created using the same configuration.`;
box.find('.bootbox-body').prepend(`<p>${message}</p>`);
const label = box.find('.form-check-label');
label.css('padding-left', '5px');
label.css('padding-right', '25px');
if (cannotPullImage) {
label.css('cursor', 'not-allowed');
label.find('i').css('cursor', 'not-allowed');
const checkbox = box.find('.bootbox-input-checkbox');
checkbox.prop('disabled', true);
const formCheck = box.find('.form-check');
formCheck.prop('style', 'height: 45px;');
const cannotPullImageMessage = `<pr-icon icon="'alert-triangle'" mode="'warning'"/>
<div class="inline-text text-warning">
<span>Cannot re-pull as the image is inaccessible - either it no longer exists or the tag or name is no longer correct.
</span>
</div>`;
formCheck.append(`${cannotPullImageMessage}`);
}
}
export function confirmServiceForceUpdate(
message: string,
callback: PromptCallback
) {
const sanitizedMessage = sanitize(message);
const box = prompt({
title: buildTitle('Are you sure?'),
inputType: 'checkbox',
inputOptions: [
{
text: 'Re-pull image<i></i>',
value: '1',
},
],
buttons: {
confirm: {
label: 'Update',
className: 'btn-primary',
},
},
callback,
});
customizeCheckboxPrompt(box, sanitizedMessage);
}
export function confirmStackUpdate(
message: string,
defaultToggle: boolean,
confirmButtonClass: string | undefined,
callback: PromptCallback
) {
const sanitizedMessage = sanitize(message);
const box = prompt({
title: buildTitle('Are you sure?'),
inputType: 'checkbox',
inputOptions: [
{
text: 'Re-pull image and redeploy<i></i>',
value: '1',
},
],
buttons: {
confirm: {
label: 'Update',
className: 'btn-primary',
},
},
callback,
});
customizeCheckboxPrompt(box, sanitizedMessage, defaultToggle);
}
function customizeCheckboxPrompt(
box: JQuery<HTMLElement>,
message: string,
toggleCheckbox = false,
showCheck = false
) {
box.find('.bootbox-body').prepend(`<p>${message}</p>`);
const checkbox = box.find('.bootbox-input-checkbox');
checkbox.prop('checked', toggleCheckbox);
if (showCheck) {
checkbox.addClass('visible');
}
}

View file

@ -1,50 +0,0 @@
import sanitize from 'sanitize-html';
interface Button {
label: string;
className?: string;
}
export interface ButtonsOptions {
confirm: Button;
cancel?: Button;
}
export enum ModalTypeIcon {
Warn = 'warning',
Destructive = 'error',
}
export function confirmButtons(options: ButtonsOptions) {
return {
confirm: {
label: sanitize(options.confirm.label),
className:
options.confirm.className && sanitize(options.confirm.className),
},
cancel: {
label:
options.cancel && options.cancel.label
? sanitize(options.cancel.label)
: 'Cancel',
className: 'btn-default',
},
};
}
export function buildTitle(
title: string,
modalType: ModalTypeIcon = ModalTypeIcon.Warn
) {
return `
<div class="background-${modalType}">
<h5 class="modal-title">${sanitize(title)}</h5>
</div>
`;
}
export function applyBoxCSS(box: JQuery<HTMLElement>) {
box.css({
'vertical-align': 'middle',
});
}

View file

@ -15,6 +15,7 @@ toastr.options = {
closeButton: true,
progressBar: true,
tapToDismiss: false,
escapeHtml: true,
// custom button, using the lucide icon x.svg inside
closeHtml: `<button type="button"><svg
xmlns="http://www.w3.org/2000/svg"

View file

@ -1,40 +1,21 @@
import _ from 'lodash';
import { selectRegistry } from '@/react/docker/images/ItemView/RegistrySelectPrompt';
angular.module('portainer.app').factory('RegistryModalService', ModalServiceFactory);
angular.module('portainer.app').factory('RegistryModalService', RegistryModalService);
function ModalServiceFactory($q, ModalService, RegistryService) {
function RegistryModalService(RegistryService) {
const service = {};
function registries2Options(registries) {
return registries.map((r) => ({
text: r.Name,
value: String(r.Id),
}));
}
service.registryModal = async function (repository, registries) {
const deferred = $q.defer();
const options = registries2Options(registries);
const registryModel = RegistryService.retrievePorRegistryModelFromRepositoryWithRegistries(repository, registries);
const defaultValue = String(_.get(registryModel, 'Registry.Id', '0'));
const defaultValue = _.get(registryModel, 'Registry.Id', 0);
ModalService.selectRegistry({
title: 'Which registry do you want to use?',
inputType: 'select',
inputOptions: options,
value: defaultValue,
callback: (registryId) => {
if (registryId) {
const registryModel = RegistryService.retrievePorRegistryModelFromRepositoryWithRegistries(repository, registries, registryId);
deferred.resolve(registryModel);
} else {
deferred.resolve(null);
}
},
});
const registryId = await selectRegistry(registries, defaultValue);
if (!registryId) {
return null;
}
return deferred.promise;
return RegistryService.retrievePorRegistryModelFromRepositoryWithRegistries(repository, registries, registryId);
};
return service;

View file

@ -1,3 +1,7 @@
import { confirmChangePassword, confirmDelete } from '@@/modals/confirm';
import { openDialog } from '@@/modals/Dialog';
import { buildConfirmButton } from '@@/modals/utils';
angular.module('portainer.app').controller('AccountController', [
'$scope',
'$state',
@ -7,8 +11,7 @@ angular.module('portainer.app').controller('AccountController', [
'SettingsService',
'StateManager',
'ThemeManager',
'ModalService',
function ($scope, $state, Authentication, UserService, Notifications, SettingsService, StateManager, ThemeManager, ModalService) {
function ($scope, $state, Authentication, UserService, Notifications, SettingsService, StateManager, ThemeManager) {
$scope.formValues = {
currentPassword: '',
newPassword: '',
@ -17,7 +20,7 @@ angular.module('portainer.app').controller('AccountController', [
};
$scope.updatePassword = async function () {
const confirmed = await ModalService.confirmChangePassword();
const confirmed = await confirmChangePassword();
if (confirmed) {
try {
await UserService.updateUserPassword($scope.userID, $scope.formValues.currentPassword, $scope.formValues.newPassword);
@ -56,8 +59,9 @@ angular.module('portainer.app').controller('AccountController', [
return true;
}
}
if ($scope.forceChangePassword) {
ModalService.confirmForceChangePassword();
confirmForceChangePassword();
}
return !$scope.forceChangePassword;
};
@ -69,7 +73,7 @@ angular.module('portainer.app').controller('AccountController', [
$scope.removeAction = (selectedTokens) => {
const msg = 'Do you want to remove the selected access token(s)? Any script or application using these tokens will no longer be able to invoke the Portainer API.';
ModalService.confirmDeletion(msg, function (confirmed) {
confirmDelete(msg).then((confirmed) => {
if (!confirmed) {
return;
}
@ -160,3 +164,10 @@ angular.module('portainer.app').controller('AccountController', [
initView();
},
]);
function confirmForceChangePassword() {
return openDialog({
message: 'Please update your password to a stronger password to continue using Portainer',
buttons: [buildConfirmButton('OK')],
});
}

View file

@ -4,30 +4,17 @@ import { TEMPLATE_NAME_VALIDATION_REGEX } from '@/constants';
import { getTemplateVariables, intersectVariables } from '@/react/portainer/custom-templates/components/utils';
import { isBE } from '@/react/portainer/feature-flags/feature-flags.service';
import { editor, upload, git } from '@@/BoxSelector/common-options/build-methods';
import { confirmWebEditorDiscard } from '@@/modals/confirm';
class CreateCustomTemplateViewController {
/* @ngInject */
constructor(
$async,
$state,
$scope,
$window,
Authentication,
ModalService,
CustomTemplateService,
FormValidator,
Notifications,
ResourceControlService,
StackService,
StateManager
) {
constructor($async, $state, $scope, $window, Authentication, CustomTemplateService, FormValidator, Notifications, ResourceControlService, StackService, StateManager) {
Object.assign(this, {
$async,
$state,
$window,
$scope,
Authentication,
ModalService,
CustomTemplateService,
FormValidator,
Notifications,
@ -253,7 +240,7 @@ class CreateCustomTemplateViewController {
async uiCanExit() {
if (this.state.Method === 'editor' && this.formValues.FileContent && this.state.isEditorDirty) {
return this.ModalService.confirmWebEditorDiscard();
return confirmWebEditorDiscard();
}
}
}

View file

@ -3,6 +3,7 @@ import { AccessControlFormData } from 'Portainer/components/accessControlForm/po
import { TEMPLATE_NAME_VALIDATION_REGEX } from '@/constants';
import { renderTemplate } from '@/react/portainer/custom-templates/components/utils';
import { isBE } from '@/react/portainer/feature-flags/feature-flags.service';
import { confirmDelete } from '@@/modals/confirm';
class CustomTemplatesViewController {
/* @ngInject */
@ -14,7 +15,6 @@ class CustomTemplatesViewController {
Authentication,
CustomTemplateService,
FormValidator,
ModalService,
NetworkService,
Notifications,
ResourceControlService,
@ -28,7 +28,6 @@ class CustomTemplatesViewController {
this.Authentication = Authentication;
this.CustomTemplateService = CustomTemplateService;
this.FormValidator = FormValidator;
this.ModalService = ModalService;
this.NetworkService = NetworkService;
this.Notifications = Notifications;
this.ResourceControlService = ResourceControlService;
@ -244,7 +243,7 @@ class CustomTemplatesViewController {
return this.$async(this.confirmDeleteAsync, templateId);
}
async confirmDeleteAsync(templateId) {
const confirmed = await this.ModalService.confirmDeletionAsync('Are you sure that you want to delete this template?');
const confirmed = await confirmDelete('Are you sure that you want to delete this template?');
if (!confirmed) {
return;
}

View file

@ -4,11 +4,12 @@ import { ResourceControlViewModel } from '@/react/portainer/access-control/model
import { AccessControlFormData } from 'Portainer/components/accessControlForm/porAccessControlFormModel';
import { getTemplateVariables, intersectVariables } from '@/react/portainer/custom-templates/components/utils';
import { isBE } from '@/react/portainer/feature-flags/feature-flags.service';
import { confirmWebEditorDiscard } from '@@/modals/confirm';
class EditCustomTemplateViewController {
/* @ngInject */
constructor($async, $state, $window, ModalService, Authentication, CustomTemplateService, FormValidator, Notifications, ResourceControlService) {
Object.assign(this, { $async, $state, $window, ModalService, Authentication, CustomTemplateService, FormValidator, Notifications, ResourceControlService });
constructor($async, $state, $window, Authentication, CustomTemplateService, FormValidator, Notifications, ResourceControlService) {
Object.assign(this, { $async, $state, $window, Authentication, CustomTemplateService, FormValidator, Notifications, ResourceControlService });
this.isTemplateVariablesEnabled = isBE;
@ -146,7 +147,7 @@ class EditCustomTemplateViewController {
async uiCanExit() {
if (this.formValues.FileContent !== this.oldFileContent && this.state.isEditorDirty) {
return this.ModalService.confirmWebEditorDiscard();
return confirmWebEditorDiscard();
}
}

View file

@ -5,7 +5,8 @@ import { createProfile } from 'Portainer/hostmanagement/fdo/fdo.service';
angular.module('portainer.app').controller('AddProfileController', AddProfileController);
export default function AddProfileController($scope, $async, $state, $window, ModalService, Authentication, Notifications) {
/* @ngInject */
export default function AddProfileController($scope, $async, $state, $window, Notifications) {
$scope.buildMethods = [editor];
$scope.formValues = {

View file

@ -4,7 +4,8 @@ import { getProfile, updateProfile } from 'Portainer/hostmanagement/fdo/fdo.serv
angular.module('portainer.app').controller('EditProfileController', EditProfileController);
export default function EditProfileController($scope, $async, $state, $window, ModalService, Authentication, Notifications) {
/* @ngInject */
export default function EditProfileController($scope, $async, $state, $window, Notifications) {
$scope.buildMethods = [editor];
$scope.formValues = {

View file

@ -5,11 +5,13 @@ import { PortainerEndpointTypes } from '@/portainer/models/endpoint/models';
import { EndpointSecurityFormData } from '@/portainer/components/endpointSecurity/porEndpointSecurityModel';
import EndpointHelper from '@/portainer/helpers/endpointHelper';
import { getAMTInfo } from 'Portainer/hostmanagement/open-amt/open-amt.service';
import { confirmDestructiveAsync } from '@/portainer/services/modal.service/confirm';
import { confirmDestructive } from '@@/modals/confirm';
import { isEdgeEnvironment } from '@/react/portainer/environments/utils';
import { commandsTabs } from '@/react/edge/components/EdgeScriptForm/scripts';
import { GpusListAngular } from '@/react/portainer/environments/wizard/EnvironmentsCreationView/shared/Hardware/GpusList';
import { confirmDisassociate } from '@/react/portainer/environments/ItemView/ConfirmDisassociateModel';
import { buildConfirmButton } from '@@/modals/utils';
angular.module('portainer.app').component('gpusList', GpusListAngular).controller('EndpointController', EndpointController);
@ -26,8 +28,7 @@ function EndpointController(
Notifications,
Authentication,
SettingsService,
ModalService
SettingsService
) {
$scope.onChangeCheckInInterval = onChangeCheckInInterval;
$scope.setFieldValue = setFieldValue;
@ -114,7 +115,7 @@ function EndpointController(
};
$scope.onDisassociateEndpoint = async function () {
ModalService.confirmDisassociate((confirmed) => {
confirmDisassociate().then((confirmed) => {
if (confirmed) {
disassociateEndpoint();
}
@ -192,19 +193,10 @@ function EndpointController(
var TLSSkipClientVerify = TLS && (TLSMode === 'tls_ca' || TLSMode === 'tls_only');
if (isEdgeEnvironment(endpoint.Type) && _.difference($scope.initialTagIds, endpoint.TagIds).length > 0) {
let confirmed = await confirmDestructiveAsync({
let confirmed = await confirmDestructive({
title: 'Confirm action',
message: 'Removing tags from this environment will remove the corresponding edge stacks when dynamic grouping is being used',
buttons: {
cancel: {
label: 'Cancel',
className: 'btn-default',
},
confirm: {
label: 'Confirm',
className: 'btn-primary',
},
},
confirmButton: buildConfirmButton(),
});
if (!confirmed) {

View file

@ -1,16 +1,16 @@
import { map } from 'lodash';
import EndpointHelper from '@/portainer/helpers/endpointHelper';
import { getEnvironments } from '@/react/portainer/environments/environment.service';
import { confirmDelete } from '@@/modals/confirm';
export class EndpointsController {
/* @ngInject */
constructor($state, $async, EndpointService, GroupService, ModalService, Notifications, EndpointProvider, StateManager) {
constructor($state, $async, EndpointService, GroupService, Notifications, EndpointProvider, StateManager) {
Object.assign(this, {
$state,
$async,
EndpointService,
GroupService,
ModalService,
Notifications,
EndpointProvider,
StateManager,
@ -30,7 +30,7 @@ export class EndpointsController {
}
removeAction(endpoints) {
this.ModalService.confirmDeletion('This action will remove all configurations associated to your environment(s). Continue?', (confirmed) => {
confirmDelete('This action will remove all configurations associated to your environment(s). Continue?').then((confirmed) => {
if (!confirmed) {
return;
}

View file

@ -1,4 +1,5 @@
import _ from 'lodash-es';
import { confirmDelete } from '@@/modals/confirm';
import { RegistryTypes } from 'Portainer/models/registryTypes';
angular.module('portainer.app').controller('RegistriesController', [
@ -6,9 +7,8 @@ angular.module('portainer.app').controller('RegistriesController', [
'$scope',
'$state',
'RegistryService',
'ModalService',
'Notifications',
function ($q, $scope, $state, RegistryService, ModalService, Notifications) {
function ($q, $scope, $state, RegistryService, Notifications) {
$scope.state = {
actionInProgress: false,
};
@ -24,7 +24,7 @@ angular.module('portainer.app').controller('RegistriesController', [
const registriesMsg = selectedItems.length > 1 ? 'registries' : 'registry';
const msg = `T${regAttrMsg} ${registriesMsg} might be used by applications inside one or more environments. Removing the ${registriesMsg} could lead to a service interruption for the applications using t${regAttrMsg} ${registriesMsg}. Do you want to remove the selected ${registriesMsg}?`;
ModalService.confirmDeletion(msg, function onConfirm(confirmed) {
confirmDelete(msg).then((confirmed) => {
if (!confirmed) {
return;
}

View file

@ -7,12 +7,10 @@ angular.module('portainer.app').controller('SettingsController', [
'$scope',
'Notifications',
'SettingsService',
'ModalService',
'StateManager',
'BackupService',
'FileSaver',
'Blob',
function ($scope, Notifications, SettingsService, ModalService, StateManager, BackupService, FileSaver) {
function ($scope, Notifications, SettingsService, StateManager, BackupService, FileSaver) {
$scope.customBannerFeatureId = FeatureId.CUSTOM_LOGIN_BANNER;
$scope.s3BackupFeatureId = FeatureId.S3_BACKUP_SETTING;
$scope.enforceDeploymentOptions = FeatureId.ENFORCE_DEPLOYMENT_OPTIONS;

View file

@ -8,6 +8,7 @@ import { FeatureId } from '@/react/portainer/feature-flags/enums';
import { isBE } from '@/react/portainer/feature-flags/feature-flags.service';
import { renderTemplate } from '@/react/portainer/custom-templates/components/utils';
import { editor, upload, git, customTemplate } from '@@/BoxSelector/common-options/build-methods';
import { confirmWebEditorDiscard } from '@@/modals/confirm';
angular
.module('portainer.app')
@ -18,7 +19,6 @@ angular
$state,
$async,
$window,
ModalService,
StackService,
Authentication,
Notifications,
@ -363,7 +363,7 @@ angular
this.uiCanExit = async function () {
if ($scope.state.Method === 'editor' && $scope.formValues.StackFileContent && $scope.state.isEditorDirty) {
return ModalService.confirmWebEditorDiscard();
return confirmWebEditorDiscard();
}
};

View file

@ -4,10 +4,13 @@ import { FeatureId } from '@/react/portainer/feature-flags/enums';
import { getEnvironments } from '@/react/portainer/environments/environment.service';
import { StackStatus, StackType } from '@/react/docker/stacks/types';
import { extractContainerNames } from '@/portainer/helpers/stackHelper';
import { confirmStackUpdate } from '@/react/docker/stacks/common/confirm-stack-update';
import { confirm, confirmDelete, confirmWebEditorDiscard } from '@@/modals/confirm';
import { ModalType } from '@@/modals';
import { buildConfirmButton } from '@@/modals/utils';
angular.module('portainer.app').controller('StackController', [
'$async',
'$compile',
'$q',
'$scope',
'$state',
@ -23,7 +26,6 @@ angular.module('portainer.app').controller('StackController', [
'Notifications',
'FormHelper',
'GroupService',
'ModalService',
'StackHelper',
'ResourceControlService',
'Authentication',
@ -31,7 +33,6 @@ angular.module('portainer.app').controller('StackController', [
'endpoint',
function (
$async,
$compile,
$q,
$scope,
$state,
@ -47,7 +48,6 @@ angular.module('portainer.app').controller('StackController', [
Notifications,
FormHelper,
GroupService,
ModalService,
StackHelper,
ResourceControlService,
Authentication,
@ -129,29 +129,24 @@ angular.module('portainer.app').controller('StackController', [
};
$scope.migrateStack = function (name, endpointId) {
return $q(function (resolve) {
ModalService.confirmWarn({
return $q(async function (resolve) {
const confirmed = await confirm({
title: 'Are you sure?',
modalType: ModalType.Warn,
message:
'This action will deploy a new instance of this stack on the target environment, please note that this does NOT relocate the content of any persistent volumes that may be attached to this stack.',
buttons: {
confirm: {
label: 'Migrate',
className: 'btn-danger',
},
},
callback: function onConfirm(confirmed) {
if (!confirmed) {
return resolve();
}
return resolve(migrateStack(name, endpointId));
},
confirmButton: buildConfirmButton('Migrate', 'danger'),
});
if (!confirmed) {
return resolve();
}
return resolve(migrateStack(name, endpointId));
});
};
$scope.removeStack = function () {
ModalService.confirmDeletion('Do you want to remove the stack? Associated services will be removed as well.', function onConfirm(confirmed) {
confirmDelete('Do you want to remove the stack? Associated services will be removed as well').then((confirmed) => {
if (!confirmed) {
return;
}
@ -160,10 +155,11 @@ angular.module('portainer.app').controller('StackController', [
};
$scope.detachStackFromGit = function () {
ModalService.confirmDetachment('Do you want to detach the stack from Git?', function onConfirm(confirmed) {
confirmDetachment().then(function onConfirm(confirmed) {
if (!confirmed) {
return;
}
$scope.deployStack();
});
};
@ -240,7 +236,7 @@ angular.module('portainer.app').controller('StackController', [
$scope.deployStack = function () {
const stack = $scope.stack;
const isSwarmStack = stack.Type === 1;
ModalService.confirmStackUpdate('Do you want to force an update of the stack?', isSwarmStack, null, function (result) {
confirmStackUpdate('Do you want to force an update of the stack?', isSwarmStack).then(function (result) {
if (!result) {
return;
}
@ -257,7 +253,7 @@ angular.module('portainer.app').controller('StackController', [
}
$scope.state.actionInProgress = true;
StackService.updateStack(stack, stackFile, env, prune, !!result[0])
StackService.updateStack(stack, stackFile, env, prune, result.pullImage)
.then(function success() {
Notifications.success('Success', 'Stack successfully deployed');
$scope.state.isEditorDirty = false;
@ -285,10 +281,11 @@ angular.module('portainer.app').controller('StackController', [
return $async(stopStackAsync);
}
async function stopStackAsync() {
const confirmed = await ModalService.confirmAsync({
const confirmed = await confirm({
title: 'Are you sure?',
modalType: ModalType.Warn,
message: 'Are you sure you want to stop this stack?',
buttons: { confirm: { label: 'Stop', className: 'btn-danger' } },
confirmButton: buildConfirmButton('Stop', 'danger'),
});
if (!confirmed) {
return;
@ -447,7 +444,7 @@ angular.module('portainer.app').controller('StackController', [
this.uiCanExit = async function () {
if ($scope.stackFileContent && $scope.state.isEditorDirty) {
return ModalService.confirmWebEditorDiscard();
return confirmWebEditorDiscard();
}
};
@ -494,3 +491,12 @@ angular.module('portainer.app').controller('StackController', [
initView();
},
]);
function confirmDetachment() {
return confirm({
modalType: ModalType.Warn,
title: 'Are you sure?',
message: 'Do you want to detach the stack from Git?',
confirmButton: buildConfirmButton('Detach', 'danger'),
});
}

View file

@ -1,9 +1,11 @@
import { confirmDelete } from '@@/modals/confirm';
angular.module('portainer.app').controller('StacksController', StacksController);
/* @ngInject */
function StacksController($scope, $state, Notifications, StackService, ModalService, Authentication, endpoint) {
function StacksController($scope, $state, Notifications, StackService, Authentication, endpoint) {
$scope.removeAction = function (selectedItems) {
ModalService.confirmDeletion('Do you want to remove the selected stack(s)? Associated services will be removed as well.', function onConfirm(confirmed) {
confirmDelete('Do you want to remove the selected stack(s)? Associated services will be removed as well.').then((confirmed) => {
if (!confirmed) {
return;
}

View file

@ -1,14 +1,17 @@
import { ModalType } from '@@/modals';
import { buildConfirmButton } from '@@/modals/utils';
import { confirm, confirmChangePassword, confirmDelete } from '@@/modals/confirm';
angular.module('portainer.app').controller('UserController', [
'$q',
'$scope',
'$state',
'$transition$',
'UserService',
'ModalService',
'Notifications',
'SettingsService',
'Authentication',
function ($q, $scope, $state, $transition$, UserService, ModalService, Notifications, SettingsService, Authentication) {
function ($q, $scope, $state, $transition$, UserService, Notifications, SettingsService, Authentication) {
$scope.state = {
updatePasswordError: '',
};
@ -27,7 +30,7 @@ angular.module('portainer.app').controller('UserController', [
};
$scope.deleteUser = function () {
ModalService.confirmDeletion('Do you want to remove this user? This user will not be able to login into Portainer anymore.', function onConfirm(confirmed) {
confirmDelete('Do you want to remove this user? This user will not be able to login into Portainer anymore.').then((confirmed) => {
if (!confirmed) {
return;
}
@ -39,26 +42,20 @@ angular.module('portainer.app').controller('UserController', [
const role = $scope.formValues.Administrator ? 1 : 2;
const oldUsername = $scope.user.Username;
const username = $scope.formValues.username;
let promise = Promise.resolve(true);
if (username != oldUsername) {
promise = new Promise((resolve) =>
ModalService.confirmWarn({
title: 'Are you sure?',
message: `Are you sure you want to rename the user ${oldUsername} to ${username}?`,
buttons: {
confirm: {
label: 'Update',
className: 'btn-primary',
},
},
callback: resolve,
})
);
}
const confirmed = await promise;
if (!confirmed) {
return;
const confirmed = await confirm({
title: 'Are you sure?',
modalType: ModalType.Warn,
message: `Are you sure you want to rename the user ${oldUsername} to ${username}?`,
confirmButton: buildConfirmButton('Update'),
});
if (!confirmed) {
return;
}
}
UserService.updateUser($scope.user.Id, { role, username })
.then(function success() {
Notifications.success('Success', 'User successfully updated');
@ -71,7 +68,7 @@ angular.module('portainer.app').controller('UserController', [
$scope.updatePassword = async function () {
const isCurrentUser = Authentication.getUserDetails().ID === $scope.user.Id;
const confirmed = !isCurrentUser || (await ModalService.confirmChangePassword());
const confirmed = !isCurrentUser || (await confirmChangePassword());
if (!confirmed) {
return;
}

View file

@ -1,4 +1,5 @@
import _ from 'lodash-es';
import { confirmDelete } from '@@/modals/confirm';
angular.module('portainer.app').controller('UsersController', [
'$q',
@ -7,11 +8,10 @@ angular.module('portainer.app').controller('UsersController', [
'UserService',
'TeamService',
'TeamMembershipService',
'ModalService',
'Notifications',
'Authentication',
'SettingsService',
function ($q, $scope, $state, UserService, TeamService, TeamMembershipService, ModalService, Notifications, Authentication, SettingsService) {
function ($q, $scope, $state, UserService, TeamService, TeamMembershipService, Notifications, Authentication, SettingsService) {
$scope.state = {
userCreationError: '',
validUsername: false,
@ -91,7 +91,7 @@ angular.module('portainer.app').controller('UsersController', [
}
$scope.removeAction = function (selectedItems) {
ModalService.confirmDeletion('Do you want to remove the selected users? They will not be able to login into Portainer anymore.', function onConfirm(confirmed) {
confirmDelete('Do you want to remove the selected users? They will not be able to login into Portainer anymore.').then((confirmed) => {
if (!confirmed) {
return;
}