mirror of
https://github.com/bitwarden/browser.git
synced 2024-11-21 11:35:34 +01:00
[AC-779] [Defect] Event log links for policies groups users and items not working (#5212)
* [AC-779] fix: policy link * [AC-779] fix: search string set by url not showing in input field * [AC-779] fix: navigation to cipher events * [AC-779] fix: collection link * [AC-779] chore: clean up old components * [AC-779] chore: remove some copy pasta
This commit is contained in:
parent
d77f77cea9
commit
4852992662
@ -1,106 +0,0 @@
|
||||
<div class="page-header d-flex">
|
||||
<h1>{{ "collections" | i18n }}</h1>
|
||||
<div class="ml-auto d-flex">
|
||||
<div>
|
||||
<label class="sr-only" for="search">{{ "search" | i18n }}</label>
|
||||
<input
|
||||
type="search"
|
||||
class="form-control form-control-sm"
|
||||
id="search"
|
||||
placeholder="{{ 'search' | i18n }}"
|
||||
[(ngModel)]="searchText"
|
||||
/>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
*ngIf="this.canCreate"
|
||||
class="btn btn-sm btn-outline-primary ml-3"
|
||||
(click)="add()"
|
||||
>
|
||||
<i class="bwi bwi-plus bwi-fw" aria-hidden="true"></i>
|
||||
{{ "newCollection" | i18n }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<ng-container *ngIf="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>
|
||||
<ng-container
|
||||
*ngIf="
|
||||
!loading &&
|
||||
(isPaging()
|
||||
? pagedCollections
|
||||
: (collections | search : searchText : 'name' : 'id')) as searchedCollections
|
||||
"
|
||||
>
|
||||
<p *ngIf="!searchedCollections.length">{{ "noCollectionsInList" | i18n }}</p>
|
||||
<table
|
||||
class="table table-hover table-list"
|
||||
*ngIf="searchedCollections.length"
|
||||
infiniteScroll
|
||||
[infiniteScrollDistance]="1"
|
||||
[infiniteScrollDisabled]="!isPaging()"
|
||||
(scrolled)="loadMore()"
|
||||
>
|
||||
<tbody>
|
||||
<tr *ngFor="let c of searchedCollections">
|
||||
<td>
|
||||
<a href="#" appStopClick (click)="edit(c)">{{ c.name }}</a>
|
||||
</td>
|
||||
<td class="table-list-options">
|
||||
<div class="dropdown" appListDropdown *ngIf="this.canEdit(c) || this.canDelete(c)">
|
||||
<button
|
||||
class="btn btn-outline-secondary dropdown-toggle"
|
||||
type="button"
|
||||
data-toggle="dropdown"
|
||||
aria-haspopup="true"
|
||||
aria-expanded="false"
|
||||
appA11yTitle="{{ 'options' | i18n }}"
|
||||
>
|
||||
<i class="bwi bwi-cog bwi-lg" aria-hidden="true"></i>
|
||||
</button>
|
||||
<div class="dropdown-menu dropdown-menu-right">
|
||||
<a
|
||||
class="dropdown-item"
|
||||
href="#"
|
||||
appStopClick
|
||||
*ngIf="this.canEdit(c)"
|
||||
(click)="edit(c)"
|
||||
>
|
||||
<i class="bwi bwi-fw bwi-pencil-square" aria-hidden="true"></i>
|
||||
{{ "edit" | i18n }}
|
||||
</a>
|
||||
<a
|
||||
class="dropdown-item"
|
||||
href="#"
|
||||
appStopClick
|
||||
*ngIf="this.canEdit(c)"
|
||||
(click)="users(c)"
|
||||
>
|
||||
<i class="bwi bwi-fw bwi-users" aria-hidden="true"></i>
|
||||
{{ "users" | i18n }}
|
||||
</a>
|
||||
<a
|
||||
class="dropdown-item text-danger"
|
||||
href="#"
|
||||
appStopClick
|
||||
*ngIf="this.canDelete(c)"
|
||||
(click)="delete(c)"
|
||||
>
|
||||
<i class="bwi bwi-fw bwi-trash" aria-hidden="true"></i>
|
||||
{{ "delete" | i18n }}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</ng-container>
|
||||
<ng-template #addEdit></ng-template>
|
||||
<ng-template #usersTemplate></ng-template>
|
@ -1,298 +0,0 @@
|
||||
import { Component, OnInit, ViewChild, ViewContainerRef } from "@angular/core";
|
||||
import { ActivatedRoute, Router } from "@angular/router";
|
||||
import { firstValueFrom, lastValueFrom } from "rxjs";
|
||||
import { first } from "rxjs/operators";
|
||||
|
||||
import { ModalService } from "@bitwarden/angular/services/modal.service";
|
||||
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 { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service";
|
||||
import { SearchService } from "@bitwarden/common/abstractions/search.service";
|
||||
import { CollectionService } from "@bitwarden/common/admin-console/abstractions/collection.service";
|
||||
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
|
||||
import { CollectionData } from "@bitwarden/common/admin-console/models/data/collection.data";
|
||||
import { Collection } from "@bitwarden/common/admin-console/models/domain/collection";
|
||||
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
|
||||
import {
|
||||
CollectionDetailsResponse,
|
||||
CollectionResponse,
|
||||
} from "@bitwarden/common/admin-console/models/response/collection.response";
|
||||
import { CollectionView } from "@bitwarden/common/admin-console/models/view/collection.view";
|
||||
import { ProductType } from "@bitwarden/common/enums";
|
||||
import { ListResponse } from "@bitwarden/common/models/response/list.response";
|
||||
import {
|
||||
DialogService,
|
||||
SimpleDialogCloseType,
|
||||
SimpleDialogOptions,
|
||||
SimpleDialogType,
|
||||
} from "@bitwarden/components";
|
||||
|
||||
import { EntityUsersComponent } from "../manage/entity-users.component";
|
||||
import {
|
||||
CollectionDialogResult,
|
||||
openCollectionDialog,
|
||||
} from "../shared/components/collection-dialog";
|
||||
|
||||
@Component({
|
||||
selector: "app-org-manage-collections",
|
||||
templateUrl: "collections.component.html",
|
||||
})
|
||||
// eslint-disable-next-line rxjs-angular/prefer-takeuntil
|
||||
export class CollectionsComponent implements OnInit {
|
||||
@ViewChild("addEdit", { read: ViewContainerRef, static: true }) addEditModalRef: ViewContainerRef;
|
||||
@ViewChild("usersTemplate", { read: ViewContainerRef, static: true })
|
||||
usersModalRef: ViewContainerRef;
|
||||
|
||||
loading = true;
|
||||
organization: Organization;
|
||||
canCreate = false;
|
||||
organizationId: string;
|
||||
collections: CollectionView[];
|
||||
assignedCollections: CollectionView[];
|
||||
pagedCollections: CollectionView[];
|
||||
searchText: string;
|
||||
|
||||
protected didScroll = false;
|
||||
protected pageSize = 100;
|
||||
|
||||
private pagedCollectionsCount = 0;
|
||||
|
||||
constructor(
|
||||
private apiService: ApiService,
|
||||
private route: ActivatedRoute,
|
||||
private collectionService: CollectionService,
|
||||
private modalService: ModalService,
|
||||
private i18nService: I18nService,
|
||||
private platformUtilsService: PlatformUtilsService,
|
||||
private searchService: SearchService,
|
||||
private logService: LogService,
|
||||
private organizationService: OrganizationService,
|
||||
private dialogService: DialogService,
|
||||
private router: Router
|
||||
) {}
|
||||
|
||||
async ngOnInit() {
|
||||
// eslint-disable-next-line rxjs-angular/prefer-takeuntil, rxjs/no-async-subscribe
|
||||
this.route.parent.parent.params.subscribe(async (params) => {
|
||||
this.organizationId = params.organizationId;
|
||||
await this.load();
|
||||
// eslint-disable-next-line rxjs-angular/prefer-takeuntil, rxjs/no-async-subscribe, rxjs/no-nested-subscribe
|
||||
this.route.queryParams.pipe(first()).subscribe(async (qParams) => {
|
||||
this.searchText = qParams.search;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async load() {
|
||||
this.organization = await this.organizationService.get(this.organizationId);
|
||||
this.canCreate = this.organization.canCreateNewCollections;
|
||||
|
||||
const decryptCollections = async (r: ListResponse<CollectionResponse>) => {
|
||||
const collections = r.data
|
||||
.filter((c) => c.organizationId === this.organizationId)
|
||||
.map((d) => new Collection(new CollectionData(d as CollectionDetailsResponse)));
|
||||
return await this.collectionService.decryptMany(collections);
|
||||
};
|
||||
|
||||
if (this.organization.canViewAssignedCollections) {
|
||||
const response = await this.apiService.getUserCollections();
|
||||
this.assignedCollections = await decryptCollections(response);
|
||||
}
|
||||
|
||||
if (this.organization.canViewAllCollections) {
|
||||
const response = await this.apiService.getCollections(this.organizationId);
|
||||
this.collections = await decryptCollections(response);
|
||||
} else {
|
||||
this.collections = this.assignedCollections;
|
||||
}
|
||||
|
||||
this.resetPaging();
|
||||
this.loading = false;
|
||||
}
|
||||
|
||||
loadMore() {
|
||||
if (!this.collections || this.collections.length <= this.pageSize) {
|
||||
return;
|
||||
}
|
||||
const pagedLength = this.pagedCollections.length;
|
||||
let pagedSize = this.pageSize;
|
||||
if (pagedLength === 0 && this.pagedCollectionsCount > this.pageSize) {
|
||||
pagedSize = this.pagedCollectionsCount;
|
||||
}
|
||||
if (this.collections.length > pagedLength) {
|
||||
this.pagedCollections = this.pagedCollections.concat(
|
||||
this.collections.slice(pagedLength, pagedLength + pagedSize)
|
||||
);
|
||||
}
|
||||
this.pagedCollectionsCount = this.pagedCollections.length;
|
||||
this.didScroll = this.pagedCollections.length > this.pageSize;
|
||||
}
|
||||
|
||||
async edit(collection?: CollectionView) {
|
||||
const canCreate = collection == undefined && this.canCreate;
|
||||
const canEdit = collection != undefined && this.canEdit(collection);
|
||||
const canDelete = collection != undefined && this.canDelete(collection);
|
||||
|
||||
if (!(canCreate || canEdit || canDelete)) {
|
||||
this.platformUtilsService.showToast("error", null, this.i18nService.t("missingPermissions"));
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
!collection &&
|
||||
this.organization.planProductType === ProductType.Free &&
|
||||
this.collections.length === this.organization.maxCollections
|
||||
) {
|
||||
// Show org upgrade modal
|
||||
// It might be worth creating a simple
|
||||
// org upgrade dialog service to launch the dialog here and in the people.comp
|
||||
// once the enterprise pod is done w/ their organization module refactor.
|
||||
const orgUpgradeSimpleDialogOpts: SimpleDialogOptions = {
|
||||
title: this.i18nService.t("upgradeOrganization"),
|
||||
content: this.i18nService.t(
|
||||
this.organization.canEditSubscription
|
||||
? "freeOrgMaxCollectionReachedManageBilling"
|
||||
: "freeOrgMaxCollectionReachedNoManageBilling",
|
||||
this.organization.maxCollections
|
||||
),
|
||||
type: SimpleDialogType.PRIMARY,
|
||||
};
|
||||
|
||||
if (this.organization.canEditSubscription) {
|
||||
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.canEditSubscription) {
|
||||
this.router.navigate(
|
||||
["/organizations", this.organization.id, "billing", "subscription"],
|
||||
{ queryParams: { upgrade: true } }
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const dialog = openCollectionDialog(this.dialogService, {
|
||||
data: { collectionId: collection?.id, organizationId: this.organizationId },
|
||||
});
|
||||
|
||||
const result = await lastValueFrom(dialog.closed);
|
||||
if (result === CollectionDialogResult.Saved || result === CollectionDialogResult.Deleted) {
|
||||
this.load();
|
||||
}
|
||||
}
|
||||
|
||||
add() {
|
||||
this.edit(null);
|
||||
}
|
||||
|
||||
async delete(collection: CollectionView) {
|
||||
const confirmed = await this.platformUtilsService.showDialog(
|
||||
this.i18nService.t("deleteCollectionConfirmation"),
|
||||
collection.name,
|
||||
this.i18nService.t("yes"),
|
||||
this.i18nService.t("no"),
|
||||
"warning"
|
||||
);
|
||||
if (!confirmed) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
await this.apiService.deleteCollection(this.organizationId, collection.id);
|
||||
this.platformUtilsService.showToast(
|
||||
"success",
|
||||
null,
|
||||
this.i18nService.t("deletedCollectionId", collection.name)
|
||||
);
|
||||
this.removeCollection(collection);
|
||||
} catch (e) {
|
||||
this.logService.error(e);
|
||||
this.platformUtilsService.showToast("error", null, this.i18nService.t("missingPermissions"));
|
||||
}
|
||||
}
|
||||
|
||||
async users(collection: CollectionView) {
|
||||
const [modal] = await this.modalService.openViewRef(
|
||||
EntityUsersComponent,
|
||||
this.usersModalRef,
|
||||
(comp) => {
|
||||
comp.organizationId = this.organizationId;
|
||||
comp.entity = "collection";
|
||||
comp.entityId = collection.id;
|
||||
comp.entityName = collection.name;
|
||||
|
||||
// eslint-disable-next-line rxjs-angular/prefer-takeuntil
|
||||
comp.onEditedUsers.subscribe(() => {
|
||||
this.load();
|
||||
modal.close();
|
||||
});
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
async resetPaging() {
|
||||
this.pagedCollections = [];
|
||||
this.loadMore();
|
||||
}
|
||||
|
||||
isSearching() {
|
||||
return this.searchService.isSearchable(this.searchText);
|
||||
}
|
||||
|
||||
isPaging() {
|
||||
const searching = this.isSearching();
|
||||
if (searching && this.didScroll) {
|
||||
this.resetPaging();
|
||||
}
|
||||
return !searching && this.collections && this.collections.length > this.pageSize;
|
||||
}
|
||||
|
||||
canEdit(collection: CollectionView) {
|
||||
if (this.organization.canEditAnyCollection) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (
|
||||
this.organization.canEditAssignedCollections &&
|
||||
this.assignedCollections.some((c) => c.id === collection.id)
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
canDelete(collection: CollectionView) {
|
||||
if (this.organization.canDeleteAnyCollection) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (
|
||||
this.organization.canDeleteAssignedCollections &&
|
||||
this.assignedCollections.some((c) => c.id === collection.id)
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private removeCollection(collection: CollectionView) {
|
||||
const index = this.collections.indexOf(collection);
|
||||
if (index > -1) {
|
||||
this.collections.splice(index, 1);
|
||||
this.resetPaging();
|
||||
}
|
||||
}
|
||||
}
|
@ -1,38 +0,0 @@
|
||||
<div class="container page-content">
|
||||
<div class="row">
|
||||
<div class="col-3">
|
||||
<div class="card" *ngIf="organization">
|
||||
<div class="card-header">{{ "manage" | i18n }}</div>
|
||||
<div class="list-group list-group-flush">
|
||||
<a
|
||||
routerLink="members"
|
||||
class="list-group-item"
|
||||
routerLinkActive="active"
|
||||
*ngIf="organization.canManageUsers"
|
||||
>
|
||||
{{ "members" | i18n }}
|
||||
</a>
|
||||
<a
|
||||
routerLink="collections"
|
||||
class="list-group-item"
|
||||
routerLinkActive="active"
|
||||
*ngIf="organization.canViewAllCollections || organization.canViewAssignedCollections"
|
||||
>
|
||||
{{ "collections" | i18n }}
|
||||
</a>
|
||||
<a
|
||||
routerLink="groups"
|
||||
class="list-group-item"
|
||||
routerLinkActive="active"
|
||||
*ngIf="organization.canManageGroups"
|
||||
>
|
||||
{{ "groups" | i18n }}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-9">
|
||||
<router-outlet></router-outlet>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
@ -1,23 +0,0 @@
|
||||
import { Component, OnInit } from "@angular/core";
|
||||
import { ActivatedRoute } from "@angular/router";
|
||||
|
||||
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
|
||||
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
|
||||
|
||||
@Component({
|
||||
selector: "app-org-manage",
|
||||
templateUrl: "manage.component.html",
|
||||
})
|
||||
// eslint-disable-next-line rxjs-angular/prefer-takeuntil
|
||||
export class ManageComponent implements OnInit {
|
||||
organization: Organization;
|
||||
|
||||
constructor(private route: ActivatedRoute, private organizationService: OrganizationService) {}
|
||||
|
||||
ngOnInit() {
|
||||
// eslint-disable-next-line rxjs-angular/prefer-takeuntil, rxjs/no-async-subscribe
|
||||
this.route.parent.params.subscribe(async (params) => {
|
||||
this.organization = await this.organizationService.get(params.organizationId);
|
||||
});
|
||||
}
|
||||
}
|
@ -15,9 +15,7 @@ import { Organization } from "@bitwarden/common/admin-console/models/domain/orga
|
||||
import { OrganizationPermissionsGuard } from "../../admin-console/organizations/guards/org-permissions.guard";
|
||||
import { OrganizationRedirectGuard } from "../../admin-console/organizations/guards/org-redirect.guard";
|
||||
import { OrganizationLayoutComponent } from "../../admin-console/organizations/layouts/organization-layout.component";
|
||||
import { CollectionsComponent } from "../../admin-console/organizations/manage/collections.component";
|
||||
import { GroupsComponent } from "../../admin-console/organizations/manage/groups.component";
|
||||
import { ManageComponent } from "../../admin-console/organizations/manage/manage.component";
|
||||
import { VaultModule } from "../../vault/org-vault/vault.module";
|
||||
|
||||
const routes: Routes = [
|
||||
@ -62,19 +60,6 @@ const routes: Routes = [
|
||||
organizationPermissions: canAccessGroupsTab,
|
||||
},
|
||||
},
|
||||
{
|
||||
path: "manage",
|
||||
component: ManageComponent,
|
||||
children: [
|
||||
{
|
||||
path: "collections",
|
||||
component: CollectionsComponent,
|
||||
data: {
|
||||
titleId: "collections",
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: "reporting",
|
||||
loadChildren: () =>
|
||||
|
@ -487,12 +487,7 @@ export class EventService {
|
||||
const a = this.makeAnchor(shortId);
|
||||
a.setAttribute(
|
||||
"href",
|
||||
"#/organizations/" +
|
||||
ev.organizationId +
|
||||
"/vault?search=" +
|
||||
shortId +
|
||||
"&viewEvents=" +
|
||||
ev.cipherId
|
||||
`#/organizations/${ev.organizationId}/vault?search=${shortId}&viewEvents=${ev.cipherId}&type=all`
|
||||
);
|
||||
return a.outerHTML;
|
||||
}
|
||||
@ -507,10 +502,9 @@ export class EventService {
|
||||
private formatCollectionId(ev: EventResponse) {
|
||||
const shortId = this.getShortId(ev.collectionId);
|
||||
const a = this.makeAnchor(shortId);
|
||||
// TODO: Update view/edit collection link after EC-14 is completed
|
||||
a.setAttribute(
|
||||
"href",
|
||||
"#/organizations/" + ev.organizationId + "/manage/collections?search=" + shortId
|
||||
`#/organizations/${ev.organizationId}/vault?collectionId=${ev.collectionId}`
|
||||
);
|
||||
return a.outerHTML;
|
||||
}
|
||||
@ -557,7 +551,7 @@ export class EventService {
|
||||
const a = this.makeAnchor(shortId);
|
||||
a.setAttribute(
|
||||
"href",
|
||||
"#/organizations/" + ev.organizationId + "/manage/policies?policyId=" + ev.policyId
|
||||
"#/organizations/" + ev.organizationId + "/settings/policies?policyId=" + ev.policyId
|
||||
);
|
||||
return a.outerHTML;
|
||||
}
|
||||
|
@ -36,10 +36,8 @@ import { VerifyRecoverDeleteComponent } from "../../auth/verify-recover-delete.c
|
||||
import { OrganizationSwitcherComponent } from "../admin-console/components/organization-switcher.component";
|
||||
import { OrganizationCreateModule } from "../admin-console/organizations/create/organization-create.module";
|
||||
import { OrganizationLayoutComponent } from "../admin-console/organizations/layouts/organization-layout.component";
|
||||
import { CollectionsComponent as OrgManageCollectionsComponent } from "../admin-console/organizations/manage/collections.component";
|
||||
import { EntityEventsComponent as OrgEntityEventsComponent } from "../admin-console/organizations/manage/entity-events.component";
|
||||
import { EventsComponent as OrgEventsComponent } from "../admin-console/organizations/manage/events.component";
|
||||
import { ManageComponent as OrgManageComponent } from "../admin-console/organizations/manage/manage.component";
|
||||
import { UserConfirmComponent as OrgUserConfirmComponent } from "../admin-console/organizations/manage/user-confirm.component";
|
||||
import { AcceptFamilySponsorshipComponent } from "../admin-console/organizations/sponsorships/accept-family-sponsorship.component";
|
||||
import { FamiliesForEnterpriseSetupComponent } from "../admin-console/organizations/sponsorships/families-for-enterprise-setup.component";
|
||||
@ -172,8 +170,6 @@ import { SharedModule } from "./shared.module";
|
||||
OrgEventsComponent,
|
||||
OrgExposedPasswordsReportComponent,
|
||||
OrgInactiveTwoFactorReportComponent,
|
||||
OrgManageCollectionsComponent,
|
||||
OrgManageComponent,
|
||||
OrgReusedPasswordsReportComponent,
|
||||
OrgToolsComponent,
|
||||
OrgUnsecuredWebsitesReportComponent,
|
||||
@ -281,8 +277,6 @@ import { SharedModule } from "./shared.module";
|
||||
OrgEventsComponent,
|
||||
OrgExposedPasswordsReportComponent,
|
||||
OrgInactiveTwoFactorReportComponent,
|
||||
OrgManageCollectionsComponent,
|
||||
OrgManageComponent,
|
||||
OrgReusedPasswordsReportComponent,
|
||||
OrgToolsComponent,
|
||||
OrgUnsecuredWebsitesReportComponent,
|
||||
|
@ -21,7 +21,8 @@
|
||||
placeholder="{{ searchPlaceholder | i18n }}"
|
||||
id="search"
|
||||
class="form-control"
|
||||
(input)="searchTextChanged($any($event.target).value)"
|
||||
[ngModel]="searchText"
|
||||
(ngModelChange)="onSearchTextChanged($event)"
|
||||
autocomplete="off"
|
||||
appAutofocus
|
||||
/>
|
||||
|
@ -32,12 +32,13 @@ import { OrganizationOptionsComponent } from "./organization-options.component";
|
||||
export class VaultFilterComponent implements OnInit, OnDestroy {
|
||||
filters?: VaultFilterList;
|
||||
@Input() activeFilter: VaultFilter = new VaultFilter();
|
||||
@Output() onSearchTextChanged = new EventEmitter<string>();
|
||||
@Output() onAddFolder = new EventEmitter<never>();
|
||||
@Output() onEditFolder = new EventEmitter<FolderFilter>();
|
||||
|
||||
@Input() searchText = "";
|
||||
@Output() searchTextChanged = new EventEmitter<string>();
|
||||
|
||||
isLoaded = false;
|
||||
searchText = "";
|
||||
|
||||
protected destroy$: Subject<void> = new Subject<void>();
|
||||
|
||||
@ -99,9 +100,9 @@ export class VaultFilterComponent implements OnInit, OnDestroy {
|
||||
this.destroy$.complete();
|
||||
}
|
||||
|
||||
searchTextChanged(t: string) {
|
||||
onSearchTextChanged(t: string) {
|
||||
this.searchText = t;
|
||||
this.onSearchTextChanged.emit(t);
|
||||
this.searchTextChanged.emit(t);
|
||||
}
|
||||
|
||||
applyOrganizationFilter = async (orgNode: TreeNode<OrganizationFilter>): Promise<void> => {
|
||||
|
@ -7,7 +7,8 @@
|
||||
<app-vault-filter
|
||||
#vaultFilter
|
||||
[activeFilter]="activeFilter"
|
||||
(onSearchTextChanged)="filterSearchText($event)"
|
||||
[searchText]="currentSearchText$ | async"
|
||||
(searchTextChanged)="filterSearchText($event)"
|
||||
(onAddFolder)="addFolder()"
|
||||
(onEditFolder)="editFolder($event)"
|
||||
></app-vault-filter>
|
||||
|
@ -8,7 +8,14 @@ import {
|
||||
ViewContainerRef,
|
||||
} from "@angular/core";
|
||||
import { ActivatedRoute, Params, Router } from "@angular/router";
|
||||
import { BehaviorSubject, combineLatest, firstValueFrom, lastValueFrom, Subject } from "rxjs";
|
||||
import {
|
||||
BehaviorSubject,
|
||||
combineLatest,
|
||||
firstValueFrom,
|
||||
lastValueFrom,
|
||||
Observable,
|
||||
Subject,
|
||||
} from "rxjs";
|
||||
import {
|
||||
concatMap,
|
||||
debounceTime,
|
||||
@ -131,9 +138,10 @@ export class VaultComponent implements OnInit, OnDestroy {
|
||||
protected collections: CollectionView[];
|
||||
protected isEmpty: boolean;
|
||||
protected selectedCollection: TreeNode<CollectionView> | undefined;
|
||||
protected currentSearchText$: Observable<string>;
|
||||
|
||||
private refresh$ = new BehaviorSubject<void>(null);
|
||||
private searchText$ = new Subject<string>();
|
||||
private refresh$ = new BehaviorSubject<void>(null);
|
||||
private destroy$ = new Subject<void>();
|
||||
|
||||
constructor(
|
||||
@ -242,12 +250,12 @@ export class VaultComponent implements OnInit, OnDestroy {
|
||||
})
|
||||
);
|
||||
|
||||
const querySearchText$ = this.route.queryParams.pipe(map((queryParams) => queryParams.search));
|
||||
this.currentSearchText$ = this.route.queryParams.pipe(map((queryParams) => queryParams.search));
|
||||
|
||||
const ciphers$ = combineLatest([
|
||||
Utils.asyncToObservable(() => this.cipherService.getAllDecrypted()),
|
||||
filter$,
|
||||
querySearchText$,
|
||||
this.currentSearchText$,
|
||||
]).pipe(
|
||||
filter(([ciphers, filter]) => ciphers != undefined && filter != undefined),
|
||||
concatMap(async ([ciphers, filter, searchText]) => {
|
||||
@ -262,7 +270,7 @@ export class VaultComponent implements OnInit, OnDestroy {
|
||||
shareReplay({ refCount: true, bufferSize: 1 })
|
||||
);
|
||||
|
||||
const collections$ = combineLatest([nestedCollections$, filter$, querySearchText$]).pipe(
|
||||
const collections$ = combineLatest([nestedCollections$, filter$, this.currentSearchText$]).pipe(
|
||||
filter(([collections, filter]) => collections != undefined && filter != undefined),
|
||||
map(([collections, filter, searchText]) => {
|
||||
if (filter.collectionId === undefined || filter.collectionId === Unassigned) {
|
||||
|
@ -10,7 +10,8 @@
|
||||
#vaultFilter
|
||||
[organization]="organization"
|
||||
[activeFilter]="activeFilter"
|
||||
(onSearchTextChanged)="filterSearchText($event)"
|
||||
[searchText]="currentSearchText$ | async"
|
||||
(searchTextChanged)="filterSearchText($event)"
|
||||
></app-organization-vault-filter>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -8,7 +8,14 @@ import {
|
||||
ViewContainerRef,
|
||||
} from "@angular/core";
|
||||
import { ActivatedRoute, Params, Router } from "@angular/router";
|
||||
import { BehaviorSubject, combineLatest, firstValueFrom, lastValueFrom, Subject } from "rxjs";
|
||||
import {
|
||||
BehaviorSubject,
|
||||
combineLatest,
|
||||
firstValueFrom,
|
||||
lastValueFrom,
|
||||
Observable,
|
||||
Subject,
|
||||
} from "rxjs";
|
||||
import {
|
||||
concatMap,
|
||||
debounceTime,
|
||||
@ -123,9 +130,10 @@ export class VaultComponent implements OnInit, OnDestroy {
|
||||
protected selectedCollection: TreeNode<CollectionAdminView> | undefined;
|
||||
protected isEmpty: boolean;
|
||||
protected showMissingCollectionPermissionMessage: boolean;
|
||||
protected currentSearchText$: Observable<string>;
|
||||
|
||||
private refresh$ = new BehaviorSubject<void>(null);
|
||||
private searchText$ = new Subject<string>();
|
||||
private refresh$ = new BehaviorSubject<void>(null);
|
||||
private destroy$ = new Subject<void>();
|
||||
|
||||
constructor(
|
||||
@ -219,7 +227,7 @@ export class VaultComponent implements OnInit, OnDestroy {
|
||||
})
|
||||
);
|
||||
|
||||
const querySearchText$ = this.route.queryParams.pipe(map((queryParams) => queryParams.search));
|
||||
this.currentSearchText$ = this.route.queryParams.pipe(map((queryParams) => queryParams.search));
|
||||
|
||||
const allCollectionsWithoutUnassigned$ = organizationId$.pipe(
|
||||
switchMap((orgId) => this.collectionAdminService.getAll(orgId)),
|
||||
@ -256,7 +264,7 @@ export class VaultComponent implements OnInit, OnDestroy {
|
||||
})
|
||||
);
|
||||
|
||||
const ciphers$ = combineLatest([allCiphers$, filter$, querySearchText$]).pipe(
|
||||
const ciphers$ = combineLatest([allCiphers$, filter$, this.currentSearchText$]).pipe(
|
||||
filter(([ciphers, filter]) => ciphers != undefined && filter != undefined),
|
||||
concatMap(async ([ciphers, filter, searchText]) => {
|
||||
if (filter.collectionId === undefined && filter.type === undefined) {
|
||||
@ -279,7 +287,7 @@ export class VaultComponent implements OnInit, OnDestroy {
|
||||
shareReplay({ refCount: true, bufferSize: 1 })
|
||||
);
|
||||
|
||||
const collections$ = combineLatest([nestedCollections$, filter$, querySearchText$]).pipe(
|
||||
const collections$ = combineLatest([nestedCollections$, filter$, this.currentSearchText$]).pipe(
|
||||
filter(([collections, filter]) => collections != undefined && filter != undefined),
|
||||
map(([collections, filter, searchText]) => {
|
||||
if (
|
||||
@ -379,6 +387,33 @@ export class VaultComponent implements OnInit, OnDestroy {
|
||||
)
|
||||
.subscribe();
|
||||
|
||||
firstSetup$
|
||||
.pipe(
|
||||
switchMap(() => combineLatest([this.route.queryParams, organization$, allCiphers$])),
|
||||
switchMap(async ([qParams, organization, allCiphers$]) => {
|
||||
const cipherId = qParams.viewEvents;
|
||||
if (!cipherId) {
|
||||
return;
|
||||
}
|
||||
const cipher = allCiphers$.find((c) => c.id === cipherId);
|
||||
if (organization.useEvents && cipher != undefined) {
|
||||
this.viewEvents(cipher);
|
||||
} else {
|
||||
this.platformUtilsService.showToast(
|
||||
"error",
|
||||
this.i18nService.t("errorOccurred"),
|
||||
this.i18nService.t("unknownCipher")
|
||||
);
|
||||
this.router.navigate([], {
|
||||
queryParams: { viewEvents: null },
|
||||
queryParamsHandling: "merge",
|
||||
});
|
||||
}
|
||||
}),
|
||||
takeUntil(this.destroy$)
|
||||
)
|
||||
.subscribe();
|
||||
|
||||
firstSetup$
|
||||
.pipe(
|
||||
switchMap(() => this.refresh$),
|
||||
|
Loading…
Reference in New Issue
Block a user