1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-22 19:23:52 +00: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:
Andreas Coroiu
2023-04-18 08:04:39 +02:00
committed by GitHub
parent d77f77cea9
commit 4852992662
13 changed files with 67 additions and 512 deletions

View File

@@ -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>

View File

@@ -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();
}
}
}

View File

@@ -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>

View File

@@ -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);
});
}
}

View File

@@ -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: () =>