You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@archiva.apache.org by ma...@apache.org on 2020/12/22 23:07:12 UTC

[archiva] 02/04: Adding loading spinner

This is an automated email from the ASF dual-hosted git repository.

martin_s pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/archiva.git

commit b988a30f2c3f9b718953b70e2130337ec4ffe6d2
Author: Martin Stockhammer <ma...@apache.org>
AuthorDate: Sat Dec 19 23:54:23 2020 +0100

    Adding loading spinner
---
 .../home.component.scss => model/role-update.ts}   |  12 ++-
 .../src/main/archiva-web/src/app/model/role.ts     |   5 +
 .../manage-roles-edit.component.html               |  23 +++--
 .../manage-roles-edit.component.ts                 |  22 ++--
 .../manage-roles-list.component.html               |  74 ++++++++------
 .../manage-roles-list.component.ts                 |  14 ++-
 .../manage-users-list.component.html               | 112 +++++++++++++--------
 .../manage-users-list.component.ts                 |  12 +--
 .../app/modules/shared/about/about.component.html  |   5 +-
 .../app/modules/shared/about/about.component.scss  |   5 +-
 .../modules/shared/about/about.component.spec.ts   |   3 +-
 .../app/modules/shared/about/about.component.ts    |   3 +-
 .../modules/shared/contact/contact.component.html  |   5 +-
 .../modules/shared/contact/contact.component.scss  |   5 +-
 .../shared/contact/contact.component.spec.ts       |   3 +-
 .../modules/shared/contact/contact.component.ts    |   3 +-
 .../app/modules/shared/home/home.component.html    |   5 +-
 .../app/modules/shared/home/home.component.scss    |   5 +-
 .../app/modules/shared/home/home.component.spec.ts |   3 +-
 .../src/app/modules/shared/home/home.component.ts  |   3 +-
 .../app/modules/shared/login/login.component.html  |   5 +-
 .../app/modules/shared/login/login.component.scss  |   5 +-
 .../modules/shared/login/login.component.spec.ts   |   3 +-
 .../app/modules/shared/login/login.component.ts    |   3 +-
 .../loading-value.spec.ts}                         |  11 +-
 .../src/app/modules/shared/model/loading-value.ts  |  62 ++++++++++++
 .../page-query.spec.ts}                            |  11 +-
 .../home.component.scss => model/page-query.ts}    |   9 +-
 .../shared/not-found/not-found.component.html      |   5 +-
 .../shared/not-found/not-found.component.scss      |   5 +-
 .../shared/not-found/not-found.component.spec.ts   |   3 +-
 .../shared/not-found/not-found.component.ts        |   3 +-
 .../paginated-entities.component.ts                |  41 ++++++--
 .../src/app/modules/shared/shared.module.ts        |  16 ++-
 .../shared/sidemenu/sidemenu.component.html        |   5 +-
 .../shared/sidemenu/sidemenu.component.scss        |   5 +-
 .../shared/sidemenu/sidemenu.component.spec.ts     |   3 +-
 .../modules/shared/sidemenu/sidemenu.component.ts  |   3 +-
 .../app/modules/shared/sorted-table-component.ts   |  18 ++++
 ...e.component.scss => strip-loading.pipe.spec.ts} |  12 ++-
 .../shared/strip-loading.pipe.ts}                  |  40 +++-----
 ...me.component.scss => with-loading.pipe.spec.ts} |  12 ++-
 .../shared/with-loading.pipe.ts}                   |  49 +++++----
 .../src/app/services/archiva-request.service.ts    |   4 +
 .../archiva-web/src/app/services/role.service.ts   |   5 +-
 .../src/main/archiva-web/src/assets/i18n/en.json   |   3 +
 46 files changed, 424 insertions(+), 239 deletions(-)

diff --git a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/shared/home/home.component.scss b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/model/role-update.ts
similarity index 76%
copy from archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/shared/home/home.component.scss
copy to archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/model/role-update.ts
index 042f3ce..4fff84e 100644
--- a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/shared/home/home.component.scss
+++ b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/model/role-update.ts
@@ -7,8 +7,7 @@
  * "License"); you may not use this file except in compliance
  * with the License.  You may obtain a copy of the License at
  *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
+ * http://www.apache.org/licenses/LICENSE-2.0
  * Unless required by applicable law or agreed to in writing,
  * software distributed under the License is distributed on an
  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
@@ -16,3 +15,12 @@
  * specific language governing permissions and limitations
  * under the License.
  */
+
+export class RoleUpdate {
+    id: string;
+    description: string;
+
+    public toString = () : string => {
+        return 'RoleUpdate: id='+this.id+', description='+this.description;
+    }
+}
diff --git a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/model/role.ts b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/model/role.ts
index ac0b469..d2b77af 100644
--- a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/model/role.ts
+++ b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/model/role.ts
@@ -43,4 +43,9 @@ export class Role {
     root_path: Array<string>
     assigned_origin: boolean;
 
+
+    public toString = () : string => {
+        return 'Role: id='+this.id+', name='+this.name+', description='+this.description;
+    }
+
 }
diff --git a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/security/roles/manage-roles-edit/manage-roles-edit.component.html b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/security/roles/manage-roles-edit/manage-roles-edit.component.html
index d92d21c..b146c72 100644
--- a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/security/roles/manage-roles-edit/manage-roles-edit.component.html
+++ b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/security/roles/manage-roles-edit/manage-roles-edit.component.html
@@ -18,9 +18,9 @@
 
 <form class="mt-3 mb-3" [formGroup]="userForm" (ngSubmit)="onSubmit()">
     <div class="form-group row col-md-8">
-        <div class="col-md-1" *ngIf="editRole && !editRole.permanent">{{'form.edit' |translate}}&nbsp;<span
+        <div class="col-md-1" >{{'form.edit' |translate}}&nbsp;<span
                 class="fas fa-edit"></span></div>
-        <div class="col-md-6" *ngIf="editRole && !editRole.permanent">
+        <div class="col-md-6" >
             <input class="form-check-input" type="checkbox" [value]="editMode" [checked]="editMode"
                    (change)="editMode=!editMode"
             >
@@ -29,8 +29,7 @@
     <div class="form-group row col-md-8">
         <label class="col-md-2 col-form-label" for="id">{{'roles.attributes.id' |translate}}</label>
         <div class="col-md-6">
-            <input type="text" formControlName="id" id="id"
-                   [ngClass]="valid('id')"
+            <input type="text" formControlName="id" id="id" class="form-control-plaintext"
                    [attr.readonly]="true">
         </div>
     </div>
@@ -38,8 +37,8 @@
         <label class="col-md-2 col-form-label" for="id">{{'roles.attributes.name' |translate}}</label>
         <div class="col-md-6">
             <input type="text" formControlName="name" id="name"
-                   [ngClass]="valid('name')"
-                   [attr.readonly]="editMode?null:'true'">
+                   class="form-control-plaintext"
+                   [attr.readonly]="true">
         </div>
     </div>
     <div class="form-group row col-md-8">
@@ -53,14 +52,13 @@
     <div class="form-group row col-md-8">
         <label class="col-md-2 col-form-label" for="id">{{'roles.attributes.resource' |translate}}</label>
         <div class="col-md-6">
-            <input type="text" formControlName="resource" id="resource"
-                   [ngClass]="valid('resource')"
+            <input type="text" formControlName="resource" id="resource" class="form-control-plaintext"
                    [attr.readonly]="true">
         </div>
     </div>
     <div class="form-group row col-md-8">
         <div class="col-md-2"></div>
-        <div class="col-md-6">
+        <div class="col-md-4">
             <div class="form-check">
                 <input class="form-check-input" type="checkbox" formControlName="assignable"
                        id="assignable" [attr.disabled]="true">
@@ -76,6 +74,10 @@
                 </label>
             </div>
         </div>
+        <div class="col-md-2" *ngIf="editMode">
+           <button class="btn btn-primary" type="submit"
+            [disabled]="userForm.invalid || !userForm.dirty">{{'form.button.save'|translate}}</button>
+        </div>
     </div>
 </form>
 <hr/>
@@ -83,7 +85,8 @@
     <ngb-panel id="parents" >
         <ng-template ngbPanelHeader let-opened="opened">
             <div class="d-flex align-items-center justify-content-between">
-                <button ngbPanelToggle class="btn btn-link text-left shadow-none"><h3>{{'roles.edit.parents'|translate}}</h3></button>
+                <button ngbPanelToggle class="btn btn-link text-left shadow-none">
+                    <h3>{{'roles.edit.parents'|translate}}</h3></button>
                 <ng-container *ngIf="!opened"><i class="fa fa-eye-slash"></i></ng-container>
                 <ng-container *ngIf="opened"><i class="fa fa-eye"></i></ng-container>
             </div>
diff --git a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/security/roles/manage-roles-edit/manage-roles-edit.component.ts b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/security/roles/manage-roles-edit/manage-roles-edit.component.ts
index cbbd5d5..b5d033f 100644
--- a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/security/roles/manage-roles-edit/manage-roles-edit.component.ts
+++ b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/security/roles/manage-roles-edit/manage-roles-edit.component.ts
@@ -16,7 +16,7 @@
  * under the License.
  */
 
-import {Component, EventEmitter, OnInit, Output} from '@angular/core';
+import {AfterContentInit, Component, EventEmitter, OnInit, Output} from '@angular/core';
 import {ActivatedRoute} from "@angular/router";
 import {FormBuilder, Validators} from "@angular/forms";
 import {RoleService} from "@app/services/role.service";
@@ -25,15 +25,14 @@ import {Role} from '@app/model/role';
 import {ErrorResult} from "@app/model/error-result";
 import {EditBaseComponent} from "@app/modules/shared/edit-base.component";
 import {forkJoin, iif, Observable, of, pipe, zip} from 'rxjs';
+import {RoleUpdate} from "@app/model/role-update";
 
 @Component({
     selector: 'app-manage-roles-edit',
     templateUrl: './manage-roles-edit.component.html',
     styleUrls: ['./manage-roles-edit.component.scss']
 })
-export class ManageRolesEditComponent extends EditBaseComponent<Role> implements OnInit {
-
-    parentsOpened: boolean
+export class ManageRolesEditComponent extends EditBaseComponent<Role> implements OnInit, AfterContentInit {
 
     editRole: Role;
     editProperties = ['id', 'name', 'description', 'template_instance', 'resource', 'assignable'];
@@ -47,7 +46,7 @@ export class ManageRolesEditComponent extends EditBaseComponent<Role> implements
     constructor(private route: ActivatedRoute, private roleService: RoleService, public fb: FormBuilder) {
         super(fb);
         super.init(fb.group({
-            id: ['', [Validators.required]],
+            id: [''],
             name: ['', Validators.required],
             description: [''],
             resource: [''],
@@ -81,6 +80,8 @@ export class ManageRolesEditComponent extends EditBaseComponent<Role> implements
         });
     }
 
+
+
     /**
      * Array of [role, children[], parents[]]
      */
@@ -137,7 +138,10 @@ export class ManageRolesEditComponent extends EditBaseComponent<Role> implements
     }
 
     onSubmit() {
-        let role = this.copyFromForm(this.editProperties);
+        let role = new RoleUpdate();
+        role.id=this.userForm.get('id').value;
+        role.description = this.userForm.get('description').value;
+        console.log("Submitting changes " + role);
         this.roleService.updateRole(role).pipe(
             catchError((err: ErrorResult) => {
                 this.error = true;
@@ -155,5 +159,11 @@ export class ManageRolesEditComponent extends EditBaseComponent<Role> implements
 
     }
 
+    ngAfterContentInit(): void {
+        if (this.originRole) {
+            this.editRole = this.originRole;
+        }
+    }
+
 }
 
diff --git a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/security/roles/manage-roles-list/manage-roles-list.component.html b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/security/roles/manage-roles-list/manage-roles-list.component.html
index 7d904e7..01654af 100644
--- a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/security/roles/manage-roles-list/manage-roles-list.component.html
+++ b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/security/roles/manage-roles-list/manage-roles-list.component.html
@@ -18,37 +18,51 @@
 
 <app-paginated-entities [service]="service" pageSize="10" [(sortField)]="sortField" [(sortOrder)]="sortOrder"
                         #parent>
+    <ng-container *ngIf="parent.items$ |async as roleItemLoader" >
+        <ng-template [ngIf]="roleItemLoader.loading" #spinner let-modal>
+            <div class="fixed-top d-flex justify-content-center mt-5 pt-5">
+            <div class="spinner-border text-info mt-5" role="status">
+                <span class="sr-only">Loading...</span>
+            </div>
+            </div>
+        </ng-template>
+    </ng-container>
+    <ng-container *ngIf="parent.items$ |stripLoading|async as roleItem" >
+            <table class="table table-striped table-bordered">
+                <thead class="thead-light">
+                <tr sorted [sortFieldEmitter]="parent.sortFieldChange" [sortOrderEmitter]="parent.sortOrderChange"
+                    [toggleObserver]="parent">
+                    <app-th-sorted [fieldArray]="['id']" contentText="roles.attributes.id"></app-th-sorted>
+                    <app-th-sorted [fieldArray]="['name']" contentText="roles.attributes.name"></app-th-sorted>
+                    <app-th-sorted [fieldArray]="['description']"
+                                   contentText="roles.attributes.description"></app-th-sorted>
+                    <app-th-sorted [fieldArray]="['assignable']"
+                                   contentText="roles.attributes.assignable"></app-th-sorted>
+                    <app-th-sorted [fieldArray]="['template_instance']"
+                                   contentText="roles.attributes.template_instance"></app-th-sorted>
+                    <app-th-sorted [fieldArray]="['resource']" contentText="roles.attributes.resource"></app-th-sorted>
+                    <th>{{'headers.action' |translate}}</th>
+                </tr>
+                </thead>
+                <tbody>
+                <tr *ngFor="let role  of roleItem.data" [ngClass]="(role.permanent)?'table-secondary':''">
+                    <td><span data-toggle="tooltip" placement="left" ngbTooltip="{{role.id}}">{{role.id}}</span></td>
+                    <td>{{role.name}}</td>
+                    <td>{{role.description}}</td>
+                    <td><span class="far" [attr.aria-valuetext]="role.assignable"
+                              [ngClass]="role.assignable?'fa-check-circle':'fa-circle'"></span></td>
+                    <td><span class="far" [attr.aria-valuetext]="role.template_instance"
+                              [ngClass]="role.template_instance?'fa-check-circle':'fa-circle'"></span></td>
+                    <td>{{role.resource}}</td>
+                    <td>
+                        <a [routerLink]="['..','edit', role.id]" [queryParams]="{editmode:true}"
+                           [attr.title]="'roles.edit.head' |translate"><span class="fas fa-edit"></span></a>
 
-    <table class="table table-striped table-bordered">
-        <thead class="thead-light">
-        <tr sorted [sortFieldEmitter]="parent.sortFieldChange" [sortOrderEmitter]="parent.sortOrderChange"
-            [toggleObserver]="parent">
-            <app-th-sorted [fieldArray]="['id']" contentText="roles.attributes.id"></app-th-sorted>
-            <app-th-sorted [fieldArray]="['name']" contentText="roles.attributes.name" ></app-th-sorted>
-            <app-th-sorted [fieldArray]="['description']" contentText="roles.attributes.description" ></app-th-sorted>
-            <app-th-sorted [fieldArray]="['assignable']" contentText="roles.attributes.assignable"></app-th-sorted>
-            <app-th-sorted [fieldArray]="['template_instance']" contentText="roles.attributes.template_instance" ></app-th-sorted>
-            <app-th-sorted [fieldArray]="['resource']" contentText="roles.attributes.resource" ></app-th-sorted>
-            <th>Action</th>
-        </tr>
-        </thead>
-        <tbody>
-        <tr *ngFor="let role  of parent.items$ | async" [ngClass]="(role.permanent)?'table-secondary':''">
-            <td><span data-toggle="tooltip" placement="left" ngbTooltip="{{role.id}}">{{role.id}}</span></td>
-            <td>{{role.name}}</td>
-            <td>{{role.description}}</td>
-            <td><span class="far" [attr.aria-valuetext]="role.assignable"
-                      [ngClass]="role.assignable?'fa-check-circle':'fa-circle'"></span></td>
-            <td><span class="far" [attr.aria-valuetext]="role.template_instance"
-                      [ngClass]="role.template_instance?'fa-check-circle':'fa-circle'"></span></td>
-            <td>{{role.resource}}</td>
-            <td>
-                <a  [routerLink]="['..','edit', role.id]" [queryParams]="{editmode:true}" [attr.title]="'roles.edit.head' |translate"><span class="fas fa-edit"></span></a>
-
-            </td>
-        </tr>
-        </tbody>
-    </table>
+                    </td>
+                </tr>
+                </tbody>
+            </table>
+    </ng-container>
 
 </app-paginated-entities>
 
diff --git a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/security/roles/manage-roles-list/manage-roles-list.component.ts b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/security/roles/manage-roles-list/manage-roles-list.component.ts
index b49f64d..1344b11 100644
--- a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/security/roles/manage-roles-list/manage-roles-list.component.ts
+++ b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/security/roles/manage-roles-list/manage-roles-list.component.ts
@@ -16,16 +16,15 @@
  * under the License.
  */
 
-import {Component, OnInit} from '@angular/core';
+import {Component, OnInit, TemplateRef, ViewChild} from '@angular/core';
 import {TranslateService} from "@ngx-translate/core";
-import {UserService} from "@app/services/user.service";
-import {EntityService} from "@app/model/entity-service";
 import {Role} from "@app/model/role";
 import {Observable} from "rxjs";
 import {PagedResult} from "@app/model/paged-result";
-import {UserInfo} from "@app/model/user-info";
 import {RoleService} from "@app/services/role.service";
 import {SortedTableComponent} from "@app/modules/shared/sorted-table-component";
+import {delay} from "rxjs/operators";
+import {NgbModal} from "@ng-bootstrap/ng-bootstrap";
 
 @Component({
   selector: 'app-manage-roles-list',
@@ -34,7 +33,10 @@ import {SortedTableComponent} from "@app/modules/shared/sorted-table-component";
 })
 export class ManageRolesListComponent extends SortedTableComponent<Role> implements OnInit {
 
-  constructor(translator: TranslateService, roleService : RoleService) {
+  @ViewChild('content') public spinnerTemplate: TemplateRef<any>;
+
+
+  constructor(translator: TranslateService, roleService : RoleService, private ngbModal:NgbModal) {
     super(translator, function (searchTerm: string, offset: number, limit: number, orderBy: string[], order: string): Observable<PagedResult<Role>> {
       console.log("Retrieving data " + searchTerm + "," + offset + "," + limit + "," + orderBy + "," + order);
       return roleService.query(searchTerm, offset, limit, orderBy, order);
@@ -45,4 +47,6 @@ export class ManageRolesListComponent extends SortedTableComponent<Role> impleme
   }
 
 
+
+
 }
diff --git a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/security/users/manage-users-list/manage-users-list.component.html b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/security/users/manage-users-list/manage-users-list.component.html
index 0731487..d87fc57 100644
--- a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/security/users/manage-users-list/manage-users-list.component.html
+++ b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/security/users/manage-users-list/manage-users-list.component.html
@@ -19,50 +19,76 @@
 <app-paginated-entities [service]="service" pageSize="10" [(sortField)]="sortField" [(sortOrder)]="sortOrder"
                         #parent>
 
-    <table class="table table-striped table-bordered">
-        <thead class="thead-light">
-        <tr sorted [sortFieldEmitter]="parent.sortFieldChange" [sortOrderEmitter]="parent.sortOrderChange"
-            [toggleObserver]="parent">
-            <app-th-sorted [fieldArray]="['user_id']" contentText="users.attributes.user_id"></app-th-sorted>
-            <app-th-sorted [fieldArray]="['full_name']" contentText="users.attributes.full_name" ></app-th-sorted>
-            <app-th-sorted [fieldArray]="['email']" contentText="users.attributes.email" ></app-th-sorted>
-            <app-th-sorted [fieldArray]="['validated','user_id']">
+    <ng-container *ngIf="parent.items$ |async as roleItemLoader">
+        <ng-template [ngIf]="roleItemLoader.loading">
+            <div class="spinner-border text-primary" role="status">
+                <span class="sr-only">Loading...</span>
+            </div>
+        </ng-template>
+    </ng-container>
+    <ng-container *ngIf="parent.items$ |stripLoading|async as roleItem">
+
+            <table class="table table-striped table-bordered">
+                <thead class="thead-light">
+                <tr sorted [sortFieldEmitter]="parent.sortFieldChange" [sortOrderEmitter]="parent.sortOrderChange"
+                    [toggleObserver]="parent">
+                    <app-th-sorted [fieldArray]="['user_id']" contentText="users.attributes.user_id"></app-th-sorted>
+                    <app-th-sorted [fieldArray]="['full_name']"
+                                   contentText="users.attributes.full_name"></app-th-sorted>
+                    <app-th-sorted [fieldArray]="['email']" contentText="users.attributes.email"></app-th-sorted>
+                    <app-th-sorted [fieldArray]="['validated','user_id']">
             <span class="fas fa-check" placement="top"
                   [ngbTooltip]="heads.validated" [attr.aria-label]="heads.validated"></span>
-            </app-th-sorted>
-            <app-th-sorted [fieldArray]="['locked','user_id']"><span class="fas fa-lock" placement="top"
-                                  [ngbTooltip]="heads.locked" [attr.aria-label]="heads.locked"></span></app-th-sorted>
-            <app-th-sorted [fieldArray]="['password_change_required','user_id']"><span class="fa fa-chevron-circle-right" placement="top"
-                                  [ngbTooltip]="heads.password_change_required" [attr.aria-label]="heads.password_change_required"></span>
-            </app-th-sorted>
-            <app-th-sorted [fieldArray]="['last_login']" contentText="users.attributes.last_login"></app-th-sorted>
-            <app-th-sorted [fieldArray]="['created']" contentText="users.attributes.created" ></app-th-sorted>
-            <app-th-sorted [fieldArray]="['last_password_change']" contentText="users.attributes.last_password_change"></app-th-sorted>
-            <th>Action</th>
-        </tr>
-        </thead>
-        <tbody>
-        <tr *ngFor="let user  of parent.items$ | async" [ngClass]="(user.permanent||user.readOnly)?'table-secondary':''">
-            <td><span data-toggle="tooltip" placement="left" ngbTooltip="{{user.id}}">{{user.user_id}}</span></td>
-            <td>{{user.full_name}}</td>
-            <td>{{user.email}}</td>
-            <td><span class="far" [attr.aria-valuetext]="user.validated"
-                      [ngClass]="user.validated?'fa-check-circle':'fa-circle'"></span></td>
-            <td><span class="far" [attr.aria-valuetext]="user.locked"
-                      [ngClass]="user.locked?'fa-check-circle':'fa-circle'"></span></td>
-            <td><span class="far" [attr.aria-valuetext]="user.password_change_required"
-                      [ngClass]="user.password_change_required?'fa-check-circle':'fa-circle'"></span></td>
-            <td>{{user.timestamp_last_login | date:'yyyy-MM-ddTHH:mm:ss'}}</td>
-            <td>{{user.timestamp_account_creation | date : 'yyyy-MM-ddTHH:mm:ss'}}</td>
-            <td>{{user.timestamp_last_password_change| date : 'yyyy-MM-ddTHH:mm:ss'}}</td>
-            <td><ng-container *ngIf="!user.permanent">
-                <a  [routerLink]="['..','edit', user.user_id]" [queryParams]="{editmode:true}" [attr.title]="'users.edit.head' |translate"><span class="fas fa-edit"></span></a>
-                &nbsp;&nbsp;<a [routerLink]="['..','delete',user.user_id]" [attr.title]="'users.delete.head'|translate"><span class="fas fa-user-minus"></span></a>
-                &nbsp;&nbsp;<a [routerLink]="['..','roles',user.user_id]" [attr.title]="'users.roles.head'|translate"><span class="fas fa-user-tag" ></span></a>
-            </ng-container>
-            </td>
-        </tr>
-        </tbody>
-    </table>
+                    </app-th-sorted>
+                    <app-th-sorted [fieldArray]="['locked','user_id']"><span class="fas fa-lock" placement="top"
+                                                                             [ngbTooltip]="heads.locked"
+                                                                             [attr.aria-label]="heads.locked"></span>
+                    </app-th-sorted>
+                    <app-th-sorted [fieldArray]="['password_change_required','user_id']"><span
+                            class="fa fa-chevron-circle-right" placement="top"
+                            [ngbTooltip]="heads.password_change_required"
+                            [attr.aria-label]="heads.password_change_required"></span>
+                    </app-th-sorted>
+                    <app-th-sorted [fieldArray]="['last_login']"
+                                   contentText="users.attributes.last_login"></app-th-sorted>
+                    <app-th-sorted [fieldArray]="['created']" contentText="users.attributes.created"></app-th-sorted>
+                    <app-th-sorted [fieldArray]="['last_password_change']"
+                                   contentText="users.attributes.last_password_change"></app-th-sorted>
+                    <th>Action</th>
+                </tr>
+                </thead>
+                <tbody>
+                <tr *ngFor="let user  of roleItem.data"
+                    [ngClass]="(user.permanent||user.readOnly)?'table-secondary':''">
+                    <td><span data-toggle="tooltip" placement="left" ngbTooltip="{{user.id}}">{{user.user_id}}</span>
+                    </td>
+                    <td>{{user.full_name}}</td>
+                    <td>{{user.email}}</td>
+                    <td><span class="far" [attr.aria-valuetext]="user.validated"
+                              [ngClass]="user.validated?'fa-check-circle':'fa-circle'"></span></td>
+                    <td><span class="far" [attr.aria-valuetext]="user.locked"
+                              [ngClass]="user.locked?'fa-check-circle':'fa-circle'"></span></td>
+                    <td><span class="far" [attr.aria-valuetext]="user.password_change_required"
+                              [ngClass]="user.password_change_required?'fa-check-circle':'fa-circle'"></span></td>
+                    <td>{{user.timestamp_last_login | date:'yyyy-MM-ddTHH:mm:ss'}}</td>
+                    <td>{{user.timestamp_account_creation | date : 'yyyy-MM-ddTHH:mm:ss'}}</td>
+                    <td>{{user.timestamp_last_password_change| date : 'yyyy-MM-ddTHH:mm:ss'}}</td>
+                    <td>
+                        <ng-container *ngIf="!user.permanent">
+                            <a [routerLink]="['..','edit', user.user_id]" [queryParams]="{editmode:true}"
+                               [attr.title]="'users.edit.head' |translate"><span class="fas fa-edit"></span></a>
+                            &nbsp;&nbsp;<a [routerLink]="['..','delete',user.user_id]"
+                                           [attr.title]="'users.delete.head'|translate"><span
+                                class="fas fa-user-minus"></span></a>
+                            &nbsp;&nbsp;<a [routerLink]="['..','roles',user.user_id]"
+                                           [attr.title]="'users.roles.head'|translate"><span
+                                class="fas fa-user-tag"></span></a>
+                        </ng-container>
+                    </td>
+                </tr>
+                </tbody>
+            </table>
+    </ng-container>
+
 
 </app-paginated-entities>
diff --git a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/security/users/manage-users-list/manage-users-list.component.ts b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/security/users/manage-users-list/manage-users-list.component.ts
index 36b3627..b22aeea 100644
--- a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/security/users/manage-users-list/manage-users-list.component.ts
+++ b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/security/users/manage-users-list/manage-users-list.component.ts
@@ -16,13 +16,13 @@
  * under the License.
  */
 
-import {Component, OnInit, Input, OnDestroy} from '@angular/core';
+import {Component, Input, OnInit} from '@angular/core';
 import {TranslateService} from "@ngx-translate/core";
-import {UserService} from "../../../../services/user.service";
-import {UserInfo} from "../../../../model/user-info";
-import {EntityService} from "../../../../model/entity-service";
-import {Observable, of} from "rxjs";
-import {PagedResult} from "../../../../model/paged-result";
+import {UserInfo} from "@app/model/user-info";
+import {EntityService} from "@app/model/entity-service";
+import {Observable} from "rxjs";
+import {PagedResult} from "@app/model/paged-result";
+import {UserService} from '@app/services/user.service';
 
 @Component({
   selector: 'app-manage-users-list',
diff --git a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/shared/about/about.component.html b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/shared/about/about.component.html
index 2c8e6de..806221f 100644
--- a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/shared/about/about.component.html
+++ b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/shared/about/about.component.html
@@ -7,13 +7,12 @@
   ~ "License"); you may not use this file except in compliance
   ~ with the License.  You may obtain a copy of the License at
   ~
-  ~   http://www.apache.org/licenses/LICENSE-2.0
-  ~
+  ~ http://www.apache.org/licenses/LICENSE-2.0
   ~ Unless required by applicable law or agreed to in writing,
   ~ software distributed under the License is distributed on an
   ~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
   ~ KIND, either express or implied.  See the License for the
   ~ specific language governing permissions and limitations
   ~ under the License.
--->
+  -->
 <p>Apache Archiva Web Console</p>
diff --git a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/shared/about/about.component.scss b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/shared/about/about.component.scss
index 042f3ce..a0ef995 100644
--- a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/shared/about/about.component.scss
+++ b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/shared/about/about.component.scss
@@ -1,4 +1,4 @@
-/*
+/*!
  * Licensed to the Apache Software Foundation (ASF) under one
  * or more contributor license agreements.  See the NOTICE file
  * distributed with this work for additional information
@@ -7,8 +7,7 @@
  * "License"); you may not use this file except in compliance
  * with the License.  You may obtain a copy of the License at
  *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
+ * http://www.apache.org/licenses/LICENSE-2.0
  * Unless required by applicable law or agreed to in writing,
  * software distributed under the License is distributed on an
  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
diff --git a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/shared/about/about.component.spec.ts b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/shared/about/about.component.spec.ts
index ddeaebb..7c45c53 100644
--- a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/shared/about/about.component.spec.ts
+++ b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/shared/about/about.component.spec.ts
@@ -7,8 +7,7 @@
  * "License"); you may not use this file except in compliance
  * with the License.  You may obtain a copy of the License at
  *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
+ * http://www.apache.org/licenses/LICENSE-2.0
  * Unless required by applicable law or agreed to in writing,
  * software distributed under the License is distributed on an
  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
diff --git a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/shared/about/about.component.ts b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/shared/about/about.component.ts
index 1e95c0b..eb1b353 100644
--- a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/shared/about/about.component.ts
+++ b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/shared/about/about.component.ts
@@ -7,8 +7,7 @@
  * "License"); you may not use this file except in compliance
  * with the License.  You may obtain a copy of the License at
  *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
+ * http://www.apache.org/licenses/LICENSE-2.0
  * Unless required by applicable law or agreed to in writing,
  * software distributed under the License is distributed on an
  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
diff --git a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/shared/contact/contact.component.html b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/shared/contact/contact.component.html
index 8ea7322..d52079a 100644
--- a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/shared/contact/contact.component.html
+++ b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/shared/contact/contact.component.html
@@ -7,13 +7,12 @@
   ~ "License"); you may not use this file except in compliance
   ~ with the License.  You may obtain a copy of the License at
   ~
-  ~   http://www.apache.org/licenses/LICENSE-2.0
-  ~
+  ~ http://www.apache.org/licenses/LICENSE-2.0
   ~ Unless required by applicable law or agreed to in writing,
   ~ software distributed under the License is distributed on an
   ~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
   ~ KIND, either express or implied.  See the License for the
   ~ specific language governing permissions and limitations
   ~ under the License.
--->
+  -->
 <p><a href="mailto:users@archiva.apache.org">User Mailing List</a></p>
diff --git a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/shared/contact/contact.component.scss b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/shared/contact/contact.component.scss
index 042f3ce..a0ef995 100644
--- a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/shared/contact/contact.component.scss
+++ b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/shared/contact/contact.component.scss
@@ -1,4 +1,4 @@
-/*
+/*!
  * Licensed to the Apache Software Foundation (ASF) under one
  * or more contributor license agreements.  See the NOTICE file
  * distributed with this work for additional information
@@ -7,8 +7,7 @@
  * "License"); you may not use this file except in compliance
  * with the License.  You may obtain a copy of the License at
  *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
+ * http://www.apache.org/licenses/LICENSE-2.0
  * Unless required by applicable law or agreed to in writing,
  * software distributed under the License is distributed on an
  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
diff --git a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/shared/contact/contact.component.spec.ts b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/shared/contact/contact.component.spec.ts
index f475933..7f7014f 100644
--- a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/shared/contact/contact.component.spec.ts
+++ b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/shared/contact/contact.component.spec.ts
@@ -7,8 +7,7 @@
  * "License"); you may not use this file except in compliance
  * with the License.  You may obtain a copy of the License at
  *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
+ * http://www.apache.org/licenses/LICENSE-2.0
  * Unless required by applicable law or agreed to in writing,
  * software distributed under the License is distributed on an
  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
diff --git a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/shared/contact/contact.component.ts b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/shared/contact/contact.component.ts
index 7435690..b2d03f6 100644
--- a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/shared/contact/contact.component.ts
+++ b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/shared/contact/contact.component.ts
@@ -7,8 +7,7 @@
  * "License"); you may not use this file except in compliance
  * with the License.  You may obtain a copy of the License at
  *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
+ * http://www.apache.org/licenses/LICENSE-2.0
  * Unless required by applicable law or agreed to in writing,
  * software distributed under the License is distributed on an
  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
diff --git a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/shared/home/home.component.html b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/shared/home/home.component.html
index dfac255..bea4271 100644
--- a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/shared/home/home.component.html
+++ b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/shared/home/home.component.html
@@ -7,15 +7,14 @@
   ~ "License"); you may not use this file except in compliance
   ~ with the License.  You may obtain a copy of the License at
   ~
-  ~   http://www.apache.org/licenses/LICENSE-2.0
-  ~
+  ~ http://www.apache.org/licenses/LICENSE-2.0
   ~ Unless required by applicable law or agreed to in writing,
   ~ software distributed under the License is distributed on an
   ~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
   ~ KIND, either express or implied.  See the License for the
   ~ specific language governing permissions and limitations
   ~ under the License.
--->
+  -->
 <div class="row">
   <div class="col-2">
     <app-sidemenu></app-sidemenu>
diff --git a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/shared/home/home.component.scss b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/shared/home/home.component.scss
index 042f3ce..a0ef995 100644
--- a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/shared/home/home.component.scss
+++ b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/shared/home/home.component.scss
@@ -1,4 +1,4 @@
-/*
+/*!
  * Licensed to the Apache Software Foundation (ASF) under one
  * or more contributor license agreements.  See the NOTICE file
  * distributed with this work for additional information
@@ -7,8 +7,7 @@
  * "License"); you may not use this file except in compliance
  * with the License.  You may obtain a copy of the License at
  *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
+ * http://www.apache.org/licenses/LICENSE-2.0
  * Unless required by applicable law or agreed to in writing,
  * software distributed under the License is distributed on an
  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
diff --git a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/shared/home/home.component.spec.ts b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/shared/home/home.component.spec.ts
index f7c82e5..2f7e651 100644
--- a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/shared/home/home.component.spec.ts
+++ b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/shared/home/home.component.spec.ts
@@ -7,8 +7,7 @@
  * "License"); you may not use this file except in compliance
  * with the License.  You may obtain a copy of the License at
  *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
+ * http://www.apache.org/licenses/LICENSE-2.0
  * Unless required by applicable law or agreed to in writing,
  * software distributed under the License is distributed on an
  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
diff --git a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/shared/home/home.component.ts b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/shared/home/home.component.ts
index c5b55f3..2f44afe 100644
--- a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/shared/home/home.component.ts
+++ b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/shared/home/home.component.ts
@@ -7,8 +7,7 @@
  * "License"); you may not use this file except in compliance
  * with the License.  You may obtain a copy of the License at
  *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
+ * http://www.apache.org/licenses/LICENSE-2.0
  * Unless required by applicable law or agreed to in writing,
  * software distributed under the License is distributed on an
  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
diff --git a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/shared/login/login.component.html b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/shared/login/login.component.html
index 04fad9f..c30dc73 100644
--- a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/shared/login/login.component.html
+++ b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/shared/login/login.component.html
@@ -7,15 +7,14 @@
   ~ "License"); you may not use this file except in compliance
   ~ with the License.  You may obtain a copy of the License at
   ~
-  ~   http://www.apache.org/licenses/LICENSE-2.0
-  ~
+  ~ http://www.apache.org/licenses/LICENSE-2.0
   ~ Unless required by applicable law or agreed to in writing,
   ~ software distributed under the License is distributed on an
   ~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
   ~ KIND, either express or implied.  See the License for the
   ~ specific language governing permissions and limitations
   ~ under the License.
--->
+  -->
 
 <!-- Modal -->
 <div class="modal fade" id="loginModal" tabindex="-1" role="dialog" aria-labelledby="loginModal" aria-hidden="true">
diff --git a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/shared/login/login.component.scss b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/shared/login/login.component.scss
index 7220975..16e476a 100644
--- a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/shared/login/login.component.scss
+++ b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/shared/login/login.component.scss
@@ -1,4 +1,4 @@
-/*
+/*!
  * Licensed to the Apache Software Foundation (ASF) under one
  * or more contributor license agreements.  See the NOTICE file
  * distributed with this work for additional information
@@ -7,8 +7,7 @@
  * "License"); you may not use this file except in compliance
  * with the License.  You may obtain a copy of the License at
  *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
+ * http://www.apache.org/licenses/LICENSE-2.0
  * Unless required by applicable law or agreed to in writing,
  * software distributed under the License is distributed on an
  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
diff --git a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/shared/login/login.component.spec.ts b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/shared/login/login.component.spec.ts
index 307e95b..2f92cf4 100644
--- a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/shared/login/login.component.spec.ts
+++ b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/shared/login/login.component.spec.ts
@@ -7,8 +7,7 @@
  * "License"); you may not use this file except in compliance
  * with the License.  You may obtain a copy of the License at
  *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
+ * http://www.apache.org/licenses/LICENSE-2.0
  * Unless required by applicable law or agreed to in writing,
  * software distributed under the License is distributed on an
  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
diff --git a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/shared/login/login.component.ts b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/shared/login/login.component.ts
index 0a12e1a..b67a97f 100644
--- a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/shared/login/login.component.ts
+++ b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/shared/login/login.component.ts
@@ -7,8 +7,7 @@
  * "License"); you may not use this file except in compliance
  * with the License.  You may obtain a copy of the License at
  *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
+ * http://www.apache.org/licenses/LICENSE-2.0
  * Unless required by applicable law or agreed to in writing,
  * software distributed under the License is distributed on an
  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
diff --git a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/shared/home/home.component.scss b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/shared/model/loading-value.spec.ts
similarity index 77%
copy from archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/shared/home/home.component.scss
copy to archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/shared/model/loading-value.spec.ts
index 042f3ce..0ad6490 100644
--- a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/shared/home/home.component.scss
+++ b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/shared/model/loading-value.spec.ts
@@ -7,8 +7,7 @@
  * "License"); you may not use this file except in compliance
  * with the License.  You may obtain a copy of the License at
  *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
+ * http://www.apache.org/licenses/LICENSE-2.0
  * Unless required by applicable law or agreed to in writing,
  * software distributed under the License is distributed on an
  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
@@ -16,3 +15,11 @@
  * specific language governing permissions and limitations
  * under the License.
  */
+
+import { LoadingValue } from './loading-value';
+
+describe('LoadingValue', () => {
+  it('should create an instance', () => {
+    expect(new LoadingValue()).toBeTruthy();
+  });
+});
diff --git a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/shared/model/loading-value.ts b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/shared/model/loading-value.ts
new file mode 100644
index 0000000..c66818e
--- /dev/null
+++ b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/shared/model/loading-value.ts
@@ -0,0 +1,62 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+export class LoadingValue<T> {
+    loading: boolean;
+    value: T;
+    error: any;
+
+    public hasError() : boolean {
+        return this.error != null;
+    }
+
+    public hasValue() : boolean {
+        return this.value != null;
+    }
+
+    static start<T>() : LoadingValue<T> {
+        let lv = new LoadingValue<T>();
+        lv.loading=true;
+        return lv;
+    }
+
+    static finish<T>(value: T) : LoadingValue<T> {
+        let lv = new LoadingValue<T>();
+        lv.loading=false;
+        lv.value = value;
+        return lv;
+    }
+
+    static of<T>(type: string, value: T = null) : LoadingValue<T>{
+        let lv = new LoadingValue<T>();
+        if (type=='start') {
+            lv.loading=true;
+        } else if (type=='finish') {
+            lv.loading=false;
+            lv.value=value;
+        }
+        return lv;
+    }
+
+    static error<T>(error:any) : LoadingValue<T> {
+        let lv = new LoadingValue<T>();
+        lv.loading=false;
+        lv.error=error;
+        return lv;
+    }
+}
diff --git a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/shared/home/home.component.scss b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/shared/model/page-query.spec.ts
similarity index 77%
copy from archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/shared/home/home.component.scss
copy to archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/shared/model/page-query.spec.ts
index 042f3ce..13b03a1 100644
--- a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/shared/home/home.component.scss
+++ b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/shared/model/page-query.spec.ts
@@ -7,8 +7,7 @@
  * "License"); you may not use this file except in compliance
  * with the License.  You may obtain a copy of the License at
  *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
+ * http://www.apache.org/licenses/LICENSE-2.0
  * Unless required by applicable law or agreed to in writing,
  * software distributed under the License is distributed on an
  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
@@ -16,3 +15,11 @@
  * specific language governing permissions and limitations
  * under the License.
  */
+
+import { PageQuery } from './page-query';
+
+describe('PageQuery', () => {
+  it('should create an instance', () => {
+    expect(new PageQuery()).toBeTruthy();
+  });
+});
diff --git a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/shared/home/home.component.scss b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/shared/model/page-query.ts
similarity index 84%
copy from archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/shared/home/home.component.scss
copy to archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/shared/model/page-query.ts
index 042f3ce..00b543c 100644
--- a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/shared/home/home.component.scss
+++ b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/shared/model/page-query.ts
@@ -7,8 +7,7 @@
  * "License"); you may not use this file except in compliance
  * with the License.  You may obtain a copy of the License at
  *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
+ * http://www.apache.org/licenses/LICENSE-2.0
  * Unless required by applicable law or agreed to in writing,
  * software distributed under the License is distributed on an
  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
@@ -16,3 +15,9 @@
  * specific language governing permissions and limitations
  * under the License.
  */
+
+export class PageQuery {
+
+    constructor(public search: string, public page: number) {
+    }
+}
diff --git a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/shared/not-found/not-found.component.html b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/shared/not-found/not-found.component.html
index cb9cb9f..27b5098 100644
--- a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/shared/not-found/not-found.component.html
+++ b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/shared/not-found/not-found.component.html
@@ -7,13 +7,12 @@
   ~ "License"); you may not use this file except in compliance
   ~ with the License.  You may obtain a copy of the License at
   ~
-  ~   http://www.apache.org/licenses/LICENSE-2.0
-  ~
+  ~ http://www.apache.org/licenses/LICENSE-2.0
   ~ Unless required by applicable law or agreed to in writing,
   ~ software distributed under the License is distributed on an
   ~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
   ~ KIND, either express or implied.  See the License for the
   ~ specific language governing permissions and limitations
   ~ under the License.
--->
+  -->
 <p>URL not found!</p>
diff --git a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/shared/not-found/not-found.component.scss b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/shared/not-found/not-found.component.scss
index 042f3ce..a0ef995 100644
--- a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/shared/not-found/not-found.component.scss
+++ b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/shared/not-found/not-found.component.scss
@@ -1,4 +1,4 @@
-/*
+/*!
  * Licensed to the Apache Software Foundation (ASF) under one
  * or more contributor license agreements.  See the NOTICE file
  * distributed with this work for additional information
@@ -7,8 +7,7 @@
  * "License"); you may not use this file except in compliance
  * with the License.  You may obtain a copy of the License at
  *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
+ * http://www.apache.org/licenses/LICENSE-2.0
  * Unless required by applicable law or agreed to in writing,
  * software distributed under the License is distributed on an
  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
diff --git a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/shared/not-found/not-found.component.spec.ts b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/shared/not-found/not-found.component.spec.ts
index 64d1ce0..78ca07b 100644
--- a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/shared/not-found/not-found.component.spec.ts
+++ b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/shared/not-found/not-found.component.spec.ts
@@ -7,8 +7,7 @@
  * "License"); you may not use this file except in compliance
  * with the License.  You may obtain a copy of the License at
  *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
+ * http://www.apache.org/licenses/LICENSE-2.0
  * Unless required by applicable law or agreed to in writing,
  * software distributed under the License is distributed on an
  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
diff --git a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/shared/not-found/not-found.component.ts b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/shared/not-found/not-found.component.ts
index cef74cc..fefb55e 100644
--- a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/shared/not-found/not-found.component.ts
+++ b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/shared/not-found/not-found.component.ts
@@ -7,8 +7,7 @@
  * "License"); you may not use this file except in compliance
  * with the License.  You may obtain a copy of the License at
  *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
+ * http://www.apache.org/licenses/LICENSE-2.0
  * Unless required by applicable law or agreed to in writing,
  * software distributed under the License is distributed on an
  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
diff --git a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/shared/paginated-entities/paginated-entities.component.ts b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/shared/paginated-entities/paginated-entities.component.ts
index ef9582f..cc62ac6 100644
--- a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/shared/paginated-entities/paginated-entities.component.ts
+++ b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/shared/paginated-entities/paginated-entities.component.ts
@@ -17,10 +17,25 @@
  */
 
 import {AfterViewInit, Component, EventEmitter, Input, OnInit, Output} from '@angular/core';
-import {merge, Observable, Subject} from "rxjs";
-import {debounceTime, distinctUntilChanged, map, mergeMap, pluck, share, startWith} from "rxjs/operators";
+import {concat, merge, Observable, of, pipe, Subject} from "rxjs";
+import {
+    concatAll,
+    debounceTime,
+    delay,
+    distinctUntilChanged,
+    filter,
+    map,
+    mergeMap,
+    pluck,
+    share,
+    startWith,
+    switchMap
+} from "rxjs/operators";
 import {EntityService} from "../../../model/entity-service";
 import {FieldToggle} from "../../../model/field-toggle";
+import {PageQuery} from "@app/modules/shared/model/page-query";
+import { LoadingValue } from '../shared.module';
+import {PagedResult} from "@app/model/paged-result";
 
 
 /**
@@ -107,7 +122,7 @@ export class PaginatedEntitiesComponent<T> implements OnInit, FieldToggle, After
     /**
      * The entity items retrieved from the service
      */
-    items$: Observable<T[]>;
+    items$: Observable<LoadingValue<PagedResult<T>>>;
 
     private pageStream: Subject<number> = new Subject<number>();
     private searchTermStream: Subject<string> = new Subject<string>();
@@ -118,22 +133,26 @@ export class PaginatedEntitiesComponent<T> implements OnInit, FieldToggle, After
     ngOnInit(): void {
         // We combine the sources for the page and the search input field to a observable 'source'
         const pageSource = this.pageStream.pipe(map(pageNumber => {
-            return {search: this.searchTerm, page: pageNumber}
+            return new PageQuery(this.searchTerm, pageNumber);
         }));
         const searchSource = this.searchTermStream.pipe(
             debounceTime(1000),
             distinctUntilChanged(),
             map(searchTerm => {
                 this.searchTerm = searchTerm;
-                return {search: searchTerm, page: 1}
+                return new PageQuery(searchTerm, 1)
             }));
         const source = merge(pageSource, searchSource).pipe(
-            startWith({search: this.searchTerm, page: this.page}),
-            mergeMap((params: { search: string, page: number }) => {
-                return this.service(params.search, (params.page - 1) * this.pageSize, this.pageSize, this.sortField, this.sortOrder);
-            }), share());
-        this.total$ = source.pipe(pluck('pagination', 'total_count'));
-        this.items$ = source.pipe(pluck('data'));
+            startWith(new PageQuery(this.searchTerm, this.page)),
+            switchMap((params: PageQuery) =>
+                concat(
+                    of(LoadingValue.start<PagedResult<T>>()),
+                    this.service(params.search, (params.page - 1) * this.pageSize, this.pageSize, this.sortField, this.sortOrder)
+                        .pipe(map(pagedResult=>LoadingValue.finish<PagedResult<T>>(pagedResult)))
+                )
+            ), share());
+        this.total$ = source.pipe(filter(val=>val.hasValue()),map(val=>val.value),pluck('pagination', 'total_count'));
+        this.items$ = source;
     }
 
     search(terms: string) {
diff --git a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/shared/shared.module.ts b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/shared/shared.module.ts
index 27978a3..0420576 100644
--- a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/shared/shared.module.ts
+++ b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/shared/shared.module.ts
@@ -21,20 +21,25 @@ import {CommonModule} from '@angular/common';
 import {PaginatedEntitiesComponent} from "./paginated-entities/paginated-entities.component";
 import {SortedTableHeaderComponent} from "./sorted-table-header/sorted-table-header.component";
 import {SortedTableHeaderRowComponent} from "./sorted-table-header-row/sorted-table-header-row.component";
-import {NgbAccordionModule, NgbPaginationModule, NgbTooltipModule} from "@ng-bootstrap/ng-bootstrap";
+import {NgbAccordionModule, NgbModalModule, NgbPaginationModule, NgbTooltipModule} from "@ng-bootstrap/ng-bootstrap";
 import {TranslateCompiler, TranslateLoader, TranslateModule} from "@ngx-translate/core";
 import {TranslateMessageFormatCompiler} from "ngx-translate-messageformat-compiler";
 import {HttpClient} from "@angular/common/http";
 import {TranslateHttpLoader} from "@ngx-translate/http-loader";
 import {RouterModule} from "@angular/router";
-import {SortedTableComponent} from "@app/modules/shared/sorted-table-component";
+import { WithLoadingPipe } from './with-loading.pipe';
+import { StripLoadingPipe } from './strip-loading.pipe';
 
+export { LoadingValue } from './model/loading-value';
+export { PageQuery } from './model/page-query';
 
 @NgModule({
     declarations: [
         PaginatedEntitiesComponent,
         SortedTableHeaderComponent,
-        SortedTableHeaderRowComponent
+        SortedTableHeaderRowComponent,
+        WithLoadingPipe,
+        StripLoadingPipe
     ],
     exports: [
         CommonModule,
@@ -43,9 +48,12 @@ import {SortedTableComponent} from "@app/modules/shared/sorted-table-component";
         NgbPaginationModule,
         NgbTooltipModule,
         NgbAccordionModule,
+        NgbModalModule,
         PaginatedEntitiesComponent,
         SortedTableHeaderComponent,
-        SortedTableHeaderRowComponent
+        SortedTableHeaderRowComponent,
+        WithLoadingPipe,
+        StripLoadingPipe
     ],
     imports: [
         CommonModule,
diff --git a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/shared/sidemenu/sidemenu.component.html b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/shared/sidemenu/sidemenu.component.html
index e147479..cbded1e 100644
--- a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/shared/sidemenu/sidemenu.component.html
+++ b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/shared/sidemenu/sidemenu.component.html
@@ -7,15 +7,14 @@
   ~ "License"); you may not use this file except in compliance
   ~ with the License.  You may obtain a copy of the License at
   ~
-  ~   http://www.apache.org/licenses/LICENSE-2.0
-  ~
+  ~ http://www.apache.org/licenses/LICENSE-2.0
   ~ Unless required by applicable law or agreed to in writing,
   ~ software distributed under the License is distributed on an
   ~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
   ~ KIND, either express or implied.  See the License for the
   ~ specific language governing permissions and limitations
   ~ under the License.
--->
+  -->
 <nav class="nav flex-column nav-pills " role="tablist" aria-orientation="vertical">
 
     <div class="nav flex-column nav-pills" role="tablist" aria-orientation="vertical"
diff --git a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/shared/sidemenu/sidemenu.component.scss b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/shared/sidemenu/sidemenu.component.scss
index 042f3ce..a0ef995 100644
--- a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/shared/sidemenu/sidemenu.component.scss
+++ b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/shared/sidemenu/sidemenu.component.scss
@@ -1,4 +1,4 @@
-/*
+/*!
  * Licensed to the Apache Software Foundation (ASF) under one
  * or more contributor license agreements.  See the NOTICE file
  * distributed with this work for additional information
@@ -7,8 +7,7 @@
  * "License"); you may not use this file except in compliance
  * with the License.  You may obtain a copy of the License at
  *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
+ * http://www.apache.org/licenses/LICENSE-2.0
  * Unless required by applicable law or agreed to in writing,
  * software distributed under the License is distributed on an
  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
diff --git a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/shared/sidemenu/sidemenu.component.spec.ts b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/shared/sidemenu/sidemenu.component.spec.ts
index 7524c81..c74423b 100644
--- a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/shared/sidemenu/sidemenu.component.spec.ts
+++ b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/shared/sidemenu/sidemenu.component.spec.ts
@@ -7,8 +7,7 @@
  * "License"); you may not use this file except in compliance
  * with the License.  You may obtain a copy of the License at
  *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
+ * http://www.apache.org/licenses/LICENSE-2.0
  * Unless required by applicable law or agreed to in writing,
  * software distributed under the License is distributed on an
  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
diff --git a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/shared/sidemenu/sidemenu.component.ts b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/shared/sidemenu/sidemenu.component.ts
index c4fa16c..ad7b21c 100644
--- a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/shared/sidemenu/sidemenu.component.ts
+++ b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/shared/sidemenu/sidemenu.component.ts
@@ -7,8 +7,7 @@
  * "License"); you may not use this file except in compliance
  * with the License.  You may obtain a copy of the License at
  *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
+ * http://www.apache.org/licenses/LICENSE-2.0
  * Unless required by applicable law or agreed to in writing,
  * software distributed under the License is distributed on an
  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
diff --git a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/shared/sorted-table-component.ts b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/shared/sorted-table-component.ts
index 27423fa..67d2888 100644
--- a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/shared/sorted-table-component.ts
+++ b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/shared/sorted-table-component.ts
@@ -1,3 +1,21 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
 import {EntityService} from "@app/model/entity-service";
 import {TranslateService} from "@ngx-translate/core";
 
diff --git a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/shared/home/home.component.scss b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/shared/strip-loading.pipe.spec.ts
similarity index 74%
copy from archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/shared/home/home.component.scss
copy to archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/shared/strip-loading.pipe.spec.ts
index 042f3ce..8efe49f 100644
--- a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/shared/home/home.component.scss
+++ b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/shared/strip-loading.pipe.spec.ts
@@ -7,8 +7,7 @@
  * "License"); you may not use this file except in compliance
  * with the License.  You may obtain a copy of the License at
  *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
+ * http://www.apache.org/licenses/LICENSE-2.0
  * Unless required by applicable law or agreed to in writing,
  * software distributed under the License is distributed on an
  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
@@ -16,3 +15,12 @@
  * specific language governing permissions and limitations
  * under the License.
  */
+
+import { StripLoadingPipe } from './strip-loading.pipe';
+
+describe('StripLoadingPipe', () => {
+  it('create an instance', () => {
+    const pipe = new StripLoadingPipe();
+    expect(pipe).toBeTruthy();
+  });
+});
diff --git a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/model/role.ts b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/shared/strip-loading.pipe.ts
similarity index 57%
copy from archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/model/role.ts
copy to archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/shared/strip-loading.pipe.ts
index ac0b469..8a528ae 100644
--- a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/model/role.ts
+++ b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/shared/strip-loading.pipe.ts
@@ -16,31 +16,23 @@
  * under the License.
  */
 
-import {Permission} from "@app/model/permission";
+import { Pipe, PipeTransform } from '@angular/core';
+import {isObservable, of} from "rxjs";
+import {catchError, filter, map, startWith, tap} from "rxjs/operators";
+import {LoadingValue} from "@app/modules/shared/model/loading-value";
 
-export class Role {
-    id: string
-    name: string
-    description: string
-    assignable: boolean
-    permanent: boolean
-    child: boolean
-    assigned: boolean
-    template_instance: boolean
-    application_id:string
-    model_id:string
-    resource:string
+@Pipe({
+  name: 'stripLoading'
+})
+export class StripLoadingPipe implements PipeTransform {
 
-    child_role_ids: Array<string>
-    parent_role_ids: Array<string>
-    children: Array<Role>
-    parents: Array<Role>
-    permissions: Array<Permission>
-
-    // Web Internal attributes
-    enabled: boolean = true
-    level:number = -1
-    root_path: Array<string>
-    assigned_origin: boolean;
+  transform(val) {
+    return isObservable(val)
+        ? val.pipe(
+            filter(val => (val instanceof LoadingValue) && val.value),
+            map(( val : LoadingValue<any>) => val.value)
+        )
+        : val;
+  }
 
 }
diff --git a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/shared/home/home.component.scss b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/shared/with-loading.pipe.spec.ts
similarity index 74%
copy from archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/shared/home/home.component.scss
copy to archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/shared/with-loading.pipe.spec.ts
index 042f3ce..cf08291 100644
--- a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/shared/home/home.component.scss
+++ b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/shared/with-loading.pipe.spec.ts
@@ -7,8 +7,7 @@
  * "License"); you may not use this file except in compliance
  * with the License.  You may obtain a copy of the License at
  *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
+ * http://www.apache.org/licenses/LICENSE-2.0
  * Unless required by applicable law or agreed to in writing,
  * software distributed under the License is distributed on an
  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
@@ -16,3 +15,12 @@
  * specific language governing permissions and limitations
  * under the License.
  */
+
+import { WithLoadingPipe } from './with-loading.pipe';
+
+describe('WithLoadingPipe', () => {
+  it('create an instance', () => {
+    const pipe = new WithLoadingPipe();
+    expect(pipe).toBeTruthy();
+  });
+});
diff --git a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/model/role.ts b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/shared/with-loading.pipe.ts
similarity index 50%
copy from archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/model/role.ts
copy to archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/shared/with-loading.pipe.ts
index ac0b469..8de45ed 100644
--- a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/model/role.ts
+++ b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/shared/with-loading.pipe.ts
@@ -16,31 +16,28 @@
  * under the License.
  */
 
-import {Permission} from "@app/model/permission";
-
-export class Role {
-    id: string
-    name: string
-    description: string
-    assignable: boolean
-    permanent: boolean
-    child: boolean
-    assigned: boolean
-    template_instance: boolean
-    application_id:string
-    model_id:string
-    resource:string
-
-    child_role_ids: Array<string>
-    parent_role_ids: Array<string>
-    children: Array<Role>
-    parents: Array<Role>
-    permissions: Array<Permission>
-
-    // Web Internal attributes
-    enabled: boolean = true
-    level:number = -1
-    root_path: Array<string>
-    assigned_origin: boolean;
+import { Pipe, PipeTransform } from '@angular/core';
+import {concat, isObservable, of } from 'rxjs';
+import {catchError, map, startWith, tap } from 'rxjs/operators';
+import {LoadingValue} from "@app/modules/shared/shared.module";
 
+@Pipe({
+  name: 'withLoading'
+})
+export class WithLoadingPipe implements PipeTransform {
+  transform(val) {
+    return isObservable(val)
+        ? val.pipe(
+            map((value: any) => {
+                if(value instanceof LoadingValue) {
+                    return value as LoadingValue<any>;
+                } else {
+                    return LoadingValue.finish(value);
+                }
+            }),
+            startWith(LoadingValue.start()),
+            catchError(error => of(LoadingValue.error(error)))
+        )
+        : val;
+  }
 }
diff --git a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/services/archiva-request.service.ts b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/services/archiva-request.service.ts
index 84af0d6..2c00a41 100644
--- a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/services/archiva-request.service.ts
+++ b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/services/archiva-request.service.ts
@@ -57,6 +57,8 @@ export class ArchivaRequestService {
             return this.http.delete<R>(httpArgs.url, httpArgs.options);
         } else if (lType == "put") {
             return this.http.put<R>(httpArgs.url, input, httpArgs.options);
+        } else if (lType == "patch") {
+            return this.http.patch<R>(httpArgs.url, input, httpArgs.options);
         }
     }
 
@@ -98,6 +100,8 @@ export class ArchivaRequestService {
             return this.http.delete<HttpResponse<R>>(httpArgs.url, httpArgs.options);
         } else if (lType=='put') {
             return this.http.put<HttpResponse<R>>(httpArgs.url, input, httpArgs.options);
+        } else if (lType=='patch') {
+            return this.http.patch<HttpResponse<R>>(httpArgs.url, input, httpArgs.options);
         }
     }
 
diff --git a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/services/role.service.ts b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/services/role.service.ts
index 3de9919..99424a0 100644
--- a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/services/role.service.ts
+++ b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/services/role.service.ts
@@ -24,6 +24,7 @@ import { Role } from '@app/model/role';
 import {HttpResponse} from "@angular/common/http";
 import {PagedResult} from "@app/model/paged-result";
 import {UserInfo} from "@app/model/user-info";
+import {RoleUpdate} from "@app/model/role-update";
 
 @Injectable({
   providedIn: 'root'
@@ -64,8 +65,8 @@ export class RoleService {
     return this.rest.executeRestCall("get", "redback", "roles/" + roleId, null);
   }
 
-  public updateRole(role:Role) : Observable<Role> {
-    return this.rest.executeRestCall("put", "redback", "roles/" + role.id, role);
+  public updateRole(role:RoleUpdate) : Observable<Role> {
+    return this.rest.executeRestCall("patch", "redback", "roles/" + role.id, role);
   }
 
 }
diff --git a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/assets/i18n/en.json b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/assets/i18n/en.json
index 7bdf9b8..d094e39 100644
--- a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/assets/i18n/en.json
+++ b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/assets/i18n/en.json
@@ -174,6 +174,9 @@
     },
     "edit": "Edit"
   },
+  "headers": {
+    "action": "Action"
+  },
   "password": {
     "violations" : {