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/29 00:11:48 UTC

[archiva] branch master updated (7744b08 -> c7344aa)

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

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


    from 7744b08  Adding toast notification
     new cb0e4d1  Improving error handling
     new c7344aa  Adding user info and password change

The 2 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 .../services/v2/SecurityConfigurationService.java} |  10 +-
 .../main/archiva-web/src/app/app-routing.module.ts |   2 +
 .../main/archiva-web/src/app/app.component.html    |   6 +-
 .../archiva-web/src/app/model/app-notification.ts  |   3 +-
 .../manage-roles-edit.component.ts                 |  35 ++++---
 .../manage-users-add.component.html                |   3 -
 .../manage-users-roles.component.ts                |   8 +-
 .../paginated-entities.component.ts                |  27 ++----
 .../src/app/modules/shared/shared.module.ts        |   6 +-
 .../app/modules/shared/toast/toast.component.ts    |   4 +-
 .../shared/user-info/user-info.component.html      |  87 +++++++++++++++++
 .../user-info.component.scss}                      |   0
 .../user-info.component.spec.ts}                   |  12 +--
 .../shared/user-info/user-info.component.ts        | 103 +++++++++++++++++++++
 .../src/app/modules/shared/with-loading.pipe.ts    |   8 +-
 .../src/app/services/archiva-request.service.ts    |   1 +
 .../archiva-web/src/app/services/role.service.ts   |  50 +++++++---
 .../archiva-web/src/app/services/toast.service.ts  |  42 ++++++++-
 .../archiva-web/src/app/services/user.service.ts   |  16 +++-
 .../src/main/archiva-web/src/assets/i18n/en.json   |  82 +++++++++-------
 20 files changed, 394 insertions(+), 111 deletions(-)
 copy archiva-modules/{archiva-base/archiva-repository-api/src/main/java/org/apache/archiva/repository/content/ArtifactType.java => archiva-web/archiva-rest/archiva-rest-api/src/main/java/org/apache/archiva/rest/api/services/v2/SecurityConfigurationService.java} (82%)
 create mode 100644 archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/shared/user-info/user-info.component.html
 copy archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/shared/{sorted-table-header/sorted-table-header.component.scss => user-info/user-info.component.scss} (100%)
 copy archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/shared/{toast/toast.component.spec.ts => user-info/user-info.component.spec.ts} (80%)
 create mode 100644 archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/shared/user-info/user-info.component.ts


[archiva] 02/02: Adding user info and password change

Posted by ma...@apache.org.
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 c7344aa1c443678f69ee280774ab2505391a562a
Author: Martin Stockhammer <ma...@apache.org>
AuthorDate: Mon Dec 28 21:05:44 2020 +0100

    Adding user info and password change
---
 .../services/v2/SecurityConfigurationService.java  |  28 ++++++
 .../main/archiva-web/src/app/app-routing.module.ts |   2 +
 .../main/archiva-web/src/app/app.component.html    |   6 +-
 .../src/app/modules/shared/shared.module.ts        |   6 +-
 .../app/modules/shared/toast/toast.component.ts    |   4 +-
 .../shared/user-info/user-info.component.html      |  87 +++++++++++++++++
 .../shared/user-info/user-info.component.scss      |  18 ++++
 .../shared/user-info/user-info.component.spec.ts   |  43 +++++++++
 .../shared/user-info/user-info.component.ts        | 103 +++++++++++++++++++++
 .../src/app/services/archiva-request.service.ts    |   1 +
 .../archiva-web/src/app/services/user.service.ts   |  16 +++-
 .../src/main/archiva-web/src/assets/i18n/en.json   |  70 ++++++++------
 12 files changed, 347 insertions(+), 37 deletions(-)

diff --git a/archiva-modules/archiva-web/archiva-rest/archiva-rest-api/src/main/java/org/apache/archiva/rest/api/services/v2/SecurityConfigurationService.java b/archiva-modules/archiva-web/archiva-rest/archiva-rest-api/src/main/java/org/apache/archiva/rest/api/services/v2/SecurityConfigurationService.java
new file mode 100644
index 0000000..b494c3f
--- /dev/null
+++ b/archiva-modules/archiva-web/archiva-rest/archiva-rest-api/src/main/java/org/apache/archiva/rest/api/services/v2/SecurityConfigurationService.java
@@ -0,0 +1,28 @@
+package org.apache.archiva.rest.api.services.v2;/*
+ * 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.
+ */
+
+/**
+ *
+ * Service for configuration of redback and security related settings.
+ *
+ * @author Martin Stockhammer <ma...@apache.org>
+ * @since 3.0
+ */
+public interface SecurityConfigurationService
+{
+}
diff --git a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/app-routing.module.ts b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/app-routing.module.ts
index c07b1fe..aa5083d 100644
--- a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/app-routing.module.ts
+++ b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/app-routing.module.ts
@@ -29,6 +29,7 @@ import {BrowseComponent} from "./modules/repo/browse/browse.component";
 import {UploadComponent} from "./modules/repo/upload/upload.component";
 import {RoutingGuardService as Guard} from "./services/routing-guard.service";
 import {SecurityConfigurationComponent} from "./modules/security/security-configuration/security-configuration.component";
