mirror of
https://github.com/bitwarden/browser
synced 2025-12-17 00:33:44 +00:00
[PM-18766] Changes to use the new Assign Collections Component in Desktop (#14180)
* Initial changes to use the new Assign Collections Component in Desktop * Renaming component properly and adding the missing messages.json entries * Adding an option in right click menu to assign to collections * lint fix * prettier * updates so that the feature flag being on will show the new assign collections dialog * lint fix * set collections property after updating cipher collections * update revision date from server response in shareManyWithServer * Removing changes from non-feature flagged files, fixing the refresh issue * return CipherResponse instead of Record * adding in the master password reprompt check if they try and share --------- Co-authored-by: bnagawiecki <107435978+bnagawiecki@users.noreply.github.com> Co-authored-by: jaasen-livefront <jaasen@livefront.com>
This commit is contained in:
@@ -9,6 +9,7 @@ import { BrowserAnimationsModule } from "@angular/platform-browser/animations";
|
|||||||
import { ColorPasswordCountPipe } from "@bitwarden/angular/pipes/color-password-count.pipe";
|
import { ColorPasswordCountPipe } from "@bitwarden/angular/pipes/color-password-count.pipe";
|
||||||
import { ColorPasswordPipe } from "@bitwarden/angular/pipes/color-password.pipe";
|
import { ColorPasswordPipe } from "@bitwarden/angular/pipes/color-password.pipe";
|
||||||
import { CalloutModule, DialogModule } from "@bitwarden/components";
|
import { CalloutModule, DialogModule } from "@bitwarden/components";
|
||||||
|
import { AssignCollectionsComponent } from "@bitwarden/vault";
|
||||||
|
|
||||||
import { DeleteAccountComponent } from "../auth/delete-account.component";
|
import { DeleteAccountComponent } from "../auth/delete-account.component";
|
||||||
import { LoginModule } from "../auth/login/login.module";
|
import { LoginModule } from "../auth/login/login.module";
|
||||||
@@ -55,6 +56,7 @@ import { SharedModule } from "./shared/shared.module";
|
|||||||
DeleteAccountComponent,
|
DeleteAccountComponent,
|
||||||
UserVerificationComponent,
|
UserVerificationComponent,
|
||||||
NavComponent,
|
NavComponent,
|
||||||
|
AssignCollectionsComponent,
|
||||||
VaultV2Component,
|
VaultV2Component,
|
||||||
],
|
],
|
||||||
declarations: [
|
declarations: [
|
||||||
|
|||||||
@@ -3812,5 +3812,139 @@
|
|||||||
"message": "Learn more about SSH agent",
|
"message": "Learn more about SSH agent",
|
||||||
"description": "Two part message",
|
"description": "Two part message",
|
||||||
"example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent"
|
"example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent"
|
||||||
|
},
|
||||||
|
"assignToCollections": {
|
||||||
|
"message": "Assign to collections"
|
||||||
|
},
|
||||||
|
"assignToTheseCollections": {
|
||||||
|
"message": "Assign to these collections"
|
||||||
|
},
|
||||||
|
"bulkCollectionAssignmentDialogDescriptionSingular": {
|
||||||
|
"message": "Only organization members with access to these collections will be able to see the item."
|
||||||
|
},
|
||||||
|
"bulkCollectionAssignmentDialogDescriptionPlural": {
|
||||||
|
"message": "Only organization members with access to these collections will be able to see the items."
|
||||||
|
},
|
||||||
|
"noCollectionsAssigned": {
|
||||||
|
"message": "No collections have been assigned"
|
||||||
|
},
|
||||||
|
"assign": {
|
||||||
|
"message": "Assign"
|
||||||
|
},
|
||||||
|
"bulkCollectionAssignmentDialogDescription": {
|
||||||
|
"message": "Only organization members with access to these collections will be able to see the items."
|
||||||
|
},
|
||||||
|
"bulkCollectionAssignmentWarning": {
|
||||||
|
"message": "You have selected $TOTAL_COUNT$ items. You cannot update $READONLY_COUNT$ of the items because you do not have edit permissions.",
|
||||||
|
"placeholders": {
|
||||||
|
"total_count": {
|
||||||
|
"content": "$1",
|
||||||
|
"example": "10"
|
||||||
|
},
|
||||||
|
"readonly_count": {
|
||||||
|
"content": "$2"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"selectCollectionsToAssign": {
|
||||||
|
"message": "Select collections to assign"
|
||||||
|
},
|
||||||
|
"personalItemsTransferWarning": {
|
||||||
|
"message": "$PERSONAL_ITEMS_COUNT$ will be permanently transferred to the selected organization. You will no longer own these items.",
|
||||||
|
"placeholders": {
|
||||||
|
"personal_items_count": {
|
||||||
|
"content": "$1",
|
||||||
|
"example": "2 items"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"personalItemsWithOrgTransferWarning": {
|
||||||
|
"message": "$PERSONAL_ITEMS_COUNT$ will be permanently transferred to $ORG$. You will no longer own these items.",
|
||||||
|
"placeholders": {
|
||||||
|
"personal_items_count": {
|
||||||
|
"content": "$1",
|
||||||
|
"example": "2 items"
|
||||||
|
},
|
||||||
|
"org": {
|
||||||
|
"content": "$2",
|
||||||
|
"example": "Organization name"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"personalItemTransferWarningSingular": {
|
||||||
|
"message": "1 item will be permanently transferred to the selected organization. You will no longer own this item."
|
||||||
|
},
|
||||||
|
"personalItemWithOrgTransferWarningSingular": {
|
||||||
|
"message": "1 item will be permanently transferred to $ORG$. You will no longer own this item.",
|
||||||
|
"placeholders": {
|
||||||
|
"org": {
|
||||||
|
"content": "$1",
|
||||||
|
"example": "Organization name"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"successfullyAssignedCollections": {
|
||||||
|
"message": "Successfully assigned collections"
|
||||||
|
},
|
||||||
|
"nothingSelected": {
|
||||||
|
"message": "You have not selected anything."
|
||||||
|
},
|
||||||
|
"itemsMovedToOrg": {
|
||||||
|
"message": "Items moved to $ORGNAME$",
|
||||||
|
"placeholders": {
|
||||||
|
"orgname": {
|
||||||
|
"content": "$1",
|
||||||
|
"example": "Company Name"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"itemMovedToOrg": {
|
||||||
|
"message": "Item moved to $ORGNAME$",
|
||||||
|
"placeholders": {
|
||||||
|
"orgname": {
|
||||||
|
"content": "$1",
|
||||||
|
"example": "Company Name"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"movedItemsToOrg": {
|
||||||
|
"message": "Selected items moved to $ORGNAME$",
|
||||||
|
"placeholders": {
|
||||||
|
"orgname": {
|
||||||
|
"content": "$1",
|
||||||
|
"example": "Company Name"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"personalItemsTransferWarningPlural": {
|
||||||
|
"message": "$PERSONAL_ITEMS_COUNT$ items will be permanently transferred to the selected organization. You will no longer own these items.",
|
||||||
|
"placeholders": {
|
||||||
|
"personal_items_count": {
|
||||||
|
"content": "$1",
|
||||||
|
"example": "2"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"personalItemWithOrgTransferWarningSingular": {
|
||||||
|
"message": "1 item will be permanently transferred to $ORG$. You will no longer own this item.",
|
||||||
|
"placeholders": {
|
||||||
|
"org": {
|
||||||
|
"content": "$1",
|
||||||
|
"example": "Organization name"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"personalItemsWithOrgTransferWarningPlural": {
|
||||||
|
"message": "$PERSONAL_ITEMS_COUNT$ items will be permanently transferred to $ORG$. You will no longer own these items.",
|
||||||
|
"placeholders": {
|
||||||
|
"personal_items_count": {
|
||||||
|
"content": "$1",
|
||||||
|
"example": "2"
|
||||||
|
},
|
||||||
|
"org": {
|
||||||
|
"content": "$2",
|
||||||
|
"example": "Organization name"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,33 @@
|
|||||||
|
<bit-dialog dialogSize="large">
|
||||||
|
<span bitDialogTitle>
|
||||||
|
{{ "assignToCollections" | i18n }}
|
||||||
|
<span class="tw-text-sm tw-normal-case tw-text-muted">
|
||||||
|
{{ editableItemCount | pluralize: ("item" | i18n) : ("items" | i18n) }}
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<div bitDialogContent>
|
||||||
|
<assign-collections
|
||||||
|
[params]="params"
|
||||||
|
[submitBtn]="assignSubmitButton"
|
||||||
|
(onCollectionAssign)="onCollectionAssign($event)"
|
||||||
|
(editableItemCountChange)="editableItemCount = $event"
|
||||||
|
></assign-collections>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<ng-container bitDialogFooter>
|
||||||
|
<button
|
||||||
|
#assignSubmitButton
|
||||||
|
form="assign_collections_form"
|
||||||
|
type="submit"
|
||||||
|
bitButton
|
||||||
|
bitFormButton
|
||||||
|
buttonType="primary"
|
||||||
|
>
|
||||||
|
{{ "assign" | i18n }}
|
||||||
|
</button>
|
||||||
|
<button type="button" bitButton buttonType="secondary" bitDialogClose>
|
||||||
|
{{ "cancel" | i18n }}
|
||||||
|
</button>
|
||||||
|
</ng-container>
|
||||||
|
</bit-dialog>
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
import { DIALOG_DATA, DialogConfig, DialogRef } from "@angular/cdk/dialog";
|
||||||
|
import { Component, Inject } from "@angular/core";
|
||||||
|
|
||||||
|
import { JslibModule } from "@bitwarden/angular/jslib.module";
|
||||||
|
import { PluralizePipe } from "@bitwarden/angular/pipes/pluralize.pipe";
|
||||||
|
import { ButtonModule, DialogModule, DialogService } from "@bitwarden/components";
|
||||||
|
import {
|
||||||
|
AssignCollectionsComponent,
|
||||||
|
CollectionAssignmentParams,
|
||||||
|
CollectionAssignmentResult,
|
||||||
|
} from "@bitwarden/vault";
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
standalone: true,
|
||||||
|
templateUrl: "./assign-collections-desktop.component.html",
|
||||||
|
imports: [AssignCollectionsComponent, PluralizePipe, DialogModule, ButtonModule, JslibModule],
|
||||||
|
})
|
||||||
|
export class AssignCollectionsDesktopComponent {
|
||||||
|
protected editableItemCount: number;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
@Inject(DIALOG_DATA) public params: CollectionAssignmentParams,
|
||||||
|
private dialogRef: DialogRef<CollectionAssignmentResult>,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
protected async onCollectionAssign(result: CollectionAssignmentResult) {
|
||||||
|
this.dialogRef.close(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
static open(dialogService: DialogService, config: DialogConfig<CollectionAssignmentParams>) {
|
||||||
|
return dialogService.open<CollectionAssignmentResult, CollectionAssignmentParams>(
|
||||||
|
AssignCollectionsDesktopComponent,
|
||||||
|
config,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export * from "./assign-collections-desktop.component";
|
||||||
@@ -9,7 +9,7 @@ import {
|
|||||||
ViewContainerRef,
|
ViewContainerRef,
|
||||||
} from "@angular/core";
|
} from "@angular/core";
|
||||||
import { ActivatedRoute, Router } from "@angular/router";
|
import { ActivatedRoute, Router } from "@angular/router";
|
||||||
import { firstValueFrom, Subject, takeUntil, switchMap, lastValueFrom } from "rxjs";
|
import { firstValueFrom, Subject, takeUntil, switchMap, lastValueFrom, Observable } from "rxjs";
|
||||||
import { filter, map, take } from "rxjs/operators";
|
import { filter, map, take } from "rxjs/operators";
|
||||||
|
|
||||||
import { CollectionService, CollectionView } from "@bitwarden/admin-console/common";
|
import { CollectionService, CollectionView } from "@bitwarden/admin-console/common";
|
||||||
@@ -19,6 +19,8 @@ import { VaultViewPasswordHistoryService } from "@bitwarden/angular/services/vie
|
|||||||
import { VaultFilter } from "@bitwarden/angular/vault/vault-filter/models/vault-filter.model";
|
import { VaultFilter } from "@bitwarden/angular/vault/vault-filter/models/vault-filter.model";
|
||||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||||
import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service";
|
import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service";
|
||||||
|
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
|
||||||
|
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
|
||||||
import { Account, AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
import { Account, AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||||
import { getUserId } from "@bitwarden/common/auth/services/account.service";
|
import { getUserId } from "@bitwarden/common/auth/services/account.service";
|
||||||
import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service";
|
import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service";
|
||||||
@@ -57,6 +59,7 @@ import {
|
|||||||
CipherFormMode,
|
CipherFormMode,
|
||||||
CipherFormModule,
|
CipherFormModule,
|
||||||
CipherViewComponent,
|
CipherViewComponent,
|
||||||
|
CollectionAssignmentResult,
|
||||||
DecryptionFailureDialogComponent,
|
DecryptionFailureDialogComponent,
|
||||||
DefaultChangeLoginPasswordService,
|
DefaultChangeLoginPasswordService,
|
||||||
DefaultCipherFormConfigService,
|
DefaultCipherFormConfigService,
|
||||||
@@ -69,6 +72,7 @@ import { DesktopCredentialGenerationService } from "../../../services/desktop-ci
|
|||||||
import { DesktopPremiumUpgradePromptService } from "../../../services/desktop-premium-upgrade-prompt.service";
|
import { DesktopPremiumUpgradePromptService } from "../../../services/desktop-premium-upgrade-prompt.service";
|
||||||
import { invokeMenu, RendererMenuItem } from "../../../utils";
|
import { invokeMenu, RendererMenuItem } from "../../../utils";
|
||||||
|
|
||||||
|
import { AssignCollectionsDesktopComponent } from "./assign-collections";
|
||||||
import { ItemFooterComponent } from "./item-footer.component";
|
import { ItemFooterComponent } from "./item-footer.component";
|
||||||
import { VaultFilterComponent } from "./vault-filter/vault-filter.component";
|
import { VaultFilterComponent } from "./vault-filter/vault-filter.component";
|
||||||
import { VaultFilterModule } from "./vault-filter/vault-filter.module";
|
import { VaultFilterModule } from "./vault-filter/vault-filter.module";
|
||||||
@@ -142,6 +146,11 @@ export class VaultV2Component implements OnInit, OnDestroy {
|
|||||||
config: CipherFormConfig | null = null;
|
config: CipherFormConfig | null = null;
|
||||||
isSubmitting = false;
|
isSubmitting = false;
|
||||||
|
|
||||||
|
private organizations$: Observable<Organization[]> = this.accountService.activeAccount$.pipe(
|
||||||
|
map((a) => a?.id),
|
||||||
|
switchMap((id) => this.organizationService.organizations$(id)),
|
||||||
|
);
|
||||||
|
|
||||||
protected canAccessAttachments$ = this.accountService.activeAccount$.pipe(
|
protected canAccessAttachments$ = this.accountService.activeAccount$.pipe(
|
||||||
filter((account): account is Account => !!account),
|
filter((account): account is Account => !!account),
|
||||||
switchMap((account) =>
|
switchMap((account) =>
|
||||||
@@ -151,6 +160,8 @@ export class VaultV2Component implements OnInit, OnDestroy {
|
|||||||
|
|
||||||
private modal: ModalRef | null = null;
|
private modal: ModalRef | null = null;
|
||||||
private componentIsDestroyed$ = new Subject<boolean>();
|
private componentIsDestroyed$ = new Subject<boolean>();
|
||||||
|
private allOrganizations: Organization[] = [];
|
||||||
|
private allCollections: CollectionView[] = [];
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private route: ActivatedRoute,
|
private route: ActivatedRoute,
|
||||||
@@ -176,6 +187,7 @@ export class VaultV2Component implements OnInit, OnDestroy {
|
|||||||
private formConfigService: CipherFormConfigService,
|
private formConfigService: CipherFormConfigService,
|
||||||
private premiumUpgradePromptService: PremiumUpgradePromptService,
|
private premiumUpgradePromptService: PremiumUpgradePromptService,
|
||||||
private collectionService: CollectionService,
|
private collectionService: CollectionService,
|
||||||
|
private organizationService: OrganizationService,
|
||||||
private folderService: FolderService,
|
private folderService: FolderService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
@@ -312,6 +324,16 @@ export class VaultV2Component implements OnInit, OnDestroy {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.organizations$.pipe(takeUntil(this.componentIsDestroyed$)).subscribe((orgs) => {
|
||||||
|
this.allOrganizations = orgs;
|
||||||
|
});
|
||||||
|
|
||||||
|
this.collectionService.decryptedCollections$
|
||||||
|
.pipe(takeUntil(this.componentIsDestroyed$))
|
||||||
|
.subscribe((collections) => {
|
||||||
|
this.allCollections = collections;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnDestroy() {
|
ngOnDestroy() {
|
||||||
@@ -420,6 +442,16 @@ export class VaultV2Component implements OnInit, OnDestroy {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (cipher.canAssignToCollections) {
|
||||||
|
menu.push({
|
||||||
|
label: this.i18nService.t("assignToCollections"),
|
||||||
|
click: () =>
|
||||||
|
this.functionWithChangeDetection(async () => {
|
||||||
|
await this.shareCipher(cipher);
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (cipher.type) {
|
switch (cipher.type) {
|
||||||
@@ -531,6 +563,36 @@ export class VaultV2Component implements OnInit, OnDestroy {
|
|||||||
await this.go().catch(() => {});
|
await this.go().catch(() => {});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async shareCipher(cipher: CipherView) {
|
||||||
|
if (!cipher) {
|
||||||
|
this.toastService.showToast({
|
||||||
|
variant: "error",
|
||||||
|
title: this.i18nService.t("errorOccurred"),
|
||||||
|
message: this.i18nService.t("nothingSelected"),
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(await this.passwordReprompt(cipher))) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const availableCollections = this.getAvailableCollections(cipher);
|
||||||
|
|
||||||
|
const dialog = AssignCollectionsDesktopComponent.open(this.dialogService, {
|
||||||
|
data: {
|
||||||
|
ciphers: [cipher],
|
||||||
|
organizationId: cipher.organizationId as OrganizationId,
|
||||||
|
availableCollections,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await lastValueFrom(dialog.closed);
|
||||||
|
if (result === CollectionAssignmentResult.Saved) {
|
||||||
|
await this.savedCipher(cipher);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async addCipher(type: CipherType) {
|
async addCipher(type: CipherType) {
|
||||||
if (this.action === "add") {
|
if (this.action === "add") {
|
||||||
return;
|
return;
|
||||||
@@ -603,6 +665,16 @@ export class VaultV2Component implements OnInit, OnDestroy {
|
|||||||
await this.go().catch(() => {});
|
await this.go().catch(() => {});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private getAvailableCollections(cipher: CipherView): CollectionView[] {
|
||||||
|
const orgId = cipher.organizationId;
|
||||||
|
if (!orgId || orgId === "MyVault") {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const organization = this.allOrganizations.find((o) => o.id === orgId);
|
||||||
|
return this.allCollections.filter((c) => c.organizationId === organization?.id && !c.readOnly);
|
||||||
|
}
|
||||||
|
|
||||||
private calculateSearchBarLocalizationString(vaultFilter: VaultFilter): string {
|
private calculateSearchBarLocalizationString(vaultFilter: VaultFilter): string {
|
||||||
if (vaultFilter.status === "favorites") {
|
if (vaultFilter.status === "favorites") {
|
||||||
return "searchFavorites";
|
return "searchFavorites";
|
||||||
|
|||||||
Reference in New Issue
Block a user