You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@archiva.apache.org by ma...@apache.org on 2020/11/08 10:39:08 UTC

[archiva] branch master updated: Adding component for entity display

This is an automated email from the ASF dual-hosted git repository.

martin_s pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/archiva.git


The following commit(s) were added to refs/heads/master by this push:
     new 0e2187b  Adding component for entity display
0e2187b is described below

commit 0e2187bf0efde67e9e51289f224183164b570ca0
Author: Martin Stockhammer <ma...@apache.org>
AuthorDate: Sun Nov 8 11:39:00 2020 +0100

    Adding component for entity display
---
 .../src/main/archiva-web/src/app/app.module.ts     |  11 +-
 .../entity-service.ts}                             |  21 ++--
 .../paginated-entities.component.html              |  38 ++++++
 .../paginated-entities.component.scss}             |  10 +-
 .../paginated-entities.component.spec.ts}          |  27 +++-
 .../paginated-entities.component.ts                | 138 +++++++++++++++++++++
 .../manage-users-list.component.html               |  37 ++----
 .../manage-users-list.component.ts                 |  69 ++++-------
 .../src/app/services/archiva-request.service.ts    |   6 +-
 .../archiva-web/src/app/services/user.service.ts   |  10 +-
 .../src/main/archiva-web/src/assets/i18n/en.json   |   3 +-
 .../src/test/resources/generate-users.sh           |  46 +++++++
 12 files changed, 306 insertions(+), 110 deletions(-)

diff --git a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/app.module.ts b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/app.module.ts
index 3535437..9e35162 100644
--- a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/app.module.ts
+++ b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/app.module.ts
@@ -41,8 +41,8 @@ import { ManageRolesComponent } from './modules/user/manage-roles/manage-roles.c
 import { SecurityConfigurationComponent } from './modules/user/security-configuration/security-configuration.component';
 import { ManageUsersListComponent } from './modules/user/users/manage-users-list/manage-users-list.component';
 import { ManageUsersAddComponent } from './modules/user/users/manage-users-add/manage-users-add.component';
-import { EnableTooltipDirective } from './directives/enable-tooltip.directive';
-import {NgbPagination, NgbPaginationModule} from "@ng-bootstrap/ng-bootstrap";
+import { NgbPaginationModule, NgbTooltipModule} from "@ng-bootstrap/ng-bootstrap";
+import { PaginatedEntitiesComponent } from './modules/general/paginated-entities/paginated-entities.component';
 
 
 @NgModule({
@@ -64,9 +64,7 @@ import {NgbPagination, NgbPaginationModule} from "@ng-bootstrap/ng-bootstrap";
     SecurityConfigurationComponent,
     ManageUsersListComponent,
     ManageUsersAddComponent,
-    EnableTooltipDirective,
-
-
+    PaginatedEntitiesComponent,
   ],
   imports: [
     BrowserModule,
@@ -81,7 +79,8 @@ import {NgbPagination, NgbPaginationModule} from "@ng-bootstrap/ng-bootstrap";
         deps: [HttpClient]
       }
     }),
-      NgbPaginationModule
+      NgbPaginationModule,
+      NgbTooltipModule
   ],
   providers: [],
   bootstrap: [AppComponent]
diff --git a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/directives/enable-tooltip.directive.ts b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/model/entity-service.ts
similarity index 68%
rename from archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/directives/enable-tooltip.directive.ts
rename to archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/model/entity-service.ts
index 099001c..a9e7ce8 100644
--- a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/directives/enable-tooltip.directive.ts
+++ b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/model/entity-service.ts
@@ -16,18 +16,13 @@
  * under the License.
  */
 
-import {AfterViewChecked, AfterViewInit, Directive, ElementRef, OnInit} from '@angular/core';
+import {PagedResult} from "./paged-result";
+import {Observable} from "rxjs";
 
