diff --git a/src/app/app.module.ts b/src/app/app.module.ts
index 75dce49c52..aee6e9c6e2 100644
--- a/src/app/app.module.ts
+++ b/src/app/app.module.ts
@@ -38,7 +38,9 @@ import { TwoFactorComponent } from './accounts/two-factor.component';
import { VerifyEmailTokenComponent } from './accounts/verify-email-token.component';
import { VerifyRecoverDeleteComponent } from './accounts/verify-recover-delete.component';
-import { BulkStatusComponent as OrgBulkStatusComponent } from './organizations/manage/bulk-status.component';
+import { BulkConfirmComponent as OrgBulkConfirmComponent } from './organizations/manage/bulk/bulk-confirm.component';
+import { BulkRemoveComponent as OrgBulkRemoveComponent } from './organizations/manage/bulk/bulk-remove.component';
+import { BulkStatusComponent as OrgBulkStatusComponent } from './organizations/manage/bulk/bulk-status.component';
import {
CollectionAddEditComponent as OrgCollectionAddEditComponent,
} from './organizations/manage/collection-add-edit.component';
@@ -351,6 +353,8 @@ registerLocaleData(localeZhTw, 'zh-TW');
OrganizationSubscriptionComponent,
OrgAttachmentsComponent,
OrgBulkStatusComponent,
+ OrgBulkConfirmComponent,
+ OrgBulkRemoveComponent,
OrgCiphersComponent,
OrgCollectionAddEditComponent,
OrgCollectionsComponent,
@@ -452,6 +456,8 @@ registerLocaleData(localeZhTw, 'zh-TW');
OrgAddEditComponent,
OrgAttachmentsComponent,
OrgBulkStatusComponent,
+ OrgBulkConfirmComponent,
+ OrgBulkRemoveComponent,
OrgCollectionAddEditComponent,
OrgCollectionsComponent,
OrgEntityEventsComponent,
diff --git a/src/app/organizations/manage/bulk/bulk-confirm.component.html b/src/app/organizations/manage/bulk/bulk-confirm.component.html
new file mode 100644
index 0000000000..17ecc5a587
--- /dev/null
+++ b/src/app/organizations/manage/bulk/bulk-confirm.component.html
@@ -0,0 +1,100 @@
+
diff --git a/src/app/organizations/manage/bulk/bulk-confirm.component.ts b/src/app/organizations/manage/bulk/bulk-confirm.component.ts
new file mode 100644
index 0000000000..d2ac452278
--- /dev/null
+++ b/src/app/organizations/manage/bulk/bulk-confirm.component.ts
@@ -0,0 +1,95 @@
+import {
+ Component,
+ Input,
+ OnInit,
+} from '@angular/core';
+
+import { ApiService } from 'jslib-common/abstractions/api.service';
+import { CryptoService } from 'jslib-common/abstractions/crypto.service';
+import { I18nService } from 'jslib-common/abstractions/i18n.service';
+
+import { OrganizationUserBulkConfirmRequest } from 'jslib-common/models/request/organizationUserBulkConfirmRequest';
+import { OrganizationUserBulkRequest } from 'jslib-common/models/request/organizationUserBulkRequest';
+
+import { OrganizationUserUserDetailsResponse } from 'jslib-common/models/response/organizationUserResponse';
+
+import { OrganizationUserStatusType } from 'jslib-common/enums/organizationUserStatusType';
+
+import { Utils } from 'jslib-common/misc/utils';
+
+@Component({
+ selector: 'app-bulk-confirm',
+ templateUrl: 'bulk-confirm.component.html',
+})
+export class BulkConfirmComponent implements OnInit {
+
+ @Input() organizationId: string;
+ @Input() users: OrganizationUserUserDetailsResponse[];
+
+ excludedUsers: OrganizationUserUserDetailsResponse[];
+ filteredUsers: OrganizationUserUserDetailsResponse[];
+ publicKeys: Map = new Map();
+ fingerprints: Map = new Map();
+ statuses: Map = new Map();
+
+ loading: boolean = true;
+ done: boolean = false;
+ error: string;
+
+ constructor(private cryptoService: CryptoService, private apiService: ApiService,
+ private i18nService: I18nService) { }
+
+ async ngOnInit() {
+ this.excludedUsers = this.users.filter(user => user.status !== OrganizationUserStatusType.Accepted);
+ this.filteredUsers = this.users.filter(user => user.status === OrganizationUserStatusType.Accepted);
+
+ if (this.filteredUsers.length <= 0) {
+ this.done = true;
+ }
+
+ const request = new OrganizationUserBulkRequest(this.filteredUsers.map(user => user.id));
+ const response = await this.apiService.postOrganizationUsersPublicKey(this.organizationId, request);
+
+ for (const entry of response.data) {
+ const publicKey = Utils.fromB64ToArray(entry.key);
+ const fingerprint = await this.cryptoService.getFingerprint(entry.id, publicKey.buffer);
+ if (fingerprint != null) {
+ this.publicKeys.set(entry.id, publicKey);
+ this.fingerprints.set(entry.id, fingerprint.join('-'));
+ }
+ }
+
+ this.loading = false;
+ }
+
+ async submit() {
+ this.loading = true;
+ try {
+ const orgKey = await this.cryptoService.getOrgKey(this.organizationId);
+ const userIdsWithKeys: any[] = [];
+ for (const user of this.filteredUsers) {
+ const publicKey = this.publicKeys.get(user.id);
+ if (publicKey == null) {
+ continue;
+ }
+ const key = await this.cryptoService.rsaEncrypt(orgKey.key, publicKey.buffer);
+ userIdsWithKeys.push({
+ id: user.id,
+ key: key.encryptedString,
+ });
+ }
+ const request = new OrganizationUserBulkConfirmRequest(userIdsWithKeys);
+ const response = await this.apiService.postOrganizationUserBulkConfirm(this.organizationId, request);
+
+ response.data.forEach(entry => {
+ const error = entry.error !== '' ? entry.error : this.i18nService.t('bulkConfirmMessage');
+ this.statuses.set(entry.id, error);
+ });
+
+ this.done = true;
+ } catch (e) {
+ this.error = e.message;
+ }
+ this.loading = false;
+ }
+}
diff --git a/src/app/organizations/manage/bulk/bulk-remove.component.html b/src/app/organizations/manage/bulk/bulk-remove.component.html
new file mode 100644
index 0000000000..14e803c585
--- /dev/null
+++ b/src/app/organizations/manage/bulk/bulk-remove.component.html
@@ -0,0 +1,81 @@
+
diff --git a/src/app/organizations/manage/bulk/bulk-remove.component.ts b/src/app/organizations/manage/bulk/bulk-remove.component.ts
new file mode 100644
index 0000000000..8b59aee638
--- /dev/null
+++ b/src/app/organizations/manage/bulk/bulk-remove.component.ts
@@ -0,0 +1,46 @@
+import {
+ Component,
+ Input,
+} from '@angular/core';
+
+import { ApiService } from 'jslib-common/abstractions/api.service';
+import { I18nService } from 'jslib-common/abstractions/i18n.service';
+import { OrganizationUserBulkRequest } from 'jslib-common/models/request/organizationUserBulkRequest';
+
+import { OrganizationUserUserDetailsResponse } from 'jslib-common/models/response/organizationUserResponse';
+
+@Component({
+ selector: 'app-bulk-remove',
+ templateUrl: 'bulk-remove.component.html',
+})
+export class BulkRemoveComponent {
+
+ @Input() organizationId: string;
+ @Input() users: OrganizationUserUserDetailsResponse[];
+
+ statuses: Map = new Map();
+
+ loading: boolean = false;
+ done: boolean = false;
+ error: string;
+
+ constructor(private apiService: ApiService, private i18nService: I18nService) { }
+
+ async submit() {
+ this.loading = true;
+ try {
+ const request = new OrganizationUserBulkRequest(this.users.map(user => user.id));
+ const response = await this.apiService.deleteManyOrganizationUsers(this.organizationId, request);
+
+ response.data.forEach(entry => {
+ const error = entry.error !== '' ? entry.error : this.i18nService.t('bulkRemovedMessage');
+ this.statuses.set(entry.id, error);
+ });
+ this.done = true;
+ } catch (e) {
+ this.error = e.message;
+ }
+
+ this.loading = false;
+ }
+}
diff --git a/src/app/organizations/manage/bulk-status.component.html b/src/app/organizations/manage/bulk/bulk-status.component.html
similarity index 100%
rename from src/app/organizations/manage/bulk-status.component.html
rename to src/app/organizations/manage/bulk/bulk-status.component.html
diff --git a/src/app/organizations/manage/bulk-status.component.ts b/src/app/organizations/manage/bulk/bulk-status.component.ts
similarity index 100%
rename from src/app/organizations/manage/bulk-status.component.ts
rename to src/app/organizations/manage/bulk/bulk-status.component.ts
diff --git a/src/app/organizations/manage/people.component.html b/src/app/organizations/manage/people.component.html
index 7bcdad67c1..7adb790871 100644
--- a/src/app/organizations/manage/people.component.html
+++ b/src/app/organizations/manage/people.component.html
@@ -36,7 +36,7 @@
{{'reinviteSelected' | i18n}}
@@ -158,3 +158,5 @@
+
+
diff --git a/src/app/organizations/manage/people.component.ts b/src/app/organizations/manage/people.component.ts
index 6edf91348e..755d4ef3f6 100644
--- a/src/app/organizations/manage/people.component.ts
+++ b/src/app/organizations/manage/people.component.ts
@@ -26,7 +26,6 @@ import { StorageService } from 'jslib-common/abstractions/storage.service';
import { UserService } from 'jslib-common/abstractions/user.service';
import { OrganizationKeysRequest } from 'jslib-common/models/request/organizationKeysRequest';
-import { OrganizationUserBulkConfirmRequest } from 'jslib-common/models/request/organizationUserBulkConfirmRequest';
import { OrganizationUserBulkRequest } from 'jslib-common/models/request/organizationUserBulkRequest';
import { OrganizationUserConfirmRequest } from 'jslib-common/models/request/organizationUserConfirmRequest';
@@ -41,7 +40,9 @@ import { PolicyType } from 'jslib-common/enums/policyType';
import { Utils } from 'jslib-common/misc/utils';
import { ModalComponent } from '../../modal.component';
-import { BulkStatusComponent } from './bulk-status.component';
+import { BulkConfirmComponent } from './bulk/bulk-confirm.component';
+import { BulkRemoveComponent } from './bulk/bulk-remove.component';
+import { BulkStatusComponent } from './bulk/bulk-status.component';
import { EntityEventsComponent } from './entity-events.component';
import { ResetPasswordComponent } from './reset-password.component';
import { UserAddEditComponent } from './user-add-edit.component';
@@ -61,6 +62,8 @@ export class PeopleComponent implements OnInit {
@ViewChild('confirmTemplate', { read: ViewContainerRef, static: true }) confirmModalRef: ViewContainerRef;
@ViewChild('resetPasswordTemplate', { read: ViewContainerRef, static: true }) resetPasswordModalRef: ViewContainerRef;
@ViewChild('bulkStatusTemplate', { read: ViewContainerRef, static: true }) bulkStatusModalRef: ViewContainerRef;
+ @ViewChild('bulkConfirmTemplate', { read: ViewContainerRef, static: true }) bulkConfirmModalRef: ViewContainerRef;
+ @ViewChild('bulkRemoveTemplate', { read: ViewContainerRef, static: true }) bulkRemoveModalRef: ViewContainerRef;
loading = true;
organizationId: string;
@@ -237,6 +240,10 @@ export class PeopleComponent implements OnInit {
this.confirmedCount > 0 && this.confirmedCount < 3 && this.acceptedCount > 0;
}
+ get showBulkConfirmUsers(): boolean {
+ return this.acceptedCount > 0;
+ }
+
edit(user: OrganizationUserUserDetailsResponse) {
if (this.modal != null) {
this.modal.close();
@@ -329,30 +336,21 @@ export class PeopleComponent implements OnInit {
return;
}
- const users = this.getCheckedUsers();
- if (users.length <= 0) {
- this.toasterService.popAsync('error', this.i18nService.t('errorOccurred'),
- this.i18nService.t('noSelectedUsersApplicable'));
- return;
+ if (this.modal != null) {
+ this.modal.close();
}
- const confirmed = await this.platformUtilsService.showDialog(
- this.i18nService.t('removeSelectedUsersConfirmation'), this.i18nService.t('remove'),
- this.i18nService.t('yes'), this.i18nService.t('no'), 'warning');
- if (!confirmed) {
- return false;
- }
+ const factory = this.componentFactoryResolver.resolveComponentFactory(ModalComponent);
+ this.modal = this.bulkRemoveModalRef.createComponent(factory).instance;
+ const childComponent = this.modal.show(BulkRemoveComponent, this.bulkRemoveModalRef);
- try {
- const request = new OrganizationUserBulkRequest(users.map(user => user.id));
- const response = this.apiService.deleteManyOrganizationUsers(this.organizationId, request);
- this.showBulkStatus(users, users, response, this.i18nService.t('bulkRemovedMessage'));
- await response;
+ childComponent.organizationId = this.organizationId;
+ childComponent.users = this.getCheckedUsers();
+
+ this.modal.onClosed.subscribe(async () => {
await this.load();
- } catch (e) {
- this.validationService.showError(e);
- }
- this.actionPromise = null;
+ this.modal = null;
+ });
}
async bulkReinvite() {
@@ -385,83 +383,38 @@ export class PeopleComponent implements OnInit {
return;
}
- const users = this.getCheckedUsers();
- const filteredUsers = users.filter(u => u.status === OrganizationUserStatusType.Accepted);
-
- if (filteredUsers.length <= 0) {
- this.toasterService.popAsync('error', this.i18nService.t('errorOccurred'),
- this.i18nService.t('noSelectedUsersApplicable'));
- return;
+ if (this.modal != null) {
+ this.modal.close();
}
- const publicKeyRequest = new OrganizationUserBulkRequest(filteredUsers.map(user => user.id));
- const publicKeyResponse = await this.apiService.postOrganizationUsersPublicKey(this.organizationId, publicKeyRequest);
+ const factory = this.componentFactoryResolver.resolveComponentFactory(ModalComponent);
+ this.modal = this.bulkConfirmModalRef.createComponent(factory).instance;
+ const childComponent = this.modal.show(BulkConfirmComponent, this.bulkConfirmModalRef);
- const keyMap = new Map();
- publicKeyResponse.data.forEach(entry => {
- keyMap.set(entry.id, Utils.fromB64ToArray(entry.key));
- });
+ childComponent.organizationId = this.organizationId;
+ childComponent.users = this.getCheckedUsers();
- const orgKey = await this.cryptoService.getOrgKey(this.organizationId);
- const userIdsWithKeys: any[] = [];
- const approvedUsers = [];
- for (const user of filteredUsers) {
- const publicKey = keyMap.get(user.id);
- if (publicKey == null) {
- continue;
- }
-
- if (await this.promptConfirmUser(user, publicKey)) {
- const key = await this.cryptoService.rsaEncrypt(orgKey.key, publicKey.buffer);
- approvedUsers.push(user);
- userIdsWithKeys.push({
- id: user.id,
- key: key.encryptedString,
- });
- }
- }
-
- if (userIdsWithKeys.length <= 0) {
- this.toasterService.popAsync('error', this.i18nService.t('errorOccurred'),
- this.i18nService.t('noSelectedUsersApplicable'));
- return;
- }
-
- try {
- const request = new OrganizationUserBulkConfirmRequest(userIdsWithKeys);
- const response = this.apiService.postOrganizationUserBulkConfirm(this.organizationId, request);
- this.showBulkStatus(users, approvedUsers, response, this.i18nService.t('bulkConfirmMessage'));
- await response;
+ this.modal.onClosed.subscribe(async () => {
await this.load();
- } catch (e) {
- this.validationService.showError(e);
- }
+ this.modal = null;
+ });
}
- async confirm(user: OrganizationUserUserDetailsResponse): Promise {
- if (this.actionPromise != null) {
- return;
+ async confirm(user: OrganizationUserUserDetailsResponse) {
+ function updateUser(self: PeopleComponent) {
+ user.status = OrganizationUserStatusType.Confirmed;
+ const mapIndex = self.statusMap.get(OrganizationUserStatusType.Accepted).indexOf(user);
+ if (mapIndex > -1) {
+ self.statusMap.get(OrganizationUserStatusType.Accepted).splice(mapIndex, 1);
+ self.statusMap.get(OrganizationUserStatusType.Confirmed).push(user);
+ }
}
- try {
- const publicKeyResponse = await this.apiService.getUserPublicKey(user.userId);
- const publicKey = Utils.fromB64ToArray(publicKeyResponse.publicKey);
-
- const confirmed = await this.promptConfirmUser(user, publicKey);
- if (!confirmed) {
- return;
- }
-
- try {
- // tslint:disable-next-line
- console.log('User\'s fingerprint: ' +
- (await this.cryptoService.getFingerprint(user.userId, publicKey.buffer)).join('-'));
- } catch { }
-
+ const confirmUser = async (publicKey: Uint8Array) => {
try {
this.actionPromise = this.doConfirmation(user, publicKey);
await this.actionPromise;
- this.confirmUser(user);
+ updateUser(this);
this.toasterService.popAsync('success', null, this.i18nService.t('hasBeenConfirmed', user.name || user.email));
} catch (e) {
this.validationService.showError(e);
@@ -469,6 +422,52 @@ export class PeopleComponent implements OnInit {
} finally {
this.actionPromise = null;
}
+ };
+
+ if (this.actionPromise != null) {
+ return;
+ }
+
+ const autoConfirm = await this.storageService.get(ConstantsService.autoConfirmFingerprints);
+ if (autoConfirm == null || !autoConfirm) {
+ if (this.modal != null) {
+ this.modal.close();
+ }
+
+ const factory = this.componentFactoryResolver.resolveComponentFactory(ModalComponent);
+ this.modal = this.confirmModalRef.createComponent(factory).instance;
+ const childComponent = this.modal.show(
+ UserConfirmComponent, this.confirmModalRef);
+
+ childComponent.name = user != null ? user.name || user.email : null;
+ childComponent.organizationId = this.organizationId;
+ childComponent.organizationUserId = user != null ? user.id : null;
+ childComponent.userId = user != null ? user.userId : null;
+ childComponent.onConfirmedUser.subscribe(async (publicKey: Uint8Array) => {
+ try {
+ await confirmUser(publicKey);
+ this.modal.close();
+ } catch (e) {
+ // tslint:disable-next-line
+ console.error('Handled exception:', e);
+ }
+ });
+
+ this.modal.onClosed.subscribe(() => {
+ this.modal = null;
+ });
+ return;
+ }
+
+ try {
+ const publicKeyResponse = await this.apiService.getUserPublicKey(user.userId);
+ const publicKey = Utils.fromB64ToArray(publicKeyResponse.publicKey);
+ try {
+ // tslint:disable-next-line
+ console.log('User\'s fingerprint: ' +
+ (await this.cryptoService.getFingerprint(user.userId, publicKey.buffer)).join('-'));
+ } catch { }
+ await confirmUser(publicKey);
} catch (e) {
// tslint:disable-next-line
console.error('Handled exception:', e);
@@ -558,9 +557,9 @@ export class PeopleComponent implements OnInit {
request: Promise>, successfullMessage: string) {
const factory = this.componentFactoryResolver.resolveComponentFactory(ModalComponent);
- this.modal = this.eventsModalRef.createComponent(factory).instance;
+ this.modal = this.bulkStatusModalRef.createComponent(factory).instance;
const childComponent = this.modal.show(
- BulkStatusComponent, this.eventsModalRef);
+ BulkStatusComponent, this.bulkStatusModalRef);
childComponent.loading = true;
@@ -639,50 +638,7 @@ export class PeopleComponent implements OnInit {
}
}
- private confirmUser(user: OrganizationUserUserDetailsResponse) {
- user.status = OrganizationUserStatusType.Confirmed;
- const mapIndex = this.statusMap.get(OrganizationUserStatusType.Accepted).indexOf(user);
- if (mapIndex > -1) {
- this.statusMap.get(OrganizationUserStatusType.Accepted).splice(mapIndex, 1);
- this.statusMap.get(OrganizationUserStatusType.Confirmed).push(user);
- }
- }
-
private getCheckedUsers() {
return this.users.filter(u => (u as any).checked);
}
-
- private promptConfirmUser(user: OrganizationUserUserDetailsResponse, publicKey: Uint8Array): Promise {
- return new Promise(async (resolve, reject) => {
- const autoConfirm = await this.storageService.get(ConstantsService.autoConfirmFingerprints);
- if (autoConfirm ?? false) {
- resolve(true);
- }
- let success = false;
-
- if (this.modal != null) {
- this.modal.close();
- }
-
- const factory = this.componentFactoryResolver.resolveComponentFactory(ModalComponent);
- this.modal = this.confirmModalRef.createComponent(factory).instance;
- const childComponent = this.modal.show(
- UserConfirmComponent, this.confirmModalRef);
-
- childComponent.name = user != null ? user.name || user.email : null;
- childComponent.organizationId = this.organizationId;
- childComponent.organizationUserId = user != null ? user.id : null;
- childComponent.userId = user != null ? user.userId : null;
- childComponent.publicKey = publicKey;
- childComponent.onConfirmedUser.subscribe(() => {
- success = true;
- this.modal.close();
- });
-
- this.modal.onClosed.subscribe(() => {
- this.modal = null;
- setTimeout(() => resolve(success), 10);
- });
- });
- }
}
diff --git a/src/app/organizations/manage/user-confirm.component.ts b/src/app/organizations/manage/user-confirm.component.ts
index ae0ada435f..f55f8aaeea 100644
--- a/src/app/organizations/manage/user-confirm.component.ts
+++ b/src/app/organizations/manage/user-confirm.component.ts
@@ -8,9 +8,11 @@ import {
import { ConstantsService } from 'jslib-common/services/constants.service';
+import { ApiService } from 'jslib-common/abstractions/api.service';
import { CryptoService } from 'jslib-common/abstractions/crypto.service';
import { StorageService } from 'jslib-common/abstractions/storage.service';
+import { Utils } from 'jslib-common/misc/utils';
@Component({
selector: 'app-user-confirm',
@@ -21,18 +23,22 @@ export class UserConfirmComponent implements OnInit {
@Input() userId: string;
@Input() organizationUserId: string;
@Input() organizationId: string;
- @Input() publicKey: Uint8Array;
@Output() onConfirmedUser = new EventEmitter();
dontAskAgain = false;
loading = true;
fingerprint: string;
- constructor(private cryptoService: CryptoService, private storageService: StorageService) { }
+ private publicKey: Uint8Array = null;
+
+ constructor(private apiService: ApiService, private cryptoService: CryptoService,
+ private storageService: StorageService) { }
async ngOnInit() {
try {
- if (this.publicKey != null) {
+ const publicKeyResponse = await this.apiService.getUserPublicKey(this.userId);
+ if (publicKeyResponse != null) {
+ this.publicKey = Utils.fromB64ToArray(publicKeyResponse.publicKey);
const fingerprint = await this.cryptoService.getFingerprint(this.userId, this.publicKey.buffer);
if (fingerprint != null) {
this.fingerprint = fingerprint.join('-');
@@ -51,6 +57,6 @@ export class UserConfirmComponent implements OnInit {
await this.storageService.save(ConstantsService.autoConfirmFingerprints, true);
}
- this.onConfirmedUser.emit();
+ this.onConfirmedUser.emit(this.publicKey);
}
}
diff --git a/src/locales/en/messages.json b/src/locales/en/messages.json
index 57147e0ae4..149a47cba4 100644
--- a/src/locales/en/messages.json
+++ b/src/locales/en/messages.json
@@ -3966,8 +3966,8 @@
"noSelectedUsersApplicable": {
"message": "This action is not applicable to any of the selected users."
},
- "removeSelectedUsersConfirmation": {
- "message": "Are you sure you want to remove the selected users?"
+ "removeUsersWarning": {
+ "message": "Are you sure you want to remove the following users? The process may take a few seconds to complete and cannot be interrupted or canceled."
},
"confirmSelected": {
"message": "Confirm Selected"
@@ -3986,5 +3986,14 @@
},
"bulkFilteredMessage": {
"message": "Excluded, not applicable for this action."
+ },
+ "fingerprint": {
+ "message": "Fingerprint"
+ },
+ "removeUsers": {
+ "message": "Remove Users"
+ },
+ "error": {
+ "message": "Error"
}
}