[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 { OrganizationPermissionsGuard } from "../../admin-console/organizations/guards/org-permissions.guard";
|
||||||
import { OrganizationRedirectGuard } from "../../admin-console/organizations/guards/org-redirect.guard";
|
import { OrganizationRedirectGuard } from "../../admin-console/organizations/guards/org-redirect.guard";
|
||||||
import { OrganizationLayoutComponent } from "../../admin-console/organizations/layouts/organization-layout.component";
|
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 { 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";
|
import { VaultModule } from "../../vault/org-vault/vault.module";
|
||||||
|
|
||||||
const routes: Routes = [
|
const routes: Routes = [
|
||||||
|
@ -62,19 +60,6 @@ const routes: Routes = [
|
||||||
organizationPermissions: canAccessGroupsTab,
|
organizationPermissions: canAccessGroupsTab,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
|
||||||
path: "manage",
|
|
||||||
component: ManageComponent,
|
|
||||||
children: [
|
|
||||||
{
|
|
||||||
path: "collections",
|
|
||||||
component: CollectionsComponent,
|
|
||||||
data: {
|
|
||||||
titleId: "collections",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
path: "reporting",
|
path: "reporting",
|
||||||
loadChildren: () =>
|
loadChildren: () =>
|
||||||
|
|
|
@ -487,12 +487,7 @@ export class EventService {
|
||||||
const a = this.makeAnchor(shortId);
|
const a = this.makeAnchor(shortId);
|
||||||
a.setAttribute(
|
a.setAttribute(
|
||||||
"href",
|
"href",
|
||||||
"#/organizations/" +
|
`#/organizations/${ev.organizationId}/vault?search=${shortId}&viewEvents=${ev.cipherId}&type=all`
|
||||||
ev.organizationId +
|
|
||||||
"/vault?search=" +
|
|
||||||
shortId +
|
|
||||||
"&viewEvents=" +
|
|
||||||
ev.cipherId
|
|
||||||
);
|
);
|
||||||
return a.outerHTML;
|
return a.outerHTML;
|
||||||
}
|
}
|
||||||
|
@ -507,10 +502,9 @@ export class EventService {
|
||||||
private formatCollectionId(ev: EventResponse) {
|
private formatCollectionId(ev: EventResponse) {
|
||||||
const shortId = this.getShortId(ev.collectionId);
|
const shortId = this.getShortId(ev.collectionId);
|
||||||
const a = this.makeAnchor(shortId);
|
const a = this.makeAnchor(shortId);
|
||||||
// TODO: Update view/edit collection link after EC-14 is completed
|
|
||||||
a.setAttribute(
|
a.setAttribute(
|
||||||
"href",
|
"href",
|
||||||
"#/organizations/" + ev.organizationId + "/manage/collections?search=" + shortId
|
`#/organizations/${ev.organizationId}/vault?collectionId=${ev.collectionId}`
|
||||||
);
|
);
|
||||||
return a.outerHTML;
|
return a.outerHTML;
|
||||||
}
|
}
|
||||||
|
@ -557,7 +551,7 @@ export class EventService {
|
||||||
const a = this.makeAnchor(shortId);
|
const a = this.makeAnchor(shortId);
|
||||||
a.setAttribute(
|
a.setAttribute(
|
||||||
"href",
|
"href",
|
||||||
"#/organizations/" + ev.organizationId + "/manage/policies?policyId=" + ev.policyId
|
"#/organizations/" + ev.organizationId + "/settings/policies?policyId=" + ev.policyId
|
||||||
);
|
);
|
||||||
return a.outerHTML;
|
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 { OrganizationSwitcherComponent } from "../admin-console/components/organization-switcher.component";
|
||||||
import { OrganizationCreateModule } from "../admin-console/organizations/create/organization-create.module";
|
import { OrganizationCreateModule } from "../admin-console/organizations/create/organization-create.module";
|
||||||
import { OrganizationLayoutComponent } from "../admin-console/organizations/layouts/organization-layout.component";
|
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 { EntityEventsComponent as OrgEntityEventsComponent } from "../admin-console/organizations/manage/entity-events.component";
|
||||||
import { EventsComponent as OrgEventsComponent } from "../admin-console/organizations/manage/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 { UserConfirmComponent as OrgUserConfirmComponent } from "../admin-console/organizations/manage/user-confirm.component";
|
||||||
import { AcceptFamilySponsorshipComponent } from "../admin-console/organizations/sponsorships/accept-family-sponsorship.component";
|
import { AcceptFamilySponsorshipComponent } from "../admin-console/organizations/sponsorships/accept-family-sponsorship.component";
|
||||||
import { FamiliesForEnterpriseSetupComponent } from "../admin-console/organizations/sponsorships/families-for-enterprise-setup.component";
|
import { FamiliesForEnterpriseSetupComponent } from "../admin-console/organizations/sponsorships/families-for-enterprise-setup.component";
|
||||||
|
@ -172,8 +170,6 @@ import { SharedModule } from "./shared.module";
|
||||||
OrgEventsComponent,
|
OrgEventsComponent,
|
||||||
OrgExposedPasswordsReportComponent,
|
OrgExposedPasswordsReportComponent,
|
||||||
OrgInactiveTwoFactorReportComponent,
|
OrgInactiveTwoFactorReportComponent,
|
||||||
OrgManageCollectionsComponent,
|
|
||||||
OrgManageComponent,
|
|
||||||
OrgReusedPasswordsReportComponent,
|
OrgReusedPasswordsReportComponent,
|
||||||
OrgToolsComponent,
|
OrgToolsComponent,
|
||||||
OrgUnsecuredWebsitesReportComponent,
|
OrgUnsecuredWebsitesReportComponent,
|
||||||
|
@ -281,8 +277,6 @@ import { SharedModule } from "./shared.module";
|
||||||
OrgEventsComponent,
|
OrgEventsComponent,
|
||||||
OrgExposedPasswordsReportComponent,
|
OrgExposedPasswordsReportComponent,
|
||||||
OrgInactiveTwoFactorReportComponent,
|
OrgInactiveTwoFactorReportComponent,
|
||||||
OrgManageCollectionsComponent,
|
|
||||||
OrgManageComponent,
|
|
||||||
OrgReusedPasswordsReportComponent,
|
OrgReusedPasswordsReportComponent,
|
||||||
OrgToolsComponent,
|
OrgToolsComponent,
|
||||||
OrgUnsecuredWebsitesReportComponent,
|
OrgUnsecuredWebsitesReportComponent,
|
||||||
|
|
|
@ -21,7 +21,8 @@
|
||||||
placeholder="{{ searchPlaceholder | i18n }}"
|
placeholder="{{ searchPlaceholder | i18n }}"
|
||||||
id="search"
|
id="search"
|
||||||
class="form-control"
|
class="form-control"
|
||||||
(input)="searchTextChanged($any($event.target).value)"
|
[ngModel]="searchText"
|
||||||
|
(ngModelChange)="onSearchTextChanged($event)"
|
||||||
autocomplete="off"
|
autocomplete="off"
|
||||||
appAutofocus
|
appAutofocus
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -32,12 +32,13 @@ import { OrganizationOptionsComponent } from "./organization-options.component";
|
||||||
export class VaultFilterComponent implements OnInit, OnDestroy {
|
export class VaultFilterComponent implements OnInit, OnDestroy {
|
||||||
filters?: VaultFilterList;
|
filters?: VaultFilterList;
|
||||||
@Input() activeFilter: VaultFilter = new VaultFilter();
|
@Input() activeFilter: VaultFilter = new VaultFilter();
|
||||||
@Output() onSearchTextChanged = new EventEmitter<string>();
|
|
||||||
@Output() onAddFolder = new EventEmitter<never>();
|
@Output() onAddFolder = new EventEmitter<never>();
|
||||||
@Output() onEditFolder = new EventEmitter<FolderFilter>();
|
@Output() onEditFolder = new EventEmitter<FolderFilter>();
|
||||||
|
|
||||||
|
@Input() searchText = "";
|
||||||
|
@Output() searchTextChanged = new EventEmitter<string>();
|
||||||
|
|
||||||
isLoaded = false;
|
isLoaded = false;
|
||||||
searchText = "";
|
|
||||||
|
|
||||||
protected destroy$: Subject<void> = new Subject<void>();
|
protected destroy$: Subject<void> = new Subject<void>();
|
||||||
|
|
||||||
|
@ -99,9 +100,9 @@ export class VaultFilterComponent implements OnInit, OnDestroy {
|
||||||
this.destroy$.complete();
|
this.destroy$.complete();
|
||||||
}
|
}
|
||||||
|
|
||||||
searchTextChanged(t: string) {
|
onSearchTextChanged(t: string) {
|
||||||
this.searchText = t;
|
this.searchText = t;
|
||||||
this.onSearchTextChanged.emit(t);
|
this.searchTextChanged.emit(t);
|
||||||
}
|
}
|
||||||
|
|
||||||
applyOrganizationFilter = async (orgNode: TreeNode<OrganizationFilter>): Promise<void> => {
|
applyOrganizationFilter = async (orgNode: TreeNode<OrganizationFilter>): Promise<void> => {
|
||||||
|
|
|
@ -7,7 +7,8 @@
|
||||||
<app-vault-filter
|
<app-vault-filter
|
||||||
#vaultFilter
|
#vaultFilter
|
||||||
[activeFilter]="activeFilter"
|
[activeFilter]="activeFilter"
|
||||||
(onSearchTextChanged)="filterSearchText($event)"
|
[searchText]="currentSearchText$ | async"
|
||||||
|
(searchTextChanged)="filterSearchText($event)"
|
||||||
(onAddFolder)="addFolder()"
|
(onAddFolder)="addFolder()"
|
||||||
(onEditFolder)="editFolder($event)"
|
(onEditFolder)="editFolder($event)"
|
||||||
></app-vault-filter>
|
></app-vault-filter>
|
||||||
|
|
|
@ -8,7 +8,14 @@ import {
|
||||||
ViewContainerRef,
|
ViewContainerRef,
|
||||||
} from "@angular/core";
|
} from "@angular/core";
|
||||||
import { ActivatedRoute, Params, Router } from "@angular/router";
|
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 {
|
import {
|
||||||
concatMap,
|
concatMap,
|
||||||
debounceTime,
|
debounceTime,
|
||||||
|
@ -131,9 +138,10 @@ export class VaultComponent implements OnInit, OnDestroy {
|
||||||
protected collections: CollectionView[];
|
protected collections: CollectionView[];
|
||||||
protected isEmpty: boolean;
|
protected isEmpty: boolean;
|
||||||
protected selectedCollection: TreeNode<CollectionView> | undefined;
|
protected selectedCollection: TreeNode<CollectionView> | undefined;
|
||||||
|
protected currentSearchText$: Observable<string>;
|
||||||
|
|
||||||
private refresh$ = new BehaviorSubject<void>(null);
|
|
||||||
private searchText$ = new Subject<string>();
|
private searchText$ = new Subject<string>();
|
||||||
|
private refresh$ = new BehaviorSubject<void>(null);
|
||||||
private destroy$ = new Subject<void>();
|
private destroy$ = new Subject<void>();
|
||||||
|
|
||||||
constructor(
|
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([
|
const ciphers$ = combineLatest([
|
||||||
Utils.asyncToObservable(() => this.cipherService.getAllDecrypted()),
|
Utils.asyncToObservable(() => this.cipherService.getAllDecrypted()),
|
||||||
filter$,
|
filter$,
|
||||||
querySearchText$,
|
this.currentSearchText$,
|
||||||
]).pipe(
|
]).pipe(
|
||||||
filter(([ciphers, filter]) => ciphers != undefined && filter != undefined),
|
filter(([ciphers, filter]) => ciphers != undefined && filter != undefined),
|
||||||
concatMap(async ([ciphers, filter, searchText]) => {
|
concatMap(async ([ciphers, filter, searchText]) => {
|
||||||
|
@ -262,7 +270,7 @@ export class VaultComponent implements OnInit, OnDestroy {
|
||||||
shareReplay({ refCount: true, bufferSize: 1 })
|
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),
|
filter(([collections, filter]) => collections != undefined && filter != undefined),
|
||||||
map(([collections, filter, searchText]) => {
|
map(([collections, filter, searchText]) => {
|
||||||
if (filter.collectionId === undefined || filter.collectionId === Unassigned) {
|
if (filter.collectionId === undefined || filter.collectionId === Unassigned) {
|
||||||
|
|
|
@ -10,7 +10,8 @@
|
||||||
#vaultFilter
|
#vaultFilter
|
||||||
[organization]="organization"
|
[organization]="organization"
|
||||||
[activeFilter]="activeFilter"
|
[activeFilter]="activeFilter"
|
||||||
(onSearchTextChanged)="filterSearchText($event)"
|
[searchText]="currentSearchText$ | async"
|
||||||
|
(searchTextChanged)="filterSearchText($event)"
|
||||||
></app-organization-vault-filter>
|
></app-organization-vault-filter>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -8,7 +8,14 @@ import {
|
||||||
ViewContainerRef,
|
ViewContainerRef,
|
||||||
} from "@angular/core";
|
} from "@angular/core";
|
||||||
import { ActivatedRoute, Params, Router } from "@angular/router";
|
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 {
|
import {
|
||||||
concatMap,
|
concatMap,
|
||||||
debounceTime,
|
debounceTime,
|
||||||
|
@ -123,9 +130,10 @@ export class VaultComponent implements OnInit, OnDestroy {
|
||||||
protected selectedCollection: TreeNode<CollectionAdminView> | undefined;
|
protected selectedCollection: TreeNode<CollectionAdminView> | undefined;
|
||||||
protected isEmpty: boolean;
|
protected isEmpty: boolean;
|
||||||
protected showMissingCollectionPermissionMessage: boolean;
|
protected showMissingCollectionPermissionMessage: boolean;
|
||||||
|
protected currentSearchText$: Observable<string>;
|
||||||
|
|
||||||
private refresh$ = new BehaviorSubject<void>(null);
|
|
||||||
private searchText$ = new Subject<string>();
|
private searchText$ = new Subject<string>();
|
||||||
|
private refresh$ = new BehaviorSubject<void>(null);
|
||||||
private destroy$ = new Subject<void>();
|
private destroy$ = new Subject<void>();
|
||||||
|
|
||||||
constructor(
|
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(
|
const allCollectionsWithoutUnassigned$ = organizationId$.pipe(
|
||||||
switchMap((orgId) => this.collectionAdminService.getAll(orgId)),
|
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),
|
filter(([ciphers, filter]) => ciphers != undefined && filter != undefined),
|
||||||
concatMap(async ([ciphers, filter, searchText]) => {
|
concatMap(async ([ciphers, filter, searchText]) => {
|
||||||
if (filter.collectionId === undefined && filter.type === undefined) {
|
if (filter.collectionId === undefined && filter.type === undefined) {
|
||||||
|
@ -279,7 +287,7 @@ export class VaultComponent implements OnInit, OnDestroy {
|
||||||
shareReplay({ refCount: true, bufferSize: 1 })
|
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),
|
filter(([collections, filter]) => collections != undefined && filter != undefined),
|
||||||
map(([collections, filter, searchText]) => {
|
map(([collections, filter, searchText]) => {
|
||||||
if (
|
if (
|
||||||
|
@ -379,6 +387,33 @@ export class VaultComponent implements OnInit, OnDestroy {
|
||||||
)
|
)
|
||||||
.subscribe();
|
.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$
|
firstSetup$
|
||||||
.pipe(
|
.pipe(
|
||||||
switchMap(() => this.refresh$),
|
switchMap(() => this.refresh$),
|
||||||
|
|
Loading…
Reference in New Issue