mirror of
https://github.com/bitwarden/browser.git
synced 2024-11-11 10:10:25 +01:00
[PM-6992] Refactor VaultComponent (#10126)
* Added function to return cipherview observable and trigger the decryption process if the cipherviews$ observable returns empty * Updated the vault component to use getCipherViews$ observable function * converted vault banner to standalone component * converted vault header to standalone component * fixed unawaited promises converted component to standalone component * cleaned up vault module * fixed imports * refactored getCipherView$ observable * refactored onVaultItemsEvent to switch case * Refactored to use toast service instead of platform utils service for toast * Added function to return cipherview observable and trigger the decryption process if the cipherviews$ observable returns empty * Updated the vault component to use getCipherViews$ observable function * converted vault banner to standalone component * converted vault header to standalone component * fixed unawaited promises converted component to standalone component * cleaned up vault module * fixed imports * refactored getCipherView$ observable * refactored onVaultItemsEvent to switch case * Refactored to use toast service instead of platform utils service for toast * merged with main and fixed conflicts * reordered standalone property * converted components to standalone * cleaned up ng module for org vault * cleaned up vault module individual vault * fixed conflicts * Replaced deprecated toast service * refactored to use switch case for org vault * fixed comments and fixed failing tests reverted to use getAllDecrypted
This commit is contained in:
parent
b32f6182a5
commit
91aa475766
@ -1,5 +1,6 @@
|
||||
import { ComponentFixture, TestBed } from "@angular/core/testing";
|
||||
import { By } from "@angular/platform-browser";
|
||||
import { RouterTestingModule } from "@angular/router/testing";
|
||||
import { mock } from "jest-mock-extended";
|
||||
import { BehaviorSubject } from "rxjs";
|
||||
|
||||
@ -11,7 +12,7 @@ import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/pl
|
||||
import { BannerComponent, BannerModule } from "@bitwarden/components";
|
||||
|
||||
import { VerifyEmailComponent } from "../../../auth/settings/verify-email.component";
|
||||
import { LooseComponentsModule } from "../../../shared";
|
||||
import { SharedModule } from "../../../shared";
|
||||
|
||||
import { VaultBannersService, VisibleVaultBanner } from "./services/vault-banners.service";
|
||||
import { VaultBannersComponent } from "./vault-banners.component";
|
||||
@ -36,13 +37,15 @@ describe("VaultBannersComponent", () => {
|
||||
bannerService.shouldShowLowKDFBanner.mockResolvedValue(false);
|
||||
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [BannerModule, LooseComponentsModule, VerifyEmailComponent],
|
||||
declarations: [VaultBannersComponent, I18nPipe],
|
||||
imports: [
|
||||
BannerModule,
|
||||
SharedModule,
|
||||
VerifyEmailComponent,
|
||||
VaultBannersComponent,
|
||||
RouterTestingModule,
|
||||
],
|
||||
declarations: [I18nPipe],
|
||||
providers: [
|
||||
{
|
||||
provide: VaultBannersService,
|
||||
useValue: bannerService,
|
||||
},
|
||||
{
|
||||
provide: I18nService,
|
||||
useValue: mock<I18nService>({ t: (key) => key }),
|
||||
@ -60,7 +63,9 @@ describe("VaultBannersComponent", () => {
|
||||
useValue: mock<TokenService>(),
|
||||
},
|
||||
],
|
||||
}).compileComponents();
|
||||
})
|
||||
.overrideProvider(VaultBannersService, { useValue: bannerService })
|
||||
.compileComponents();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
|
@ -1,11 +1,19 @@
|
||||
import { Component, OnInit } from "@angular/core";
|
||||
import { Observable } from "rxjs";
|
||||
|
||||
import { BannerModule } from "@bitwarden/components";
|
||||
|
||||
import { VerifyEmailComponent } from "../../../auth/settings/verify-email.component";
|
||||
import { SharedModule } from "../../../shared";
|
||||
|
||||
import { VaultBannersService, VisibleVaultBanner } from "./services/vault-banners.service";
|
||||
|
||||
@Component({
|
||||
standalone: true,
|
||||
selector: "app-vault-banners",
|
||||
templateUrl: "./vault-banners.component.html",
|
||||
imports: [VerifyEmailComponent, SharedModule, BannerModule],
|
||||
providers: [VaultBannersService],
|
||||
})
|
||||
export class VaultBannersComponent implements OnInit {
|
||||
visibleBanners: VisibleVaultBanner[] = [];
|
||||
|
@ -1,3 +1,4 @@
|
||||
import { CommonModule } from "@angular/common";
|
||||
import {
|
||||
ChangeDetectionStrategy,
|
||||
Component,
|
||||
@ -8,14 +9,19 @@ import {
|
||||
} from "@angular/core";
|
||||
import { firstValueFrom } from "rxjs";
|
||||
|
||||
import { JslibModule } from "@bitwarden/angular/jslib.module";
|
||||
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
|
||||
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
|
||||
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { TreeNode } from "@bitwarden/common/vault/models/domain/tree-node";
|
||||
import { CollectionView } from "@bitwarden/common/vault/models/view/collection.view";
|
||||
import { BreadcrumbsModule, MenuModule } from "@bitwarden/components";
|
||||
|
||||
import { HeaderModule } from "../../../layouts/header/header.module";
|
||||
import { SharedModule } from "../../../shared";
|
||||
import { CollectionDialogTabType } from "../../components/collection-dialog";
|
||||
import { PipesModule } from "../pipes/pipes.module";
|
||||
import {
|
||||
All,
|
||||
RoutedVaultFilterModel,
|
||||
@ -23,8 +29,18 @@ import {
|
||||
} from "../vault-filter/shared/models/routed-vault-filter.model";
|
||||
|
||||
@Component({
|
||||
standalone: true,
|
||||
selector: "app-vault-header",
|
||||
templateUrl: "./vault-header.component.html",
|
||||
imports: [
|
||||
CommonModule,
|
||||
MenuModule,
|
||||
SharedModule,
|
||||
BreadcrumbsModule,
|
||||
HeaderModule,
|
||||
PipesModule,
|
||||
JslibModule,
|
||||
],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class VaultHeaderComponent implements OnInit {
|
||||
|
@ -158,10 +158,9 @@ describe("VaultOnboardingComponent", () => {
|
||||
});
|
||||
|
||||
it("should set installExtension to true when hasBWInstalled command is passed", async () => {
|
||||
const saveCompletedTasksSpy = jest.spyOn(
|
||||
(component as any).vaultOnboardingService,
|
||||
"setVaultOnboardingTasks",
|
||||
);
|
||||
const saveCompletedTasksSpy = jest
|
||||
.spyOn((component as any).vaultOnboardingService, "setVaultOnboardingTasks")
|
||||
.mockReturnValue(Promise.resolve());
|
||||
|
||||
(component as any).vaultOnboardingService.vaultOnboardingState$ = of({
|
||||
createAccount: true,
|
||||
|
@ -24,11 +24,17 @@ import { LinkModule } from "@bitwarden/components";
|
||||
import { OnboardingModule } from "../../../shared/components/onboarding/onboarding.module";
|
||||
|
||||
import { VaultOnboardingService as VaultOnboardingServiceAbstraction } from "./services/abstraction/vault-onboarding.service";
|
||||
import { VaultOnboardingTasks } from "./services/vault-onboarding.service";
|
||||
import { VaultOnboardingService, VaultOnboardingTasks } from "./services/vault-onboarding.service";
|
||||
|
||||
@Component({
|
||||
standalone: true,
|
||||
imports: [OnboardingModule, CommonModule, JslibModule, LinkModule],
|
||||
providers: [
|
||||
{
|
||||
provide: VaultOnboardingServiceAbstraction,
|
||||
useClass: VaultOnboardingService,
|
||||
},
|
||||
],
|
||||
selector: "app-vault-onboarding",
|
||||
templateUrl: "vault-onboarding.component.html",
|
||||
})
|
||||
|
@ -60,6 +60,7 @@ import { ServiceUtils } from "@bitwarden/common/vault/service-utils";
|
||||
import { DialogService, Icons, ToastService } from "@bitwarden/components";
|
||||
import { CollectionAssignmentResult, PasswordRepromptService } from "@bitwarden/vault";
|
||||
|
||||
import { SharedModule } from "../../shared/shared.module";
|
||||
import { AssignCollectionsWebComponent } from "../components/assign-collections";
|
||||
import {
|
||||
CollectionDialogAction,
|
||||
@ -68,6 +69,7 @@ import {
|
||||
} from "../components/collection-dialog";
|
||||
import { VaultItem } from "../components/vault-items/vault-item";
|
||||
import { VaultItemEvent } from "../components/vault-items/vault-item-event";
|
||||
import { VaultItemsModule } from "../components/vault-items/vault-items.module";
|
||||
import { getNestedCollectionTree } from "../utils/collection-utils";
|
||||
|
||||
import { AddEditComponent } from "./add-edit.component";
|
||||
@ -87,6 +89,7 @@ import {
|
||||
import { openIndividualVaultCollectionsDialog } from "./collections.component";
|
||||
import { FolderAddEditDialogResult, openFolderAddEditDialog } from "./folder-add-edit.component";
|
||||
import { ShareComponent } from "./share.component";
|
||||
import { VaultBannersComponent } from "./vault-banners/vault-banners.component";
|
||||
import { VaultFilterComponent } from "./vault-filter/components/vault-filter.component";
|
||||
import { VaultFilterService } from "./vault-filter/services/abstractions/vault-filter.service";
|
||||
import { RoutedVaultFilterBridgeService } from "./vault-filter/services/routed-vault-filter-bridge.service";
|
||||
@ -99,13 +102,25 @@ import {
|
||||
} from "./vault-filter/shared/models/routed-vault-filter.model";
|
||||
import { VaultFilter } from "./vault-filter/shared/models/vault-filter.model";
|
||||
import { FolderFilter, OrganizationFilter } from "./vault-filter/shared/models/vault-filter.type";
|
||||
import { VaultFilterModule } from "./vault-filter/vault-filter.module";
|
||||
import { VaultHeaderComponent } from "./vault-header/vault-header.component";
|
||||
import { VaultOnboardingComponent } from "./vault-onboarding/vault-onboarding.component";
|
||||
|
||||
const BroadcasterSubscriptionId = "VaultComponent";
|
||||
const SearchTextDebounceInterval = 200;
|
||||
|
||||
@Component({
|
||||
standalone: true,
|
||||
selector: "app-vault",
|
||||
templateUrl: "vault.component.html",
|
||||
imports: [
|
||||
VaultHeaderComponent,
|
||||
VaultOnboardingComponent,
|
||||
VaultBannersComponent,
|
||||
VaultFilterModule,
|
||||
VaultItemsModule,
|
||||
SharedModule,
|
||||
],
|
||||
providers: [RoutedVaultFilterService, RoutedVaultFilterBridgeService],
|
||||
})
|
||||
export class VaultComponent implements OnInit, OnDestroy {
|
||||
@ -323,18 +338,14 @@ export class VaultComponent implements OnInit, OnDestroy {
|
||||
const cipherId = getCipherIdFromParams(params);
|
||||
if (cipherId) {
|
||||
if ((await this.cipherService.get(cipherId)) != null) {
|
||||
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
this.editCipherId(cipherId);
|
||||
await this.editCipherId(cipherId);
|
||||
} else {
|
||||
this.platformUtilsService.showToast(
|
||||
"error",
|
||||
null,
|
||||
this.i18nService.t("unknownCipher"),
|
||||
);
|
||||
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
this.router.navigate([], {
|
||||
this.toastService.showToast({
|
||||
variant: "error",
|
||||
title: null,
|
||||
message: this.i18nService.t("unknownCipher"),
|
||||
});
|
||||
await this.router.navigate([], {
|
||||
queryParams: { itemId: null, cipherId: null },
|
||||
queryParamsHandling: "merge",
|
||||
});
|
||||
@ -403,36 +414,48 @@ export class VaultComponent implements OnInit, OnDestroy {
|
||||
async onVaultItemsEvent(event: VaultItemEvent) {
|
||||
this.processingEvent = true;
|
||||
try {
|
||||
if (event.type === "viewAttachments") {
|
||||
await this.editCipherAttachments(event.item);
|
||||
} else if (event.type === "viewCipherCollections") {
|
||||
await this.editCipherCollections(event.item);
|
||||
} else if (event.type === "clone") {
|
||||
await this.cloneCipher(event.item);
|
||||
} else if (event.type === "restore") {
|
||||
if (event.items.length === 1) {
|
||||
await this.restore(event.items[0]);
|
||||
} else {
|
||||
await this.bulkRestore(event.items);
|
||||
}
|
||||
} else if (event.type === "delete") {
|
||||
await this.handleDeleteEvent(event.items);
|
||||
} else if (event.type === "moveToFolder") {
|
||||
await this.bulkMove(event.items);
|
||||
} else if (event.type === "moveToOrganization") {
|
||||
if (event.items.length === 1) {
|
||||
await this.shareCipher(event.items[0]);
|
||||
} else {
|
||||
await this.bulkShare(event.items);
|
||||
}
|
||||
} else if (event.type === "copyField") {
|
||||
await this.copy(event.item, event.field);
|
||||
} else if (event.type === "editCollection") {
|
||||
await this.editCollection(event.item, CollectionDialogTabType.Info);
|
||||
} else if (event.type === "viewCollectionAccess") {
|
||||
await this.editCollection(event.item, CollectionDialogTabType.Access);
|
||||
} else if (event.type === "assignToCollections") {
|
||||
await this.bulkAssignToCollections(event.items);
|
||||
switch (event.type) {
|
||||
case "viewAttachments":
|
||||
await this.editCipherAttachments(event.item);
|
||||
break;
|
||||
case "viewCipherCollections":
|
||||
await this.editCipherCollections(event.item);
|
||||
break;
|
||||
case "clone":
|
||||
await this.cloneCipher(event.item);
|
||||
break;
|
||||
case "restore":
|
||||
if (event.items.length === 1) {
|
||||
await this.restore(event.items[0]);
|
||||
} else {
|
||||
await this.bulkRestore(event.items);
|
||||
}
|
||||
break;
|
||||
case "delete":
|
||||
await this.handleDeleteEvent(event.items);
|
||||
break;
|
||||
case "moveToFolder":
|
||||
await this.bulkMove(event.items);
|
||||
break;
|
||||
case "moveToOrganization":
|
||||
if (event.items.length === 1) {
|
||||
await this.shareCipher(event.items[0]);
|
||||
} else {
|
||||
await this.bulkShare(event.items);
|
||||
}
|
||||
break;
|
||||
case "copyField":
|
||||
await this.copy(event.item, event.field);
|
||||
break;
|
||||
case "editCollection":
|
||||
await this.editCollection(event.item, CollectionDialogTabType.Info);
|
||||
break;
|
||||
case "viewCollectionAccess":
|
||||
await this.editCollection(event.item, CollectionDialogTabType.Access);
|
||||
break;
|
||||
case "assignToCollections":
|
||||
await this.bulkAssignToCollections(event.items);
|
||||
break;
|
||||
}
|
||||
} finally {
|
||||
this.processingEvent = false;
|
||||
@ -445,9 +468,7 @@ export class VaultComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
const orgs = await firstValueFrom(this.filterComponent.filters.organizationFilter.data$);
|
||||
const orgNode = ServiceUtils.getTreeNodeObject(orgs, orgId) as TreeNode<OrganizationFilter>;
|
||||
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
this.filterComponent.filters?.organizationFilter?.action(orgNode);
|
||||
await this.filterComponent.filters?.organizationFilter?.action(orgNode);
|
||||
}
|
||||
|
||||
addFolder = async (): Promise<void> => {
|
||||
@ -670,7 +691,7 @@ export class VaultComponent implements OnInit, OnDestroy {
|
||||
this.refresh();
|
||||
// Navigate away if we deleted the collection we were viewing
|
||||
if (this.selectedCollection?.node.id === c?.id) {
|
||||
void this.router.navigate([], {
|
||||
await this.router.navigate([], {
|
||||
queryParams: { collectionId: this.selectedCollection.parent?.node.id ?? null },
|
||||
queryParamsHandling: "merge",
|
||||
replaceUrl: true,
|
||||
@ -697,14 +718,15 @@ export class VaultComponent implements OnInit, OnDestroy {
|
||||
try {
|
||||
await this.apiService.deleteCollection(collection.organizationId, collection.id);
|
||||
await this.collectionService.delete(collection.id);
|
||||
this.platformUtilsService.showToast(
|
||||
"success",
|
||||
null,
|
||||
this.i18nService.t("deletedCollectionId", collection.name),
|
||||
);
|
||||
|
||||
this.toastService.showToast({
|
||||
variant: "success",
|
||||
title: null,
|
||||
message: this.i18nService.t("deletedCollectionId", collection.name),
|
||||
});
|
||||
// Navigate away if we deleted the collection we were viewing
|
||||
if (this.selectedCollection?.node.id === collection.id) {
|
||||
void this.router.navigate([], {
|
||||
await this.router.navigate([], {
|
||||
queryParams: { collectionId: this.selectedCollection.parent?.node.id ?? null },
|
||||
queryParamsHandling: "merge",
|
||||
replaceUrl: true,
|
||||
@ -722,11 +744,11 @@ export class VaultComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
if (ciphers.length === 0) {
|
||||
this.platformUtilsService.showToast(
|
||||
"error",
|
||||
this.i18nService.t("errorOccurred"),
|
||||
this.i18nService.t("nothingSelected"),
|
||||
);
|
||||
this.toastService.showToast({
|
||||
variant: "error",
|
||||
title: this.i18nService.t("errorOccurred"),
|
||||
message: this.i18nService.t("nothingSelected"),
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
@ -790,7 +812,11 @@ export class VaultComponent implements OnInit, OnDestroy {
|
||||
|
||||
try {
|
||||
await this.cipherService.restoreWithServer(c.id);
|
||||
this.platformUtilsService.showToast("success", null, this.i18nService.t("restoredItem"));
|
||||
this.toastService.showToast({
|
||||
variant: "success",
|
||||
title: null,
|
||||
message: this.i18nService.t("restoredItem"),
|
||||
});
|
||||
this.refresh();
|
||||
} catch (e) {
|
||||
this.logService.error(e);
|
||||
@ -809,12 +835,20 @@ export class VaultComponent implements OnInit, OnDestroy {
|
||||
|
||||
const selectedCipherIds = ciphers.map((cipher) => cipher.id);
|
||||
if (selectedCipherIds.length === 0) {
|
||||
this.platformUtilsService.showToast("error", null, this.i18nService.t("nothingSelected"));
|
||||
this.toastService.showToast({
|
||||
variant: "error",
|
||||
title: null,
|
||||
message: this.i18nService.t("nothingSelected"),
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
await this.cipherService.restoreManyWithServer(selectedCipherIds);
|
||||
this.platformUtilsService.showToast("success", null, this.i18nService.t("restoredItems"));
|
||||
this.toastService.showToast({
|
||||
variant: "success",
|
||||
title: null,
|
||||
message: this.i18nService.t("restoredItems"),
|
||||
});
|
||||
this.refresh();
|
||||
}
|
||||
|
||||
@ -862,11 +896,12 @@ export class VaultComponent implements OnInit, OnDestroy {
|
||||
|
||||
try {
|
||||
await this.deleteCipherWithServer(c.id, permanent);
|
||||
this.platformUtilsService.showToast(
|
||||
"success",
|
||||
null,
|
||||
this.i18nService.t(permanent ? "permanentlyDeletedItem" : "deletedItem"),
|
||||
);
|
||||
|
||||
this.toastService.showToast({
|
||||
variant: "success",
|
||||
title: null,
|
||||
message: this.i18nService.t(permanent ? "permanentlyDeletedItem" : "deletedItem"),
|
||||
});
|
||||
this.refresh();
|
||||
} catch (e) {
|
||||
this.logService.error(e);
|
||||
@ -883,7 +918,11 @@ export class VaultComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
if (ciphers.length === 0 && collections.length === 0) {
|
||||
this.platformUtilsService.showToast("error", null, this.i18nService.t("nothingSelected"));
|
||||
this.toastService.showToast({
|
||||
variant: "error",
|
||||
title: null,
|
||||
message: this.i18nService.t("nothingSelected"),
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
@ -926,7 +965,11 @@ export class VaultComponent implements OnInit, OnDestroy {
|
||||
|
||||
const selectedCipherIds = ciphers.map((cipher) => cipher.id);
|
||||
if (selectedCipherIds.length === 0) {
|
||||
this.platformUtilsService.showToast("error", null, this.i18nService.t("nothingSelected"));
|
||||
this.toastService.showToast({
|
||||
variant: "error",
|
||||
title: null,
|
||||
message: this.i18nService.t("nothingSelected"),
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
@ -958,7 +1001,11 @@ export class VaultComponent implements OnInit, OnDestroy {
|
||||
value = await this.totpService.getCode(cipher.login.totp);
|
||||
typeI18nKey = "verificationCodeTotp";
|
||||
} else {
|
||||
this.platformUtilsService.showToast("info", null, this.i18nService.t("unexpectedError"));
|
||||
this.toastService.showToast({
|
||||
variant: "error",
|
||||
title: null,
|
||||
message: this.i18nService.t("unexpectedError"),
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
@ -974,20 +1021,19 @@ export class VaultComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
this.platformUtilsService.copyToClipboard(value, { window: window });
|
||||
this.platformUtilsService.showToast(
|
||||
"info",
|
||||
null,
|
||||
this.i18nService.t("valueCopied", this.i18nService.t(typeI18nKey)),
|
||||
);
|
||||
this.toastService.showToast({
|
||||
variant: "info",
|
||||
title: null,
|
||||
message: this.i18nService.t("valueCopied", this.i18nService.t(typeI18nKey)),
|
||||
});
|
||||
|
||||
if (field === "password") {
|
||||
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
this.eventCollectionService.collect(EventType.Cipher_ClientCopiedPassword, cipher.id);
|
||||
await this.eventCollectionService.collect(EventType.Cipher_ClientCopiedPassword, cipher.id);
|
||||
} else if (field === "totp") {
|
||||
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
this.eventCollectionService.collect(EventType.Cipher_ClientCopiedHiddenField, cipher.id);
|
||||
await this.eventCollectionService.collect(
|
||||
EventType.Cipher_ClientCopiedHiddenField,
|
||||
cipher.id,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1006,7 +1052,11 @@ export class VaultComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
if (ciphers.length === 0) {
|
||||
this.platformUtilsService.showToast("error", null, this.i18nService.t("nothingSelected"));
|
||||
this.toastService.showToast({
|
||||
variant: "error",
|
||||
title: null,
|
||||
message: this.i18nService.t("nothingSelected"),
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
@ -1056,9 +1106,7 @@ export class VaultComponent implements OnInit, OnDestroy {
|
||||
};
|
||||
}
|
||||
|
||||
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
this.router.navigate([], {
|
||||
void this.router.navigate([], {
|
||||
relativeTo: this.route,
|
||||
queryParams: queryParams,
|
||||
queryParamsHandling: "merge",
|
||||
|
@ -1,30 +1,18 @@
|
||||
import { NgModule } from "@angular/core";
|
||||
|
||||
import { BannerModule, BreadcrumbsModule } from "@bitwarden/components";
|
||||
|
||||
import { VerifyEmailComponent } from "../../auth/settings/verify-email.component";
|
||||
import { LooseComponentsModule, SharedModule } from "../../shared";
|
||||
import { CollectionDialogModule } from "../components/collection-dialog";
|
||||
import { VaultItemsModule } from "../components/vault-items/vault-items.module";
|
||||
import { CollectionBadgeModule } from "../org-vault/collection-badge/collection-badge.module";
|
||||
import { GroupBadgeModule } from "../org-vault/group-badge/group-badge.module";
|
||||
|
||||
import { BulkDialogsModule } from "./bulk-action-dialogs/bulk-dialogs.module";
|
||||
import { OrganizationBadgeModule } from "./organization-badge/organization-badge.module";
|
||||
import { PipesModule } from "./pipes/pipes.module";
|
||||
import { VaultBannersService } from "./vault-banners/services/vault-banners.service";
|
||||
import { VaultBannersComponent } from "./vault-banners/vault-banners.component";
|
||||
import { VaultFilterModule } from "./vault-filter/vault-filter.module";
|
||||
import { VaultHeaderComponent } from "./vault-header/vault-header.component";
|
||||
import { VaultOnboardingService as VaultOnboardingServiceAbstraction } from "./vault-onboarding/services/abstraction/vault-onboarding.service";
|
||||
import { VaultOnboardingService } from "./vault-onboarding/services/vault-onboarding.service";
|
||||
import { VaultOnboardingComponent } from "./vault-onboarding/vault-onboarding.component";
|
||||
import { VaultRoutingModule } from "./vault-routing.module";
|
||||
import { VaultComponent } from "./vault.component";
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
VaultFilterModule,
|
||||
VaultRoutingModule,
|
||||
OrganizationBadgeModule,
|
||||
GroupBadgeModule,
|
||||
@ -33,21 +21,8 @@ import { VaultComponent } from "./vault.component";
|
||||
SharedModule,
|
||||
LooseComponentsModule,
|
||||
BulkDialogsModule,
|
||||
BreadcrumbsModule,
|
||||
VaultItemsModule,
|
||||
CollectionDialogModule,
|
||||
VaultOnboardingComponent,
|
||||
BannerModule,
|
||||
VerifyEmailComponent,
|
||||
],
|
||||
declarations: [VaultComponent, VaultHeaderComponent, VaultBannersComponent],
|
||||
exports: [VaultComponent],
|
||||
providers: [
|
||||
VaultBannersService,
|
||||
{
|
||||
provide: VaultOnboardingServiceAbstraction,
|
||||
useClass: VaultOnboardingService,
|
||||
},
|
||||
VaultComponent,
|
||||
],
|
||||
})
|
||||
export class VaultModule {}
|
||||
|
@ -1,7 +1,9 @@
|
||||
import { CommonModule } from "@angular/common";
|
||||
import { Component, EventEmitter, Input, OnInit, Output } from "@angular/core";
|
||||
import { Router } from "@angular/router";
|
||||
import { firstValueFrom } from "rxjs";
|
||||
|
||||
import { JslibModule } from "@bitwarden/angular/jslib.module";
|
||||
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
|
||||
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
|
||||
import { ProductTierType } from "@bitwarden/common/billing/enums";
|
||||
@ -9,8 +11,16 @@ import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
|
||||
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { TreeNode } from "@bitwarden/common/vault/models/domain/tree-node";
|
||||
import { DialogService, SimpleDialogOptions } from "@bitwarden/components";
|
||||
import {
|
||||
DialogService,
|
||||
SimpleDialogOptions,
|
||||
BreadcrumbsModule,
|
||||
MenuModule,
|
||||
SearchModule,
|
||||
} from "@bitwarden/components";
|
||||
|
||||
import { HeaderModule } from "../../../layouts/header/header.module";
|
||||
import { SharedModule } from "../../../shared";
|
||||
import { CollectionAdminView } from "../../../vault/core/views/collection-admin.view";
|
||||
import { CollectionDialogTabType } from "../../components/collection-dialog";
|
||||
import { CollectionAdminService } from "../../core/collection-admin.service";
|
||||
@ -21,8 +31,18 @@ import {
|
||||
} from "../../individual-vault/vault-filter/shared/models/routed-vault-filter.model";
|
||||
|
||||
@Component({
|
||||
standalone: true,
|
||||
selector: "app-org-vault-header",
|
||||
templateUrl: "./vault-header.component.html",
|
||||
imports: [
|
||||
CommonModule,
|
||||
MenuModule,
|
||||
SharedModule,
|
||||
BreadcrumbsModule,
|
||||
HeaderModule,
|
||||
SearchModule,
|
||||
JslibModule,
|
||||
],
|
||||
})
|
||||
export class VaultHeaderComponent implements OnInit {
|
||||
protected All = All;
|
||||
|
@ -58,11 +58,12 @@ import { TreeNode } from "@bitwarden/common/vault/models/domain/tree-node";
|
||||
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
|
||||
import { CollectionView } from "@bitwarden/common/vault/models/view/collection.view";
|
||||
import { ServiceUtils } from "@bitwarden/common/vault/service-utils";
|
||||
import { DialogService, Icons, ToastService } from "@bitwarden/components";
|
||||
import { DialogService, Icons, NoItemsModule, ToastService } from "@bitwarden/components";
|
||||
import { CollectionAssignmentResult, PasswordRepromptService } from "@bitwarden/vault";
|
||||
|
||||
import { GroupService, GroupView } from "../../admin-console/organizations/core";
|
||||
import { openEntityEventsDialog } from "../../admin-console/organizations/manage/entity-events.component";
|
||||
import { SharedModule } from "../../shared";
|
||||
import { VaultFilterService } from "../../vault/individual-vault/vault-filter/services/abstractions/vault-filter.service";
|
||||
import { VaultFilter } from "../../vault/individual-vault/vault-filter/shared/models/vault-filter.model";
|
||||
import { AssignCollectionsWebComponent } from "../components/assign-collections";
|
||||
@ -72,6 +73,7 @@ import {
|
||||
openCollectionDialog,
|
||||
} from "../components/collection-dialog";
|
||||
import { VaultItemEvent } from "../components/vault-items/vault-item-event";
|
||||
import { VaultItemsModule } from "../components/vault-items/vault-items.module";
|
||||
import { CollectionAdminService } from "../core/collection-admin.service";
|
||||
import { CollectionAdminView } from "../core/views/collection-admin.view";
|
||||
import {
|
||||
@ -87,6 +89,7 @@ import {
|
||||
RoutedVaultFilterModel,
|
||||
Unassigned,
|
||||
} from "../individual-vault/vault-filter/shared/models/routed-vault-filter.model";
|
||||
import { VaultHeaderComponent } from "../org-vault/vault-header/vault-header.component";
|
||||
import { getNestedCollectionTree } from "../utils/collection-utils";
|
||||
|
||||
import { AddEditComponent } from "./add-edit.component";
|
||||
@ -95,7 +98,9 @@ import {
|
||||
BulkCollectionsDialogComponent,
|
||||
BulkCollectionsDialogResult,
|
||||
} from "./bulk-collections-dialog";
|
||||
import { CollectionAccessRestrictedComponent } from "./collection-access-restricted.component";
|
||||
import { openOrgVaultCollectionsDialog } from "./collections.component";
|
||||
import { VaultFilterModule } from "./vault-filter/vault-filter.module";
|
||||
|
||||
const BroadcasterSubscriptionId = "OrgVaultComponent";
|
||||
const SearchTextDebounceInterval = 200;
|
||||
@ -106,8 +111,17 @@ enum AddAccessStatusType {
|
||||
}
|
||||
|
||||
@Component({
|
||||
standalone: true,
|
||||
selector: "app-org-vault",
|
||||
templateUrl: "vault.component.html",
|
||||
imports: [
|
||||
VaultHeaderComponent,
|
||||
CollectionAccessRestrictedComponent,
|
||||
VaultFilterModule,
|
||||
VaultItemsModule,
|
||||
SharedModule,
|
||||
NoItemsModule,
|
||||
],
|
||||
providers: [RoutedVaultFilterService, RoutedVaultFilterBridgeService],
|
||||
})
|
||||
export class VaultComponent implements OnInit, OnDestroy {
|
||||
@ -577,7 +591,11 @@ export class VaultComponent implements OnInit, OnDestroy {
|
||||
if (canEditCipher) {
|
||||
await this.editCipherId(cipherId);
|
||||
} else {
|
||||
this.platformUtilsService.showToast("error", null, this.i18nService.t("unknownCipher"));
|
||||
this.toastService.showToast({
|
||||
variant: "error",
|
||||
title: null,
|
||||
message: this.i18nService.t("unknownCipher"),
|
||||
});
|
||||
await this.router.navigate([], {
|
||||
queryParams: { cipherId: null, itemId: null },
|
||||
queryParamsHandling: "merge",
|
||||
@ -598,14 +616,14 @@ export class VaultComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
const cipher = allCiphers$.find((c) => c.id === cipherId);
|
||||
if (organization.useEvents && cipher != undefined) {
|
||||
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
this.viewEvents(cipher);
|
||||
await this.viewEvents(cipher);
|
||||
} else {
|
||||
this.platformUtilsService.showToast("error", null, this.i18nService.t("unknownCipher"));
|
||||
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
this.router.navigate([], {
|
||||
this.toastService.showToast({
|
||||
variant: "error",
|
||||
title: null,
|
||||
message: this.i18nService.t("unknownCipher"),
|
||||
});
|
||||
await this.router.navigate([], {
|
||||
queryParams: { viewEvents: null },
|
||||
queryParamsHandling: "merge",
|
||||
});
|
||||
@ -686,50 +704,65 @@ export class VaultComponent implements OnInit, OnDestroy {
|
||||
this.processingEvent = true;
|
||||
|
||||
try {
|
||||
if (event.type === "viewAttachments") {
|
||||
await this.editCipherAttachments(event.item);
|
||||
} else if (event.type === "viewCipherCollections") {
|
||||
await this.editCipherCollections(event.item);
|
||||
} else if (event.type === "clone") {
|
||||
await this.cloneCipher(event.item);
|
||||
} else if (event.type === "restore") {
|
||||
if (event.items.length === 1) {
|
||||
await this.restore(event.items[0]);
|
||||
} else {
|
||||
await this.bulkRestore(event.items);
|
||||
switch (event.type) {
|
||||
case "viewAttachments":
|
||||
await this.editCipherAttachments(event.item);
|
||||
break;
|
||||
case "viewCipherCollections":
|
||||
await this.editCipherCollections(event.item);
|
||||
break;
|
||||
case "clone":
|
||||
await this.cloneCipher(event.item);
|
||||
break;
|
||||
case "restore":
|
||||
if (event.items.length === 1) {
|
||||
await this.restore(event.items[0]);
|
||||
} else {
|
||||
await this.bulkRestore(event.items);
|
||||
}
|
||||
break;
|
||||
case "delete": {
|
||||
const ciphers = event.items
|
||||
.filter((i) => i.collection === undefined)
|
||||
.map((i) => i.cipher);
|
||||
const collections = event.items
|
||||
.filter((i) => i.cipher === undefined)
|
||||
.map((i) => i.collection);
|
||||
if (ciphers.length === 1 && collections.length === 0) {
|
||||
await this.deleteCipher(ciphers[0]);
|
||||
} else if (ciphers.length === 0 && collections.length === 1) {
|
||||
await this.deleteCollection(collections[0] as CollectionAdminView);
|
||||
} else {
|
||||
await this.bulkDelete(ciphers, collections, this.organization);
|
||||
}
|
||||
break;
|
||||
}
|
||||
} else if (event.type === "delete") {
|
||||
const ciphers = event.items.filter((i) => i.collection === undefined).map((i) => i.cipher);
|
||||
const collections = event.items
|
||||
.filter((i) => i.cipher === undefined)
|
||||
.map((i) => i.collection);
|
||||
if (ciphers.length === 1 && collections.length === 0) {
|
||||
await this.deleteCipher(ciphers[0]);
|
||||
} else if (ciphers.length === 0 && collections.length === 1) {
|
||||
await this.deleteCollection(collections[0] as CollectionAdminView);
|
||||
} else {
|
||||
await this.bulkDelete(ciphers, collections, this.organization);
|
||||
}
|
||||
} else if (event.type === "copyField") {
|
||||
await this.copy(event.item, event.field);
|
||||
} else if (event.type === "editCollection") {
|
||||
await this.editCollection(
|
||||
event.item as CollectionAdminView,
|
||||
CollectionDialogTabType.Info,
|
||||
event.readonly,
|
||||
);
|
||||
} else if (event.type === "viewCollectionAccess") {
|
||||
await this.editCollection(
|
||||
event.item as CollectionAdminView,
|
||||
CollectionDialogTabType.Access,
|
||||
event.readonly,
|
||||
);
|
||||
} else if (event.type === "bulkEditCollectionAccess") {
|
||||
await this.bulkEditCollectionAccess(event.items, this.organization);
|
||||
} else if (event.type === "assignToCollections") {
|
||||
await this.bulkAssignToCollections(event.items);
|
||||
} else if (event.type === "viewEvents") {
|
||||
await this.viewEvents(event.item);
|
||||
case "copyField":
|
||||
await this.copy(event.item, event.field);
|
||||
break;
|
||||
case "editCollection":
|
||||
await this.editCollection(
|
||||
event.item as CollectionAdminView,
|
||||
CollectionDialogTabType.Info,
|
||||
event.readonly,
|
||||
);
|
||||
break;
|
||||
case "viewCollectionAccess":
|
||||
await this.editCollection(
|
||||
event.item as CollectionAdminView,
|
||||
CollectionDialogTabType.Access,
|
||||
event.readonly,
|
||||
);
|
||||
break;
|
||||
case "bulkEditCollectionAccess":
|
||||
await this.bulkEditCollectionAccess(event.items, this.organization);
|
||||
break;
|
||||
case "assignToCollections":
|
||||
await this.bulkAssignToCollections(event.items);
|
||||
break;
|
||||
case "viewEvents":
|
||||
await this.viewEvents(event.item);
|
||||
break;
|
||||
}
|
||||
} finally {
|
||||
this.processingEvent = false;
|
||||
@ -962,7 +995,11 @@ export class VaultComponent implements OnInit, OnDestroy {
|
||||
this.organization?.canEditAnyCollection(this.flexibleCollectionsV1Enabled) ||
|
||||
c.isUnassigned;
|
||||
await this.cipherService.restoreWithServer(c.id, asAdmin);
|
||||
this.platformUtilsService.showToast("success", null, this.i18nService.t("restoredItem"));
|
||||
this.toastService.showToast({
|
||||
variant: "success",
|
||||
title: null,
|
||||
message: this.i18nService.t("restoredItem"),
|
||||
});
|
||||
this.refresh();
|
||||
} catch (e) {
|
||||
this.logService.error(e);
|
||||
@ -1008,11 +1045,11 @@ export class VaultComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
if (unassignedCiphers.length === 0 && editAccessCiphers.length === 0) {
|
||||
this.platformUtilsService.showToast(
|
||||
"error",
|
||||
this.i18nService.t("errorOccurred"),
|
||||
this.i18nService.t("nothingSelected"),
|
||||
);
|
||||
this.toastService.showToast({
|
||||
variant: "error",
|
||||
title: this.i18nService.t("errorOccurred"),
|
||||
message: this.i18nService.t("nothingSelected"),
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
@ -1023,7 +1060,11 @@ export class VaultComponent implements OnInit, OnDestroy {
|
||||
);
|
||||
}
|
||||
|
||||
this.platformUtilsService.showToast("success", null, this.i18nService.t("restoredItems"));
|
||||
this.toastService.showToast({
|
||||
variant: "success",
|
||||
title: null,
|
||||
message: this.i18nService.t("restoredItems"),
|
||||
});
|
||||
this.refresh();
|
||||
}
|
||||
|
||||
@ -1058,11 +1099,11 @@ export class VaultComponent implements OnInit, OnDestroy {
|
||||
|
||||
try {
|
||||
await this.deleteCipherWithServer(c.id, permanent, c.isUnassigned);
|
||||
this.platformUtilsService.showToast(
|
||||
"success",
|
||||
null,
|
||||
this.i18nService.t(permanent ? "permanentlyDeletedItem" : "deletedItem"),
|
||||
);
|
||||
this.toastService.showToast({
|
||||
variant: "success",
|
||||
title: null,
|
||||
message: this.i18nService.t(permanent ? "permanentlyDeletedItem" : "deletedItem"),
|
||||
});
|
||||
this.refresh();
|
||||
} catch (e) {
|
||||
this.logService.error(e);
|
||||
@ -1085,11 +1126,11 @@ export class VaultComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
try {
|
||||
await this.apiService.deleteCollection(this.organization?.id, collection.id);
|
||||
this.platformUtilsService.showToast(
|
||||
"success",
|
||||
null,
|
||||
this.i18nService.t("deletedCollectionId", collection.name),
|
||||
);
|
||||
this.toastService.showToast({
|
||||
variant: "success",
|
||||
title: null,
|
||||
message: this.i18nService.t("deletedCollectionId", collection.name),
|
||||
});
|
||||
|
||||
// Navigate away if we deleted the collection we were viewing
|
||||
if (this.selectedCollection?.node.id === collection.id) {
|
||||
@ -1128,7 +1169,11 @@ export class VaultComponent implements OnInit, OnDestroy {
|
||||
});
|
||||
|
||||
if (ciphers.length === 0 && collections.length === 0) {
|
||||
this.platformUtilsService.showToast("error", null, this.i18nService.t("nothingSelected"));
|
||||
this.toastService.showToast({
|
||||
variant: "error",
|
||||
title: null,
|
||||
message: this.i18nService.t("nothingSelected"),
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
@ -1182,7 +1227,11 @@ export class VaultComponent implements OnInit, OnDestroy {
|
||||
value = await this.totpService.getCode(cipher.login.totp);
|
||||
typeI18nKey = "verificationCodeTotp";
|
||||
} else {
|
||||
this.platformUtilsService.showToast("info", null, this.i18nService.t("unexpectedError"));
|
||||
this.toastService.showToast({
|
||||
variant: "error",
|
||||
title: null,
|
||||
message: this.i18nService.t("unexpectedError"),
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
@ -1198,20 +1247,19 @@ export class VaultComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
this.platformUtilsService.copyToClipboard(value, { window: window });
|
||||
this.platformUtilsService.showToast(
|
||||
"info",
|
||||
null,
|
||||
this.i18nService.t("valueCopied", this.i18nService.t(typeI18nKey)),
|
||||
);
|
||||
this.toastService.showToast({
|
||||
variant: "info",
|
||||
title: null,
|
||||
message: this.i18nService.t("valueCopied", this.i18nService.t(typeI18nKey)),
|
||||
});
|
||||
|
||||
if (field === "password") {
|
||||
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
this.eventCollectionService.collect(EventType.Cipher_ClientCopiedPassword, cipher.id);
|
||||
await this.eventCollectionService.collect(EventType.Cipher_ClientCopiedPassword, cipher.id);
|
||||
} else if (field === "totp") {
|
||||
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
this.eventCollectionService.collect(EventType.Cipher_ClientCopiedHiddenField, cipher.id);
|
||||
await this.eventCollectionService.collect(
|
||||
EventType.Cipher_ClientCopiedHiddenField,
|
||||
cipher.id,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1310,7 +1358,11 @@ export class VaultComponent implements OnInit, OnDestroy {
|
||||
|
||||
async bulkAssignToCollections(items: CipherView[]) {
|
||||
if (items.length === 0) {
|
||||
this.platformUtilsService.showToast("error", null, this.i18nService.t("nothingSelected"));
|
||||
this.toastService.showToast({
|
||||
variant: "error",
|
||||
title: null,
|
||||
message: this.i18nService.t("nothingSelected"),
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
@ -1381,9 +1433,7 @@ export class VaultComponent implements OnInit, OnDestroy {
|
||||
};
|
||||
}
|
||||
|
||||
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
this.router.navigate([], {
|
||||
void this.router.navigate([], {
|
||||
relativeTo: this.route,
|
||||
queryParams: queryParams,
|
||||
queryParamsHandling: "merge",
|
||||
|
@ -1,40 +1,25 @@
|
||||
import { NgModule } from "@angular/core";
|
||||
|
||||
import { BreadcrumbsModule, NoItemsModule, SearchModule } from "@bitwarden/components";
|
||||
|
||||
import { LooseComponentsModule } from "../../shared/loose-components.module";
|
||||
import { SharedModule } from "../../shared/shared.module";
|
||||
import { OrganizationBadgeModule } from "../../vault/individual-vault/organization-badge/organization-badge.module";
|
||||
import { PipesModule } from "../../vault/individual-vault/pipes/pipes.module";
|
||||
import { CollectionDialogModule } from "../components/collection-dialog";
|
||||
import { VaultItemsModule } from "../components/vault-items/vault-items.module";
|
||||
|
||||
import { CollectionAccessRestrictedComponent } from "./collection-access-restricted.component";
|
||||
import { CollectionBadgeModule } from "./collection-badge/collection-badge.module";
|
||||
import { GroupBadgeModule } from "./group-badge/group-badge.module";
|
||||
import { VaultFilterModule } from "./vault-filter/vault-filter.module";
|
||||
import { VaultHeaderComponent } from "./vault-header/vault-header.component";
|
||||
import { VaultRoutingModule } from "./vault-routing.module";
|
||||
import { VaultComponent } from "./vault.component";
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
VaultRoutingModule,
|
||||
VaultFilterModule,
|
||||
SharedModule,
|
||||
LooseComponentsModule,
|
||||
GroupBadgeModule,
|
||||
CollectionBadgeModule,
|
||||
OrganizationBadgeModule,
|
||||
PipesModule,
|
||||
BreadcrumbsModule,
|
||||
VaultItemsModule,
|
||||
CollectionDialogModule,
|
||||
CollectionAccessRestrictedComponent,
|
||||
NoItemsModule,
|
||||
SearchModule,
|
||||
VaultComponent,
|
||||
],
|
||||
declarations: [VaultComponent, VaultHeaderComponent],
|
||||
exports: [VaultComponent],
|
||||
})
|
||||
export class VaultModule {}
|
||||
|
Loading…
Reference in New Issue
Block a user