+import {UserInfoComponent} from "@app/modules/shared/user-info/user-info.component";
 
 /**
  * You can use Guard (RoutingGuardService) for permission checking. The service needs data with one parameter 'perm',
@@ -55,6 +56,7 @@ const routes: Routes = [
         ]
     },
     {path: 'contact', component: ContactComponent},
+    {path: 'me/info', component: UserInfoComponent},
     {path: 'about', component: AboutComponent},
     {path: 'login', component: LoginComponent},
     {path: 'logout', component: HomeComponent},
diff --git a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/app.component.html b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/app.component.html
index c49a00e..cd91722 100644
--- a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/app.component.html
+++ b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/app.component.html
@@ -50,9 +50,11 @@
             <div class="collapse navbar-collapse" id="navbarsDefault">
                 <div class="navbar-nav ml-auto">
                     <span *ngIf="auth.authenticated" class="navbar-text border-right pr-2 mr-2">
-                      {{user.userInfo.full_name}}
+                      <a class="nav-link" routerLink="/me/info" >
+                                {{user.userInfo.full_name}}
+                            </a>
                     </span>
-                    <ul class="navbar-nav">
+                    <ul class="navbar-nav d-lg-flex align-items-center">
                         <li class="nav-item active">
                             <a class="nav-link" routerLink="/">
                                 <i class="fas fa-home mr-1"></i>{{ 'menu.home' |translate }}
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 a6bb547..38fc25d 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
@@ -37,6 +37,8 @@ import {RouterModule} from "@angular/router";
 import { WithLoadingPipe } from './with-loading.pipe';
 import { StripLoadingPipe } from './strip-loading.pipe';
 import { ToastComponent } from './toast/toast.component';
+import { UserInfoComponent } from './user-info/user-info.component';
+import {FormsModule} from "@angular/forms";
 
 export { LoadingValue } from './model/loading-value';
 export { PageQuery } from './model/page-query';
@@ -48,7 +50,8 @@ export { PageQuery } from './model/page-query';
         SortedTableHeaderRowComponent,
         WithLoadingPipe,
         StripLoadingPipe,
-        ToastComponent
+        ToastComponent,
+        UserInfoComponent
     ],
     exports: [
         CommonModule,
@@ -84,6 +87,7 @@ export { PageQuery } from './model/page-query';
                 deps: [HttpClient]
             }
         }),
+        FormsModule,
     ]
 })
 export class SharedModule {
diff --git a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/shared/toast/toast.component.ts b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/shared/toast/toast.component.ts
index ff6a033..2873b8a 100644
--- a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/shared/toast/toast.component.ts
+++ b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/shared/toast/toast.component.ts
@@ -41,9 +41,7 @@ import {AppNotification} from "@app/model/app-notification";
       <ng-template #text>{{ toast.body }}</ng-template>
     </ngb-toast>
   `,
-  styles: [".ngb-toasts{margin:.5em;padding:0.5em;position:fixed;right:2px;top:20px;z-index:1200}"
-  ],
-  host: {'[class.ngb-toasts]': 'true'}
+  styles: [':host { margin:.5em; padding:1em; position:fixed; right:10px; top:40px; z-index:1200; }']
 })
 export class ToastComponent implements OnInit {
 
diff --git a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/shared/user-info/user-info.component.html b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/shared/user-info/user-info.component.html
new file mode 100644
index 0000000..a8b7c4f
--- /dev/null
+++ b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/shared/user-info/user-info.component.html
@@ -0,0 +1,87 @@
+<!--
+  ~ 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="col-md-12">
+    <h2>{{'me.title'|translate}}</h2>
+    <form #passwordForm="ngForm" (ngSubmit)="changePassword()" class="mt-4">
+        <div class="form-group row" *ngFor="let att of ['user_id','full_name','email','language']">
+            <label for="{{att}}" class="col-md-1 col-form-label">{{'users.attributes.' + att|translate}}</label>
+            <div class="col-md-4">
+                <input id="{{att}}" class="form-control" value="{{userService.userInfo[att]}}" readonly>
+            </div>
+        </div>
+        <div class="form-group row"
+             *ngFor="let dateAtt of ['timestamp_account_creation','timestamp_last_password_change']">
+            <label for="{{dateAtt}}" class="col-md-1 col-form-label">{{'users.attributes.' + dateAtt|translate}}</label>
+            <ng-container *ngIf="userService.userInfo[dateAtt]">
+                <div class="col-md-4">
+                    <input id="{{dateAtt}}" class="form-control"
+                           value="{{userService.userInfo[dateAtt]|date:'YYYY-MM-dd HH:mm:ss'}}" readonly>
+                </div>
+            </ng-container>
+        </div>
+        <div class="form-group row">
+            <div class="col-md-1">&nbsp;</div>
+            <div class="col-md-4">
+                <div class="form-check" *ngFor="let checkAtt of ['password_change_required','validated','locked']">
+                    <input id="{{checkAtt}}" type="checkbox" class="form-check-input disabled"
+                           [checked]="userService.userInfo[checkAtt]?'true':null" disabled="disabled">
+                    <label for="{{checkAtt}}"
+                           class="form-check-label">{{'users.attributes.' + checkAtt|translate}}</label>
+                </div>
+            </div>
+        </div>
+        <hr class="mt-3" />
+        <h2>{{'users.edit.changePasswordTitle'|translate}}</h2>
+        <div class="form-group row mt-4" >
+            <label for="current_password" class="col-md-1 col-form-label">{{'users.attributes.current_password'|translate}}</label>
+            <div class="col-md-4">
+                <input id="current_password" name="current_password" type="password" class="form-control" required
+                       [(ngModel)]="formData.current_password" #v_current_password="ngModel" [ngClass]="valid('current_password')">
+                <small class="invalid-feedback" *ngIf="v_current_password.errors?.required">{{'form.error.required'|translate}}</small>
+            </div>
+        </div>
+        <div class="form-group row" >
+            <label for="password" class="col-md-1 col-form-label">{{'users.attributes.new_password'|translate}}</label>
+            <div class="col-md-4">
+                <input id="password" name="password" type="password" class="form-control" required
+                       [ngClass]="valid('password')"
+                       [(ngModel)]="formData.password"  #v_password="ngModel" >
+                <small class="invalid-feedback" *ngIf="v_password.errors?.required">{{'form.error.required'|translate}}</small>
+                <small class="invalid-feedback" *ngIf="v_password.errors?.invalidpassword">{{v_password.errors.invalidpassword}}</small>
+            </div>
+        </div>
+        <div class="form-group row" >
+            <label for="confirm_password" class="col-md-1 col-form-label">{{'users.attributes.confirm_password'|translate}}</label>
+            <div class="col-md-4">
+                <input id="confirm_password" name="confirm_password" type="password" class="form-control" required
+                       [(ngModel)]="formData.confirm_password" [class.is-valid]="confirmIsValid()" [class.is-invalid]="confirmIsNotValid()">
+            </div>
+        </div>
+        <div class="form-group row" >
+            <div class="col-md-1">&nbsp;</div>
+            <div class="col-md-4">
+            <button class="btn btn-primary"
+                    type="submit">{{'users.edit.submitPassword'|translate}}</button>
+            </div>
+        </div>
+
+    </form>
+</div>
+
+
diff --git a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/shared/user-info/user-info.component.scss b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/shared/user-info/user-info.component.scss
new file mode 100644
index 0000000..343c3b1
--- /dev/null
+++ b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/shared/user-info/user-info.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/shared/user-info/user-info.component.spec.ts b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/shared/user-info/user-info.component.spec.ts
new file mode 100644
index 0000000..4f9cfbc
--- /dev/null
+++ b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/shared/user-info/user-info.component.spec.ts
@@ -0,0 +1,43 @@
+/*
+ * 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 { UserInfoComponent } from './user-info.component';
+
+describe('UserInfoComponent', () => {
+  let component: UserInfoComponent;
+  let fixture: ComponentFixture<UserInfoComponent>;
+
+  beforeEach(async () => {
+    await TestBed.configureTestingModule({
+      declarations: [ UserInfoComponent ]
+    })
+    .compileComponents();
+  });
+
+  beforeEach(() => {
+    fixture = TestBed.createComponent(UserInfoComponent);
+    component = fixture.componentInstance;
+    fixture.detectChanges();
+  });
+
+  it('should create', () => {
+    expect(component).toBeTruthy();
+  });
+});
diff --git a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/shared/user-info/user-info.component.ts b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/shared/user-info/user-info.component.ts
new file mode 100644
index 0000000..c317ef6
--- /dev/null
+++ b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/shared/user-info/user-info.component.ts
@@ -0,0 +1,103 @@
+/*
+ * 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 {Component, OnInit, ViewChild} from '@angular/core';
+import {NgForm} from '@angular/forms';
+import {UserService} from "@app/services/user.service";
+import {ToastService} from "@app/services/toast.service";
+import {ErrorResult} from "@app/model/error-result";
+
+@Component({
+  selector: 'app-user-info',
+  templateUrl: './user-info.component.html',
+  styleUrls: ['./user-info.component.scss']
+})
+export class UserInfoComponent implements OnInit {
+
+  @ViewChild('passwordForm') passwordForm: NgForm;
+
+  formData = {
+    current_password:"",
+    password: "",
+    confirm_password: ""
+  }
+
+
+  constructor(public userService: UserService, private toastService: ToastService) { }
+
+  ngOnInit(): void {
+  }
+
+  changePassword() {
+    console.log("Submit Password ")
+    if (!this.formData.current_password || this.formData.current_password.length==0) {
+      this.passwordForm.controls['current_password'].setErrors({'required':'true'})
+      this.passwordForm.controls['current_password'].markAsDirty()
+    }
+    if (!this.formData.password || this.formData.password.length==0) {
+      this.passwordForm.controls['password'].setErrors({'required':'true'})
+      this.passwordForm.controls['password'].markAsDirty()
+    }
+    if (this.passwordForm.valid) {
+      this.userService.changeOwnPassword(this.formData.current_password, this.formData.password, this.formData.confirm_password).subscribe(
+          val => {
+              this.toastService.showSuccessByKey('user-info','users.edit.passwordChanged')
+            this.formData.password=""
+            this.formData.current_password=""
+            this.formData.confirm_password=""
+            this.passwordForm.reset()
+          },
+          ( error : ErrorResult)=>{
+            console.log("Error " + error.error_messages[0].message);
+            if (error.error_messages.length>0) {
+              if (error.error_messages[0].error_key.startsWith('user.password.violation')) {
+                this.toastService.showError('user-info', error.error_messages[0].message)
+                this.passwordForm.controls['password'].setErrors({'invalidpassword':error.error_messages[0].message})
+              }
+            }
+          }
+      )
+    } else {
+      this.toastService.showErrorByKey('user-info','form.error.invaliddata')
+    }
+
+  }
+
+  confirmIsValid() : boolean {
+    return (this.formData.password && this.formData.password.length >0 &&
+        this.formData.confirm_password && this.formData.confirm_password.length>0 && this.formData.password==this.formData.confirm_password )
+  }
+
+  confirmIsNotValid() : boolean {
+    return (this.formData.password && this.formData.password.length>0 &&
+        this.formData.confirm_password && this.formData.confirm_password.length>0 && this.formData.password!=this.formData.confirm_password )
+  }
+
+  valid(field:string) {
+    if (this.passwordForm) {
+      let ctrl = this.passwordForm.controls[field];
+      if (ctrl && ( ctrl.dirty || ctrl.touched ) && ctrl.valid) {
+        return 'is-valid'
+      }
+      if (ctrl && ( ctrl.dirty || ctrl.touched  ) && ctrl.invalid) {
+        return 'is-invalid'
+      }
+    }
+  }
+
+}
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 2c00a41..4e8ca0c 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
@@ -128,6 +128,7 @@ export class ArchivaRequestService {
      * @param errorMsg the errorMsg as returned by a REST call
      */
     public translateError(errorMsg: ErrorMessage): string {
+        console.log("Translating error "+errorMsg.error_key)
         if (errorMsg.error_key != null && errorMsg.error_key != '') {
             let parms = {};
             if (errorMsg.args != null && errorMsg.args.length > 0) {
diff --git a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/services/user.service.ts b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/services/user.service.ts
index d42d77c..fa89b40 100644
--- a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/services/user.service.ts
+++ b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/services/user.service.ts
@@ -328,7 +328,7 @@ export class UserService implements OnInit, OnDestroy {
             }), map((httpResponse: HttpResponse<string>) => httpResponse.status == 200));
     }
 
