mirror of
https://github.com/bitwarden/web
synced 2025-12-06 00:03:28 +00:00
[Paging] Added for Organization Users, Pages, and Collections (#539)
* Updating jslib * Added paging for Organizational Users, Groups, and Collections * Updated jslib fb7335b -> 2858724
This commit is contained in:
@@ -16,9 +16,11 @@
|
|||||||
<i class="fa fa-spinner fa-spin text-muted" title="{{'loading' | i18n}}" aria-hidden="true"></i>
|
<i class="fa fa-spinner fa-spin text-muted" title="{{'loading' | i18n}}" aria-hidden="true"></i>
|
||||||
<span class="sr-only">{{'loading' | i18n}}</span>
|
<span class="sr-only">{{'loading' | i18n}}</span>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
<ng-container *ngIf="!loading && (collections | search:searchText:'name':'id') as searchedCollections">
|
<ng-container
|
||||||
|
*ngIf="!loading && (isPaging() ? pagedCollections : collections | search:searchText:'name':'id') as searchedCollections">
|
||||||
<p *ngIf="!searchedCollections.length">{{'noCollectionsInList' | i18n}}</p>
|
<p *ngIf="!searchedCollections.length">{{'noCollectionsInList' | i18n}}</p>
|
||||||
<table class="table table-hover table-list" *ngIf="searchedCollections.length">
|
<table class="table table-hover table-list" *ngIf="searchedCollections.length" infiniteScroll
|
||||||
|
[infiniteScrollDistance]="1" [infiniteScrollDisabled]="!isPaging()" (scrolled)="loadMore()">
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr *ngFor="let c of searchedCollections">
|
<tr *ngFor="let c of searchedCollections">
|
||||||
<td>
|
<td>
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import { ApiService } from 'jslib/abstractions/api.service';
|
|||||||
import { CollectionService } from 'jslib/abstractions/collection.service';
|
import { CollectionService } from 'jslib/abstractions/collection.service';
|
||||||
import { I18nService } from 'jslib/abstractions/i18n.service';
|
import { I18nService } from 'jslib/abstractions/i18n.service';
|
||||||
import { PlatformUtilsService } from 'jslib/abstractions/platformUtils.service';
|
import { PlatformUtilsService } from 'jslib/abstractions/platformUtils.service';
|
||||||
|
import { SearchService } from 'jslib/abstractions/search.service';
|
||||||
import { UserService } from 'jslib/abstractions/user.service';
|
import { UserService } from 'jslib/abstractions/user.service';
|
||||||
|
|
||||||
import { CollectionData } from 'jslib/models/data/collectionData';
|
import { CollectionData } from 'jslib/models/data/collectionData';
|
||||||
@@ -40,15 +41,20 @@ export class CollectionsComponent implements OnInit {
|
|||||||
loading = true;
|
loading = true;
|
||||||
organizationId: string;
|
organizationId: string;
|
||||||
collections: CollectionView[];
|
collections: CollectionView[];
|
||||||
|
pagedCollections: CollectionView[];
|
||||||
searchText: string;
|
searchText: string;
|
||||||
|
|
||||||
|
protected didScroll = false;
|
||||||
|
protected pageSize = 100;
|
||||||
|
|
||||||
|
private pagedCollectionsCount = 0;
|
||||||
private modal: ModalComponent = null;
|
private modal: ModalComponent = null;
|
||||||
|
|
||||||
constructor(private apiService: ApiService, private route: ActivatedRoute,
|
constructor(private apiService: ApiService, private route: ActivatedRoute,
|
||||||
private collectionService: CollectionService, private componentFactoryResolver: ComponentFactoryResolver,
|
private collectionService: CollectionService, private componentFactoryResolver: ComponentFactoryResolver,
|
||||||
private analytics: Angulartics2, private toasterService: ToasterService,
|
private analytics: Angulartics2, private toasterService: ToasterService,
|
||||||
private i18nService: I18nService, private platformUtilsService: PlatformUtilsService,
|
private i18nService: I18nService, private platformUtilsService: PlatformUtilsService,
|
||||||
private userService: UserService) { }
|
private userService: UserService, private searchService: SearchService) { }
|
||||||
|
|
||||||
async ngOnInit() {
|
async ngOnInit() {
|
||||||
this.route.parent.parent.params.subscribe(async (params) => {
|
this.route.parent.parent.params.subscribe(async (params) => {
|
||||||
@@ -74,9 +80,27 @@ export class CollectionsComponent implements OnInit {
|
|||||||
const collections = response.data.filter((c) => c.organizationId === this.organizationId).map((r) =>
|
const collections = response.data.filter((c) => c.organizationId === this.organizationId).map((r) =>
|
||||||
new Collection(new CollectionData(r as CollectionDetailsResponse)));
|
new Collection(new CollectionData(r as CollectionDetailsResponse)));
|
||||||
this.collections = await this.collectionService.decryptMany(collections);
|
this.collections = await this.collectionService.decryptMany(collections);
|
||||||
|
this.resetPaging();
|
||||||
this.loading = false;
|
this.loading = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
loadMore() {
|
||||||
|
if (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;
|
||||||
|
}
|
||||||
|
|
||||||
edit(collection: CollectionView) {
|
edit(collection: CollectionView) {
|
||||||
if (this.modal != null) {
|
if (this.modal != null) {
|
||||||
this.modal.close();
|
this.modal.close();
|
||||||
@@ -147,10 +171,28 @@ export class CollectionsComponent implements OnInit {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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.length > this.pageSize;
|
||||||
|
}
|
||||||
|
|
||||||
private removeCollection(collection: CollectionView) {
|
private removeCollection(collection: CollectionView) {
|
||||||
const index = this.collections.indexOf(collection);
|
const index = this.collections.indexOf(collection);
|
||||||
if (index > -1) {
|
if (index > -1) {
|
||||||
this.collections.splice(index, 1);
|
this.collections.splice(index, 1);
|
||||||
|
this.resetPaging();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,9 +16,10 @@
|
|||||||
<i class="fa fa-spinner fa-spin text-muted" title="{{'loading' | i18n}}" aria-hidden="true"></i>
|
<i class="fa fa-spinner fa-spin text-muted" title="{{'loading' | i18n}}" aria-hidden="true"></i>
|
||||||
<span class="sr-only">{{'loading' | i18n}}</span>
|
<span class="sr-only">{{'loading' | i18n}}</span>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
<ng-container *ngIf="!loading && (groups | search:searchText:'name':'id') as searchedGroups">
|
<ng-container *ngIf="!loading && (isPaging() ? pagedGroups : groups | search:searchText:'name':'id') as searchedGroups">
|
||||||
<p *ngIf="!searchedGroups.length">{{'noGroupsInList' | i18n}}</p>
|
<p *ngIf="!searchedGroups.length">{{'noGroupsInList' | i18n}}</p>
|
||||||
<table class="table table-hover table-list" *ngIf="searchedGroups.length">
|
<table class="table table-hover table-list" *ngIf="searchedGroups.length" infiniteScroll
|
||||||
|
[infiniteScrollDistance]="1" [infiniteScrollDisabled]="!isPaging()" (scrolled)="loadMore()">
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr *ngFor="let g of searchedGroups">
|
<tr *ngFor="let g of searchedGroups">
|
||||||
<td>
|
<td>
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import { Angulartics2 } from 'angulartics2';
|
|||||||
import { ApiService } from 'jslib/abstractions/api.service';
|
import { ApiService } from 'jslib/abstractions/api.service';
|
||||||
import { I18nService } from 'jslib/abstractions/i18n.service';
|
import { I18nService } from 'jslib/abstractions/i18n.service';
|
||||||
import { PlatformUtilsService } from 'jslib/abstractions/platformUtils.service';
|
import { PlatformUtilsService } from 'jslib/abstractions/platformUtils.service';
|
||||||
|
import { SearchService } from 'jslib/abstractions/search.service';
|
||||||
import { UserService } from 'jslib/abstractions/user.service';
|
import { UserService } from 'jslib/abstractions/user.service';
|
||||||
|
|
||||||
import { GroupResponse } from 'jslib/models/response/groupResponse';
|
import { GroupResponse } from 'jslib/models/response/groupResponse';
|
||||||
@@ -37,15 +38,20 @@ export class GroupsComponent implements OnInit {
|
|||||||
loading = true;
|
loading = true;
|
||||||
organizationId: string;
|
organizationId: string;
|
||||||
groups: GroupResponse[];
|
groups: GroupResponse[];
|
||||||
|
pagedGroups: GroupResponse[];
|
||||||
searchText: string;
|
searchText: string;
|
||||||
|
|
||||||
|
protected didScroll = false;
|
||||||
|
protected pageSize = 100;
|
||||||
|
|
||||||
|
private pagedGroupsCount = 0;
|
||||||
private modal: ModalComponent = null;
|
private modal: ModalComponent = null;
|
||||||
|
|
||||||
constructor(private apiService: ApiService, private route: ActivatedRoute,
|
constructor(private apiService: ApiService, private route: ActivatedRoute,
|
||||||
private i18nService: I18nService, private componentFactoryResolver: ComponentFactoryResolver,
|
private i18nService: I18nService, private componentFactoryResolver: ComponentFactoryResolver,
|
||||||
private analytics: Angulartics2, private toasterService: ToasterService,
|
private analytics: Angulartics2, private toasterService: ToasterService,
|
||||||
private platformUtilsService: PlatformUtilsService, private userService: UserService,
|
private platformUtilsService: PlatformUtilsService, private userService: UserService,
|
||||||
private router: Router) { }
|
private router: Router, private searchService: SearchService) { }
|
||||||
|
|
||||||
async ngOnInit() {
|
async ngOnInit() {
|
||||||
this.route.parent.parent.params.subscribe(async (params) => {
|
this.route.parent.parent.params.subscribe(async (params) => {
|
||||||
@@ -70,9 +76,26 @@ export class GroupsComponent implements OnInit {
|
|||||||
const groups = response.data != null && response.data.length > 0 ? response.data : [];
|
const groups = response.data != null && response.data.length > 0 ? response.data : [];
|
||||||
groups.sort(Utils.getSortFunction(this.i18nService, 'name'));
|
groups.sort(Utils.getSortFunction(this.i18nService, 'name'));
|
||||||
this.groups = groups;
|
this.groups = groups;
|
||||||
|
this.resetPaging();
|
||||||
this.loading = false;
|
this.loading = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
loadMore() {
|
||||||
|
if (this.groups.length <= this.pageSize) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const pagedLength = this.pagedGroups.length;
|
||||||
|
let pagedSize = this.pageSize;
|
||||||
|
if (pagedLength === 0 && this.pagedGroupsCount > this.pageSize) {
|
||||||
|
pagedSize = this.pagedGroupsCount;
|
||||||
|
}
|
||||||
|
if (this.groups.length > pagedLength) {
|
||||||
|
this.pagedGroups = this.pagedGroups.concat(this.groups.slice(pagedLength, pagedLength + pagedSize));
|
||||||
|
}
|
||||||
|
this.pagedGroupsCount = this.pagedGroups.length;
|
||||||
|
this.didScroll = this.pagedGroups.length > this.pageSize;
|
||||||
|
}
|
||||||
|
|
||||||
edit(group: GroupResponse) {
|
edit(group: GroupResponse) {
|
||||||
if (this.modal != null) {
|
if (this.modal != null) {
|
||||||
this.modal.close();
|
this.modal.close();
|
||||||
@@ -142,10 +165,28 @@ export class GroupsComponent implements OnInit {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async resetPaging() {
|
||||||
|
this.pagedGroups = [];
|
||||||
|
this.loadMore();
|
||||||
|
}
|
||||||
|
|
||||||
|
isSearching() {
|
||||||
|
return this.searchService.isSearchable(this.searchText);
|
||||||
|
}
|
||||||
|
|
||||||
|
isPaging() {
|
||||||
|
const searching = this.isSearching();
|
||||||
|
if (searching && this.didScroll) {
|
||||||
|
this.resetPaging();
|
||||||
|
}
|
||||||
|
return !searching && this.groups.length > this.pageSize;
|
||||||
|
}
|
||||||
|
|
||||||
private removeGroup(group: GroupResponse) {
|
private removeGroup(group: GroupResponse) {
|
||||||
const index = this.groups.indexOf(group);
|
const index = this.groups.indexOf(group);
|
||||||
if (index > -1) {
|
if (index > -1) {
|
||||||
this.groups.splice(index, 1);
|
this.groups.splice(index, 1);
|
||||||
|
this.resetPaging();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,13 +35,15 @@
|
|||||||
<i class="fa fa-spinner fa-spin text-muted" title="{{'loading' | i18n}}" aria-hidden="true"></i>
|
<i class="fa fa-spinner fa-spin text-muted" title="{{'loading' | i18n}}" aria-hidden="true"></i>
|
||||||
<span class="sr-only">{{'loading' | i18n}}</span>
|
<span class="sr-only">{{'loading' | i18n}}</span>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
<ng-container *ngIf="!loading && (users | search:searchText:'name':'email':'id') as searchedUsers">
|
<ng-container
|
||||||
|
*ngIf="!loading && (isPaging() ? pagedUsers : users | search:searchText:'name':'email':'id') as searchedUsers">
|
||||||
<p *ngIf="!searchedUsers.length">{{'noUsersInList' | i18n}}</p>
|
<p *ngIf="!searchedUsers.length">{{'noUsersInList' | i18n}}</p>
|
||||||
<ng-container *ngIf="searchedUsers.length">
|
<ng-container *ngIf="searchedUsers.length">
|
||||||
<app-callout type="info" title="{{'confirmUsers' | i18n}}" icon="fa-check-circle" *ngIf="showConfirmUsers">
|
<app-callout type="info" title="{{'confirmUsers' | i18n}}" icon="fa-check-circle" *ngIf="showConfirmUsers">
|
||||||
{{'usersNeedConfirmed' | i18n}}
|
{{'usersNeedConfirmed' | i18n}}
|
||||||
</app-callout>
|
</app-callout>
|
||||||
<table class="table table-hover table-list">
|
<table class="table table-hover table-list" infiniteScroll [infiniteScrollDistance]="1"
|
||||||
|
[infiniteScrollDisabled]="!isPaging()" (scrolled)="loadMore()">
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr *ngFor="let u of searchedUsers">
|
<tr *ngFor="let u of searchedUsers">
|
||||||
<td width="30">
|
<td width="30">
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ import { ApiService } from 'jslib/abstractions/api.service';
|
|||||||
import { CryptoService } from 'jslib/abstractions/crypto.service';
|
import { CryptoService } from 'jslib/abstractions/crypto.service';
|
||||||
import { I18nService } from 'jslib/abstractions/i18n.service';
|
import { I18nService } from 'jslib/abstractions/i18n.service';
|
||||||
import { PlatformUtilsService } from 'jslib/abstractions/platformUtils.service';
|
import { PlatformUtilsService } from 'jslib/abstractions/platformUtils.service';
|
||||||
|
import { SearchService } from 'jslib/abstractions/search.service';
|
||||||
import { StorageService } from 'jslib/abstractions/storage.service';
|
import { StorageService } from 'jslib/abstractions/storage.service';
|
||||||
import { UserService } from 'jslib/abstractions/user.service';
|
import { UserService } from 'jslib/abstractions/user.service';
|
||||||
|
|
||||||
@@ -50,6 +51,7 @@ export class PeopleComponent implements OnInit {
|
|||||||
loading = true;
|
loading = true;
|
||||||
organizationId: string;
|
organizationId: string;
|
||||||
users: OrganizationUserUserDetailsResponse[];
|
users: OrganizationUserUserDetailsResponse[];
|
||||||
|
pagedUsers: OrganizationUserUserDetailsResponse[];
|
||||||
searchText: string;
|
searchText: string;
|
||||||
status: OrganizationUserStatusType = null;
|
status: OrganizationUserStatusType = null;
|
||||||
statusMap = new Map<OrganizationUserStatusType, OrganizationUserUserDetailsResponse[]>();
|
statusMap = new Map<OrganizationUserStatusType, OrganizationUserUserDetailsResponse[]>();
|
||||||
@@ -59,6 +61,10 @@ export class PeopleComponent implements OnInit {
|
|||||||
accessEvents = false;
|
accessEvents = false;
|
||||||
accessGroups = false;
|
accessGroups = false;
|
||||||
|
|
||||||
|
protected didScroll = false;
|
||||||
|
protected pageSize = 100;
|
||||||
|
|
||||||
|
private pagedUsersCount = 0;
|
||||||
private modal: ModalComponent = null;
|
private modal: ModalComponent = null;
|
||||||
private allUsers: OrganizationUserUserDetailsResponse[];
|
private allUsers: OrganizationUserUserDetailsResponse[];
|
||||||
|
|
||||||
@@ -67,7 +73,7 @@ export class PeopleComponent implements OnInit {
|
|||||||
private platformUtilsService: PlatformUtilsService, private analytics: Angulartics2,
|
private platformUtilsService: PlatformUtilsService, private analytics: Angulartics2,
|
||||||
private toasterService: ToasterService, private cryptoService: CryptoService,
|
private toasterService: ToasterService, private cryptoService: CryptoService,
|
||||||
private userService: UserService, private router: Router,
|
private userService: UserService, private router: Router,
|
||||||
private storageService: StorageService) { }
|
private storageService: StorageService, private searchService: SearchService) { }
|
||||||
|
|
||||||
async ngOnInit() {
|
async ngOnInit() {
|
||||||
this.route.parent.parent.params.subscribe(async (params) => {
|
this.route.parent.parent.params.subscribe(async (params) => {
|
||||||
@@ -119,6 +125,23 @@ export class PeopleComponent implements OnInit {
|
|||||||
} else {
|
} else {
|
||||||
this.users = this.allUsers;
|
this.users = this.allUsers;
|
||||||
}
|
}
|
||||||
|
this.resetPaging();
|
||||||
|
}
|
||||||
|
|
||||||
|
loadMore() {
|
||||||
|
if (this.users.length <= this.pageSize) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const pagedLength = this.pagedUsers.length;
|
||||||
|
let pagedSize = this.pageSize;
|
||||||
|
if (pagedLength === 0 && this.pagedUsersCount > this.pageSize) {
|
||||||
|
pagedSize = this.pagedUsersCount;
|
||||||
|
}
|
||||||
|
if (this.users.length > pagedLength) {
|
||||||
|
this.pagedUsers = this.pagedUsers.concat(this.users.slice(pagedLength, pagedLength + pagedSize));
|
||||||
|
}
|
||||||
|
this.pagedUsersCount = this.pagedUsers.length;
|
||||||
|
this.didScroll = this.pagedUsers.length > this.pageSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
get allCount() {
|
get allCount() {
|
||||||
@@ -294,6 +317,23 @@ export class PeopleComponent implements OnInit {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async resetPaging() {
|
||||||
|
this.pagedUsers = [];
|
||||||
|
this.loadMore();
|
||||||
|
}
|
||||||
|
|
||||||
|
isSearching() {
|
||||||
|
return this.searchService.isSearchable(this.searchText);
|
||||||
|
}
|
||||||
|
|
||||||
|
isPaging() {
|
||||||
|
const searching = this.isSearching();
|
||||||
|
if (searching && this.didScroll) {
|
||||||
|
this.resetPaging();
|
||||||
|
}
|
||||||
|
return !searching && this.users.length > this.pageSize;
|
||||||
|
}
|
||||||
|
|
||||||
private async doConfirmation(user: OrganizationUserUserDetailsResponse) {
|
private async doConfirmation(user: OrganizationUserUserDetailsResponse) {
|
||||||
const orgKey = await this.cryptoService.getOrgKey(this.organizationId);
|
const orgKey = await this.cryptoService.getOrgKey(this.organizationId);
|
||||||
const publicKeyResponse = await this.apiService.getUserPublicKey(user.userId);
|
const publicKeyResponse = await this.apiService.getUserPublicKey(user.userId);
|
||||||
@@ -313,6 +353,7 @@ export class PeopleComponent implements OnInit {
|
|||||||
let index = this.users.indexOf(user);
|
let index = this.users.indexOf(user);
|
||||||
if (index > -1) {
|
if (index > -1) {
|
||||||
this.users.splice(index, 1);
|
this.users.splice(index, 1);
|
||||||
|
this.resetPaging();
|
||||||
}
|
}
|
||||||
if (this.statusMap.has(OrganizationUserStatusType.Accepted)) {
|
if (this.statusMap.has(OrganizationUserStatusType.Accepted)) {
|
||||||
index = this.statusMap.get(OrganizationUserStatusType.Accepted).indexOf(user);
|
index = this.statusMap.get(OrganizationUserStatusType.Accepted).indexOf(user);
|
||||||
|
|||||||
Reference in New Issue
Block a user