From 39655ebe29b9921fdbd6843cad1468dcf1509729 Mon Sep 17 00:00:00 2001 From: Andreas Coroiu Date: Tue, 22 Nov 2022 14:33:47 +0100 Subject: [PATCH] [EC-73] edit collection modal (#3638) * [EC-16] Cleanup RxJS linting problems * [EC-16] Update Group tab to use table component and show collections. * [EC-16] Extract interface from GroupResponse and use it in the view * [EC-16] Remove heading underline * [EC-16] Cleanup i18n * [EC-16] More i18n cleanup * [EC-16] Fix bulk group request type name * [EC-16] Rename group details type * [EC-73] feat: add inital version of modal using dialog service * [EC-73] feat: create story for dialog * [EC-73] feat: setup story with support for injected data * [EC-73] feat: add inital version of subtitle * [EC-73] feat: add tabs * [EC-73] feat: initial version of collection info form * [EC-73] feat: start of working form * [EC-73] feat: add custom form validator * [EC-73] fix: dialog directive names after rebase * [EC-73] feat: use custom validator * [EC-73] fix: story * [EC-73] feat: allow parent picking * [EC-73] feat: remove tabs to allow for merging * [EC-73] feat: extend story with new and edit dialogs * [EC-73] feat: change title depending on if editing or not * [EC-73] fix: parent not connected to form * [EC-73] feat: add organizationId to dialog data * [EC-73] feat: only allow nesting within collections with access * [EC-73] feat: handle loading with spinner * [EC-73] feat: update collections on submit * [EC-73] feat: reload on save * [EC-73] feat: update story to work with latest changes * [EC-73] feat: always fetch collections from server * [EC-73] fix: do not submit if form invalid * [EC-73] feat: create new collections using new ui * [EC-73] fix: external id not being saved * [EC-73] chore: move calls to separete collection admin service * [EC-73] feat: use new admin views * [EC-73] feat: implement deletion * [EC-73] feat: add support for collection details in service * [EC-73] fix: story * [EC-73] fix: cancel button * [EC-73] feat: re-add tabs * [EC-73] fix: jslib service collection deps * [EC-73] chore: rename component to collection-dialog * [EC-73] chore: clean up collection api service which was replaced * [EC-73] chore: restore collection.service * [EC-73] chore: restore dialog component changes * [EC-73] fix: move subscription to ngOnInit * [EC-73] feat: disable padding when using tabbed content * [EC-73] fix: new lint rules after merge * Add Access Selector Component and Stories * Cherry pick FormSelectionList * Fix some problems caused from cherry-pick * Fix some Web module problems caused from cherry-pick * Move AccessSelector out of the root components directory. Move UserType pipe to AccessSelectorModule * Fix broken member access selector story * Add organization feature module * Undo changes to messages.json * Fix messages.json * Remove redundant CommonModule * [EC-86] Clear collectionMap before populating it with new collections * [EC-86] Update initialization/loading logic to make better use of the Observable pattern * [EC-86] Make table cells use a pointer cursor * [EC-86] Use bitIconButton for row menu triggers * [EC-86] Refactor GroupDetailsRow interface to wrap GroupDetailsResponse. Remove response model interfaces. Cleanup GroupsComponent. * [EC-86] Add bit-badge-list component and tweak BadgeModule to support both the component and directive. Update mockI18nService to support templated strings. * [EC-86] Cleanup badge color and bitIconButton classes * [EC-86] Cleanup more styles * [EC-86] Add GroupApiService Add a new GroupApiService to replace Group Api calls in the ApiService. * [EC-599] Fix avatar/icon sizing * [EC-599] Remove padding in permission column * [EC-599] Make FormSelectionList operations immutable * [EC-599] Integrate the multi-select component * [EC-599] Handle readonly/access all edge cases * [EC-599] Add initial unit tests Also cleans up public interface for the AccessSelectorComponent. Fixes a bug found during unit test creation. * [EC-599] Include item name in control labels * [EC-599] Cleanup member email display * [EC-86] Revisions for badge-list implementation. - Remove `| null` for maxItems according to ADR-0014 - Remove custom setter for items - Use ngOnChanges to update filteredItems - Fix sr-only tailwind class and show screen reader comma after last item if truncated. * [EC-86] Refactor badge-list module/component - Move the badge list component to its own module. - Extract badge list stories from badge stories. - Cleanup bade stories and module after refactor. * [EC-86] Refactor/rename GroupApiService - Re-name GroupApiService to GroupService as there is no need for a separate Api service (no sync or local data for admin services) - Add GroupView for use in the GroupService instead of raw API models - Update views to use GroupView instead of raw GroupResponse models * [EC-86] Refactor group API request models - Move organizationGroupBulkRequest to group requests folder - Fix relative imports in GroupService * [EC-86] Fix linting errors * Fix tab item text color Tab item text color broke after a merge from master and needs a fix to account for bootstrap styles in Web. * [EC-599] Review suggestions - Change PermissionMode to Enum - Rename permControl to permissionControl to be more clear - Rename FormSelectionList file to kebab case. - Move permission row boolean logic to named function for readability * [EC-599] Cleanup AccessSelectorComponent tests - Clarify test states - Add tests for column rendering - Add tests for permission mode - Add id to column headers for testing - Fix small permissionControl bug found during testing * [EC-599] Add FormSelectionList unit tests * [EC-73] chore: re-add collections page * [EC-86] Rename new files using kebab-case * [EC-73] chore: move component to shared org module * Fix MultiSelect component styles and CSP error (#3841) * Update Web styles and CSP to support MultiSelect component - Include the MultiSelect module in the CL barrel file of exports - Import the MultiSelect scss into the Web styles.scss - Add the necessary sha256 hash to webpack CSP policy to support ngSelect inline styles * Undo removal of 127.0.0.1 from webpack CSP (cherry picked from commit 3ed1221f7f150928612f3fab01a2ae63a39f781a) * [EC-73] feat: add empty access selector * [EC-73] feat: add groups to access selector * [EC-73] chore: improve storybook support * [EC-73] feat: tweak item assignment * [EC-73] feat: add support for showing users * [EC-73] feat: use async actions * [EC-73] chore: clean up casting * [EC-73] fix: permissions not loading correctly in access selector * [EC-73] feat: implement saving group permissions * [EC-73] feat: rename to collection access selection view * [EC-73] feat: save users as well * [EC-73] fix: access selector usage * [EC-73] feat: new collection creation * [EC-73] feat: fetch users from collection details * [EC-73] chore: clean up * [EC-73] fix: circular dependency issues * [EC-73] fix: import shared module directly to workaround build issues * [EC-73] fix: missing dependencies in story * [EC-73] chore: move story * [EC-73] fix: manual cherry pick permission bug fix * [EC-73] feat: hide delete button if no permission * [EC-73] feat: properly handle orgs without groups * [EC-73] fix: use correct functions in template * [EC-73] feat: properly handle non-existing parent * [EC-73] chore: use double ngIf instead of else template * [EC-73] fix: add type to dialog ref * [EC-73] fix: restrict field modifiers * [EC-73] fix: use result enum directly * [EC-73] fix: simplify mapping logic * [EC-73] * [EC-73] feat: add story for free orgs without groups * [EC-73] fix: parametrized i18n * [EC-73] feat: create new shared org module * [EC-73] feat: move collection dialog to shared * [EC-73] feat: move access selector to shared * [EC-73] feat: create core organization module * [EC-73] feat: move collection admin service to web * [EC-73] feat: move collection admin views to web * [EC-73] fix: missing i18n * [EC-73] fix: refactor for type safety * [EC-73] fix: storybook not compiling again * [EC-73] feat: use helper function to open dialog * [EC-73] chore: remove comment * [EC-73] fix: revert permission fix * [EC-73] fix: only show delete if in edit mode * [EC-73] chore: remove ngIf else in template * [EC-73] fix: add missing appA11yTitle * [EC-73] chore: rename remove to delete * [EC-73] chore: refactor ngOnInit * [EC-73] fix: dialog position strategy * [EC-73] fix: revert spinner to old way of doing it Signed-off-by: Jacob Fink Co-authored-by: Shane Melton Co-authored-by: Thomas Rittson <31796059+eliykat@users.noreply.github.com> Co-authored-by: Thomas Rittson --- .../core/core-organization.module.ts | 4 + apps/web/src/app/organizations/core/index.ts | 4 + .../core/services/collection-admin.service.ts | 123 ++++++++ .../views/collection-access-selection-view.ts | 25 ++ .../core/views/collection-admin-view.ts | 25 ++ .../manage/collection-add-edit.component.html | 162 ----------- .../manage/collection-add-edit.component.ts | 181 ------------ .../manage/collections.component.html | 10 + .../manage/collections.component.ts | 46 ++- .../manage/group-add-edit.component.ts | 5 +- .../organizations/manage/groups.component.ts | 10 - .../organization-routing.module.ts | 15 + .../app/organizations/organization.module.ts | 22 +- .../access-selector.component.html | 0 .../access-selector.component.spec.ts | 2 +- .../access-selector.component.ts | 0 .../access-selector/access-selector.models.ts | 2 +- .../access-selector/access-selector.module.ts | 2 +- .../access-selector.stories.ts | 2 +- .../components/access-selector/index.ts | 0 .../access-selector/user-type.pipe.ts | 0 .../collection-dialog.component.html | 94 +++++++ .../collection-dialog.component.ts | 266 ++++++++++++++++++ .../collection-dialog.module.ts | 13 + .../collection-dialog.stories.ts | 245 ++++++++++++++++ .../components/collection-dialog/index.ts | 2 + .../web/src/app/organizations/shared/index.ts | 2 + .../shared/shared-organization.module.ts | 9 + .../src/app/shared/loose-components.module.ts | 2 +- apps/web/src/app/shared/shared.module.ts | 5 +- apps/web/src/locales/en/messages.json | 46 ++- .../src/services/jslib-services.module.ts | 7 + libs/common/src/abstractions/api.service.ts | 4 +- .../group/group.service.abstraction.ts | 9 + libs/common/src/abstractions/group/index.ts | 2 + .../group/responses/group-response.ts | 31 ++ libs/common/src/misc/utils.ts | 4 + .../src/models/request/collection.request.ts | 1 + .../models/response/collection.response.ts | 8 +- .../common/src/models/view/collection.view.ts | 4 +- libs/common/src/models/view/group-view.ts | 17 ++ libs/common/src/services/api.service.ts | 6 +- .../src/services/group/group.service.ts | 65 +++++ .../organization-group-bulk-request.ts | 7 + .../async-actions/form-button.directive.ts | 3 +- .../src/badge-list/badge-list.component.ts | 4 +- libs/components/src/badge/badge.directive.ts | 8 +- libs/components/src/badge/badge.stories.ts | 8 +- libs/components/src/badge/index.ts | 2 +- .../src/callout/callout.component.spec.ts | 2 +- .../src/form-field/bit-validators.stories.ts | 56 ++++ .../forbidden-characters.validator.spec.ts | 45 +++ .../forbidden-characters.validator.ts | 23 ++ .../src/form-field/bit-validators/index.ts | 1 + .../src/form-field/error.component.ts | 2 + libs/components/src/form-field/index.ts | 1 + libs/components/src/multi-select/index.ts | 1 + 57 files changed, 1225 insertions(+), 420 deletions(-) create mode 100644 apps/web/src/app/organizations/core/core-organization.module.ts create mode 100644 apps/web/src/app/organizations/core/index.ts create mode 100644 apps/web/src/app/organizations/core/services/collection-admin.service.ts create mode 100644 apps/web/src/app/organizations/core/views/collection-access-selection-view.ts create mode 100644 apps/web/src/app/organizations/core/views/collection-admin-view.ts delete mode 100644 apps/web/src/app/organizations/manage/collection-add-edit.component.html delete mode 100644 apps/web/src/app/organizations/manage/collection-add-edit.component.ts rename apps/web/src/app/organizations/{ => shared}/components/access-selector/access-selector.component.html (100%) rename apps/web/src/app/organizations/{ => shared}/components/access-selector/access-selector.component.spec.ts (98%) rename apps/web/src/app/organizations/{ => shared}/components/access-selector/access-selector.component.ts (100%) rename apps/web/src/app/organizations/{ => shared}/components/access-selector/access-selector.models.ts (97%) rename apps/web/src/app/organizations/{ => shared}/components/access-selector/access-selector.module.ts (83%) rename apps/web/src/app/organizations/{ => shared}/components/access-selector/access-selector.stories.ts (98%) rename apps/web/src/app/organizations/{ => shared}/components/access-selector/index.ts (100%) rename apps/web/src/app/organizations/{ => shared}/components/access-selector/user-type.pipe.ts (100%) create mode 100644 apps/web/src/app/organizations/shared/components/collection-dialog/collection-dialog.component.html create mode 100644 apps/web/src/app/organizations/shared/components/collection-dialog/collection-dialog.component.ts create mode 100644 apps/web/src/app/organizations/shared/components/collection-dialog/collection-dialog.module.ts create mode 100644 apps/web/src/app/organizations/shared/components/collection-dialog/collection-dialog.stories.ts create mode 100644 apps/web/src/app/organizations/shared/components/collection-dialog/index.ts create mode 100644 apps/web/src/app/organizations/shared/index.ts create mode 100644 apps/web/src/app/organizations/shared/shared-organization.module.ts create mode 100644 libs/common/src/abstractions/group/group.service.abstraction.ts create mode 100644 libs/common/src/abstractions/group/index.ts create mode 100644 libs/common/src/abstractions/group/responses/group-response.ts create mode 100644 libs/common/src/models/view/group-view.ts create mode 100644 libs/common/src/services/group/group.service.ts create mode 100644 libs/common/src/services/group/requests/organization-group-bulk-request.ts create mode 100644 libs/components/src/form-field/bit-validators.stories.ts create mode 100644 libs/components/src/form-field/bit-validators/forbidden-characters.validator.spec.ts create mode 100644 libs/components/src/form-field/bit-validators/forbidden-characters.validator.ts create mode 100644 libs/components/src/form-field/bit-validators/index.ts diff --git a/apps/web/src/app/organizations/core/core-organization.module.ts b/apps/web/src/app/organizations/core/core-organization.module.ts new file mode 100644 index 0000000000..57362e01d7 --- /dev/null +++ b/apps/web/src/app/organizations/core/core-organization.module.ts @@ -0,0 +1,4 @@ +import { NgModule } from "@angular/core"; + +@NgModule({}) +export class CoreOrganizationModule {} diff --git a/apps/web/src/app/organizations/core/index.ts b/apps/web/src/app/organizations/core/index.ts new file mode 100644 index 0000000000..e68991103c --- /dev/null +++ b/apps/web/src/app/organizations/core/index.ts @@ -0,0 +1,4 @@ +export * from "./core-organization.module"; +export * from "./services/collection-admin.service"; +export * from "./views/collection-access-selection-view"; +export * from "./views/collection-admin-view"; diff --git a/apps/web/src/app/organizations/core/services/collection-admin.service.ts b/apps/web/src/app/organizations/core/services/collection-admin.service.ts new file mode 100644 index 0000000000..039751eb75 --- /dev/null +++ b/apps/web/src/app/organizations/core/services/collection-admin.service.ts @@ -0,0 +1,123 @@ +import { Injectable } from "@angular/core"; + +import { ApiService } from "@bitwarden/common/abstractions/api.service"; +import { CryptoService } from "@bitwarden/common/abstractions/crypto.service"; +import { EncString } from "@bitwarden/common/models/domain/enc-string"; +import { CollectionRequest } from "@bitwarden/common/models/request/collection.request"; +import { SelectionReadOnlyRequest } from "@bitwarden/common/models/request/selection-read-only.request"; +import { + CollectionAccessDetailsResponse, + CollectionResponse, +} from "@bitwarden/common/models/response/collection.response"; +import { CollectionView } from "@bitwarden/common/models/view/collection.view"; + +import { CoreOrganizationModule } from "../core-organization.module"; +import { CollectionAdminView } from "../views/collection-admin-view"; + +@Injectable({ providedIn: CoreOrganizationModule }) +export class CollectionAdminService { + constructor(private apiService: ApiService, private cryptoService: CryptoService) {} + + async getAll(organizationId: string): Promise { + const collectionResponse = await this.apiService.getCollections(organizationId); + if (collectionResponse?.data == null || collectionResponse.data.length === 0) { + return []; + } + + return await this.decryptMany(organizationId, collectionResponse.data); + } + + async get( + organizationId: string, + collectionId: string + ): Promise { + const collectionResponse = await this.apiService.getCollectionDetails( + organizationId, + collectionId + ); + + if (collectionResponse == null) { + return undefined; + } + + const [view] = await this.decryptMany(organizationId, [collectionResponse]); + + return view; + } + + async save(collection: CollectionAdminView): Promise { + const request = await this.encrypt(collection); + + let response: CollectionResponse; + if (collection.id == null) { + response = await this.apiService.postCollection(collection.organizationId, request); + collection.id = response.id; + } else { + response = await this.apiService.putCollection( + collection.organizationId, + collection.id, + request + ); + } + + // TODO: Implement upsert when in PS-1083: Collection Service refactors + // await this.collectionService.upsert(data); + return; + } + + async delete(organizationId: string, collectionId: string): Promise { + await this.apiService.deleteCollection(organizationId, collectionId); + } + + private async decryptMany( + organizationId: string, + collections: CollectionResponse[] | CollectionAccessDetailsResponse[] + ): Promise { + const orgKey = await this.cryptoService.getOrgKey(organizationId); + + const promises = collections.map(async (c) => { + const view = new CollectionAdminView(); + view.id = c.id; + view.name = await this.cryptoService.decryptToUtf8(new EncString(c.name), orgKey); + view.externalId = c.externalId; + view.organizationId = c.organizationId; + + if (isCollectionAccessDetailsResponse(c)) { + view.groups = c.groups; + view.users = c.users; + } + + return view; + }); + + return await Promise.all(promises); + } + + private async encrypt(model: CollectionAdminView): Promise { + if (model.organizationId == null) { + throw new Error("Collection has no organization id."); + } + const key = await this.cryptoService.getOrgKey(model.organizationId); + if (key == null) { + throw new Error("No key for this collection's organization."); + } + const collection = new CollectionRequest(); + collection.externalId = model.externalId; + collection.name = (await this.cryptoService.encrypt(model.name, key)).encryptedString; + collection.groups = model.groups.map( + (group) => new SelectionReadOnlyRequest(group.id, group.readOnly, group.hidePasswords) + ); + collection.users = model.users.map( + (user) => new SelectionReadOnlyRequest(user.id, user.readOnly, user.hidePasswords) + ); + return collection; + } +} + +function isCollectionAccessDetailsResponse( + response: CollectionResponse | CollectionAccessDetailsResponse +): response is CollectionAccessDetailsResponse { + const anyResponse = response as any; + + return anyResponse?.groups instanceof Array && anyResponse?.users instanceof Array; +} diff --git a/apps/web/src/app/organizations/core/views/collection-access-selection-view.ts b/apps/web/src/app/organizations/core/views/collection-access-selection-view.ts new file mode 100644 index 0000000000..38191605fd --- /dev/null +++ b/apps/web/src/app/organizations/core/views/collection-access-selection-view.ts @@ -0,0 +1,25 @@ +import { View } from "@bitwarden/common/models/view/view"; + +interface SelectionResponseLike { + id: string; + readOnly: boolean; + hidePasswords: boolean; +} + +export class CollectionAccessSelectionView extends View { + readonly id: string; + readonly readOnly: boolean; + readonly hidePasswords: boolean; + + constructor(response?: SelectionResponseLike) { + super(); + + if (!response) { + return; + } + + this.id = response.id; + this.readOnly = response.readOnly; + this.hidePasswords = response.hidePasswords; + } +} diff --git a/apps/web/src/app/organizations/core/views/collection-admin-view.ts b/apps/web/src/app/organizations/core/views/collection-admin-view.ts new file mode 100644 index 0000000000..e0ad35bf43 --- /dev/null +++ b/apps/web/src/app/organizations/core/views/collection-admin-view.ts @@ -0,0 +1,25 @@ +import { CollectionView } from "@bitwarden/common/models/view/collection.view"; +import { CollectionAccessDetailsResponse } from "@bitwarden/common/src/models/response/collection.response"; + +import { CollectionAccessSelectionView } from "./collection-access-selection-view"; + +export class CollectionAdminView extends CollectionView { + groups: CollectionAccessSelectionView[] = []; + users: CollectionAccessSelectionView[] = []; + + constructor(response?: CollectionAccessDetailsResponse) { + super(response); + + if (!response) { + return; + } + + this.groups = response.groups + ? response.groups.map((g) => new CollectionAccessSelectionView(g)) + : []; + + this.users = response.users + ? response.users.map((g) => new CollectionAccessSelectionView(g)) + : []; + } +} diff --git a/apps/web/src/app/organizations/manage/collection-add-edit.component.html b/apps/web/src/app/organizations/manage/collection-add-edit.component.html deleted file mode 100644 index 97c973a419..0000000000 --- a/apps/web/src/app/organizations/manage/collection-add-edit.component.html +++ /dev/null @@ -1,162 +0,0 @@ - diff --git a/apps/web/src/app/organizations/manage/collection-add-edit.component.ts b/apps/web/src/app/organizations/manage/collection-add-edit.component.ts deleted file mode 100644 index 6f0ad08292..0000000000 --- a/apps/web/src/app/organizations/manage/collection-add-edit.component.ts +++ /dev/null @@ -1,181 +0,0 @@ -import { Component, EventEmitter, Input, OnInit, Output } from "@angular/core"; - -import { ApiService } from "@bitwarden/common/abstractions/api.service"; -import { CryptoService } from "@bitwarden/common/abstractions/crypto.service"; -import { I18nService } from "@bitwarden/common/abstractions/i18n.service"; -import { LogService } from "@bitwarden/common/abstractions/log.service"; -import { OrganizationService } from "@bitwarden/common/abstractions/organization/organization.service.abstraction"; -import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service"; -import { Utils } from "@bitwarden/common/misc/utils"; -import { EncString } from "@bitwarden/common/models/domain/enc-string"; -import { SymmetricCryptoKey } from "@bitwarden/common/models/domain/symmetric-crypto-key"; -import { CollectionRequest } from "@bitwarden/common/models/request/collection.request"; -import { SelectionReadOnlyRequest } from "@bitwarden/common/models/request/selection-read-only.request"; - -import { GroupServiceAbstraction } from "../services/abstractions/group"; -import { GroupView } from "../views/group.view"; - -@Component({ - selector: "app-collection-add-edit", - templateUrl: "collection-add-edit.component.html", -}) -export class CollectionAddEditComponent implements OnInit { - @Input() collectionId: string; - @Input() organizationId: string; - @Input() canSave: boolean; - @Input() canDelete: boolean; - @Output() onSavedCollection = new EventEmitter(); - @Output() onDeletedCollection = new EventEmitter(); - - loading = true; - editMode = false; - accessGroups = false; - title: string; - name: string; - externalId: string; - groups: GroupView[] = []; - formPromise: Promise; - deletePromise: Promise; - - private orgKey: SymmetricCryptoKey; - - constructor( - private apiService: ApiService, - private groupApiService: GroupServiceAbstraction, - private i18nService: I18nService, - private platformUtilsService: PlatformUtilsService, - private cryptoService: CryptoService, - private logService: LogService, - private organizationService: OrganizationService - ) {} - - async ngOnInit() { - const organization = await this.organizationService.get(this.organizationId); - this.accessGroups = organization.useGroups; - this.editMode = this.loading = this.collectionId != null; - if (this.accessGroups) { - const groupsResponse = await this.groupApiService.getAll(this.organizationId); - this.groups = groupsResponse.sort(Utils.getSortFunction(this.i18nService, "name")); - } - this.orgKey = await this.cryptoService.getOrgKey(this.organizationId); - - if (this.editMode) { - this.editMode = true; - this.title = this.i18nService.t("editCollection"); - try { - const collection = await this.apiService.getCollectionDetails( - this.organizationId, - this.collectionId - ); - this.name = await this.cryptoService.decryptToUtf8( - new EncString(collection.name), - this.orgKey - ); - this.externalId = collection.externalId; - if (collection.groups != null && this.groups.length > 0) { - collection.groups.forEach((s) => { - const group = this.groups.filter((g) => !g.accessAll && g.id === s.id); - if (group != null && group.length > 0) { - (group[0] as any).checked = true; - (group[0] as any).readOnly = s.readOnly; - (group[0] as any).hidePasswords = s.hidePasswords; - } - }); - } - } catch (e) { - this.logService.error(e); - } - } else { - this.title = this.i18nService.t("addCollection"); - } - - this.groups.forEach((g) => { - if (g.accessAll) { - (g as any).checked = true; - } - }); - - this.loading = false; - } - - check(g: GroupView, select?: boolean) { - if (g.accessAll) { - return; - } - (g as any).checked = select == null ? !(g as any).checked : select; - if (!(g as any).checked) { - (g as any).readOnly = false; - (g as any).hidePasswords = false; - } - } - - selectAll(select: boolean) { - this.groups.forEach((g) => this.check(g, select)); - } - - async submit() { - if (this.orgKey == null) { - throw new Error("No encryption key for this organization."); - } - - const request = new CollectionRequest(); - request.name = (await this.cryptoService.encrypt(this.name, this.orgKey)).encryptedString; - request.externalId = this.externalId; - request.groups = this.groups - .filter((g) => (g as any).checked && !g.accessAll) - .map( - (g) => new SelectionReadOnlyRequest(g.id, !!(g as any).readOnly, !!(g as any).hidePasswords) - ); - - try { - if (this.editMode) { - this.formPromise = this.apiService.putCollection( - this.organizationId, - this.collectionId, - request - ); - } else { - this.formPromise = this.apiService.postCollection(this.organizationId, request); - } - await this.formPromise; - this.platformUtilsService.showToast( - "success", - null, - this.i18nService.t(this.editMode ? "editedCollectionId" : "createdCollectionId", this.name) - ); - this.onSavedCollection.emit(); - } catch (e) { - this.logService.error(e); - } - } - - async delete() { - if (!this.editMode) { - return; - } - - const confirmed = await this.platformUtilsService.showDialog( - this.i18nService.t("deleteCollectionConfirmation"), - this.name, - this.i18nService.t("yes"), - this.i18nService.t("no"), - "warning" - ); - if (!confirmed) { - return false; - } - - try { - this.deletePromise = this.apiService.deleteCollection(this.organizationId, this.collectionId); - await this.deletePromise; - this.platformUtilsService.showToast( - "success", - null, - this.i18nService.t("deletedCollectionId", this.name) - ); - this.onDeletedCollection.emit(); - } catch (e) { - this.logService.error(e); - } - } -} diff --git a/apps/web/src/app/organizations/manage/collections.component.html b/apps/web/src/app/organizations/manage/collections.component.html index 976a63fe6d..69fc9e9896 100644 --- a/apps/web/src/app/organizations/manage/collections.component.html +++ b/apps/web/src/app/organizations/manage/collections.component.html @@ -65,6 +65,16 @@