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/07 12:38:41 UTC

[archiva] 02/02: Improving user view in ng

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

commit 88b27de5e28c724b18d63d2ca342c1aaf1fea589
Author: Martin Stockhammer <ma...@apache.org>
AuthorDate: Sat Nov 7 13:38:32 2020 +0100

    Improving user view in ng
---
 .../src/main/archiva-web/package-lock.json         | 217 +++++++++++++++++++++
 .../src/main/archiva-web/package.json              |   4 +-
 .../src/main/archiva-web/src/app/app.module.ts     |   4 +-
 .../archiva-web/src/app/model/paged-result.spec.ts |   7 +
 .../main/archiva-web/src/app/model/paged-result.ts |   6 +
 .../src/app/model/pagination-info.spec.ts          |   7 +
 .../archiva-web/src/app/model/pagination-info.ts   |   5 +
 .../manage-users-list.component.html               |  21 +-
 .../manage-users-list.component.ts                 |  52 ++++-
 .../archiva-web/src/app/services/user.service.ts   |   5 +
 .../src/main/archiva-web/src/assets/i18n/en.json   |   5 +-
 .../src/main/archiva-web/src/polyfills.ts          |   3 +-
 12 files changed, 325 insertions(+), 11 deletions(-)

diff --git a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/package-lock.json b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/package-lock.json
index 5ca161b..e3aaebc 100644
--- a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/package-lock.json
+++ b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/package-lock.json
@@ -535,6 +535,215 @@
         "tslib": "^2.0.0"
       }
     },