-    public userRoleTree(userid:String): Observable<RoleTree> {
+    public userRoleTree(userid:string): Observable<RoleTree> {
         return this.rest.executeResponseCall<RoleTree>("get", "redback","users/"+userid+"/roletree", null).pipe(
             catchError((error: HttpErrorResponse)=>{
                 if (error.status==404) {
@@ -341,4 +341,18 @@ export class UserService implements OnInit, OnDestroy {
         ).pipe(map((httpResponse:HttpResponse<RoleTree>)=>httpResponse.body))
     }
 
+    public changeOwnPassword(current_password:string, password:string, confirm_password:string) {
+        let data = {
+            "user_id":this.userInfo.user_id,
+            "current_password":current_password,
+            "new_password":password,
+            "new_password_confirmation":confirm_password
+        }
+        return this.rest.executeRestCall<any>("post", "redback", "users/me/password/update", data).pipe(
+            catchError((error: HttpErrorResponse)=>{
+                return throwError(this.rest.getTranslatedErrorResult(error));
+            })
+        );
+    }
+
 }
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 e39cf21..645701e 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
@@ -6,7 +6,7 @@
     "userid": "User Id",
     "submit": "Login"
   },
-  "modal" : {
+  "modal": {
     "close": "Close"
   },
   "menu": {
@@ -19,7 +19,7 @@
   "error": {
     "modal": {
       "title": "Application Error",
-      "info":"The backend does not answer as expected. Please check, if your archiva service is running. See detail messages below."
+      "info": "The backend does not answer as expected. Please check, if your archiva service is running. See detail messages below."
     },
     "http": {
       "unknownError": "We got a bad response from the backend REST service. Maybe the connection is broken, or the service is down. Please check your network to the backend service and the archiva.log for any errors.",
@@ -47,7 +47,7 @@
     "user": {
       "section": "User",
       "users": "Manage Users",
-      "roles":"Manage Roles",
+      "roles": "Manage Roles",
       "config": "Security Configuration"
     },
     "doc": {
@@ -56,32 +56,38 @@
       "restapi": "REST API"
     }
   },
-  "api" : {
+  "api": {
     "rb.auth.invalid_credentials": "Invalid credentials given",
     "user.password.violation.length": "You must provide a password between {arg0} and {arg1} characters in length.",
     "user.password.violation.alpha": "You must provide a password containing at least {arg0} alphabetic {arg0, plural, one {character} other {characters}}.",
-    "user.password.violation.numeric":"You must provide a password containing at least {arg0} numeric {arg0, plural, one {character} other {characters}}.",
-    "user.password.violation.reuse":"The password must not match any of the previous {arg0} {arg0, plural, one {password} other {passwords}}.",
-    "user.password.violation.alphanum.only":"You must provide a password containing all alpha-numeric characters.",
-    "user.password.violation.whitespace.detected":"You must provide a password without whitespace characters."
+    "user.password.violation.numeric": "You must provide a password containing at least {arg0} numeric {arg0, plural, one {character} other {characters}}.",
+    "user.password.violation.reuse": "The password must not match any of the previous {arg0} {arg0, plural, one {password} other {passwords}}.",
+    "user.password.violation.alphanum.only": "You must provide a password containing all alpha-numeric characters.",
+    "user.password.violation.whitespace.detected": "You must provide a password without whitespace characters."
   },
   "users": {
-    "attributes":{
-        "id": "ID",
-        "user_id": "Login Name",
-        "email": "Email",
-        "full_name": "Name",
-        "validated": "User Validated",
-        "locked": "User Locked",
-        "password_change_required": "Password Change Required",
-        "last_login": "Last Login",
-        "created": "Created",
-        "permanent": "Permanent",
-        "last_password_change": "Last Password Change",
-        "password": "Password",
-        "confirm_password": "Confirm Password"
+    "attributes": {
+      "id": "ID",
+      "user_id": "Login Name",
+      "email": "Email",
+      "full_name": "Name",
+      "validated": "User Validated",
+      "locked": "User Locked",
+      "password_change_required": "Password Change Required",
+      "last_login": "Last Login",
+      "created": "Created",
+      "permanent": "Permanent",
+      "last_password_change": "Last Password Change",
+      "timestamp_last_password_change": "Last Password Change",
+      "timestamp_account_creation": "Account Created",
+      "timestamp_last_login": "Last Login",
+      "password": "Password",
+      "new_password": "New Password",
+      "current_password": "Current Password",
+      "confirm_password": "Confirm Password",
+      "language": "UI Language"
     },
-    "input" : {
+    "input": {
       "small": {
         "user_id": "Must be a unique key with at least {minSize} characters. No spaces allowed.",
         "full_name": "This is the display name of the user"
@@ -92,7 +98,6 @@
       "password": "Enter password",
       "confirm_password": "Confirm password"
     },
-
     "list": {
       "head": "List Users"
     },
@@ -107,10 +112,13 @@
       "head": "View/Edit User",
       "small": {
         "password": "If the password field is empty, it will not be updated."
-      }
+      },
+      "submitPassword": "Change Password",
+      "passwordChanged": "Password has been changed successfully",
+      "changePasswordTitle": "Change password"
     },
     "delete": {
-      "head":"Delete User",
+      "head": "Delete User",
       "modal": {
         "title": "Delete User",
         "text": "Are you sure? <br/> Do you want to delete the user <strong>{user_id}</strong>?"
@@ -164,7 +172,6 @@
       "assignable": "Assignable"
     }
   },
-
   "permissions": {
     "attributes": {
       "permission": "Permission",
@@ -182,7 +189,8 @@
     "error": {
       "required": "Value is empty. This is required.",
       "containsWhitespace": "Value must not contain whitespace.",
-      "userexists": "This user exists already."
+      "userexists": "This user exists already.",
+      "invaliddata": "The data is not valid"
     },
     "button": {
       "yes": "Yes",
@@ -197,8 +205,10 @@
     "action": "Action"
   },
   "password": {
-    "violations" : {
-
+    "violations": {
     }
+  },
+  "me": {
+    "title": "Current User Information"
   }
 }
\ No newline at end of file


[archiva] 01/02: Improving error handling

Posted by ma...@apache.org.
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 cb0e4d19d78956b536c62772ae042c281d07ad9f
Author: Martin Stockhammer <ma...@apache.org>
AuthorDate: Thu Dec 24 10:37:55 2020 +0100

    Improving error handling
---
 .../archiva-web/src/app/model/app-notification.ts  |  3 +-
 .../manage-roles-edit.component.ts                 | 35 +++++++++------
 .../manage-users-add.component.html                |  3 --
 .../manage-users-roles.component.ts                |  8 ++--
 .../paginated-entities.component.ts                | 27 +++---------
 .../app/modules/shared/toast/toast.component.ts    |  2 +-
 .../src/app/modules/shared/with-loading.pipe.ts    |  8 ++--
 .../archiva-web/src/app/services/role.service.ts   | 50 +++++++++++++++++-----
 .../archiva-web/src/app/services/toast.service.ts  | 42 +++++++++++++++---
 .../src/main/archiva-web/src/assets/i18n/en.json   | 12 +++++-
 10 files changed, 127 insertions(+), 63 deletions(-)

diff --git a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/model/app-notification.ts b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/model/app-notification.ts
index 787762b..ae1b0a4 100644
--- a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/model/app-notification.ts
+++ b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/model/app-notification.ts
@@ -33,7 +33,6 @@ export class AppNotification {
         this.header = header;
         this.body = body;
         this.timestamp = timestamp;
-        console.log("Options " + JSON.stringify(options));
         if (options.classname) {
             this.classname = options.classname;
         }
@@ -49,7 +48,7 @@ export class AppNotification {
     }
 
     public toString(): string {
-        return this.origin + ',classname:' + this.classname + ", delay:" + this.delay +", context: "+JSON.stringify(this.contextData);
+        return this.origin + ',classname:' + this.classname + ", delay:" + this.delay ;
     }
 
 }
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 321557b..7654ee9 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,16 +16,7 @@
  * under the License.
  */
 
-import {
-    AfterContentInit,
-    ChangeDetectorRef,
-    Component,
-    EventEmitter,
-    OnInit,
-    Output,
-    TemplateRef,
-    ViewChild
-} from '@angular/core';
+import {AfterContentInit, Component, EventEmitter, OnInit, Output, ViewChild} from '@angular/core';
 import {ActivatedRoute} from "@angular/router";
 import {FormBuilder, Validators} from "@angular/forms";
 import {RoleService} from "@app/services/role.service";
@@ -42,6 +33,7 @@ import {UserService} from "@app/services/user.service";
 import {UserInfo} from '@app/model/user-info';
 import {HttpResponse} from "@angular/common/http";
 import {PaginatedEntitiesComponent} from "@app/modules/shared/paginated-entities/paginated-entities.component";
+import {ToastService} from "@app/services/toast.service";
 
 @Component({
     selector: 'app-manage-roles-edit',
@@ -73,7 +65,7 @@ export class ManageRolesEditComponent extends EditBaseComponent<Role> implements
     roleIdEvent: EventEmitter<string> = new EventEmitter<string>(true);
 
     constructor(private route: ActivatedRoute, public roleService: RoleService, private userService: UserService,
-                public fb: FormBuilder, private changeDetect : ChangeDetectorRef) {
+                public fb: FormBuilder, private toastService: ToastService) {
         super(fb);
         super.init(fb.group({
             id: [''],
@@ -151,7 +143,9 @@ export class ManageRolesEditComponent extends EditBaseComponent<Role> implements
                 } else {
                     return this.roleService.getRole(myId).pipe(tap(role => {
                         this.roleCache.set(role.id, role);
-                    }),catchError(() => of(this.createRole(id))));
+                    }),catchError((error : ErrorResult) => {
+                        this.showError(error, "roles.edit.errors.retrieveFailed")
+                        return of(this.createRole(id)); }));
                 }
             }));
     }
@@ -192,6 +186,7 @@ export class ManageRolesEditComponent extends EditBaseComponent<Role> implements
                 this.error = true;
                 this.success = false;
                 this.errorResult = err;
+                this.showError(err, 'roles.edit.errors.updateFailed',{'role_id':role.id})
                 return [];
             })
         ).subscribe(roleInfo => {
@@ -199,6 +194,7 @@ export class ManageRolesEditComponent extends EditBaseComponent<Role> implements
             this.success = true;
             this.errorResult = null;
             this.result = roleInfo;
+            this.showSuccess('roles.edit.success.updated',{'role_id':role.id})
             this.editMode = false;
         });
 
@@ -233,6 +229,17 @@ export class ManageRolesEditComponent extends EditBaseComponent<Role> implements
         return item.user_id;
     }
 
+    showError(err: ErrorResult, errorKey:string, params:any={}) : void {
+        let message = err.error_messages.length>0?err.error_messages[0]:''
+        params['message']=message
+        this.toastService.showErrorByKey('manage-roles-edit',errorKey,params)
+    }
+
+    showSuccess(successKey:string, params:any={}) : void  {
+        console.log("Success " + successKey + " - " + JSON.stringify(params));
+        this.toastService.showSuccessByKey('manage-roles-edit',successKey,params)
+    }
+
     assignUserRole() {
         let userId;
         if (typeof(this.userSearchModel)=='string') {
@@ -248,6 +255,7 @@ export class ManageRolesEditComponent extends EditBaseComponent<Role> implements
                     this.error = true;
                     this.success = false;
                     this.errorResult = err;
+                    this.showError(err, 'roles.edit.errors.assignFailed', {'role_id':this.editRole.id,'user_id':userId})
                     return [];
                 })
             ).subscribe((response : HttpResponse<Role>)  => {
@@ -256,6 +264,7 @@ export class ManageRolesEditComponent extends EditBaseComponent<Role> implements
                 this.errorResult = null;
                 this.result = response.body;
                 this.roleUserComponent.changePage(1);
+                this.showSuccess('roles.edit.success.assign',{'role_id':this.editRole.id,'user_id':userId})
                 this.userSearchModel=''
             });
         }
@@ -269,6 +278,7 @@ export class ManageRolesEditComponent extends EditBaseComponent<Role> implements
                     this.error = true;
                     this.success = false;
                     this.errorResult = err;
+                    this.showError(err, 'roles.edit.errors.unassignFailed',{'role_id':this.editRole.id,'user_id':user_id})
                     return [];
                 })
             ).subscribe((response : HttpResponse<Role>)  => {
@@ -278,6 +288,7 @@ export class ManageRolesEditComponent extends EditBaseComponent<Role> implements
                     this.errorResult = null;
                     this.result = response.body;
                     this.roleUserComponent.changePage(1);
+                    this.showSuccess('roles.edit.success.unassign',{'role_id':this.editRole.id,'user_id':user_id})
                 }
             );
         }
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 915c812..c04ee50 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
@@ -90,9 +90,6 @@
         <button class="btn btn-primary" type="submit"
                 [attr.disabled]="userForm.valid?null:true">{{'users.add.submit'|translate}}</button>
     </div>
