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 5ca161b53..e3aaebcc2 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 060bd9903..113e3a1e2 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 1fe0382d4..35354372e 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 000000000..c3ca58ad7 --- /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 000000000..08c3caa22 --- /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 { + pagination : PaginationInfo; + data : Array; +} 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 000000000..64d8e03ef --- /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 000000000..d67070f03 --- /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 2f367636a..98be856a3 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 @@
- +
@@ -32,7 +32,7 @@ - +
@@ -49,8 +49,23 @@ + + + + + + + + + + + + +
{{'users.list.table.head.id' | translate}} {{'users.list.table.head.lastLogin' | translate}} {{'users.list.table.head.created' | translate}}{{'users.list.table.head.lastPwChange' | translate}}
{{user.id}}{{user.user_id}}{{user.fullName}}{{user.email}}{{user.timestampLastLogin | date:'yyyy-MM-ddTHH:mm:ss'}}{{user.timestampAccountCreation | date : 'yyyy-MM-ddTHH:mm:ss'}}{{user.timestampLastPasswordChange| date : 'yyyy-MM-ddTHH:mm:ss'}}
+ + 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 22625a7b1..d7629a920 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; + items$: Observable; + searchTerm: string; + private pageStream: Subject = new Subject(); + private searchTermStream: Subject = new Subject(); 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 a1cc6526b..21f9d1a83 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> { + return this.rest.executeRestCall>("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 d43bd2a92..3e212d755 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 488506e12..b704a5e0d 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.