mirror of
https://github.com/bitwarden/browser
synced 2025-12-17 16:53:34 +00:00
[SG-998] Move vault folder into app folder for web (#4824)
* Move vault folder into app folder for web * Remove extra line is oss module
This commit is contained in:
@@ -0,0 +1,243 @@
|
||||
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 { CollectionAdminService, CollectionAdminView } from "../../../organizations/core";
|
||||
import {
|
||||
CollectionDialogResult,
|
||||
CollectionDialogTabType,
|
||||
openCollectionDialog,
|
||||
} from "../../../organizations/shared";
|
||||
import { VaultFilterService } from "../../individual-vault/vault-filter/services/abstractions/vault-filter.service";
|
||||
import { VaultFilter } from "../../individual-vault/vault-filter/shared/models/vault-filter.model";
|
||||
import { CollectionFilter } from "../../individual-vault/vault-filter/shared/models/vault-filter.type";
|
||||
|
||||
@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);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user