diff options
author | Martin Stockhammer <martin_s@apache.org> | 2020-11-12 21:39:17 +0100 |
---|---|---|
committer | Martin Stockhammer <martin_s@apache.org> | 2020-11-12 21:39:17 +0100 |
commit | ef97e905a3a1d01a22379ceef9f4399f13852002 (patch) | |
tree | 6babdaac020b6fc8a1e698c53ab8088012dcc1f2 | |
parent | dd422882cac0e1634bcfe73b5e6707ab40924b78 (diff) | |
download | archiva-ef97e905a3a1d01a22379ceef9f4399f13852002.tar.gz archiva-ef97e905a3a1d01a22379ceef9f4399f13852002.zip |
Improving localization. Adding user edit.
10 files changed, 345 insertions, 101 deletions
diff --git a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/package-lock.json b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/package-lock.json index e3aaebcc2..f3e9dfff0 100644 --- a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/package-lock.json +++ b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/package-lock.json @@ -7758,6 +7758,14 @@ } } }, + "make-plural": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/make-plural/-/make-plural-4.3.0.tgz", + "integrity": "sha512-xTYd4JVHpSCW+aqDof6w/MebaMVNTVYBZhbB/vi513xXdiPT92JMVCo0Jq8W2UZnzYRFeVbQiQ+I25l13JuKvA==", + "requires": { + "minimist": "^1.2.0" + } + }, "map-cache": { "version": "0.2.2", "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz", @@ -7841,6 +7849,26 @@ "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", "dev": true }, + "messageformat": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/messageformat/-/messageformat-2.3.0.tgz", + "integrity": "sha512-uTzvsv0lTeQxYI2y1NPa1lItL5VRI8Gb93Y2K2ue5gBPyrbJxfDi/EYWxh2PKv5yO42AJeeqblS9MJSh/IEk4w==", + "requires": { + "make-plural": "^4.3.0", + "messageformat-formatters": "^2.0.1", + "messageformat-parser": "^4.1.2" + } + }, + "messageformat-formatters": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/messageformat-formatters/-/messageformat-formatters-2.0.1.tgz", + "integrity": "sha512-E/lQRXhtHwGuiQjI7qxkLp8AHbMD5r2217XNe/SREbBlSawe0lOqsFb7rflZJmlQFSULNLIqlcjjsCPlB3m3Mg==" + }, + "messageformat-parser": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/messageformat-parser/-/messageformat-parser-4.1.3.tgz", + "integrity": "sha512-2fU3XDCanRqeOCkn7R5zW5VQHWf+T3hH65SzuqRvjatBK7r4uyFa5mEX+k6F9Bd04LVM5G4/BHBTUJsOdW7uyg==" + }, "methods": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", @@ -7983,8 +8011,7 @@ "minimist": { "version": "1.2.5", "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", - "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", - "dev": true + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" }, "minipass": { "version": "3.1.3", @@ -8177,6 +8204,21 @@ "integrity": "sha1-yobR/ogoFpsBICCOPchCS524NCw=", "dev": true }, + "ngx-translate-messageformat-compiler": { + "version": "4.8.0", + "resolved": "https://registry.npmjs.org/ngx-translate-messageformat-compiler/-/ngx-translate-messageformat-compiler-4.8.0.tgz", + "integrity": "sha512-A1Zg2sC0uCc1r8siT1M2DFcLhgjX6aEIu2g5NGnPh51KGtGqQqXHiXx2qCxz1U9sKMlYrvCZzfxzJ2kaCTtw+A==", + "requires": { + "tslib": "^1.10.0" + }, + "dependencies": { + "tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" + } + } + }, "nice-try": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", diff --git a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/package.json b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/package.json index 113e3a1e2..a0935ae03 100644 --- a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/package.json +++ b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/package.json @@ -27,6 +27,8 @@ "bootstrap": "^4.5.0", "flag-icon-css": "^3.5.0", "jquery": "^3.5.1", + "messageformat": "^2.3.0", + "ngx-translate-messageformat-compiler": "^4.8.0", "popper.js": "^1.16.1", "rxjs": "~6.6.3", "service": "^0.1.4", diff --git a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/app.module.ts b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/app.module.ts index 6b7d2c4bb..bd46271ec 100644 --- a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/app.module.ts +++ b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/app.module.ts @@ -19,8 +19,9 @@ import { BrowserModule } from '@angular/platform-browser'; import { NgModule } from '@angular/core'; import { HttpClient, HttpClientModule } from '@angular/common/http'; -import { TranslateLoader, TranslateModule } from '@ngx-translate/core'; +import { TranslateLoader, TranslateModule, TranslateCompiler } from '@ngx-translate/core'; import { TranslateHttpLoader } from '@ngx-translate/http-loader'; +import { TranslateMessageFormatCompiler, MESSAGE_FORMAT_CONFIG } from 'ngx-translate-messageformat-compiler'; import { AppRoutingModule } from './app-routing.module'; import { AppComponent } from './app.component'; @@ -79,6 +80,10 @@ import { ManageUsersEditComponent } from './modules/user/users/manage-users-edit ReactiveFormsModule, HttpClientModule, TranslateModule.forRoot({ + compiler: { + provide: TranslateCompiler, + useClass: TranslateMessageFormatCompiler + }, loader: { provide: TranslateLoader, useFactory: httpTranslateLoader, @@ -88,7 +93,9 @@ import { ManageUsersEditComponent } from './modules/user/users/manage-users-edit NgbPaginationModule, NgbTooltipModule ], - providers: [], + providers: [ + { provide: MESSAGE_FORMAT_CONFIG, useValue: { locales: ['en', 'de'] }} + ], bootstrap: [AppComponent] }) export class AppModule { } diff --git a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/model/user-info.ts b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/model/user-info.ts index 653a4919b..0f4a476bc 100644 --- a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/model/user-info.ts +++ b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/model/user-info.ts @@ -32,4 +32,5 @@ export class UserInfo { user_manager_id:string; validation_token:string; language:string; + location; } diff --git a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/user/users/manage-users-add/manage-users-add.component.html b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/user/users/manage-users-add/manage-users-add.component.html index ad538a838..13b764cf6 100644 --- a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/user/users/manage-users-add/manage-users-add.component.html +++ b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/user/users/manage-users-add/manage-users-add.component.html @@ -24,6 +24,18 @@ [ngClass]="valid('user_id')" placeholder="{{'users.input.user_id'|translate}}"> <small>{{'users.input.small.user_id'|translate:{'minSize':this.minUserIdSize} }}</small> + <div *ngIf="userForm.get('user_id').invalid" class="invalid-feedback"> + <div *ngIf="userForm.get('user_id').errors.required"> + {{'form.error.required'|translate}} + </div> + <div *ngIf="userForm.get('user_id').errors.containsWhitespace"> + {{'form.error.containsWhitespace'|translate}} + </div> + <div *ngIf="userForm.get('user_id').errors.userexists"> + {{'form.error.userexists'|translate}} + </div> + </div> + </div> <div class="form-group col-md-8"> <label for="full_name">{{'users.attributes.full_name' |translate}}</label> @@ -52,28 +64,35 @@ </div> <div class="form-group col-md-8"> <div class="form-check"> - <input class="form-check-input" type="checkbox" value="" formControlName="locked" id="locked"> + <input class="form-check-input" type="checkbox" formControlName="locked" id="locked"> <label class="form-check-label" for="locked"> {{'users.attributes.locked'|translate}} </label> </div> <div class="form-check"> - <input class="form-check-input" type="checkbox" value="" formControlName="password_change_required" + <input class="form-check-input" type="checkbox" formControlName="password_change_required" id="password_change_required" checked> <label class="form-check-label" for="password_change_required"> {{'users.attributes.password_change_required'|translate}} </label> </div> + <div class="form-check"> + <input class="form-check-input" type="checkbox" formControlName="validated" + id="validated" checked> + <label class="form-check-label" for="validated"> + {{'users.attributes.validated'|translate}} + </label> + </div> </div> <div class="form-group col-md-8"> <button class="btn btn-primary" type="submit" - [disabled]="!userForm.valid">{{'users.add.submit'|translate}}</button> + [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',userid]">{{userid}}</a> was added to the list. + User <a [routerLink]="['user','users','edit',result?.user_id]">{{result?.userid}}</a> was added to the list. </div> <div *ngIf="error" class="alert alert-danger" role="alert" > - <h4 class="alert-heading">Errors</h4> + <h4 class="alert-heading">{{'users.add.errortitle'|translate}}</h4> <ng-container *ngFor="let message of errorResult?.error_messages; first as isFirst" > <hr *ngIf="!isFirst"> <p>{{message.message}}</p> diff --git a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/user/users/manage-users-add/manage-users-add.component.ts b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/user/users/manage-users-add/manage-users-add.component.ts index 60649fc8c..f148d1838 100644 --- a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/user/users/manage-users-add/manage-users-add.component.ts +++ b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/user/users/manage-users-add/manage-users-add.component.ts @@ -18,15 +18,23 @@ */ import {Component, OnInit} from '@angular/core'; -import {Validators, FormBuilder, FormGroup} from '@angular/forms'; +import { + FormBuilder, + FormGroup, + Validators, + FormControl, + AsyncValidator, + AbstractControl, + ValidationErrors, + ValidatorFn +} from '@angular/forms'; import {UserService} from "../../../../services/user.service"; import {User} from "../../../../model/user"; -import { UserInfo } from 'src/app/model/user-info'; -import {HttpErrorResponse} from "@angular/common/http"; import {ErrorResult} from "../../../../model/error-result"; -import {catchError} from "rxjs/operators"; -import {of, throwError} from 'rxjs'; +import {catchError, debounceTime, distinctUntilChanged, map, switchMap} from "rxjs/operators"; +import {throwError, Observable, of, pipe, timer} from 'rxjs'; import {environment} from "../../../../../environments/environment"; +import {UserInfo} from "../../../../model/user-info"; @Component({ selector: 'app-manage-users-add', @@ -35,21 +43,24 @@ import {environment} from "../../../../../environments/environment"; }) export class ManageUsersAddComponent implements OnInit { - minUserIdSize=environment.application.minUserIdLength; - success:boolean=false; - error:boolean=false; - errorResult:ErrorResult; - result:string; - userid:string; + editProperties = ['user_id', 'full_name', 'email', 'locked', 'password_change_required', + 'password', 'confirm_password', 'validated']; + minUserIdSize = environment.application.minUserIdLength; + success: boolean = false; + error: boolean = false; + errorResult: ErrorResult; + result: UserInfo; + user: string; userForm = this.fb.group({ - user_id: ['', [Validators.required, Validators.minLength(this.minUserIdSize)]], + user_id: ['', [Validators.required, Validators.minLength(this.minUserIdSize), whitespaceValidator()],this.userUidExistsValidator()], full_name: ['', Validators.required], - email: ['', [Validators.required,Validators.email]], + email: ['', [Validators.required, Validators.email]], locked: [false], password_change_required: [true], password: [''], confirm_password: [''], + validated: [true] }, { validator: MustMatch('password', 'confirm_password') }) @@ -63,33 +74,39 @@ export class ManageUsersAddComponent implements OnInit { onSubmit() { // Process checkout data here - this.result=null; + this.result = null; if (this.userForm.valid) { - let user = this.copyForm(['user_id','full_name','email','locked','password_change_required', - 'password','confirm_password']) + let user = this.copyFromForm(this.editProperties); console.info('Adding user ' + user); - this.userService.addUser(user).pipe(catchError((error : ErrorResult)=> { - console.log("Error " + error + " - " + typeof (error) + " - " + JSON.stringify(error)); - if (error.status==422) { - console.warn("Validation error"); + this.userService.addUser(user).pipe(catchError((error: ErrorResult) => { + // console.log("Error " + error + " - " + typeof (error) + " - " + JSON.stringify(error)); + if (error.status == 422) { + // console.warn("Validation error"); + let pwdErrors = {}; + for (let message of error.error_messages) { + if (message.error_key.startsWith('user.password.violation')) { + pwdErrors[message.error_key] = message.message; + } + } + this.userForm.get('password').setErrors(pwdErrors); } this.errorResult = error; - this.success=false; - this.error=true; - return throwError(error); - })).subscribe((location : string ) => { - this.result = location; - this.success=true; + this.success = false; + this.error = true; + return []; + // return throwError(error); + })).subscribe((user: UserInfo) => { + this.result = user; + this.success = true; this.error = false; - this.userid = location.substring(location.lastIndexOf('/') + 1); }); } } - private copyForm(properties:string[]) : User { - let user : any = new User(); + public copyFromForm(properties: string[]): User { + let user: any = new User(); for (let prop of properties) { user[prop] = this.userForm.get(prop).value; } @@ -97,27 +114,83 @@ export class ManageUsersAddComponent implements OnInit { return user; } + public copyToForm(properties: string[], user: User): void { + let propMap = {}; + for (let prop of properties) { + let propValue = user[prop] == null ? '' : user[prop]; + propMap[prop] = propValue; + } + this.userForm.patchValue(propMap); + console.log("User " + user); + } - valid(field:string) : string[] { - let formField = this.userForm.get(field); - if (formField.dirty||formField.touched) { - if (formField.valid) { - return ['is-valid'] + + valid(field: string): string[] { + let formField = this.userForm.get(field); + if (formField.dirty || formField.touched) { + if (formField.valid) { + return ['is-valid'] + } else { + return ['is-invalid'] + } } else { - return ['is-invalid'] + return [''] + } + } + + getAllErrors(formGroup: FormGroup, errors: string[] = []) : string[] { + Object.keys(formGroup.controls).forEach(field => { + const control = formGroup.get(field); + if (control instanceof FormControl && control.errors != null) { + let keys = Object.keys(control.errors).map(errorKey=>field+'.'+errorKey); + errors = errors.concat(keys); + } else if (control instanceof FormGroup) { + errors = errors.concat(this.getAllErrors(control)); + } + }); + return errors; + } + + getAttributeErrors(control:string):string[] { + return Object.keys(this.userForm.get(control).errors); + } + + /** + * Async validator with debounce time + * @constructor + */ + userUidExistsValidator() { + + return (ctrl : FormControl) => { + // debounceTimer() does not work here, as the observable is created with each keystroke + // but angular does unsubscribe on previous started async observables. + return timer(500).pipe( + switchMap((userid) => this.userService.userExists(ctrl.value)), + catchError(() => of(null)), + map(exists => (exists ? {userexists: true} : null)) + ); } - } else { - return [''] - } } + forbiddenNameValidator(nameRe: RegExp): ValidatorFn { + return (control: AbstractControl): {[key: string]: any} | null => { + const forbidden = nameRe.test(control.value); + return forbidden ? {forbiddenName: {value: control.value}} : null; + }; + } } -export function MustMatch(controlName: string, matchingControlName: string) { - return (formGroup: FormGroup) => { +export function whitespaceValidator(): ValidatorFn { + return (control: AbstractControl): ValidationErrors | null => { + const hasWhitespace = /\s/g.test(control.value); + return hasWhitespace ? {containsWhitespace: {value: control.value}} : null; + }; +} +export function MustMatch(controlName: string, matchingControlName: string) : ValidatorFn { + return (formGroup: FormGroup): ValidationErrors | null => { const control = formGroup.controls[controlName]; const matchingControl = formGroup.controls[matchingControlName]; @@ -128,9 +201,10 @@ export function MustMatch(controlName: string, matchingControlName: string) { // set error on matchingControl if validation fails if (control.value !== matchingControl.value) { - matchingControl.setErrors({ mustMatch: true }); + matchingControl.setErrors({mustMatch: true}); } else { matchingControl.setErrors(null); } } -}
\ No newline at end of file +} + diff --git a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/user/users/manage-users-edit/manage-users-edit.component.html b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/user/users/manage-users-edit/manage-users-edit.component.html index f929de210..8fa520d10 100644 --- a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/user/users/manage-users-edit/manage-users-edit.component.html +++ b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/user/users/manage-users-edit/manage-users-edit.component.html @@ -17,86 +17,126 @@ --> <form class="mt-3 mb-3" [formGroup]="userForm" (ngSubmit)="onSubmit()"> - <div class="form-group row col-md-8"> - <div class="col-md-1">Edit <span class="fas fa-edit"></span></div> + <div class="form-group row col-md-8" *ngIf="!editUser.permanent"> + <div class="col-md-1">Edit <span class="fas fa-edit"></span></div> <div class="col-md-6"> - <input class="form-check-input" type="checkbox" [value]="editMode" - (change)="editMode=!editMode" - > + <input class="form-check-input" type="checkbox" [value]="editMode" + (change)="editMode=!editMode" + > </div> </div> <div class="form-group row col-md-8"> <label class="col-md-2 col-form-label" for="user_id">{{'users.attributes.user_id' |translate}}</label> - <div class="col-md-6" > - <input type="text" formControlName="user_id" id="user_id" - [ngClass]="valid('user_id')" - value="{{editUser.user_id}}" [attr.readonly]="!editMode"> - <small *ngIf="editMode">{{'users.input.small.user_id'|translate:{'minSize':this.minUserIdSize} }}</small> + <div class="col-md-6"> + <input type="text" formControlName="user_id" id="user_id" + [ngClass]="valid('user_id')" + [attr.readonly]="true"> </div> </div> <div class="form-group row col-md-8"> <label class="col-md-2 col-form-label" for="full_name">{{'users.attributes.full_name' |translate}}</label> - <div class="col-md-6" > - <input type="text" formControlName="full_name" id="full_name" - [ngClass]="valid('full_name')" value="{{editUser.full_name}}" [attr.readonly]="!editMode" > - <small *ngIf="editMode">{{'users.input.small.full_name'|translate}}</small> + <div class="col-md-6"> + <input type="text" formControlName="full_name" id="full_name" + [ngClass]="valid('full_name')" [attr.readonly]="editMode?null:true"> + <small *ngIf="editMode">{{'users.input.small.full_name'|translate}}</small> </div> </div> <div class="form-group row col-md-8"> <label class="col-md-2 col-form-label" for="email">{{'users.attributes.email' |translate}}</label> - <div class="col-md-6" > - <input type="text" formControlName="email" id="email" - [ngClass]="valid('email')" value="{{editUser.email}}" [attr.readonly]="!editMode"> + <div class="col-md-6"> + <input type="text" formControlName="email" id="email" + [ngClass]="valid('email')" value="{{editUser.email}}" [attr.readonly]="editMode?null:true"> </div> </div> <div class="form-group row col-md-8" *ngIf="editMode"> <label class="col-md-2 col-form-label" for="password">{{'users.attributes.password' |translate}}</label> - <div class="col-md-6" > - <input type="password" class="form-control" formControlName="password" id="password" - [ngClass]="valid('password')" - placeholder="{{'users.input.password'|translate}}"> + <div class="col-md-6"> + <input type="password" class="form-control" formControlName="password" id="password" + [ngClass]="valid('password')" + placeholder="{{'users.input.password'|translate}}"> + <small>{{'users.edit.small.password'|translate}}</small> </div> </div> <div class="form-group row col-md-8" *ngIf="editMode"> - <label class="col-md-2 col-form-label" for="confirm_password">{{'users.attributes.confirm_password' |translate}}</label> + <label class="col-md-2 col-form-label" + for="confirm_password">{{'users.attributes.confirm_password' |translate}}</label> <div class="col-md-6"> - <input type="password" class="form-control" formControlName="confirm_password" id="confirm_password" - [ngClass]="valid('confirm_password')" - placeholder="{{'users.input.confirm_password'|translate}}"> + <input type="password" class="form-control" formControlName="confirm_password" id="confirm_password" + [ngClass]="valid('confirm_password')" + placeholder="{{'users.input.confirm_password'|translate}}"> </div> </div> <div class="form-group row col-md-8"> <div class="col-md-2">Flags</div> <div class="col-md-6"> - <div class="form-check"> - <input class="form-check-input" type="checkbox" value="{{editUser.locked}}" formControlName="locked" id="locked" [attr.disabled]="editMode?null:true"> - <label class="form-check-label" for="locked" > - {{'users.attributes.locked'|translate}} - </label> + <div class="form-check"> + <input class="form-check-input" type="checkbox" formControlName="locked" + id="locked" [attr.disabled]="editMode?null:true"> + <label class="form-check-label " for="locked"> + {{'users.attributes.locked'|translate}} + </label> + </div> + <div class="form-check" > + <input class="form-check-input" type="checkbox" + formControlName="password_change_required" + id="password_change_required" [attr.disabled]="editMode?null:true"> + <label class="form-check-label" for="password_change_required" > + {{'users.attributes.password_change_required'|translate}} + </label> + </div> + <div class="form-check"> + <input class="form-check-input" type="checkbox" + formControlName="validated" + id="validated" [attr.disabled]="editMode?null:true"> + <label class="form-check-label" for="validated"> + {{'users.attributes.validated'|translate}} + </label> + </div> </div> - <div class="form-check"> - <input class="form-check-input" type="checkbox" value="{{editUser.password_change_required}}" formControlName="password_change_required" - id="password_change_required" [attr.disabled]="editMode?null:true"> - <label class="form-check-label" for="password_change_required"> - {{'users.attributes.password_change_required'|translate}} - </label> + </div> + <div class="form-group row col-md-8"> + <label class="col-md-2 col-form-label" for="created">{{'users.attributes.created' |translate}}</label> + <div class="col-md-6"> + <input type="text" id="created" class="form-control-plaintext" + value="{{editUser.timestamp_account_creation|date:'yyyy-MM-ddTHH:mm:ss'}}" [attr.readonly]="true"> + </div> + </div> + <div class="form-group row col-md-8"> + <label class="col-md-2 col-form-label" for="last_login">{{'users.attributes.last_login' |translate}}</label> + <div class="col-md-6"> + <input type="text" id="last_login" class="form-control-plaintext" + value="{{editUser.timestamp_last_login|date:'yyyy-MM-ddTHH:mm:ss'}}" [attr.readonly]="true"> </div> + </div> + <div class="form-group row col-md-8"> + <label class="col-md-2 col-form-label" for="email">{{'users.attributes.last_password_change' |translate}}</label> + <div class="col-md-6"> + <input type="text" id="last_password_change" class="form-control-plaintext" + value="{{editUser.timestamp_last_password_change|date:'yyyy-MM-ddTHH:mm:ss'}}" [attr.readonly]="true"> </div> </div> + <div class="form-group col-md-8" *ngIf="editMode"> <button class="btn btn-primary" type="submit" - [disabled]="!userForm.valid">{{'users.edit.submit'|translate}}</button> + [disabled]="userForm.invalid || !userForm.dirty">{{'users.edit.submit'|translate}}</button> </div> <div *ngIf="success" class="alert alert-success" role="alert"> User <a [routerLink]="['user','users','edit',userid]">{{userid}}</a> was added to the list. </div> - <div *ngIf="error" class="alert alert-danger" role="alert" > + <div *ngIf="editMode && error" class="alert alert-danger" role="alert"> <h4 class="alert-heading">Errors</h4> - <ng-container *ngFor="let message of errorResult?.error_messages; first as isFirst" > + <ng-container *ngFor="let message of errorResult?.error_messages; first as isFirst"> <hr *ngIf="!isFirst"> <p>{{message.message}}</p> </ng-container> </div> + <div *ngIf="editMode && userForm.invalid" class="alert alert-danger" role="alert" > + <h4 class="alert-heading">Errors</h4> + <ng-container *ngFor="let message of getAllErrors(userForm); first as isFirst" > + <hr *ngIf="!isFirst"> + <p>{{message}}</p> + </ng-container> + </div> </form> diff --git a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/user/users/manage-users-edit/manage-users-edit.component.ts b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/user/users/manage-users-edit/manage-users-edit.component.ts index adb5b91ea..7a01f253c 100644 --- a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/user/users/manage-users-edit/manage-users-edit.component.ts +++ b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/user/users/manage-users-edit/manage-users-edit.component.ts @@ -19,7 +19,7 @@ import { Component, OnInit } from '@angular/core'; import { ActivatedRoute } from '@angular/router'; import {UserService} from "../../../../services/user.service"; -import {FormBuilder, Validators} from "@angular/forms"; +import {FormBuilder, FormControl, Validators} from "@angular/forms"; import {ManageUsersAddComponent, MustMatch} from "../manage-users-add/manage-users-add.component"; import {environment} from "../../../../../environments/environment"; import {map, switchMap} from 'rxjs/operators'; @@ -31,17 +31,23 @@ import {map, switchMap} from 'rxjs/operators'; }) export class ManageUsersEditComponent extends ManageUsersAddComponent implements OnInit { + editProperties = ['user_id', 'full_name', 'email', 'locked', 'password_change_required', + 'password', 'confirm_password', 'validated']; editUser; + originUser; editMode:boolean=false; + minUserIdSize=0; constructor(private route: ActivatedRoute, public userService: UserService, public fb: FormBuilder) { super(userService, fb); this.editUser = this.route.params.pipe(map (params => params.userid ), switchMap(userid => userService.getUser(userid)) ).subscribe(user => { - this.editUser = user;}); + this.editUser = user; + this.originUser = user; + this.copyToForm(this.editProperties, this.editUser);}); } ngOnInit(): void { - + this.userForm.setControl('user_id', new FormControl()); } valid(field: string): string[] { @@ -54,4 +60,10 @@ export class ManageUsersEditComponent extends ManageUsersAddComponent implements } + onSubmit() { + this.copyFromForm(this.editProperties) + + } + + } 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 5e0f17282..5205bfc0c 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 @@ -278,11 +278,19 @@ export class UserService implements OnInit, OnDestroy { } - public addUser(user: User): Observable<string> { - return this.rest.executeResponseCall<string>("post", "redback", "users", user).pipe( + public addUser(user: User): Observable<UserInfo> { + return this.rest.executeResponseCall<UserInfo>("post", "redback", "users", user).pipe( catchError((error: HttpErrorResponse) => { return throwError(this.rest.getTranslatedErrorResult(error)); - }), map((httpResponse: HttpResponse<string>) => httpResponse.headers.get('Location'))); + }), map((httpResponse: HttpResponse<UserInfo>) => { + if (httpResponse.status==201) { + let user = httpResponse.body; + user.location = httpResponse.headers.get('Location'); + return user; + } else { + throwError(new HttpErrorResponse({headers:httpResponse.headers,status:httpResponse.status,statusText:"Bad response code"})) + } + })); } public getUser(userid: string): Observable<UserInfo> { @@ -291,4 +299,25 @@ export class UserService implements OnInit, OnDestroy { return throwError(this.rest.getTranslatedErrorResult(error)); })); } + + public updateUser(user:User): Observable<UserInfo> { + return this.rest.executeRestCall<UserInfo>("put", "redback", "users/" + user.user_id, user).pipe( + catchError((error: HttpErrorResponse) => { + return throwError(this.rest.getTranslatedErrorResult(error)); + })); + } + + public userExists(userid:string): Observable<boolean> { + console.log("Checking user " + userid); + return this.rest.executeResponseCall<string>("head", "redback", "users/" + userid, null).pipe( + catchError((error: HttpErrorResponse) => { + if (error.status==404) { + console.log("Status 404") + return [false]; + } else { + return throwError(this.rest.getTranslatedErrorResult(error)); + } + }), map((httpResponse: HttpResponse<string>) => httpResponse.status == 200)); + } + } 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 05843c492..aaffa09e4 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 @@ -48,7 +48,12 @@ }, "api" : { "rb.auth.invalid_credentials": "Invalid credentials given", - "user.password.violation.numeric" : "Password must have at least {{arg0}} numeric characters." + "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." }, "users": { "attributes":{ @@ -64,11 +69,11 @@ "permanent": "Permanent", "last_password_change": "Last Password Change", "password": "Password", - "confirm_password": "Confirm Password" + "confirm_password": "Confirm Password" }, "input" : { "small": { - "user_id": "Must be a unique key with at least {{minSize}} characters. No spaces allowed.", + "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" }, "user_id": "Enter user ID", @@ -83,7 +88,15 @@ }, "add": { "head": "Add User", - "submit": "Add User" + "submit": "Add User", + "errortitle": "Could not add the user. Please check the following error messages." + }, + "edit": { + "submit": "Save Changes", + "head": "View/Edit User", + "small": { + "password": "If the password field is empty, it will not be updated." + } } }, "search": { @@ -92,7 +105,12 @@ "input": "Search" }, "form": { - "submit": "Submit" + "submit": "Submit", + "error": { + "required": "Value is empty. This is required.", + "containsWhitespace": "Value must not contain whitespace.", + "userexists": "This user exists already." + } }, "password": { "violations" : { |