1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-14 07:13:32 +00:00

[EC-424] top level vault (#4267)

* [EC-424] remove cog menu and header hr

* [EC-424] change "Add item" to "New item"

* [EC-424] include text for "New item"

* [EC-424] add new item dropdown to org vault
- add parent collection to dialog params

* [EC-14] show Add Item if missing permissions
This commit is contained in:
Jake Fink
2022-12-20 10:16:45 -05:00
committed by GitHub
parent 8585b0e1eb
commit 260580237a
8 changed files with 75 additions and 235 deletions

View File

@@ -36,6 +36,7 @@ export interface CollectionDialogParams {
collectionId?: string;
organizationId: string;
initialTab?: CollectionDialogTabType;
parentCollectionId?: string;
}
export enum CollectionDialogResult {
@@ -133,6 +134,8 @@ export class CollectionDialogComponent implements OnInit, OnDestroy {
});
} else {
this.nestOptions = collections;
const parent = collections.find((c) => c.id === this.params.parentCollectionId);
this.formGroup.patchValue({ parent: parent?.name ?? null });
}
this.loading = false;

View File

@@ -16,7 +16,7 @@
</div>
</div>
<div class="col-9">
<div class="page-header d-flex">
<div class="tw-mb-4 tw-flex">
<h1>
{{ "vaultItems" | i18n }}
<small #actionSpinner [appApiAction]="vaultItemsComponent.actionPromise">
@@ -30,20 +30,44 @@
</ng-container>
</small>
</h1>
<div class="ml-auto d-flex">
<app-vault-bulk-actions
[vaultItemsComponent]="vaultItemsComponent"
[deleted]="activeFilter.isDeleted"
[organization]="organization"
>
</app-vault-bulk-actions>
<div *ngIf="!activeFilter.isDeleted" class="ml-auto d-flex">
<div *ngIf="organization.canCreateNewCollections" class="dropdown mr-2" appListDropdown>
<button
class="btn"
bitButton
buttonType="primary"
type="button"
data-toggle="dropdown"
id="newItemDropdown"
aria-haspopup="true"
aria-expanded="false"
appA11yTitle="{{ 'new' | i18n }}"
>
{{ "new" | i18n }}<i class="bwi bwi-angle-down tw-ml-2" aria-hidden="true"></i>
</button>
<div
id="dropdown"
class="dropdown-menu dropdown-menu-right tw-mt-2"
aria-labelledby="newItemDropdown"
>
<button class="dropdown-item" appStopClick (click)="addCipher()">
<i class="bwi bwi-fw bwi-globe" aria-hidden="true"></i>
{{ "item" | i18n }}
</button>
<button class="dropdown-item" appStopClick (click)="addCollection()">
<i class="bwi bwi-fw bwi-collection" aria-hidden="true"></i>
{{ "collection" | i18n }}
</button>
</div>
</div>
<button
*ngIf="!organization?.canCreateNewCollections"
type="button"
class="btn btn-outline-primary btn-sm ml-auto"
bitButton
buttonType="primary"
(click)="addCipher()"
*ngIf="!activeFilter.isDeleted"
>
<i class="bwi bwi-plus bwi-fw" aria-hidden="true"></i>{{ "addItem" | i18n }}
<i class="bwi bwi-plus bwi-fw" aria-hidden="true"></i>{{ "newItem" | i18n }}
</button>
</div>
</div>

View File