-    <div class="form-group col-md-8">
-        <button class="btn btn-primary" (click)="showMessage()">Show Message</button>
-    </div>
 
     <ng-template #successTmpl let-userId="user_id">
         User <a [routerLink]="['/security','users','edit',userId]">{{userId}}</a> was added to the list.
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 1beb1f0..9608bba 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
@@ -17,16 +17,16 @@
  */
 
 import {AfterViewInit, Component, EventEmitter, OnInit, Output} from '@angular/core';
-import { Role } from '@app/model/role';
+import {Role} from '@app/model/role';
 import {UserService} from "@app/services/user.service";
 import {ActivatedRoute} from "@angular/router";
-import {catchError, filter, map, multicast, share, switchMap, tap} from "rxjs/operators";
+import {catchError, filter, map, 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, of} from "rxjs";
+import {Observable} from "rxjs";
 import {Util} from "@app/modules/shared/shared.module";
-import { RoleResult } from './role-result';
+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";
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 8e2e166..f7d2188 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
@@ -16,26 +16,13 @@
  * under the License.
  */
 
-import {AfterViewInit, ChangeDetectorRef, Component, EventEmitter, Input, OnInit, Output} from '@angular/core';
-import {concat, merge, Observable, of, pipe, Subject} from "rxjs";
-import {
-    concatAll,
-    debounceTime,
-    delay,
-    distinctUntilChanged,
-    filter,
-    map,
-    mergeMap,
-    pluck,
-    share,
-    startWith,
-    switchMap,
-    tap
-} 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 {AfterViewInit, Component, EventEmitter, Input, OnInit, Output} from '@angular/core';
+import {concat, merge, Observable, of, Subject} from "rxjs";
+import {debounceTime, distinctUntilChanged, filter, map, pluck, startWith, switchMap} from "rxjs/operators";
+import {EntityService} from "@app/model/entity-service";
+import {FieldToggle} from "@app/model/field-toggle";
+import {PageQuery} from "../model/page-query";
+import {LoadingValue} from '../model/loading-value';
 import {PagedResult} from "@app/model/paged-result";
 
 
