Improving user view in ng

This commit is contained in:
Martin Stockhammer 2020-11-07 13:38:32 +01:00
parent d89b03d058
commit 88b27de5e2
12 changed files with 325 additions and 11 deletions

View File

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

View File

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

View File

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

View File

@ -0,0 +1,7 @@
import { PagedResult } from './paged-result';
describe('PagedResult', () => {
it('should create an instance', () => {
expect(new PagedResult()).toBeTruthy();
});
});

View File

@ -0,0 +1,6 @@
import {PaginationInfo} from "./pagination-info";
export class PagedResult<T> {
pagination : PaginationInfo;
data : Array<T>;
}

View File

@ -0,0 +1,7 @@
import { PaginationInfo } from './pagination-info';
describe('PaginationInfo', () => {
it('should create an instance', () => {
expect(new PaginationInfo()).toBeTruthy();
});
});

View File

@ -0,0 +1,5 @@
export class PaginationInfo {
totalCount : number;
offset: number;
limit: number;
}

View File

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

View File

@ -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,6 +32,13 @@ 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) { }
@ -41,6 +51,42 @@ 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);
}
}

View File

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

View File

@ -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"
}
}
},

View File

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