@@ -8,7 +8,7 @@ import {
ViewContainerRef,
} from "@angular/core";
import { ActivatedRoute, Params, Router } from "@angular/router";
import { combineLatest, firstValueFrom, Subject } from "rxjs";
import { combineLatest, firstValueFrom, lastValueFrom, Subject } from "rxjs";
import { first, switchMap, takeUntil } from "rxjs/operators";
import { ModalService } from "@bitwarden/angular/services/modal.service";
@@ -22,10 +22,15 @@ import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUti
import { SyncService } from "@bitwarden/common/abstractions/sync/sync.service.abstraction";
import { Organization } from "@bitwarden/common/models/domain/organization";
import { CipherView } from "@bitwarden/common/models/view/cipher.view";
import { DialogService } from "@bitwarden/components";
import { VaultFilterService } from "../../vault/vault-filter/services/abstractions/vault-filter.service";
import { VaultFilter } from "../../vault/vault-filter/shared/models/vault-filter.model";
import { EntityEventsComponent } from "../manage/entity-events.component";
import {
CollectionDialogResult,
openCollectionDialog,
} from "../shared/components/collection-dialog";
import { AddEditComponent } from "./add-edit.component";
import { AttachmentsComponent } from "./attachments.component";
@@ -66,6 +71,7 @@ export class VaultComponent implements OnInit, OnDestroy {
private syncService: SyncService,
private i18nService: I18nService,
private modalService: ModalService,
private dialogService: DialogService,
private messagingService: MessagingService,
private broadcasterService: BroadcasterService,
private ngZone: NgZone,
@@ -160,6 +166,21 @@ export class VaultComponent implements OnInit, OnDestroy {
this.vaultItemsComponent.search(200);
}
async addCollection() {
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.vaultItemsComponent.actionPromise = this.vaultItemsComponent.refresh();
await this.vaultItemsComponent.actionPromise;
this.vaultItemsComponent.actionPromise = null;
}
}
async editCipherAttachments(cipher: CipherView) {
if (this.organization.maxStorageGb == null || this.organization.maxStorageGb === 0) {
this.messagingService.send("upgradeOrganization", { organizationId: cipher.organizationId });

View File

@@ -103,7 +103,6 @@ import { ToolsComponent } from "../tools/tools.component";
import { AddEditCustomFieldsComponent } from "../vault/add-edit-custom-fields.component";
import { AddEditComponent } from "../vault/add-edit.component";
import { AttachmentsComponent } from "../vault/attachments.component";
import { BulkActionsComponent } from "../vault/bulk-actions.component";
import { CollectionsComponent } from "../vault/collections.component";
import { FolderAddEditComponent } from "../vault/folder-add-edit.component";
import { ShareComponent } from "../vault/share.component";
@@ -130,7 +129,6 @@ import { SharedModule } from "./shared.module";
ApiKeyComponent,
AttachmentsComponent,
BillingSyncKeyComponent,
BulkActionsComponent,
ChangeEmailComponent,
ChangeKdfComponent,
ChangePasswordComponent,
@@ -237,7 +235,6 @@ import { SharedModule } from "./shared.module";
AdjustStorageComponent,
ApiKeyComponent,
AttachmentsComponent,
BulkActionsComponent,
ChangeEmailComponent,
ChangeKdfComponent,
ChangePasswordComponent,

View File

@@ -1,50 +0,0 @@
<div class="dropdown mr-2" appListDropdown>
<button
class="btn btn-sm btn-outline-secondary dropdown-toggle"
type="button"
id="bulkActionsButton"
data-toggle="dropdown"
aria-haspopup="true"
aria-expanded="false"
appA11yTitle="{{ 'options' | i18n }}"
>
<i class="bwi bwi-cog" aria-hidden="true"></i>
</button>
<div class="dropdown-menu dropdown-menu-right" aria-labelledby="bulkActionsButton">
<button
class="dropdown-item"
appStopClick
(click)="bulkMove()"
*ngIf="!deleted && !organization"
>
<i class="bwi bwi-fw bwi-folder" aria-hidden="true"></i>
{{ "moveSelected" | i18n }}
</button>
<button
class="dropdown-item"
appStopClick
(click)="bulkShare()"
*ngIf="!deleted && !organization"
>
<i class="bwi bwi-fw bwi-arrow-circle-right" aria-hidden="true"></i>
{{ "moveSelectedToOrg" | i18n }}
</button>
<button class="dropdown-item" (click)="bulkRestore()" *ngIf="deleted && !organization">
<i class="bwi bwi-fw bwi-undo" aria-hidden="true"></i>
{{ "restoreSelected" | i18n }}
</button>
<button class="dropdown-item text-danger" (click)="bulkDelete()">
<i class="bwi bwi-fw bwi-trash" aria-hidden="true"></i>
{{ (deleted ? "permanentlyDeleteSelected" : "deleteSelected") | i18n }}
</button>
<div class="dropdown-divider"></div>
<button class="dropdown-item" appStopClick (click)="selectAll(true)">
<i class="bwi bwi-fw bwi-check-square" aria-hidden="true"></i>
{{ "selectAll" | i18n }}
</button>
<button class="dropdown-item" appStopClick (click)="selectAll(false)">
<i class="bwi bwi-fw bwi-minus-square" aria-hidden="true"></i>
{{ "unselectAll" | i18n }}
</button>
</div>
</div>

View File

@@ -1,162 +0,0 @@
import { Component, Input } from "@angular/core";
import { lastValueFrom } from "rxjs";
import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
import { PasswordRepromptService } from "@bitwarden/common/abstractions/passwordReprompt.service";
import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service";
import { CipherRepromptType } from "@bitwarden/common/enums/cipherRepromptType";
import { Organization } from "@bitwarden/common/models/domain/organization";
import { DialogService } from "@bitwarden/components";
import {
BulkDeleteDialogResult,
openBulkDeleteDialog,
} from "./bulk-action-dialogs/bulk-delete-dialog/bulk-delete-dialog.component";
import {
BulkMoveDialogResult,
openBulkMoveDialog,
} from "./bulk-action-dialogs/bulk-move-dialog/bulk-move-dialog.component";
import {
BulkRestoreDialogResult,
openBulkRestoreDialog,
} from "./bulk-action-dialogs/bulk-restore-dialog/bulk-restore-dialog.component";
import {
BulkShareDialogResult,
openBulkShareDialog,
} from "./bulk-action-dialogs/bulk-share-dialog/bulk-share-dialog.component";
import { VaultItemsComponent } from "./vault-items.component";
@Component({
selector: "app-vault-bulk-actions",
templateUrl: "bulk-actions.component.html",
})
// eslint-disable-next-line rxjs-angular/prefer-takeuntil
export class BulkActionsComponent {
@Input() vaultItemsComponent: VaultItemsComponent;
@Input() deleted: boolean;
@Input() organization: Organization;
constructor(
private platformUtilsService: PlatformUtilsService,
private i18nService: I18nService,
private dialogService: DialogService,
private passwordRepromptService: PasswordRepromptService
) {}
async bulkDelete() {
if (!(await this.promptPassword())) {
return;
}
const selectedCipherIds = this.vaultItemsComponent.selectedCipherIds;
if (selectedCipherIds.length === 0) {
this.platformUtilsService.showToast(
"error",
this.i18nService.t("errorOccurred"),
this.i18nService.t("nothingSelected")
);
return;
}
const dialog = openBulkDeleteDialog(this.dialogService, {
data: {
permanent: this.deleted,
cipherIds: selectedCipherIds,
organization: this.organization,
},
});
const result = await lastValueFrom(dialog.closed);
if (result === BulkDeleteDialogResult.Deleted) {
await this.vaultItemsComponent.refresh();
}
}
async bulkRestore() {
if (!(await this.promptPassword())) {
return;
}
const selectedCipherIds = this.vaultItemsComponent.selectedCipherIds;
if (selectedCipherIds.length === 0) {
this.platformUtilsService.showToast(
"error",
this.i18nService.t("errorOccurred"),
this.i18nService.t("nothingSelected")
);
return;
}
const dialog = openBulkRestoreDialog(this.dialogService, {
data: {
cipherIds: selectedCipherIds,
},
});
const result = await lastValueFrom(dialog.closed);
if (result === BulkRestoreDialogResult.Restored) {
this.vaultItemsComponent.refresh();
}
}
async bulkShare() {
if (!(await this.promptPassword())) {
return;
}
const selectedCiphers = this.vaultItemsComponent.selectedCiphers;
if (selectedCiphers.length === 0) {
this.platformUtilsService.showToast(
"error",
this.i18nService.t("errorOccurred"),
this.i18nService.t("nothingSelected")
);
return;
}
const dialog = openBulkShareDialog(this.dialogService, { data: { ciphers: selectedCiphers } });
const result = await lastValueFrom(dialog.closed);
if (result === BulkShareDialogResult.Shared) {
this.vaultItemsComponent.refresh();
}
}
async bulkMove() {
if (!(await this.promptPassword())) {
return;
}
const selectedCipherIds = this.vaultItemsComponent.selectedCipherIds;
if (selectedCipherIds.length === 0) {
this.platformUtilsService.showToast(
"error",
this.i18nService.t("errorOccurred"),
this.i18nService.t("nothingSelected")
);
return;
}
const dialog = openBulkMoveDialog(this.dialogService, {
data: { cipherIds: selectedCipherIds },
});
const result = await lastValueFrom(dialog.closed);
if (result === BulkMoveDialogResult.Moved) {
this.vaultItemsComponent.refresh();
}
}
selectAll(select: boolean) {
this.vaultItemsComponent.checkAll(select);
}
private async promptPassword() {
const selectedCiphers = this.vaultItemsComponent.selectedCiphers;
const notProtected = !selectedCiphers.find(
(cipher) => cipher.reprompt !== CipherRepromptType.None
);
return notProtected || (await this.passwordRepromptService.showPasswordPrompt());
}
}

View File

@@ -17,7 +17,7 @@
</div>
</div>
<div [ngClass]="{ 'col-6': isShowingCards, 'col-9': !isShowingCards }">
<div class="page-header d-flex">
<div class="tw-mb-4 tw-flex">
<h1>
{{ "vaultItems" | i18n }}
<small #actionSpinner [appApiAction]="vaultItemsComponent.actionPromise">
@@ -32,18 +32,14 @@
</small>
</h1>
<div class="ml-auto d-flex">
<app-vault-bulk-actions
[vaultItemsComponent]="vaultItemsComponent"
[deleted]="activeFilter.isDeleted"
>
</app-vault-bulk-actions>
<button
type="button"
class="btn btn-outline-primary btn-sm"
bitButton
buttonType="primary"
(click)="addCipher()"
*ngIf="!activeFilter.isDeleted"
>
<i class="bwi bwi-plus bwi-fw" aria-hidden="true"></i>{{ "addItem" | i18n }}
<i class="bwi bwi-plus bwi-fw" aria-hidden="true"></i>{{ "newItem" | i18n }}
</button>
</div>
</div>

View File

@@ -386,6 +386,9 @@
"select": {
"message": "Select"
},
"newItem": {
"message": "New item"
},
"addItem": {
"message": "Add item"
},
@@ -395,6 +398,14 @@
"viewItem": {
"message": "View item"
},
"new":
{
"message": "New",
"description": "for adding new items"
},
"item": {
"message": "Item"
},
"ex": {
"message": "ex.",
"description": "Short abbreviation for 'example'."