From 10d3d8c50f21774fbd0a85534986e79aa01cc7e5 Mon Sep 17 00:00:00 2001 From: Lukas Metzger Date: Tue, 10 Apr 2018 22:03:56 +0200 Subject: [PATCH] Implemented user editor --- .../src/app/apitypes/Permission.apitype.ts | 10 +++ frontend/src/app/app.module.ts | 4 +- .../src/app/operations/domains.operations.ts | 9 ++- .../src/app/operations/users.operations.ts | 31 ++++++++ .../app/pages/domains/domains.component.ts | 4 + .../pages/edit-user/edit-user.component.html | 30 ++++++- .../pages/edit-user/edit-user.component.ts | 44 ++++++++++- .../src/app/pages/users/users.component.ts | 5 ++ .../app/partials/search/search.component.html | 10 +++ .../app/partials/search/search.component.scss | 0 .../app/partials/search/search.component.ts | 79 +++++++++++++++++++ .../src/app/utils/search-service.interface.ts | 8 ++ 12 files changed, 230 insertions(+), 4 deletions(-) create mode 100644 frontend/src/app/apitypes/Permission.apitype.ts create mode 100644 frontend/src/app/partials/search/search.component.html create mode 100644 frontend/src/app/partials/search/search.component.scss create mode 100644 frontend/src/app/partials/search/search.component.ts create mode 100644 frontend/src/app/utils/search-service.interface.ts diff --git a/frontend/src/app/apitypes/Permission.apitype.ts b/frontend/src/app/apitypes/Permission.apitype.ts new file mode 100644 index 0000000..711856a --- /dev/null +++ b/frontend/src/app/apitypes/Permission.apitype.ts @@ -0,0 +1,10 @@ +export class PermissionApitype { + + public domainId = 0; + + public domainName = ''; + + constructor(init: Object) { + Object.assign(this, init); + } +} diff --git a/frontend/src/app/app.module.ts b/frontend/src/app/app.module.ts index 5039db6..3e95f45 100644 --- a/frontend/src/app/app.module.ts +++ b/frontend/src/app/app.module.ts @@ -1,3 +1,4 @@ +import { SearchComponent } from './partials/search/search.component'; import { CreateUserComponent } from './pages/create-user/create-user.component'; import { EditUserComponent } from './pages/edit-user/edit-user.component'; import { UsersOperation } from './operations/users.operations'; @@ -60,7 +61,8 @@ import { UsersComponent } from './pages/users/users.component'; CreateAuthComponent, UsersComponent, EditUserComponent, - CreateUserComponent + CreateUserComponent, + SearchComponent ], imports: [ BrowserModule, diff --git a/frontend/src/app/operations/domains.operations.ts b/frontend/src/app/operations/domains.operations.ts index 9e830c2..7c5b9f1 100644 --- a/frontend/src/app/operations/domains.operations.ts +++ b/frontend/src/app/operations/domains.operations.ts @@ -1,3 +1,4 @@ +import { SearchService, SearchServiceResult } from './../utils/search-service.interface'; import { DomainApitype } from './../apitypes/Domain.apitype'; import { ListApitype } from './../apitypes/List.apitype'; import { Injectable } from '@angular/core'; @@ -6,10 +7,16 @@ import { StateService } from '../services/state.service'; import { SessionApitype } from '../apitypes/Session.apitype'; @Injectable() -export class DomainsOperation { +export class DomainsOperation implements SearchService { constructor(private http: HttpService, private gs: StateService) { } + public async search(query: string): Promise { + const result = await this.getList(1, 10, query, null); + + return result.results.map((v: DomainApitype) => ({ id: v.id, text: v.name })); + } + public async getList(page?: number, pageSize?: number, query?: string, sort?: Array | string, type?: string): Promise> { try { diff --git a/frontend/src/app/operations/users.operations.ts b/frontend/src/app/operations/users.operations.ts index a265c72..c598f12 100644 --- a/frontend/src/app/operations/users.operations.ts +++ b/frontend/src/app/operations/users.operations.ts @@ -1,3 +1,4 @@ +import { PermissionApitype } from './../apitypes/Permission.apitype'; import { UserApitype } from './../apitypes/User.apitype'; import { ListApitype } from './../apitypes/List.apitype'; import { Injectable } from '@angular/core'; @@ -89,4 +90,34 @@ export class UsersOperation { } } } + + public async getPermissions(page: number, pagesize: number, userId: number): Promise> { + try { + return new ListApitype(await this.http.get(['/users', userId.toString(), 'permissions'], { + page: page, + pagesize: pagesize + })); + } catch (e) { + console.error(e); + return new ListApitype({ paging: {}, results: [] }); + } + } + + public async removePermission(userId: number, domainId: number): Promise { + try { + await this.http.delete(['/users', userId.toString(), 'permissions', domainId.toString()]); + } catch (e) { + console.error(e); + return; + } + } + + public async addPermission(userId: number, domainId: number): Promise { + try { + await this.http.post(['/users', userId.toString(), 'permissions'], { domainId: domainId }); + } catch (e) { + console.error(e); + return; + } + } } diff --git a/frontend/src/app/pages/domains/domains.component.ts b/frontend/src/app/pages/domains/domains.component.ts index 2f6d185..1708e40 100644 --- a/frontend/src/app/pages/domains/domains.component.ts +++ b/frontend/src/app/pages/domains/domains.component.ts @@ -51,6 +51,10 @@ export class DomainsComponent implements OnInit { this.pagingInfo = res.paging; this.domainList = res.results; + if (res.paging.total < this.pageRequested && res.paging.total > 1) { + this.pageRequested = Math.max(1, res.paging.total); + await this.loadData(); + } } public async onDeleteDomain(domain: DomainApitype) { diff --git a/frontend/src/app/pages/edit-user/edit-user.component.html b/frontend/src/app/pages/edit-user/edit-user.component.html index d840264..2d3fd01 100644 --- a/frontend/src/app/pages/edit-user/edit-user.component.html +++ b/frontend/src/app/pages/edit-user/edit-user.component.html @@ -1,5 +1,5 @@
-
+

Edit user {{ username }}

@@ -35,4 +35,32 @@
+
+
+
+

Add permission for domain

+ +
+
+ +
+
+

Manage existing permissions

+ + + + + + + + +
{{ permission.domainName }} + +
+

There are currently no permission for this user!

+ +
+
+ +
\ No newline at end of file diff --git a/frontend/src/app/pages/edit-user/edit-user.component.ts b/frontend/src/app/pages/edit-user/edit-user.component.ts index 398d9b2..901881e 100644 --- a/frontend/src/app/pages/edit-user/edit-user.component.ts +++ b/frontend/src/app/pages/edit-user/edit-user.component.ts @@ -1,3 +1,7 @@ +import { PagingApitype } from './../../apitypes/Paging.apitype'; +import { StateService } from './../../services/state.service'; +import { PermissionApitype } from './../../apitypes/Permission.apitype'; +import { DomainsOperation } from './../../operations/domains.operations'; import { UsersOperation } from './../../operations/users.operations'; import { ModalService } from './../../services/modal.service'; import { ActivatedRoute, ParamMap, Router } from '@angular/router'; @@ -15,12 +19,17 @@ export class EditUserComponent implements OnInit { public userForm: FormGroup; + public permissions: PermissionApitype[]; + + public pageRequested = 1; + public pagingInfo = new PagingApitype({}); + public isNative = false; public username = ''; public userId = 0; constructor(private fb: FormBuilder, private route: ActivatedRoute, private users: UsersOperation, - private router: Router, private modal: ModalService) { } + private router: Router, private modal: ModalService, public domains: DomainsOperation, public gs: StateService) { } ngOnInit() { this.createForm(); @@ -31,6 +40,8 @@ export class EditUserComponent implements OnInit { private async initControl(params: ParamMap) { this.userId = +params.get('userId'); + this.loadPermissions(); + const user = await this.users.getSingle(this.userId); this.username = user.name; @@ -79,4 +90,35 @@ export class EditUserComponent implements OnInit { })); } } + + public async loadPermissions() { + const res = await this.users.getPermissions(this.pageRequested, this.gs.pageSize, this.userId); + this.permissions = res.results; + this.pagingInfo = res.paging; + if (res.paging.total < this.pageRequested && res.paging.total > 1) { + this.pageRequested = Math.max(1, res.paging.total); + await this.loadPermissions(); + } + } + + public async onRemovePermission(permission: PermissionApitype) { + await this.users.removePermission(this.userId, permission.domainId); + await this.loadPermissions(); + } + + public async addPermissionFor(domainId: number) { + await this.users.addPermission(this.userId, domainId); + await this.loadPermissions(); + } + + public async onPagesizeChange(pagesize: number) { + this.pageRequested = 1; + this.gs.pageSize = pagesize; + await this.loadPermissions(); + } + + public async onPageChange(page: number) { + this.pageRequested = page; + await this.loadPermissions(); + } } diff --git a/frontend/src/app/pages/users/users.component.ts b/frontend/src/app/pages/users/users.component.ts index 715bc91..e245b31 100644 --- a/frontend/src/app/pages/users/users.component.ts +++ b/frontend/src/app/pages/users/users.component.ts @@ -51,6 +51,11 @@ export class UsersComponent implements OnInit { this.pagingInfo = res.paging; this.userList = res.results; + + if (res.paging.total < this.pageRequested && res.paging.total > 1) { + this.pageRequested = Math.max(1, res.paging.total); + await this.loadData(); + } } public async onDeleteUser(user: UserApitype) { diff --git a/frontend/src/app/partials/search/search.component.html b/frontend/src/app/partials/search/search.component.html new file mode 100644 index 0000000..d621ab9 --- /dev/null +++ b/frontend/src/app/partials/search/search.component.html @@ -0,0 +1,10 @@ + \ No newline at end of file diff --git a/frontend/src/app/partials/search/search.component.scss b/frontend/src/app/partials/search/search.component.scss new file mode 100644 index 0000000..e69de29 diff --git a/frontend/src/app/partials/search/search.component.ts b/frontend/src/app/partials/search/search.component.ts new file mode 100644 index 0000000..15f74d2 --- /dev/null +++ b/frontend/src/app/partials/search/search.component.ts @@ -0,0 +1,79 @@ +import { FormControl } from '@angular/forms'; +import { SearchService, SearchServiceResult } from './../../utils/search-service.interface'; +import { Component, OnInit, Input, forwardRef, EventEmitter, Output } from '@angular/core'; + +@Component({ + selector: 'app-search', + templateUrl: './search.component.html', + styleUrls: ['./search.component.scss'] +}) +export class SearchComponent implements OnInit { + + @Input() searchService: SearchService; + @Output() resultClicked = new EventEmitter(); + + public searchResults: SearchServiceResult[] = []; + public searchComplete = false; + + public inputControl: FormControl; + + public open = false; + + constructor() { } + + ngOnInit() { + this.inputControl = new FormControl(''); + this.inputControl.valueChanges.debounceTime(500).subscribe(() => this.reloadResults()); + } + + public async reloadResults() { + if (this.inputControl.value.length === 0) { + this.searchResults = []; + this.searchComplete = false; + return; + } + + this.searchComplete = false; + this.searchResults = await this.searchService.search(this.inputControl.value); + this.searchComplete = true; + } + + public toggleOpen() { + this.open = !this.open; + } + + public onClick(itemId: number) { + this.inputControl.reset(''); + this.searchResults = []; + this.searchComplete = false; + this.open = false; + this.resultClicked.emit(itemId); + } + + public onEnter() { + if (this.searchResults.length > 0) { + const itemId = this.searchResults[0].id; + this.inputControl.reset(''); + this.searchResults = []; + this.searchComplete = false; + this.resultClicked.emit(itemId); + } + } + + public onBlur() { + setTimeout(() => this.open = false, 100); + } + + public hintText(): string | null { + if (this.inputControl.value.length === 0) { + return 'Type to search...'; + } else if (this.searchComplete && this.searchResults.length === 0) { + return 'No results found.'; + } else if (!this.searchComplete) { + return 'Searching...'; + } else { + return null; + } + } + +} diff --git a/frontend/src/app/utils/search-service.interface.ts b/frontend/src/app/utils/search-service.interface.ts new file mode 100644 index 0000000..a7c44e4 --- /dev/null +++ b/frontend/src/app/utils/search-service.interface.ts @@ -0,0 +1,8 @@ +export interface SearchService { + search(query: string): Promise; +} + +export interface SearchServiceResult { + id: number; + text: string; +}