mirror of
https://github.com/portainer/portainer.git
synced 2025-08-02 20:35:25 +02:00
refactor(ui/modals): replace bootbox with react solution [EE-4541] (#8010)
This commit is contained in:
parent
392c7f74b8
commit
e66dea44e3
111 changed files with 1330 additions and 1562 deletions
|
@ -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) {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue