mirror of
https://github.com/bitwarden/browser
synced 2025-12-23 19:53:43 +00:00
[SG-998] Move org vault to vault team (#4681)
* Rename vault folder to org-vault * Move org-vault folder to vault * Rename nested vault folder to individual-vault * Fix vault module imports * Undo desktop imports * Remove extra app folder * Add @bitwarden/team-vault-dev code owners * Update .github/CODEOWNERS Co-authored-by: Matt Gibson <mgibson@bitwarden.com> * Remove eslint ignore comments --------- Co-authored-by: Matt Gibson <mgibson@bitwarden.com>
This commit is contained in:
@@ -24,9 +24,7 @@ import { StateFactory } from "@bitwarden/common/factories/stateFactory";
|
||||
import { MemoryStorageService } from "@bitwarden/common/services/memoryStorage.service";
|
||||
import { PasswordRepromptService as PasswordRepromptServiceAbstraction } from "@bitwarden/common/vault/abstractions/password-reprompt.service";
|
||||
|
||||
// TODO refine elsint rule for **/app/core/*
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { PasswordRepromptService } from "../../vault/app/core/password-reprompt.service";
|
||||
import { PasswordRepromptService } from "../../vault/core/password-reprompt.service";
|
||||
|
||||
import { BroadcasterMessagingService } from "./broadcaster-messaging.service";
|
||||
import { EventService } from "./event.service";
|
||||
|
||||
@@ -12,13 +12,14 @@ import {
|
||||
} from "@bitwarden/common/abstractions/organization/organization.service.abstraction";
|
||||
import { Organization } from "@bitwarden/common/models/domain/organization";
|
||||
|
||||
import { VaultModule } from "../../vault/org-vault/vault.module";
|
||||
|
||||
import { OrganizationPermissionsGuard } from "./guards/org-permissions.guard";
|
||||
import { OrganizationRedirectGuard } from "./guards/org-redirect.guard";
|
||||
import { OrganizationLayoutComponent } from "./layouts/organization-layout.component";
|
||||
import { CollectionsComponent } from "./manage/collections.component";
|
||||
import { GroupsComponent } from "./manage/groups.component";
|
||||
import { ManageComponent } from "./manage/manage.component";
|
||||
import { VaultModule } from "./vault/vault.module";
|
||||
|
||||
const routes: Routes = [
|
||||
{
|
||||
|
||||
@@ -1,133 +0,0 @@
|
||||
import { Component } from "@angular/core";
|
||||
|
||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||
import { AuditService } from "@bitwarden/common/abstractions/audit.service";
|
||||
import { CollectionService } from "@bitwarden/common/abstractions/collection.service";
|
||||
import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service";
|
||||
import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
|
||||
import { LogService } from "@bitwarden/common/abstractions/log.service";
|
||||
import { MessagingService } from "@bitwarden/common/abstractions/messaging.service";
|
||||
import { OrganizationService } from "@bitwarden/common/abstractions/organization/organization.service.abstraction";
|
||||
import { PasswordGenerationService } from "@bitwarden/common/abstractions/passwordGeneration.service";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service";
|
||||
import { PolicyService } from "@bitwarden/common/abstractions/policy/policy.service.abstraction";
|
||||
import { StateService } from "@bitwarden/common/abstractions/state.service";
|
||||
import { TotpService } from "@bitwarden/common/abstractions/totp.service";
|
||||
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
||||
import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction";
|
||||
import { PasswordRepromptService } from "@bitwarden/common/vault/abstractions/password-reprompt.service";
|
||||
import { CipherData } from "@bitwarden/common/vault/models/data/cipher.data";
|
||||
import { Cipher } from "@bitwarden/common/vault/models/domain/cipher";
|
||||
import { CipherCreateRequest } from "@bitwarden/common/vault/models/request/cipher-create.request";
|
||||
import { CipherRequest } from "@bitwarden/common/vault/models/request/cipher.request";
|
||||
|
||||
import { AddEditComponent as BaseAddEditComponent } from "../../../vault/app/vault/add-edit.component";
|
||||
|
||||
@Component({
|
||||
selector: "app-org-vault-add-edit",
|
||||
templateUrl: "../../../vault/app/vault/add-edit.component.html",
|
||||
})
|
||||
export class AddEditComponent extends BaseAddEditComponent {
|
||||
originalCipher: Cipher = null;
|
||||
protected override componentName = "app-org-vault-add-edit";
|
||||
|
||||
constructor(
|
||||
cipherService: CipherService,
|
||||
folderService: FolderService,
|
||||
i18nService: I18nService,
|
||||
platformUtilsService: PlatformUtilsService,
|
||||
auditService: AuditService,
|
||||
stateService: StateService,
|
||||
collectionService: CollectionService,
|
||||
totpService: TotpService,
|
||||
passwordGenerationService: PasswordGenerationService,
|
||||
private apiService: ApiService,
|
||||
messagingService: MessagingService,
|
||||
eventCollectionService: EventCollectionService,
|
||||
policyService: PolicyService,
|
||||
logService: LogService,
|
||||
passwordRepromptService: PasswordRepromptService,
|
||||
organizationService: OrganizationService
|
||||
) {
|
||||
super(
|
||||
cipherService,
|
||||
folderService,
|
||||
i18nService,
|
||||
platformUtilsService,
|
||||
auditService,
|
||||
stateService,
|
||||
collectionService,
|
||||
totpService,
|
||||
passwordGenerationService,
|
||||
messagingService,
|
||||
eventCollectionService,
|
||||
policyService,
|
||||
organizationService,
|
||||
logService,
|
||||
passwordRepromptService
|
||||
);
|
||||
}
|
||||
|
||||
protected allowOwnershipAssignment() {
|
||||
if (
|
||||
this.ownershipOptions != null &&
|
||||
(this.ownershipOptions.length > 1 || !this.allowPersonal)
|
||||
) {
|
||||
if (this.organization != null) {
|
||||
return this.cloneMode && this.organization.canEditAnyCollection;
|
||||
} else {
|
||||
return !this.editMode || this.cloneMode;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
protected loadCollections() {
|
||||
if (!this.organization.canEditAnyCollection) {
|
||||
return super.loadCollections();
|
||||
}
|
||||
return Promise.resolve(this.collections);
|
||||
}
|
||||
|
||||
protected async loadCipher() {
|
||||
if (!this.organization.canEditAnyCollection) {
|
||||
return await super.loadCipher();
|
||||
}
|
||||
const response = await this.apiService.getCipherAdmin(this.cipherId);
|
||||
const data = new CipherData(response);
|
||||
|
||||
data.edit = true;
|
||||
const cipher = new Cipher(data);
|
||||
this.originalCipher = cipher;
|
||||
return cipher;
|
||||
}
|
||||
|
||||
protected encryptCipher() {
|
||||
if (!this.organization.canEditAnyCollection) {
|
||||
return super.encryptCipher();
|
||||
}
|
||||
return this.cipherService.encrypt(this.cipher, null, this.originalCipher);
|
||||
}
|
||||
|
||||
protected async saveCipher(cipher: Cipher) {
|
||||
if (!this.organization.canEditAnyCollection || cipher.organizationId == null) {
|
||||
return super.saveCipher(cipher);
|
||||
}
|
||||
if (this.editMode && !this.cloneMode) {
|
||||
const request = new CipherRequest(cipher);
|
||||
return this.apiService.putCipherAdmin(this.cipherId, request);
|
||||
} else {
|
||||
const request = new CipherCreateRequest(cipher);
|
||||
return this.apiService.postCipherAdmin(request);
|
||||
}
|
||||
}
|
||||
|
||||
protected async deleteCipher() {
|
||||
if (!this.organization.canEditAnyCollection) {
|
||||
return super.deleteCipher();
|
||||
}
|
||||
return this.cipher.isDeleted
|
||||
? this.apiService.deleteCipherAdmin(this.cipherId)
|
||||
: this.apiService.putDeleteCipherAdmin(this.cipherId);
|
||||
}
|
||||
}
|
||||
@@ -1,80 +0,0 @@
|
||||
import { Component } from "@angular/core";
|
||||
|
||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||
import { CryptoService } from "@bitwarden/common/abstractions/crypto.service";
|
||||
import { FileDownloadService } from "@bitwarden/common/abstractions/fileDownload/fileDownload.service";
|
||||
import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
|
||||
import { LogService } from "@bitwarden/common/abstractions/log.service";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service";
|
||||
import { StateService } from "@bitwarden/common/abstractions/state.service";
|
||||
import { Organization } from "@bitwarden/common/models/domain/organization";
|
||||
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
||||
import { CipherData } from "@bitwarden/common/vault/models/data/cipher.data";
|
||||
import { Cipher } from "@bitwarden/common/vault/models/domain/cipher";
|
||||
import { AttachmentView } from "@bitwarden/common/vault/models/view/attachment.view";
|
||||
|
||||
import { AttachmentsComponent as BaseAttachmentsComponent } from "../../../vault/app/vault/attachments.component";
|
||||
|
||||
@Component({
|
||||
selector: "app-org-vault-attachments",
|
||||
templateUrl: "../../../vault/app/vault/attachments.component.html",
|
||||
})
|
||||
export class AttachmentsComponent extends BaseAttachmentsComponent {
|
||||
viewOnly = false;
|
||||
organization: Organization;
|
||||
|
||||
constructor(
|
||||
cipherService: CipherService,
|
||||
i18nService: I18nService,
|
||||
cryptoService: CryptoService,
|
||||
stateService: StateService,
|
||||
platformUtilsService: PlatformUtilsService,
|
||||
apiService: ApiService,
|
||||
logService: LogService,
|
||||
fileDownloadService: FileDownloadService
|
||||
) {
|
||||
super(
|
||||
cipherService,
|
||||
i18nService,
|
||||
cryptoService,
|
||||
stateService,
|
||||
platformUtilsService,
|
||||
apiService,
|
||||
logService,
|
||||
fileDownloadService
|
||||
);
|
||||
}
|
||||
|
||||
protected async reupload(attachment: AttachmentView) {
|
||||
if (this.organization.canEditAnyCollection && this.showFixOldAttachments(attachment)) {
|
||||
await super.reuploadCipherAttachment(attachment, true);
|
||||
}
|
||||
}
|
||||
|
||||
protected async loadCipher() {
|
||||
if (!this.organization.canEditAnyCollection) {
|
||||
return await super.loadCipher();
|
||||
}
|
||||
const response = await this.apiService.getCipherAdmin(this.cipherId);
|
||||
return new Cipher(new CipherData(response));
|
||||
}
|
||||
|
||||
protected saveCipherAttachment(file: File) {
|
||||
return this.cipherService.saveAttachmentWithServer(
|
||||
this.cipherDomain,
|
||||
file,
|
||||
this.organization.canEditAnyCollection
|
||||
);
|
||||
}
|
||||
|
||||
protected deleteCipherAttachment(attachmentId: string) {
|
||||
if (!this.organization.canEditAnyCollection) {
|
||||
return super.deleteCipherAttachment(attachmentId);
|
||||
}
|
||||
return this.apiService.deleteCipherAttachmentAdmin(this.cipherId, attachmentId);
|
||||
}
|
||||
|
||||
protected showFixOldAttachments(attachment: AttachmentView) {
|
||||
return attachment.key == null && this.organization.canEditAnyCollection;
|
||||
}
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
import { NgModule } from "@angular/core";
|
||||
|
||||
import { PipesModule } from "../../../../vault/app/vault/pipes/pipes.module";
|
||||
import { SharedModule } from "../../../shared";
|
||||
|
||||
import { CollectionNameBadgeComponent } from "./collection-name.badge.component";
|
||||
|
||||
@NgModule({
|
||||
imports: [SharedModule, PipesModule],
|
||||
declarations: [CollectionNameBadgeComponent],
|
||||
exports: [CollectionNameBadgeComponent],
|
||||
})
|
||||
export class CollectionBadgeModule {}
|
||||
@@ -1,6 +0,0 @@
|
||||
<ng-container *ngFor="let c of shownCollections">
|
||||
<span bitBadge badgeType="secondary">{{ c | collectionNameFromId: collections }}</span>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="showXMore">
|
||||
<span bitBadge badgeType="secondary">+ {{ xMoreCount }} more</span>
|
||||
</ng-container>
|
||||
@@ -1,24 +0,0 @@
|
||||
import { Component, Input } from "@angular/core";
|
||||
|
||||
import { CollectionView } from "@bitwarden/common/models/view/collection.view";
|
||||
|
||||
@Component({
|
||||
selector: "app-collection-badge",
|
||||
templateUrl: "collection-name-badge.component.html",
|
||||
})
|
||||
export class CollectionNameBadgeComponent {
|
||||
@Input() collectionIds: string[];
|
||||
@Input() collections: CollectionView[];
|
||||
|
||||
get shownCollections(): string[] {
|
||||
return this.showXMore ? this.collectionIds.slice(0, 2) : this.collectionIds;
|
||||
}
|
||||
|
||||
get showXMore(): boolean {
|
||||
return this.collectionIds.length > 3;
|
||||
}
|
||||
|
||||
get xMoreCount(): number {
|
||||
return this.collectionIds.length - 2;
|
||||
}
|
||||
}
|
||||
@@ -11,11 +11,11 @@ import { CipherData } from "@bitwarden/common/vault/models/data/cipher.data";
|
||||
import { Cipher } from "@bitwarden/common/vault/models/domain/cipher";
|
||||
import { CipherCollectionsRequest } from "@bitwarden/common/vault/models/request/cipher-collections.request";
|
||||
|
||||
import { CollectionsComponent as BaseCollectionsComponent } from "../../../vault/app/vault/collections.component";
|
||||
import { CollectionsComponent as BaseCollectionsComponent } from "../../../vault/individual-vault/collections.component";
|
||||
|
||||
@Component({
|
||||
selector: "app-org-vault-collections",
|
||||
templateUrl: "../../../vault/app/vault/collections.component.html",
|
||||
templateUrl: "../../../vault/individual-vault/collections.component.html",
|
||||
})
|
||||
export class CollectionsComponent extends BaseCollectionsComponent {
|
||||
organization: Organization;
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
import { NgModule } from "@angular/core";
|
||||
|
||||
import { PipesModule } from "../../../../vault/app/vault/pipes/pipes.module";
|
||||
import { SharedModule } from "../../../shared";
|
||||
|
||||
import { GroupNameBadgeComponent } from "./group-name-badge.component";
|
||||
|
||||
@NgModule({
|
||||
imports: [SharedModule, PipesModule],
|
||||
declarations: [GroupNameBadgeComponent],
|
||||
exports: [GroupNameBadgeComponent],
|
||||
})
|
||||
export class GroupBadgeModule {}
|
||||
@@ -1 +0,0 @@
|
||||
<bit-badge-list [items]="groupNames" [maxItems]="3" badgeType="secondary"></bit-badge-list>
|
||||
@@ -1,27 +0,0 @@
|
||||
import { Component, Input, OnChanges } from "@angular/core";
|
||||
|
||||
import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
|
||||
import { SelectionReadOnlyRequest } from "@bitwarden/common/models/request/selection-read-only.request";
|
||||
|
||||
import { GroupView } from "../../core";
|
||||
|
||||
@Component({
|
||||
selector: "app-group-badge",
|
||||
templateUrl: "group-name-badge.component.html",
|
||||
})
|
||||
export class GroupNameBadgeComponent implements OnChanges {
|
||||
@Input() selectedGroups: SelectionReadOnlyRequest[];
|
||||
@Input() allGroups: GroupView[];
|
||||
|
||||
protected groupNames: string[] = [];
|
||||
|
||||
constructor(private i18nService: I18nService) {}
|
||||
|
||||
ngOnChanges() {
|
||||
this.groupNames = this.selectedGroups
|
||||
.map((g) => {
|
||||
return this.allGroups.find((o) => o.id === g.id)?.name;
|
||||
})
|
||||
.sort(this.i18nService.collator.compare);
|
||||
}
|
||||
}
|
||||
@@ -1,77 +0,0 @@
|
||||
import { Component, Input, OnDestroy, OnInit } from "@angular/core";
|
||||
import { firstValueFrom, Subject, switchMap, takeUntil } from "rxjs";
|
||||
|
||||
import { Organization } from "@bitwarden/common/models/domain/organization";
|
||||
import { TreeNode } from "@bitwarden/common/models/domain/tree-node";
|
||||
import { CollectionView } from "@bitwarden/common/models/view/collection.view";
|
||||
|
||||
import { VaultFilterComponent as BaseVaultFilterComponent } from "../../../../vault/app/vault/vault-filter/components/vault-filter.component";
|
||||
import {
|
||||
VaultFilterList,
|
||||
VaultFilterType,
|
||||
} from "../../../../vault/app/vault/vault-filter/shared/models/vault-filter-section.type";
|
||||
import { CollectionFilter } from "../../../../vault/app/vault/vault-filter/shared/models/vault-filter.type";
|
||||
|
||||
@Component({
|
||||
selector: "app-organization-vault-filter",
|
||||
templateUrl: "../../../../vault/app/vault/vault-filter/components/vault-filter.component.html",
|
||||
})
|
||||
export class VaultFilterComponent extends BaseVaultFilterComponent implements OnInit, OnDestroy {
|
||||
@Input() set organization(value: Organization) {
|
||||
if (value && value !== this._organization) {
|
||||
this._organization = value;
|
||||
this.vaultFilterService.setOrganizationFilter(this._organization);
|
||||
}
|
||||
}
|
||||
_organization: Organization;
|
||||
protected destroy$: Subject<void>;
|
||||
|
||||
async ngOnInit() {
|
||||
this.filters = await this.buildAllFilters();
|
||||
if (!this.activeFilter.selectedCipherTypeNode) {
|
||||
this.activeFilter.resetFilter();
|
||||
this.activeFilter.selectedCollectionNode =
|
||||
(await this.getDefaultFilter()) as TreeNode<CollectionFilter>;
|
||||
}
|
||||
this.isLoaded = true;
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this.destroy$.next();
|
||||
this.destroy$.complete();
|
||||
}
|
||||
|
||||
protected loadSubscriptions() {
|
||||
this.vaultFilterService.filteredCollections$
|
||||
.pipe(
|
||||
switchMap(async (collections) => {
|
||||
this.removeInvalidCollectionSelection(collections);
|
||||
}),
|
||||
takeUntil(this.destroy$)
|
||||
)
|
||||
.subscribe();
|
||||
}
|
||||
|
||||
protected async removeInvalidCollectionSelection(collections: CollectionView[]) {
|
||||
if (this.activeFilter.selectedCollectionNode) {
|
||||
if (!collections.some((f) => f.id === this.activeFilter.collectionId)) {
|
||||
this.activeFilter.resetFilter();
|
||||
this.activeFilter.selectedCollectionNode =
|
||||
(await this.getDefaultFilter()) as TreeNode<CollectionFilter>;
|
||||
this.applyVaultFilter(this.activeFilter);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async buildAllFilters(): Promise<VaultFilterList> {
|
||||
const builderFilter = {} as VaultFilterList;
|
||||
builderFilter.typeFilter = await this.addTypeFilter(["favorites"]);
|
||||
builderFilter.collectionFilter = await this.addCollectionFilter();
|
||||
builderFilter.trashFilter = await this.addTrashFilter();
|
||||
return builderFilter;
|
||||
}
|
||||
|
||||
async getDefaultFilter(): Promise<TreeNode<VaultFilterType>> {
|
||||
return await firstValueFrom(this.filters?.collectionFilter.data$);
|
||||
}
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
import { NgModule } from "@angular/core";
|
||||
|
||||
import { VaultFilterService as VaultFilterServiceAbstraction } from "../../../../vault/app/vault/vault-filter/services/abstractions/vault-filter.service";
|
||||
import { VaultFilterSharedModule } from "../../../../vault/app/vault/vault-filter/shared/vault-filter-shared.module";
|
||||
|
||||
import { VaultFilterComponent } from "./vault-filter.component";
|
||||
import { VaultFilterService } from "./vault-filter.service";
|
||||
|
||||
@NgModule({
|
||||
imports: [VaultFilterSharedModule],
|
||||
declarations: [VaultFilterComponent],
|
||||
exports: [VaultFilterComponent],
|
||||
providers: [
|
||||
{
|
||||
provide: VaultFilterServiceAbstraction,
|
||||
useClass: VaultFilterService,
|
||||
},
|
||||
],
|
||||
})
|
||||
export class VaultFilterModule {}
|
||||
@@ -1,90 +0,0 @@
|
||||
import { Injectable, OnDestroy } from "@angular/core";
|
||||
import { filter, map, Observable, ReplaySubject, Subject, switchMap, takeUntil } from "rxjs";
|
||||
|
||||
import { CollectionService } from "@bitwarden/common/abstractions/collection.service";
|
||||
import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
|
||||
import {
|
||||
canAccessVaultTab,
|
||||
OrganizationService,
|
||||
} from "@bitwarden/common/abstractions/organization/organization.service.abstraction";
|
||||
import { PolicyService } from "@bitwarden/common/abstractions/policy/policy.service.abstraction";
|
||||
import { StateService } from "@bitwarden/common/abstractions/state.service";
|
||||
import { Organization } from "@bitwarden/common/models/domain/organization";
|
||||
import { TreeNode } from "@bitwarden/common/models/domain/tree-node";
|
||||
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
||||
import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction";
|
||||
|
||||
import { VaultFilterService as BaseVaultFilterService } from "../../../../vault/app/vault/vault-filter/services/vault-filter.service";
|
||||
import { CollectionFilter } from "../../../../vault/app/vault/vault-filter/shared/models/vault-filter.type";
|
||||
import { CollectionAdminView } from "../../core";
|
||||
import { CollectionAdminService } from "../../core/services/collection-admin.service";
|
||||
|
||||
@Injectable()
|
||||
export class VaultFilterService extends BaseVaultFilterService implements OnDestroy {
|
||||
private destroy$ = new Subject<void>();
|
||||
private _collections = new ReplaySubject<CollectionAdminView[]>(1);
|
||||
|
||||
filteredCollections$: Observable<CollectionAdminView[]> = this._collections.asObservable();
|
||||
|
||||
collectionTree$: Observable<TreeNode<CollectionFilter>> = this.filteredCollections$.pipe(
|
||||
map((collections) => this.buildCollectionTree(collections))
|
||||
);
|
||||
|
||||
constructor(
|
||||
stateService: StateService,
|
||||
organizationService: OrganizationService,
|
||||
folderService: FolderService,
|
||||
cipherService: CipherService,
|
||||
collectionService: CollectionService,
|
||||
policyService: PolicyService,
|
||||
i18nService: I18nService,
|
||||
protected collectionAdminService: CollectionAdminService
|
||||
) {
|
||||
super(
|
||||
stateService,
|
||||
organizationService,
|
||||
folderService,
|
||||
cipherService,
|
||||
collectionService,
|
||||
policyService,
|
||||
i18nService
|
||||
);
|
||||
this.loadSubscriptions();
|
||||
}
|
||||
|
||||
protected loadSubscriptions() {
|
||||
this._organizationFilter
|
||||
.pipe(
|
||||
filter((org) => org != null),
|
||||
switchMap((org) => {
|
||||
return this.loadCollections(org);
|
||||
}),
|
||||
takeUntil(this.destroy$)
|
||||
)
|
||||
.subscribe((collections) => {
|
||||
this._collections.next(collections);
|
||||
});
|
||||
}
|
||||
|
||||
async reloadCollections() {
|
||||
this._collections.next(await this.loadCollections(this._organizationFilter.getValue()));
|
||||
}
|
||||
|
||||
protected async loadCollections(org: Organization): Promise<CollectionAdminView[]> {
|
||||
let collections: CollectionAdminView[] = [];
|
||||
if (canAccessVaultTab(org)) {
|
||||
collections = await this.collectionAdminService.getAll(org.id);
|
||||
|
||||
const noneCollection = new CollectionAdminView();
|
||||
noneCollection.name = this.i18nService.t("unassigned");
|
||||
noneCollection.organizationId = org.id;
|
||||
collections.push(noneCollection);
|
||||
}
|
||||
return collections;
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this.destroy$.next();
|
||||
this.destroy$.complete();
|
||||
}
|
||||
}
|
||||
@@ -1,113 +0,0 @@
|
||||
<div class="tw-mb-4 tw-flex tw-items-start tw-justify-between">
|
||||
<div>
|
||||
<bit-breadcrumbs *ngIf="activeFilter.collectionBreadcrumbs.length > 0">
|
||||
<bit-breadcrumb
|
||||
*ngFor="let collection of activeFilter.collectionBreadcrumbs; let first = first"
|
||||
[icon]="first ? undefined : 'bwi-collection'"
|
||||
(click)="applyCollectionFilter(collection)"
|
||||
>
|
||||
<!-- First node in the tree is the "Org Name Vault" item. The rest come from user input. -->
|
||||
<ng-container *ngIf="first">
|
||||
{{ activeOrganizationId | orgNameFromId: (organizations$ | async) }}
|
||||
{{ "vault" | i18n | lowercase }}
|
||||
</ng-container>
|
||||
<ng-container *ngIf="!first">{{ collection.node.name }}</ng-container>
|
||||
</bit-breadcrumb>
|
||||
</bit-breadcrumbs>
|
||||
<h1 class="tw-mb-0 tw-mt-1 tw-flex tw-items-center tw-space-x-2">
|
||||
<i
|
||||
*ngIf="activeFilter.isCollectionSelected"
|
||||
class="bwi bwi-collection"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
<span>{{ title }}</span>
|
||||
<ng-container
|
||||
*ngIf="activeFilter.isCollectionSelected && !activeFilter.isUnassignedCollectionSelected"
|
||||
>
|
||||
<button
|
||||
bitIconButton="bwi-angle-down"
|
||||
[bitMenuTriggerFor]="editCollectionMenu"
|
||||
size="small"
|
||||
type="button"
|
||||
aria-haspopup
|
||||
></button>
|
||||
<bit-menu #editCollectionMenu>
|
||||
<button
|
||||
type="button"
|
||||
*ngIf="canEditCollection(activeFilter.selectedCollectionNode.node)"
|
||||
bitMenuItem
|
||||
(click)="editCollection(activeFilter.selectedCollectionNode.node, 'info')"
|
||||
>
|
||||
<i class="bwi bwi-fw bwi-pencil-square" aria-hidden="true"></i>
|
||||
{{ "editInfo" | i18n }}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
*ngIf="canEditCollection(activeFilter.selectedCollectionNode.node)"
|
||||
bitMenuItem
|
||||
(click)="editCollection(activeFilter.selectedCollectionNode.node, 'access')"
|
||||
>
|
||||
<i class="bwi bwi-fw bwi-users" aria-hidden="true"></i>
|
||||
{{ "access" | i18n }}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
*ngIf="canDeleteCollection(activeFilter.selectedCollectionNode.node)"
|
||||
bitMenuItem
|
||||
(click)="deleteCollection(activeFilter.selectedCollectionNode.node)"
|
||||
>
|
||||
<span class="tw-text-danger">
|
||||
<i class="bwi bwi-fw bwi-trash" aria-hidden="true"></i>
|
||||
{{ "delete" | i18n }}
|
||||
</span>
|
||||
</button>
|
||||
</bit-menu>
|
||||
</ng-container>
|
||||
<small #actionSpinner [appApiAction]="actionPromise">
|
||||
<ng-container *ngIf="$any(actionSpinner).loading">
|
||||
<i
|
||||
class="bwi bwi-spinner bwi-spin text-muted"
|
||||
title="{{ 'loading' | i18n }}"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
<span class="sr-only">{{ "loading" | i18n }}</span>
|
||||
</ng-container>
|
||||
</small>
|
||||
</h1>
|
||||
</div>
|
||||
|
||||
<div *ngIf="!activeFilter.isDeleted" class="tw-shrink-0">
|
||||
<div *ngIf="organization.canCreateNewCollections" appListDropdown>
|
||||
<button
|
||||
bitButton
|
||||
buttonType="primary"
|
||||
type="button"
|
||||
[bitMenuTriggerFor]="addOptions"
|
||||
id="newItemDropdown"
|
||||
appA11yTitle="{{ 'new' | i18n }}"
|
||||
>
|
||||
{{ "new" | i18n }}<i class="bwi bwi-angle-down tw-ml-2" aria-hidden="true"></i>
|
||||
</button>
|
||||
<bit-menu #addOptions aria-labelledby="newItemDropdown">
|
||||
<button type="button" bitMenuItem (click)="addCipher()">
|
||||
<i class="bwi bwi-fw bwi-globe" aria-hidden="true"></i>
|
||||
{{ "item" | i18n }}
|
||||
</button>
|
||||
<button type="button" bitMenuItem (click)="addCollection()">
|
||||
<i class="bwi bwi-fw bwi-collection" aria-hidden="true"></i>
|
||||
{{ "collection" | i18n }}
|
||||
</button>
|
||||
</bit-menu>
|
||||
</div>
|
||||
<button
|
||||
*ngIf="!organization.canCreateNewCollections"
|
||||
type="button"
|
||||
bitButton
|
||||
buttonType="primary"
|
||||
(click)="addCipher()"
|
||||
>
|
||||
<i class="bwi bwi-plus bwi-fw" aria-hidden="true"></i>
|
||||
{{ "newItem" | i18n }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,243 +0,0 @@
|
||||
import { Component, EventEmitter, Input, Output } from "@angular/core";
|
||||
import { Router } from "@angular/router";
|
||||
import { firstValueFrom, lastValueFrom } from "rxjs";
|
||||
|
||||
import { ApiService } from "@bitwarden/common/abstractions/api.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 { ProductType } from "@bitwarden/common/enums/productType";
|
||||
import { Organization } from "@bitwarden/common/models/domain/organization";
|
||||
import { TreeNode } from "@bitwarden/common/models/domain/tree-node";
|
||||
import { CollectionView } from "@bitwarden/common/models/view/collection.view";
|
||||
import {
|
||||
DialogService,
|
||||
SimpleDialogCloseType,
|
||||
SimpleDialogOptions,
|
||||
SimpleDialogType,
|
||||
} from "@bitwarden/components";
|
||||
|
||||
import { VaultFilterService } from "../../../../vault/app/vault/vault-filter/services/abstractions/vault-filter.service";
|
||||
import { VaultFilter } from "../../../../vault/app/vault/vault-filter/shared/models/vault-filter.model";
|
||||
import { CollectionFilter } from "../../../../vault/app/vault/vault-filter/shared/models/vault-filter.type";
|
||||
import { CollectionAdminService, CollectionAdminView } from "../../core";
|
||||
import {
|
||||
CollectionDialogResult,
|
||||
CollectionDialogTabType,
|
||||
openCollectionDialog,
|
||||
} from "../../shared";
|
||||
|
||||
@Component({
|
||||
selector: "app-org-vault-header",
|
||||
templateUrl: "./vault-header.component.html",
|
||||
})
|
||||
export class VaultHeaderComponent {
|
||||
/**
|
||||
* The organization currently being viewed
|
||||
*/
|
||||
@Input() organization: Organization;
|
||||
|
||||
/**
|
||||
* Promise that is used to determine the loading state of the header via the ApiAction directive.
|
||||
* When the promise exists and is not resolved, the loading spinner will be shown.
|
||||
*/
|
||||
@Input() actionPromise: Promise<any>;
|
||||
|
||||
/**
|
||||
* The filter being actively applied to the vault view
|
||||
*/
|
||||
@Input() activeFilter: VaultFilter;
|
||||
|
||||
/**
|
||||
* Emits when the active filter has been modified by the header
|
||||
*/
|
||||
@Output() activeFilterChanged = new EventEmitter<VaultFilter>();
|
||||
|
||||
/**
|
||||
* Emits an event when a collection is modified or deleted via the header collection dropdown menu
|
||||
*/
|
||||
@Output() onCollectionChanged = new EventEmitter<CollectionView | null>();
|
||||
|
||||
/**
|
||||
* Emits an event when the new item button is clicked in the header
|
||||
*/
|
||||
@Output() onAddCipher = new EventEmitter<void>();
|
||||
|
||||
protected organizations$ = this.organizationService.organizations$;
|
||||
|
||||
constructor(
|
||||
private organizationService: OrganizationService,
|
||||
private i18nService: I18nService,
|
||||
private dialogService: DialogService,
|
||||
private vaultFilterService: VaultFilterService,
|
||||
private platformUtilsService: PlatformUtilsService,
|
||||
private apiService: ApiService,
|
||||
private logService: LogService,
|
||||
private collectionAdminService: CollectionAdminService,
|
||||
private router: Router
|
||||
) {}
|
||||
|
||||
/**
|
||||
* The id of the organization that is currently being filtered on.
|
||||
* This can come from a collection filter, organization filter, or the current organization when viewed
|
||||
* in the organization admin console and no other filters are applied.
|
||||
*/
|
||||
get activeOrganizationId() {
|
||||
if (this.activeFilter.selectedCollectionNode != null) {
|
||||
return this.activeFilter.selectedCollectionNode.node.organizationId;
|
||||
}
|
||||
if (this.activeFilter.selectedOrganizationNode != null) {
|
||||
return this.activeFilter.selectedOrganizationNode.node.id;
|
||||
}
|
||||
return this.organization.id;
|
||||
}
|
||||
|
||||
get title() {
|
||||
if (this.activeFilter.isCollectionSelected) {
|
||||
return this.activeFilter.selectedCollectionNode.node.name;
|
||||
}
|
||||
if (this.activeFilter.isUnassignedCollectionSelected) {
|
||||
return this.i18nService.t("unassigned");
|
||||
}
|
||||
return `${this.organization.name} ${this.i18nService.t("vault").toLowerCase()}`;
|
||||
}
|
||||
|
||||
private showFreeOrgUpgradeDialog(): void {
|
||||
const orgUpgradeSimpleDialogOpts: SimpleDialogOptions = {
|
||||
title: this.i18nService.t("upgradeOrganization"),
|
||||
content: this.i18nService.t(
|
||||
this.organization.canManageBilling
|
||||
? "freeOrgMaxCollectionReachedManageBilling"
|
||||
: "freeOrgMaxCollectionReachedNoManageBilling",
|
||||
this.organization.maxCollections
|
||||
),
|
||||
type: SimpleDialogType.PRIMARY,
|
||||
};
|
||||
|
||||
if (this.organization.canManageBilling) {
|
||||
orgUpgradeSimpleDialogOpts.acceptButtonText = this.i18nService.t("upgrade");
|
||||
} else {
|
||||
orgUpgradeSimpleDialogOpts.acceptButtonText = this.i18nService.t("ok");
|
||||
orgUpgradeSimpleDialogOpts.cancelButtonText = null; // hide secondary btn
|
||||
}
|
||||
|
||||
const simpleDialog = this.dialogService.openSimpleDialog(orgUpgradeSimpleDialogOpts);
|
||||
|
||||
firstValueFrom(simpleDialog.closed).then((result: SimpleDialogCloseType | undefined) => {
|
||||
if (!result) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (result == SimpleDialogCloseType.ACCEPT && this.organization.canManageBilling) {
|
||||
this.router.navigate(["/organizations", this.organization.id, "billing", "subscription"], {
|
||||
queryParams: { upgrade: true },
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
applyCollectionFilter(collection: TreeNode<CollectionFilter>) {
|
||||
const filter = this.activeFilter;
|
||||
filter.resetFilter();
|
||||
filter.selectedCollectionNode = collection;
|
||||
this.activeFilterChanged.emit(filter);
|
||||
}
|
||||
|
||||
canEditCollection(c: CollectionAdminView): boolean {
|
||||
// Only edit collections if we're in the org vault and not editing "Unassigned"
|
||||
if (this.organization === undefined || c.id === null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Otherwise, check if we can edit the specified collection
|
||||
return (
|
||||
this.organization.canEditAnyCollection ||
|
||||
(this.organization.canEditAssignedCollections && c.assigned)
|
||||
);
|
||||
}
|
||||
|
||||
addCipher() {
|
||||
this.onAddCipher.emit();
|
||||
}
|
||||
|
||||
async addCollection() {
|
||||
if (this.organization.planProductType === ProductType.Free) {
|
||||
const collections = await this.collectionAdminService.getAll(this.organization.id);
|
||||
if (collections.length === this.organization.maxCollections) {
|
||||
this.showFreeOrgUpgradeDialog();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const dialog = openCollectionDialog(this.dialogService, {
|
||||
data: {
|
||||
organizationId: this.organization?.id,
|
||||
parentCollectionId: this.activeFilter.collectionId,
|
||||
},
|
||||
});
|
||||
const result = await lastValueFrom(dialog.closed);
|
||||
if (result === CollectionDialogResult.Saved || result === CollectionDialogResult.Deleted) {
|
||||
this.onCollectionChanged.emit(null);
|
||||
}
|
||||
}
|
||||
|
||||
async editCollection(c: CollectionView, tab: "info" | "access"): Promise<void> {
|
||||
const tabType = tab == "info" ? CollectionDialogTabType.Info : CollectionDialogTabType.Access;
|
||||
|
||||
const dialog = openCollectionDialog(this.dialogService, {
|
||||
data: { collectionId: c?.id, organizationId: this.organization?.id, initialTab: tabType },
|
||||
});
|
||||
|
||||
const result = await lastValueFrom(dialog.closed);
|
||||
if (result === CollectionDialogResult.Saved || result === CollectionDialogResult.Deleted) {
|
||||
this.onCollectionChanged.emit(c);
|
||||
}
|
||||
}
|
||||
|
||||
canDeleteCollection(c: CollectionAdminView): boolean {
|
||||
// Only delete collections if we're in the org vault and not deleting "Unassigned"
|
||||
if (this.organization === undefined || c.id === null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Otherwise, check if we can delete the specified collection
|
||||
return (
|
||||
this.organization?.canDeleteAnyCollection ||
|
||||
(this.organization?.canDeleteAssignedCollections && c.assigned)
|
||||
);
|
||||
}
|
||||
|
||||
async deleteCollection(collection: CollectionView): Promise<void> {
|
||||
if (!this.organization.canDeleteAssignedCollections) {
|
||||
this.platformUtilsService.showToast(
|
||||
"error",
|
||||
this.i18nService.t("errorOccurred"),
|
||||
this.i18nService.t("missingPermissions")
|
||||
);
|
||||
return;
|
||||
}
|
||||
const confirmed = await this.platformUtilsService.showDialog(
|
||||
this.i18nService.t("deleteCollectionConfirmation"),
|
||||
collection.name,
|
||||
this.i18nService.t("yes"),
|
||||
this.i18nService.t("no"),
|
||||
"warning"
|
||||
);
|
||||
if (!confirmed) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
this.actionPromise = this.apiService.deleteCollection(this.organization?.id, collection.id);
|
||||
await this.actionPromise;
|
||||
this.platformUtilsService.showToast(
|
||||
"success",
|
||||
null,
|
||||
this.i18nService.t("deletedCollectionId", collection.name)
|
||||
);
|
||||
this.onCollectionChanged.emit(collection);
|
||||
} catch (e) {
|
||||
this.logService.error(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,321 +0,0 @@
|
||||
import { Component, EventEmitter, Input, OnDestroy, Output } from "@angular/core";
|
||||
import { lastValueFrom } from "rxjs";
|
||||
|
||||
import { SearchPipe } from "@bitwarden/angular/pipes/search.pipe";
|
||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||
import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.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 { SearchService } from "@bitwarden/common/abstractions/search.service";
|
||||
import { StateService } from "@bitwarden/common/abstractions/state.service";
|
||||
import { TotpService } from "@bitwarden/common/abstractions/totp.service";
|
||||
import { TokenService } from "@bitwarden/common/auth/abstractions/token.service";
|
||||
import { Organization } from "@bitwarden/common/models/domain/organization";
|
||||
import { TreeNode } from "@bitwarden/common/models/domain/tree-node";
|
||||
import { CollectionView } from "@bitwarden/common/models/view/collection.view";
|
||||
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
||||
import { PasswordRepromptService } from "@bitwarden/common/vault/abstractions/password-reprompt.service";
|
||||
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
|
||||
import { DialogService } from "@bitwarden/components";
|
||||
|
||||
import {
|
||||
BulkDeleteDialogResult,
|
||||
openBulkDeleteDialog,
|
||||
} from "../../../vault/app/vault/bulk-action-dialogs/bulk-delete-dialog/bulk-delete-dialog.component";
|
||||
import { VaultFilterService } from "../../../vault/app/vault/vault-filter/services/abstractions/vault-filter.service";
|
||||
import { CollectionFilter } from "../../../vault/app/vault/vault-filter/shared/models/vault-filter.type";
|
||||
import {
|
||||
VaultItemRow,
|
||||
VaultItemsComponent as BaseVaultItemsComponent,
|
||||
} from "../../../vault/app/vault/vault-items.component";
|
||||
import { CollectionAdminView } from "../core";
|
||||
import { GroupService } from "../core/services/group/group.service";
|
||||
import {
|
||||
CollectionDialogResult,
|
||||
CollectionDialogTabType,
|
||||
openCollectionDialog,
|
||||
} from "../shared/components/collection-dialog/collection-dialog.component";
|
||||
|
||||
const MaxCheckedCount = 500;
|
||||
|
||||
@Component({
|
||||
selector: "app-org-vault-items",
|
||||
templateUrl: "../../../vault/app/vault/vault-items.component.html",
|
||||
})
|
||||
export class VaultItemsComponent extends BaseVaultItemsComponent implements OnDestroy {
|
||||
@Input() set initOrganization(value: Organization) {
|
||||
this.organization = value;
|
||||
this.changeOrganization();
|
||||
}
|
||||
@Output() onEventsClicked = new EventEmitter<CipherView>();
|
||||
|
||||
protected allCiphers: CipherView[] = [];
|
||||
|
||||
constructor(
|
||||
searchService: SearchService,
|
||||
i18nService: I18nService,
|
||||
platformUtilsService: PlatformUtilsService,
|
||||
cipherService: CipherService,
|
||||
vaultFilterService: VaultFilterService,
|
||||
eventCollectionService: EventCollectionService,
|
||||
totpService: TotpService,
|
||||
passwordRepromptService: PasswordRepromptService,
|
||||
dialogService: DialogService,
|
||||
logService: LogService,
|
||||
stateService: StateService,
|
||||
organizationService: OrganizationService,
|
||||
tokenService: TokenService,
|
||||
searchPipe: SearchPipe,
|
||||
protected groupService: GroupService,
|
||||
private apiService: ApiService
|
||||
) {
|
||||
super(
|
||||
searchService,
|
||||
i18nService,
|
||||
platformUtilsService,
|
||||
vaultFilterService,
|
||||
cipherService,
|
||||
eventCollectionService,
|
||||
totpService,
|
||||
stateService,
|
||||
passwordRepromptService,
|
||||
dialogService,
|
||||
logService,
|
||||
searchPipe,
|
||||
organizationService,
|
||||
tokenService
|
||||
);
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
super.ngOnDestroy();
|
||||
}
|
||||
|
||||
async changeOrganization() {
|
||||
this.groups = await this.groupService.getAll(this.organization?.id);
|
||||
await this.loadCiphers();
|
||||
await this.reload(this.activeFilter.buildFilter());
|
||||
}
|
||||
|
||||
async loadCiphers() {
|
||||
if (this.organization?.canEditAnyCollection) {
|
||||
this.accessEvents = this.organization?.useEvents;
|
||||
this.allCiphers = await this.cipherService.getAllFromApiForOrganization(
|
||||
this.organization?.id
|
||||
);
|
||||
} else {
|
||||
this.allCiphers = (await this.cipherService.getAllDecrypted()).filter(
|
||||
(c) => c.organizationId === this.organization?.id
|
||||
);
|
||||
}
|
||||
await this.searchService.indexCiphers(this.organization?.id, this.allCiphers);
|
||||
}
|
||||
|
||||
async refreshCollections(): Promise<void> {
|
||||
await this.vaultFilterService.reloadCollections();
|
||||
if (this.activeFilter.selectedCollectionNode) {
|
||||
this.activeFilter.selectedCollectionNode =
|
||||
await this.vaultFilterService.getCollectionNodeFromTree(
|
||||
this.activeFilter.selectedCollectionNode.node.id
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async load(filter: (cipher: CipherView) => boolean = null, deleted = false) {
|
||||
this.deleted = deleted ?? false;
|
||||
await this.applyFilter(filter);
|
||||
this.loaded = true;
|
||||
}
|
||||
|
||||
async refresh() {
|
||||
await this.loadCiphers();
|
||||
await this.refreshCollections();
|
||||
super.refresh();
|
||||
}
|
||||
|
||||
async search(timeout: number = null) {
|
||||
await super.search(timeout, this.allCiphers);
|
||||
}
|
||||
|
||||
events(c: CipherView) {
|
||||
this.onEventsClicked.emit(c);
|
||||
}
|
||||
|
||||
protected showFixOldAttachments(c: CipherView) {
|
||||
return this.organization?.canEditAnyCollection && c.hasOldAttachments;
|
||||
}
|
||||
|
||||
checkAll(select: boolean) {
|
||||
if (select) {
|
||||
this.checkAll(false);
|
||||
}
|
||||
|
||||
const items: VaultItemRow[] = [...this.collections, ...this.ciphers];
|
||||
if (!items.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
const selectCount = select && items.length > MaxCheckedCount ? MaxCheckedCount : items.length;
|
||||
for (let i = 0; i < selectCount; i++) {
|
||||
this.checkRow(items[i], select);
|
||||
}
|
||||
}
|
||||
|
||||
checkRow(item: VaultItemRow, select?: boolean) {
|
||||
if (item instanceof TreeNode && item.node.id == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Do not allow checking a collection we cannot delete
|
||||
if (item instanceof TreeNode && !this.canDeleteCollection(item.node)) {
|
||||
return;
|
||||
}
|
||||
|
||||
item.checked = select ?? !item.checked;
|
||||
}
|
||||
|
||||
get selectedCollections(): TreeNode<CollectionFilter>[] {
|
||||
if (!this.collections) {
|
||||
return [];
|
||||
}
|
||||
return this.collections.filter((c) => !!(c as VaultItemRow).checked);
|
||||
}
|
||||
|
||||
get selectedCollectionIds(): string[] {
|
||||
return this.selectedCollections.map((c) => c.node.id);
|
||||
}
|
||||
|
||||
canEditCollection(c: CollectionAdminView): boolean {
|
||||
// Only edit collections if we're in the org vault and not editing "Unassigned"
|
||||
if (this.organization === undefined || c.id === null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Otherwise, check if we can edit the specified collection
|
||||
return (
|
||||
this.organization.canEditAnyCollection ||
|
||||
(this.organization.canEditAssignedCollections && c.assigned)
|
||||
);
|
||||
}
|
||||
|
||||
async editCollection(c: CollectionView, tab: "info" | "access"): Promise<void> {
|
||||
const tabType = tab == "info" ? CollectionDialogTabType.Info : CollectionDialogTabType.Access;
|
||||
|
||||
const dialog = openCollectionDialog(this.dialogService, {
|
||||
data: { collectionId: c?.id, organizationId: this.organization?.id, initialTab: tabType },
|
||||
});
|
||||
|
||||
const result = await lastValueFrom(dialog.closed);
|
||||
if (result === CollectionDialogResult.Saved || result === CollectionDialogResult.Deleted) {
|
||||
this.actionPromise = this.refresh();
|
||||
await this.actionPromise;
|
||||
this.actionPromise = null;
|
||||
}
|
||||
}
|
||||
|
||||
get showMissingCollectionPermissionMessage(): boolean {
|
||||
// Not filtering by collections, so no need to show message
|
||||
if (this.activeFilter.selectedCollectionNode == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Filtering by all collections, so no need to show message
|
||||
if (this.activeFilter.selectedCollectionNode.node.id == "AllCollections") {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Filtering by a collection, so show message if user is not assigned
|
||||
return !this.activeFilter.selectedCollectionNode.node.assigned && !this.organization.isAdmin;
|
||||
}
|
||||
|
||||
canDeleteCollection(c: CollectionAdminView): boolean {
|
||||
// Only delete collections if we're in the org vault and not deleting "Unassigned"
|
||||
if (this.organization === undefined || c.id === null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Otherwise, check if we can delete the specified collection
|
||||
return (
|
||||
this.organization?.canDeleteAnyCollection ||
|
||||
(this.organization?.canDeleteAssignedCollections && c.assigned)
|
||||
);
|
||||
}
|
||||
|
||||
async deleteCollection(collection: CollectionView): Promise<void> {
|
||||
if (!this.organization.canDeleteAssignedCollections) {
|
||||
this.platformUtilsService.showToast(
|
||||
"error",
|
||||
this.i18nService.t("errorOccurred"),
|
||||
this.i18nService.t("missingPermissions")
|
||||
);
|
||||
return;
|
||||
}
|
||||
const confirmed = await this.platformUtilsService.showDialog(
|
||||
this.i18nService.t("deleteCollectionConfirmation"),
|
||||
collection.name,
|
||||
this.i18nService.t("yes"),
|
||||
this.i18nService.t("no"),
|
||||
"warning"
|
||||
);
|
||||
if (!confirmed) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
this.actionPromise = this.apiService.deleteCollection(this.organization?.id, collection.id);
|
||||
await this.actionPromise;
|
||||
this.platformUtilsService.showToast(
|
||||
"success",
|
||||
null,
|
||||
this.i18nService.t("deletedCollectionId", collection.name)
|
||||
);
|
||||
await this.refresh();
|
||||
} catch (e) {
|
||||
this.logService.error(e);
|
||||
}
|
||||
}
|
||||
|
||||
async bulkDelete() {
|
||||
if (!(await this.repromptCipher())) {
|
||||
return;
|
||||
}
|
||||
|
||||
const selectedCipherIds = this.selectedCipherIds;
|
||||
const selectedCollectionIds = this.deleted ? null : this.selectedCollectionIds;
|
||||
|
||||
if (!selectedCipherIds?.length && !selectedCollectionIds?.length) {
|
||||
this.platformUtilsService.showToast(
|
||||
"error",
|
||||
this.i18nService.t("errorOccurred"),
|
||||
this.i18nService.t("nothingSelected")
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const dialog = openBulkDeleteDialog(this.dialogService, {
|
||||
data: {
|
||||
permanent: this.deleted,
|
||||
cipherIds: selectedCipherIds,
|
||||
collectionIds: selectedCollectionIds,
|
||||
organization: this.organization,
|
||||
},
|
||||
});
|
||||
|
||||
const result = await lastValueFrom(dialog.closed);
|
||||
if (result === BulkDeleteDialogResult.Deleted) {
|
||||
this.actionPromise = this.refresh();
|
||||
await this.actionPromise;
|
||||
this.actionPromise = null;
|
||||
}
|
||||
}
|
||||
|
||||
protected deleteCipherWithServer(id: string, permanent: boolean) {
|
||||
if (!this.organization?.canEditAnyCollection) {
|
||||
return super.deleteCipherWithServer(id, this.deleted);
|
||||
}
|
||||
return permanent
|
||||
? this.apiService.deleteCipherAdmin(id)
|
||||
: this.apiService.putDeleteCipherAdmin(id);
|
||||
}
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
import { NgModule } from "@angular/core";
|
||||
import { RouterModule, Routes } from "@angular/router";
|
||||
|
||||
import { canAccessVaultTab } from "@bitwarden/common/abstractions/organization/organization.service.abstraction";
|
||||
|
||||
import { OrganizationPermissionsGuard } from "../guards/org-permissions.guard";
|
||||
|
||||
import { VaultComponent } from "./vault.component";
|
||||
const routes: Routes = [
|
||||
{
|
||||
path: "",
|
||||
component: VaultComponent,
|
||||
canActivate: [OrganizationPermissionsGuard],
|
||||
data: { titleId: "vaults", organizationPermissions: canAccessVaultTab },
|
||||
},
|
||||
];
|
||||
@NgModule({
|
||||
imports: [RouterModule.forChild(routes)],
|
||||
exports: [RouterModule],
|
||||
})
|
||||
export class VaultRoutingModule {}
|
||||
@@ -1,54 +0,0 @@
|
||||
<!-- Please remove this disable statement when editing this file! -->
|
||||
<!-- eslint-disable @angular-eslint/template/button-has-type -->
|
||||
<div class="container page-content">
|
||||
<div class="row">
|
||||
<div class="col-3">
|
||||
<div class="groupings">
|
||||
<div class="content">
|
||||
<div class="inner-content">
|
||||
<app-organization-vault-filter
|
||||
#vaultFilter
|
||||
[organization]="organization"
|
||||
[activeFilter]="activeFilter"
|
||||
(activeFilterChanged)="applyVaultFilter($event)"
|
||||
(onSearchTextChanged)="filterSearchText($event)"
|
||||
></app-organization-vault-filter>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-9">
|
||||
<app-org-vault-header
|
||||
[activeFilter]="activeFilter"
|
||||
(activeFilterChanged)="applyVaultFilter($event)"
|
||||
(onCollectionChanged)="refreshItems()"
|
||||
[actionPromise]="vaultItemsComponent.actionPromise"
|
||||
(onAddCipher)="addCipher()"
|
||||
[organization]="organization"
|
||||
></app-org-vault-header>
|
||||
<app-callout
|
||||
type="warning"
|
||||
*ngIf="activeFilter.isDeleted"
|
||||
icon="bwi bwi-exclamation-triangle"
|
||||
>
|
||||
{{ trashCleanupWarning }}
|
||||
</app-callout>
|
||||
<app-org-vault-items
|
||||
[activeFilter]="activeFilter"
|
||||
[initOrganization]="organization"
|
||||
(activeFilterChanged)="applyVaultFilter($event)"
|
||||
(onCipherClicked)="navigateToCipher($event)"
|
||||
(onAttachmentsClicked)="editCipherAttachments($event)"
|
||||
(onAddCipher)="addCipher()"
|
||||
(onEditCipherCollectionsClicked)="editCipherCollections($event)"
|
||||
(onEventsClicked)="viewEvents($event)"
|
||||
(onCloneClicked)="cloneCipher($event)"
|
||||
>
|
||||
</app-org-vault-items>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<ng-template #attachments></ng-template>
|
||||
<ng-template #cipherAddEdit></ng-template>
|
||||
<ng-template #collections></ng-template>
|
||||
<ng-template #eventsTemplate></ng-template>
|
||||
@@ -1,324 +0,0 @@
|
||||
import {
|
||||
ChangeDetectorRef,
|
||||
Component,
|
||||
NgZone,
|
||||
OnDestroy,
|
||||
OnInit,
|
||||
ViewChild,
|
||||
ViewContainerRef,
|
||||
} from "@angular/core";
|
||||
import { ActivatedRoute, Params, Router } from "@angular/router";
|
||||
import { combineLatest, firstValueFrom, Subject } from "rxjs";
|
||||
import { first, switchMap, takeUntil } from "rxjs/operators";
|
||||
|
||||
import { ModalService } from "@bitwarden/angular/services/modal.service";
|
||||
import { BroadcasterService } from "@bitwarden/common/abstractions/broadcaster.service";
|
||||
import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
|
||||
import { MessagingService } from "@bitwarden/common/abstractions/messaging.service";
|
||||
import { OrganizationService } from "@bitwarden/common/abstractions/organization/organization.service.abstraction";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service";
|
||||
import { Organization } from "@bitwarden/common/models/domain/organization";
|
||||
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
||||
import { PasswordRepromptService } from "@bitwarden/common/vault/abstractions/password-reprompt.service";
|
||||
import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction";
|
||||
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
|
||||
import { DialogService } from "@bitwarden/components";
|
||||
|
||||
import { VaultFilterService } from "../../../vault/app/vault/vault-filter/services/abstractions/vault-filter.service";
|
||||
import { VaultFilter } from "../../../vault/app/vault/vault-filter/shared/models/vault-filter.model";
|
||||
import { EntityEventsComponent } from "../manage/entity-events.component";
|
||||
|
||||
import { AddEditComponent } from "./add-edit.component";
|
||||
import { AttachmentsComponent } from "./attachments.component";
|
||||
import { CollectionsComponent } from "./collections.component";
|
||||
import { VaultFilterComponent } from "./vault-filter/vault-filter.component";
|
||||
import { VaultItemsComponent } from "./vault-items.component";
|
||||
|
||||
const BroadcasterSubscriptionId = "OrgVaultComponent";
|
||||
|
||||
@Component({
|
||||
selector: "app-org-vault",
|
||||
templateUrl: "vault.component.html",
|
||||
})
|
||||
export class VaultComponent implements OnInit, OnDestroy {
|
||||
@ViewChild("vaultFilter", { static: true })
|
||||
vaultFilterComponent: VaultFilterComponent;
|
||||
@ViewChild(VaultItemsComponent, { static: true }) vaultItemsComponent: VaultItemsComponent;
|
||||
@ViewChild("attachments", { read: ViewContainerRef, static: true })
|
||||
attachmentsModalRef: ViewContainerRef;
|
||||
@ViewChild("cipherAddEdit", { read: ViewContainerRef, static: true })
|
||||
cipherAddEditModalRef: ViewContainerRef;
|
||||
@ViewChild("collections", { read: ViewContainerRef, static: true })
|
||||
collectionsModalRef: ViewContainerRef;
|
||||
@ViewChild("eventsTemplate", { read: ViewContainerRef, static: true })
|
||||
eventsModalRef: ViewContainerRef;
|
||||
|
||||
organization: Organization;
|
||||
trashCleanupWarning: string = null;
|
||||
activeFilter: VaultFilter = new VaultFilter();
|
||||
private destroy$ = new Subject<void>();
|
||||
|
||||
constructor(
|
||||
private route: ActivatedRoute,
|
||||
private organizationService: OrganizationService,
|
||||
protected vaultFilterService: VaultFilterService,
|
||||
private router: Router,
|
||||
private changeDetectorRef: ChangeDetectorRef,
|
||||
private syncService: SyncService,
|
||||
private i18nService: I18nService,
|
||||
private modalService: ModalService,
|
||||
private dialogService: DialogService,
|
||||
private messagingService: MessagingService,
|
||||
private broadcasterService: BroadcasterService,
|
||||
private ngZone: NgZone,
|
||||
private platformUtilsService: PlatformUtilsService,
|
||||
private cipherService: CipherService,
|
||||
private passwordRepromptService: PasswordRepromptService
|
||||
) {}
|
||||
|
||||
async ngOnInit() {
|
||||
this.trashCleanupWarning = this.i18nService.t(
|
||||
this.platformUtilsService.isSelfHost()
|
||||
? "trashCleanupWarningSelfHosted"
|
||||
: "trashCleanupWarning"
|
||||
);
|
||||
|
||||
this.route.parent.params.pipe(takeUntil(this.destroy$)).subscribe((params) => {
|
||||
this.organization = this.organizationService.get(params.organizationId);
|
||||
});
|
||||
|
||||
this.route.queryParams.pipe(first(), takeUntil(this.destroy$)).subscribe((qParams) => {
|
||||
this.vaultItemsComponent.searchText = this.vaultFilterComponent.searchText = qParams.search;
|
||||
});
|
||||
|
||||
// verifies that the organization has been set
|
||||
combineLatest([this.route.queryParams, this.route.parent.params])
|
||||
.pipe(
|
||||
switchMap(async ([qParams]) => {
|
||||
const cipherId = getCipherIdFromParams(qParams);
|
||||
if (!cipherId) {
|
||||
return;
|
||||
}
|
||||
if (
|
||||
// Handle users with implicit collection access since they use the admin endpoint
|
||||
this.organization.canUseAdminCollections ||
|
||||
(await this.cipherService.get(cipherId)) != null
|
||||
) {
|
||||
this.editCipherId(cipherId);
|
||||
} else {
|
||||
this.platformUtilsService.showToast(
|
||||
"error",
|
||||
this.i18nService.t("errorOccurred"),
|
||||
this.i18nService.t("unknownCipher")
|
||||
);
|
||||
this.router.navigate([], {
|
||||
queryParams: { cipherId: null, itemId: null },
|
||||
queryParamsHandling: "merge",
|
||||
});
|
||||
}
|
||||
}),
|
||||
takeUntil(this.destroy$)
|
||||
)
|
||||
.subscribe();
|
||||
|
||||
if (!this.organization.canUseAdminCollections) {
|
||||
this.broadcasterService.subscribe(BroadcasterSubscriptionId, (message: any) => {
|
||||
this.ngZone.run(async () => {
|
||||
switch (message.command) {
|
||||
case "syncCompleted":
|
||||
if (message.successfully) {
|
||||
await Promise.all([
|
||||
this.vaultFilterService.reloadCollections(),
|
||||
this.vaultItemsComponent.refresh(),
|
||||
]);
|
||||
this.changeDetectorRef.detectChanges();
|
||||
}
|
||||
break;
|
||||
}
|
||||
});
|
||||
});
|
||||
await this.syncService.fullSync(false);
|
||||
}
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this.broadcasterService.unsubscribe(BroadcasterSubscriptionId);
|
||||
this.destroy$.next();
|
||||
this.destroy$.complete();
|
||||
}
|
||||
|
||||
async applyVaultFilter(filter: VaultFilter) {
|
||||
this.activeFilter = filter;
|
||||
this.vaultItemsComponent.showAddNew = !this.activeFilter.isDeleted;
|
||||
await this.vaultItemsComponent.reload(
|
||||
this.activeFilter.buildFilter(),
|
||||
this.activeFilter.isDeleted
|
||||
);
|
||||
this.go();
|
||||
}
|
||||
|
||||
async refreshItems() {
|
||||
this.vaultItemsComponent.actionPromise = this.vaultItemsComponent.refresh();
|
||||
await this.vaultItemsComponent.actionPromise;
|
||||
this.vaultItemsComponent.actionPromise = null;
|
||||
}
|
||||
|
||||
filterSearchText(searchText: string) {
|
||||
this.vaultItemsComponent.searchText = searchText;
|
||||
this.vaultItemsComponent.search(200);
|
||||
}
|
||||
|
||||
async editCipherAttachments(cipher: CipherView) {
|
||||
if (this.organization.maxStorageGb == null || this.organization.maxStorageGb === 0) {
|
||||
this.messagingService.send("upgradeOrganization", { organizationId: cipher.organizationId });
|
||||
return;
|
||||
}
|
||||
|
||||
let madeAttachmentChanges = false;
|
||||
|
||||
const [modal] = await this.modalService.openViewRef(
|
||||
AttachmentsComponent,
|
||||
this.attachmentsModalRef,
|
||||
(comp) => {
|
||||
comp.organization = this.organization;
|
||||
comp.cipherId = cipher.id;
|
||||
// eslint-disable-next-line rxjs-angular/prefer-takeuntil
|
||||
comp.onUploadedAttachment.subscribe(() => (madeAttachmentChanges = true));
|
||||
// eslint-disable-next-line rxjs-angular/prefer-takeuntil
|
||||
comp.onDeletedAttachment.subscribe(() => (madeAttachmentChanges = true));
|
||||
}
|
||||
);
|
||||
|
||||
// eslint-disable-next-line rxjs-angular/prefer-takeuntil, rxjs/no-async-subscribe
|
||||
modal.onClosed.subscribe(async () => {
|
||||
if (madeAttachmentChanges) {
|
||||
await this.vaultItemsComponent.refresh();
|
||||
}
|
||||
madeAttachmentChanges = false;
|
||||
});
|
||||
}
|
||||
|
||||
async editCipherCollections(cipher: CipherView) {
|
||||
const currCollections = await firstValueFrom(this.vaultFilterService.filteredCollections$);
|
||||
const [modal] = await this.modalService.openViewRef(
|
||||
CollectionsComponent,
|
||||
this.collectionsModalRef,
|
||||
(comp) => {
|
||||
comp.collectionIds = cipher.collectionIds;
|
||||
comp.collections = currCollections.filter((c) => !c.readOnly && c.id != null);
|
||||
comp.organization = this.organization;
|
||||
comp.cipherId = cipher.id;
|
||||
// eslint-disable-next-line rxjs-angular/prefer-takeuntil, rxjs/no-async-subscribe
|
||||
comp.onSavedCollections.subscribe(async () => {
|
||||
modal.close();
|
||||
await this.vaultItemsComponent.refresh();
|
||||
});
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
async addCipher() {
|
||||
const component = await this.editCipher(null);
|
||||
component.organizationId = this.organization.id;
|
||||
component.type = this.activeFilter.cipherType;
|
||||
component.collections = (
|
||||
await firstValueFrom(this.vaultFilterService.filteredCollections$)
|
||||
).filter((c) => !c.readOnly && c.id != null);
|
||||
if (this.activeFilter.collectionId) {
|
||||
component.collectionIds = [this.activeFilter.collectionId];
|
||||
}
|
||||
}
|
||||
|
||||
async navigateToCipher(cipher: CipherView) {
|
||||
this.go({ itemId: cipher?.id });
|
||||
}
|
||||
|
||||
async editCipher(cipher: CipherView) {
|
||||
return this.editCipherId(cipher?.id);
|
||||
}
|
||||
|
||||
async editCipherId(cipherId: string) {
|
||||
const cipher = await this.cipherService.get(cipherId);
|
||||
if (cipher != null && cipher.reprompt != 0) {
|
||||
if (!(await this.passwordRepromptService.showPasswordPrompt())) {
|
||||
this.go({ cipherId: null, itemId: null });
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const [modal, childComponent] = await this.modalService.openViewRef(
|
||||
AddEditComponent,
|
||||
this.cipherAddEditModalRef,
|
||||
(comp) => {
|
||||
comp.organization = this.organization;
|
||||
comp.cipherId = cipherId;
|
||||
// eslint-disable-next-line rxjs-angular/prefer-takeuntil, rxjs/no-async-subscribe
|
||||
comp.onSavedCipher.subscribe(async () => {
|
||||
modal.close();
|
||||
await this.vaultItemsComponent.refresh();
|
||||
});
|
||||
// eslint-disable-next-line rxjs-angular/prefer-takeuntil, rxjs/no-async-subscribe
|
||||
comp.onDeletedCipher.subscribe(async () => {
|
||||
modal.close();
|
||||
await this.vaultItemsComponent.refresh();
|
||||
});
|
||||
// eslint-disable-next-line rxjs-angular/prefer-takeuntil, rxjs/no-async-subscribe
|
||||
comp.onRestoredCipher.subscribe(async () => {
|
||||
modal.close();
|
||||
await this.vaultItemsComponent.refresh();
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
modal.onClosedPromise().then(() => {
|
||||
this.go({ cipherId: null, itemId: null });
|
||||
});
|
||||
|
||||
return childComponent;
|
||||
}
|
||||
|
||||
async cloneCipher(cipher: CipherView) {
|
||||
const component = await this.editCipher(cipher);
|
||||
component.cloneMode = true;
|
||||
component.organizationId = this.organization.id;
|
||||
component.collections = (
|
||||
await firstValueFrom(this.vaultFilterService.filteredCollections$)
|
||||
).filter((c) => !c.readOnly && c.id != null);
|
||||
component.collectionIds = cipher.collectionIds;
|
||||
}
|
||||
|
||||
async viewEvents(cipher: CipherView) {
|
||||
await this.modalService.openViewRef(EntityEventsComponent, this.eventsModalRef, (comp) => {
|
||||
comp.name = cipher.name;
|
||||
comp.organizationId = this.organization.id;
|
||||
comp.entityId = cipher.id;
|
||||
comp.showUser = true;
|
||||
comp.entity = "cipher";
|
||||
});
|
||||
}
|
||||
|
||||
private go(queryParams: any = null) {
|
||||
if (queryParams == null) {
|
||||
queryParams = {
|
||||
type: this.activeFilter.cipherType,
|
||||
collectionId: this.activeFilter.collectionId,
|
||||
deleted: this.activeFilter.isDeleted || null,
|
||||
};
|
||||
}
|
||||
|
||||
this.router.navigate([], {
|
||||
relativeTo: this.route,
|
||||
queryParams: queryParams,
|
||||
queryParamsHandling: "merge",
|
||||
replaceUrl: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows backwards compatibility with
|
||||
* old links that used the original `cipherId` param
|
||||
*/
|
||||
const getCipherIdFromParams = (params: Params): string => {
|
||||
return params["itemId"] || params["cipherId"];
|
||||
};
|
||||
@@ -1,33 +0,0 @@
|
||||
import { NgModule } from "@angular/core";
|
||||
|
||||
import { BreadcrumbsModule } from "@bitwarden/components";
|
||||
|
||||
import { OrganizationBadgeModule } from "../../../vault/app/vault/organization-badge/organization-badge.module";
|
||||
import { PipesModule } from "../../../vault/app/vault/pipes/pipes.module";
|
||||
import { LooseComponentsModule } from "../../shared/loose-components.module";
|
||||
import { SharedModule } from "../../shared/shared.module";
|
||||
|
||||
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 { VaultItemsComponent } from "./vault-items.component";
|
||||
import { VaultRoutingModule } from "./vault-routing.module";
|
||||
import { VaultComponent } from "./vault.component";
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
VaultRoutingModule,
|
||||
VaultFilterModule,
|
||||
SharedModule,
|
||||
LooseComponentsModule,
|
||||
GroupBadgeModule,
|
||||
CollectionBadgeModule,
|
||||
OrganizationBadgeModule,
|
||||
PipesModule,
|
||||
BreadcrumbsModule,
|
||||
],
|
||||
declarations: [VaultComponent, VaultItemsComponent, VaultHeaderComponent],
|
||||
exports: [VaultComponent],
|
||||
})
|
||||
export class VaultModule {}
|
||||
@@ -24,7 +24,7 @@ import { UpdateTempPasswordComponent } from "../auth/update-temp-password.compon
|
||||
import { VerifyEmailTokenComponent } from "../auth/verify-email-token.component";
|
||||
import { VerifyRecoverDeleteComponent } from "../auth/verify-recover-delete.component";
|
||||
import { flagEnabled, Flags } from "../utils/flags";
|
||||
import { VaultModule } from "../vault/app/vault/vault.module";
|
||||
import { VaultModule } from "../vault/individual-vault/vault.module";
|
||||
|
||||
import { TrialInitiationComponent } from "./accounts/trial-initiation/trial-initiation.component";
|
||||
import { HomeGuard } from "./guards/home.guard";
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { NgModule } from "@angular/core";
|
||||
|
||||
import { LoginModule } from "../auth/login/login.module";
|
||||
import { OrganizationBadgeModule } from "../vault/app/vault/organization-badge/organization-badge.module";
|
||||
import { VaultFilterModule } from "../vault/app/vault/vault-filter/vault-filter.module";
|
||||
import { OrganizationBadgeModule } from "../vault/individual-vault/organization-badge/organization-badge.module";
|
||||
import { VaultFilterModule } from "../vault/individual-vault/vault-filter/vault-filter.module";
|
||||
|
||||
import { TrialInitiationModule } from "./accounts/trial-initiation/trial-initiation.module";
|
||||
import { OrganizationCreateModule } from "./organizations/create/organization-create.module";
|
||||
|
||||
@@ -7,8 +7,8 @@ import { PasswordRepromptService } from "@bitwarden/common/vault/abstractions/pa
|
||||
import { CipherRepromptType } from "@bitwarden/common/vault/enums/cipher-reprompt-type";
|
||||
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
|
||||
|
||||
import { AddEditComponent } from "../../../vault/app/vault/add-edit.component";
|
||||
import { AddEditComponent as OrgAddEditComponent } from "../../organizations/vault/add-edit.component";
|
||||
import { AddEditComponent } from "../../../vault/individual-vault/add-edit.component";
|
||||
import { AddEditComponent as OrgAddEditComponent } from "../../../vault/org-vault/add-edit.component";
|
||||
|
||||
@Directive()
|
||||
export class CipherReportComponent {
|
||||
|
||||
@@ -4,7 +4,7 @@ import { Meta, Story, moduleMetadata } from "@storybook/angular";
|
||||
import { JslibModule } from "@bitwarden/angular/jslib.module";
|
||||
import { BadgeModule, IconModule } from "@bitwarden/components";
|
||||
|
||||
import { PremiumBadgeComponent } from "../../../../vault/app/components/premium-badge.component";
|
||||
import { PremiumBadgeComponent } from "../../../../vault/components/premium-badge.component";
|
||||
import { PreloadedEnglishI18nModule } from "../../../tests/preloaded-english-i18n.module";
|
||||
import { ReportVariant } from "../models/report-variant";
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ import { Meta, Story, moduleMetadata } from "@storybook/angular";
|
||||
import { JslibModule } from "@bitwarden/angular/jslib.module";
|
||||
import { BadgeModule, IconModule } from "@bitwarden/components";
|
||||
|
||||
import { PremiumBadgeComponent } from "../../../../vault/app/components/premium-badge.component";
|
||||
import { PremiumBadgeComponent } from "../../../../vault/components/premium-badge.component";
|
||||
import { PreloadedEnglishI18nModule } from "../../../tests/preloaded-english-i18n.module";
|
||||
import { reports } from "../../reports";
|
||||
import { ReportVariant } from "../models/report-variant";
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { NgModule } from "@angular/core";
|
||||
|
||||
import { PasswordRepromptComponent } from "../../../src/vault/app/components/password-reprompt.component";
|
||||
import { PasswordRepromptComponent } from "../../../src/vault/components/password-reprompt.component";
|
||||
import { AcceptEmergencyComponent } from "../../auth/accept-emergency.component";
|
||||
import { AcceptOrganizationComponent } from "../../auth/accept-organization.component";
|
||||
import { HintComponent } from "../../auth/hint.component";
|
||||
@@ -34,13 +34,15 @@ import { UpdatePasswordComponent } from "../../auth/update-password.component";
|
||||
import { UpdateTempPasswordComponent } from "../../auth/update-temp-password.component";
|
||||
import { VerifyEmailTokenComponent } from "../../auth/verify-email-token.component";
|
||||
import { VerifyRecoverDeleteComponent } from "../../auth/verify-recover-delete.component";
|
||||
import { PremiumBadgeComponent } from "../../vault/app/components/premium-badge.component";
|
||||
import { AddEditCustomFieldsComponent } from "../../vault/app/vault/add-edit-custom-fields.component";
|
||||
import { AddEditComponent } from "../../vault/app/vault/add-edit.component";
|
||||
import { AttachmentsComponent } from "../../vault/app/vault/attachments.component";
|
||||
import { CollectionsComponent } from "../../vault/app/vault/collections.component";
|
||||
import { FolderAddEditComponent } from "../../vault/app/vault/folder-add-edit.component";
|
||||
import { ShareComponent } from "../../vault/app/vault/share.component";
|
||||
import { PremiumBadgeComponent } from "../../vault/components/premium-badge.component";
|
||||
import { AddEditCustomFieldsComponent } from "../../vault/individual-vault/add-edit-custom-fields.component";
|
||||
import { AddEditComponent } from "../../vault/individual-vault/add-edit.component";
|
||||
import { AttachmentsComponent } from "../../vault/individual-vault/attachments.component";
|
||||
import { CollectionsComponent } from "../../vault/individual-vault/collections.component";
|
||||
import { FolderAddEditComponent } from "../../vault/individual-vault/folder-add-edit.component";
|
||||
import { ShareComponent } from "../../vault/individual-vault/share.component";
|
||||
import { AddEditComponent as OrgAddEditComponent } from "../../vault/org-vault/add-edit.component";
|
||||
import { AttachmentsComponent as OrgAttachmentsComponent } from "../../vault/org-vault/attachments.component";
|
||||
import { DynamicAvatarComponent } from "../components/dynamic-avatar.component";
|
||||
import { OrganizationSwitcherComponent } from "../components/organization-switcher.component";
|
||||
import { SelectableAvatarComponent } from "../components/selectable-avatar.component";
|
||||
@@ -66,8 +68,6 @@ import { ReusedPasswordsReportComponent as OrgReusedPasswordsReportComponent } f
|
||||
import { ToolsComponent as OrgToolsComponent } from "../organizations/tools/tools.component";
|
||||
import { UnsecuredWebsitesReportComponent as OrgUnsecuredWebsitesReportComponent } from "../organizations/tools/unsecured-websites-report.component";
|
||||
import { WeakPasswordsReportComponent as OrgWeakPasswordsReportComponent } from "../organizations/tools/weak-passwords-report.component";
|
||||
import { AddEditComponent as OrgAddEditComponent } from "../organizations/vault/add-edit.component";
|
||||
import { AttachmentsComponent as OrgAttachmentsComponent } from "../organizations/vault/attachments.component";
|
||||
import { CollectionsComponent as OrgCollectionsComponent } from "../organizations/vault/collections.component";
|
||||
import { ProvidersComponent } from "../providers/providers.component";
|
||||
import { AccessComponent } from "../send/access.component";
|
||||
|
||||
Reference in New Issue
Block a user