-declare var jQuery:any;
-@Directive({
-  selector: '[appEnableTooltip]'
-})
-export class EnableTooltipDirective implements AfterViewInit {
-
-
-  constructor() { }
-
-  ngAfterViewInit(): void {
-     jQuery('[data-toggle="tooltip"]').tooltip({container: 'body', html: true});
-  }
+/**
+ * This is a functional interface that is used to retrieve entity data.
+ * @typeparam T The type of the entity that is returned from the service
+ */
+export interface EntityService<T> {
+    (searchTerm:string,offset:number,limit:number,orderBy:string,order:string):Observable<PagedResult<T>>
 }
diff --git a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/general/paginated-entities/paginated-entities.component.html b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/general/paginated-entities/paginated-entities.component.html
new file mode 100644
index 0000000..f37059e
--- /dev/null
+++ b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/general/paginated-entities/paginated-entities.component.html
@@ -0,0 +1,38 @@
+<!--
+  ~ Licensed to the Apache Software Foundation (ASF) under one
+  ~ or more contributor license agreements.  See the NOTICE file
+  ~ distributed with this work for additional information
+  ~ regarding copyright ownership.  The ASF licenses this file
+  ~ to you under the Apache License, Version 2.0 (the
+  ~ "License"); you may not use this file except in compliance
+  ~ with the License.  You may obtain a copy of the License at
+  ~
+  ~ http://www.apache.org/licenses/LICENSE-2.0
+  ~ Unless required by applicable law or agreed to in writing,
+  ~ software distributed under the License is distributed on an
+  ~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+  ~ KIND, either express or implied.  See the License for the
+  ~ specific language governing permissions and limitations
+  ~ under the License.
+  -->
+
+<form class="mt-3 mb-3">
+    <div class="form-row align-items-center">
+        <div class="col-lg-4 col-md-2 col-sm-1">
+            <label class="sr-only" for="searchQuery">{{'search.label' |translate}}</label>
+            <input type="text" class="form-control" id="searchQuery" placeholder="Search" #searchTerm
+                   (keyup)="search(searchTerm.value)">
+        </div>
+        <div class="col-auto">
+            <button type="submit" class="btn btn-primary">{{'search.button'|translate}}</button>
+        </div>
+    </div>
+
+
+</form>
+
+<ng-content></ng-content>
+
+<ngb-pagination [collectionSize]="total$|async" [pageSize]="pageSize" [maxSize]="pagination.maxSize" [rotate]="pagination.rotate"
+                [boundaryLinks]="pagination.boundaryLinks" [ellipses]="pagination.ellipses"
+                [(page)]="page" (pageChange)="changePage($event)" aria-label="Pagination"></ngb-pagination>
diff --git a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/directives/enable-tooltip.directive.spec.ts b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/general/paginated-entities/paginated-entities.component.scss
similarity index 75%
copy from archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/directives/enable-tooltip.directive.spec.ts
copy to archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/general/paginated-entities/paginated-entities.component.scss
index 916f276..343c3b1 100644
--- a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/directives/enable-tooltip.directive.spec.ts
+++ b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/general/paginated-entities/paginated-entities.component.scss
@@ -1,4 +1,4 @@
-/*
+/*!
  * Licensed to the Apache Software Foundation (ASF) under one
  * or more contributor license agreements.  See the NOTICE file
  * distributed with this work for additional information
@@ -16,11 +16,3 @@
  * under the License.
  */
 
-import { EnableTooltipDirective } from './enable-tooltip.directive';
-
-describe('EnableTooltipDirective', () => {
-  it('should create an instance', () => {
-    const directive = new EnableTooltipDirective();
-    expect(directive).toBeTruthy();
-  });
-});
diff --git a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/directives/enable-tooltip.directive.spec.ts b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/general/paginated-entities/paginated-entities.component.spec.ts
similarity index 54%
rename from archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/directives/enable-tooltip.directive.spec.ts
rename to archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/general/paginated-entities/paginated-entities.component.spec.ts
index 916f276..0bd4fc8 100644
--- a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/directives/enable-tooltip.directive.spec.ts
+++ b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/general/paginated-entities/paginated-entities.component.spec.ts
@@ -16,11 +16,28 @@
  * under the License.
  */
 
-import { EnableTooltipDirective } from './enable-tooltip.directive';
+import { ComponentFixture, TestBed } from '@angular/core/testing';
 
-describe('EnableTooltipDirective', () => {
-  it('should create an instance', () => {
-    const directive = new EnableTooltipDirective();
-    expect(directive).toBeTruthy();
+import { PaginatedEntitiesComponent } from './paginated-entities.component';
+
+describe('PaginatedEntitiesComponent', () => {
+  let component: PaginatedEntitiesComponent;
+  let fixture: ComponentFixture<PaginatedEntitiesComponent>;
+
+  beforeEach(async () => {
+    await TestBed.configureTestingModule({
+      declarations: [ PaginatedEntitiesComponent ]
+    })
+    .compileComponents();
+  });
+
+  beforeEach(() => {
+    fixture = TestBed.createComponent(PaginatedEntitiesComponent);
+    component = fixture.componentInstance;
+    fixture.detectChanges();
+  });
+
+  it('should create', () => {
+    expect(component).toBeTruthy();
   });
 });
diff --git a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/general/paginated-entities/paginated-entities.component.ts b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/general/paginated-entities/paginated-entities.component.ts
new file mode 100644
index 0000000..2e5123b
--- /dev/null
+++ b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/general/paginated-entities/paginated-entities.component.ts
@@ -0,0 +1,138 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import {Component, OnInit, Input, Output, EventEmitter} from '@angular/core';
+import {merge, Observable, Subject} from "rxjs";
+import {UserInfo} from "../../../model/user-info";
+import {TranslateService} from "@ngx-translate/core";
+import {debounceTime, distinctUntilChanged, map, mergeMap, pluck, share, startWith} from "rxjs/operators";
+import {EntityService} from "../../../model/entity-service";
+
+
+/**
+ * This component has a search field and pagination section. Entering data in the search field, or
+ * a button click on the pagination triggers a call to a service method, that returns the entity data.
+ * The service must implement the {@link EntityService} interface.
+ *
+ * The content is displayed between the search input and the pagination section. To use the data, you should
+ * add an identifier and refer to the item$ variable:
+ * ```
+ * <app-paginated-entities #parent>
+ *   <table>
+ *       <tr ngFor="let entity in parent.item$ | async" >
+ *           <td>{{entity.id}}</td>
+ *       </tr>
+ *   </table>
+ * </app-paginated-entities>
+ * ```
+ *
+ * @typeparam T The type of the retrieved entity elements.
+ */
+@Component({
+  selector: 'app-paginated-entities',
+  templateUrl: './paginated-entities.component.html',
+  styleUrls: ['./paginated-entities.component.scss']
+})
+export class PaginatedEntitiesComponent<T> implements OnInit {
+
+  /**
+   * This must be set, if you use the component. This service retrieves the entity data.
+   */
+  @Input() service : EntityService<T>;
+
+  /**
+   * The number of elements per page retrieved
+   */
+  @Input() pageSize = 10;
+
+  /**
+   * Pagination controls
+   */
+  @Input() pagination = {
+    maxSize:5,
+    rotate:true,
+    boundaryLinks:true,
+    ellipses:false
+  }
+
+  /**
+   * The current page that is selected
+   */
+  page = 1;
+  /**
+   * The current search term entered in the search field
+   */
+  searchTerm: string;
+
+  /**
+   * Event thrown, if the page value changes
+   */
+  @Output() pageEvent : EventEmitter<number> = new EventEmitter<number>();
+  /**
+   * Event thrown, if the search term changes
+   */
+  @Output() searchTermEvent: EventEmitter<string> = new EventEmitter<string>();
+
+  /**
+   * The total number of elements available for the given search term
+   */
+  total$: Observable<number>;
+  /**
+   * The entity items retrieved from the service
+   */
+  items$: Observable<T[]>;
+
+  private pageStream: Subject<number> = new Subject<number>();
+  private searchTermStream: Subject<string> = new Subject<string>();
+
+  constructor() { }
+
+  ngOnInit(): void {
+    // We combine the sources for the page and the search input field to a observable 'source'
+    const pageSource = this.pageStream.pipe(map(pageNumber => {
+      return {search: this.searchTerm, page: pageNumber}
+    }));
+    const searchSource = this.searchTermStream.pipe(
+        debounceTime(1000),
+        distinctUntilChanged(),
+        map(searchTerm => {
+          this.searchTerm = searchTerm;
+          return {search: searchTerm, page: 1}
+        }));
+    const source = merge(pageSource, searchSource).pipe(
+        startWith({search: this.searchTerm, page: this.page}),
+        mergeMap((params: { search: string, page: number }) => {
+          return this.service(params.search, (params.page - 1) * this.pageSize, this.pageSize, "", "asc");
+        }),share());
+    this.total$ = source.pipe(pluck('pagination','totalCount'));
+    this.items$ = source.pipe(pluck('data'));
+  }
+
+  search(terms: string) {
+    // console.log("Keystroke " + terms);
+    this.searchTermEvent.emit(terms);
+    this.searchTermStream.next(terms)
+  }
+
+  changePage(pageNumber : number) {
+    // console.log("Page change " +pageNumber);
+    this.pageEvent.emit(pageNumber);
+    this.pageStream.next(pageNumber);
+  }
+
+}
diff --git a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/user/users/manage-users-list/manage-users-list.component.html b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/user/users/manage-users-list/manage-users-list.component.html
index 98be856..56b02ae 100644
--- a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/user/users/manage-users-list/manage-users-list.component.html
+++ b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/user/users/manage-users-list/manage-users-list.component.html
@@ -17,35 +17,21 @@
   ~ under the License.
   -->
 
-<form class="mt-3 mb-3">
-    <div class="form-row align-items-center">
-        <div class="col-lg-4 col-md-2 col-sm-1">
-            <label class="sr-only" for="searchQuery">{{'users.list.search' |translate}}</label>
-            <input type="text" class="form-control" id="searchQuery" placeholder="Search" #searchTerm
-            (keyup)="search(searchTerm.value)">
-        </div>
-        <div class="col-auto">
-            <button type="submit" class="btn btn-primary">{{'search.button'|translate}}</button>
-        </div>
-    </div>
+<app-paginated-entities [service]="service" pageSize="5"We #parent>
 
-
-</form>
-
-<table class="table table-striped table-bordered" appEnableTooltip>
+<table class="table table-striped table-bordered">
     <thead class="thead-light">
     <tr>
-        <th scope="col">{{'users.list.table.head.id' | translate}}</th>
         <th scope="col">{{'users.list.table.head.user_id' | translate}}</th>
         <th scope="col">{{'users.list.table.head.fullName' | translate}}</th>
         <th scope="col">{{'users.list.table.head.email' | translate}}</th>
-        <th scope="col"><span class="fas fa-check" data-toggle="tooltip" data-placement="top"
-                              [attr.data-original-title]="heads.validated" [attr.aria-label]="heads.validated"></span>
+        <th scope="col"><span class="fas fa-check" placement="top"
+                              [ngbTooltip]="heads.validated" [attr.aria-label]="heads.validated"></span>
         </th>
-        <th scope="col"><span class="fas fa-lock" data-toggle="tooltip" data-placement="top"
-                              [attr.data-original-title]="heads.locked" [attr.aria-label]="heads.locked"></span></th>
-        <th scope="col"><span class="fa fa-chevron-circle-right" data-toggle="tooltip" data-placement="top"
-                              [attr.data-original-title]="heads.pwchange" [attr.aria-label]="heads.pwchange"></span>
+        <th scope="col"><span class="fas fa-lock" placement="top"
+                              [ngbTooltip]="heads.locked" [attr.aria-label]="heads.locked"></span></th>
+        <th scope="col"><span class="fa fa-chevron-circle-right" placement="top"
+                              [ngbTooltip]="heads.pwchange" [attr.aria-label]="heads.pwchange"></span>
         </th>
         <th scope="col">{{'users.list.table.head.lastLogin' | translate}}</th>
         <th scope="col">{{'users.list.table.head.created' | translate}}</th>
@@ -53,9 +39,8 @@
     </tr>
     </thead>
     <tbody>
-    <tr *ngFor="let user of items$ | async"  [ngClass]="user.permanent?'table-secondary':''">
-        <td>{{user.id}}</td>
-        <td>{{user.user_id}}</td>
+    <tr *ngFor="let user of parent.items$ | async"  [ngClass]="(user.permanent||user.readOnly)?'table-secondary':''" >
+        <td><span data-toggle="tooltip" placement="left" ngbTooltip="{{user.id}}">{{user.user_id}}</span></td>
         <td>{{user.fullName}}</td>
         <td>{{user.email}}</td>
         <td><span class="far" [attr.aria-valuetext]="user.validated" [ngClass]="user.validated?'fa-check-circle':'fa-circle'"></span></td>
@@ -68,4 +53,4 @@
     </tbody>
 </table>
 
-<ngb-pagination [collectionSize]="total$|async" maxSize="2" rotate="true" [(page)]="page" (pageChange)="changePage($event)" aria-label="Default pagination"></ngb-pagination>
+</app-paginated-entities>
diff --git a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/user/users/manage-users-list/manage-users-list.component.ts b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/user/users/manage-users-list/manage-users-list.component.ts
index d7629a9..8e91dd3 100644
--- a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/user/users/manage-users-list/manage-users-list.component.ts
+++ b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/user/users/manage-users-list/manage-users-list.component.ts
@@ -17,12 +17,13 @@
  * under the License.
  */
 
-import { Component, OnInit, Input } from '@angular/core';
+import {Component, OnInit, Input, OnDestroy} from '@angular/core';
 import {TranslateService} from "@ngx-translate/core";
 import {UserService} from "../../../../services/user.service";
-import {Observable, Subject, merge} from 'rxjs';
-import { map, pluck, debounceTime, distinctUntilChanged, startWith, mergeMap} from "rxjs/operators";
 import {UserInfo} from "../../../../model/user-info";
+import {EntityService} from "../../../../model/entity-service";
+import {Observable, of} from "rxjs";
+import {PagedResult} from "../../../../model/paged-result";
 
 
 @Component({
@@ -31,16 +32,17 @@ import {UserInfo} from "../../../../model/user-info";
   styleUrls: ['./manage-users-list.component.scss']
 })
 export class ManageUsersListComponent implements OnInit {
+
   @Input() heads: any;
-  page = 1;
-  pageSize = 10;
-  total$: Observable<number>;
-  items$: Observable<UserInfo[]>;
-  searchTerm: string;
-  private pageStream: Subject<number> = new Subject<number>();
-  private searchTermStream: Subject<string> = new Subject<string>();
+  service : EntityService<UserInfo>;
+
 
-  constructor(private translator: TranslateService, private userService : UserService) { }
+  constructor(private translator: TranslateService, private userService : UserService) {
+    this.service = function (searchTerm: string, offset: number, limit: number, orderBy: string, order: string) : Observable<PagedResult<UserInfo>> {
+      return userService.query(searchTerm, offset, limit, orderBy, order);
+    }
+
+  }
 
   ngOnInit(): void {
     this.heads = {};
@@ -51,42 +53,17 @@ export class ManageUsersListComponent implements OnInit {
         this.heads[suffix] = this.translator.instant('users.list.table.head.' + suffix);
       }
     });
-    const pageSource = this.pageStream.pipe(map(pageNumber => {
-      return {search: this.searchTerm, page: pageNumber}
-    }));
-    const searchSource = this.searchTermStream.pipe(
-        debounceTime(1000),
-        distinctUntilChanged(),
-        map(searchTerm => {
-          this.searchTerm = searchTerm;
-          console.log("Search term " + searchTerm);
-          return {search: searchTerm, page: 1}
-        }));
-    const source = merge(pageSource, searchSource).pipe(
-    startWith({search: this.searchTerm, page: this.page}),
-        mergeMap((params: { search: string, page: number }) => {
-          console.log("Executing user list " + params.search);
-          return this.userService.getUserList(params.search, params.page*this.pageSize, this.pageSize)
-        }));
-
-    this.total$ = source.pipe(pluck('pagination.total'));
-    this.items$ = source.pipe(pluck('data'));
-
-
-
-    // const pageSource = map(pageNumber => {
-    //   this.page = pageNumber
-    //   return {search: this.searchTerm, page: pageNumber}
-    // })
-  }
-  search(terms: string) {
-    console.log("Keystroke " + terms);
-    this.searchTermStream.next(terms)
-  }
 
-  changePage(pageNumber : number) {
-    console.log("Page change " +typeof(pageNumber) +":" + JSON.stringify(pageNumber));
-    this.pageStream.next(pageNumber);
+
+
   }
 
+
+
+
+
+
+
+
+
 }
diff --git a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/services/archiva-request.service.ts b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/services/archiva-request.service.ts
index 9d79733..c8028ff 100644
--- a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/services/archiva-request.service.ts
+++ b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/services/archiva-request.service.ts
@@ -54,7 +54,11 @@ export class ArchivaRequestService {
             headers = {};
         }
         if (type == "get") {
-            return this.http.get<R>(url, {"headers": headers});
+            let params = {}
+            if (input!=null) {
+                params = input;
+            }
+            return this.http.get<R>(url, {"headers": headers,"params":params});
         } else if (type == "post") {
             return this.http.post<R>(url, input, {"headers": headers});
         }
diff --git a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/services/user.service.ts b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/services/user.service.ts
index 21f9d1a..bdeeafa 100644
--- a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/services/user.service.ts
+++ b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/services/user.service.ts
@@ -24,6 +24,7 @@ import {ErrorResult} from "../model/error-result";
 import {Observable} from "rxjs";
 import {Permission} from '../model/permission';
 import {PagedResult} from "../model/paged-result";
+import {EntityService} from "../model/entity-service";
 
 @Injectable({
     providedIn: 'root'
@@ -202,7 +203,6 @@ export class UserService implements OnInit, OnDestroy {
                 }
             }
         }
-        console.log("New permissions: " + JSON.stringify(this.uiPermissions));
     }
 
     private deepCopy(src: Object, dst: Object) {
@@ -258,8 +258,12 @@ export class UserService implements OnInit, OnDestroy {
         this.authenticated = false;
     }
 
-    public getUserList(searchTerm : string, offset : number = 0, limit : number = 10) : Observable<PagedResult<UserInfo>>  {
-        return this.rest.executeRestCall<PagedResult<UserInfo>>("get", "redback", "users", {'offset':offset,'limit':limit});
+    public query(searchTerm : string, offset : number = 0, limit : number = 10, orderBy : string = 'user_id', order: string = 'asc') : Observable<PagedResult<UserInfo>>  {
+        console.log("getUserList " + searchTerm + "," + offset + "," + limit + "," + orderBy + "," + order);
+        if (searchTerm==null) {
+            searchTerm=""
+        }
+        return this.rest.executeRestCall<PagedResult<UserInfo>>("get", "redback", "users", {'q':searchTerm, 'offset':offset,'limit':limit});
     }
 
 }
diff --git a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/assets/i18n/en.json b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/assets/i18n/en.json
index 3e212d7..f54e4d5 100644
--- a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/assets/i18n/en.json
+++ b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/assets/i18n/en.json
@@ -72,6 +72,7 @@
     }
   },
   "search": {
-    "button": "Search"
+    "button": "Search",
+    "label": "Enter your search term"
   }
 }