diff --git a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/shared/toast/toast.component.ts b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/shared/toast/toast.component.ts
index 0813f6c..ff6a033 100644
--- a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/shared/toast/toast.component.ts
+++ b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/shared/toast/toast.component.ts
@@ -41,7 +41,7 @@ import {AppNotification} from "@app/model/app-notification";
       <ng-template #text>{{ toast.body }}</ng-template>
     </ngb-toast>
   `,
-  styles: [".ngb-toasts{margin:.5em;padding:0.5em;position:fixed;right:2px;top:2px;z-index:1200}"
+  styles: [".ngb-toasts{margin:.5em;padding:0.5em;position:fixed;right:2px;top:20px;z-index:1200}"
   ],
   host: {'[class.ngb-toasts]': 'true'}
 })
diff --git a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/shared/with-loading.pipe.ts b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/shared/with-loading.pipe.ts
index 8de45ed..dbb9cd0 100644
--- a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/shared/with-loading.pipe.ts
+++ b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/shared/with-loading.pipe.ts
@@ -16,10 +16,10 @@
  * under the License.
  */
 
-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";
+import {Pipe, PipeTransform} from '@angular/core';
+import {isObservable, of} from 'rxjs';
+import {catchError, map, startWith} from 'rxjs/operators';
+import {LoadingValue} from "@app/modules/shared/model/loading-value";
 
 @Pipe({
   name: 'withLoading'
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 e5b7e39..5886c8a 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
@@ -19,13 +19,14 @@
 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 {Observable, throwError} from 'rxjs';
 import { Role } from '@app/model/role';
-import {HttpResponse} from "@angular/common/http";
+import {HttpErrorResponse, 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";
 import { User } from '@app/model/user';
+import {catchError} from "rxjs/operators";
 
 @Injectable({
   providedIn: 'root'
@@ -35,15 +36,24 @@ export class RoleService {
   constructor(private rest: ArchivaRequestService) { }
 
   public getTemplates() : Observable<RoleTemplate[]> {
-    return this.rest.executeRestCall("get", "redback", "roles/templates", null);
+    return this.rest.executeRestCall<RoleTemplate[]>("get", "redback", "roles/templates", null).pipe(
+        catchError((error: HttpErrorResponse) => {
+          return throwError(this.rest.getTranslatedErrorResult(error));
+        }));
   }
 
   public assignRole(roleId, userId) : Observable<HttpResponse<Role>> {
-    return this.rest.executeResponseCall<Role>("put", "redback", "roles/" + roleId + "/user/" + userId, null);
+    return this.rest.executeResponseCall<Role>("put", "redback", "roles/" + roleId + "/user/" + userId, null).pipe(
+        catchError((error: HttpErrorResponse) => {
+          return throwError(this.rest.getTranslatedErrorResult(error));
+        }));
   }
 
   public unAssignRole(roleId, userId) : Observable<HttpResponse<Role>> {
-    return this.rest.executeResponseCall<Role>("delete", "redback", "roles/" + roleId + "/user/" + userId, null);
+    return this.rest.executeResponseCall<Role>("delete", "redback", "roles/" + roleId + "/user/" + userId, null).pipe(
+        catchError((error: HttpErrorResponse) => {
+          return throwError(this.rest.getTranslatedErrorResult(error));
+        }));
   }
 
   public query(searchTerm: string, offset: number = 0, limit: number = 10, orderBy: string[] = ['id'], order: string = 'asc'): Observable<PagedResult<Role>> {
@@ -59,7 +69,10 @@ export class RoleService {
       'limit': limit,
       'orderBy': orderBy,
       'order': order
-    });
+    }).pipe(
+        catchError((error: HttpErrorResponse) => {
+          return throwError(this.rest.getTranslatedErrorResult(error));
+        }));
   }
 
   public queryAssignedUsers(roleId: string,
@@ -77,7 +90,10 @@ export class RoleService {
       'limit': limit,
       'orderBy': orderBy,
       'order': order
-    });
+    }).pipe(
+        catchError((error: HttpErrorResponse) => {
+          return throwError(this.rest.getTranslatedErrorResult(error));
+        }));
   }
 
   /**
@@ -108,7 +124,10 @@ export class RoleService {
       'limit': limit,
       'orderBy': orderBy,
       'order': order
-    });
+    }).pipe(
+        catchError((error: HttpErrorResponse) => {
+          return throwError(this.rest.getTranslatedErrorResult(error));
+        }));
   }
 
   public queryUnAssignedUsers(roleId: string,
@@ -126,16 +145,25 @@ export class RoleService {
       'limit': limit,
       'orderBy': orderBy,
       'order': order
-    });
+    }).pipe(
+        catchError((error: HttpErrorResponse) => {
+          return throwError(this.rest.getTranslatedErrorResult(error));
+        }));
   }
 
 
   public getRole(roleId:string) : Observable<Role> {
-    return this.rest.executeRestCall("get", "redback", "roles/" + roleId, null);
+    return this.rest.executeRestCall<Role>("get", "redback", "roles/" + roleId, null).pipe(
+        catchError((error: HttpErrorResponse) => {
+          return throwError(this.rest.getTranslatedErrorResult(error));
+        }));
   }
 
   public updateRole(role:RoleUpdate) : Observable<Role> {
-    return this.rest.executeRestCall("patch", "redback", "roles/" + role.id, role);
+    return this.rest.executeRestCall<Role>("patch", "redback", "roles/" + role.id, role).pipe(
+        catchError((error: HttpErrorResponse) => {
+          return throwError(this.rest.getTranslatedErrorResult(error));
+        }));
   }
 
 }
diff --git a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/services/toast.service.ts b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/services/toast.service.ts
index b168a0f..1b1a934 100644
--- a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/services/toast.service.ts
+++ b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/services/toast.service.ts
@@ -19,6 +19,7 @@
 import { Injectable, TemplateRef } from '@angular/core';
 import {AppNotification} from "@app/model/app-notification";
 import {not} from "rxjs/internal-compatibility";
+import {TranslateService} from "@ngx-translate/core";
 
 @Injectable({
   providedIn: 'root'
@@ -30,9 +31,9 @@ export class ToastService {
   toasts:AppNotification[]=[]
   toastHistory:AppNotification[]=[]
 
-  constructor() { }
+  constructor(private translator: TranslateService) { }
 
-  show(origin:string, textOrTpl: string | TemplateRef<any>, options: any = {}) {
+  public show(origin:string, textOrTpl: string | TemplateRef<any>, options: any = {}): void {
     let notification = new AppNotification(origin, textOrTpl, "", options);
     this.toasts.push(notification);
     this.toastHistory.push(notification);
@@ -45,7 +46,7 @@ export class ToastService {
     console.log("Notification " + notification);
   }
 
-  showStandard(origin:string,textOrTpl:string|TemplateRef<any>, options:any={}) {
+  public showStandard(origin:string,textOrTpl:string|TemplateRef<any>, options:any={}) : void {
     options.classname='bg-primary'
     if (!options.delay) {
       options.delay=8000
@@ -53,7 +54,17 @@ export class ToastService {
     this.show(origin,textOrTpl,options)
   }
 
-  showError(origin:string,textOrTpl:string|TemplateRef<any>, options:any={}) {
+  public showStandardByKey(origin:string,translationKey:string, params:any={}, options:any={} ) : void {
+    let message:string;
+    if (params) {
+      message = this.translator.instant(translationKey, params);
+    } else {
+      message = this.translator.instant(translationKey);
+    }
+    this.showStandard(origin, message, options);
+  }
+
+  public showError(origin:string,textOrTpl:string|TemplateRef<any>, options:any={}) : void {
     options.classname='bg-warning'
     options.type='error'
     if (!options.delay) {
@@ -62,7 +73,17 @@ export class ToastService {
     this.show(origin,textOrTpl,options)
   }
 
-  showSuccess(origin:string,textOrTpl:string|TemplateRef<any>, options:any={}) {
+  public showErrorByKey(origin:string,translationKey:string, params:any={}, options:any={} ) : void {
+    let message:string;
+    if (params) {
+      message = this.translator.instant(translationKey, params);
+    } else {
+      message = this.translator.instant(translationKey);
+    }
+    this.showError(origin, message, options);
+  }
+
+  public showSuccess(origin:string,textOrTpl:string|TemplateRef<any>, options:any={}) : void {
     options.classname='bg-info'
     options.type='success'
     if (!options.delay) {
@@ -71,6 +92,17 @@ export class ToastService {
     this.show(origin,textOrTpl,options)
   }
 
+  public showSuccessByKey(origin:string,translationKey:string, params:any={}, options:any={} ) : void {
+    let message:string;
+    if (params) {
+      message = this.translator.instant(translationKey, params);
+    } else {
+      message = this.translator.instant(translationKey);
+    }
+    this.showSuccess(origin, message, options);
+  }
+
+
   remove(toast) {
     this.toasts = this.toasts.filter(t => t != toast);
   }
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 0be67d3..e39cf21 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
@@ -143,7 +143,17 @@
       "noUsersAssigned": "There are no users assigned to this role",
       "assignUserSearch": "Search and assign users to this role",
       "userSearchFailed": "Sorry, could not load users",
-      "assignButton": "Assign"
+      "assignButton": "Assign",
+      "errors": {
+        "updateFailed": "Could not update role {role_id}: {message}",
+        "assignFailed": "Could not assign role {role_id} to user {user_id}: {message}",
+        "retrieveFailed": "Could not retrieve role information: {message}"
+      },
+      "success": {
+        "updated": "Role {role_id} was updated",
+        "assign": "Role {role_id} was assigned to user {user_id}",
+        "unassign": "Removed assignment of {role_id} to {user_id}"
+      }
     },
     "attributes": {
       "id": "Identifier",