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


The following commit(s) were added to refs/heads/master by this push:
     new 7744b08  Adding toast notification
7744b08 is described below

commit 7744b086d7f71b67f436c0f617d262b2ae91c8f6
Author: Martin Stockhammer <[email protected]>
AuthorDate: Wed Dec 23 22:43:04 2020 +0100

    Adding toast notification
---
 .../main/archiva-web/src/app/app.component.html    |  1 +
 .../archiva-web/src/app/model/app-notification.ts  | 55 ++++++++++++++++
 .../archiva-web/src/app/model/error-message.ts     | 10 +++
 .../manage-users-add.component.html                | 22 +++++--
 .../manage-users-add/manage-users-add.component.ts | 33 ++++++++--
 .../security/users/manage-users-base.component.ts  | 10 +++
 .../src/app/modules/shared/shared.module.ts        | 12 +++-
 .../shared/toast/toast.component.spec.ts}          | 30 +++++++--
 .../app/modules/shared/toast/toast.component.ts    | 61 +++++++++++++++++
 .../toast.service.spec.ts}                         | 21 ++++--
 .../archiva-web/src/app/services/toast.service.ts  | 77 ++++++++++++++++++++++
 .../src/main/archiva-web/src/assets/i18n/en.json   |  3 +-
 12 files changed, 307 insertions(+), 28 deletions(-)

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 e1d9bb0..c49a00e 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
@@ -35,6 +35,7 @@
         <button type="button" class="btn btn-danger" 
(click)="modal.close('Save click')">{{'modal.close'|translate}}</button>
     </div>
 </ng-template>
+<app-toasts aria-live="polite" aria-atomic="true"></app-toasts>
 <div class="app d-flex flex-column">
     <header>
         <nav class="navbar navbar-expand-md fixed-top navbar-light " 
style="background-color: #c6cbd2;">
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
new file mode 100644
index 0000000..787762b
--- /dev/null
+++ 
b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/model/app-notification.ts
@@ -0,0 +1,55 @@
+/*
+ * 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 {TemplateRef} from "@angular/core";
+
+export class AppNotification {
+    origin: string;
+    header: string;
+    body: string | TemplateRef<any>;
+    timestamp: Date;
+    classname: string='';
+    delay:number=5000;
+    contextData:any;
+    type:string='normal'
+
+    constructor(origin: string, body: string|TemplateRef<any>, 
header:string="", options: any = {}, timestamp:Date = new Date()) {
+        this.origin = origin
+        this.header = header;
+        this.body = body;
+        this.timestamp = timestamp;
+        console.log("Options " + JSON.stringify(options));
+        if (options.classname) {
+            this.classname = options.classname;
+        }
+        if (options.delay) {
+            this.delay = options.delay;
+        }
+        if (options.contextData) {
+            this.contextData = options.contextData;
+        }
+        if (options.type)  {
+            this.type = options.type;
+        }
+    }
+
+    public toString(): string {
+        return this.origin + ',classname:' + this.classname + ", delay:" + 
this.delay +", context: "+JSON.stringify(this.contextData);
+    }
+
+}
diff --git 
a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/model/error-message.ts
 
b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/model/error-message.ts
index 479f949..ff3f16c 100644
--- 
a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/model/error-message.ts
+++ 
b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/model/error-message.ts
@@ -20,4 +20,14 @@ export class ErrorMessage {
     error_key: string;
     args: string[];
     message: string;
+
+    static of(messageString:string): ErrorMessage {
+        const msg = new ErrorMessage()
+        msg.message = messageString;
+        return msg;
+    }
+
+    public toString() {
+        return this.message;
+    }
 }
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 1f5e246..915c812 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
@@ -54,6 +54,9 @@
         <input type="password" class="form-control" formControlName="password" 
id="password"
                [ngClass]="valid('password')"
                placeholder="{{'users.input.password'|translate}}">
+        <div *ngFor="let error of getErrorsFor('password')" 
class="invalid-feedback">
+            {{error}}
+        </div>
     </div>
     <div class="form-group col-md-8">
         <label for="confirm_password">{{'users.attributes.confirm_password' 
|translate}}</label>
@@ -87,16 +90,21 @@
         <button class="btn btn-primary" type="submit"
                 
[attr.disabled]="userForm.valid?null:true">{{'users.add.submit'|translate}}</button>
     </div>
-    <div *ngIf="success" class="alert alert-success" role="alert">
-        User <a 
[routerLink]="['/security','users','edit',result?.user_id]">{{result?.user_id}}</a>
 was added to the list.
+    <div class="form-group col-md-8">
+        <button class="btn btn-primary" (click)="showMessage()">Show 
Message</button>
     </div>
-    <div *ngIf="error" class="alert alert-danger" role="alert" >
-        <h4 class="alert-heading">{{'users.add.errortitle'|translate}}</h4>
-        <ng-container *ngFor="let message of errorResult?.error_messages; 
first as isFirst" >
-            <hr *ngIf="!isFirst">
+
+    <ng-template #successTmpl let-userId="user_id">
+        User <a 
[routerLink]="['/security','users','edit',userId]">{{userId}}</a> was added to 
the list.
+    </ng-template>
+    <ng-template #errorTmpl let-messages="error_messages">
+        <h4 class="alert-heading">{{'users.add.errortitle1'|translate}}</h4>
+        <p>{{'users.add.errortitle2'|translate}}</p>
+        <ng-container *ngFor="let message of messages; first as isFirst" >
+            <hr>
             <p>{{message.message}}</p>
         </ng-container>
-    </div>
+    </ng-template>
 
 
 </form>
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 1cd5c36..227e7d9 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
@@ -16,13 +16,15 @@
  * under the License.
  */
 
