refactor(ui/modals): replace bootbox with react solution [EE-4541] (#8010)
parent
392c7f74b8
commit
e66dea44e3
|
@ -1,11 +1,12 @@
|
|||
import _ from 'lodash-es';
|
||||
import { confirmDelete } from '@@/modals/confirm';
|
||||
|
||||
const ROOT_PATH = '/host';
|
||||
|
||||
export class HostBrowserController {
|
||||
/* @ngInject */
|
||||
constructor($async, HostBrowserService, Notifications, FileSaver, ModalService) {
|
||||
Object.assign(this, { $async, HostBrowserService, Notifications, FileSaver, ModalService });
|
||||
constructor($async, HostBrowserService, Notifications, FileSaver) {
|
||||
Object.assign(this, { $async, HostBrowserService, Notifications, FileSaver });
|
||||
|
||||
this.state = {
|
||||
path: ROOT_PATH,
|
||||
|
@ -95,7 +96,7 @@ export class HostBrowserController {
|
|||
confirmDeleteFile(name) {
|
||||
const filePath = this.buildPath(this.state.path, name);
|
||||
|
||||
this.ModalService.confirmDeletion(`Are you sure that you want to delete ${this.getRelativePath(filePath)} ?`, (confirmed) => {
|
||||
confirmDelete(`Are you sure that you want to delete ${this.getRelativePath(filePath)}?`).then((confirmed) => {
|
||||
if (!confirmed) {
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
import _ from 'lodash-es';
|
||||
import { confirmDelete } from '@@/modals/confirm';
|
||||
|
||||
export class VolumeBrowserController {
|
||||
/* @ngInject */
|
||||
constructor($async, HttpRequestHelper, VolumeBrowserService, FileSaver, Blob, ModalService, Notifications) {
|
||||
Object.assign(this, { $async, HttpRequestHelper, VolumeBrowserService, FileSaver, Blob, ModalService, Notifications });
|
||||
constructor($async, HttpRequestHelper, VolumeBrowserService, FileSaver, Blob, Notifications) {
|
||||
Object.assign(this, { $async, HttpRequestHelper, VolumeBrowserService, FileSaver, Blob, Notifications });
|
||||
this.state = {
|
||||
path: '/',
|
||||
};
|
||||
|
@ -47,7 +48,7 @@ export class VolumeBrowserController {
|
|||
confirmDelete(file) {
|
||||
const filePath = this.state.path === '/' ? file : `${this.state.path}/${file}`;
|
||||
|
||||
this.ModalService.confirmDeletion(`Are you sure that you want to delete ${filePath} ?`, (confirmed) => {
|
||||
confirmDelete(`Are you sure that you want to delete ${filePath} ?`).then((confirmed) => {
|
||||
if (!confirmed) {
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -401,34 +401,6 @@ input[type='checkbox'] {
|
|||
float: none !important;
|
||||
}
|
||||
|
||||
.bootbox-form .bootbox-input-checkbox {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.bootbox-form .visible {
|
||||
position: initial !important;
|
||||
display: initial !important;
|
||||
margin-left: 10px !important;
|
||||
margin-top: -2px !important;
|
||||
}
|
||||
|
||||
.bootbox-form label {
|
||||
padding-left: 0;
|
||||
}
|
||||
|
||||
.bootbox-checkbox-list {
|
||||
max-height: 200px;
|
||||
overflow-y: auto;
|
||||
background-color: var(--white-color);
|
||||
border: 1px solid var(--grey-48);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
:root[theme='dark'] .bootbox-checkbox-list,
|
||||
:root[theme='highcontrast'] .bootbox-checkbox-list {
|
||||
background-color: var(--bg-modal-content-color);
|
||||
}
|
||||
|
||||
.small-select {
|
||||
display: inline-block;
|
||||
padding: 0px 6px;
|
||||
|
@ -440,10 +412,6 @@ input[type='checkbox'] {
|
|||
font-size: 14px;
|
||||
}
|
||||
|
||||
.bootbox-form .checkbox i {
|
||||
margin-left: 21px;
|
||||
}
|
||||
|
||||
.visualizer_container {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
|
@ -584,20 +552,6 @@ input[type='checkbox'] {
|
|||
padding: 0 !important;
|
||||
}
|
||||
|
||||
.modal::before {
|
||||
content: '';
|
||||
display: inline-block;
|
||||
height: 100%;
|
||||
vertical-align: middle;
|
||||
margin-right: -4px;
|
||||
}
|
||||
|
||||
.modal-dialog {
|
||||
display: inline-block;
|
||||
text-align: left;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.striked::after {
|
||||
border-bottom: 0.2em solid var(--grey-26);
|
||||
content: '';
|
||||
|
@ -630,17 +584,6 @@ input[type='checkbox'] {
|
|||
margin-right: -50%;
|
||||
}
|
||||
|
||||
/*bootbox override */
|
||||
.modal-open {
|
||||
padding-right: 0 !important;
|
||||
}
|
||||
|
||||
.image-zoom-modal .modal-dialog {
|
||||
width: 80%;
|
||||
}
|
||||
|
||||
/*!bootbox override*/
|
||||
|
||||
/*toaster override*/
|
||||
#toast-container > div {
|
||||
opacity: 0.9;
|
||||
|
@ -732,20 +675,6 @@ json-tree .branch-preview {
|
|||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
/* used for bootbox prompt with inputType radio */
|
||||
.form-check.radio {
|
||||
margin-left: 15px;
|
||||
}
|
||||
|
||||
.inline-text {
|
||||
display: inline;
|
||||
position: absolute;
|
||||
font-family: 'Montserrat';
|
||||
font-size: smaller;
|
||||
margin-left: 5px;
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
.web-editor {
|
||||
background-color: var(--bg-webeditor-color);
|
||||
border-radius: 8px;
|
||||
|
|
|
@ -284,65 +284,7 @@ input:checked + .slider:before {
|
|||
width: 450px;
|
||||
}
|
||||
|
||||
.modal-content {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.background-error {
|
||||
padding-top: 55px;
|
||||
background-image: url(../images/icon-error.svg);
|
||||
background-repeat: no-repeat;
|
||||
background-position: top left;
|
||||
}
|
||||
|
||||
.background-warning {
|
||||
padding-top: 55px;
|
||||
background-image: url(../images/icon-warning.svg);
|
||||
background-repeat: no-repeat;
|
||||
background-position: top left;
|
||||
}
|
||||
|
||||
.modal-header {
|
||||
margin-bottom: 10px;
|
||||
padding: 0px;
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.modal-header .close {
|
||||
margin-top: 0px;
|
||||
}
|
||||
|
||||
.modal-header .modal-title {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.modal-body {
|
||||
padding: 10px 0px;
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.modal-body .bootbox-body {
|
||||
font-size: 12px;
|
||||
color: var(--text-bootbox);
|
||||
}
|
||||
|
||||
.modal-footer {
|
||||
padding: 10px 0px;
|
||||
border-top: none;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.modal-footer .bootbox-cancel {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.modal-footer .bootbox-accept {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.bootbox-checkbox-list {
|
||||
border: 0px;
|
||||
}
|
||||
/* !Modal */
|
||||
|
||||
/* Status Indicator Inside Table Section Label Style */
|
||||
.table .label {
|
||||
|
|
|
@ -17,8 +17,7 @@
|
|||
background-color: var(--bg-hover-table-color);
|
||||
}
|
||||
|
||||
.switch i,
|
||||
.bootbox-form .checkbox i {
|
||||
.switch i {
|
||||
background: var(--bg-switch-box-color);
|
||||
}
|
||||
|
||||
|
@ -77,25 +76,6 @@ fieldset[disabled] .form-control {
|
|||
background-color: var(--bg-modal-content-color);
|
||||
}
|
||||
|
||||
.modal-header {
|
||||
border-bottom: 1px solid var(--border-modal-header-color);
|
||||
}
|
||||
|
||||
.modal-footer {
|
||||
border-top: 1px solid var(--border-modal-header-color);
|
||||
}
|
||||
|
||||
.close {
|
||||
color: var(--button-close-color);
|
||||
opacity: var(--button-opacity);
|
||||
}
|
||||
|
||||
.close:hover,
|
||||
.close:focus {
|
||||
color: var(--button-close-color);
|
||||
opacity: var(--button-opacity-hover);
|
||||
}
|
||||
|
||||
code {
|
||||
color: var(--text-code-color);
|
||||
background-color: var(--bg-code-color);
|
||||
|
|
|
@ -1,13 +1,15 @@
|
|||
import { confirmDelete } from '@@/modals/confirm';
|
||||
import { confirmServiceForceUpdate } from '@/react/docker/services/common/update-service-modal';
|
||||
|
||||
angular.module('portainer.docker').controller('ServicesDatatableActionsController', [
|
||||
'$q',
|
||||
'$state',
|
||||
'ServiceService',
|
||||
'ServiceHelper',
|
||||
'Notifications',
|
||||
'ModalService',
|
||||
'ImageHelper',
|
||||
'WebhookService',
|
||||
function ($q, $state, ServiceService, ServiceHelper, Notifications, ModalService, ImageHelper, WebhookService) {
|
||||
function ($q, $state, ServiceService, ServiceHelper, Notifications, ImageHelper, WebhookService) {
|
||||
const ctrl = this;
|
||||
|
||||
this.scaleAction = function scaleService(service) {
|
||||
|
@ -26,29 +28,22 @@ angular.module('portainer.docker').controller('ServicesDatatableActionsControlle
|
|||
};
|
||||
|
||||
this.removeAction = function (selectedItems) {
|
||||
ModalService.confirmDeletion(
|
||||
'Do you want to remove the selected service(s)? All the containers associated to the selected service(s) will be removed too.',
|
||||
function onConfirm(confirmed) {
|
||||
if (!confirmed) {
|
||||
return;
|
||||
}
|
||||
removeServices(selectedItems);
|
||||
confirmDelete('Do you want to remove the selected service(s)? All the containers associated to the selected service(s) will be removed too.').then((confirmed) => {
|
||||
if (!confirmed) {
|
||||
return;
|
||||
}
|
||||
);
|
||||
removeServices(selectedItems);
|
||||
});
|
||||
};
|
||||
|
||||
this.updateAction = function (selectedItems) {
|
||||
ModalService.confirmServiceForceUpdate(
|
||||
'Do you want to force an update of the selected service(s)? All the tasks associated to the selected service(s) will be recreated.',
|
||||
function (result) {
|
||||
confirmServiceForceUpdate('Do you want to force an update of the selected service(s)? All the tasks associated to the selected service(s) will be recreated.').then(
|
||||
(result) => {
|
||||
if (!result) {
|
||||
return;
|
||||
}
|
||||
var pullImage = false;
|
||||
if (result[0]) {
|
||||
pullImage = true;
|
||||
}
|
||||
forceUpdateServices(selectedItems, pullImage);
|
||||
|
||||
forceUpdateServices(selectedItems, result.pullLatest);
|
||||
}
|
||||
);
|
||||
};
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import angular from 'angular';
|
||||
import { confirmDeletionAsync } from 'Portainer/services/modal.service/confirm';
|
||||
import { confirmDelete } from '@@/modals/confirm';
|
||||
|
||||
class ConfigsController {
|
||||
/* @ngInject */
|
||||
|
@ -33,7 +33,7 @@ class ConfigsController {
|
|||
}
|
||||
|
||||
async removeAction(selectedItems) {
|
||||
const confirmed = await confirmDeletionAsync('Do you want to remove the selected config(s)?');
|
||||
const confirmed = await confirmDelete('Do you want to remove the selected config(s)?');
|
||||
if (!confirmed) {
|
||||
return null;
|
||||
}
|
||||
|
|
|
@ -1,14 +1,14 @@
|
|||
import _ from 'lodash-es';
|
||||
import angular from 'angular';
|
||||
import { AccessControlFormData } from 'Portainer/components/accessControlForm/porAccessControlFormModel';
|
||||
import { confirmWebEditorDiscard } from '@@/modals/confirm';
|
||||
|
||||
class CreateConfigController {
|
||||
/* @ngInject */
|
||||
constructor($async, $state, $transition$, $window, ModalService, Notifications, ConfigService, Authentication, FormValidator, ResourceControlService) {
|
||||
constructor($async, $state, $transition$, $window, Notifications, ConfigService, Authentication, FormValidator, ResourceControlService) {
|
||||
this.$state = $state;
|
||||
this.$transition$ = $transition$;
|
||||
this.$window = $window;
|
||||
this.ModalService = ModalService;
|
||||
this.Notifications = Notifications;
|
||||
this.ConfigService = ConfigService;
|
||||
this.Authentication = Authentication;
|
||||
|
@ -67,7 +67,7 @@ class CreateConfigController {
|
|||
|
||||
async uiCanExit() {
|
||||
if (this.formValues.displayCodeEditor && this.formValues.ConfigContent && this.state.isEditorDirty) {
|
||||
return this.ModalService.confirmWebEditorDiscard();
|
||||
return confirmWebEditorDiscard();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -2,8 +2,11 @@ import _ from 'lodash-es';
|
|||
|
||||
import { PorImageRegistryModel } from 'Docker/models/porImageRegistry';
|
||||
|
||||
import { confirmDestructive } from '@@/modals/confirm';
|
||||
import * as envVarsUtils from '@/portainer/helpers/env-vars';
|
||||
import { FeatureId } from '@/react/portainer/feature-flags/enums';
|
||||
import { buildConfirmButton } from '@@/modals/utils';
|
||||
|
||||
import { ContainerCapabilities, ContainerCapability } from '../../../models/containerCapabilities';
|
||||
import { AccessControlFormData } from '../../../../portainer/components/accessControlForm/porAccessControlFormModel';
|
||||
import { ContainerDetailsViewModel } from '../../../models/container';
|
||||
|
@ -30,7 +33,6 @@ angular.module('portainer.docker').controller('CreateContainerController', [
|
|||
'ContainerService',
|
||||
'ImageService',
|
||||
'FormValidator',
|
||||
'ModalService',
|
||||
'RegistryService',
|
||||
'SystemService',
|
||||
'PluginService',
|
||||
|
@ -56,7 +58,6 @@ angular.module('portainer.docker').controller('CreateContainerController', [
|
|||
ContainerService,
|
||||
ImageService,
|
||||
FormValidator,
|
||||
ModalService,
|
||||
RegistryService,
|
||||
SystemService,
|
||||
PluginService,
|
||||
|
@ -1000,18 +1001,12 @@ angular.module('portainer.docker').controller('CreateContainerController', [
|
|||
function showConfirmationModal() {
|
||||
var deferred = $q.defer();
|
||||
|
||||
ModalService.confirmDestructive({
|
||||
title: 'Are you sure ?',
|
||||
confirmDestructive({
|
||||
title: 'Are you sure?',
|
||||
message: 'A container with the same name already exists. Portainer can automatically remove it and re-create one. Do you want to replace it?',
|
||||
buttons: {
|
||||
confirm: {
|
||||
label: 'Replace',
|
||||
className: 'btn-danger',
|
||||
},
|
||||
},
|
||||
callback: function onConfirm(confirmed) {
|
||||
deferred.resolve(confirmed);
|
||||
},
|
||||
confirmButton: buildConfirmButton('Replace', 'danger'),
|
||||
}).then(function onConfirm(confirmed) {
|
||||
deferred.resolve(confirmed);
|
||||
});
|
||||
|
||||
return deferred.promise;
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
import moment from 'moment';
|
||||
import _ from 'lodash-es';
|
||||
import { PorImageRegistryModel } from 'Docker/models/porImageRegistry';
|
||||
import { confirmContainerDeletion } from '@/portainer/services/modal.service/prompt';
|
||||
import { confirmContainerDeletion } from '@/react/docker/containers/common/confirm-container-delete-modal';
|
||||
import { FeatureId } from '@/react/portainer/feature-flags/enums';
|
||||
import { ResourceControlType } from '@/react/portainer/access-control/types';
|
||||
import { confirmContainerRecreation } from '@/react/docker/containers/ItemView/ConfirmRecreationModal';
|
||||
|
||||
angular.module('portainer.docker').controller('ContainerController', [
|
||||
'$q',
|
||||
|
@ -18,7 +19,6 @@ angular.module('portainer.docker').controller('ContainerController', [
|
|||
'ImageHelper',
|
||||
'NetworkService',
|
||||
'Notifications',
|
||||
'ModalService',
|
||||
'ResourceControlService',
|
||||
'RegistryService',
|
||||
'ImageService',
|
||||
|
@ -38,7 +38,6 @@ angular.module('portainer.docker').controller('ContainerController', [
|
|||
ImageHelper,
|
||||
NetworkService,
|
||||
Notifications,
|
||||
ModalService,
|
||||
ResourceControlService,
|
||||
RegistryService,
|
||||
ImageService,
|
||||
|
@ -275,20 +274,20 @@ angular.module('portainer.docker').controller('ContainerController', [
|
|||
};
|
||||
|
||||
$scope.confirmRemove = function () {
|
||||
var title = 'You are about to remove a container.';
|
||||
if ($scope.container.State.Running) {
|
||||
title = 'You are about to remove a running container.';
|
||||
}
|
||||
return $async(async () => {
|
||||
var title = 'You are about to remove a container.';
|
||||
if ($scope.container.State.Running) {
|
||||
title = 'You are about to remove a running container.';
|
||||
}
|
||||
|
||||
const result = await confirmContainerDeletion(title);
|
||||
|
||||
confirmContainerDeletion(title, function (result) {
|
||||
if (!result) {
|
||||
return;
|
||||
}
|
||||
var cleanAssociatedVolumes = false;
|
||||
if (result[0]) {
|
||||
cleanAssociatedVolumes = true;
|
||||
}
|
||||
removeContainer(cleanAssociatedVolumes);
|
||||
const { removeVolumes } = result;
|
||||
|
||||
removeContainer(removeVolumes);
|
||||
});
|
||||
};
|
||||
|
||||
|
@ -397,15 +396,12 @@ angular.module('portainer.docker').controller('ContainerController', [
|
|||
|
||||
$scope.recreate = function () {
|
||||
const cannotPullImage = !$scope.container.Config.Image || $scope.container.Config.Image.toLowerCase().startsWith('sha256');
|
||||
ModalService.confirmContainerRecreation(cannotPullImage, function (result) {
|
||||
confirmContainerRecreation(cannotPullImage).then(function (result) {
|
||||
if (!result) {
|
||||
return;
|
||||
}
|
||||
var pullImage = false;
|
||||
if (result[0]) {
|
||||
pullImage = true;
|
||||
}
|
||||
recreateContainer(pullImage);
|
||||
|
||||
recreateContainer(result.pullLatest);
|
||||
});
|
||||
};
|
||||
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
import { confirmWebEditorDiscard } from '@@/modals/confirm';
|
||||
import { options } from './options';
|
||||
|
||||
angular.module('portainer.docker').controller('BuildImageController', BuildImageController);
|
||||
|
||||
/* @ngInject */
|
||||
function BuildImageController($scope, $async, $window, ModalService, BuildService, Notifications, HttpRequestHelper, endpoint) {
|
||||
function BuildImageController($scope, $async, $window, BuildService, Notifications, HttpRequestHelper, endpoint) {
|
||||
$scope.endpoint = endpoint;
|
||||
$scope.options = options;
|
||||
|
||||
|
@ -154,7 +155,7 @@ function BuildImageController($scope, $async, $window, ModalService, BuildServic
|
|||
|
||||
this.uiCanExit = async function () {
|
||||
if ($scope.state.BuildType === 'editor' && $scope.formValues.DockerFileContent && $scope.state.isEditorDirty) {
|
||||
return ModalService.confirmWebEditorDiscard();
|
||||
return confirmWebEditorDiscard();
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import _ from 'lodash-es';
|
||||
import { PorImageRegistryModel } from 'Docker/models/porImageRegistry';
|
||||
import { confirmImageExport } from '@/react/docker/images/common/ConfirmExportModal';
|
||||
|
||||
angular.module('portainer.docker').controller('ImageController', [
|
||||
'$async',
|
||||
|
@ -13,11 +14,9 @@ angular.module('portainer.docker').controller('ImageController', [
|
|||
'RegistryService',
|
||||
'Notifications',
|
||||
'HttpRequestHelper',
|
||||
'ModalService',
|
||||
'FileSaver',
|
||||
'Blob',
|
||||
'endpoint',
|
||||
'EndpointService',
|
||||
'RegistryModalService',
|
||||
function (
|
||||
$async,
|
||||
|
@ -31,11 +30,9 @@ angular.module('portainer.docker').controller('ImageController', [
|
|||
RegistryService,
|
||||
Notifications,
|
||||
HttpRequestHelper,
|
||||
ModalService,
|
||||
FileSaver,
|
||||
Blob,
|
||||
endpoint,
|
||||
EndpointService,
|
||||
RegistryModalService
|
||||
) {
|
||||
$scope.endpoint = endpoint;
|
||||
|
@ -90,6 +87,7 @@ angular.module('portainer.docker').controller('ImageController', [
|
|||
return $async(async () => {
|
||||
try {
|
||||
const registryModel = await RegistryModalService.registryModal(repository, $scope.registries);
|
||||
|
||||
if (registryModel) {
|
||||
$('#uploadResourceHint').show();
|
||||
await ImageService.pushImage(registryModel);
|
||||
|
@ -171,7 +169,7 @@ angular.module('portainer.docker').controller('ImageController', [
|
|||
return;
|
||||
}
|
||||
|
||||
ModalService.confirmImageExport(function (confirmed) {
|
||||
confirmImageExport(function (confirmed) {
|
||||
if (!confirmed) {
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -1,5 +1,9 @@
|
|||
import _ from 'lodash-es';
|
||||
import { PorImageRegistryModel } from 'Docker/models/porImageRegistry';
|
||||
import { ModalType } from '@@/modals';
|
||||
import { confirmImageExport } from '@/react/docker/images/common/ConfirmExportModal';
|
||||
import { confirm } from '@@/modals/confirm';
|
||||
import { buildConfirmButton } from '@@/modals/utils';
|
||||
|
||||
angular.module('portainer.docker').controller('ImagesController', [
|
||||
'$scope',
|
||||
|
@ -7,12 +11,11 @@ angular.module('portainer.docker').controller('ImagesController', [
|
|||
'Authentication',
|
||||
'ImageService',
|
||||
'Notifications',
|
||||
'ModalService',
|
||||
'HttpRequestHelper',
|
||||
'FileSaver',
|
||||
'Blob',
|
||||
'endpoint',
|
||||
function ($scope, $state, Authentication, ImageService, Notifications, ModalService, HttpRequestHelper, FileSaver, Blob, endpoint) {
|
||||
function ($scope, $state, Authentication, ImageService, Notifications, HttpRequestHelper, FileSaver, Blob, endpoint) {
|
||||
$scope.endpoint = endpoint;
|
||||
$scope.isAdmin = Authentication.isAdmin();
|
||||
|
||||
|
@ -52,7 +55,7 @@ angular.module('portainer.docker').controller('ImagesController', [
|
|||
};
|
||||
|
||||
$scope.confirmRemovalAction = function (selectedItems, force) {
|
||||
ModalService.confirmImageForceRemoval(function (confirmed) {
|
||||
confirmImageForceRemoval().then((confirmed) => {
|
||||
if (!confirmed) {
|
||||
return;
|
||||
}
|
||||
|
@ -104,7 +107,7 @@ angular.module('portainer.docker').controller('ImagesController', [
|
|||
return;
|
||||
}
|
||||
|
||||
ModalService.confirmImageExport(function (confirmed) {
|
||||
confirmImageExport(function (confirmed) {
|
||||
if (!confirmed) {
|
||||
return;
|
||||
}
|
||||
|
@ -158,3 +161,12 @@ angular.module('portainer.docker').controller('ImagesController', [
|
|||
initView();
|
||||
},
|
||||
]);
|
||||
|
||||
function confirmImageForceRemoval() {
|
||||
return confirm({
|
||||
title: 'Are you sure?',
|
||||
modalType: ModalType.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.',
|
||||
confirmButton: buildConfirmButton('Remote the image', 'danger'),
|
||||
});
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import _ from 'lodash-es';
|
||||
import DockerNetworkHelper from '@/docker/helpers/networkHelper';
|
||||
import { confirmDeletionAsync } from '@/portainer/services/modal.service/confirm';
|
||||
import { confirmDelete } from '@@/modals/confirm';
|
||||
|
||||
angular.module('portainer.docker').controller('NetworksController', [
|
||||
'$q',
|
||||
|
@ -13,7 +13,7 @@ angular.module('portainer.docker').controller('NetworksController', [
|
|||
'AgentService',
|
||||
function ($q, $scope, $state, NetworkService, Notifications, HttpRequestHelper, endpoint, AgentService) {
|
||||
$scope.removeAction = async function (selectedItems) {
|
||||
const confirmed = await confirmDeletionAsync('Do you want to remove the selected network(s)?');
|
||||
const confirmed = await confirmDelete('Do you want to remove the selected network(s)?');
|
||||
if (!confirmed) {
|
||||
return null;
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { confirmDeletionAsync } from 'Portainer/services/modal.service/confirm';
|
||||
import { confirmDelete } from '@@/modals/confirm';
|
||||
angular.module('portainer.docker').controller('SecretsController', [
|
||||
'$scope',
|
||||
'$state',
|
||||
|
@ -6,7 +6,7 @@ angular.module('portainer.docker').controller('SecretsController', [
|
|||
'Notifications',
|
||||
function ($scope, $state, SecretService, Notifications) {
|
||||
$scope.removeAction = async function (selectedItems) {
|
||||
const confirmed = await confirmDeletionAsync('Do you want to remove the selected secret(s)?');
|
||||
const confirmed = await confirmDelete('Do you want to remove the selected secret(s)?');
|
||||
if (!confirmed) {
|
||||
return null;
|
||||
}
|
||||
|
|
|
@ -22,6 +22,10 @@ import _ from 'lodash-es';
|
|||
import { PorImageRegistryModel } from 'Docker/models/porImageRegistry';
|
||||
import * as envVarsUtils from '@/portainer/helpers/env-vars';
|
||||
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';
|
||||
|
||||
angular.module('portainer.docker').controller('ServiceController', [
|
||||
'$q',
|
||||
|
@ -44,7 +48,6 @@ angular.module('portainer.docker').controller('ServiceController', [
|
|||
'ContainerService',
|
||||
'TaskHelper',
|
||||
'Notifications',
|
||||
'ModalService',
|
||||
'PluginService',
|
||||
'Authentication',
|
||||
'VolumeService',
|
||||
|
@ -76,7 +79,6 @@ angular.module('portainer.docker').controller('ServiceController', [
|
|||
ContainerService,
|
||||
TaskHelper,
|
||||
Notifications,
|
||||
ModalService,
|
||||
PluginService,
|
||||
Authentication,
|
||||
VolumeService,
|
||||
|
@ -550,21 +552,16 @@ angular.module('portainer.docker').controller('ServiceController', [
|
|||
}
|
||||
|
||||
$scope.rollbackService = function (service) {
|
||||
ModalService.confirmWarn({
|
||||
confirm({
|
||||
title: 'Rollback service',
|
||||
message: 'Are you sure you want to rollback?',
|
||||
buttons: {
|
||||
confirm: {
|
||||
label: 'Yes',
|
||||
className: 'btn-danger',
|
||||
},
|
||||
},
|
||||
callback: function onConfirm(confirmed) {
|
||||
if (!confirmed) {
|
||||
return;
|
||||
}
|
||||
rollbackService(service);
|
||||
},
|
||||
modalType: ModalType.Warn,
|
||||
confirmButton: buildConfirmButton('Yes', 'danger'),
|
||||
}).then((confirmed) => {
|
||||
if (!confirmed) {
|
||||
return;
|
||||
}
|
||||
rollbackService(service);
|
||||
});
|
||||
};
|
||||
|
||||
|
@ -594,7 +591,7 @@ angular.module('portainer.docker').controller('ServiceController', [
|
|||
};
|
||||
|
||||
$scope.removeService = function () {
|
||||
ModalService.confirmDeletion('Do you want to remove this service? All the containers associated to this service will be removed too.', function onConfirm(confirmed) {
|
||||
confirmDelete('Do you want to remove this service? All the containers associated to this service will be removed too.').then((confirmed) => {
|
||||
if (!confirmed) {
|
||||
return;
|
||||
}
|
||||
|
@ -621,15 +618,12 @@ angular.module('portainer.docker').controller('ServiceController', [
|
|||
}
|
||||
|
||||
$scope.forceUpdateService = function (service) {
|
||||
ModalService.confirmServiceForceUpdate('Do you want to force an update of the service? All the tasks associated to the service will be recreated.', function (result) {
|
||||
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;
|
||||
}
|
||||
var pullImage = false;
|
||||
if (result[0]) {
|
||||
pullImage = true;
|
||||
}
|
||||
forceUpdateService(service, pullImage);
|
||||
|
||||
forceUpdateService(service, result.pullLatest);
|
||||
});
|
||||
};
|
||||
|
||||
|
|
|
@ -1,16 +1,15 @@
|
|||
import { ResourceControlType } from '@/react/portainer/access-control/types';
|
||||
import { confirmDelete } from '@@/modals/confirm';
|
||||
|
||||
angular.module('portainer.docker').controller('VolumeController', [
|
||||
'$scope',
|
||||
'$state',
|
||||
'$transition$',
|
||||
'$q',
|
||||
'ModalService',
|
||||
'VolumeService',
|
||||
'ContainerService',
|
||||
'Notifications',
|
||||
'HttpRequestHelper',
|
||||
function ($scope, $state, $transition$, $q, ModalService, VolumeService, ContainerService, Notifications, HttpRequestHelper) {
|
||||
function ($scope, $state, $transition$, VolumeService, ContainerService, Notifications, HttpRequestHelper) {
|
||||
$scope.resourceType = ResourceControlType.Volume;
|
||||
|
||||
$scope.onUpdateResourceControlSuccess = function () {
|
||||
|
@ -18,7 +17,7 @@ angular.module('portainer.docker').controller('VolumeController', [
|
|||
};
|
||||
|
||||
$scope.removeVolume = function removeVolume() {
|
||||
ModalService.confirmDeletion('Do you want to remove this volume?', (confirmed) => {
|
||||
confirmDelete('Do you want to remove this volume?').then((confirmed) => {
|
||||
if (confirmed) {
|
||||
VolumeService.remove($scope.volume)
|
||||
.then(function success() {
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
import { confirmDelete } from '@@/modals/confirm';
|
||||
|
||||
angular.module('portainer.docker').controller('VolumesController', [
|
||||
'$q',
|
||||
'$scope',
|
||||
|
@ -8,11 +10,10 @@ angular.module('portainer.docker').controller('VolumesController', [
|
|||
'Notifications',
|
||||
'HttpRequestHelper',
|
||||
'Authentication',
|
||||
'ModalService',
|
||||
'endpoint',
|
||||
function ($q, $scope, $state, VolumeService, ServiceService, VolumeHelper, Notifications, HttpRequestHelper, Authentication, ModalService, endpoint) {
|
||||
function ($q, $scope, $state, VolumeService, ServiceService, VolumeHelper, Notifications, HttpRequestHelper, Authentication, endpoint) {
|
||||
$scope.removeAction = function (selectedItems) {
|
||||
ModalService.confirmDeletion('Do you want to remove the selected volume(s)?', (confirmed) => {
|
||||
confirmDelete('Do you want to remove the selected volume(s)?').then((confirmed) => {
|
||||
if (confirmed) {
|
||||
var actionCount = selectedItems.length;
|
||||
angular.forEach(selectedItems, function (volume) {
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
import _ from 'lodash-es';
|
||||
import { confirmDestructiveAsync } from '@/portainer/services/modal.service/confirm';
|
||||
import { confirmDestructive } from '@@/modals/confirm';
|
||||
import { EdgeTypes } from '@/react/portainer/environments/types';
|
||||
import { getEnvironments } from '@/react/portainer/environments/environment.service';
|
||||
import { getTags } from '@/portainer/tags/tags.service';
|
||||
import { notifyError } from '@/portainer/services/notifications';
|
||||
import { buildConfirmButton } from '@@/modals/utils';
|
||||
import { groupTypeOptions } from './group-type-options';
|
||||
import { tagOptions } from './tag-options';
|
||||
|
||||
|
@ -77,19 +78,10 @@ export class EdgeGroupFormController {
|
|||
|
||||
dissociateEndpoint(endpoint) {
|
||||
return this.$async(async () => {
|
||||
const confirmed = await confirmDestructiveAsync({
|
||||
const confirmed = await confirmDestructive({
|
||||
title: 'Confirm action',
|
||||
message: 'Removing the environment from this group will remove its corresponding edge stacks',
|
||||
buttons: {
|
||||
cancel: {
|
||||
label: 'Cancel',
|
||||
className: 'btn-default',
|
||||
},
|
||||
confirm: {
|
||||
label: 'Confirm',
|
||||
className: 'btn-primary',
|
||||
},
|
||||
},
|
||||
confirmButton: buildConfirmButton('Confirm'),
|
||||
});
|
||||
|
||||
if (!confirmed) {
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
import { confirmWebEditorDiscard } from '@@/modals/confirm';
|
||||
|
||||
export class CreateEdgeJobViewController {
|
||||
/* @ngInject */
|
||||
constructor($async, $q, $state, $window, ModalService, EdgeJobService, GroupService, Notifications, TagService) {
|
||||
constructor($async, $q, $state, $window, EdgeJobService, GroupService, Notifications, TagService) {
|
||||
this.state = {
|
||||
actionInProgress: false,
|
||||
isEditorDirty: false,
|
||||
|
@ -20,7 +22,6 @@ export class CreateEdgeJobViewController {
|
|||
this.$q = $q;
|
||||
this.$state = $state;
|
||||
this.$window = $window;
|
||||
this.ModalService = ModalService;
|
||||
this.Notifications = Notifications;
|
||||
this.GroupService = GroupService;
|
||||
this.EdgeJobService = EdgeJobService;
|
||||
|
@ -59,7 +60,7 @@ export class CreateEdgeJobViewController {
|
|||
|
||||
async uiCanExit() {
|
||||
if (this.model.FileContent && this.state.isEditorDirty) {
|
||||
return this.ModalService.confirmWebEditorDiscard();
|
||||
return confirmWebEditorDiscard();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
import _ from 'lodash-es';
|
||||
import { getEnvironments } from '@/react/portainer/environments/environment.service';
|
||||
import { confirmWebEditorDiscard } from '@@/modals/confirm';
|
||||
|
||||
export class EdgeJobController {
|
||||
/* @ngInject */
|
||||
constructor($async, $q, $state, $window, ModalService, EdgeJobService, FileSaver, GroupService, HostBrowserService, Notifications, TagService) {
|
||||
constructor($async, $q, $state, $window, EdgeJobService, FileSaver, GroupService, HostBrowserService, Notifications, TagService) {
|
||||
this.state = {
|
||||
actionInProgress: false,
|
||||
showEditorTab: false,
|
||||
|
@ -14,7 +15,6 @@ export class EdgeJobController {
|
|||
this.$q = $q;
|
||||
this.$state = $state;
|
||||
this.$window = $window;
|
||||
this.ModalService = ModalService;
|
||||
this.EdgeJobService = EdgeJobService;
|
||||
this.FileSaver = FileSaver;
|
||||
this.GroupService = GroupService;
|
||||
|
@ -127,7 +127,7 @@ export class EdgeJobController {
|
|||
|
||||
async uiCanExit() {
|
||||
if (this.edgeJob && this.edgeJob.FileContent !== this.oldFileContent && this.state.isEditorDirty) {
|
||||
return this.ModalService.confirmWebEditorDiscard();
|
||||
return confirmWebEditorDiscard();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
import _ from 'lodash-es';
|
||||
import { confirmDelete } from '@@/modals/confirm';
|
||||
|
||||
export class EdgeJobsViewController {
|
||||
/* @ngInject */
|
||||
constructor($async, $state, EdgeJobService, ModalService, Notifications) {
|
||||
constructor($async, $state, EdgeJobService, Notifications) {
|
||||
this.$async = $async;
|
||||
this.$state = $state;
|
||||
this.EdgeJobService = EdgeJobService;
|
||||
this.ModalService = ModalService;
|
||||
this.Notifications = Notifications;
|
||||
|
||||
this.removeAction = this.removeAction.bind(this);
|
||||
|
@ -15,7 +15,7 @@ export class EdgeJobsViewController {
|
|||
}
|
||||
|
||||
removeAction(selectedItems) {
|
||||
this.ModalService.confirmDeletion('Do you want to remove the selected edge job(s) ?', (confirmed) => {
|
||||
confirmDelete('Do you want to remove the selected edge job(s)?').then((confirmed) => {
|
||||
if (!confirmed) {
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
import { EditorType } from '@/react/edge/edge-stacks/types';
|
||||
import { PortainerEndpointTypes } from '@/portainer/models/endpoint/models';
|
||||
import { getValidEditorTypes } from '@/react/edge/edge-stacks/utils';
|
||||
import { confirmWebEditorDiscard } from '@@/modals/confirm';
|
||||
|
||||
export default class CreateEdgeStackViewController {
|
||||
/* @ngInject */
|
||||
constructor($state, $window, ModalService, EdgeStackService, EdgeGroupService, EdgeTemplateService, Notifications, FormHelper, $async, $scope) {
|
||||
Object.assign(this, { $state, $window, ModalService, EdgeStackService, EdgeGroupService, EdgeTemplateService, Notifications, FormHelper, $async, $scope });
|
||||
constructor($state, $window, EdgeStackService, EdgeGroupService, EdgeTemplateService, Notifications, FormHelper, $async, $scope) {
|
||||
Object.assign(this, { $state, $window, EdgeStackService, EdgeGroupService, EdgeTemplateService, Notifications, FormHelper, $async, $scope });
|
||||
|
||||
this.formValues = {
|
||||
Name: '',
|
||||
|
@ -75,7 +76,7 @@ export default class CreateEdgeStackViewController {
|
|||
|
||||
uiCanExit() {
|
||||
if (this.state.Method === 'editor' && this.formValues.StackFileContent && this.state.isEditorDirty) {
|
||||
return this.ModalService.confirmWebEditorDiscard();
|
||||
return confirmWebEditorDiscard();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
import _ from 'lodash-es';
|
||||
import { getEnvironments } from '@/react/portainer/environments/environment.service';
|
||||
import { confirmWebEditorDiscard } from '@@/modals/confirm';
|
||||
|
||||
export class EditEdgeStackViewController {
|
||||
/* @ngInject */
|
||||
constructor($async, $state, $window, ModalService, EdgeGroupService, EdgeStackService, Notifications) {
|
||||
constructor($async, $state, $window, EdgeGroupService, EdgeStackService, Notifications) {
|
||||
this.$async = $async;
|
||||
this.$state = $state;
|
||||
this.$window = $window;
|
||||
this.ModalService = ModalService;
|
||||
this.EdgeGroupService = EdgeGroupService;
|
||||
this.EdgeStackService = EdgeStackService;
|
||||
this.Notifications = Notifications;
|
||||
|
@ -64,7 +64,7 @@ export class EditEdgeStackViewController {
|
|||
this.formValues.StackFileContent.replace(/(\r\n|\n|\r)/gm, '') !== this.oldFileContent.replace(/(\r\n|\n|\r)/gm, '') &&
|
||||
this.state.isEditorDirty
|
||||
) {
|
||||
return this.ModalService.confirmWebEditorDiscard();
|
||||
return confirmWebEditorDiscard();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
import _ from 'lodash-es';
|
||||
import KubernetesNamespaceHelper from 'Kubernetes/helpers/namespaceHelper';
|
||||
import { confirmWebEditorDiscard } from '@@/modals/confirm';
|
||||
|
||||
export default class HelmTemplatesController {
|
||||
/* @ngInject */
|
||||
constructor($analytics, $async, $state, $window, $anchorScroll, Authentication, HelmService, KubernetesResourcePoolService, Notifications, ModalService) {
|
||||
constructor($analytics, $async, $state, $window, $anchorScroll, Authentication, HelmService, KubernetesResourcePoolService, Notifications) {
|
||||
this.$analytics = $analytics;
|
||||
this.$async = $async;
|
||||
this.$window = $window;
|
||||
|
@ -13,7 +14,6 @@ export default class HelmTemplatesController {
|
|||
this.HelmService = HelmService;
|
||||
this.KubernetesResourcePoolService = KubernetesResourcePoolService;
|
||||
this.Notifications = Notifications;
|
||||
this.ModalService = ModalService;
|
||||
|
||||
this.editorUpdate = this.editorUpdate.bind(this);
|
||||
this.uiCanExit = this.uiCanExit.bind(this);
|
||||
|
@ -42,7 +42,7 @@ export default class HelmTemplatesController {
|
|||
|
||||
async uiCanExit() {
|
||||
if (this.state.isEditorDirty) {
|
||||
return this.ModalService.confirmWebEditorDiscard();
|
||||
return confirmWebEditorDiscard();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -2,11 +2,12 @@ import { AccessControlFormData } from '@/portainer/components/accessControlForm/
|
|||
import { getTemplateVariables, intersectVariables } from '@/react/portainer/custom-templates/components/utils';
|
||||
import { isBE } from '@/react/portainer/feature-flags/feature-flags.service';
|
||||
import { editor, upload } from '@@/BoxSelector/common-options/build-methods';
|
||||
import { confirmWebEditorDiscard } from '@@/modals/confirm';
|
||||
|
||||
class KubeCreateCustomTemplateViewController {
|
||||
/* @ngInject */
|
||||
constructor($async, $state, Authentication, CustomTemplateService, FormValidator, ModalService, Notifications, ResourceControlService) {
|
||||
Object.assign(this, { $async, $state, Authentication, CustomTemplateService, FormValidator, ModalService, Notifications, ResourceControlService });
|
||||
constructor($async, $state, Authentication, CustomTemplateService, FormValidator, Notifications, ResourceControlService) {
|
||||
Object.assign(this, { $async, $state, Authentication, CustomTemplateService, FormValidator, Notifications, ResourceControlService });
|
||||
|
||||
this.methodOptions = [editor, upload];
|
||||
|
||||
|
@ -202,7 +203,7 @@ class KubeCreateCustomTemplateViewController {
|
|||
|
||||
uiCanExit() {
|
||||
if (this.isEditorDirty()) {
|
||||
return this.ModalService.confirmWebEditorDiscard();
|
||||
return confirmWebEditorDiscard();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
import _ from 'lodash-es';
|
||||
import { confirmDelete } from '@@/modals/confirm';
|
||||
|
||||
export default class KubeCustomTemplatesViewController {
|
||||
/* @ngInject */
|
||||
constructor($async, $state, Authentication, CustomTemplateService, FormValidator, ModalService, Notifications) {
|
||||
Object.assign(this, { $async, $state, Authentication, CustomTemplateService, FormValidator, ModalService, Notifications });
|
||||
constructor($async, $state, Authentication, CustomTemplateService, FormValidator, Notifications) {
|
||||
Object.assign(this, { $async, $state, Authentication, CustomTemplateService, FormValidator, Notifications });
|
||||
|
||||
this.state = {
|
||||
selectedTemplate: null,
|
||||
|
@ -55,7 +56,7 @@ export default class KubeCustomTemplatesViewController {
|
|||
|
||||
confirmDelete(templateId) {
|
||||
return this.$async(async () => {
|
||||
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;
|
||||
}
|
||||
|
|
|
@ -2,11 +2,12 @@ import { ResourceControlViewModel } from '@/react/portainer/access-control/model
|
|||
import { AccessControlFormData } from '@/portainer/components/accessControlForm/porAccessControlFormModel';
|
||||
import { isBE } from '@/react/portainer/feature-flags/feature-flags.service';
|
||||
import { getTemplateVariables, intersectVariables } from '@/react/portainer/custom-templates/components/utils';
|
||||
import { confirmWebEditorDiscard } from '@@/modals/confirm';
|
||||
|
||||
class KubeEditCustomTemplateViewController {
|
||||
/* @ngInject */
|
||||
constructor($async, $state, ModalService, Authentication, CustomTemplateService, FormValidator, Notifications, ResourceControlService) {
|
||||
Object.assign(this, { $async, $state, ModalService, Authentication, CustomTemplateService, FormValidator, Notifications, ResourceControlService });
|
||||
constructor($async, $state, Authentication, CustomTemplateService, FormValidator, Notifications, ResourceControlService) {
|
||||
Object.assign(this, { $async, $state, Authentication, CustomTemplateService, FormValidator, Notifications, ResourceControlService });
|
||||
|
||||
this.isTemplateVariablesEnabled = isBE;
|
||||
|
||||
|
@ -160,7 +161,7 @@ class KubeEditCustomTemplateViewController {
|
|||
|
||||
uiCanExit() {
|
||||
if (this.isEditorDirty()) {
|
||||
return this.ModalService.confirmWebEditorDiscard();
|
||||
return confirmWebEditorDiscard();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
import { confirmDelete } from '@@/modals/confirm';
|
||||
import KubernetesNamespaceHelper from 'Kubernetes/helpers/namespaceHelper';
|
||||
|
||||
export default class KubernetesRegistryAccessController {
|
||||
/* @ngInject */
|
||||
constructor($async, $scope, $state, ModalService, EndpointService, Notifications, RegistryService, KubernetesResourcePoolService) {
|
||||
constructor($async, $scope, $state, EndpointService, Notifications, RegistryService, KubernetesResourcePoolService) {
|
||||
this.$async = $async;
|
||||
this.$scope = $scope;
|
||||
this.$state = $state;
|
||||
this.ModalService = ModalService;
|
||||
this.Notifications = Notifications;
|
||||
this.KubernetesResourcePoolService = KubernetesResourcePoolService;
|
||||
this.RegistryService = RegistryService;
|
||||
|
@ -34,7 +34,7 @@ export default class KubernetesRegistryAccessController {
|
|||
|
||||
const displayedMessage =
|
||||
'This registry might be used by one or more applications inside this environment. Removing the registry access could lead to a service interruption for these applications.<br/><br/>Do you wish to continue?';
|
||||
this.ModalService.confirmDeletion(displayedMessage, (confirmed) => {
|
||||
confirmDelete(displayedMessage).then((confirmed) => {
|
||||
if (confirmed) {
|
||||
return this.updateNamespaces(nsToUpdate);
|
||||
}
|
||||
|
|
|
@ -5,9 +5,11 @@ import KubernetesApplicationHelper from 'Kubernetes/helpers/application';
|
|||
import KubernetesConfigurationHelper from 'Kubernetes/helpers/configurationHelper';
|
||||
import { KubernetesApplicationTypes } from 'Kubernetes/models/application/models';
|
||||
import { KubernetesPortainerApplicationStackNameLabel } from 'Kubernetes/models/application/models';
|
||||
import { confirmDelete } from '@@/modals/confirm';
|
||||
|
||||
class KubernetesApplicationsController {
|
||||
/* @ngInject */
|
||||
constructor($async, $state, Notifications, KubernetesApplicationService, HelmService, KubernetesConfigurationService, Authentication, ModalService, LocalStorage, StackService) {
|
||||
constructor($async, $state, Notifications, KubernetesApplicationService, HelmService, KubernetesConfigurationService, Authentication, LocalStorage, StackService) {
|
||||
this.$async = $async;
|
||||
this.$state = $state;
|
||||
this.Notifications = Notifications;
|
||||
|
@ -15,7 +17,6 @@ class KubernetesApplicationsController {
|
|||
this.HelmService = HelmService;
|
||||
this.KubernetesConfigurationService = KubernetesConfigurationService;
|
||||
this.Authentication = Authentication;
|
||||
this.ModalService = ModalService;
|
||||
this.LocalStorage = LocalStorage;
|
||||
this.StackService = StackService;
|
||||
|
||||
|
@ -63,14 +64,11 @@ class KubernetesApplicationsController {
|
|||
}
|
||||
|
||||
removeStacksAction(selectedItems) {
|
||||
this.ModalService.confirmDeletion(
|
||||
'Are you sure that you want to remove the selected stack(s) ? This will remove all the applications associated to the stack(s).',
|
||||
(confirmed) => {
|
||||
if (confirmed) {
|
||||
return this.$async(this.removeStacksActionAsync, selectedItems);
|
||||
}
|
||||
confirmDelete('Are you sure that you want to remove the selected stack(s) ? This will remove all the applications associated to the stack(s).').then((confirmed) => {
|
||||
if (confirmed) {
|
||||
return this.$async(this.removeStacksActionAsync, selectedItems);
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
async removeActionAsync(selectedItems) {
|
||||
|
@ -109,7 +107,7 @@ class KubernetesApplicationsController {
|
|||
}
|
||||
|
||||
removeAction(selectedItems) {
|
||||
this.ModalService.confirmDeletion('Do you want to remove the selected application(s)?', (confirmed) => {
|
||||
confirmDelete('Do you want to remove the selected application(s)?').then((confirmed) => {
|
||||
if (confirmed) {
|
||||
return this.$async(this.removeActionAsync, selectedItems);
|
||||
}
|
||||
|
|
|
@ -33,7 +33,10 @@ import KubernetesVolumeHelper from 'Kubernetes/helpers/volumeHelper';
|
|||
import KubernetesNamespaceHelper from 'Kubernetes/helpers/namespaceHelper';
|
||||
import { KubernetesNodeHelper } from 'Kubernetes/node/helper';
|
||||
import { updateIngress, getIngresses } from '@/react/kubernetes/ingresses/service';
|
||||
import { confirmUpdateAppIngress } from '@/portainer/services/modal.service/prompt';
|
||||
import { confirmUpdateAppIngress } from '@/react/kubernetes/applications/CreateView/UpdateIngressPrompt';
|
||||
import { confirm, confirmUpdate, confirmWebEditorDiscard } from '@@/modals/confirm';
|
||||
import { buildConfirmButton } from '@@/modals/utils';
|
||||
import { ModalType } from '@@/modals';
|
||||
import { placementOptions } from './placementTypes';
|
||||
|
||||
class KubernetesCreateApplicationController {
|
||||
|
@ -46,7 +49,6 @@ class KubernetesCreateApplicationController {
|
|||
$state,
|
||||
Notifications,
|
||||
Authentication,
|
||||
ModalService,
|
||||
KubernetesResourcePoolService,
|
||||
KubernetesApplicationService,
|
||||
KubernetesStackService,
|
||||
|
@ -64,7 +66,6 @@ class KubernetesCreateApplicationController {
|
|||
this.$state = $state;
|
||||
this.Notifications = Notifications;
|
||||
this.Authentication = Authentication;
|
||||
this.ModalService = ModalService;
|
||||
this.KubernetesResourcePoolService = KubernetesResourcePoolService;
|
||||
this.KubernetesApplicationService = KubernetesApplicationService;
|
||||
this.KubernetesStackService = KubernetesStackService;
|
||||
|
@ -187,19 +188,16 @@ class KubernetesCreateApplicationController {
|
|||
async updateApplicationViaWebEditor() {
|
||||
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 overriden and may cause a service interruption. Do you wish to continue?',
|
||||
buttons: {
|
||||
confirm: {
|
||||
label: 'Update',
|
||||
className: 'btn-warning',
|
||||
},
|
||||
},
|
||||
message: 'Any changes to this application will be overridden and may cause a service interruption. Do you wish to continue?',
|
||||
confirmButton: buildConfirmButton('Update', 'warning'),
|
||||
modalType: ModalType.Warn,
|
||||
});
|
||||
if (!confirmed) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.state.updateWebEditorInProgress = true;
|
||||
await this.StackService.updateKubeStack({ EndpointId: this.endpoint.Id, Id: this.application.StackId }, this.stackFileContent, null);
|
||||
this.state.isEditorDirty = false;
|
||||
|
@ -214,7 +212,7 @@ class KubernetesCreateApplicationController {
|
|||
|
||||
async uiCanExit() {
|
||||
if (this.stackFileContent && this.state.isEditorDirty) {
|
||||
return this.ModalService.confirmWebEditorDiscard();
|
||||
return confirmWebEditorDiscard();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1055,11 +1053,11 @@ class KubernetesCreateApplicationController {
|
|||
}
|
||||
}
|
||||
|
||||
async updateApplicationAsync(ingressesToUpdate, rulePlural) {
|
||||
async updateApplicationAsync(ingressesToUpdate) {
|
||||
if (ingressesToUpdate.length) {
|
||||
try {
|
||||
await Promise.all(ingressesToUpdate.map((ing) => updateIngress(this.endpoint.Id, ing)));
|
||||
this.Notifications.success('Success', `Ingress ${rulePlural} successfully updated`);
|
||||
this.Notifications.success('Success', `Ingress ${ingressesToUpdate.length > 1 ? 'rules' : 'rule'} successfully updated`);
|
||||
} catch (error) {
|
||||
this.Notifications.error('Failure', error, 'Unable to update ingress');
|
||||
}
|
||||
|
@ -1081,33 +1079,22 @@ class KubernetesCreateApplicationController {
|
|||
const [ingressesToUpdate, servicePortsToUpdate] = await this.checkIngressesToUpdate();
|
||||
// if there is an ingressesToUpdate, then show a warning modal with asking if they want to update the ingresses
|
||||
if (ingressesToUpdate.length) {
|
||||
const rulePlural = ingressesToUpdate.length > 1 ? 'rules' : 'rule';
|
||||
const noMatchSentence =
|
||||
servicePortsToUpdate.length > 1
|
||||
? `Service ports in this application no longer match the ingress ${rulePlural}.`
|
||||
: `A service port in this application no longer matches the ingress ${rulePlural} which may break ingress rule paths.`;
|
||||
const message = `
|
||||
<ul class="ml-3">
|
||||
<li>Updating the application may cause a service interruption.</li>
|
||||
<li>${noMatchSentence}</li>
|
||||
</ul>
|
||||
`;
|
||||
const inputLabel = `Update ingress ${rulePlural} to match the service port changes`;
|
||||
confirmUpdateAppIngress(`Are you sure?`, message, inputLabel, (value) => {
|
||||
if (value === null) {
|
||||
return;
|
||||
}
|
||||
if (value.length === 0) {
|
||||
return this.$async(this.updateApplicationAsync, [], '');
|
||||
}
|
||||
if (value[0] === '1') {
|
||||
return this.$async(this.updateApplicationAsync, ingressesToUpdate, rulePlural);
|
||||
}
|
||||
});
|
||||
const result = await confirmUpdateAppIngress(ingressesToUpdate, servicePortsToUpdate);
|
||||
if (!result) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { noMatch } = result;
|
||||
if (!noMatch) {
|
||||
return this.$async(this.updateApplicationAsync, []);
|
||||
}
|
||||
if (noMatch) {
|
||||
return this.$async(this.updateApplicationAsync, ingressesToUpdate);
|
||||
}
|
||||
} else {
|
||||
this.ModalService.confirmUpdate('Updating the application may cause a service interruption. Do you wish to continue?', (confirmed) => {
|
||||
confirmUpdate('Updating the application may cause a service interruption. Do you wish to continue?', (confirmed) => {
|
||||
if (confirmed) {
|
||||
return this.$async(this.updateApplicationAsync, [], '');
|
||||
return this.$async(this.updateApplicationAsync, []);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
@ -15,6 +15,9 @@ import { KubernetesServiceTypes } from 'Kubernetes/models/service/models';
|
|||
import { KubernetesPodNodeAffinityNodeSelectorRequirementOperators } from 'Kubernetes/pod/models';
|
||||
import { KubernetesPodContainerTypes } from 'Kubernetes/pod/models/index';
|
||||
import KubernetesNamespaceHelper from 'Kubernetes/helpers/namespaceHelper';
|
||||
import { confirmUpdate, confirm } from '@@/modals/confirm';
|
||||
import { buildConfirmButton } from '@@/modals/utils';
|
||||
import { ModalType } from '@@/modals';
|
||||
|
||||
function computeTolerations(nodes, application) {
|
||||
const pod = application.Pods[0];
|
||||
|
@ -108,7 +111,6 @@ class KubernetesApplicationController {
|
|||
clipboard,
|
||||
Notifications,
|
||||
LocalStorage,
|
||||
ModalService,
|
||||
KubernetesResourcePoolService,
|
||||
KubernetesApplicationService,
|
||||
KubernetesEventService,
|
||||
|
@ -122,7 +124,6 @@ class KubernetesApplicationController {
|
|||
this.clipboard = clipboard;
|
||||
this.Notifications = Notifications;
|
||||
this.LocalStorage = LocalStorage;
|
||||
this.ModalService = ModalService;
|
||||
this.KubernetesResourcePoolService = KubernetesResourcePoolService;
|
||||
this.StackService = StackService;
|
||||
|
||||
|
@ -224,7 +225,7 @@ class KubernetesApplicationController {
|
|||
}
|
||||
|
||||
rollbackApplication() {
|
||||
this.ModalService.confirmUpdate('Rolling back the application to a previous configuration may cause service interruption. Do you wish to continue?', (confirmed) => {
|
||||
confirmUpdate('Rolling back the application to a previous configuration may cause service interruption. Do you wish to continue?', (confirmed) => {
|
||||
if (confirmed) {
|
||||
return this.$async(this.rollbackApplicationAsync);
|
||||
}
|
||||
|
@ -234,10 +235,11 @@ class KubernetesApplicationController {
|
|||
* REDEPLOY
|
||||
*/
|
||||
async redeployApplicationAsync() {
|
||||
const confirmed = await this.ModalService.confirmAsync({
|
||||
const confirmed = await confirm({
|
||||
modalType: ModalType.Warn,
|
||||
title: 'Are you sure?',
|
||||
message: 'Redeploying the application may cause a service interruption. Do you wish to continue?',
|
||||
buttons: { confirm: { label: 'Redeploy', className: 'btn-primary' } },
|
||||
confirmButton: buildConfirmButton('Redeploy'),
|
||||
});
|
||||
if (!confirmed) {
|
||||
return;
|
||||
|
|
|
@ -8,6 +8,7 @@ import { KubernetesNodeLabelFormValues, KubernetesNodeTaintFormValues } from 'Ku
|
|||
import { KubernetesNodeTaintEffects, KubernetesNodeAvailabilities } from 'Kubernetes/node/models';
|
||||
import KubernetesFormValidationHelper from 'Kubernetes/helpers/formValidationHelper';
|
||||
import { KubernetesNodeHelper } from 'Kubernetes/node/helper';
|
||||
import { confirmUpdate } from '@@/modals/confirm';
|
||||
|
||||
class KubernetesNodeController {
|
||||
/* @ngInject */
|
||||
|
@ -16,7 +17,6 @@ class KubernetesNodeController {
|
|||
$state,
|
||||
Notifications,
|
||||
LocalStorage,
|
||||
ModalService,
|
||||
KubernetesNodeService,
|
||||
KubernetesEventService,
|
||||
KubernetesPodService,
|
||||
|
@ -29,7 +29,6 @@ class KubernetesNodeController {
|
|||
this.$state = $state;
|
||||
this.Notifications = Notifications;
|
||||
this.LocalStorage = LocalStorage;
|
||||
this.ModalService = ModalService;
|
||||
this.KubernetesNodeService = KubernetesNodeService;
|
||||
this.KubernetesEventService = KubernetesEventService;
|
||||
this.KubernetesPodService = KubernetesPodService;
|
||||
|
@ -266,7 +265,7 @@ class KubernetesNodeController {
|
|||
const drainWarning = this.computeDrainWarning();
|
||||
|
||||
if (taintsWarning && !labelsWarning) {
|
||||
this.ModalService.confirmUpdate(
|
||||
confirmUpdate(
|
||||
'Changes to taints will immediately deschedule applications running on this node without the corresponding tolerations. Do you wish to continue?',
|
||||
(confirmed) => {
|
||||
if (confirmed) {
|
||||
|
@ -275,7 +274,7 @@ class KubernetesNodeController {
|
|||
}
|
||||
);
|
||||
} else if (!taintsWarning && labelsWarning) {
|
||||
this.ModalService.confirmUpdate(
|
||||
confirmUpdate(
|
||||
'Removing or changing a label that is used might prevent applications from being scheduled on this node in the future. Do you wish to continue?',
|
||||
(confirmed) => {
|
||||
if (confirmed) {
|
||||
|
@ -284,7 +283,7 @@ class KubernetesNodeController {
|
|||
}
|
||||
);
|
||||
} else if (taintsWarning && labelsWarning) {
|
||||
this.ModalService.confirmUpdate(
|
||||
confirmUpdate(
|
||||
'Changes to taints will immediately deschedule applications running on this node without the corresponding tolerations.<br/></br/>Removing or changing a label that is used might prevent applications from scheduling on this node in the future.\n\nDo you wish to continue?',
|
||||
(confirmed) => {
|
||||
if (confirmed) {
|
||||
|
@ -293,7 +292,7 @@ class KubernetesNodeController {
|
|||
}
|
||||
);
|
||||
} else if (cordonWarning) {
|
||||
this.ModalService.confirmUpdate(
|
||||
confirmUpdate(
|
||||
'Marking this node as unschedulable will effectively cordon the node and prevent any new workload from being scheduled on that node. Are you sure?',
|
||||
(confirmed) => {
|
||||
if (confirmed) {
|
||||
|
@ -302,14 +301,11 @@ class KubernetesNodeController {
|
|||
}
|
||||
);
|
||||
} else if (drainWarning) {
|
||||
this.ModalService.confirmUpdate(
|
||||
'Draining this node will cause all workloads to be evicted from that node. This might lead to some service interruption. Are you sure?',
|
||||
(confirmed) => {
|
||||
if (confirmed) {
|
||||
return this.$async(this.updateNodeAsync);
|
||||
}
|
||||
confirmUpdate('Draining this node will cause all workloads to be evicted from that node. This might lead to some service interruption. Are you sure?', (confirmed) => {
|
||||
if (confirmed) {
|
||||
return this.$async(this.updateNodeAsync);
|
||||
}
|
||||
);
|
||||
});
|
||||
} else {
|
||||
return this.$async(this.updateNodeAsync);
|
||||
}
|
||||
|
|
|
@ -1,16 +1,16 @@
|
|||
import angular from 'angular';
|
||||
import { confirmDelete } from '@@/modals/confirm';
|
||||
import KubernetesConfigurationHelper from 'Kubernetes/helpers/configurationHelper';
|
||||
|
||||
class KubernetesConfigurationsController {
|
||||
/* @ngInject */
|
||||
constructor($async, $state, Notifications, Authentication, KubernetesConfigurationService, KubernetesApplicationService, ModalService) {
|
||||
constructor($async, $state, Notifications, Authentication, KubernetesConfigurationService, KubernetesApplicationService) {
|
||||
this.$async = $async;
|
||||
this.$state = $state;
|
||||
this.Notifications = Notifications;
|
||||
this.Authentication = Authentication;
|
||||
this.KubernetesConfigurationService = KubernetesConfigurationService;
|
||||
this.KubernetesApplicationService = KubernetesApplicationService;
|
||||
this.ModalService = ModalService;
|
||||
|
||||
this.onInit = this.onInit.bind(this);
|
||||
this.getConfigurations = this.getConfigurations.bind(this);
|
||||
|
@ -58,7 +58,7 @@ class KubernetesConfigurationsController {
|
|||
}
|
||||
|
||||
removeAction(selectedItems) {
|
||||
this.ModalService.confirmDeletion('Do you want to remove the selected configuration(s)?', (confirmed) => {
|
||||
confirmDelete('Do you want to remove the selected configuration(s)?').then((confirmed) => {
|
||||
if (confirmed) {
|
||||
return this.$async(this.removeActionAsync, selectedItems);
|
||||
}
|
||||
|
|
|
@ -6,18 +6,18 @@ import KubernetesConfigurationHelper from 'Kubernetes/helpers/configurationHelpe
|
|||
import KubernetesNamespaceHelper from 'Kubernetes/helpers/namespaceHelper';
|
||||
import { getServiceAccounts } from 'Kubernetes/rest/serviceAccount';
|
||||
|
||||
import { confirmWebEditorDiscard } from '@@/modals/confirm';
|
||||
import { isConfigurationFormValid } from '../validation';
|
||||
import { typeOptions } from './options';
|
||||
|
||||
class KubernetesCreateConfigurationController {
|
||||
/* @ngInject */
|
||||
constructor($async, $state, $scope, $window, ModalService, Notifications, Authentication, KubernetesConfigurationService, KubernetesResourcePoolService, EndpointProvider) {
|
||||
constructor($async, $state, $scope, $window, Notifications, Authentication, KubernetesConfigurationService, KubernetesResourcePoolService, EndpointProvider) {
|
||||
this.$async = $async;
|
||||
this.$state = $state;
|
||||
this.$scope = $scope;
|
||||
this.$window = $window;
|
||||
this.EndpointProvider = EndpointProvider;
|
||||
this.ModalService = ModalService;
|
||||
this.Notifications = Notifications;
|
||||
this.Authentication = Authentication;
|
||||
this.KubernetesConfigurationService = KubernetesConfigurationService;
|
||||
|
@ -176,7 +176,7 @@ class KubernetesCreateConfigurationController {
|
|||
|
||||
async uiCanExit() {
|
||||
if (!this.formValues.IsSimple && this.formValues.DataYaml && this.state.isEditorDirty) {
|
||||
return this.ModalService.confirmWebEditorDiscard();
|
||||
return confirmWebEditorDiscard();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -8,6 +8,7 @@ import KubernetesConfigurationConverter from 'Kubernetes/converters/configuratio
|
|||
import KubernetesEventHelper from 'Kubernetes/helpers/eventHelper';
|
||||
import KubernetesNamespaceHelper from 'Kubernetes/helpers/namespaceHelper';
|
||||
|
||||
import { confirmUpdate, confirmWebEditorDiscard } from '@@/modals/confirm';
|
||||
import { isConfigurationFormValid } from '../validation';
|
||||
|
||||
class KubernetesConfigurationController {
|
||||
|
@ -23,7 +24,6 @@ class KubernetesConfigurationController {
|
|||
KubernetesConfigMapService,
|
||||
KubernetesSecretService,
|
||||
KubernetesResourcePoolService,
|
||||
ModalService,
|
||||
KubernetesApplicationService,
|
||||
KubernetesEventService
|
||||
) {
|
||||
|
@ -33,7 +33,6 @@ class KubernetesConfigurationController {
|
|||
this.clipboard = clipboard;
|
||||
this.Notifications = Notifications;
|
||||
this.LocalStorage = LocalStorage;
|
||||
this.ModalService = ModalService;
|
||||
this.KubernetesConfigurationService = KubernetesConfigurationService;
|
||||
this.KubernetesResourcePoolService = KubernetesResourcePoolService;
|
||||
this.KubernetesApplicationService = KubernetesApplicationService;
|
||||
|
@ -121,7 +120,7 @@ class KubernetesConfigurationController {
|
|||
updateConfiguration() {
|
||||
if (this.configuration.Used) {
|
||||
const plural = this.configuration.Applications.length > 1 ? 's' : '';
|
||||
this.ModalService.confirmUpdate(
|
||||
confirmUpdate(
|
||||
`The changes will be propagated to ${this.configuration.Applications.length} running application${plural}. Are you sure you want to update this configuration?`,
|
||||
(confirmed) => {
|
||||
if (confirmed) {
|
||||
|
@ -240,7 +239,7 @@ class KubernetesConfigurationController {
|
|||
|
||||
async uiCanExit() {
|
||||
if (!this.formValues.IsSimple && this.formValues.DataYaml !== this.oldDataYaml && this.state.isEditorDirty) {
|
||||
return this.ModalService.confirmWebEditorDiscard();
|
||||
return confirmWebEditorDiscard();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -8,6 +8,8 @@ import { FeatureId } from '@/react/portainer/feature-flags/enums';
|
|||
|
||||
import { getIngressControllerClassMap, updateIngressControllerClassMap } from '@/react/kubernetes/cluster/ingressClass/utils';
|
||||
import { getIsRBACEnabled } from '@/react/kubernetes/cluster/service';
|
||||
import { buildConfirmButton } from '@@/modals/utils';
|
||||
import { confirm } from '@@/modals/confirm';
|
||||
|
||||
class KubernetesConfigureController {
|
||||
/* #region CONSTRUCTOR */
|
||||
|
@ -21,7 +23,6 @@ class KubernetesConfigureController {
|
|||
KubernetesStorageService,
|
||||
EndpointService,
|
||||
EndpointProvider,
|
||||
ModalService,
|
||||
KubernetesResourcePoolService,
|
||||
KubernetesIngressService,
|
||||
KubernetesMetricsService
|
||||
|
@ -33,7 +34,6 @@ class KubernetesConfigureController {
|
|||
this.KubernetesStorageService = KubernetesStorageService;
|
||||
this.EndpointService = EndpointService;
|
||||
this.EndpointProvider = EndpointProvider;
|
||||
this.ModalService = ModalService;
|
||||
this.KubernetesResourcePoolService = KubernetesResourcePoolService;
|
||||
this.KubernetesIngressService = KubernetesIngressService;
|
||||
this.KubernetesMetricsService = KubernetesMetricsService;
|
||||
|
@ -386,15 +386,10 @@ class KubernetesConfigureController {
|
|||
|
||||
uiCanExit() {
|
||||
if (!this.state.isSaving && (this.areControllersChanged() || this.areFormValuesChanged() || this.areStorageClassesChanged()) && !this.isIngressControllersLoading) {
|
||||
return this.ModalService.confirmAsync({
|
||||
return confirm({
|
||||
title: 'Are you sure?',
|
||||
message: 'You currently have unsaved changes in the cluster setup view. Are you sure you want to leave?',
|
||||
buttons: {
|
||||
confirm: {
|
||||
label: 'Yes',
|
||||
className: 'btn-danger',
|
||||
},
|
||||
},
|
||||
confirmButton: buildConfirmButton('Yes', 'danger'),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,15 +9,15 @@ import { renderTemplate } from '@/react/portainer/custom-templates/components/ut
|
|||
import { isBE } from '@/react/portainer/feature-flags/feature-flags.service';
|
||||
import { kubernetes } from '@@/BoxSelector/common-options/deployment-methods';
|
||||
import { editor, git, customTemplate, url } from '@@/BoxSelector/common-options/build-methods';
|
||||
import { confirmWebEditorDiscard } from '@@/modals/confirm';
|
||||
|
||||
class KubernetesDeployController {
|
||||
/* @ngInject */
|
||||
constructor($async, $state, $window, Authentication, ModalService, Notifications, KubernetesResourcePoolService, StackService, WebhookHelper, CustomTemplateService) {
|
||||
constructor($async, $state, $window, Authentication, Notifications, KubernetesResourcePoolService, StackService, WebhookHelper, CustomTemplateService) {
|
||||
this.$async = $async;
|
||||
this.$state = $state;
|
||||
this.$window = $window;
|
||||
this.Authentication = Authentication;
|
||||
this.ModalService = ModalService;
|
||||
this.Notifications = Notifications;
|
||||
this.KubernetesResourcePoolService = KubernetesResourcePoolService;
|
||||
this.StackService = StackService;
|
||||
|
@ -321,7 +321,7 @@ class KubernetesDeployController {
|
|||
|
||||
async uiCanExit() {
|
||||
if (this.formValues.EditorContent && this.state.isEditorDirty) {
|
||||
return this.ModalService.confirmWebEditorDiscard();
|
||||
return confirmWebEditorDiscard();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -14,6 +14,7 @@ import KubernetesResourceQuotaConverter from 'Kubernetes/converters/resourceQuot
|
|||
import KubernetesNamespaceHelper from 'Kubernetes/helpers/namespaceHelper';
|
||||
import { FeatureId } from '@/react/portainer/feature-flags/enums';
|
||||
import { updateIngressControllerClassMap, getIngressControllerClassMap } from '@/react/kubernetes/cluster/ingressClass/utils';
|
||||
import { confirmUpdate } from '@@/modals/confirm';
|
||||
|
||||
class KubernetesResourcePoolController {
|
||||
/* #region CONSTRUCTOR */
|
||||
|
@ -26,7 +27,6 @@ class KubernetesResourcePoolController {
|
|||
Notifications,
|
||||
LocalStorage,
|
||||
EndpointService,
|
||||
ModalService,
|
||||
KubernetesNodeService,
|
||||
KubernetesMetricsService,
|
||||
KubernetesResourceQuotaService,
|
||||
|
@ -45,7 +45,6 @@ class KubernetesResourcePoolController {
|
|||
Notifications,
|
||||
LocalStorage,
|
||||
EndpointService,
|
||||
ModalService,
|
||||
KubernetesNodeService,
|
||||
KubernetesMetricsService,
|
||||
KubernetesResourceQuotaService,
|
||||
|
@ -189,7 +188,7 @@ class KubernetesResourcePoolController {
|
|||
${warnings.ingress ? messages.ingress + '<br/><br/>' : ''}
|
||||
${warnings.registries ? messages.registries + '<br/><br/>' : ''}
|
||||
Do you wish to continue?`;
|
||||
this.ModalService.confirmUpdate(displayedMessage, (confirmed) => {
|
||||
confirmUpdate(displayedMessage, (confirmed) => {
|
||||
if (confirmed) {
|
||||
return this.$async(this.updateResourcePoolAsync, this.savedFormValues, this.formValues);
|
||||
}
|
||||
|
@ -205,7 +204,7 @@ class KubernetesResourcePoolController {
|
|||
: 'Marking this namespace as a system namespace will prevent non administrator users from managing it and the resources it contains. Are you sure?';
|
||||
|
||||
return new Promise((resolve) => {
|
||||
this.ModalService.confirmUpdate(message, resolve);
|
||||
confirmUpdate(message, resolve);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -1,12 +1,14 @@
|
|||
import angular from 'angular';
|
||||
import { confirm } from '@@/modals/confirm';
|
||||
import { ModalType } from '@@/modals';
|
||||
import { buildConfirmButton } from '@@/modals/utils';
|
||||
|
||||
class KubernetesResourcePoolsController {
|
||||
/* @ngInject */
|
||||
constructor($async, $state, Notifications, ModalService, KubernetesResourcePoolService, KubernetesNamespaceService) {
|
||||
constructor($async, $state, Notifications, KubernetesResourcePoolService, KubernetesNamespaceService) {
|
||||
this.$async = $async;
|
||||
this.$state = $state;
|
||||
this.Notifications = Notifications;
|
||||
this.ModalService = ModalService;
|
||||
this.KubernetesResourcePoolService = KubernetesResourcePoolService;
|
||||
this.KubernetesNamespaceService = KubernetesNamespaceService;
|
||||
|
||||
|
@ -53,7 +55,13 @@ class KubernetesResourcePoolsController {
|
|||
const message = isTerminatingNS
|
||||
? 'At least one namespace is in a terminating state. For terminating state namespaces, you may continue and force removal, but doing so without having properly cleaned up may lead to unstable and unpredictable behavior. Are you sure you wish to proceed?'
|
||||
: 'Do you want to remove the selected namespace(s)? All the resources associated to the selected namespace(s) will be removed too. Are you sure you wish to proceed?';
|
||||
this.ModalService.confirmWithTitle(isTerminatingNS ? 'Force namespace removal' : 'Are you sure?', message, (confirmed) => {
|
||||
confirm({
|
||||
title: isTerminatingNS ? 'Force namespace removal' : 'Are you sure?',
|
||||
message,
|
||||
confirmButton: buildConfirmButton('Remove', 'danger'),
|
||||
|
||||
modalType: ModalType.Destructive,
|
||||
}).then((confirmed) => {
|
||||
if (confirmed) {
|
||||
return this.$async(this.removeActionAsync, selectedItems);
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@ import KubernetesVolumeHelper from 'Kubernetes/helpers/volumeHelper';
|
|||
import KubernetesEventHelper from 'Kubernetes/helpers/eventHelper';
|
||||
import { KubernetesStorageClassAccessPolicies } from 'Kubernetes/models/storage-class/models';
|
||||
import KubernetesNamespaceHelper from 'Kubernetes/helpers/namespaceHelper';
|
||||
import { confirmRedeploy } from '@/react/kubernetes/volumes/ItemView/ConfirmRedeployModal';
|
||||
|
||||
class KubernetesVolumeController {
|
||||
/* @ngInject */
|
||||
|
@ -17,7 +18,6 @@ class KubernetesVolumeController {
|
|||
KubernetesEventService,
|
||||
KubernetesApplicationService,
|
||||
KubernetesPersistentVolumeClaimService,
|
||||
ModalService,
|
||||
KubernetesPodService
|
||||
) {
|
||||
this.$async = $async;
|
||||
|
@ -29,7 +29,6 @@ class KubernetesVolumeController {
|
|||
this.KubernetesEventService = KubernetesEventService;
|
||||
this.KubernetesApplicationService = KubernetesApplicationService;
|
||||
this.KubernetesPersistentVolumeClaimService = KubernetesPersistentVolumeClaimService;
|
||||
this.ModalService = ModalService;
|
||||
this.KubernetesPodService = KubernetesPodService;
|
||||
|
||||
this.onInit = this.onInit.bind(this);
|
||||
|
@ -104,12 +103,9 @@ class KubernetesVolumeController {
|
|||
|
||||
updateVolume() {
|
||||
if (KubernetesVolumeHelper.isUsed(this.volume)) {
|
||||
this.ModalService.confirmRedeploy(
|
||||
'One or multiple applications are currently using this volume.</br> For the change to be taken into account these applications will need to be redeployed. Do you want us to reschedule it now?',
|
||||
(redeploy) => {
|
||||
return this.$async(this.updateVolumeAsync, redeploy);
|
||||
}
|
||||
);
|
||||
confirmRedeploy().then((redeploy) => {
|
||||
return this.$async(this.updateVolumeAsync, redeploy);
|
||||
});
|
||||
} else {
|
||||
return this.$async(this.updateVolumeAsync, false);
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@ import filesizeParser from 'filesize-parser';
|
|||
import angular from 'angular';
|
||||
import KubernetesVolumeHelper from 'Kubernetes/helpers/volumeHelper';
|
||||
import KubernetesResourceQuotaHelper from 'Kubernetes/helpers/resourceQuotaHelper';
|
||||
import { confirmDelete } from '@@/modals/confirm';
|
||||
|
||||
function buildStorages(storages, volumes) {
|
||||
_.forEach(storages, (s) => {
|
||||
|
@ -21,12 +22,11 @@ function computeSize(volumes) {
|
|||
|
||||
class KubernetesVolumesController {
|
||||
/* @ngInject */
|
||||
constructor($async, $state, Notifications, Authentication, ModalService, LocalStorage, KubernetesStorageService, KubernetesVolumeService, KubernetesApplicationService) {
|
||||
constructor($async, $state, Notifications, Authentication, LocalStorage, KubernetesStorageService, KubernetesVolumeService, KubernetesApplicationService) {
|
||||
this.$async = $async;
|
||||
this.$state = $state;
|
||||
this.Notifications = Notifications;
|
||||
this.Authentication = Authentication;
|
||||
this.ModalService = ModalService;
|
||||
this.LocalStorage = LocalStorage;
|
||||
this.KubernetesStorageService = KubernetesStorageService;
|
||||
this.KubernetesVolumeService = KubernetesVolumeService;
|
||||
|
@ -63,7 +63,7 @@ class KubernetesVolumesController {
|
|||
}
|
||||
|
||||
removeAction(selectedItems) {
|
||||
this.ModalService.confirmDeletion('Do you want to remove the selected volume(s)?', (confirmed) => {
|
||||
confirmDelete('Do you want to remove the selected volume(s)?').then((confirmed) => {
|
||||
if (confirmed) {
|
||||
return this.$async(this.removeActionAsync, selectedItems);
|
||||
}
|
||||
|
|
|
@ -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');
|
||||
|
|
|
@ -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');
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
}
|
|
@ -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,
|
||||
};
|
||||
}
|
|
@ -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');
|
||||
}
|
||||
}
|
|
@ -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',
|
||||
});
|
||||
}
|
|
@ -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"
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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')],
|
||||
});
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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 = {
|
||||
|
|
|
@ -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 = {
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -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'),
|
||||
});
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -3,8 +3,8 @@ import { useStore } from 'zustand';
|
|||
|
||||
import { ContainerGroup } from '@/react/azure/types';
|
||||
import { Authorized } from '@/react/hooks/useUser';
|
||||
import { confirmDeletionAsync } from '@/portainer/services/modal.service/confirm';
|
||||
|
||||
import { confirmDelete } from '@@/modals/confirm';
|
||||
import { Datatable } from '@@/datatables';
|
||||
import { Button } from '@@/buttons';
|
||||
import { Link } from '@@/Link';
|
||||
|
@ -63,7 +63,7 @@ export function ContainersDatatable({ dataset, onRemoveClick }: Props) {
|
|||
);
|
||||
|
||||
async function handleRemoveClick(containerIds: string[]) {
|
||||
const confirmed = await confirmDeletionAsync(
|
||||
const confirmed = await confirmDelete(
|
||||
'Are you sure you want to delete the selected containers?'
|
||||
);
|
||||
if (!confirmed) {
|
||||
|
|
|
@ -22,6 +22,7 @@ type Color =
|
|||
| 'light'
|
||||
| 'dangerlight'
|
||||
| 'warninglight'
|
||||
| 'warning'
|
||||
| 'none';
|
||||
type Size = 'xsmall' | 'small' | 'medium' | 'large';
|
||||
|
||||
|
|
|
@ -67,9 +67,10 @@ export function SingleSelect<TValue = string>({
|
|||
isClearable,
|
||||
bindToBody,
|
||||
}: SingleProps<TValue>) {
|
||||
const selectedValue = value
|
||||
? _.first(findSelectedOptions<TValue>(options, value))
|
||||
: null;
|
||||
const selectedValue =
|
||||
value || (typeof value === 'number' && value === 0)
|
||||
? _.first(findSelectedOptions<TValue>(options, value))
|
||||
: null;
|
||||
|
||||
return (
|
||||
<ReactSelect<Option<TValue>>
|
||||
|
@ -94,7 +95,8 @@ function findSelectedOptions<TValue>(
|
|||
value: TValue | readonly TValue[]
|
||||
) {
|
||||
const valueArr = Array.isArray(value) ? value : [value];
|
||||
return _.compact(
|
||||
|
||||
const values = _.compact(
|
||||
options.flatMap((option) => {
|
||||
if (isGroup(option)) {
|
||||
return option.options.find((option) => valueArr.includes(option.value));
|
||||
|
@ -107,6 +109,8 @@ function findSelectedOptions<TValue>(
|
|||
return null;
|
||||
})
|
||||
);
|
||||
|
||||
return values;
|
||||
}
|
||||
|
||||
export function MultiSelect<TValue = string>({
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
/* switch box */
|
||||
|
||||
.switch,
|
||||
.bootbox-checkbox-list > .checkbox > label {
|
||||
.switch {
|
||||
--switch-size: 24px;
|
||||
}
|
||||
|
||||
|
@ -13,8 +12,7 @@
|
|||
display: none;
|
||||
}
|
||||
|
||||
.switch i,
|
||||
.bootbox-form .checkbox i {
|
||||
.switch i {
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
cursor: pointer;
|
||||
|
@ -27,8 +25,7 @@
|
|||
box-shadow: inset 0 0 1px 1px rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
.switch i:before,
|
||||
.bootbox-form .checkbox i:before {
|
||||
.switch i:before {
|
||||
display: block;
|
||||
content: '';
|
||||
width: var(--switch-size);
|
||||
|
@ -38,8 +35,7 @@
|
|||
box-shadow: 0 0 1px 1px rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
.switch :checked + i,
|
||||
.bootbox-form .checkbox :checked ~ i {
|
||||
.switch :checked + i {
|
||||
padding-right: 0;
|
||||
padding-left: var(--switch-size);
|
||||
-webkit-box-shadow: inset 0 0 1px rgba(0, 0, 0, 0.5), inset 0 0 40px #337ab7;
|
||||
|
|
|
@ -0,0 +1,56 @@
|
|||
import { ReactNode } from 'react';
|
||||
|
||||
import { Button } from '@@/buttons';
|
||||
|
||||
import { ButtonOptions, ModalType } from './types';
|
||||
import { openModal } from './open-modal';
|
||||
import { Modal, OnSubmit } from './Modal';
|
||||
|
||||
export interface DialogOptions<T> {
|
||||
title?: ReactNode;
|
||||
message: ReactNode;
|
||||
modalType?: ModalType;
|
||||
buttons: Array<ButtonOptions<T>>;
|
||||
}
|
||||
|
||||
interface Props<T> extends DialogOptions<T> {
|
||||
onSubmit: OnSubmit<T>;
|
||||
}
|
||||
|
||||
export function Dialog<T>({
|
||||
buttons,
|
||||
message,
|
||||
title,
|
||||
onSubmit,
|
||||
modalType,
|
||||
}: Props<T>) {
|
||||
const ariaLabel = requireString(title) || requireString(message) || 'Dialog';
|
||||
|
||||
return (
|
||||
<Modal onDismiss={() => onSubmit()} aria-label={ariaLabel}>
|
||||
{title && <Modal.Header title={title} modalType={modalType} />}
|
||||
<Modal.Body>{message}</Modal.Body>
|
||||
<Modal.Footer>
|
||||
{buttons.map((button, index) => (
|
||||
<Button
|
||||
onClick={() => onSubmit(button.value)}
|
||||
className={button.className}
|
||||
color={button.color}
|
||||
key={index}
|
||||
size="medium"
|
||||
>
|
||||
{button.label}
|
||||
</Button>
|
||||
))}
|
||||
</Modal.Footer>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
function requireString(value: ReactNode) {
|
||||
return typeof value === 'string' ? value : undefined;
|
||||
}
|
||||
|
||||
export async function openDialog<T>(options: DialogOptions<T>) {
|
||||
return openModal<DialogOptions<T>, T>(Dialog, options);
|
||||
}
|
|
@ -0,0 +1,74 @@
|
|||
import { ReactNode, useState } from 'react';
|
||||
|
||||
import { SwitchField } from '@@/form-components/SwitchField';
|
||||
|
||||
import { ModalType, type ButtonOptions } from './types';
|
||||
import { openModal } from './open-modal';
|
||||
import { OnSubmit } from './Modal/types';
|
||||
import { Dialog } from './Dialog';
|
||||
import { buildCancelButton, buildConfirmButton } from './utils';
|
||||
|
||||
function SwitchPrompt({
|
||||
onSubmit,
|
||||
title,
|
||||
confirmButton = buildConfirmButton('OK'),
|
||||
switchLabel,
|
||||
modalType,
|
||||
message,
|
||||
defaultValue = false,
|
||||
}: {
|
||||
onSubmit: OnSubmit<{ value: boolean }>;
|
||||
title: string;
|
||||
switchLabel: string;
|
||||
confirmButton?: ButtonOptions<true>;
|
||||
modalType?: ModalType;
|
||||
message?: ReactNode;
|
||||
defaultValue?: boolean;
|
||||
}) {
|
||||
const [value, setValue] = useState(defaultValue);
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
modalType={modalType}
|
||||
title={title}
|
||||
message={
|
||||
<>
|
||||
{message && <div className="mb-3">{message}</div>}
|
||||
<SwitchField
|
||||
name="value"
|
||||
label={switchLabel}
|
||||
checked={value}
|
||||
onChange={setValue}
|
||||
/>
|
||||
</>
|
||||
}
|
||||
onSubmit={(confirm) => onSubmit(confirm ? { value } : undefined)}
|
||||
buttons={[buildCancelButton(), confirmButton]}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export async function openSwitchPrompt(
|
||||
title: string,
|
||||
switchLabel: string,
|
||||
{
|
||||
confirmButton,
|
||||
modalType,
|
||||
message,
|
||||
defaultValue,
|
||||
}: {
|
||||
confirmButton?: ButtonOptions<true>;
|
||||
modalType?: ModalType;
|
||||
message?: ReactNode;
|
||||
defaultValue?: boolean;
|
||||
} = {}
|
||||
) {
|
||||
return openModal(SwitchPrompt, {
|
||||
confirmButton,
|
||||
title,
|
||||
switchLabel,
|
||||
modalType,
|
||||
message,
|
||||
defaultValue,
|
||||
});
|
||||
}
|
|
@ -0,0 +1,80 @@
|
|||
import { openDialog, DialogOptions } from './Dialog';
|
||||
import { OnSubmit, ModalType } from './Modal';
|
||||
import { ButtonOptions } from './types';
|
||||
import { buildCancelButton, buildConfirmButton } from './utils';
|
||||
|
||||
export type ConfirmCallback = OnSubmit<boolean>;
|
||||
|
||||
export interface ConfirmOptions
|
||||
extends Omit<DialogOptions<boolean>, 'title' | 'buttons'> {
|
||||
title: string;
|
||||
confirmButton?: ButtonOptions<true>;
|
||||
cancelButtonLabel?: string;
|
||||
}
|
||||
|
||||
export async function openConfirm({
|
||||
confirmButton = buildConfirmButton(),
|
||||
cancelButtonLabel,
|
||||
...options
|
||||
}: ConfirmOptions) {
|
||||
const result = await openDialog({
|
||||
...options,
|
||||
buttons: [buildCancelButton(cancelButtonLabel), confirmButton],
|
||||
});
|
||||
return !!result;
|
||||
}
|
||||
|
||||
export function confirm(options: ConfirmOptions) {
|
||||
return openConfirm(options);
|
||||
}
|
||||
|
||||
export function confirmDestructive(options: Omit<ConfirmOptions, 'modalType'>) {
|
||||
return openConfirm({
|
||||
...options,
|
||||
modalType: ModalType.Destructive,
|
||||
});
|
||||
}
|
||||
|
||||
export function confirmWebEditorDiscard() {
|
||||
return openConfirm({
|
||||
modalType: ModalType.Warn,
|
||||
title: 'Are you sure?',
|
||||
message:
|
||||
'You currently have unsaved changes in the editor. Are you sure you want to leave?',
|
||||
confirmButton: buildConfirmButton('Yes', 'danger'),
|
||||
});
|
||||
}
|
||||
|
||||
export function confirmDelete(message: string) {
|
||||
return confirmDestructive({
|
||||
title: 'Are you sure?',
|
||||
message,
|
||||
confirmButton: buildConfirmButton('Remove', 'danger'),
|
||||
});
|
||||
}
|
||||
|
||||
export async function confirmUpdate(
|
||||
message: string,
|
||||
callback: ConfirmCallback
|
||||
) {
|
||||
const result = await openConfirm({
|
||||
title: 'Are you sure?',
|
||||
modalType: ModalType.Warn,
|
||||
message,
|
||||
confirmButton: buildConfirmButton('Update'),
|
||||
});
|
||||
|
||||
callback(result);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
export function confirmChangePassword() {
|
||||
return openConfirm({
|
||||
modalType: ModalType.Warn,
|
||||
title: 'Are you sure?',
|
||||
message:
|
||||
'You will be logged out after the password change. Do you want to change your password?',
|
||||
confirmButton: buildConfirmButton('Change'),
|
||||
});
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
export { Modal } from './Modal';
|
||||
export { openModal } from './open-modal';
|
||||
export { ModalType } from './types';
|
||||
export { type OnSubmit } from './Modal/types';
|
|
@ -0,0 +1,29 @@
|
|||
import { ComponentType } from 'react';
|
||||
import { render, unmountComponentAtNode } from 'react-dom';
|
||||
|
||||
import '@reach/dialog/styles.css';
|
||||
import { OnSubmit } from './Modal/types';
|
||||
|
||||
let counter = 0;
|
||||
export async function openModal<TProps, TResult>(
|
||||
Modal: ComponentType<{ onSubmit: OnSubmit<TResult> } & TProps>,
|
||||
props: TProps = {} as TProps
|
||||
) {
|
||||
const modal = document.createElement('div');
|
||||
counter += 1;
|
||||
modal.id = `dialog-${counter}`;
|
||||
document.body.appendChild(modal);
|
||||
|
||||
const result = await new Promise<TResult | undefined>((resolve) => {
|
||||
render(
|
||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||
<Modal {...props} onSubmit={(result) => resolve(result)} />,
|
||||
modal
|
||||
);
|
||||
});
|
||||
|
||||
unmountComponentAtNode(modal);
|
||||
document.body.removeChild(modal);
|
||||
|
||||
return result;
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
import { ComponentProps } from 'react';
|
||||
|
||||
import { Button } from '@@/buttons';
|
||||
|
||||
export interface ButtonOptions<TValue = undefined> {
|
||||
label: string;
|
||||
className?: string;
|
||||
color?: ComponentProps<typeof Button>['color'];
|
||||
value?: TValue;
|
||||
}
|
||||
|
||||
export interface ButtonsOptions<T> {
|
||||
confirm: ButtonOptions<T>;
|
||||
cancel?: ButtonOptions<T>;
|
||||
}
|
||||
|
||||
export enum ModalType {
|
||||
Warn = 'warning',
|
||||
Destructive = 'error',
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
import { ComponentProps } from 'react';
|
||||
|
||||
import { Button } from '@@/buttons';
|
||||
|
||||
import { ButtonOptions } from './types';
|
||||
|
||||
export function buildConfirmButton(
|
||||
label = 'Confirm',
|
||||
color: ComponentProps<typeof Button>['color'] = 'primary'
|
||||
): ButtonOptions<true> {
|
||||
return { label, color, value: true };
|
||||
}
|
||||
|
||||
export function buildCancelButton(label = 'Cancel'): ButtonOptions<false> {
|
||||
return {
|
||||
label,
|
||||
color: 'default',
|
||||
value: false,
|
||||
};
|
||||
}
|
|
@ -0,0 +1,62 @@
|
|||
import { useState } from 'react';
|
||||
|
||||
import { Modal, OnSubmit, ModalType, openModal } from '@@/modals';
|
||||
import { Button } from '@@/buttons';
|
||||
import { SwitchField } from '@@/form-components/SwitchField';
|
||||
import { TextTip } from '@@/Tip/TextTip';
|
||||
|
||||
interface Props {
|
||||
onSubmit: OnSubmit<{ pullLatest: boolean }>;
|
||||
|
||||
cannotPullImage: boolean;
|
||||
}
|
||||
|
||||
function ConfirmRecreationModal({ onSubmit, cannotPullImage }: Props) {
|
||||
const [pullLatest, setPullLatest] = useState(false);
|
||||
|
||||
return (
|
||||
<Modal
|
||||
onDismiss={() => onSubmit()}
|
||||
aria-label="confirm recreate container modal"
|
||||
>
|
||||
<Modal.Header title="Are you sure?" modalType={ModalType.Destructive} />
|
||||
|
||||
<Modal.Body>
|
||||
<p>
|
||||
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.
|
||||
</p>
|
||||
<SwitchField
|
||||
name="pullLatest"
|
||||
label="Re-pull image"
|
||||
checked={pullLatest}
|
||||
onChange={setPullLatest}
|
||||
disabled={cannotPullImage}
|
||||
/>
|
||||
{cannotPullImage && (
|
||||
<div className="mt-1 text-sm">
|
||||
<TextTip color="orange">
|
||||
Cannot re-pull as the image is inaccessible - either it no longer
|
||||
exists or the tag or name is no longer correct.
|
||||
</TextTip>
|
||||
</div>
|
||||
)}
|
||||
</Modal.Body>
|
||||
<Modal.Footer>
|
||||
<Button onClick={() => onSubmit()} color="default">
|
||||
Cancel
|
||||
</Button>
|
||||
<Button onClick={() => onSubmit({ pullLatest })} color="danger">
|
||||
Recreate
|
||||
</Button>
|
||||
</Modal.Footer>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
export async function confirmContainerRecreation(cannotPullImage: boolean) {
|
||||
return openModal(ConfirmRecreationModal, {
|
||||
cannotPullImage,
|
||||
});
|
||||
}
|
|
@ -11,7 +11,7 @@ import {
|
|||
|
||||
import * as notifications from '@/portainer/services/notifications';
|
||||
import { useAuthorizations, Authorized } from '@/react/hooks/useUser';
|
||||
import { confirmContainerDeletion } from '@/portainer/services/modal.service/prompt';
|
||||
import { confirmContainerDeletion } from '@/react/docker/containers/common/confirm-container-delete-modal';
|
||||
import { setPortainerAgentTargetHeader } from '@/portainer/services/http-request.helper';
|
||||
import {
|
||||
ContainerId,
|
||||
|
@ -242,7 +242,7 @@ export function ContainersDatatableActions({
|
|||
);
|
||||
}
|
||||
|
||||
function onRemoveClick(selectedItems: DockerContainer[]) {
|
||||
async function onRemoveClick(selectedItems: DockerContainer[]) {
|
||||
const isOneContainerRunning = selectedItems.some(
|
||||
(container) => container.State === 'running'
|
||||
);
|
||||
|
@ -250,14 +250,13 @@ export function ContainersDatatableActions({
|
|||
const runningTitle = isOneContainerRunning ? 'running' : '';
|
||||
const title = `You are about to remove one or more ${runningTitle} containers.`;
|
||||
|
||||
confirmContainerDeletion(title, (result: string[]) => {
|
||||
if (!result) {
|
||||
return;
|
||||
}
|
||||
const cleanVolumes = !!result[0];
|
||||
const result = await confirmContainerDeletion(title);
|
||||
if (!result) {
|
||||
return;
|
||||
}
|
||||
const { removeVolumes } = result;
|
||||
|
||||
removeSelectedContainers(selectedItems, cleanVolumes);
|
||||
});
|
||||
removeSelectedContainers(selectedItems, removeVolumes);
|
||||
}
|
||||
|
||||
async function executeActionOnContainerList(
|
||||
|
|
|
@ -0,0 +1,16 @@
|
|||
import { ModalType } from '@@/modals';
|
||||
import { openSwitchPrompt } from '@@/modals/SwitchPrompt';
|
||||
import { buildConfirmButton } from '@@/modals/utils';
|
||||
|
||||
export async function confirmContainerDeletion(title: string) {
|
||||
const result = await openSwitchPrompt(
|
||||
title,
|
||||
'Automatically remove non-persistent volumes',
|
||||
{
|
||||
confirmButton: buildConfirmButton('Remove', 'danger'),
|
||||
modalType: ModalType.Destructive,
|
||||
}
|
||||
);
|
||||
|
||||
return result ? { removeVolumes: result.value } : undefined;
|
||||
}
|
|
@ -0,0 +1,58 @@
|
|||
import { useState } from 'react';
|
||||
|
||||
import { Registry } from '@/react/portainer/environments/environment.service/registries';
|
||||
|
||||
import { Modal, OnSubmit, openModal } from '@@/modals';
|
||||
import { Button } from '@@/buttons';
|
||||
import { PortainerSelect } from '@@/form-components/PortainerSelect';
|
||||
|
||||
interface Props {
|
||||
registries: Registry[];
|
||||
onSubmit: OnSubmit<Registry['Id']>;
|
||||
defaultValue: Registry['Id'];
|
||||
}
|
||||
|
||||
function RegistrySelectPrompt({ onSubmit, defaultValue, registries }: Props) {
|
||||
const title = 'Which registry do you want to use?';
|
||||
const [registryId, setRegistryId] = useState(defaultValue);
|
||||
const options = registries2Options(registries);
|
||||
|
||||
return (
|
||||
<Modal onDismiss={() => onSubmit()} aria-label={title}>
|
||||
<Modal.Header title={title} />
|
||||
|
||||
<Modal.Body>
|
||||
<PortainerSelect
|
||||
onChange={setRegistryId}
|
||||
value={registryId}
|
||||
options={options}
|
||||
/>
|
||||
</Modal.Body>
|
||||
<Modal.Footer>
|
||||
<Button onClick={() => onSubmit()} color="default">
|
||||
Cancel
|
||||
</Button>
|
||||
<Button onClick={() => onSubmit(registryId)} color="primary">
|
||||
Update
|
||||
</Button>
|
||||
</Modal.Footer>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
export function selectRegistry(
|
||||
registries: Registry[],
|
||||
defaultValue: Registry['Id']
|
||||
) {
|
||||
return openModal(RegistrySelectPrompt, {
|
||||
registries,
|
||||
defaultValue,
|
||||
});
|
||||
}
|
||||
|
||||
function registries2Options(registries: Registry[]) {
|
||||
return registries.map((r) => ({
|
||||
label: r.Name,
|
||||
value: r.Id,
|
||||
}));
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
import { ModalType } from '@@/modals';
|
||||
import { ConfirmCallback, openConfirm } from '@@/modals/confirm';
|
||||
import { buildConfirmButton } from '@@/modals/utils';
|
||||
|
||||
export async function confirmImageExport(callback: ConfirmCallback) {
|
||||
const result = await openConfirm({
|
||||
modalType: ModalType.Warn,
|
||||
title: 'Caution',
|
||||
message:
|
||||
'The export may take several minutes, do not navigate away whilst the export is in progress.',
|
||||
confirmButton: buildConfirmButton('Continue'),
|
||||
});
|
||||
|
||||
callback(result);
|
||||
}
|
|
@ -4,13 +4,13 @@ import { useQueryClient } from 'react-query';
|
|||
import _ from 'lodash';
|
||||
|
||||
import { useEnvironmentId } from '@/react/hooks/useEnvironmentId';
|
||||
import { confirmDeletionAsync } from '@/portainer/services/modal.service/confirm';
|
||||
import { AccessControlPanel } from '@/react/portainer/access-control/AccessControlPanel/AccessControlPanel';
|
||||
import { ResourceControlType } from '@/react/portainer/access-control/types';
|
||||
import { DockerContainer } from '@/react/docker/containers/types';
|
||||
import { ResourceControlViewModel } from '@/react/portainer/access-control/models/ResourceControlViewModel';
|
||||
import { useContainers } from '@/react/docker/containers/queries/containers';
|
||||
|
||||
import { confirmDelete } from '@@/modals/confirm';
|
||||
import { PageHeader } from '@@/PageHeader';
|
||||
|
||||
import { useNetwork, useDeleteNetwork } from '../queries';
|
||||
|
@ -103,7 +103,7 @@ export function ItemView() {
|
|||
|
||||
async function onRemoveNetworkClicked() {
|
||||
const message = 'Do you want to delete the network?';
|
||||
const confirmed = await confirmDeletionAsync(message);
|
||||
const confirmed = await confirmDelete(message);
|
||||
|
||||
if (confirmed) {
|
||||
deleteNetworkMutation.mutate(
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
import { openSwitchPrompt } from '@@/modals/SwitchPrompt';
|
||||
import { ModalType } from '@@/modals';
|
||||
import { buildConfirmButton } from '@@/modals/utils';
|
||||
|
||||
export async function confirmServiceForceUpdate(message: string) {
|
||||
const result = await openSwitchPrompt('Are you sure?', 'Re-pull image', {
|
||||
message,
|
||||
confirmButton: buildConfirmButton('Update'),
|
||||
modalType: ModalType.Warn,
|
||||
});
|
||||
|
||||
return result ? { pullLatest: result.value } : undefined;
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
import { openSwitchPrompt } from '@@/modals/SwitchPrompt';
|
||||
import { ModalType } from '@@/modals';
|
||||
import { buildConfirmButton } from '@@/modals/utils';
|
||||
|
||||
export async function confirmStackUpdate(
|
||||
message: string,
|
||||
defaultValue: boolean
|
||||
) {
|
||||
const result = await openSwitchPrompt(
|
||||
'Are you sure?',
|
||||
'Re-pull image and redeploy',
|
||||
{
|
||||
message,
|
||||
confirmButton: buildConfirmButton('Update'),
|
||||
modalType: ModalType.Warn,
|
||||
defaultValue,
|
||||
}
|
||||
);
|
||||
|
||||
return result ? { pullImage: result.value } : undefined;
|
||||
}
|
|
@ -0,0 +1,64 @@
|
|||
import { useState } from 'react';
|
||||
|
||||
import { Modal, openModal } from '@@/modals';
|
||||
import { Button } from '@@/buttons';
|
||||
import { SwitchField } from '@@/form-components/SwitchField';
|
||||
|
||||
function UpdateIngressPrompt({
|
||||
onSubmit,
|
||||
title,
|
||||
hasOneIngress,
|
||||
hasOnePort,
|
||||
}: {
|
||||
onSubmit: (value?: { noMatch: boolean }) => void;
|
||||
title: string;
|
||||
hasOneIngress: boolean;
|
||||
hasOnePort: boolean;
|
||||
}) {
|
||||
const [value, setValue] = useState(false);
|
||||
|
||||
const rulePlural = !hasOneIngress ? 'rules' : 'rule';
|
||||
const noMatchSentence = !hasOnePort
|
||||
? `Service ports in this application no longer match the ingress ${rulePlural}.`
|
||||
: `A service port in this application no longer matches the ingress ${rulePlural} which may break ingress rule paths.`;
|
||||
const inputLabel = `Update ingress ${rulePlural} to match the service port changes`;
|
||||
|
||||
return (
|
||||
<Modal onDismiss={() => onSubmit()} aria-label={title}>
|
||||
<Modal.Header title={title} />
|
||||
|
||||
<Modal.Body>
|
||||
<ul className="ml-3">
|
||||
<li>Updating the application may cause a service interruption.</li>
|
||||
<li>{noMatchSentence}</li>
|
||||
</ul>
|
||||
|
||||
<SwitchField
|
||||
name="noMatch"
|
||||
label={inputLabel}
|
||||
checked={value}
|
||||
onChange={setValue}
|
||||
/>
|
||||
</Modal.Body>
|
||||
<Modal.Footer>
|
||||
<Button onClick={() => onSubmit({ noMatch: value })} color="primary">
|
||||
Update
|
||||
</Button>
|
||||
</Modal.Footer>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
export function confirmUpdateAppIngress(
|
||||
ingressesToUpdate: Array<unknown>,
|
||||
servicePortsToUpdate: Array<unknown>
|
||||
) {
|
||||
const hasOneIngress = ingressesToUpdate.length === 1;
|
||||
const hasOnePort = servicePortsToUpdate.length === 1;
|
||||
|
||||
return openModal(UpdateIngressPrompt, {
|
||||
title: 'Are you sure?',
|
||||
hasOneIngress,
|
||||
hasOnePort,
|
||||
});
|
||||
}
|
|
@ -2,13 +2,14 @@ import { useEffect, useState } from 'react';
|
|||
import { AlertTriangle, Database } from 'lucide-react';
|
||||
import { useStore } from 'zustand';
|
||||
|
||||
import { confirmWarn } from '@/portainer/services/modal.service/confirm';
|
||||
|
||||
import { confirm } from '@@/modals/confirm';
|
||||
import { ModalType } from '@@/modals';
|
||||
import { Datatable } from '@@/datatables';
|
||||
import { Button, ButtonGroup } from '@@/buttons';
|
||||
import { Icon } from '@@/Icon';
|
||||
import { useSearchBarState } from '@@/datatables/SearchBar';
|
||||
import { createPersistedStore } from '@@/datatables/types';
|
||||
import { buildConfirmButton } from '@@/modals/utils';
|
||||
|
||||
import { IngressControllerClassMap } from '../types';
|
||||
|
||||
|
@ -158,7 +159,7 @@ export function IngressClassDatatable({
|
|||
);
|
||||
}
|
||||
|
||||
function updateIngressControllers(
|
||||
async function updateIngressControllers(
|
||||
selectedRows: IngressControllerClassMap[],
|
||||
ingControllerFormValues: IngressControllerClassMap[],
|
||||
availability: boolean
|
||||
|
@ -194,38 +195,32 @@ export function IngressClassDatatable({
|
|||
);
|
||||
|
||||
if (usedControllersToDisallow.length > 0) {
|
||||
const usedControllerHtmlListItems = usedControllersToDisallow.map(
|
||||
(controller) => `<li>${controller.ClassName}</li>`
|
||||
);
|
||||
const usedControllerHtmlList = `<ul class="ml-6">${usedControllerHtmlListItems.join(
|
||||
''
|
||||
)}</ul>`;
|
||||
confirmWarn({
|
||||
const confirmed = await confirm({
|
||||
title: 'Disallow in-use ingress controllers?',
|
||||
message: `
|
||||
modalType: ModalType.Warn,
|
||||
message: (
|
||||
<div>
|
||||
<p>There are ingress controllers you want to disallow that are in use:</p>
|
||||
${usedControllerHtmlList}
|
||||
<p>No new ingress rules can be created for the disallowed controllers.</p>
|
||||
</div>`,
|
||||
buttons: {
|
||||
cancel: {
|
||||
label: 'Cancel',
|
||||
className: 'btn-default',
|
||||
},
|
||||
confirm: {
|
||||
label: 'Disallow',
|
||||
className: 'btn-warning',
|
||||
},
|
||||
},
|
||||
callback: (confirmed) => {
|
||||
if (confirmed) {
|
||||
setIngControllerFormValues(updatedIngressControllers);
|
||||
onChangeControllers(updatedIngressControllers);
|
||||
}
|
||||
},
|
||||
<p>
|
||||
There are ingress controllers you want to disallow that are in
|
||||
use:
|
||||
</p>
|
||||
<ul className="ml-6">
|
||||
{usedControllersToDisallow.map((controller) => (
|
||||
<li key={controller.ClassName}>${controller.ClassName}</li>
|
||||
))}
|
||||
</ul>
|
||||
<p>
|
||||
No new ingress rules can be created for the disallowed
|
||||
controllers.
|
||||
</p>
|
||||
</div>
|
||||
),
|
||||
confirmButton: buildConfirmButton('Disallow', 'warning'),
|
||||
});
|
||||
return;
|
||||
|
||||
if (!confirmed) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
setIngControllerFormValues(updatedIngressControllers);
|
||||
onChangeControllers(updatedIngressControllers);
|
||||
|
|
|
@ -5,9 +5,9 @@ import { useStore } from 'zustand';
|
|||
import { useEnvironmentId } from '@/react/hooks/useEnvironmentId';
|
||||
import { useNamespaces } from '@/react/kubernetes/namespaces/queries';
|
||||
import { useAuthorizations, Authorized } from '@/react/hooks/useUser';
|
||||
import { confirmDeletionAsync } from '@/portainer/services/modal.service/confirm';
|
||||
import Route from '@/assets/ico/route.svg?c';
|
||||
|
||||
import { confirmDelete } from '@@/modals/confirm';
|
||||
import { Datatable } from '@@/datatables';
|
||||
import { Button } from '@@/buttons';
|
||||
import { Link } from '@@/Link';
|
||||
|
@ -110,7 +110,7 @@ export function IngressDatatable() {
|
|||
}
|
||||
|
||||
async function handleRemoveClick(ingresses: SelectedIngress[]) {
|
||||
const confirmed = await confirmDeletionAsync(
|
||||
const confirmed = await confirmDelete(
|
||||
'Are you sure you want to delete the selected ingresses?'
|
||||
);
|
||||
if (!confirmed) {
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
import { confirm } from '@@/modals/confirm';
|
||||
import { buildConfirmButton } from '@@/modals/utils';
|
||||
|
||||
export function confirmRedeploy() {
|
||||
return confirm({
|
||||
title: '',
|
||||
message: (
|
||||
<>
|
||||
One or multiple applications are currently using this volume.
|
||||
<br /> For the change to be taken into account these applications will
|
||||
need to be redeployed. Do you want us to reschedule it now?
|
||||
</>
|
||||
),
|
||||
confirmButton: buildConfirmButton('Redeploy the applications'),
|
||||
cancelButtonLabel: "I'll do it later",
|
||||
});
|
||||
}
|
|
@ -3,8 +3,8 @@ import { Trash2 } from 'lucide-react';
|
|||
|
||||
import { useEnvironmentId } from '@/react/hooks/useEnvironmentId';
|
||||
import { Job } from '@/react/nomad/types';
|
||||
import { confirmDeletionAsync } from '@/portainer/services/modal.service/confirm';
|
||||
|
||||
import { confirmDelete } from '@@/modals/confirm';
|
||||
import { LoadingButton } from '@@/buttons/LoadingButton';
|
||||
|
||||
import { deleteJobs } from './delete';
|
||||
|
@ -33,7 +33,7 @@ export function JobActions({ selectedItems, refreshData }: Props) {
|
|||
);
|
||||
|
||||
async function handleDeleteClicked() {
|
||||
const confirmed = await confirmDeletionAsync(
|
||||
const confirmed = await confirmDelete(
|
||||
'Are you sure to delete all selected jobs?'
|
||||
);
|
||||
|
||||
|
|
|
@ -6,3 +6,8 @@ import {
|
|||
export interface TableSettings
|
||||
extends BasicTableSettings,
|
||||
RefreshableTableSettings {}
|
||||
|
||||
export enum DeployType {
|
||||
FDO = 'FDO',
|
||||
MANUAL = 'MANUAL',
|
||||
}
|
||||
|
|
|
@ -2,8 +2,15 @@
|
|||
padding-left: 0.5rem;
|
||||
}
|
||||
|
||||
.dialog {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
.checkbox-list {
|
||||
max-height: 200px;
|
||||
overflow-y: auto;
|
||||
background-color: var(--white-color);
|
||||
border: 0;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
:root[theme='dark'] .checkbox-list,
|
||||
:root[theme='highcontrast'] .checkbox-list {
|
||||
background-color: var(--bg-modal-content-color);
|
||||
}
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
import { X } from 'lucide-react';
|
||||
import clsx from 'clsx';
|
||||
import { useState } from 'react';
|
||||
import { DialogContent, DialogOverlay } from '@reach/dialog';
|
||||
|
||||
import { downloadKubeconfigFile } from '@/react/kubernetes/services/kubeconfig.service';
|
||||
import * as notifications from '@/portainer/services/notifications';
|
||||
|
@ -17,12 +15,12 @@ import {
|
|||
} from '@/react/portainer/environments/queries/useEnvironmentList';
|
||||
import { useListSelection } from '@/react/hooks/useListSelection';
|
||||
|
||||
import { Modal } from '@@/modals';
|
||||
import { PaginationControls } from '@@/PaginationControls';
|
||||
import { Checkbox } from '@@/form-components/Checkbox';
|
||||
import { Button } from '@@/buttons';
|
||||
|
||||
import styles from './KubeconfigPrompt.module.css';
|
||||
import '@reach/dialog/styles.css';
|
||||
|
||||
export interface KubeconfigPromptProps {
|
||||
envQueryParams: Query;
|
||||
|
@ -63,90 +61,69 @@ export function KubeconfigPrompt({
|
|||
.every((env) => selection.includes(env.Id));
|
||||
|
||||
return (
|
||||
<DialogOverlay
|
||||
className={styles.dialog}
|
||||
aria-label="Kubeconfig View"
|
||||
role="dialog"
|
||||
onDismiss={onClose}
|
||||
>
|
||||
<DialogContent className="modal-dialog bg-transparent p-0">
|
||||
<div className="modal-dialog">
|
||||
<div className="modal-content">
|
||||
<div className="modal-header">
|
||||
<button type="button" className="close" onClick={onClose}>
|
||||
<X />
|
||||
</button>
|
||||
<h5 className="modal-title">Download kubeconfig file</h5>
|
||||
</div>
|
||||
<div className="modal-body">
|
||||
<form className="bootbox-form">
|
||||
<div className="bootbox-prompt-message">
|
||||
<span>
|
||||
Select the kubernetes environments to add to the kubeconfig
|
||||
file. You may select across multiple pages.
|
||||
</span>
|
||||
<span className="space-left">{expiryQuery.data}</span>
|
||||
</div>
|
||||
</form>
|
||||
<br />
|
||||
<div className="flex h-8 items-center">
|
||||
<Checkbox
|
||||
id="settings-container-truncate-name"
|
||||
label="Select all (in this page)"
|
||||
checked={isAllPageSelected}
|
||||
onChange={handleSelectAll}
|
||||
/>
|
||||
</div>
|
||||
<div className="datatable">
|
||||
<div className="bootbox-checkbox-list">
|
||||
{environments
|
||||
.filter((env) => env.Status <= 2)
|
||||
.map((env) => (
|
||||
<div
|
||||
key={env.Id}
|
||||
className={clsx(
|
||||
styles.checkbox,
|
||||
'flex h-8 items-center pt-1'
|
||||
)}
|
||||
>
|
||||
<Checkbox
|
||||
id={`${env.Id}`}
|
||||
label={`${env.Name} (${env.URL})`}
|
||||
checked={selection.includes(env.Id)}
|
||||
onChange={() =>
|
||||
toggleSelection(env.Id, !selection.includes(env.Id))
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<div className="flex w-full justify-end pt-3">
|
||||
<PaginationControls
|
||||
showAll={totalCount <= 100}
|
||||
page={page}
|
||||
onPageChange={setPage}
|
||||
pageLimit={pageLimit}
|
||||
onPageLimitChange={setPageLimit}
|
||||
totalCount={totalCount}
|
||||
<Modal aria-label="Kubeconfig View" onDismiss={onClose}>
|
||||
<Modal.Header title="Download kubeconfig file" />
|
||||
|
||||
<Modal.Body>
|
||||
<div>
|
||||
<span>
|
||||
Select the kubernetes environments to add to the kubeconfig file.
|
||||
You may select across multiple pages.
|
||||
</span>
|
||||
<span className="space-left">{expiryQuery.data}</span>
|
||||
</div>
|
||||
|
||||
<div className="mt-2 flex h-8 items-center">
|
||||
<Checkbox
|
||||
id="settings-container-truncate-name"
|
||||
label="Select all (in this page)"
|
||||
checked={isAllPageSelected}
|
||||
onChange={handleSelectAll}
|
||||
/>
|
||||
</div>
|
||||
<div className="datatable">
|
||||
<div className={styles.checkboxList}>
|
||||
{environments
|
||||
.filter((env) => env.Status <= 2)
|
||||
.map((env) => (
|
||||
<div
|
||||
key={env.Id}
|
||||
className={clsx(
|
||||
styles.checkbox,
|
||||
'flex h-8 items-center pt-1'
|
||||
)}
|
||||
>
|
||||
<Checkbox
|
||||
id={`${env.Id}`}
|
||||
label={`${env.Name} (${env.URL})`}
|
||||
checked={!!selection[env.Id]}
|
||||
onChange={() => toggleSelection(env.Id, !selection[env.Id])}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="modal-footer">
|
||||
<Button onClick={onClose} color="default">
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
onClick={handleDownload}
|
||||
disabled={selection.length === 0}
|
||||
>
|
||||
Download File
|
||||
</Button>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<div className="flex w-full justify-end pt-3">
|
||||
<PaginationControls
|
||||
showAll={totalCount <= 100}
|
||||
page={page}
|
||||
onPageChange={setPage}
|
||||
pageLimit={pageLimit}
|
||||
onPageLimitChange={setPageLimit}
|
||||
totalCount={totalCount}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</DialogContent>
|
||||
</DialogOverlay>
|
||||
</Modal.Body>
|
||||
|
||||
<Modal.Footer>
|
||||
<Button onClick={onClose} color="default">
|
||||
Cancel
|
||||
</Button>
|
||||
<Button onClick={handleDownload} disabled={selection.length === 0}>
|
||||
Download File
|
||||
</Button>
|
||||
</Modal.Footer>
|
||||
</Modal>
|
||||
);
|
||||
|
||||
function handleSelectAll() {
|
||||
|
|
|
@ -5,10 +5,10 @@ import { Environment } from '@/react/portainer/environments/types';
|
|||
import { snapshotEndpoints } from '@/react/portainer/environments/environment.service';
|
||||
import { isEdgeEnvironment } from '@/react/portainer/environments/utils';
|
||||
import * as notifications from '@/portainer/services/notifications';
|
||||
import { confirmAsync } from '@/portainer/services/modal.service/confirm';
|
||||
import { buildTitle } from '@/portainer/services/modal.service/utils';
|
||||
|
||||
import { confirm } from '@@/modals/confirm';
|
||||
import { PageHeader } from '@@/PageHeader';
|
||||
import { ModalType } from '@@/modals';
|
||||
|
||||
import { EnvironmentList } from './EnvironmentList';
|
||||
import { EdgeLoadingSpinner } from './EdgeLoadingSpinner';
|
||||
|
@ -72,15 +72,10 @@ export function HomeView() {
|
|||
}
|
||||
|
||||
async function confirmEndpointSnapshot() {
|
||||
return confirmAsync({
|
||||
title: buildTitle('Are you sure?'),
|
||||
return confirm({
|
||||
title: 'Are you sure?',
|
||||
modalType: ModalType.Warn,
|
||||
message:
|
||||
'Triggering a manual refresh will poll each environment to retrieve its information, this may take a few moments.',
|
||||
buttons: {
|
||||
confirm: {
|
||||
label: 'Continue',
|
||||
className: 'btn-primary',
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
|
|
@ -4,12 +4,14 @@ import { useMutation } from 'react-query';
|
|||
import { object } from 'yup';
|
||||
|
||||
import { useUser } from '@/react/hooks/useUser';
|
||||
import { confirmAsync } from '@/portainer/services/modal.service/confirm';
|
||||
import { notifySuccess } from '@/portainer/services/notifications';
|
||||
import { EnvironmentId } from '@/react/portainer/environments/types';
|
||||
|
||||
import { confirm } from '@@/modals/confirm';
|
||||
import { Button } from '@@/buttons';
|
||||
import { LoadingButton } from '@@/buttons/LoadingButton';
|
||||
import { buildConfirmButton } from '@@/modals/utils';
|
||||
import { ModalType } from '@@/modals';
|
||||
|
||||
import { EditDetails } from '../EditDetails';
|
||||
import { parseAccessControlFormData } from '../utils';
|
||||
|
@ -128,15 +130,11 @@ export function AccessControlPanelForm({
|
|||
}
|
||||
|
||||
function confirmAccessControlUpdate() {
|
||||
return confirmAsync({
|
||||
return confirm({
|
||||
modalType: ModalType.Warn,
|
||||
title: 'Are you sure?',
|
||||
message:
|
||||
'Changing the ownership of this resource will potentially restrict its management to some users.',
|
||||
buttons: {
|
||||
confirm: {
|
||||
label: 'Change ownership',
|
||||
className: 'btn-primary',
|
||||
},
|
||||
},
|
||||
confirmButton: buildConfirmButton('Change ownership'),
|
||||
});
|
||||
}
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
import { confirm } from '@@/modals/confirm';
|
||||
import { ModalType } from '@@/modals';
|
||||
import { buildConfirmButton } from '@@/modals/utils';
|
||||
|
||||
export function confirmDisassociate() {
|
||||
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>
|
||||
</>
|
||||
);
|
||||
|
||||
return confirm({
|
||||
title: 'About disassociating',
|
||||
modalType: ModalType.Warn,
|
||||
message,
|
||||
confirmButton: buildConfirmButton('Disassociate'),
|
||||
});
|
||||
}
|
|
@ -2,9 +2,9 @@ import { Clock, Trash2 } from 'lucide-react';
|
|||
import { useStore } from 'zustand';
|
||||
|
||||
import { notifySuccess } from '@/portainer/services/notifications';
|
||||
import { confirmDeletionAsync } from '@/portainer/services/modal.service/confirm';
|
||||
import { withLimitToBE } from '@/react/hooks/useLimitToBE';
|
||||
|
||||
import { confirmDelete } from '@@/modals/confirm';
|
||||
import { Datatable } from '@@/datatables';
|
||||
import { PageHeader } from '@@/PageHeader';
|
||||
import { Button } from '@@/buttons';
|
||||
|
@ -91,7 +91,7 @@ function TableActions({
|
|||
);
|
||||
|
||||
async function handleRemove() {
|
||||
const confirmed = await confirmDeletionAsync(
|
||||
const confirmed = await confirmDelete(
|
||||
'Are you sure you want to remove these?'
|
||||
);
|
||||
if (!confirmed) {
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue