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/07 22:00:46 UTC

[archiva] 03/03: Updating angular web app

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 5732208f5bbe4c082f6d6b67444dec36a7964814
Author: Martin Stockhammer <ma...@apache.org>
AuthorDate: Mon Dec 7 23:00:37 2020 +0100

    Updating angular web app
---
 .../archiva-web/src/app/model/access-token.spec.ts |   3 +-
 .../archiva-web/src/app/model/application.spec.ts  |  18 ++
 .../main/archiva-web/src/app/model/application.ts  |  18 ++
 .../src/app/model/role-template.spec.ts            |  18 ++
 .../archiva-web/src/app/model/role-template.ts     |  18 ++
 .../src/main/archiva-web/src/app/model/role.ts     |   2 +
 .../manage-users-add.component.html                |   3 +-
 .../manage-users-add.component.scss                |   1 -
 .../manage-users-add.component.spec.ts             |   1 -
 .../manage-users-add/manage-users-add.component.ts |   1 -
 .../manage-users-edit.component.ts                 |  21 +-
 .../manage-users-list.component.html               |   1 -
 .../manage-users-list.component.scss               |   1 -
 .../manage-users-list.component.spec.ts            |   1 -
 .../manage-users-list.component.ts                 |   1 -
 .../manage-users-roles.component.html              | 146 ++++++----
 .../manage-users-roles.component.scss              |  18 ++
 .../manage-users-roles.component.spec.ts           |  18 ++
 .../manage-users-roles.component.ts                | 312 +++++++++++++++++----
 .../role-result.ts}                                |  11 +-
 .../users/manage-users/manage-users.component.html |   7 +-
 .../users/manage-users/manage-users.component.scss |   1 -
 .../manage-users/manage-users.component.spec.ts    |   1 -
 .../users/manage-users/manage-users.component.ts   |  50 +++-
 .../src/app/modules/shared/shared.module.ts        |  10 +-
 .../app/services/archiva-request.service.spec.ts   |   3 +-
 .../src/app/services/archiva-request.service.ts    |   3 +-
 .../app/services/authentication.service.spec.ts    |   3 +-
 .../src/app/services/authentication.service.ts     |   3 +-
 .../src/app/services/role.service.spec.ts          |  18 ++
 .../archiva-web/src/app/services/role.service.ts   |  28 ++
 .../src/main/archiva-web/src/assets/i18n/en.json   |  13 +-
 32 files changed, 614 insertions(+), 139 deletions(-)

diff --git a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/model/access-token.spec.ts b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/model/access-token.spec.ts
index e222aeb..1d83ed5 100644
--- a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/model/access-token.spec.ts
+++ b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/model/access-token.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/model/application.spec.ts b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/model/application.spec.ts
index a772571..7945fb4 100644
--- a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/model/application.spec.ts
+++ b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/model/application.spec.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 { Application } from './application';
 
 describe('Application', () => {
diff --git a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/model/application.ts b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/model/application.ts
index 857d080..69878bf 100644
--- a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/model/application.ts
+++ b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/model/application.ts
@@ -1,2 +1,20 @@
+/*
+ * 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 Application {
 }
diff --git a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/model/role-template.spec.ts b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/model/role-template.spec.ts
index c95251c..52eaabf 100644
--- a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/model/role-template.spec.ts
+++ b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/model/role-template.spec.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 { RoleTemplate } from './role-template';
 
 describe('RoleTemplate', () => {
diff --git a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/model/role-template.ts b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/model/role-template.ts
index 82363d3..01a5bd9 100644
--- a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/model/role-template.ts
+++ b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/model/role-template.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.
+ */
+
 export class RoleTemplate {
     id:string
     name:string
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 b2b9bd5..819d0f1 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
@@ -34,5 +34,7 @@ export class Role {
     // Web Internal attributes
     enabled: boolean = true
     level:number = -1
+    root_path: Array<string>
+    assigned_origin: boolean;
 
 }
diff --git a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/security/users/manage-users-add/manage-users-add.component.html b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/security/users/manage-users-add/manage-users-add.component.html
index 4f95595..1f5e246 100644
--- a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/security/users/manage-users-add/manage-users-add.component.html
+++ b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/security/users/manage-users-add/manage-users-add.component.html
@@ -8,7 +8,6 @@
   ~ 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
@@ -89,7 +88,7 @@
                 [attr.disabled]="userForm.valid?null:true">{{'users.add.submit'|translate}}</button>
     </div>
     <div *ngIf="success" class="alert alert-success" role="alert">
-        User <a [routerLink]="['/user','users','edit',result?.user_id]">{{result?.user_id}}</a> was added to the list.
+        User <a [routerLink]="['/security','users','edit',result?.user_id]">{{result?.user_id}}</a> was added to the list.
     </div>
     <div *ngIf="error" class="alert alert-danger" role="alert" >
         <h4 class="alert-heading">{{'users.add.errortitle'|translate}}</h4>
diff --git a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/security/users/manage-users-add/manage-users-add.component.scss b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/security/users/manage-users-add/manage-users-add.component.scss
index 573c9ef..343c3b1 100644
--- a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/security/users/manage-users-add/manage-users-add.component.scss
+++ b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/security/users/manage-users-add/manage-users-add.component.scss
@@ -8,7 +8,6 @@
  * 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
diff --git a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/security/users/manage-users-add/manage-users-add.component.spec.ts b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/security/users/manage-users-add/manage-users-add.component.spec.ts
index 71ff4cb..b345fd5 100644
--- a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/security/users/manage-users-add/manage-users-add.component.spec.ts
+++ b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/security/users/manage-users-add/manage-users-add.component.spec.ts
@@ -8,7 +8,6 @@
  * 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
diff --git a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/security/users/manage-users-add/manage-users-add.component.ts b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/security/users/manage-users-add/manage-users-add.component.ts
index 6588813..1cd5c36 100644
--- a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/security/users/manage-users-add/manage-users-add.component.ts
+++ b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/security/users/manage-users-add/manage-users-add.component.ts
@@ -8,7 +8,6 @@
  * 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
diff --git a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/security/users/manage-users-edit/manage-users-edit.component.ts b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/security/users/manage-users-edit/manage-users-edit.component.ts
index 288b552..adf0cb0 100644
--- a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/security/users/manage-users-edit/manage-users-edit.component.ts
+++ b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/security/users/manage-users-edit/manage-users-edit.component.ts
@@ -16,11 +16,11 @@
  * under the License.
  */
 
-import {Component, OnInit} from '@angular/core';
+import {Component, EventEmitter, OnInit, Output} from '@angular/core';
 import {ActivatedRoute} from '@angular/router';
 import {UserService} from "../../../../services/user.service";
 import {FormBuilder, FormControl} from "@angular/forms";
-import {catchError, map, switchMap, tap} from 'rxjs/operators';
+import {catchError, filter, map, switchMap, tap} from 'rxjs/operators';
 import {ManageUsersBaseComponent} from "../manage-users-base.component";
 import {ErrorResult} from "../../../../model/error-result";
 
@@ -38,6 +38,9 @@ export class ManageUsersEditComponent extends ManageUsersBaseComponent implement
     editMode: boolean;
     minUserIdSize = 0;
 
+    @Output()
+    userIdEvent: EventEmitter<string> = new EventEmitter<string>(true);
+
     constructor(private route: ActivatedRoute, public userService: UserService, public fb: FormBuilder) {
         super(userService, fb);
         this.editMode=false;
@@ -46,15 +49,21 @@ export class ManageUsersEditComponent extends ManageUsersBaseComponent implement
             this.editMode=true;
           }
         })
+
+    }
+
+    ngOnInit(): void {
         this.editUser = this.route.params.pipe(
-            map(params => params.userid), switchMap(userid => userService.getUser(userid))).subscribe(user => {
+            map(params => params.userid),
+            filter(userid=>userid!=null),
+            tap(userid=>{
+                this.userIdEvent.emit(userid)
+            }),
+            switchMap(userid => this.userService.getUser(userid))).subscribe(user => {
             this.editUser = user;
             this.originUser = user;
             this.copyToForm(this.editProperties, this.editUser);
         });
-    }
-
-    ngOnInit(): void {
       // This resets the validators of the base class
       this.userForm.get('user_id').clearValidators();
       this.userForm.get('user_id').clearAsyncValidators();
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 77251b7..1c449ba 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
@@ -8,7 +8,6 @@
   ~ 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
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.scss b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/security/users/manage-users-list/manage-users-list.component.scss
index 573c9ef..343c3b1 100644
--- a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/security/users/manage-users-list/manage-users-list.component.scss
+++ b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/security/users/manage-users-list/manage-users-list.component.scss
@@ -8,7 +8,6 @@
  * 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
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.spec.ts b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/security/users/manage-users-list/manage-users-list.component.spec.ts
index c32bd39..8080de4 100644
--- a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/security/users/manage-users-list/manage-users-list.component.spec.ts
+++ b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/security/users/manage-users-list/manage-users-list.component.spec.ts
@@ -8,7 +8,6 @@
  * 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
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 7c9d7b1..36b3627 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
@@ -8,7 +8,6 @@
  * 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
diff --git a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/security/users/manage-users-roles/manage-users-roles.component.html b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/security/users/manage-users-roles/manage-users-roles.component.html
index 6cc3274..279e552 100644
--- a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/security/users/manage-users-roles/manage-users-roles.component.html
+++ b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/security/users/manage-users-roles/manage-users-roles.component.html
@@ -1,50 +1,102 @@
-<h3>Roles</h3>
-<table class="table">
-    <thead class="thead-light">
-    <tr class="d-flex">
-        <th class="col-3">Role</th>
-        <th class="col-2">Scope</th>
-        <th class="col-1">Assign</th>
-    </tr>
-    </thead>
-    <tbody>
-    <tr *ngFor="let baseRole of baseRoles" class="d-flex">
-        <td class="col-3" [innerHTML]="getRoleContent(baseRole)"></td>
-        <td class="col-2">
-            {{baseRole.application_id}}
-        </td>
-        <td class="col-1">
-            <div class="form-check form-check-inline"><input class="form-check-input" type="checkbox"
-                                                             [attr.disabled]="baseRole.enabled?null:true"
-                                                             [attr.id]="baseRole.id"
-                                                             [(ngModel)]="baseRole.assigned"
-                                                             (change)="changeBaseAssignment(baseRole, $event)"
-            >
+
+
+<!--
+  ~ 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.
+  -->
+
+<div class="row">
+
+    <div class="row col-md-6">
+    <h4 class="col-md-2 mt-3">{{'users.roles.base_roles'|translate}} </h4>
+    <h4 class="col-md-2 offset-md-4 mt-3"><span class="badge badge-primary">{{userid}}</span></h4>
+    </div>
+    <ng-container *ngIf="roles$|async as myRoles">
+        <table class="table col-md-12">
+            <thead class="thead-light">
+            <tr class="d-flex">
+                <th class="col-3">{{'users.roles.table.role'|translate}}</th>
+                <th class="col-2">{{'users.roles.table.scope'|translate}}</th>
+                <th class="col-1">{{'users.roles.table.assign'|translate}}</th>
+            </tr>
+            </thead>
+            <tbody>
+            <tr *ngFor="let baseRoleItem of myRoles.baseRoles|keyvalue" class="d-flex">
+                <td class="col-3" [innerHTML]="getRoleContent(baseRoleItem.value)"></td>
+                <td class="col-2">
+                    {{baseRoleItem.value.application_id}}
+                </td>
+                <td class="col-1">
+                    <div class="form-check form-check-inline"><input class="form-check-input" type="checkbox"
+                                                                     [attr.disabled]="baseRoleItem.value.enabled?null:true"
+                                                                     [attr.id]="baseRoleItem.key"
+                                                                     [(ngModel)]="baseRoleItem.value.assigned"
+                                                                     (change)="changeBaseAssignment(baseRoleItem.value, $event)"
+                    >
+                    </div>
+                </td>
+            </tr>
+            </tbody>
+        </table>
+
+
+        <h4>{{'users.roles.template_roles'|translate}}</h4>
+        <table class="table">
+            <thead class="thead-light">
+            <tr class="d-flex">
+                <th scope="row" class="col-1">{{'users.roles.table.repository'|translate}}</th>
+                <th scope="col" class="col-1"
+                    *ngFor="let templateRole of templateRoles$ | async">{{templateRole.name}}</th>
+            </tr>
+            </thead>
+            <tbody>
+            <tr class="d-flex" *ngFor="let res of myRoles.templateRoleInstances | keyvalue">
+                <td class="table-secondary col-1">{{res.key}}</td>
+                <td class="col-1 text-center" *ngFor="let templateRole of templateRoles$ | async">
+                    <div class="form-check form-check-inline"
+                         *ngIf="getInstanceContent(templateRole, res.value) as role">
+                        <input class="form-check-input"
+                               [attr.disabled]="role.enabled?null:true"
+                               type="checkbox" [attr.id]="role.id"
+                               [(ngModel)]="role.assigned" (ngModelChange)="changeTemplateAssignment(role, $event)"/>
+                    </div>
+                </td>
+            </tr>
+            </tbody>
+
+        </table>
+
+        <div class="form-group col-md-12 mt-3 ">
+            <button class="btn btn-primary" type="submit"
+                    (click)="saveAssignments()">{{'form.button.save'|translate}}</button>
+        </div>
+
+        <ng-container *ngIf="saved">
+            <div *ngIf="success" class="alert alert-success" role="alert">
+                Roles have been assigned
             </div>
-        </td>
-    </tr>
-    </tbody>
-</table>
-
-
-<h3>Repository Roles</h3>
-<table class="table">
-    <thead class="thead-light">
-    <tr class="d-flex">
-        <th scope="row" class="col-1">Repository</th>
-        <th scope="col" class="col-1" *ngFor="let templateRole of templateRoles$ | async">{{templateRole.name}}</th>
-    </tr>
-    </thead>
-    <tbody>
-    <tr class="d-flex" *ngFor="let res of templateRoleInstances | keyvalue" >
-        <td class="table-secondary col-1">{{res.key}}</td>
-        <td class="col-1 text-center" *ngFor="let templateRole of templateRoles$ | async">
-            <div class="form-check form-check-inline" *ngIf="getInstanceContent(templateRole, res.value) as role">
-                <input class="form-check-input" type="checkbox" [attr.id]="role.id"
-                       [(ngModel)]="role.assigned" (ngModelChange)="changeInstAssignment(role, $event)"/>
+            <div *ngIf="!success" class="alert alert-danger" role="alert">
+                <h4 class="alert-heading">Errors</h4>
+                <ng-container *ngFor="let message of errors.error_messages; first as isFirst">
+                    <hr *ngIf="!isFirst">
+                    <p>{{message.message}}</p>
+                </ng-container>
             </div>
-        </td>
-    </tr>
-    </tbody>
+        </ng-container>
+
+    </ng-container>
 
-</table>
+</div>
\ No newline at end of file
diff --git a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/security/users/manage-users-roles/manage-users-roles.component.scss b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/security/users/manage-users-roles/manage-users-roles.component.scss
index e69de29..343c3b1 100644
--- a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/security/users/manage-users-roles/manage-users-roles.component.scss
+++ b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/security/users/manage-users-roles/manage-users-roles.component.scss
@@ -0,0 +1,18 @@
+/*!
+ * 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.
+ */
+
diff --git a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/security/users/manage-users-roles/manage-users-roles.component.spec.ts b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/security/users/manage-users-roles/manage-users-roles.component.spec.ts
index b532584..fdc5912 100644
--- a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/security/users/manage-users-roles/manage-users-roles.component.spec.ts
+++ b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/security/users/manage-users-roles/manage-users-roles.component.spec.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 { ComponentFixture, TestBed } from '@angular/core/testing';
 
 import { ManageUsersRolesComponent } from './manage-users-roles.component';
diff --git a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/security/users/manage-users-roles/manage-users-roles.component.ts b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/security/users/manage-users-roles/manage-users-roles.component.ts
index 26de0d8..1beb1f0 100644
--- a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/security/users/manage-users-roles/manage-users-roles.component.ts
+++ b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/security/users/manage-users-roles/manage-users-roles.component.ts
@@ -1,54 +1,125 @@
-import { Component, OnInit } from '@angular/core';
+/*
+ * 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 {AfterViewInit, Component, EventEmitter, OnInit, Output} from '@angular/core';
 import { Role } from '@app/model/role';
 import {UserService} from "@app/services/user.service";
 import {ActivatedRoute} from "@angular/router";
-import {filter, map, switchMap} from "rxjs/operators";
+import {catchError, filter, map, multicast, share, switchMap, tap} from "rxjs/operators";
 import {RoleTree} from "@app/model/role-tree";
 import {RoleService} from "@app/services/role.service";
 import {RoleTemplate} from "@app/model/role-template";
-import {Observable} from "rxjs";
+import {Observable, of} from "rxjs";
 import {Util} from "@app/modules/shared/shared.module";
+import { RoleResult } from './role-result';
+import {fromArray} from "rxjs/internal/observable/fromArray";
+import {ErrorResult} from "@app/model/error-result";
+import {HttpResponse} from "@angular/common/http";
 
 @Component({
   selector: 'app-manage-users-roles',
   templateUrl: './manage-users-roles.component.html',
   styleUrls: ['./manage-users-roles.component.scss']
 })
-export class ManageUsersRolesComponent implements OnInit {
+export class ManageUsersRolesComponent implements OnInit, AfterViewInit {
 
-  baseRoles : Array<Role>
+  roles$ : Observable<RoleResult>
+  currentRoles: RoleResult
   guest: Role
   registered: Role
-  // Map of (resource, [roles])
-  templateRoleInstances: Map<string, Array<Role>>
   templateRoles$: Observable<RoleTemplate[]>;
+  userid: string;
+  success:boolean=true;
+  errors: ErrorResult[]=[];
+  saved:boolean=false;
 
-  constructor(private route : ActivatedRoute, private userService : UserService, private roleService : RoleService) {
-    this.route.params.pipe(
-        map(params => params.userid), filter(userid => userid!=null), switchMap(userid => userService.userRoleTree(userid))).subscribe(roleTree => {
-      this.parseRoleTree(roleTree);
-    });
+  @Output()
+  userIdEvent: EventEmitter<string> = new EventEmitter<string>(true);
 
+  constructor(private route : ActivatedRoute, private userService : UserService, private roleService : RoleService) {
   }
 
   ngOnInit(): void {
+    this.roles$ = this.route.params.pipe(
+        map(params => params.userid), filter(userid => userid != null),
+        tap(userid => this.userid = userid),
+        tap(userid=>{
+          this.userIdEvent.emit(userid)
+        }),
+        switchMap(userid => {
+          return this.userService.userRoleTree(userid)
+        }),
+        map(roleTree=>this.parseRoleTree(roleTree)),
+        // This is to avoid multiple userService.userRoleTree() calls for template and base roles
+        share()
+    );
     this.templateRoles$ = this.roleService.getTemplates();
   }
 
-  private parseRoleTree(roleTree:RoleTree): void {
-    let roleTable = [];
-    for(let rootRole of roleTree.root_roles) {
-      roleTable = this.recurseRoleTree(rootRole, roleTable, 0);
+  private parseRoleTree(roleTree:RoleTree): RoleResult {
+      let roleResult = new RoleResult();
+      let rootRoles = roleTree.root_roles;
+      rootRoles.sort((a, b)=>{
+        if (b.id=='guest') {
+          return 1;
+        } else if (b.id=='registered-user') {
+          return 1;
+        } else {
+          return -1;
+        }
+      })
+      for (let rootRole of rootRoles) {
+        this.recurseTree(rootRole, roleResult, 0, null);
+      }
+      return roleResult;
+  }
+
+  private recurseTree(role:Role,roleResult:RoleResult, level:number, parent: Role) : void {
+    let newLevel=level;
+    if (parent!=null) {
+      if (role.root_path==null) {
+        role.root_path = (parent.root_path == null ? [] : parent.root_path.slice());
+      }
+      role.root_path.push(parent.id)
     }
-    this.baseRoles = roleTable;
-    let templateMap : Map<string,Array<Role>> = new Map<string, Array<Role>>();
-    for (let rootRole of roleTree.root_roles) {
-      templateMap = this.recurseTemplates(rootRole, templateMap, 0);
+    role.assigned_origin = role.assigned;
+    if (role.template_instance) {
+      newLevel = this.parseTemplateRole(role,roleResult.templateRoleInstances,newLevel)
+    } else {
+      newLevel = this.parseBaseRole(role, roleResult.baseRoles, newLevel)
+    }
+    for(let childRole of role.children) {
+      let recurseChild = childRole;
+      if (childRole.template_instance) {
+        if (roleResult.templateRoleInstances.has(childRole.resource) && roleResult.templateRoleInstances.get(childRole.resource).has(childRole.model_id)) {
+          recurseChild = roleResult.templateRoleInstances.get(childRole.resource).get(childRole.model_id)
+        }
+      } else {
+        let existingBaseRole = roleResult.baseRoles.find(role => role.id == childRole.id)
+        if (existingBaseRole != null) {
+          recurseChild = existingBaseRole;
+        }
+      }
+      this.recurseTree(recurseChild, roleResult, newLevel, role);
     }
-    this.templateRoleInstances = templateMap;
   }
 
-  private recurseRoleTree(role:Role, roles : Array<Role>, level:number) : Array<Role> {
+  private parseBaseRole(role:Role, roles : Array<Role>, level:number) : number {
     if (role.id=='guest') {
       this.guest = role;
     } else if (role.id=='registered-user') {
@@ -56,34 +127,32 @@ export class ManageUsersRolesComponent implements OnInit {
     }
     role.enabled=true;
     let newLevel;
-    if (!role.template_instance && role.assignable) {
+    if (role.assignable) {
       role.level=level
       roles.push(role);
       newLevel = level+1;
     } else {
       newLevel = level;
     }
-    for(let childRole of role.children) {
-      roles = this.recurseRoleTree(childRole, roles, newLevel);
-    }
-    return roles;
+    return newLevel;
   }
 
-  private recurseTemplates(role:Role, roles : Map<string, Array<Role>>, level:number) : Map<string, Array<Role>> {
+  private parseTemplateRole(role:Role, roles : Map<string, Map<string, Role>>, level:number) : number {
+    let newLevel=level;
     role.enabled=true;
-    if (role.template_instance && role.assignable) {
+    if (role.assignable) {
       role.level=level
-      let roleList = roles.get(role.resource)
-      if (roleList==null) {
-        roleList = []
+      let modelRoleMap = roles.get(role.resource)
+      if (modelRoleMap==null) {
+        modelRoleMap = new Map<string, Role>();
       }
-      roleList.push(role);
-      roles.set(role.resource, roleList);
-    }
-    for(let childRole of role.children) {
-      roles = this.recurseTemplates(childRole, roles, level+1);
+      modelRoleMap.set(role.model_id, role);
+      roles.set(role.resource, modelRoleMap);
+      newLevel = level + 1;
+    } else {
+      newLevel = level;
     }
-    return roles;
+    return newLevel;
   }
 
   getRoleContent(role:Role) : string {
@@ -101,55 +170,192 @@ export class ManageUsersRolesComponent implements OnInit {
 
   changeBaseAssignment(role : Role, event) {
     let cLevel=-1
-    let assignStatus;
+    let assignStatus
+    // Guest is special and exclusive
     if (role.id==this.guest.id) {
       if (role.assigned) {
-        for (let cRole of this.baseRoles) {
+        this.currentRoles.baseRoles.forEach((cRole:Role)=> {
           if (cRole.id != this.guest.id) {
             cRole.assigned = false;
             cRole.enabled=true;
           }
-        }
+
+        })
         role.enabled = false;
+        this.currentRoles.templateRoleInstances.forEach((value, key)=>{
+          value.forEach((templateInstance, modelId) => {
+            templateInstance.assigned = false;
+            templateInstance.enabled = true;
+
+          });
+        })
       }
     } else {
       this.guest.enabled = true;
-      for (let cRole of this.baseRoles) {
+      this.currentRoles.baseRoles.forEach((cRole)=> {
         if (cRole.id == role.id) {
-          console.log("Value: " + cRole.assigned);
           cLevel = cRole.level;
           assignStatus = cRole.assigned;
           if (assignStatus) {
             this.guest.assigned = false;
+            this.guest.enabled = true;
           } else {
-            if (!this.baseRoles.find(role=>role.assigned)) {
+            if (!this.isAnyAssigned()) {
               this.guest.assigned=true;
+              this.guest.enabled = false;
             }
           }
         } else {
-          console.log("Level " + cLevel);
-          if (cLevel >= 0 && cLevel < cRole.level) {
+          if (cLevel >= 0 && cLevel < cRole.level && cRole.root_path.find(pRoleId => pRoleId==role.id)) {
             if (assignStatus) {
               cRole.assigned = true;
               cRole.enabled = false;
             } else {
               cRole.enabled = true;
+              cRole.assigned=cRole.assigned_origin
             }
           } else if (cLevel >= 0) {
-            break;
+            return;
           }
         }
+      })
+      this.currentRoles.templateRoleInstances.forEach((value, key)=>{
+        value.forEach((templateInstance, modelId) => {
+              if(templateInstance.root_path.find(roleId => roleId==role.id)) {
+                if (role.assigned) {
+                  templateInstance.assigned = true;
+                  templateInstance.enabled = false
+                } else {
+                  templateInstance.enabled = true;
+                  templateInstance.assigned=templateInstance.assigned_origin
+                }
+              }
+            }
+        )
+      })
+    }
+  }
+
+  isAnyAssigned() : boolean {
+    if (Array.from(this.currentRoles.baseRoles.values()).find(role=>role.assigned)!=null) {
+      return true;
+    }
+    return Array.from(this.currentRoles.templateRoleInstances.values()).map((roleMap: Map<string, Role>) => Array.from(roleMap.values()))
+        .find(values=>values.find(role=>role.assigned))!=null
+  }
+
+
+
+
+  changeTemplateAssignment(role : Role, event) {
+    if (role.assigned) {
+      if (this.guest.assigned) {
+        this.guest.assigned = false;
+        this.guest.enabled = true;
       }
+      if (!this.registered.assigned) {
+        this.registered.assigned=true;
+      }
+    } else {
+      if (!this.isAnyAssigned()) {
+        this.guest.assigned=true;
+        this.guest.enabled = false;
+      }
+
     }
   }
 
-  changeInstAssignment(role : Role, event) {
-    console.log("Change " + role.id);
-    console.log("Assignment changed "+JSON.stringify(event));
-    console.log("Event target "+event.target);
+  getInstanceContent(template:RoleTemplate, roles:Map<string,Role>) : Role {
+      return roles.get(template.id)
+  }
+
+  saveAssignments() {
+    this.saved=false;
+    this.success=true;
+    this.errors = [];
+    let assignmentMap : Map<string, Role> = new Map(this.currentRoles.baseRoles.filter(role => role.assigned != role.assigned_origin).map(role => [role.id, role]));
+    let assignments : Array<Role> =  []
+    let unassignments : Array<Role> = []
+    assignmentMap.forEach((role, roleId)=>{
+      if (role.level>0) {
+        for(let parentId of role.root_path) {
+          if (assignmentMap.has(parentId) && assignmentMap.get(parentId).assigned) {
+            return;
+          }
+        }
+      }
+      if (role.assigned) {
+        assignments.push(role);
+      } else {
+        unassignments.push(role);
+      }
+    })
+    this.currentRoles.templateRoleInstances.forEach((templMap, resource)=> {
+          templMap.forEach((role, modelId)=> {
+            if (role.assigned!=role.assigned_origin) {
+              if (role.level>0) {
+                for(let parentId of role.root_path) {
+                  if (assignmentMap.has(parentId) && assignmentMap.get(parentId).assigned) {
+                    return;
+                  }
+                }
+              }
+              if (role.assigned) {
+                assignments.push(role);
+              } else {
+                unassignments.push(role);
+              }
+            }
+          })
+        }
+    )
+    fromArray(assignments).pipe(switchMap((role) => this.roleService.assignRole(role.id, this.userid)),
+        catchError((err: ErrorResult, caught) => {
+              this.success = false;
+              this.errors.push(err);
+              return [];
+            }
+        )
+    ).subscribe((result:HttpResponse<Role>)=> {
+          this.updateRole(result.body, true);
+          this.saved=true;
+        }
+    );
+    fromArray(unassignments).pipe(switchMap((role) => this.roleService.unAssignRole(role.id, this.userid)),
+        catchError((err: ErrorResult, caught) => {
+              this.success = false;
+              this.errors.push(err);
+              return [];
+            }
+        )
+    ).subscribe(result=>{
+      this.updateRole(result.body,false);
+      this.saved=true;
+    });
+    this.saved=true;
+  }
+
+  private updateRole(role:Role, assignment:boolean) : void {
+    if (role!=null) {
+      if (role.template_instance) {
+        this.currentRoles.templateRoleInstances.forEach((templMap, resource)=>{
+          templMap.forEach((tmplRole, modelId)=> {
+                if (tmplRole.id == role.id) {
+                  Util.deepCopy(role, tmplRole, false);
+                  tmplRole.assigned = assignment;
+                }
+              }
+          )
+        })
+      } else {
+        let target = this.currentRoles.baseRoles.find(baseRole => baseRole.id == role.id);
+        Util.deepCopy(role, target, false);
+        target.assigned = assignment;
+      }
+    }
   }
 
-  getInstanceContent(template:RoleTemplate, roles:Array<Role>) : Role {
-      return roles.find(role=>role.model_id==template.id)
+  ngAfterViewInit(): void {
+    this.roles$.subscribe(roleResult => this.currentRoles = roleResult);
   }
 }
diff --git a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/security/users/manage-users/manage-users.component.scss b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/security/users/manage-users-roles/role-result.ts
similarity index 74%
copy from archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/security/users/manage-users/manage-users.component.scss
copy to archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/security/users/manage-users-roles/role-result.ts
index 573c9ef..83b854e 100644
--- a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/security/users/manage-users/manage-users.component.scss
+++ b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/security/users/manage-users-roles/role-result.ts
@@ -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
@@ -8,7 +8,6 @@
  * 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
@@ -17,3 +16,11 @@
  * under the License.
  */
 
+import {Role} from "@app/model/role";
+
+export class RoleResult {
+    // Map of (roleId, Role)
+    baseRoles: Array<Role> = [];
+    // Map of (resource, [(modelId, role), ...])
+    templateRoleInstances: Map<string, Map<string, Role>> = new Map<string, Map<string, Role>>();
+}
\ No newline at end of file
diff --git a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/security/users/manage-users/manage-users.component.html b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/security/users/manage-users/manage-users.component.html
index d5c5b42..ebe5bb4 100644
--- a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/security/users/manage-users/manage-users.component.html
+++ b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/security/users/manage-users/manage-users.component.html
@@ -8,7 +8,6 @@
   ~ 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
@@ -25,11 +24,11 @@
         <a class="nav-link" routerLink="/security/users/add" routerLinkActive="active" href="#">{{'users.add.head' |translate }}</a>
     </li>
     <li class="nav-item">
-        <a class="nav-link" routerLink="/security/users/edit" routerLinkActive="active" href="#">{{'users.edit.head' |translate }}</a>
+        <a class="nav-link" routerLink="/security/users/edit{{userId$|async}}" routerLinkActive="active" href="#">{{'users.edit.head' |translate }}</a>
     </li>
     <li class="nav-item">
-        <a class="nav-link" routerLink="/security/users/roles" routerLinkActive="active" href="#">{{'users.roles.head' |translate }}</a>
+        <a class="nav-link" routerLink="/security/users/roles{{userId$|async}}" routerLinkActive="active" href="#">{{'users.roles.head' |translate }}</a>
     </li>
 </ul>
 
-<router-outlet ></router-outlet>
\ No newline at end of file
+<router-outlet (activate)="onChildActivate($event)" ></router-outlet>
\ No newline at end of file
diff --git a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/security/users/manage-users/manage-users.component.scss b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/security/users/manage-users/manage-users.component.scss
index 573c9ef..343c3b1 100644
--- a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/security/users/manage-users/manage-users.component.scss
+++ b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/security/users/manage-users/manage-users.component.scss
@@ -8,7 +8,6 @@
  * 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
diff --git a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/security/users/manage-users/manage-users.component.spec.ts b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/security/users/manage-users/manage-users.component.spec.ts
index 9e991b4..20b1440 100644
--- a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/security/users/manage-users/manage-users.component.spec.ts
+++ b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/security/users/manage-users/manage-users.component.spec.ts
@@ -8,7 +8,6 @@
  * 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
diff --git a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/security/users/manage-users/manage-users.component.ts b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/security/users/manage-users/manage-users.component.ts
index dd72616..e33bdcd 100644
--- a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/security/users/manage-users/manage-users.component.ts
+++ b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/security/users/manage-users/manage-users.component.ts
@@ -8,7 +8,6 @@
  * 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
@@ -17,7 +16,12 @@
  * under the License.
  */
 
-import { Component, OnInit } from '@angular/core';
+import {AfterViewInit, Component, OnInit} from '@angular/core';
+import {ActivatedRoute, Params} from "@angular/router";
+import {iif, Observable, of, pipe, merge, combineLatest} from "rxjs";
+import {combineAll, filter, map, mergeMap, share, switchMap, tap} from 'rxjs/operators';
+import {flatMap} from "rxjs/internal/operators";
+import {fromArray} from "rxjs/internal/observable/fromArray";
 
 @Component({
   selector: 'app-manage-users',
@@ -26,9 +30,49 @@ import { Component, OnInit } from '@angular/core';
 })
 export class ManageUsersComponent implements OnInit {
 
-  constructor() { }
+  userId$:Observable<string>
+
+  constructor(private route : ActivatedRoute) {
+  }
 
   ngOnInit(): void {
   }
 
+
+
+  onChildActivate(componentReference) {
+    // console.log("Activating "+componentReference+" - "+JSON.stringify(componentReference,getCircularReplacer()))
+    if (componentReference.userIdEvent!=null) {
+      let componentEmit : Observable<string> = componentReference.userIdEvent.pipe(
+          tap(userid=>console.log("Event "+componentReference.class+" "+userid)),
+          map((userid: string) => this.getSubPath(userid)));
+      if (this.userId$!=null) {
+        this.userId$ = merge(this.userId$, componentEmit)
+      } else {
+        this.userId$ = componentEmit;
+      }
+    }
+  }
+
+  getSubPath(userid:string) {
+    if (userid!=null && userid.length>0) {
+      return '/' + userid;
+    } else {
+      return '';
+    }
+  }
+
 }
+
+const getCircularReplacer = () => {
+  const seen = new WeakSet();
+  return (key, value) => {
+    if (typeof value === "object" && value !== null) {
+      if (seen.has(value)) {
+        return;
+      }
+      seen.add(value);
+    }
+    return value;
+  };
+};
\ No newline at end of file
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 0ff9025..1030390 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
@@ -71,7 +71,7 @@ export function httpTranslateLoader(http: HttpClient) {
 }
 
 export const Util = {
-    deepCopy(src: Object, dst: Object) {
+    deepCopy(src: Object, dst: Object, overwriteWithEmptyString:boolean=true) {
         Object.keys(src).forEach((key, idx) => {
             let srcEl = src[key];
             if (typeof (srcEl) == 'object') {
@@ -81,6 +81,14 @@ export const Util = {
                 }
                 dstEl = dst[key];
                 this.deepCopy(srcEl, dstEl);
+            } else if (typeof(srcEl)=='string') {
+                if (overwriteWithEmptyString) {
+                    dst[key]=srcEl
+                } else {
+                    if ((srcEl as string).length>0) {
+                        dst[key]=srcEl
+                    }
+                }
             } else {
                 // console.debug("setting " + key + " = " + srcEl);
                 dst[key] = srcEl;
diff --git a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/services/archiva-request.service.spec.ts b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/services/archiva-request.service.spec.ts
index 159b1ee..039ca3f 100644
--- a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/services/archiva-request.service.spec.ts
+++ b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/services/archiva-request.service.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/services/archiva-request.service.ts b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/services/archiva-request.service.ts
index 52a55e6..84af0d6 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
@@ -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/services/authentication.service.spec.ts b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/services/authentication.service.spec.ts
index 03c7b23..d0382d8 100644
--- a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/services/authentication.service.spec.ts
+++ b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/services/authentication.service.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/services/authentication.service.ts b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/services/authentication.service.ts
index 519eb27..422d339 100644
--- a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/services/authentication.service.ts
+++ b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/services/authentication.service.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/services/role.service.spec.ts b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/services/role.service.spec.ts
index 275b25a..6328dbb 100644
--- a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/services/role.service.spec.ts
+++ b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/services/role.service.spec.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 { TestBed } from '@angular/core/testing';
 
 import { RoleService } from './role.service';
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 f7e1bc8..f92a4a7 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
@@ -1,7 +1,27 @@
+/*
+ * 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 { Injectable } from '@angular/core';
 import {ArchivaRequestService} from "@app/services/archiva-request.service";
 import {RoleTemplate} from "@app/model/role-template";
 import { Observable } from 'rxjs';
+import { Role } from '@app/model/role';
+import {HttpResponse} from "@angular/common/http";
 
 @Injectable({
   providedIn: 'root'
@@ -14,4 +34,12 @@ export class RoleService {
     return this.rest.executeRestCall("get", "redback", "roles/templates", null);
   }
 
+  public assignRole(roleId, userId) : Observable<HttpResponse<Role>> {
+    return this.rest.executeResponseCall<Role>("put", "redback", "roles/" + roleId + "/user/" + userId, null);
+  }
+
+  public unAssignRole(roleId, userId) : Observable<HttpResponse<Role>> {
+    return this.rest.executeResponseCall<Role>("delete", "redback", "roles/" + roleId + "/user/" + userId, null);
+  }
+
 }
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 2be44e1..f12c6f2 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
@@ -116,7 +116,15 @@
       }
     },
     "roles": {
-      "head": "Edit Roles"
+      "head": "Edit Roles",
+      "base_roles": "Roles",
+      "template_roles": "Repository Roles",
+      "table": {
+        "role": "Role",
+        "scope": "Scope",
+        "assign": "Assign",
+        "repository": "Repository"
+      }
     }
   },
   "search": {
@@ -133,7 +141,8 @@
     },
     "button": {
       "yes": "Yes",
-      "no": "No"
+      "no": "No",
+      "save": "Save Changes"
     }
   },
   "password": {