-import {Component, OnInit} from '@angular/core';
-import {AbstractControl, FormBuilder, FormControl, FormGroup, 
ValidationErrors, ValidatorFn} from '@angular/forms';
-import {UserService} from "../../../../services/user.service";
-import {ErrorResult} from "../../../../model/error-result";
+import {Component, OnInit, TemplateRef, ViewChild} from '@angular/core';
+import {FormBuilder} from '@angular/forms';
+import {UserService} from "@app/services/user.service";
+import {ErrorResult} from "@app/model/error-result";
 import {catchError} from "rxjs/operators";
-import {UserInfo} from "../../../../model/user-info";
+import {UserInfo} from "@app/model/user-info";
 import {ManageUsersBaseComponent} from "../manage-users-base.component";
+import {ToastService} from "@app/services/toast.service";
+import {ErrorMessage} from "@app/model/error-message";
 
 @Component({
     selector: 'app-manage-users-add',
@@ -31,7 +33,10 @@ import {ManageUsersBaseComponent} from 
"../manage-users-base.component";
 })
 export class ManageUsersAddComponent extends ManageUsersBaseComponent 
implements OnInit {
 
-    constructor(userService: UserService, fb: FormBuilder) {
+    @ViewChild('errorTmpl') public errorTmpl: TemplateRef<any>;
+    @ViewChild('successTmpl') public successTmpl: TemplateRef<any>;
+
+    constructor(userService: UserService, fb: FormBuilder, private 
toastService: ToastService) {
         super(userService, fb);
 
     }
@@ -61,12 +66,15 @@ export class ManageUsersAddComponent extends 
ManageUsersBaseComponent implements
                 this.errorResult = error;
                 this.success = false;
                 this.error = true;
+                
this.toastService.showError('manage-users-add',this.errorTmpl,{contextData:this.errorResult})
+
                 return [];
                 // return throwError(error);
             })).subscribe((user: UserInfo) => {
                 this.result = user;
                 this.success = true;
                 this.error = false;
+                
this.toastService.showSuccess('manage-users-add',this.successTmpl,{contextData:this.result})
                 this.userForm.reset(this.formInitialValues);
             });
         }
@@ -74,7 +82,18 @@ export class ManageUsersAddComponent extends 
ManageUsersBaseComponent implements
 
 
 
-
+    showMessage() {
+        this.result=new UserInfo()
+        this.result.user_id='XXXXX'
+        const errorResult : ErrorResult = new ErrorResult([
+            ErrorMessage.of('Not so good'),
+            ErrorMessage.of('Completely crap')
+        ]);
+        console.log(JSON.stringify(errorResult));
+        errorResult.status=422;
+        
this.toastService.showSuccess('manage-users-add',this.successTmpl,{contextData:this.result,delay:1000})
+        
this.toastService.showError('manage-users-add',this.errorTmpl,{contextData:errorResult,delay:10000})
+    }
 
 }
 
