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 fb2a7fd Additional angular code
fb2a7fd is described below
commit fb2a7fd6439aa0813af20167af9f001cf1af588d
Author: Martin Stockhammer <[email protected]>
AuthorDate: Tue Nov 3 00:31:44 2020 +0100
Additional angular code
---
.../main/archiva-web/src/app/app-routing.module.ts | 1 +
.../main/archiva-web/src/app/app.component.html | 134 +++++++++++---------
.../src/main/archiva-web/src/app/app.component.ts | 34 ++++-
.../archiva-web/src/app/model/user-info.spec.ts | 25 ++++
.../main/archiva-web/src/app/model/user-info.ts | 36 ++++++
.../app/modules/general/login/login.component.ts | 3 +
.../src/app/services/archiva-request.service.ts | 92 +++++++++-----
.../src/app/services/authentication.service.ts | 140 ++++++++++++++-------
.../src/app/services/user.service.spec.ts | 34 +++++
.../archiva-web/src/app/services/user.service.ts | 109 ++++++++++++++++
.../src/main/archiva-web/src/assets/i18n/de.json | 1 +
.../src/main/archiva-web/src/assets/i18n/en.json | 1 +
12 files changed, 474 insertions(+), 136 deletions(-)
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 19a64ef..f8c9eb0 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
@@ -30,6 +30,7 @@ const routes: Routes = [
{ path: 'contact', component: ContactComponent },
{ path: 'about', component: AboutComponent },
{ path: 'login', component: LoginComponent },
+ { path: 'logout', component: HomeComponent },
{ path: '**', component: NotFoundComponent }
];
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 7e15657..f223be3 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
@@ -16,70 +16,84 @@
~ specific language governing permissions and limitations
~ under the License.
-->
-<div class="app d-flex flex-column" >
- <header>
- <nav class="navbar navbar-expand-md fixed-top navbar-light "
style="background-color: #c6cbd2;">
- <a class="navbar-brand" routerLink="/">
- <img src="../assets/params/images/archiva_logo_40h.png" alt="ARCHIVA">
- </a>
- <button class="navbar-toggler" type="button" data-toggle="collapse"
data-target="#navbarsDefault"
- aria-controls="navbarsDefault" aria-expanded="false"
aria-label="Toggle Navigation">
- <span class="navbar-toggler-icon"></span>
- </button>
- <div class="collapse navbar-collapse" id="navbarsDefault">
- <ul class="navbar-nav ml-auto">
- <li class="nav-item active">
- <a class="nav-link" routerLink="/">
- <i class="fas fa-home mr-1"></i>{{ 'menu.home' |translate }}
+<div class="app d-flex flex-column">
+ <header>
+ <nav class="navbar navbar-expand-md fixed-top navbar-light "
style="background-color: #c6cbd2;">
+ <a class="navbar-brand" routerLink="/">
+ <img src="../assets/params/images/archiva_logo_40h.png"
alt="ARCHIVA">
</a>
- </li>
- <li class="nav-item active">
- <a class="nav-link" routerLink="/login" data-toggle="modal"
data-target="#loginModal">
- <i class="fas fa-user mr-1"></i>{{'menu.login' | translate}}
- </a>
- </li>
- <li class="nav-item active dropdown">
- <a class="nav-link dropdown-toggle" id="dropdown09"
data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"><span
class="flag-icon {{langIcon()}}"></span></a>
- <div class="dropdown-menu" aria-labelledby="dropdown09">
- <a class="dropdown-item" href="#en"
(click)="switchLang('en')"><span class="flag-icon flag-icon-gb"> </span>
English</a>
- <a class="dropdown-item" href="#de"
(click)="switchLang('de')"><span class="flag-icon flag-icon-de"> </span>
German</a>
+ <button class="navbar-toggler" type="button"
data-toggle="collapse" data-target="#navbarsDefault"
+ aria-controls="navbarsDefault" aria-expanded="false"
aria-label="Toggle Navigation">
+ <span class="navbar-toggler-icon"></span>
+ </button>
+
+ <div class="collapse navbar-collapse" id="navbarsDefault">
+ <div class="navbar-nav ml-auto">
+ <span *ngIf="auth.loggedIn" class="navbar-text
border-right pr-2 mr-2">
+ {{user.userInfo.fullName}}
+ </span>
+ <ul class="navbar-nav">
+ <li class="nav-item active">
+ <a class="nav-link" routerLink="/">
+ <i class="fas fa-home mr-1"></i>{{ 'menu.home'
|translate }}
+ </a>
+ </li>
+ <li *ngIf="!auth.loggedIn" class="nav-item active">
+ <a class="nav-link" routerLink="/login"
data-toggle="modal" data-target="#loginModal">
+ <i class="fas fa-user mr-1"></i>{{'menu.login'
| translate}}
+ </a>
+ </li>
+ <li *ngIf="auth.loggedIn" class="nav-item active">
+ <a class="nav-link" routerLink="/logout"
(click)="auth.logout()">
+ <i class="fas fa-user
mr-1"></i>{{'menu.logout' | translate}}
+ </a>
+ </li>
+ <li class="nav-item active dropdown">
+ <a class="nav-link dropdown-toggle"
id="dropdown09" data-toggle="dropdown"
+ aria-haspopup="true"
aria-expanded="false"><span class="flag-icon {{langIcon()}}"></span></a>
+ <div class="dropdown-menu"
aria-labelledby="dropdown09">
+ <a class="dropdown-item" href="#en"
(click)="switchLang('en')"><span
+ class="flag-icon flag-icon-gb">
</span> English</a>
+ <a class="dropdown-item" href="#de"
(click)="switchLang('de')"><span
+ class="flag-icon flag-icon-de">
</span> German</a>
+ </div>
+ </li>
+ <li class="nav-item active">
+ <a class="nav-link" routerLink="/about">
+ <i class="far fa-question-circle
mr-1"></i>{{'menu.about' | translate}}
+ </a>
+ </li>
+ <li class="nav-item active">
+ <a class="nav-link" routerLink="/contact">
+ <i class="fas fa-envelope mr-1"></i>{{
'menu.contact' | translate }}
+ </a>
+ </li>
+ <li class="nav-item active">
+ <a class="nav-link"
href="https://github.com/apache/archiva">
+ <i class="fab fa-github mr-1"></i>
+ </a>
+ </li>
+ </ul>
+ </div>
</div>
- </li>
- <li class="nav-item active">
- <a class="nav-link" routerLink="/about">
- <i class="far fa-question-circle mr-1"></i>{{'menu.about' |
translate}}
- </a>
- </li>
- <li class="nav-item active">
- <a class="nav-link" routerLink="/contact">
- <i class="fas fa-envelope mr-1"></i>{{ 'menu.contact' |
translate }}
- </a>
- </li>
- <li class="nav-item active">
- <a class="nav-link" href="https://github.com/apache/archiva">
- <i class="fab fa-github mr-1"></i>
- </a>
- </li>
- </ul>
- </div>
- </nav>
- </header>
+ </nav>
+ </header>
- <main class="container-fluid flex-fill">
- <div >
- <router-outlet></router-outlet>
- </div>
- </main>
+ <main class="container-fluid flex-fill">
+ <div>
+ <router-outlet></router-outlet>
+ </div>
+ </main>
- <hr />
- <footer class="container-fluid">
- <div class="container">
- <div class="row">
- <div class="col-12">
- <p class="text-center text-black-50">© 2020 -
archiva.apache.org</p>
+ <hr/>
+ <footer class="container-fluid">
+ <div class="container">
+ <div class="row">
+ <div class="col-12">
+ <p class="text-center text-black-50">© 2020 -
archiva.apache.org</p>
+ </div>
+ </div>
</div>
- </div>
- </div>
- </footer>
+ </footer>
</div>
\ No newline at end of file
diff --git
a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/app.component.ts
b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/app.component.ts
index 30b0a29..3dae41b 100644
---
a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/app.component.ts
+++
b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/app.component.ts
@@ -16,28 +16,33 @@
* specific language governing permissions and limitations
* under the License.
*/
-import { Component } from '@angular/core';
+import {Component, OnDestroy, OnInit} from '@angular/core';
import { TranslateService } from '@ngx-translate/core';
+import { AuthenticationService } from "./services/authentication.service";
+import {UserService} from "./services/user.service";
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss']
})
-export class AppComponent {
+export class AppComponent implements OnInit, OnDestroy{
title = 'archiva-web';
version = 'Angular version 10.0.2';
constructor(
- public translate: TranslateService
+ public translate: TranslateService,
+ public auth: AuthenticationService,
+ public user: UserService
) {
translate.addLangs(['en', 'de']);
translate.setDefaultLang('en');
- translate.use('en');
}
switchLang(lang: string) {
this.translate.use(lang);
+ this.user.userInfo.language = lang;
+ this.user.persistUserInfo();
}
langIcon() : string {
@@ -50,4 +55,25 @@ export class AppComponent {
return "flag-icon-" + this.translate.currentLang;
}
}
+
+ ngOnDestroy(): void {
+ this.auth.LoginEvent.unsubscribe();
+ }
+
+
+ ngOnInit(): void {
+ let lang = this.user.userInfo.language;
+ if (lang==null) {
+ this.translate.use('en');
+ } else {
+ this.translate.use(lang);
+ }
+ // Subscribe to login event in authenticator to switch the language
+ this.auth.LoginEvent.subscribe(userInfo => {
+ if (userInfo.language != null) {
+ this.switchLang(userInfo.language);
+ }
+ })
+
+ }
}
diff --git
a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/model/user-info.spec.ts
b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/model/user-info.spec.ts
new file mode 100644
index 0000000..79fadf1
--- /dev/null
+++
b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/model/user-info.spec.ts
@@ -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 { UserInfo } from './user-info';
+
+describe('UserInfo', () => {
+ it('should create an instance', () => {
+ expect(new UserInfo()).toBeTruthy();
+ });
+});
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
new file mode 100644
index 0000000..6356fe2
--- /dev/null
+++
b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/model/user-info.ts
@@ -0,0 +1,36 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+export class UserInfo {
+ user_id:string;
+ id:string;
+ fullName:string;
+ email:string;
+ validated:boolean;
+ locked:boolean;
+ passwordChangeRequired:boolean;
+ permanent:boolean;
+ timestampAccountCreation:Date;
+ timestampLastLogin:Date;
+ timestampLastPasswordChange:Date;
+ assignedRoles:string[];
+ readOnly:boolean;
+ userManagerId:string;
+ validationToken:string;
+ language:string;
+}
diff --git
a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/general/login/login.component.ts
b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/general/login/login.component.ts
index 82c797b..5cdc7f5 100644
---
a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/general/login/login.component.ts
+++
b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/general/login/login.component.ts
@@ -74,4 +74,7 @@ export class LoginComponent implements OnInit {
this.loginForm.reset();
this.authenticationService.login(customerData.userid,
customerData.password, resultHandler);
}
+
+
+
}
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 2060469..9d79733 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
@@ -16,49 +16,81 @@
* specific language governing permissions and limitations
* under the License.
*/
-import { Injectable } from '@angular/core';
-import {HttpClient, HttpEvent, HttpResponse} from "@angular/common/http";
-import { environment } from "../../environments/environment";
+import {Injectable} from '@angular/core';
+import {HttpClient} 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";
@Injectable({
- providedIn: 'root'
+ providedIn: 'root'
})
export class ArchivaRequestService {
- constructor(private http : HttpClient, private translator :
TranslateService) { }
+ // Stores the access token locally
+ accessToken: string;
- executeRestCall<R>(type: string, module: string, service: string, input:
object ) : Observable<R> {
- let modulePath = environment.application.servicePaths[module];
- let url = environment.application.baseUrl +
environment.application.restPath + "/" + modulePath + "/" + service;
- let token = localStorage.getItem("access_token")
- let headers = null;
- if (token != null) {
- headers = {
- "Authorization": "Bearer " + localStorage.getItem("access_token")
- }
- } else {
- headers = {};
+ constructor(private http: HttpClient, private translator:
TranslateService) {
}
- if (type == "get") {
- return this.http.get<R>(url, {"headers":headers});
- } else if ( type == "post") {
- return this.http.post<R>(url, input, {"headers":headers});
+
+ /**
+ * Executes a rest call to the archiva / redback REST services.
+ * @param type the type of the call (get, post, update)
+ * @param module the module (archiva, redback)
+ * @param service the REST service to call
+ * @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 modulePath = environment.application.servicePaths[module];
+ let url = environment.application.baseUrl +
environment.application.restPath + "/" + modulePath + "/" + service;
+ let token = this.getToken();
+ let headers = null;
+ if (token != null) {
+ headers = {
+ "Authorization": "Bearer " + token
+ }
+ } else {
+ headers = {};
+ }
+ if (type == "get") {
+ return this.http.get<R>(url, {"headers": headers});
+ } else if (type == "post") {
+ return this.http.post<R>(url, input, {"headers": headers});
+ }
}
- }
+ public resetToken() {
+ this.accessToken = null;
+ }
+
+ private getToken(): string {
+ if (this.accessToken != null) {
+ return this.accessToken;
+ } else {
+ let token = localStorage.getItem("access_token");
+ if (token != null && token != "") {
+ this.accessToken = token;
+ return token;
+ } else {
+ return null;
+ }
+ }
+ }
- translateError(errorMsg : ErrorMessage) : string {
- if (errorMsg.errorKey!=null && errorMsg.errorKey!='') {
- let parms = {};
- if (errorMsg.args!=null && errorMsg.args.length>0) {
- for ( let i=0; i<errorMsg.args.length; i++) {
- parms['arg' + i] = errorMsg.args[i];
+ /**
+ * Translates a given error message to the current set language.
+ * @param errorMsg the errorMsg as returned by a REST call
+ */
+ public translateError(errorMsg: ErrorMessage): string {
+ if (errorMsg.errorKey != null && errorMsg.errorKey != '') {
+ let parms = {};
+ if (errorMsg.args != null && errorMsg.args.length > 0) {
+ for (let i = 0; i < errorMsg.args.length; i++) {
+ parms['arg' + i] = errorMsg.args[i];
+ }
+ }
+ return this.translator.instant('api.' + errorMsg.errorKey, parms);
}
- }
- return this.translator.instant('api.'+errorMsg.errorKey, parms);
}
- }
}
diff --git
a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/services/authentication.service.ts
b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/services/authentication.service.ts
index c062b95..ba4cdb1 100644
---
a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/services/authentication.service.ts
+++
b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/services/authentication.service.ts
@@ -16,61 +16,117 @@
* specific language governing permissions and limitations
* under the License.
*/
-import { Injectable } from '@angular/core';
+import {EventEmitter, Injectable} from '@angular/core';
import {ArchivaRequestService} from "./archiva-request.service";
import {AccessToken} from "../model/access-token";
-import { environment } from "../../environments/environment";
+import {environment} from "../../environments/environment";
import {ErrorMessage} from "../model/error-message";
import {ErrorResult} from "../model/error-result";
import {HttpErrorResponse} from "@angular/common/http";
+import {UserService} from "./user.service";
+import {UserInfo} from "../model/user-info";
+/**
+ * The AuthenticationService handles user authentication and stores user data
after successful login
+ */
@Injectable({
- providedIn: 'root'
+ providedIn: 'root'
})
export class AuthenticationService {
+ loggedIn: boolean;
+
+ /**
+ * The LoginEvent is emitted, when a successful login happened. And the
corresponding user info was retrieved.
+ */
+ public LoginEvent: EventEmitter<UserInfo> = new EventEmitter<UserInfo>();
+
+
+ constructor(private rest: ArchivaRequestService,
+ private userService: UserService) {
+ this.loggedIn = false;
+ this.restoreLoginData();
+ }
+
+ private restoreLoginData() {
+ let accessToken = localStorage.getItem("access_token");
+ if (accessToken != null) {
+ let expirationDate = localStorage.getItem("token_expire");
+ if (expirationDate != null) {
+ let expDate = new Date(expirationDate);
+ let currentDate = new Date();
+ if (currentDate < expDate) {
+ this.loggedIn = true
+ let observer = this.userService.retrieveUserInfo();
+ observer.subscribe(userInfo =>
+ this.LoginEvent.emit(userInfo)
+ );
+ }
+ }
+ }
+
+
+ }
- constructor(private rest: ArchivaRequestService) { }
- login(userid:string, password:string, resultHandler: (n: string, err?:
ErrorMessage[]) => void) {
+ /**
+ * Tries to login by sending the login data to the REST service. If the
login was successful the access
+ * and refresh token is stored locally.
+ *
+ * @param userid The user id for the login
+ * @param password The password
+ * @param resultHandler A result handler that is executed, after calling
the login service
+ */
+ login(userid: string, password: string, resultHandler: (n: string, err?:
ErrorMessage[]) => void) {
- const data = { 'grant_type':'authorization_code',
- 'client_id':environment.application.client_id,
- 'user_id':userid, 'password':password
- };
- let authObserver =
this.rest.executeRestCall<AccessToken>('post','redback', 'auth/authenticate',
data );
- let tokenObserver = {
- next: (x: AccessToken) => {
- localStorage.setItem("access_token", x.access_token);
- localStorage.setItem("refresh_token", x.refresh_token);
- if (x.expires_in!=null) {
- let dt = new Date();
- dt.setSeconds(dt.getSeconds() + x.expires_in);
- localStorage.setItem("token_expire", dt.toISOString());
- }
- resultHandler("OK");
- },
- error: ( err: HttpErrorResponse) => {
- console.log("Error " + (JSON.stringify(err)));
- let result = err.error as ErrorResult
- if (result.errorMessages!=null) {
- for (let msg of result.errorMessages) {
- console.error('Observer got an error: ' + msg.errorKey)
- }
- resultHandler("ERROR", result.errorMessages);
- } else {
- resultHandler("ERROR", null);
- }
+ const data = {
+ 'grant_type': 'authorization_code',
+ 'client_id': environment.application.client_id,
+ 'user_id': userid, 'password': password
+ };
+ let authObserver = this.rest.executeRestCall<AccessToken>('post',
'redback', 'auth/authenticate', data);
+ let tokenObserver = {
+ next: (x: AccessToken) => {
+ localStorage.setItem("access_token", x.access_token);
+ localStorage.setItem("refresh_token", x.refresh_token);
+ if (x.expires_in != null) {
+ let dt = new Date();
+ dt.setSeconds(dt.getSeconds() + x.expires_in);
+ localStorage.setItem("token_expire", dt.toISOString());
+ }
+ let userObserver = this.userService.retrieveUserInfo();
+ this.loggedIn = true;
+ userObserver.subscribe(userInfo =>
+ this.LoginEvent.emit(userInfo));
+ resultHandler("OK");
+ },
+ error: (err: HttpErrorResponse) => {
+ console.log("Error " + (JSON.stringify(err)));
+ let result = err.error as ErrorResult
+ if (result.errorMessages != null) {
+ for (let msg of result.errorMessages) {
+ console.error('Observer got an error: ' + msg.errorKey)
+ }
+ resultHandler("ERROR", result.errorMessages);
+ } else {
+ resultHandler("ERROR", null);
+ }
- },
- // complete: () => console.log('Observer got a complete
notification'),
- };
- authObserver.subscribe(tokenObserver)
+ },
+ // complete: () => console.log('Observer got a complete
notification'),
+ };
+ authObserver.subscribe(tokenObserver)
- }
+ }
- logout() {
- localStorage.removeItem("access_token");
- localStorage.removeItem("refresh_token");
- localStorage.removeItem("token_expire");
- }
+ /**
+ * Resets the stored user data
+ */
+ logout() {
+ localStorage.removeItem("access_token");
+ localStorage.removeItem("refresh_token");
+ localStorage.removeItem("token_expire");
+ this.loggedIn = false;
+ this.userService.resetUser();
+ this.rest.resetToken();
+ }
}
diff --git
a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/services/user.service.spec.ts
b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/services/user.service.spec.ts
new file mode 100644
index 0000000..e21a651
--- /dev/null
+++
b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/services/user.service.spec.ts
@@ -0,0 +1,34 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import { TestBed } from '@angular/core/testing';
+
+import { UserService } from './user.service';
+
+describe('UserService', () => {
+ let service: UserService;
+
+ beforeEach(() => {
+ TestBed.configureTestingModule({});
+ service = TestBed.inject(UserService);
+ });
+
+ it('should be created', () => {
+ expect(service).toBeTruthy();
+ });
+});
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
new file mode 100644
index 0000000..3360e55
--- /dev/null
+++
b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/services/user.service.ts
@@ -0,0 +1,109 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import {Injectable} from '@angular/core';
+import {ArchivaRequestService} from "./archiva-request.service";
+import {UserInfo} from '../model/user-info';
+import {HttpErrorResponse} from "@angular/common/http";
+import {ErrorResult} from "../model/error-result";
+import {Observable} from "rxjs";
+
+@Injectable({
+ providedIn: 'root'
+})
+export class UserService {
+
+ userInfo: UserInfo;
+
+ constructor(private rest: ArchivaRequestService) {
+ this.userInfo = new UserInfo()
+ this.loadPersistedUserInfo();
+ }
+
+ /**
+ * Retrieves the user information from the REST service for the current
logged in user.
+ * This works only, if a valid access token is present.
+ * It returns a observable that can be subscribed to catch the user
information.
+ */
+ public retrieveUserInfo(): Observable<UserInfo> {
+ return new Observable<UserInfo>((resultObserver) => {
+ let accessToken = localStorage.getItem("access_token");
+
+ if (accessToken != null) {
+ let infoObserver = this.rest.executeRestCall<UserInfo>("get",
"redback", "users/me", null);
+ let userInfoObserver = {
+ next: (x: UserInfo) => {
+ this.userInfo = x;
+ if (this.userInfo.language == null) {
+ this.loadPersistedUserInfo();
+ }
+ this.persistUserInfo();
+ resultObserver.next(this.userInfo);
+ },
+ error: (err: HttpErrorResponse) => {
+ console.log("Error " + (JSON.stringify(err)));
+ let result = err.error as ErrorResult
+ if (result.errorMessages != null) {
+ for (let msg of result.errorMessages) {
+ console.error('Observer got an error: ' +
msg.errorKey)
+ }
+ }
+ resultObserver.error();
+ },
+ complete: () => {
+ resultObserver.complete();
+ }
+ };
+ infoObserver.subscribe(userInfoObserver);
+ }
+ });
+ }
+
+ /**
+ * Stores user information persistent. Not the complete UserInfo object,
only properties, that
+ * are needed.
+ */
+ public persistUserInfo() {
+ if (this.userInfo != null && this.userInfo.user_id != null &&
this.userInfo.user_id != "") {
+ let prefix = "user." + this.userInfo.user_id;
+ localStorage.setItem(prefix + ".user_id", this.userInfo.user_id);
+ localStorage.setItem(prefix + ".id", this.userInfo.id);
+ if (this.userInfo.language != null && this.userInfo.language !=
"") {
+ localStorage.setItem(prefix + ".language",
this.userInfo.language);
+ }
+ }
+ }
+
+ /**
+ * Loads the persisted user info from the local storage
+ */
+ public loadPersistedUserInfo() {
+ if (this.userInfo.user_id != null && this.userInfo.user_id != "") {
+ let prefix = "user." + this.userInfo.user_id;
+ this.userInfo.language = localStorage.getItem(prefix +
".language");
+ }
+ }
+
+ /**
+ * Resets the user info to default values.
+ */
+ resetUser() {
+ this.userInfo = new UserInfo();
+ }
+
+}
diff --git
a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/assets/i18n/de.json
b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/assets/i18n/de.json
index c98ece6..12eec0c 100644
---
a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/assets/i18n/de.json
+++
b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/assets/i18n/de.json
@@ -11,6 +11,7 @@
"menu": {
"home": "Home",
"login": "Anmelden",
+ "logout": "Abmelden",
"about": "Über",
"contact": "Kontakt"
},
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 418bc20..68494c4 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
@@ -11,6 +11,7 @@
"menu": {
"home": "Home",
"login": "Login",
+ "logout": "Logout",
"about": "About",
"contact": "Contact"
},