Updating user add component

This commit is contained in:
Martin Stockhammer 2020-11-11 21:01:46 +01:00
parent 1caa397790
commit 055964cce1
10 changed files with 258 additions and 56 deletions

View File

@ -20,6 +20,7 @@ import {ErrorMessage} from "./error-message";
export class ErrorResult {
error_messages: Array<ErrorMessage>
status: number;
constructor(errorMessages: Array<ErrorMessage>) {
this.error_messages = errorMessages;

View File

@ -0,0 +1,25 @@
/*
* 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 { User } from './user';
describe('User', () => {
it('should create an instance', () => {
expect(new User()).toBeTruthy();
});
});

View File

@ -0,0 +1,24 @@
/*
* 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 { UserInfo } from './user-info';
export class User extends UserInfo {
password: string;
confirm_password: string;
}

View File

@ -24,6 +24,9 @@
<li class="nav-item">
<a class="nav-link" routerLink="/user/users/add" routerLinkActive="active" href="#">{{'users.add.head' |translate }}</a>
</li>
<li class="nav-item">
<a class="nav-link" routerLink="/user/users/edit/guest" routerLinkActive="active" href="#">{{'users.edit.head' |translate }}</a>
</li>
</ul>
<router-outlet ></router-outlet>

View File

@ -18,47 +18,67 @@
-->
<form class="mt-3 mb-3" [formGroup]="userForm" (ngSubmit)="onSubmit()">
<fieldset>
<div class="form-group col-md-8">
<label for="userId">{{'users.attributes.user_id' |translate}}</label>
<input type="text" class="form-control" formControlName="userId" id="userId"
[ngClass]="valid('userId')"
placeholder="{{'users.input.user_id'|translate}}">
<small>{{'users.input.small.user_id'|translate}}</small>
<div class="form-group col-md-8">
<label for="user_id">{{'users.attributes.user_id' |translate}}</label>
<input type="text" class="form-control" formControlName="user_id" id="user_id"
[ngClass]="valid('user_id')"
placeholder="{{'users.input.user_id'|translate}}">
<small>{{'users.input.small.user_id'|translate:{'minSize':this.minUserIdSize} }}</small>
</div>
<div class="form-group col-md-8">
<label for="full_name">{{'users.attributes.full_name' |translate}}</label>
<input type="text" class="form-control" formControlName="full_name" id="full_name"
[ngClass]="valid('full_name')"
placeholder="{{'users.input.full_name'|translate}}">
<small>{{'users.input.small.full_name'|translate}}</small>
</div>
<div class="form-group col-md-8">
<label for="email">{{'users.attributes.email' |translate}}</label>
<input type="text" class="form-control" formControlName="email" id="email"
[ngClass]="valid('email')"
placeholder="{{'users.input.email'|translate}}">
</div>
<div class="form-group col-md-8">
<label for="password">{{'users.attributes.password' |translate}}</label>
<input type="password" class="form-control" formControlName="password" id="password"
[ngClass]="valid('password')"
placeholder="{{'users.input.password'|translate}}">
</div>
<div class="form-group col-md-8">
<label for="confirm_password">{{'users.attributes.confirm_password' |translate}}</label>
<input type="password" class="form-control" formControlName="confirm_password" id="confirm_password"
[ngClass]="valid('confirm_password')"
placeholder="{{'users.input.confirm_password'|translate}}">
</div>
<div class="form-group col-md-8">
<div class="form-check">
<input class="form-check-input" type="checkbox" value="" formControlName="locked" id="locked">
<label class="form-check-label" for="locked">
{{'users.attributes.locked'|translate}}
</label>
</div>
<div class="form-group col-md-8">
<label for="fullName">{{'users.attributes.full_name' |translate}}</label>
<input type="text" class="form-control" formControlName="fullName" id="fullName"
[ngClass]="valid('fullName')"
placeholder="{{'users.input.full_name'|translate}}">
<small>{{'users.input.small.full_name'|translate}}</small>
<div class="form-check">
<input class="form-check-input" type="checkbox" value="" 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-group col-md-8">
<label for="email">{{'users.attributes.email' |translate}}</label>
<input type="text" class="form-control" formControlName="email" id="email"
[ngClass]="valid('email')"
placeholder="{{'users.input.email'|translate}}">
</div>
<div class="form-group col-md-8">
<div class="form-check">
<input class="form-check-input" type="checkbox" value="" 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="passwordChangeRequired"
id="password_change_required" checked>
<label class="form-check-label" for="password_change_required">
{{'users.attributes.password_change_required'|translate}}
</label>
</div>
</div>
<div class="form-group col-md-8">
</div>
<div class="form-group col-md-8">
<button class="btn btn-primary" type="submit"
[disabled]="!userForm.valid">{{'users.add.submit'|translate}}</button>
</div>
</fieldset>
</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" >
<h4 class="alert-heading">Errors</h4>
<ng-container *ngFor="let message of errorResult?.error_messages; first as isFirst" >
<hr *ngIf="!isFirst">
<p>{{message.message}}</p>
</ng-container>
</div>
</form>

View File

@ -18,8 +18,14 @@
*/
import {Component, OnInit} from '@angular/core';
import {FormControl, FormGroup, Validators, FormBuilder} from '@angular/forms';
import {Validators, FormBuilder, FormGroup} 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';
@Component({
selector: 'app-manage-users-add',
@ -28,12 +34,23 @@ import {UserService} from "../../../../services/user.service";
})
export class ManageUsersAddComponent implements OnInit {
minUserIdSize=8;
success:boolean=false;
error:boolean=false;
errorResult:ErrorResult;
result:string;
userid:string;
userForm = this.fb.group({
userId: ['', [Validators.required, Validators.minLength(8)]],
fullName: ['', Validators.required],
user_id: ['', [Validators.required, Validators.minLength(this.minUserIdSize)]],
full_name: ['', Validators.required],
email: ['', [Validators.required,Validators.email]],
locked: [false],
passwordChangeRequired: [true]
password_change_required: [true],
password: [''],
confirm_password: [''],
}, {
validator: MustMatch('password', 'confirm_password')
})
constructor(private userService: UserService, private fb: FormBuilder) {
@ -45,13 +62,41 @@ export class ManageUsersAddComponent implements OnInit {
onSubmit() {
// Process checkout data here
console.warn('Your order has been submitted', JSON.stringify(this.userForm.value));
this.result=null;
if (this.userForm.valid) {
let user = this.copyForm(['user_id','full_name','email','locked','password_change_required',
'password','confirm_password'])
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.errorResult = error;
this.success=false;
this.error=true;
return throwError(error);
})).subscribe((location : string ) => {
this.result = location;
this.success=true;
this.error = false;
this.userid = location.substring(location.lastIndexOf('/') + 1);
});
}
}
get userId() {
return this.userForm.get('userId');
private copyForm(properties:string[]) : User {
let user : any = new User();
for (let prop of properties) {
user[prop] = this.userForm.get(prop).value;
}
console.log("User " + user);
return user;
}
valid(field:string) : string {
let formField = this.userForm.get(field);
if (formField.dirty||formField.touched) {
@ -65,4 +110,26 @@ export class ManageUsersAddComponent implements OnInit {
}
}
}
export function MustMatch(controlName: string, matchingControlName: string) {
return (formGroup: FormGroup) => {
const control = formGroup.controls[controlName];
const matchingControl = formGroup.controls[matchingControlName];
if (matchingControl.errors && !matchingControl.errors.mustMatch) {
// return if another validator has already found an error on the matchingControl
return;
}
// set error on matchingControl if validation fails
if (control.value !== matchingControl.value) {
matchingControl.setErrors({ mustMatch: true });
} else {
matchingControl.setErrors(null);
}
}
}

View File

@ -50,7 +50,7 @@
[ngClass]="user.validated?'fa-check-circle':'fa-circle'"></span></td>
<td><span class="far" [attr.aria-valuetext]="user.locked"
[ngClass]="user.locked?'fa-check-circle':'fa-circle'"></span></td>
<td><span class="far" [attr.aria-valuetext]="user.passwordChangeRequired"
<td><span class="far" [attr.aria-valuetext]="user.password_change_required"
[ngClass]="user.password_change_required?'fa-check-circle':'fa-circle'"></span></td>
<td>{{user.timestamp_last_login | date:'yyyy-MM-ddTHH:mm:ss'}}</td>
<td>{{user.timestamp_account_creation | date : 'yyyy-MM-ddTHH:mm:ss'}}</td>

View File

@ -17,11 +17,12 @@
* under the License.
*/
import {Injectable} from '@angular/core';
import {HttpClient} from "@angular/common/http";
import {HttpClient, HttpErrorResponse, HttpResponse} from "@angular/common/http";
import {environment} from "../../environments/environment";
import {Observable} from "rxjs";
import {ErrorMessage} from "../model/error-message";
import {TranslateService} from "@ngx-translate/core";
import {ErrorResult} from "../model/error-result";
@Injectable({
providedIn: 'root'
@ -42,6 +43,21 @@ export class ArchivaRequestService {
* @param input the input data, if this is a POST or UPDATE request
*/
executeRestCall<R>(type: string, module: string, service: string, input: object): Observable<R> {
let httpArgs = this.getHttpOptions(type, module, service, input);
httpArgs['options']['observe'] = 'body';
httpArgs['options']['responseType'] = 'json';
let lType = type.toLowerCase();
if (lType == "get") {
return this.http.get<R>(httpArgs.url, httpArgs.options);
} else if (lType == "head" ) {
return this.http.head<R>(httpArgs.url, httpArgs.options);
} else if (lType == "post") {
return this.http.post<R>(httpArgs.url, input, httpArgs.options);
}
}
private getHttpOptions(type: string, module: string, service: string, input: object) {
let modulePath = environment.application.servicePaths[module];
let url = environment.application.baseUrl + environment.application.restPath + "/" + modulePath + "/" + service;
let token = this.getToken();
@ -53,14 +69,28 @@ export class ArchivaRequestService {
} else {
headers = {};
}
if (type == "get") {
let options = {'headers': headers}
if (type.toLowerCase()=='get') {
let params = {}
if (input!=null) {
params = input;
}
return this.http.get<R>(url, {"headers": headers,"params":params});
} else if (type == "post") {
return this.http.post<R>(url, input, {"headers": headers});
options['params'] = params;
}
return {'url':url, 'options':options}
}
executeResponseCall<R>(type: string, module: string, service:string, input:object) : Observable<HttpResponse<R>> {
let httpArgs = this.getHttpOptions(type, module, service, input);
httpArgs['options']['observe'] = 'response';
httpArgs['options']['responseType'] = 'json';
let lType = type.toLowerCase();
if (lType == "get") {
return this.http.get<HttpResponse<R>>(httpArgs.url, httpArgs.options);
} else if (lType=='head') {
return this.http.head<HttpResponse<R>>(httpArgs.url, httpArgs.options);
} else if (lType == 'post') {
return this.http.post<HttpResponse<R>>(httpArgs.url, input, httpArgs.options);
}
}
@ -97,4 +127,17 @@ export class ArchivaRequestService {
return this.translator.instant('api.' + errorMsg.error_key, parms);
}
}
public getTranslatedErrorResult(httpError : HttpErrorResponse) : ErrorResult {
let errorResult = httpError.error as ErrorResult;
errorResult.status = httpError.status;
if (errorResult.error_messages!=null) {
for (let message of errorResult.error_messages) {
if (message.message==null || message.message=='') {
message.message = this.translateError(message);
}
}
}
return errorResult;
}
}

View File

@ -19,12 +19,14 @@
import {Injectable, OnDestroy, OnInit} from '@angular/core';
import {ArchivaRequestService} from "./archiva-request.service";
import {UserInfo} from '../model/user-info';
import {HttpErrorResponse} from "@angular/common/http";
import {HttpErrorResponse, HttpResponse} from "@angular/common/http";
import {ErrorResult} from "../model/error-result";
import {Observable} from "rxjs";
import {Observable, throwError} from "rxjs";
import {Permission} from '../model/permission';
import {PagedResult} from "../model/paged-result";
import {EntityService} from "../model/entity-service";
import { User } from '../model/user';
import {catchError, map} from "rxjs/operators";
@Injectable({
providedIn: 'root'
@ -269,4 +271,11 @@ export class UserService implements OnInit, OnDestroy {
return this.rest.executeRestCall<PagedResult<UserInfo>>("get", "redback", "users", {'q':searchTerm, 'offset':offset,'limit':limit,'orderBy':orderBy,'order':order});
}
public addUser(user : User) : Observable<string> {
return this.rest.executeResponseCall<string>("post", "redback", "users", user).pipe(
catchError( ( error: HttpErrorResponse) => {
return throwError(this.rest.getTranslatedErrorResult(error));
}),map( (httpResponse : HttpResponse<string>) => httpResponse.headers.get('Location')));
}
}

View File

@ -47,7 +47,8 @@
}
},
"api" : {
"rb.auth.invalid_credentials": "Invalid credentials given"
"rb.auth.invalid_credentials": "Invalid credentials given",
"user.password.violation.numeric" : "Password must have at least {{arg0}} numeric characters."
},
"users": {
"attributes":{
@ -61,16 +62,20 @@
"last_login": "Last Login",
"created": "Created",
"permanent": "Permanent",
"last_password_change": "Last Password Change"
"last_password_change": "Last Password Change",
"password": "Password",
"confirm_password": "Confirm Password"
},
"input" : {
"small": {
"user_id": "Must be a unique key. No space 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",
"full_name": "Enter full user name",
"email": "email@example.org"
"email": "email@example.org",
"password": "Enter password",
"confirm_password": "Confirm password"
},
"list": {
@ -88,5 +93,10 @@
},
"form": {
"submit": "Submit"
},
"password": {
"violations" : {
}
}
}