+    "@angular/localize": {
+      "version": "10.2.0",
+      "resolved": "https://registry.npmjs.org/@angular/localize/-/localize-10.2.0.tgz",
+      "integrity": "sha512-hAtmjdPs8BLQfHPtYUSFFDSn1mv/OoMxDO2iXdnvPQ5HBVIHuHb9qrpY3twX8LutJf2O175cJR7OB66DljuBmA==",
+      "dev": true,
+      "requires": {
+        "@babel/core": "7.8.3",
+        "glob": "7.1.2",
+        "yargs": "15.3.0"
+      },
+      "dependencies": {
+        "@babel/core": {
+          "version": "7.8.3",
+          "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.8.3.tgz",
+          "integrity": "sha512-4XFkf8AwyrEG7Ziu3L2L0Cv+WyY47Tcsp70JFmpftbAA1K7YL/sgE9jh9HyNj08Y/U50ItUchpN0w6HxAoX1rA==",
+          "dev": true,
+          "requires": {
+            "@babel/code-frame": "^7.8.3",
+            "@babel/generator": "^7.8.3",
+            "@babel/helpers": "^7.8.3",
+            "@babel/parser": "^7.8.3",
+            "@babel/template": "^7.8.3",
+            "@babel/traverse": "^7.8.3",
+            "@babel/types": "^7.8.3",
+            "convert-source-map": "^1.7.0",
+            "debug": "^4.1.0",
+            "gensync": "^1.0.0-beta.1",
+            "json5": "^2.1.0",
+            "lodash": "^4.17.13",
+            "resolve": "^1.3.2",
+            "semver": "^5.4.1",
+            "source-map": "^0.5.0"
+          }
+        },
+        "ansi-regex": {
+          "version": "5.0.0",
+          "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz",
+          "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==",
+          "dev": true
+        },
+        "ansi-styles": {
+          "version": "4.3.0",
+          "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
+          "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+          "dev": true,
+          "requires": {
+            "color-convert": "^2.0.1"
+          }
+        },
+        "cliui": {
+          "version": "6.0.0",
+          "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz",
+          "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==",
+          "dev": true,
+          "requires": {
+            "string-width": "^4.2.0",
+            "strip-ansi": "^6.0.0",
+            "wrap-ansi": "^6.2.0"
+          }
+        },
+        "color-convert": {
+          "version": "2.0.1",
+          "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
+          "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+          "dev": true,
+          "requires": {
+            "color-name": "~1.1.4"
+          }
+        },
+        "color-name": {
+          "version": "1.1.4",
+          "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
+          "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
+          "dev": true
+        },
+        "emoji-regex": {
+          "version": "8.0.0",
+          "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
+          "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
+          "dev": true
+        },
+        "find-up": {
+          "version": "4.1.0",
+          "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz",
+          "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==",
+          "dev": true,
+          "requires": {
+            "locate-path": "^5.0.0",
+            "path-exists": "^4.0.0"
+          }
+        },
+        "glob": {
+          "version": "7.1.2",
+          "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz",
+          "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==",
+          "dev": true,
+          "requires": {
+            "fs.realpath": "^1.0.0",
+            "inflight": "^1.0.4",
+            "inherits": "2",
+            "minimatch": "^3.0.4",
+            "once": "^1.3.0",
+            "path-is-absolute": "^1.0.0"
+          }
+        },
+        "is-fullwidth-code-point": {
+          "version": "3.0.0",
+          "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
+          "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
+          "dev": true
+        },
+        "locate-path": {
+          "version": "5.0.0",
+          "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz",
+          "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==",
+          "dev": true,
+          "requires": {
+            "p-locate": "^4.1.0"
+          }
+        },
+        "p-locate": {
+          "version": "4.1.0",
+          "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz",
+          "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==",
+          "dev": true,
+          "requires": {
+            "p-limit": "^2.2.0"
+          }
+        },
+        "path-exists": {
+          "version": "4.0.0",
+          "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
+          "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==",
+          "dev": true
+        },
+        "semver": {
+          "version": "5.7.1",
+          "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
+          "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==",
+          "dev": true
+        },
+        "source-map": {
+          "version": "0.5.7",
+          "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
+          "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=",
+          "dev": true
+        },
+        "string-width": {
+          "version": "4.2.0",
+          "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz",
+          "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==",
+          "dev": true,
+          "requires": {
+            "emoji-regex": "^8.0.0",
+            "is-fullwidth-code-point": "^3.0.0",
+            "strip-ansi": "^6.0.0"
+          }
+        },
+        "strip-ansi": {
+          "version": "6.0.0",
+          "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz",
+          "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==",
+          "dev": true,
+          "requires": {
+            "ansi-regex": "^5.0.0"
+          }
+        },
+        "wrap-ansi": {
+          "version": "6.2.0",
+          "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz",
+          "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==",
+          "dev": true,
+          "requires": {
+            "ansi-styles": "^4.0.0",
+            "string-width": "^4.1.0",
+            "strip-ansi": "^6.0.0"
+          }
+        },
+        "yargs": {
+          "version": "15.3.0",
+          "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.3.0.tgz",
+          "integrity": "sha512-g/QCnmjgOl1YJjGsnUg2SatC7NUYEiLXJqxNOQU9qSpjzGtGXda9b+OKccr1kLTy8BN9yqEyqfq5lxlwdc13TA==",
+          "dev": true,
+          "requires": {
+            "cliui": "^6.0.0",
+            "decamelize": "^1.2.0",
+            "find-up": "^4.1.0",
+            "get-caller-file": "^2.0.1",
+            "require-directory": "^2.1.1",
+            "require-main-filename": "^2.0.0",
+            "set-blocking": "^2.0.0",
+            "string-width": "^4.2.0",
+            "which-module": "^2.0.0",
+            "y18n": "^4.0.0",
+            "yargs-parser": "^18.1.0"
+          }
+        },
+        "yargs-parser": {
+          "version": "18.1.3",
+          "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz",
+          "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==",
+          "dev": true,
+          "requires": {
+            "camelcase": "^5.0.0",
+            "decamelize": "^1.2.0"
+          }
+        }
+      }
+    },
     "@angular/platform-browser": {
       "version": "10.2.0",
       "resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-10.2.0.tgz",
@@ -1733,6 +1942,14 @@
         "schema-utils": "^2.7.0"
       }
     },
