Implemented user editor
This commit is contained in:
parent
ee72dd2620
commit
10d3d8c50f
10
frontend/src/app/apitypes/Permission.apitype.ts
Normal file
10
frontend/src/app/apitypes/Permission.apitype.ts
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
export class PermissionApitype {
|
||||||
|
|
||||||
|
public domainId = 0;
|
||||||
|
|
||||||
|
public domainName = '';
|
||||||
|
|
||||||
|
constructor(init: Object) {
|
||||||
|
Object.assign(this, init);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,3 +1,4 @@
|
||||||
|
import { SearchComponent } from './partials/search/search.component';
|
||||||
import { CreateUserComponent } from './pages/create-user/create-user.component';
|
import { CreateUserComponent } from './pages/create-user/create-user.component';
|
||||||
import { EditUserComponent } from './pages/edit-user/edit-user.component';
|
import { EditUserComponent } from './pages/edit-user/edit-user.component';
|
||||||
import { UsersOperation } from './operations/users.operations';
|
import { UsersOperation } from './operations/users.operations';
|
||||||
|
@ -60,7 +61,8 @@ import { UsersComponent } from './pages/users/users.component';
|
||||||
CreateAuthComponent,
|
CreateAuthComponent,
|
||||||
UsersComponent,
|
UsersComponent,
|
||||||
EditUserComponent,
|
EditUserComponent,
|
||||||
CreateUserComponent
|
CreateUserComponent,
|
||||||
|
SearchComponent
|
||||||
],
|
],
|
||||||
imports: [
|
imports: [
|
||||||
BrowserModule,
|
BrowserModule,
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import { SearchService, SearchServiceResult } from './../utils/search-service.interface';
|
||||||
import { DomainApitype } from './../apitypes/Domain.apitype';
|
import { DomainApitype } from './../apitypes/Domain.apitype';
|
||||||
import { ListApitype } from './../apitypes/List.apitype';
|
import { ListApitype } from './../apitypes/List.apitype';
|
||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
|
@ -6,10 +7,16 @@ import { StateService } from '../services/state.service';
|
||||||
import { SessionApitype } from '../apitypes/Session.apitype';
|
import { SessionApitype } from '../apitypes/Session.apitype';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class DomainsOperation {
|
export class DomainsOperation implements SearchService {
|
||||||
|
|
||||||
constructor(private http: HttpService, private gs: StateService) { }
|
constructor(private http: HttpService, private gs: StateService) { }
|
||||||
|
|
||||||
|
public async search(query: string): Promise<SearchServiceResult[]> {
|
||||||
|
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,
|
public async getList(page?: number, pageSize?: number, query?: string,
|
||||||
sort?: Array<String> | string, type?: string): Promise<ListApitype<DomainApitype>> {
|
sort?: Array<String> | string, type?: string): Promise<ListApitype<DomainApitype>> {
|
||||||
try {
|
try {
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import { PermissionApitype } from './../apitypes/Permission.apitype';
|
||||||
import { UserApitype } from './../apitypes/User.apitype';
|
import { UserApitype } from './../apitypes/User.apitype';
|
||||||
import { ListApitype } from './../apitypes/List.apitype';
|
import { ListApitype } from './../apitypes/List.apitype';
|
||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
|
@ -89,4 +90,34 @@ export class UsersOperation {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async getPermissions(page: number, pagesize: number, userId: number): Promise<ListApitype<PermissionApitype>> {
|
||||||
|
try {
|
||||||
|
return new ListApitype<PermissionApitype>(await this.http.get(['/users', userId.toString(), 'permissions'], {
|
||||||
|
page: page,
|
||||||
|
pagesize: pagesize
|
||||||
|
}));
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
return new ListApitype<PermissionApitype>({ paging: {}, results: [] });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async removePermission(userId: number, domainId: number): Promise<void> {
|
||||||
|
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<void> {
|
||||||
|
try {
|
||||||
|
await this.http.post(['/users', userId.toString(), 'permissions'], { domainId: domainId });
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -51,6 +51,10 @@ export class DomainsComponent implements OnInit {
|
||||||
|
|
||||||
this.pagingInfo = res.paging;
|
this.pagingInfo = res.paging;
|
||||||
this.domainList = res.results;
|
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) {
|
public async onDeleteDomain(domain: DomainApitype) {
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-12 col-md-6 col-lg-3">
|
<div class="col-12 col-md-4 col-lg-3">
|
||||||
<p class="font-weight-bold">Edit user {{ username }}</p>
|
<p class="font-weight-bold">Edit user {{ username }}</p>
|
||||||
|
|
||||||
<form autocomplete="off" [formGroup]="userForm" (ngSubmit)="onSubmit()">
|
<form autocomplete="off" [formGroup]="userForm" (ngSubmit)="onSubmit()">
|
||||||
|
@ -35,4 +35,32 @@
|
||||||
<button type="submit" class="btn btn-primary float-right" [disabled]="!userForm.valid || userForm.pristine">Save</button>
|
<button type="submit" class="btn btn-primary float-right" [disabled]="!userForm.valid || userForm.pristine">Save</button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="col-12 col-md-8 col-lg-8 offset-lg-1">
|
||||||
|
<div class="row no-gutters">
|
||||||
|
<div class="col-12 col-md-8 col-lg-4">
|
||||||
|
<p class="font-weight-bold">Add permission for domain</p>
|
||||||
|
<app-search (resultClicked)="addPermissionFor($event)" [searchService]="domains"></app-search>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row no-gutters mt-3">
|
||||||
|
<div class="col-12 col-lg-8">
|
||||||
|
<p class="font-weight-bold">Manage existing permissions</p>
|
||||||
|
<app-pagesize class="float-md-right" [pagesizes]="gs.pageSizes" [currentPagesize]="gs.pageSize" (pagesizeChange)="onPagesizeChange($event)"></app-pagesize>
|
||||||
|
<table class="table table-hover">
|
||||||
|
<tbody>
|
||||||
|
<tr *ngFor="let permission of permissions">
|
||||||
|
<td>{{ permission.domainName }}</td>
|
||||||
|
<td class="w-10">
|
||||||
|
<app-fa-icon class="cursor-pointer" icon="times" appStopPropagateClick (click)="onRemovePermission(permission)"></app-fa-icon>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<p *ngIf="permissions.length === 0" class="text-center">There are currently no permission for this user!</p>
|
||||||
|
<app-paging [pagingInfo]="pagingInfo" [pageWidth]="3" (pageChange)="onPageChange($event)"></app-paging>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
|
@ -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 { UsersOperation } from './../../operations/users.operations';
|
||||||
import { ModalService } from './../../services/modal.service';
|
import { ModalService } from './../../services/modal.service';
|
||||||
import { ActivatedRoute, ParamMap, Router } from '@angular/router';
|
import { ActivatedRoute, ParamMap, Router } from '@angular/router';
|
||||||
|
@ -15,12 +19,17 @@ export class EditUserComponent implements OnInit {
|
||||||
|
|
||||||
public userForm: FormGroup;
|
public userForm: FormGroup;
|
||||||
|
|
||||||
|
public permissions: PermissionApitype[];
|
||||||
|
|
||||||
|
public pageRequested = 1;
|
||||||
|
public pagingInfo = new PagingApitype({});
|
||||||
|
|
||||||
public isNative = false;
|
public isNative = false;
|
||||||
public username = '';
|
public username = '';
|
||||||
public userId = 0;
|
public userId = 0;
|
||||||
|
|
||||||
constructor(private fb: FormBuilder, private route: ActivatedRoute, private users: UsersOperation,
|
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() {
|
ngOnInit() {
|
||||||
this.createForm();
|
this.createForm();
|
||||||
|
@ -31,6 +40,8 @@ export class EditUserComponent implements OnInit {
|
||||||
private async initControl(params: ParamMap) {
|
private async initControl(params: ParamMap) {
|
||||||
this.userId = +params.get('userId');
|
this.userId = +params.get('userId');
|
||||||
|
|
||||||
|
this.loadPermissions();
|
||||||
|
|
||||||
const user = await this.users.getSingle(this.userId);
|
const user = await this.users.getSingle(this.userId);
|
||||||
|
|
||||||
this.username = user.name;
|
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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -51,6 +51,11 @@ export class UsersComponent implements OnInit {
|
||||||
|
|
||||||
this.pagingInfo = res.paging;
|
this.pagingInfo = res.paging;
|
||||||
this.userList = res.results;
|
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) {
|
public async onDeleteUser(user: UserApitype) {
|
||||||
|
|
10
frontend/src/app/partials/search/search.component.html
Normal file
10
frontend/src/app/partials/search/search.component.html
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
<div class="dropdown">
|
||||||
|
<input type="text" class="form-control no-shadow" (focus)="open = true" (blur)="onBlur()" [formControl]="inputControl"
|
||||||
|
(keyup.enter)="onEnter()">
|
||||||
|
<div class="dropdown-menu" [class.show]="open">
|
||||||
|
<span *ngIf="hintText()" class="dropdown-item font-italic">{{ hintText() }}
|
||||||
|
</span>
|
||||||
|
<span *ngFor="let result of searchResults; let i=index" class="dropdown-item cursor-pointer" [class.active]="i === 0"
|
||||||
|
(click)="onClick(result.id)">{{ result.text }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
79
frontend/src/app/partials/search/search.component.ts
Normal file
79
frontend/src/app/partials/search/search.component.ts
Normal file
|
@ -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<number>();
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
8
frontend/src/app/utils/search-service.interface.ts
Normal file
8
frontend/src/app/utils/search-service.interface.ts
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
export interface SearchService {
|
||||||
|
search(query: string): Promise<SearchServiceResult[]>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SearchServiceResult {
|
||||||
|
id: number;
|
||||||
|
text: string;
|
||||||
|
}
|
Loading…
Reference in a new issue