diff --git 
a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/security/users/manage-users-base.component.ts
 
b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/security/users/manage-users-base.component.ts
index 012d704..708c8e7 100644
--- 
a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/security/users/manage-users-base.component.ts
+++ 
b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/security/users/manage-users-base.component.ts
@@ -96,6 +96,16 @@ export class ManageUsersBaseComponent {
         }
     }
 
+    public getErrorsFor(formField:string) : string[] {
+        let field=this.userForm.get(formField)
+        if (field) {
+            if (field.errors) {
+                return Object.values(field.errors);
+            }
+        }
+        return []
+    }
+
     /**
      * Async validator with debounce time
      * @constructor
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 76bd984..a6bb547 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
@@ -26,7 +26,8 @@ import {
     NgbModalModule,
     NgbPaginationModule,
     NgbTooltipModule,
-    NgbTypeaheadModule
+    NgbTypeaheadModule,
+    NgbToastModule
 } from "@ng-bootstrap/ng-bootstrap";
 import {TranslateCompiler, TranslateLoader, TranslateModule} from 
"@ngx-translate/core";
 import {TranslateMessageFormatCompiler} from 
"ngx-translate-messageformat-compiler";
@@ -35,6 +36,7 @@ import {TranslateHttpLoader} from 
"@ngx-translate/http-loader";
 import {RouterModule} from "@angular/router";
 import { WithLoadingPipe } from './with-loading.pipe';
 import { StripLoadingPipe } from './strip-loading.pipe';
+import { ToastComponent } from './toast/toast.component';
 
 export { LoadingValue } from './model/loading-value';
 export { PageQuery } from './model/page-query';
@@ -45,7 +47,8 @@ export { PageQuery } from './model/page-query';
         SortedTableHeaderComponent,
         SortedTableHeaderRowComponent,
         WithLoadingPipe,
-        StripLoadingPipe
+        StripLoadingPipe,
+        ToastComponent
     ],
     exports: [
         CommonModule,
@@ -56,17 +59,20 @@ export { PageQuery } from './model/page-query';
         NgbAccordionModule,
         NgbModalModule,
         NgbTypeaheadModule,
+        NgbToastModule,
         PaginatedEntitiesComponent,
         SortedTableHeaderComponent,
         SortedTableHeaderRowComponent,
         WithLoadingPipe,
-        StripLoadingPipe
+        StripLoadingPipe,
+        ToastComponent
     ],
     imports: [
         CommonModule,
         RouterModule,
         NgbPaginationModule,
         NgbTooltipModule,
+        NgbToastModule,
         TranslateModule.forChild({
             compiler: {
                 provide: TranslateCompiler,
diff --git 
a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/model/error-message.ts
 
b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/shared/toast/toast.component.spec.ts
similarity index 56%
copy from 
archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/model/error-message.ts
copy to 
archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/shared/toast/toast.component.spec.ts
index 479f949..1ccb4d7 100644
--- 
a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/model/error-message.ts
+++ 
b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/shared/toast/toast.component.spec.ts
@@ -16,8 +16,28 @@
  * under the License.
  */
 
-export class ErrorMessage {
-    error_key: string;
-    args: string[];
-    message: string;
-}
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { ToastComponent } from './toast.component';
+
+describe('ToastComponent', () => {
+  let component: ToastComponent;
+  let fixture: ComponentFixture<ToastComponent>;
+
+  beforeEach(async () => {
+    await TestBed.configureTestingModule({
+      declarations: [ ToastComponent ]
+    })
+    .compileComponents();
+  });
+
+  beforeEach(() => {
+    fixture = TestBed.createComponent(ToastComponent);
+    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/toast/toast.component.ts
 
b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/shared/toast/toast.component.ts
new file mode 100644
index 0000000..0813f6c
--- /dev/null
+++ 
b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/shared/toast/toast.component.ts
@@ -0,0 +1,61 @@
+/*
+ * 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 } from '@angular/core';
+import {ToastService} from "@app/services/toast.service";
+import {TemplateRef} from "@angular/core";
+import {AppNotification} from "@app/model/app-notification";
+
+@Component({
+  selector: 'app-toasts',
+  template: `
+    <ngb-toast
+        *ngFor="let toast of toastService.toasts"
+        [class]="toast.classname"
+        [autohide]="autohide"
+        [delay]="toast.delay || 5000"
+        (hidden)="toastService.remove(toast); autohide=true;"
+        (mouseenter)="autohide = false"
+        (mouseleave)="autohide = true"
+    >
+      <i *ngIf="toast.type=='error'" class="fas fa-exclamation-triangle"></i>
+      <ng-template [ngIf]="isTemplate(toast)" [ngIfElse]="text">
+        <ng-template [ngTemplateOutlet]="toast.body" 
[ngTemplateOutletContext]="toast.contextData" ></ng-template>
+      </ng-template>
+
+      <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}"
+  ],
+  host: {'[class.ngb-toasts]': 'true'}
+})
+export class ToastComponent implements OnInit {
+
+  autohide:boolean=true;
+
+  constructor(public toastService:ToastService) { }
+
+  ngOnInit(): void {
+  }
+
+  isTemplate(toast:AppNotification) {
+    console.log("Context data: "+JSON.stringify(toast.contextData))
+    return toast.body instanceof TemplateRef; }
+
+}
diff --git 
a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/model/error-message.ts
 
b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/services/toast.service.spec.ts
similarity index 69%
copy from 
archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/model/error-message.ts
copy to 
archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/services/toast.service.spec.ts
index 479f949..38c4a5e 100644
--- 
a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/model/error-message.ts
+++ 
b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/services/toast.service.spec.ts
@@ -16,8 +16,19 @@
  * under the License.
  */
 
-export class ErrorMessage {
-    error_key: string;
-    args: string[];
-    message: string;
-}
+import { TestBed } from '@angular/core/testing';
+
+import { ToastService } from './toast.service';
+
+describe('ToastService', () => {
+  let service: ToastService;
+
+  beforeEach(() => {
+    TestBed.configureTestingModule({});
+    service = TestBed.inject(ToastService);
+  });
+
+  it('should be created', () => {
+    expect(service).toBeTruthy();
+  });
+});
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
new file mode 100644
index 0000000..b168a0f
--- /dev/null
+++ 
b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/services/toast.service.ts
@@ -0,0 +1,77 @@
+/*
+ * 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, TemplateRef } from '@angular/core';
+import {AppNotification} from "@app/model/app-notification";
+import {not} from "rxjs/internal-compatibility";
+
+@Injectable({
+  providedIn: 'root'
+})
+export class ToastService {
+
+  maxNotifications:number=10
+  maxHistory:number=100
+  toasts:AppNotification[]=[]
+  toastHistory:AppNotification[]=[]
+
+  constructor() { }
+
+  show(origin:string, textOrTpl: string | TemplateRef<any>, options: any = {}) 
{
+    let notification = new AppNotification(origin, textOrTpl, "", options);
+    this.toasts.push(notification);
+    this.toastHistory.push(notification);
+    if (this.toasts.length>this.maxNotifications) {
+      this.toasts.splice(0, 1);
+    }
+    if (this.toastHistory.length>this.maxHistory) {
+      this.toastHistory.splice(0, 1);
+    }
+    console.log("Notification " + notification);
+  }
+
+  showStandard(origin:string,textOrTpl:string|TemplateRef<any>, 
options:any={}) {
+    options.classname='bg-primary'
+    if (!options.delay) {
+      options.delay=8000
+    }
+    this.show(origin,textOrTpl,options)
+  }
+
+  showError(origin:string,textOrTpl:string|TemplateRef<any>, options:any={}) {
+    options.classname='bg-warning'
+    options.type='error'
+    if (!options.delay) {
+      options.delay=10000
+    }
+    this.show(origin,textOrTpl,options)
+  }
+
+  showSuccess(origin:string,textOrTpl:string|TemplateRef<any>, options:any={}) 
{
+    options.classname='bg-info'
+    options.type='success'
+    if (!options.delay) {
+      options.delay=8000
+    }
+    this.show(origin,textOrTpl,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 297c9f2..0be67d3 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
@@ -99,7 +99,8 @@
     "add": {
       "head": "Add User",
       "submit": "Add User",
-      "errortitle": "Could not add the user. Please check the following error 
messages."
+      "errortitle1": "Could not add the user!",
+      "errortitle2": "Please check the following error messages:"
     },
     "edit": {
       "submit": "Save Changes",

Reply via email to