Code development platform for open source projects from the European Union institutions

Skip to content
Snippets Groups Projects
Commit d3018480 authored by Sebastian-Ion TINCU's avatar Sebastian-Ion TINCU
Browse files

EDELIVERY-3687 SMP UI Add/Edit user

Add code to support adding, deleting and editing users in the front-end.
Rename username property to userName.
Add roles and a role service.
parent 86b51665
No related branches found
No related tags found
No related merge requests found
Showing
with 491 additions and 168 deletions
import { SmpAngular2WebPage } from './app.po';
describe('domibus-MSH-web App', function() {
describe('smp-MSH-web App', function() {
let page: SmpAngular2WebPage;
beforeEach(() => {
......
......@@ -69,6 +69,8 @@ import {DomainDetailsDialogComponent} from "./domain/domain-details-dialog/domai
import {UserDetailsDialogComponent} from "./user/user-details-dialog/user-details-dialog.component";
import {DownloadService} from "./download/download.service";
import {TrustStoreService} from "./trust-store/trust-store.service";
import {UserService} from "./user/user.service";
import {RoleService} from "./security/role.service";
export function extendedHttpClientFactory(xhrBackend: XHRBackend, requestOptions: RequestOptions, httpEventService: HttpEventService) {
return new ExtendedHttpClient(xhrBackend, requestOptions, httpEventService);
......@@ -158,6 +160,8 @@ export function extendedHttpClientFactory(xhrBackend: XHRBackend, requestOptions
AlertService,
DownloadService,
TrustStoreService,
UserService,
RoleService,
{
provide: Http,
useFactory: extendedHttpClientFactory,
......
import {MdDialogConfig, MdDialogRef} from "@angular/material";
import {SearchTableEntity} from "./search-table-entity.model";
export interface SearchTableController {
showDetails(row);
edit(row);
delete(row);
newRow(): SearchTableEntity;
newDialog(config?: MdDialogConfig): MdDialogRef<any>;
}
export enum SearchTableEntityStatus {
PERSISTED,
UPDATED,
NEW,
REMOVED
}
import {SearchTableEntityStatus} from "./search-table-entity-status.model";
export interface SearchTableEntity {
status: SearchTableEntityStatus;
deleted?: boolean;
}
import {SearchTableEntity} from "./search-table-entity.model";
export interface SearchTableResult {
serviceEntities: Array<any>;
serviceEntities: Array<SearchTableEntity>;
pageSize: number;
count: number;
filter: any;
......
......@@ -12,7 +12,16 @@
position: fixed;
}
.datatable-body{
.datatable-body {
overflow-y: scroll;
}
/deep/ .deleted span[title] {
text-decoration: line-through !important;
}
.group-action-button {
position: absolute;
left: 8px;
bottom: 8px;
}
......@@ -3,9 +3,7 @@
<div class="selectionCriteria">
<md-card>
<md-card-content>
<div class="panel">
<form name="filterForm" #filterForm="ngForm" (ngSubmit)="search()">
<ng-container *ngTemplateOutlet="searchPanel"></ng-container>
......@@ -24,22 +22,22 @@
<div class="panel" style="position: absolute; top: 270px; bottom: 5px; left: 5px; right: 5px;">
<div class="group-filter-button">
<span class="row-button">
<app-row-limiter [pageSizes]="rowLimiter.pageSizes"
(onPageSizeChanged)="changePageSize($event.value)"></app-row-limiter>
</span>
<span class="row-button">
<app-row-limiter [pageSizes]="rowLimiter.pageSizes"
(onPageSizeChanged)="changePageSize($event.value)"></app-row-limiter>
</span>
<span class="column-filter-button">
<app-column-picker [allColumns]="columnPicker.allColumns" [selectedColumns]="columnPicker.selectedColumns"
(onSelectedColumnsChanged)="columnPicker.changeSelectedColumns($event)"></app-column-picker>
</span>
<app-column-picker [allColumns]="columnPicker.allColumns" [selectedColumns]="columnPicker.selectedColumns"
(onSelectedColumnsChanged)="columnPicker.changeSelectedColumns($event)"></app-column-picker>
</span>
</div>
<!-- temporal solution <div - absolut - wrapping> for stretch table height to fit screen size: scrollbarV does not work - virtual scrolling has
row bugs.-->
<div class="panel" style="position: absolute; overflow-y: scroll;top: 100px; bottom: 40px; left: 0px; right: 0px;">
<div class="panel">
<ngx-datatable
id="serviceGroupTable"
id="searchTable"
class="material striped"
style=""
[rowClass]="getRowClass"
[rows]="rows"
[columns]="columnPicker.selectedColumns"
[columnMode]="'force'"
......@@ -50,62 +48,55 @@
[externalPaging]="true"
[externalSorting]="true"
[loadingIndicator]="loading"
[count]="count"
[count]="rows.length"
[offset]="offset"
[limit]="rowLimiter.pageSize"
[sorts]="[{prop: 'received', dir: 'desc'}]"
(page)='onPage($event)'
(page)="onPage($event)"
(sort)="onSort($event)"
[selected]="selected"
[selectionType]="'multi'"
(activate)="onActivate($event)"
(select)="onSelect($event)">
</ngx-datatable>
</div>
<ng-template #rowActions let-row="row" let-value="value" ngx-datatable-cell-template>
<button md-icon-button color="primary"
(click)="editRowButtonAction(row)" id="editButtonRow{{row.$$index}}_id" tooltip="Edit">
<md-icon>edit</md-icon>
</button>
<button md-icon-button color="primary" (click)="deleteRowButtonAction(row)"
id="deleteButtonRow{{row.$$index}}_id" tooltip="Delete">
<md-icon>delete</md-icon>
</button>
</ng-template>
<div class="group-action-button" style="position: absolute;left: 8px; bottom: 8px;">
<button md-raised-button color="primary" (click)="newButtonAction()"
id="add_id">
<md-icon>add</md-icon>
<span>New</span>
</button>
<button md-raised-button color="primary" [disabled]="!isRowSelected()" (click)="editButtonAction()"
id="edit_id">
<md-icon>edit</md-icon>
<span>Edit</span>
</button>
<button md-raised-button color="primary" [disabled]="!isRowSelected()" (click)="deleteButtonAction()"
id="resendbutton_id">
<md-icon>delete</md-icon>
<span>Delete</span>
</button>
<ng-container *ngTemplateOutlet="additionalToolButtons"></ng-container>
<div class="group-action-button">
<button id="cancelButton" md-raised-button (click)="onCancelButtonClicked()" color="primary" [disabled]="!submitButtonsEnabled">
<md-icon>cancel</md-icon>
<span>Cancel</span>
</button>
<button id="saveButton" md-raised-button (click)="onSaveButtonClicked(false)" color="primary" [disabled]="!submitButtonsEnabled">
<md-icon>save</md-icon>
<span>Save</span>
</button>
<button id="newButton" md-raised-button (click)="onNewButtonClicked()" [disabled]="loading" color="primary">
<md-icon>add</md-icon>
<span>New</span>
</button>
<button id="editButton" md-raised-button (click)="onEditButtonClicked()" [disabled]="!editButtonEnabled || loading" color="primary">
<md-icon>edit</md-icon>
<span>Edit</span>
</button>
<button id="deleteButton" md-raised-button (click)="onDeleteButtonClicked()" [disabled]="!deleteButtonEnabled || loading" color="primary">
<md-icon>delete</md-icon>
<span>Delete</span>
</button>
<ng-container *ngTemplateOutlet="additionalToolButtons"></ng-container>
</div>
<ng-template #rowActions let-row="row" ngx-datatable-cell-template>
<div>
<button md-icon-button color="primary" [disabled]="row.deleted || loading"
(click)="onEditRowActionClicked(row.$$index)" tooltip="Edit">
<md-icon>edit</md-icon>
</button>
<button md-icon-button color="primary" [disabled]="row.deleted || loading"
(click)="onDeleteRowActionClicked(row)" tooltip="Delete">
<md-icon>delete</md-icon>
</button>
</div>
</ng-template>
</div>
</div>
</div>
<!--
-->
import {Component, EventEmitter, Input, OnInit, TemplateRef, ViewChild} from "@angular/core";
import {Http, URLSearchParams, Response} from "@angular/http";
import {ChangeDetectionStrategy, ChangeDetectorRef, Component, Input, OnInit, TemplateRef, ViewChild} from "@angular/core";
import {Http, Response, URLSearchParams} from "@angular/http";
import {SearchTableResult} from "./search-table-result.model";
import {Observable} from "rxjs";
import {AlertService} from "../../alert/alert.service";
......@@ -8,13 +8,18 @@ import {ColumnPicker} from "../column-picker/column-picker.model";
import {RowLimiter} from "../row-limiter/row-limiter.model";
import {AlertComponent} from "../../alert/alert.component";
import {SearchTableController} from "./search-table-controller";
import {finalize, map} from "rxjs/operators";
import {SearchTableEntity} from "./search-table-entity.model";
import {SearchTableEntityStatus} from "./search-table-entity-status.model";
import {CancelDialogComponent} from "../cancel-dialog/cancel-dialog.component";
import {SaveDialogComponent} from "../save-dialog/save-dialog.component";
import {DownloadService} from "../../download/download.service";
@Component({
selector: 'smp-search-table',
templateUrl: './search-table.component.html',
styleUrls: ['./search-table.component.css']
})
export class SearchTableComponent implements OnInit {
@ViewChild('rowActions') rowActions: TemplateRef<any>;
......@@ -28,26 +33,26 @@ export class SearchTableComponent implements OnInit {
@Input() searchTableController: SearchTableController;
@Input() filter: any = {};
columnActions:any;
loading = false;
columnActions: any;
rowLimiter: RowLimiter = new RowLimiter();
selected = [];
rowNumber: number;
rows: Array<SearchTableEntity> = [];
selected: Array<SearchTableEntity> = [];
loading: boolean = false;
rows = [];
count: number = 0;
offset: number = 0;
//default value
orderBy: string = null;
//default value
asc: boolean = false;
asc = false;
msgStatus: Array<String>;
messageResent = new EventEmitter(false);
constructor(protected http: Http, protected alertService: AlertService, public dialog: MdDialog) {
constructor(protected http: Http,
protected alertService: AlertService,
private downloadService: DownloadService,
public dialog: MdDialog) {
}
ngOnInit() {
......@@ -57,24 +62,30 @@ export class SearchTableComponent implements OnInit {
width: 80,
sortable: false
};
/**
* Add actions to last column
*/
// Add actions to last column
if (this.columnPicker) {
this.columnPicker.allColumns.push(this.columnActions);
this.columnPicker.selectedColumns.push(this.columnActions);
}
this.page(this.offset, this.rowLimiter.pageSize, this.orderBy, this.asc);
}
getTableDataEntries(offset: number, pageSize: number, orderBy: string, asc: boolean): Observable< SearchTableResult > {
getRowClass(row): string {
return row.deleted ? 'deleted' : '';
}
getTableDataEntries$(offset: number, pageSize: number, orderBy: string, asc: boolean): Observable<SearchTableResult> {
let searchParams: URLSearchParams = new URLSearchParams();
searchParams.set('page', offset.toString());
searchParams.set('pageSize', pageSize.toString());
searchParams.set('orderBy', orderBy);
//filters
if (this.filter.userName) {
searchParams.set('userName', this.filter.userName);
}
if (this.filter.participantId) {
searchParams.set('participantId', this.filter.participantId);
}
......@@ -83,7 +94,6 @@ export class SearchTableComponent implements OnInit {
searchParams.set('participantSchema', this.filter.participantSchema);
}
if(this.filter.domain) {
searchParams.set('domain', this.filter.domain )
}
......@@ -92,114 +102,212 @@ export class SearchTableComponent implements OnInit {
searchParams.set('asc', asc.toString());
}
// TODO move to the HTTP service
this.loading = true;
return this.http.get(this.url, {
search: searchParams
}).map((response: Response) =>
response.json()
}).pipe(
map((response: Response) => response.json()),
finalize(() => {
this.loading = false;
})
);
}
page(offset, pageSize, orderBy, asc) {
this.loading = true;
this.getTableDataEntries(offset, pageSize, orderBy, asc).subscribe((result: SearchTableResult ) => {
console.log("service group response:" + result);
page(offset: number, pageSize: number, orderBy: string, asc: boolean) {
this.getTableDataEntries$(offset, pageSize, orderBy, asc).subscribe((result: SearchTableResult ) => {
this.offset = offset;
this.rowLimiter.pageSize = pageSize;
this.orderBy = orderBy;
this.asc = asc;
this.count = result.count;
this.selected = [];
this.unselectRows();
const count = result.count;
const start = offset * pageSize;
const end = start + pageSize;
const end = Math.min(start + pageSize, count);
const newRows = [...result.serviceEntities];
let index = 0;
for (let i = start; i < end; i++) {
newRows[i] = result.serviceEntities[index++];
newRows[i] = {...result.serviceEntities[index++],
status: SearchTableEntityStatus.PERSISTED,
deleted: false
};
}
this.rows = newRows;
this.loading = false;
if(this.count > AlertComponent.MAX_COUNT_CSV) {
if(count > AlertComponent.MAX_COUNT_CSV) {
this.alertService.error("Maximum number of rows reached for downloading CSV");
}
}, (error: any) => {
console.log("error getting the message log:" + error);
this.loading = false;
this.alertService.error("Error occured:" + error);
this.alertService.error("Error occurred:" + error);
});
}
onPage(event) {
console.log('Page Event', event);
this.page(event.offset, event.pageSize, this.orderBy, this.asc);
}
onSort(event) {
console.log('Sort Event', event);
let ascending = true;
if (event.newValue === 'desc') {
ascending = false;
}
let ascending = event.newValue !== 'desc';
this.page(this.offset, this.rowLimiter.pageSize, event.column.prop, ascending);
}
onSelect({selected}) {
// console.log('Select Event', selected, this.selected);
this.selected = [...selected];
if(this.editButtonEnabled) {
this.rowNumber = this.selected[0]["$$index"];
}
}
onActivate(event) {
// console.log('Activate Event', event);
if ("dblclick" === event.type) {
this.details(event.row);
}
}
changePageSize(newPageLimit: number) {
console.log('New page limit:', newPageLimit);
this.page(0, newPageLimit, this.orderBy, this.asc);
}
search() {
console.log("Searching using filter:" + this.filter);
this.page(0, this.rowLimiter.pageSize, this.orderBy, this.asc);
}
isRowSelected() {
if (this.selected)
return true;
details(selectedRow: any) {
this.searchTableController.showDetails(selectedRow);
}
onEditRowActionClicked(rowNumber: number) {
this.editSearchTableEntity(rowNumber);
}
return false;
onDeleteRowActionClicked(row: SearchTableEntity) {
this.deleteSearchTableEntities([row]);
}
onNewButtonClicked() {
const formRef: MdDialogRef<any> = this.searchTableController.newDialog({
data: { edit: false }
});
formRef.afterClosed().subscribe(result => {
if (result) {
this.rows = [...this.rows, {...formRef.componentInstance.current}];
} else {
this.unselectRows();
}
});
}
details(selectedRow: any) {
this.searchTableController.showDetails(selectedRow);
onDeleteButtonClicked() {
this.deleteSearchTableEntities(this.selected);
}
onEditButtonClicked() {
if (this.rowNumber >= 0 && this.rows[this.rowNumber] && this.rows[this.rowNumber].deleted) {
this.alertService.error('You cannot edit a deleted entry.', false);
return;
}
this.editSearchTableEntity(this.rowNumber);
}
onSaveButtonClicked(withDownloadCSV: boolean) {
try {
// TODO: add validation support to existing controllers
// const isValid = this.userValidatorService.validateUsers(this.users);
// if (!isValid) return;
this.dialog.open(SaveDialogComponent).afterClosed().subscribe(result => {
if (result) {
// this.unselectRows();
const modifiedUsers = this.rows.filter(el => el.status !== SearchTableEntityStatus.PERSISTED);
// this.isBusy = true;
this.http.put(/*UserComponent.USER_USERS_URL TODO: use PUT url*/'', modifiedUsers).subscribe(res => {
// this.isBusy = false;
// this.getUsers();
this.alertService.success('The operation \'update\' completed successfully.', false);
if (withDownloadCSV) {
this.downloadService.downloadNative(/*UserComponent.USER_CSV_URL TODO: use CSV url*/ '');
}
}, err => {
// this.isBusy = false;
// this.getUsers();
this.alertService.exception('The operation \'update\' not completed successfully.', err, false);
});
} else {
if (withDownloadCSV) {
this.downloadService.downloadNative(/*UserComponent.USER_CSV_URL TODO: use CSV url*/ '');
}
}
});
} catch (err) {
// this.isBusy = false;
this.alertService.exception('The operation \'update\' completed with errors.', err);
}
}
onCancelButtonClicked() {
this.dialog.open(CancelDialogComponent).afterClosed().subscribe(result => {
if (result) {
this.page(this.offset, this.rowLimiter.pageSize, this.orderBy, this.asc);
}
});
}
newButtonAction(){
getRowsAsString(): number {
return this.rows.length;
}
get editButtonEnabled(): boolean {
return this.selected && this.selected.length == 1 && !this.selected[0].deleted;
}
editButtonAction(){
this.editRowButtonAction( this.selected[0]);
get deleteButtonEnabled(): boolean {
return this.selected && this.selected.length > 0 && !this.selected.every(el => el.deleted);
}
deleteButtonAction(){
// delete all seleted rows
get submitButtonsEnabled(): boolean {
const rowsDeleted = !!this.rows.find(row => row.deleted);
const dirty = rowsDeleted || !!this.rows.find(el => el.status !== SearchTableEntityStatus.PERSISTED);
return dirty;
}
editRowButtonAction(row: any){
this.details(row);
private editSearchTableEntity(rowNumber: number) {
const row = this.rows[rowNumber];
const formRef: MdDialogRef<any> = this.searchTableController.newDialog({
data: {edit: true, row}
});
formRef.afterClosed().subscribe(result => {
if (result) {
const status = row.status === SearchTableEntityStatus.PERSISTED
? SearchTableEntityStatus.UPDATED
: row.status;
this.rows[rowNumber] = {...formRef.componentInstance.current, status};
this.rows = [...this.rows];
}
});
}
deleteRowButtonAction(row: any){
private deleteSearchTableEntities(rows: Array<SearchTableEntity>) {
// TODO: add validation support to existing controllers
// if (this.searchTableController.validateDeleteOperation(rows)) {
// this.alertService.error('You cannot delete the logged in user: ' + this.securityService.getCurrentUser().username);
// return;
// }
for (const row of rows) {
if (row.status === SearchTableEntityStatus.NEW) {
this.rows.splice(this.rows.indexOf(row), 1);
} else {
row.status = SearchTableEntityStatus.REMOVED;
row.deleted = true;
}
}
this.unselectRows()
}
private unselectRows() {
this.selected = [];
}
}
import {SearchTableController} from "../common/search-table/search-table-controller";
import {MdDialog, MdDialogRef} from "@angular/material";
import {MdDialog, MdDialogConfig, MdDialogRef} from "@angular/material";
import {DomainDetailsDialogComponent} from "./domain-details-dialog/domain-details-dialog.component";
import {DomainRo} from "./domain-ro.model";
import {SearchTableEntityStatus} from "../common/search-table/search-table-entity-status.model";
export class DomainController implements SearchTableController {
......@@ -17,4 +19,19 @@ export class DomainController implements SearchTableController {
public edit(row: any) { }
public delete(row: any) { }
public newDialog(config?: MdDialogConfig): MdDialogRef<DomainDetailsDialogComponent> {
return this.dialog.open(DomainDetailsDialogComponent, config);
}
public newRow(): DomainRo {
return {
domainId: '',
bdmslClientCertHeader: '',
bdmslClientCertAlias: '',
bdmslSmpId: '',
signatureCertAlias: '',
status: SearchTableEntityStatus.NEW
}
}
}
export interface DomainRo {
import {SearchTableEntity} from "../common/search-table/search-table-entity.model";
export interface DomainRo extends SearchTableEntity {
domainId: string;
bdmslClientCertHeader: string;
bdmslClientCertAlias: string;
......
......@@ -5,7 +5,7 @@
<tr>
<td>
<md-input-container>
<input mdInput placeholder="Username" name="username" [(ngModel)]="model.username" #username="ngModel"
<input mdInput placeholder="Username" name="userName" [(ngModel)]="model.userName" #userName="ngModel"
required id="username_id">
</md-input-container>
</td>
......
export enum Role {
SMP_ADMINISTRATOR,
SERVICE_GROUP_ADMINISTRATOR,
SYSTEM_ADMINISTRATOR,
}
import {Injectable} from "@angular/core";
import {Role} from "./role.model";
@Injectable()
export class RoleService {
public getLabel(role: Role): string {
switch (role) {
case Role.SMP_ADMINISTRATOR:
return 'SMP Administrator';
case Role.SERVICE_GROUP_ADMINISTRATOR:
return 'ServiceGroup Administrator';
case Role.SYSTEM_ADMINISTRATOR:
return 'System Administrator';
default:
return '';
}
}
}
import {SearchTableController} from "../common/search-table/search-table-controller";
import {MdDialog, MdDialogRef} from "@angular/material";
import {MdDialog, MdDialogConfig, MdDialogRef} from "@angular/material";
import {ServiceGroupDetailsDialogComponent} from "./service-group-details-dialog/service-group-details-dialog.component";
import {Http} from "@angular/http";
import {AlertService} from "../alert/alert.service";
import {ServiceGroupExtensionDialogComponent} from "./service-group-extension-dialog/service-group-extension-dialog.component";
import {ServiceGroupMetadataListDialogComponent} from "./service-group-metadata-list-dialog/service-group-metadata-list-dialog.component";
import {UserDetailsDialogComponent} from "../user/user-details-dialog/user-details-dialog.component";
import {SearchTableEntity} from "../common/search-table/search-table-entity.model";
import {ServiceGroupRo} from "./service-group-ro.model";
import {SearchTableEntityStatus} from "../common/search-table/search-table-entity-status.model";
export class ServiceGroupController implements SearchTableController {
constructor(public dialog: MdDialog) { }
public showDetails(row: any) {
let dialogRef: MdDialogRef<ServiceGroupDetailsDialogComponent> = this.dialog.open(ServiceGroupDetailsDialogComponent);
let dialogRef: MdDialogRef<ServiceGroupDetailsDialogComponent> = this.newDialog();
dialogRef.componentInstance.servicegroup = row;
dialogRef.afterClosed().subscribe(result => {
//Todo:
......@@ -37,5 +41,21 @@ export class ServiceGroupController implements SearchTableController {
public edit(row: any) { }
public delete(row: any) { }
public delete(row: any) { }
public newDialog(config?: MdDialogConfig): MdDialogRef<ServiceGroupDetailsDialogComponent> {
return this.dialog.open(ServiceGroupDetailsDialogComponent, config);
}
public newRow(): ServiceGroupRo {
return {
domain: '',
serviceGroupROId: {
participantId: '',
participantSchema: ''
},
status: SearchTableEntityStatus.NEW
};
}
}
import { ServiceGroupROId } from './service-group-ro-id.model';
import {SearchTableEntity} from "../common/search-table/search-table-entity.model";
export interface ServiceGroupRo {
export interface ServiceGroupRo extends SearchTableEntity {
serviceGroupROId: ServiceGroupROId;
domain: string;
}
import {SearchTableController} from "../common/search-table/search-table-controller";
import {MdDialog, MdDialogRef} from "@angular/material";
import {MdDialog, MdDialogConfig, MdDialogRef} from "@angular/material";
import {UserDetailsDialogComponent} from "./user-details-dialog/user-details-dialog.component";
import {UserRo} from "./user-ro.model";
import {SearchTableEntityStatus} from "../common/search-table/search-table-entity-status.model";
export class UserController implements SearchTableController {
......@@ -8,7 +10,6 @@ export class UserController implements SearchTableController {
public showDetails(row: any) {
let dialogRef: MdDialogRef<UserDetailsDialogComponent> = this.dialog.open(UserDetailsDialogComponent);
dialogRef.componentInstance.user = row;
dialogRef.afterClosed().subscribe(result => {
//Todo:
});
......@@ -16,5 +17,17 @@ export class UserController implements SearchTableController {
public edit(row: any) { }
public delete(row: any) { }
public delete(row: any) { }
public newDialog(config?: MdDialogConfig): MdDialogRef<UserDetailsDialogComponent> {
return this.dialog.open(UserDetailsDialogComponent, config);
}
public newRow(): UserRo {
return {
userName: '',
role: '',
status: SearchTableEntityStatus.NEW
}
}
}
<h2 md-dialog-title>User details</h2>
<h2 md-dialog-title>{{formTitle}}</h2>
<md-dialog-content style="height:260px;width:650px">
<md-card>
<md-card-content>
<md-input-container style="width:100%">
<input mdInput placeholder="Username id" value="{{user.username}}" readonly/>
</md-input-container>
<div style="margin-top:15px;">
<md-input-container style="width:100%">
<input mdInput placeholder="Username" name="userName" id="userName" [value]="current.userName" (blur)="updateUserName($event)" [formControl]="userForm.controls['userName']" maxlength="255" required>
<div *ngIf="userForm.controls['userName'].hasError('required') && userForm.controls['userName'].touched" style="color:red; font-size: 70%">You should type an username</div>
</md-input-container>
</div>
<md-input-container style="width:100%">
<input mdInput placeholder="isAdmin" value="{{user.idAdmin}}" readonly/>
</md-input-container>
<div style="margin-top:10px;">
<md2-select mdInput placeholder="Role" [style.width]="'100%'" [formControl]="userForm.controls['role']"
[(ngModel)]="role" required>
<md2-option *ngFor="let item of existingRoles" [value]="item">{{getRoleLabel(item)}}</md2-option>
</md2-select>
<div *ngIf="userForm.controls['role'].hasError('required') && userForm.controls['role'].touched" style="color:red; font-size: 70%">You need to choose at least one role for this user</div>
</div>
<div style="margin-top:15px;">
<md-input-container [style.width]="'100%'">
<input mdInput placeholder="Password" name="password" type="password" [value]="current.password" (blur)="updatePassword($event)" [formControl]="userForm.controls['password']" [pattern]="passwordPattern" [required]="!editMode">
<div *ngIf="!editMode && userForm.controls['password'].hasError('required') && userForm.controls['password'].touched" style="color:red; font-size: 70%">You should type a password</div>
<div *ngIf="userForm.controls['password'].dirty && userForm.controls['password'].hasError('pattern') && userForm.controls['password'].touched" style="color:red; font-size: 70%">
Password should follow all of these rules:<br>
- Minimum length: 8 characters<br>
- Maximum length: 32 characters<br>
- At least one letter in lowercase<br>
- At least one letter in uppercase<br>
- At least one digit<br>
- At least one special character
</div>
</md-input-container>
</div>
<div>
<md-input-container [style.width]="'100%'">
<input mdInput placeholder="Confirmation" name="confirmation" type="password" [value]="current.confirmation" [formControl]="userForm.controls['confirmation']" [required]="!editMode">
<div *ngIf="!editMode && userForm.controls['confirmation'].hasError('required') && userForm.controls['confirmation'].touched" style="color:red; font-size: 70%">You should type a password</div>
<div *ngIf="userForm.errors?.confirmation && userForm.controls['confirmation'].touched" style="color:red; font-size: 70%">Passwords do not match</div>
</md-input-container>
</div>
</md-card-content>
</md-card>
</md-dialog-content>
<md-dialog-actions>
<div class="group-action-button" >
<button id="ServiceGroupsSaveButton" md-raised-button color="primary" (click)="dialogRef.close({})"
style="margin-top:10px">
<md-icon>save</md-icon>
<span>Save</span>
</button>
<button id="ServiceGroupsCloseButton" md-raised-button color="primary" (click)="dialogRef.close({})"
style="margin-top:10px">
<md-icon>close</md-icon>
<span>Close</span>
</button>
</div>
</md-dialog-actions>
<table class="buttonsRow">
<tr>
<td>
<button md-raised-button color="primary" [md-dialog-close]="true" (click)="submitForm()" [disabled]="!userForm.valid">
<md-icon>check_circle</md-icon>
<span>OK</span>
</button>
<button md-raised-button color="primary" md-dialog-close>
<md-icon>cancel</md-icon>
<span>Cancel</span>
</button>
</td>
</tr>
</table>
<div style="text-align: right; font-size: 70%">* required fields</div>
import {Component} from '@angular/core';
import {MdDialogRef} from "@angular/material";
import {Component, Inject} from '@angular/core';
import {MD_DIALOG_DATA, MdDialogRef} from "@angular/material";
import {AbstractControl, FormBuilder, FormControl, FormGroup, ValidationErrors, ValidatorFn, Validators} from "@angular/forms";
import {UserService} from "../user.service";
import {Role} from "../../security/role.model";
import {RoleService} from "../../security/role.service";
import {UserRo} from "../user-ro.model";
import {SearchTableEntityStatus} from "../../common/search-table/search-table-entity-status.model";
@Component({
selector: 'user-details-dialog',
......@@ -7,10 +13,86 @@ import {MdDialogRef} from "@angular/material";
})
export class UserDetailsDialogComponent {
user;
dateFormat: String = 'yyyy-MM-dd HH:mm:ssZ';
static readonly NEW_MODE = 'New User';
static readonly EDIT_MODE = 'User Edit';
constructor(public dialogRef: MdDialogRef<UserDetailsDialogComponent>) {
// readonly emailPattern = '[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}';
readonly passwordPattern = '^(?=.*[A-Z])(?=.*[ !#$%&\'()*+,-./:;<=>?@\\[^_`{|}~\\\]"])(?=.*[0-9])(?=.*[a-z]).{8,32}$';
editMode: boolean;
formTitle: string;
userRoles = [];
existingRoles = [];
confirmation = '';
current: UserRo & { confirmation?: string };
userForm: FormGroup;
private passwordConfirmationValidator: ValidatorFn = (control: FormGroup): ValidationErrors | null => {
const password = control.get('password');
const confirmation = control.get('confirmation');
return password && confirmation && password.value !== confirmation.value ? { confirmation: true } : null;
};
constructor(private dialogRef: MdDialogRef<UserDetailsDialogComponent>,
private userService: UserService,
private roleService: RoleService,
@Inject(MD_DIALOG_DATA) public data: any,
private fb: FormBuilder) {
this.editMode = data.edit;
this.formTitle = this.editMode ? UserDetailsDialogComponent.EDIT_MODE: UserDetailsDialogComponent.NEW_MODE;
this.current = this.editMode
? {
...data.row,
confirmation: data.row.password
}
: {
userName: '',
password: '',
confirmation: '',
role: '',
status: SearchTableEntityStatus.NEW
};
this.userForm = fb.group({
'userName': new FormControl({value: this.current.userName, disabled: this.editMode}, this.editMode ? Validators.nullValidator : null),
'role': new FormControl(this.current.role, Validators.required),
'password': new FormControl(this.current.password, [Validators.required, Validators.pattern(this.passwordPattern)]),
'confirmation': new FormControl(this.current.password, Validators.pattern(this.passwordPattern))
}, {
validator: this.passwordConfirmationValidator
});
this.userService.getUserRoles$().subscribe(userRoles => {
this.userRoles = userRoles.json();
this.existingRoles = this.editMode
? this.getAllowedRoles(this.userRoles, this.current.role)
: this.userRoles;
});
}
submitForm() {
this.dialogRef.close(true);
}
updateUserName(event) {
this.current.userName = event.target.value;
}
updatePassword(event) {
this.current.password = event.target.value;
}
getRoleLabel(role: Role): string {
return this.roleService.getLabel(role);
}
// filters out roles so that the user cannot change from system administrator to the other roles or vice-versa
private getAllowedRoles(allRoles, userRole) {
if (userRole === Role.SYSTEM_ADMINISTRATOR) {
return [Role.SYSTEM_ADMINISTRATOR];
} else {
return allRoles.filter(role => role !== Role.SYSTEM_ADMINISTRATOR);
}
}
}
export interface UserRo {
username: string;
admin: string;
import {SearchTableEntity} from "../common/search-table/search-table-entity.model";
export interface UserRo extends SearchTableEntity {
userName: string;
password?: string;
role: string;
suspended?: boolean;
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment