From d30184800c32c6161a0244ef263bda31bf016d4b Mon Sep 17 00:00:00 2001
From: TINCU Sebastian-Ion <Sebastian-Ion.TINCU@ext.ec.europa.eu>
Date: Tue, 9 Oct 2018 14:37:21 +0200
Subject: [PATCH] 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.
---
 smp-angular/e2e/app.e2e-spec.ts               |   2 +-
 smp-angular/src/app/app.module.ts             |   4 +
 .../search-table/search-table-controller.ts   |   5 +
 .../search-table-entity-status.model.ts       |   6 +
 .../search-table/search-table-entity.model.ts |   6 +
 .../search-table/search-table-result.model.ts |   4 +-
 .../search-table/search-table.component.css   |  13 +-
 .../search-table/search-table.component.html  | 109 ++++----
 .../search-table/search-table.component.ts    | 236 +++++++++++++-----
 .../src/app/domain/domain-controller.ts       |  19 +-
 smp-angular/src/app/domain/domain-ro.model.ts |   4 +-
 .../src/app/login/login.component.html        |   2 +-
 smp-angular/src/app/security/role.model.ts    |   5 +
 smp-angular/src/app/security/role.service.ts  |  20 ++
 .../service-group/service-group-controller.ts |  26 +-
 .../service-group/service-group-ro.model.ts   |   3 +-
 smp-angular/src/app/user/user-controller.ts   |  19 +-
 .../user-details-dialog.component.html        |  74 ++++--
 .../user-details-dialog.component.ts          |  92 ++++++-
 smp-angular/src/app/user/user-ro.model.ts     |  10 +-
 smp-angular/src/app/user/user.component.css   |   2 -
 smp-angular/src/app/user/user.component.html  |  21 +-
 smp-angular/src/app/user/user.component.ts    |  21 +-
 smp-angular/src/app/user/user.service.ts      |  16 ++
 .../ec/edelivery/smp/data/ui/UserRO.java      |  23 +-
 .../ServiceUIDataIntegrationTest.java         |   4 +-
 .../ec/edelivery/smp/ui/UserResource.java     |   2 +-
 27 files changed, 536 insertions(+), 212 deletions(-)
 create mode 100644 smp-angular/src/app/common/search-table/search-table-entity-status.model.ts
 create mode 100644 smp-angular/src/app/common/search-table/search-table-entity.model.ts
 create mode 100644 smp-angular/src/app/security/role.model.ts
 create mode 100644 smp-angular/src/app/security/role.service.ts
 create mode 100644 smp-angular/src/app/user/user.service.ts

diff --git a/smp-angular/e2e/app.e2e-spec.ts b/smp-angular/e2e/app.e2e-spec.ts
index c19f4bb57..08f1001f7 100644
--- a/smp-angular/e2e/app.e2e-spec.ts
+++ b/smp-angular/e2e/app.e2e-spec.ts
@@ -1,6 +1,6 @@
 import { SmpAngular2WebPage } from './app.po';
 
-describe('domibus-MSH-web App', function() {
+describe('smp-MSH-web App', function() {
   let page: SmpAngular2WebPage;
 
   beforeEach(() => {
diff --git a/smp-angular/src/app/app.module.ts b/smp-angular/src/app/app.module.ts
index c020feac1..a84d410ed 100644
--- a/smp-angular/src/app/app.module.ts
+++ b/smp-angular/src/app/app.module.ts
@@ -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,
diff --git a/smp-angular/src/app/common/search-table/search-table-controller.ts b/smp-angular/src/app/common/search-table/search-table-controller.ts
index 4bce65d93..534c99864 100644
--- a/smp-angular/src/app/common/search-table/search-table-controller.ts
+++ b/smp-angular/src/app/common/search-table/search-table-controller.ts
@@ -1,5 +1,10 @@
+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>;
 }
diff --git a/smp-angular/src/app/common/search-table/search-table-entity-status.model.ts b/smp-angular/src/app/common/search-table/search-table-entity-status.model.ts
new file mode 100644
index 000000000..9a4a22120
--- /dev/null
+++ b/smp-angular/src/app/common/search-table/search-table-entity-status.model.ts
@@ -0,0 +1,6 @@
+export enum SearchTableEntityStatus {
+  PERSISTED,
+  UPDATED,
+  NEW,
+  REMOVED
+}
diff --git a/smp-angular/src/app/common/search-table/search-table-entity.model.ts b/smp-angular/src/app/common/search-table/search-table-entity.model.ts
new file mode 100644
index 000000000..572e888d8
--- /dev/null
+++ b/smp-angular/src/app/common/search-table/search-table-entity.model.ts
@@ -0,0 +1,6 @@
+import {SearchTableEntityStatus} from "./search-table-entity-status.model";
+
+export interface SearchTableEntity {
+  status: SearchTableEntityStatus;
+  deleted?: boolean;
+}
diff --git a/smp-angular/src/app/common/search-table/search-table-result.model.ts b/smp-angular/src/app/common/search-table/search-table-result.model.ts
index 9c6c6ad72..2588f29c6 100644
--- a/smp-angular/src/app/common/search-table/search-table-result.model.ts
+++ b/smp-angular/src/app/common/search-table/search-table-result.model.ts
@@ -1,5 +1,7 @@
+import {SearchTableEntity} from "./search-table-entity.model";
+
 export interface SearchTableResult {
-  serviceEntities: Array<any>;
+  serviceEntities: Array<SearchTableEntity>;
   pageSize: number;
   count: number;
   filter: any;
diff --git a/smp-angular/src/app/common/search-table/search-table.component.css b/smp-angular/src/app/common/search-table/search-table.component.css
index d1a612141..163929e63 100644
--- a/smp-angular/src/app/common/search-table/search-table.component.css
+++ b/smp-angular/src/app/common/search-table/search-table.component.css
@@ -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;
+}
diff --git a/smp-angular/src/app/common/search-table/search-table.component.html b/smp-angular/src/app/common/search-table/search-table.component.html
index 3135c062c..a2f392c20 100644
--- a/smp-angular/src/app/common/search-table/search-table.component.html
+++ b/smp-angular/src/app/common/search-table/search-table.component.html
@@ -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>
-
-<!--
-
-
--->
diff --git a/smp-angular/src/app/common/search-table/search-table.component.ts b/smp-angular/src/app/common/search-table/search-table.component.ts
index 294f8b26b..3fc052269 100644
--- a/smp-angular/src/app/common/search-table/search-table.component.ts
+++ b/smp-angular/src/app/common/search-table/search-table.component.ts
@@ -1,5 +1,5 @@
-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 = [];
+  }
 }
diff --git a/smp-angular/src/app/domain/domain-controller.ts b/smp-angular/src/app/domain/domain-controller.ts
index b5b62d4a9..3748b9156 100644
--- a/smp-angular/src/app/domain/domain-controller.ts
+++ b/smp-angular/src/app/domain/domain-controller.ts
@@ -1,6 +1,8 @@
 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
+    }
+  }
 }
diff --git a/smp-angular/src/app/domain/domain-ro.model.ts b/smp-angular/src/app/domain/domain-ro.model.ts
index 7b90e0359..b6c52bb97 100644
--- a/smp-angular/src/app/domain/domain-ro.model.ts
+++ b/smp-angular/src/app/domain/domain-ro.model.ts
@@ -1,4 +1,6 @@
-export interface DomainRo {
+import {SearchTableEntity} from "../common/search-table/search-table-entity.model";
+
+export interface DomainRo extends SearchTableEntity {
   domainId: string;
   bdmslClientCertHeader: string;
   bdmslClientCertAlias: string;
diff --git a/smp-angular/src/app/login/login.component.html b/smp-angular/src/app/login/login.component.html
index aae172e41..c01af67c6 100644
--- a/smp-angular/src/app/login/login.component.html
+++ b/smp-angular/src/app/login/login.component.html
@@ -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>
diff --git a/smp-angular/src/app/security/role.model.ts b/smp-angular/src/app/security/role.model.ts
new file mode 100644
index 000000000..4322e22ec
--- /dev/null
+++ b/smp-angular/src/app/security/role.model.ts
@@ -0,0 +1,5 @@
+export enum Role {
+  SMP_ADMINISTRATOR,
+  SERVICE_GROUP_ADMINISTRATOR,
+  SYSTEM_ADMINISTRATOR,
+}
diff --git a/smp-angular/src/app/security/role.service.ts b/smp-angular/src/app/security/role.service.ts
new file mode 100644
index 000000000..f3bb00120
--- /dev/null
+++ b/smp-angular/src/app/security/role.service.ts
@@ -0,0 +1,20 @@
+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 '';
+    }
+  }
+
+}
diff --git a/smp-angular/src/app/service-group/service-group-controller.ts b/smp-angular/src/app/service-group/service-group-controller.ts
index 8dc516710..ab2408eb7 100644
--- a/smp-angular/src/app/service-group/service-group-controller.ts
+++ b/smp-angular/src/app/service-group/service-group-controller.ts
@@ -1,17 +1,21 @@
 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
+    };
+  }
+
 }
diff --git a/smp-angular/src/app/service-group/service-group-ro.model.ts b/smp-angular/src/app/service-group/service-group-ro.model.ts
index 13955cf6a..4c4427e2e 100644
--- a/smp-angular/src/app/service-group/service-group-ro.model.ts
+++ b/smp-angular/src/app/service-group/service-group-ro.model.ts
@@ -1,6 +1,7 @@
 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;
 }
diff --git a/smp-angular/src/app/user/user-controller.ts b/smp-angular/src/app/user/user-controller.ts
index 9ada4224b..ac8984341 100644
--- a/smp-angular/src/app/user/user-controller.ts
+++ b/smp-angular/src/app/user/user-controller.ts
@@ -1,6 +1,8 @@
 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
+    }
+  }
 }
diff --git a/smp-angular/src/app/user/user-details-dialog/user-details-dialog.component.html b/smp-angular/src/app/user/user-details-dialog/user-details-dialog.component.html
index c31301815..fb361aee1 100644
--- a/smp-angular/src/app/user/user-details-dialog/user-details-dialog.component.html
+++ b/smp-angular/src/app/user/user-details-dialog/user-details-dialog.component.html
@@ -1,36 +1,64 @@
-<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>
 
diff --git a/smp-angular/src/app/user/user-details-dialog/user-details-dialog.component.ts b/smp-angular/src/app/user/user-details-dialog/user-details-dialog.component.ts
index 6921a83d8..f88bdb227 100644
--- a/smp-angular/src/app/user/user-details-dialog/user-details-dialog.component.ts
+++ b/smp-angular/src/app/user/user-details-dialog/user-details-dialog.component.ts
@@ -1,5 +1,11 @@
-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);
+    }
+  }
 }
diff --git a/smp-angular/src/app/user/user-ro.model.ts b/smp-angular/src/app/user/user-ro.model.ts
index 5642811ad..fc604cea8 100644
--- a/smp-angular/src/app/user/user-ro.model.ts
+++ b/smp-angular/src/app/user/user-ro.model.ts
@@ -1,4 +1,8 @@
-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;
 }
diff --git a/smp-angular/src/app/user/user.component.css b/smp-angular/src/app/user/user.component.css
index 94bdbcf05..1e2c45675 100644
--- a/smp-angular/src/app/user/user.component.css
+++ b/smp-angular/src/app/user/user.component.css
@@ -11,5 +11,3 @@
 #hiddenButtonId {
   position: fixed;
 }
-
-
diff --git a/smp-angular/src/app/user/user.component.html b/smp-angular/src/app/user/user.component.html
index d14b4cd37..cf3b4eef1 100644
--- a/smp-angular/src/app/user/user.component.html
+++ b/smp-angular/src/app/user/user.component.html
@@ -1,31 +1,18 @@
-
 <smp-search-table
   page_id= 'user_id'
   title= 'Users'
   [columnPicker] = "columnPicker"
-  url="ui/user"
+  [url]="'ui/user'"
   [additionalToolButtons]="additionalToolButtons"
   [searchTableController]="userController"
   [searchPanel]="searchPanel"
-  [filter]="filter"
-
->
-
+  [filter]="filter">
 
-  <ng-template #additionalToolButtons >
-
-  </ng-template>
+  <ng-template #additionalToolButtons></ng-template>
 
   <ng-template #searchPanel>
     <md-input-container>
-      <input mdInput placeholder="Username" name="Username" [(ngModel)]="filter.messageId"
-             #messageId="ngModel" id="messageid_id">
+      <input mdInput placeholder="Username" name="Username" [(ngModel)]="filter.userName" #messageId="ngModel">
     </md-input-container>
-    <md-input-container>
-      <input mdInput placeholder="isAdmin" name="isAdmin" [(ngModel)]="filter.messageId"
-             #messageId="ngModel" id="participanschema_id">
-    </md-input-container>
-
   </ng-template>
-
 </smp-search-table>
diff --git a/smp-angular/src/app/user/user.component.ts b/smp-angular/src/app/user/user.component.ts
index bcac77b2b..ebb9a4b37 100644
--- a/smp-angular/src/app/user/user.component.ts
+++ b/smp-angular/src/app/user/user.component.ts
@@ -7,7 +7,6 @@ import {AlertService} from "../alert/alert.service";
 import {UserController} from "./user-controller";
 
 @Component({
-  moduleId: module.id,
   templateUrl:'./user.component.html',
   styleUrls: ['./user.component.css']
 })
@@ -30,23 +29,29 @@ export class UserComponent implements OnInit {
     this.columnPicker.allColumns = [
       {
         name: 'Username',
-        prop: 'username',
-        width: 275
+        prop: 'userName',
+        canAutoResize: true
       },
       {
-        name: 'isAdmin',
-        prop: 'isadmin',
-        width: 40
+        name: 'Role',
+        prop: 'role',
+        canAutoResize: true
+      },
+      {
+        name: 'Password',
+        prop: 'password',
+        canAutoResize: true,
+        sortable: false,
+        width: 25
       }
     ];
 
     this.columnPicker.selectedColumns = this.columnPicker.allColumns.filter(col => {
-      return ["Username", "isAdmin"].indexOf(col.name) != -1
+      return ['Username', 'Role'].indexOf(col.name) != -1
     });
   }
 
   details(row: any) {
     this.userController.showDetails(row);
-
   }
 }
diff --git a/smp-angular/src/app/user/user.service.ts b/smp-angular/src/app/user/user.service.ts
new file mode 100644
index 000000000..998b81b54
--- /dev/null
+++ b/smp-angular/src/app/user/user.service.ts
@@ -0,0 +1,16 @@
+import {Injectable} from "@angular/core";
+import {Observable, Subject} from "rxjs";
+import {Http} from "@angular/http";
+import {Role} from "../security/role.model";
+
+@Injectable()
+export class UserService {
+
+  constructor(private http: Http) {}
+
+  getUserRoles$() {
+    // return this.http.get('rest/user/userroles');
+    // TODO create the endpoint
+    return Observable.of({json: () => [Role.SMP_ADMINISTRATOR, Role.SERVICE_GROUP_ADMINISTRATOR, Role.SYSTEM_ADMINISTRATOR]});
+  }
+}
diff --git a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/ui/UserRO.java b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/ui/UserRO.java
index 0547cec7f..510787760 100644
--- a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/ui/UserRO.java
+++ b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/ui/UserRO.java
@@ -1,15 +1,10 @@
 package eu.europa.ec.edelivery.smp.data.ui;
 
 
-import lombok.EqualsAndHashCode;
-import lombok.ToString;
-
 import javax.persistence.*;
 import java.io.Serializable;
 import java.util.Objects;
 
-import static eu.europa.ec.edelivery.smp.data.model.CommonColumnsLengths.MAX_USERNAME_LENGTH;
-
 /**
  * @author Joze Rihtarsic
  * @since 4.1
@@ -23,7 +18,7 @@ public class UserRO implements Serializable {
     private static final long serialVersionUID = -4971552086560325302L;
     @Id
     @Column(name = "username")
-    private String username;
+    private String userName;
     @Column(name = "password")
     private String password;
     @Column(name = "isadmin")
@@ -33,18 +28,18 @@ public class UserRO implements Serializable {
 
     }
 
-    public UserRO(String username, String password, boolean isAdmin) {
-        this.username = username;
+    public UserRO(String userName, String password, boolean isAdmin) {
+        this.userName = userName;
         this.password = password;
         this.isAdmin = isAdmin;
     }
 
-    public String getUsername() {
-        return username;
+    public String getUserName() {
+        return userName;
     }
 
-    public void setUsername(String username) {
-        this.username = username;
+    public void setUserName(String userName) {
+        this.userName = userName;
     }
 
     public String getPassword() {
@@ -68,12 +63,12 @@ public class UserRO implements Serializable {
         if (this == o) return true;
         if (o == null || getClass() != o.getClass()) return false;
         UserRO userRO = (UserRO) o;
-        return Objects.equals(username, userRO.username);
+        return Objects.equals(userName, userRO.userName);
     }
 
     @Override
     public int hashCode() {
 
-        return Objects.hash(username);
+        return Objects.hash(userName);
     }
 }
diff --git a/smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/services/ServiceUIDataIntegrationTest.java b/smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/services/ServiceUIDataIntegrationTest.java
index 8bd592970..063af4d54 100644
--- a/smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/services/ServiceUIDataIntegrationTest.java
+++ b/smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/services/ServiceUIDataIntegrationTest.java
@@ -64,7 +64,7 @@ public class ServiceUIDataIntegrationTest {
         for (int i = 0; i < 20; i++) {
             UserRO ent = new UserRO();
             ent.setAdmin(false);
-            ent.setUsername("Username" + i);
+            ent.setUserName("Username" + i);
             ent.setPassword("Password");
             serviceUIData.persistUser(ent);
         }
@@ -128,7 +128,7 @@ public class ServiceUIDataIntegrationTest {
 
         UserRO ent = new UserRO();
         ent.setAdmin(false);
-        ent.setUsername("Username");
+        ent.setUserName("Username");
         ent.setPassword("Password");
 
         long cnt = serviceUIData.getUserList(0, 10, null, null).getCount();
diff --git a/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/ui/UserResource.java b/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/ui/UserResource.java
index 0eea0022f..f225ed206 100644
--- a/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/ui/UserResource.java
+++ b/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/ui/UserResource.java
@@ -33,7 +33,7 @@ public class UserResource {
     @PutMapping(produces = {"application/json"})
     @ResponseBody
     @RequestMapping(method = RequestMethod.GET)
-    public ServiceResult<UserRO> getUserist(
+    public ServiceResult<UserRO> getUsers(
             @RequestParam(value = "page", defaultValue = "0") int page,
             @RequestParam(value = "pageSize", defaultValue = "10") int pageSize,
             @RequestParam(value = "orderBy", required = false) String orderBy,
-- 
GitLab