+    "@ng-bootstrap/ng-bootstrap": {
+      "version": "8.0.0",
+      "resolved": "https://registry.npmjs.org/@ng-bootstrap/ng-bootstrap/-/ng-bootstrap-8.0.0.tgz",
+      "integrity": "sha512-v77Gfd8xHH+exq0WqIqVRlxbUEHdA/2+RUJenUP2IDTQN9E1rWl7O461/kosr+0XPuxPArHQJxhh/WsCYckcNg==",
+      "requires": {
+        "tslib": "^2.0.0"
+      }
+    },
     "@ngtools/webpack": {
       "version": "10.2.0",
       "resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-10.2.0.tgz",
diff --git a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/package.json b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/package.json
index 060bd99..113e3a1 100644
--- a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/package.json
+++ b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/package.json
@@ -21,6 +21,7 @@
     "@angular/router": "~10.2.0",
     "@fortawesome/fontawesome-free": "^5.13.1",
     "@fortawesome/fontawesome-svg-core": "^1.2.29",
+    "@ng-bootstrap/ng-bootstrap": "^8.0.0",
     "@ngx-translate/core": "^13.0.0",
     "@ngx-translate/http-loader": "^6.0.0",
     "bootstrap": "^4.5.0",
@@ -36,9 +37,10 @@
     "@angular-devkit/build-angular": "~0.1002.0",
     "@angular/cli": "~10.2.0",
     "@angular/compiler-cli": "~10.2.0",
-    "@types/node": "^12.11.1",
+    "@angular/localize": "^10.2.0",
     "@types/jasmine": "~3.5.0",
     "@types/jasminewd2": "~2.0.3",
+    "@types/node": "^12.11.1",
     "codelyzer": "^6.0.0-next.1",
     "jasmine-core": "~3.5.0",
     "jasmine-spec-reporter": "~5.0.0",
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 1fe0382..3535437 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
@@ -42,6 +42,7 @@ import { SecurityConfigurationComponent } from './modules/user/security-configur
 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";
 
 
 @NgModule({
@@ -79,7 +80,8 @@ import { EnableTooltipDirective } from './directives/enable-tooltip.directive';
         useFactory: httpTranslateLoader,
         deps: [HttpClient]
       }
-    })
+    }),
+      NgbPaginationModule
   ],
   providers: [],
   bootstrap: [AppComponent]
diff --git a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/model/paged-result.spec.ts b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/model/paged-result.spec.ts
new file mode 100644
index 0000000..c3ca58a
--- /dev/null
+++ b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/model/paged-result.spec.ts
@@ -0,0 +1,7 @@
+import { PagedResult } from './paged-result';
+
+describe('PagedResult', () => {
+  it('should create an instance', () => {
+    expect(new PagedResult()).toBeTruthy();
+  });
+});
diff --git a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/model/paged-result.ts b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/model/paged-result.ts
new file mode 100644
index 0000000..08c3caa
--- /dev/null
+++ b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/model/paged-result.ts
@@ -0,0 +1,6 @@
+import {PaginationInfo} from "./pagination-info";
+
+export class PagedResult<T> {
+    pagination : PaginationInfo;
+    data : Array<T>;
+}
diff --git a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/model/pagination-info.spec.ts b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/model/pagination-info.spec.ts
new file mode 100644
index 0000000..64d8e03
--- /dev/null
+++ b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/model/pagination-info.spec.ts
@@ -0,0 +1,7 @@
+import { PaginationInfo } from './pagination-info';
+
+describe('PaginationInfo', () => {
+  it('should create an instance', () => {
+    expect(new PaginationInfo()).toBeTruthy();
+  });
+});
diff --git a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/model/pagination-info.ts b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/model/pagination-info.ts
new file mode 100644
index 0000000..d67070f
--- /dev/null
+++ b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/model/pagination-info.ts
@@ -0,0 +1,5 @@
+export class PaginationInfo {
+    totalCount : number;
+    offset: number;
+    limit: number;
+}
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 2f36763..98be856 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
@@ -21,8 +21,8 @@
     <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" data-toggle="tooltip"
-                   data-placement="top" title="SEARCHHH">
+            <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>
@@ -32,7 +32,7 @@
 
 </form>
 
-<table class="table" appEnableTooltip>
+<table class="table table-striped table-bordered" appEnableTooltip>
     <thead class="thead-light">
     <tr>
         <th scope="col">{{'users.list.table.head.id' | translate}}</th>
@@ -49,8 +49,23 @@
         </th>
         <th scope="col">{{'users.list.table.head.lastLogin' | translate}}</th>
         <th scope="col">{{'users.list.table.head.created' | translate}}</th>
+        <th scope="col">{{'users.list.table.head.lastPwChange' | translate}}</th>
     </tr>
     </thead>
     <tbody>
+    <tr *ngFor="let user of items$ | async"  [ngClass]="user.permanent?'table-secondary':''">
+        <td>{{user.id}}</td>
+        <td>{{user.user_id}}</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>
+        <td><span class="far" [attr.aria-valuetext]="user.locked" [ngClass]="user.locked?'fa-check-circle':'fa-circle'"></span></td>
+        <td><span class="far" [attr.aria-valuetext]="user.passwordChangeRequired" [ngClass]="user.passwordChangeRequired?'fa-check-circle':'fa-circle'"></span></td>
+        <td>{{user.timestampLastLogin | date:'yyyy-MM-ddTHH:mm:ss'}}</td>
+        <td>{{user.timestampAccountCreation | date : 'yyyy-MM-ddTHH:mm:ss'}}</td>
+        <td>{{user.timestampLastPasswordChange| date : 'yyyy-MM-ddTHH:mm:ss'}}</td>
+    </tr>
     </tbody>
 </table>
+
+<ngb-pagination [collectionSize]="total$|async" maxSize="2" rotate="true" [(page)]="page" (pageChange)="changePage($event)" aria-label="Default pagination"></ngb-pagination>
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 22625a7..d7629a9 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
@@ -19,8 +19,11 @@
 
 import { Component, OnInit, Input } from '@angular/core';
 import {TranslateService} from "@ngx-translate/core";
-import {AppComponent} from "../../../../app.component";
 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";
+
 
 @Component({
   selector: 'app-manage-users-list',
@@ -29,18 +32,61 @@ import {UserService} from "../../../../services/user.service";
 })
 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>();
 
   constructor(private translator: TranslateService, private userService : UserService) { }
 
   ngOnInit(): void {
-    this.heads={};
+    this.heads = {};
     // We need to wait for the translator initialization and use the init key as step in.
-    this.translator.get('init').subscribe( () => {
+    this.translator.get('init').subscribe(() => {
       // Only table headings for small columns that use icons
       for (let suffix of ['validated', 'locked', 'pwchange']) {
         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/user.service.ts b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/services/user.service.ts
index a1cc652..21f9d1a 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
@@ -23,6 +23,7 @@ import {HttpErrorResponse} from "@angular/common/http";
 import {ErrorResult} from "../model/error-result";
 import {Observable} from "rxjs";
 import {Permission} from '../model/permission';
+import {PagedResult} from "../model/paged-result";
 
 @Injectable({
     providedIn: 'root'
@@ -257,4 +258,8 @@ 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});
+    }
+
 }
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 d43bd2a..3e212d7 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
@@ -55,14 +55,15 @@
       "table":{
         "head": {
           "id": "ID",
-          "user_id": "User Identifier",
+          "user_id": "Login Name",
           "email": "Email",
           "fullName": "Name",
           "validated": "User Validated",
           "locked": "User Locked",
           "pwchange": "Password Change Required",
           "lastLogin": "Last Login",
-          "created": "Created"
+          "created": "Created",
+          "lastPwChange": "Last Password Change"
         }
       }
     },
diff --git a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/polyfills.ts b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/polyfills.ts
index 488506e..b704a5e 100644
--- a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/polyfills.ts
+++ b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/polyfills.ts
@@ -19,7 +19,8 @@
 /***************************************************************************************************
  * Load `$localize` onto the global scope - used if i18n tags appear in Angular templates.
  */
-// import '@angular/localize/init';
+// We don't use it, but ng-bootstrap needs it.
+import '@angular/localize/init';
 /**
  * This file includes polyfills needed by Angular and is loaded before the app.
  * You can add your own extra polyfills to this file.