\ No newline at end of file
diff --git a/archiva-modules/archiva-web/archiva-webapp/src/test/resources/generate-users.sh b/archiva-modules/archiva-web/archiva-webapp/src/test/resources/generate-users.sh
new file mode 100644
index 0000000..4f52aaf
--- /dev/null
+++ b/archiva-modules/archiva-web/archiva-webapp/src/test/resources/generate-users.sh
@@ -0,0 +1,46 @@
+#!/bin/bash
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+
+# Just a simple script to generate users on the local archiva instance for interactive UI testing
+
+BASE_URL="http://localhost:8080/archiva"
+USER_NAME="admin"
+PASSWD="admin456"
+USERS=25
+
+#Authenticate
+TOKEN=$(curl -s -X POST "${BASE_URL}/api/v2/redback/auth/authenticate" -H  "accept: application/json" -H  "Content-Type: application/json" \
+ -d "{\"grant_type\":\"authorization_code\",\"client_id\":\"test-bash\",\"client_secret\":\"string\",\"code\":\"string\",\"scope\":\"string\",\"state\":\"string\",\"user_id\":\"${USER_NAME}\",\
+ \"password\":\"${PASSWD}\",\"redirect_uri\":\"string\"}"|sed -n -e '/access_token/s/.*"access_token":"\([^"]\+\)".*/\1/gp')
+if [ "${TOKEN}" == "" ]; then
+    echo "Authentication failed!"
+    exit 1
+fi
+
+
+NUM=$USERS
+while [ $NUM -ge 0 ]; do
+  SUFFIX=$(printf "%03d" $NUM)
+  echo "User: test${SUFFIX}"
+  curl -s -w ' - %{http_code}' -X POST "${BASE_URL}/api/v2/redback/users" -H  "accept: application/json" \
+   -H  "Authorization: Bearer ${TOKEN}" \
+   -H  "Content-Type: application/json" \
+   -d "{\"user_id\":\"test${SUFFIX}\",\"fullName\":\"Test User ${SUFFIX}\",\"email\":\"test${SUFFIX}@test.org\",\"validated\":true,\"locked\":false,\"passwordChangeRequired\":false,\"password\":\"test123\"}"
+  NUM=$((NUM-1))
+  echo " "
+done
\ No newline at end of file