Adding angular code

This commit is contained in:
Martin Stockhammer 2020-11-02 14:24:30 +01:00
parent ee7d339b45
commit 108b60a4df
43 changed files with 2267 additions and 1739 deletions

View File

@ -689,6 +689,7 @@
<exclude>src/main/archiva-web/src/polyfills.ts</exclude>
<exclude>src/main/archiva-web/e2e/protractor.conf.js</exclude>
<exclude>src/main/archiva-web/e2e/tsconfig.json</exclude>
<exclude>src/main/archiva-web/src/assets/i18n/*.json</exclude>
</excludes>
</configuration>
</plugin>
@ -735,7 +736,7 @@
<webApp>
<contextPath>/archiva</contextPath>
</webApp>
<jettyXml>${basedir}/src/test/jetty-env.xml</jettyXml>
<jettyXml>${basedir}/src/test/resources/jetty-env.xml</jettyXml>
<systemProperties>
<systemProperty>
<name>plexus.home</name>
@ -787,7 +788,7 @@
</systemProperty>
<systemProperty>
<name>log4j.configurationFile</name>
<value>${basedir}/src/test/log4j2-test.xml</value>
<value>${basedir}/src/test/resources/log4j2-test.xml</value>
</systemProperty>
<systemProperty>
<name>AsyncLoggerConfig.WaitStrategy</name>
@ -801,8 +802,12 @@
<name>openjpa.Log</name>
<value>${openjpa.Log}</value>
</systemProperty>
<systemProperty>
<name>redback.admin.creation.file</name>
<value>${basedir}/src/test/resources/auto-admin-creation.properties</value>
</systemProperty>
</systemProperties>
<useTestScope>false</useTestScope>
<useTestScope>true</useTestScope>
</configuration>
<dependencies>
<dependency>

View File

@ -2,26 +2,36 @@
This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 10.0.1.
## Development server
## Usage instructions
### Development server
Run `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The app will automatically reload if you change any of the source files.
## Code scaffolding
### Code scaffolding
Run `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`.
## Build
### Build
Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory. Use the `--prod` flag for a production build.
## Running unit tests
### Running unit tests
Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io).
## Running end-to-end tests
### Running end-to-end tests
Run `ng e2e` to execute the end-to-end tests via [Protractor](http://www.protractortest.org/).
## Further help
### Further help
To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI README](https://github.com/angular/angular-cli/blob/master/README.md).
## Information about used components
### I18n-Support
We are using the ngx-translate package for i18n, and not the builtin localization of angular 9.+ . Because, the builtin module
does not allow runtime translations by keys. All keys must exist during compile phase.

View File

@ -13,10 +13,14 @@
"root": "",
"sourceRoot": "src",
"prefix": "app",
"i18n": {
"sourceLocale": "en-US"
},
"architect": {
"build": {
"builder": "@angular-devkit/build-angular:browser",
"options": {
"localize": false,
"outputPath": "dist/archiva-web",
"index": "src/index.html",
"main": "src/main.ts",
@ -79,12 +83,6 @@
}
}
},
"extract-i18n": {
"builder": "@angular-devkit/build-angular:extract-i18n",
"options": {
"browserTarget": "archiva-web:build"
}
},
"test": {
"builder": "@angular-devkit/build-angular:karma",
"options": {

View File

@ -1,3 +1,21 @@
/*
* 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.
*/
// @ts-check
// Protractor configuration file, see link for more information
// https://github.com/angular/protractor/blob/master/lib/config.ts

View File

@ -1,6 +1,6 @@
/* To learn more about this file see: https://angular.io/config/tsconfig. */
{
"extends": "../tsconfig.base.json",
"extends": "../tsconfig.json",
"compilerOptions": {
"outDir": "../out-tsc/e2e",
"module": "commonjs",

View File

@ -1,3 +1,21 @@
/*
* 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.
*/
// Karma configuration file, see link for more information
// https://karma-runner.github.io/1.0/config/configuration-file.html

View File

@ -11,27 +11,30 @@
},
"private": true,
"dependencies": {
"@angular/animations": "~10.0.2",
"@angular/common": "~10.0.2",
"@angular/compiler": "~10.0.2",
"@angular/core": "~10.0.2",
"@angular/forms": "~10.0.2",
"@angular/localize": "^10.0.2",
"@angular/platform-browser": "~10.0.2",
"@angular/platform-browser-dynamic": "~10.0.2",
"@angular/router": "~10.0.2",
"@angular/animations": "~10.2.0",
"@angular/common": "~10.2.0",
"@angular/compiler": "~10.2.0",
"@angular/core": "~10.2.0",
"@angular/forms": "~10.2.0",
"@angular/platform-browser": "~10.2.0",
"@angular/platform-browser-dynamic": "~10.2.0",
"@angular/router": "~10.2.0",
"@fortawesome/fontawesome-free": "^5.13.1",
"@fortawesome/fontawesome-svg-core": "^1.2.29",
"@ngx-translate/core": "^13.0.0",
"@ngx-translate/http-loader": "^6.0.0",
"bootstrap": "^4.5.0",
"flag-icon-css": "^3.5.0",
"jquery": "^3.5.1",
"rxjs": "~6.5.5",
"rxjs": "~6.6.3",
"service": "^0.1.4",
"tslib": "^2.0.0",
"zone.js": "~0.10.3"
},
"devDependencies": {
"@angular-devkit/build-angular": "~0.1000.1",
"@angular/cli": "~10.0.1",
"@angular/compiler-cli": "~10.0.2",
"@angular-devkit/build-angular": "~0.1002.0",
"@angular/cli": "~10.2.0",
"@angular/compiler-cli": "~10.2.0",
"@types/node": "^12.11.1",
"@types/jasmine": "~3.5.0",
"@types/jasminewd2": "~2.0.3",

View File

@ -22,8 +22,8 @@ import { Routes, RouterModule } from '@angular/router';
import { AboutComponent } from './modules/general/about/about.component';
import { ContactComponent } from './modules/general/contact/contact.component';
import { HomeComponent } from './modules/general/home/home.component';
import { LoginComponent } from './modules/general/login/login.component';
import { NotFoundComponent } from './modules/general/not-found/not-found.component';
import { LoginComponent } from "./modules/general/login/login.component";
const routes: Routes = [
{ path: '', component: HomeComponent, },

View File

@ -29,28 +29,35 @@
<div class="collapse navbar-collapse" id="navbarsDefault">
<ul class="navbar-nav ml-auto">
<li class="nav-item active">
<a i18n="navbar|" class="nav-link" routerLink="/">
<i class="fas fa-home mr-1"></i>Home
<a class="nav-link" routerLink="/">
<i class="fas fa-home mr-1"></i>{{ 'menu.home' |translate }}
</a>
</li>
<li class="nav-item active">
<a i18n="navbar|" class="nav-link" routerLink="/about">
<i class="far fa-question-circle mr-1"></i>About
<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>
</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 i18n="navbar|" class="nav-link" routerLink="/contact">
<i class="fas fa-envelope mr-1"></i>Contact
</a>
</li>
<li class="nav-item active">
<a i18n="navbar|" class="nav-link" routerLink="/login">
<i class="fas fa-user mr-1"></i>Login
<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>Github
<i class="fab fa-github mr-1"></i>
</a>
</li>
</ul>

View File

@ -17,6 +17,7 @@
* under the License.
*/
import { Component } from '@angular/core';
import { TranslateService } from '@ngx-translate/core';
@Component({
selector: 'app-root',
@ -26,4 +27,27 @@ import { Component } from '@angular/core';
export class AppComponent {
title = 'archiva-web';
version = 'Angular version 10.0.2';
constructor(
public translate: TranslateService
) {
translate.addLangs(['en', 'de']);
translate.setDefaultLang('en');
translate.use('en');
}
switchLang(lang: string) {
this.translate.use(lang);
}
langIcon() : string {
switch (this.translate.currentLang) {
case "de":
return "flag-icon-de";
case "en":
return "flag-icon-gb";
default:
return "flag-icon-" + this.translate.currentLang;
}
}
}

View File

@ -18,17 +18,19 @@
*/
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { HttpClientModule } from '@angular/common/http';
import { HttpClient, HttpClientModule } from '@angular/common/http';
import { TranslateLoader, TranslateModule } from '@ngx-translate/core';
import { TranslateHttpLoader } from '@ngx-translate/http-loader';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { HomeComponent } from './modules/general/home/home.component';
import { ContactComponent } from './modules/general/contact/contact.component';
import { AboutComponent } from './modules/general/about/about.component';
import { LoginComponent } from './modules/general/login/login.component';
import { NotFoundComponent } from './modules/general/not-found/not-found.component';
import { SidemenuComponent } from './modules/general/sidemenu/sidemenu.component';
import {FormsModule} from "@angular/forms";
import {FormsModule, ReactiveFormsModule} from "@angular/forms";
import { LoginComponent } from './modules/general/login/login.component';
@NgModule({
declarations: [
@ -36,17 +38,29 @@ import {FormsModule} from "@angular/forms";
HomeComponent,
ContactComponent,
AboutComponent,
LoginComponent,
NotFoundComponent,
SidemenuComponent,
LoginComponent,
],
imports: [
BrowserModule,
AppRoutingModule,
FormsModule,
ReactiveFormsModule,
HttpClientModule,
TranslateModule.forRoot({
loader: {
provide: TranslateLoader,
useFactory: httpTranslateLoader,
deps: [HttpClient]
}
})
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }
export function httpTranslateLoader(http: HttpClient) {
return new TranslateHttpLoader(http);
}

View File

@ -16,19 +16,11 @@
* specific language governing permissions and limitations
* under the License.
*/
import { TestBed } from '@angular/core/testing';
import { LoginService } from './login.service';
import { AccessToken } from './access-token';
describe('LoginService', () => {
let service: LoginService;
beforeEach(() => {
TestBed.configureTestingModule({});
service = TestBed.inject(LoginService);
});
it('should be created', () => {
expect(service).toBeTruthy();
describe('AccessToken', () => {
it('should create an instance', () => {
expect(new AccessToken()).toBeTruthy();
});
});

View File

@ -16,19 +16,11 @@
* specific language governing permissions and limitations
* under the License.
*/
import { Injectable } from '@angular/core';
import { ArchivaRequestService } from "./archiva-request.service";
@Injectable({
providedIn: 'root'
})
export class LoginService {
login(username: string, password: string) {
throw new Error("Method not implemented.");
}
constructor(private archiva : ArchivaRequestService) { }
export class AccessToken {
access_token: string;
refresh_token: string;
expires_in: number;
token_type: string;
scope: string;
state: string;
}

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 { ErrorMessage } from './error-message';
describe('ErrorMessage', () => {
it('should create an instance', () => {
expect(new ErrorMessage()).toBeTruthy();
});
});

View File

@ -16,11 +16,8 @@
* under the License.
*/
export class Logindata {
constructor(
public username: string,
public password: string
) { }
}
export class ErrorMessage {
errorKey: string;
args: string[];
message: string;
}

View File

@ -0,0 +1,27 @@
/*
* 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 { ErrorResult } from './error-result';
import { ErrorMessage } from './error-message';
describe('ErrorResult', () => {
it('should create an instance', () => {
const resultArray = new ErrorMessage[0]();
expect(resultArray).toBeTruthy();
});
});

View File

@ -0,0 +1,27 @@
/*
* 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 {ErrorMessage} from "./error-message";
export class ErrorResult {
errorMessages: Array<ErrorMessage>
constructor(errorMessages: Array<ErrorMessage>) {
this.errorMessages = errorMessages;
}
}

View File

@ -16,36 +16,38 @@
~ specific language governing permissions and limitations
~ under the License.
-->
<div class="container-md" >
<div class="row">
<div class="col-6">
<form (ngSubmit)="onSubmit()" #loginForm="ngForm">
<div class="form-group">
<label for="username" i18n="loginform|">Username</label>
<input type="text" class="form-control" id="username" aria-describedby="usernameHelp"
required name="username"
[(ngModel)]="model.username" #username="ngModel"
>
<small id="usernameHelp" class="form-text text-muted" i18n="inputhelp|">Enter your username.</small>
<div [hidden]="username.valid || username.pristine"
class="alert alert-danger" i18n>
Username is required
<!-- Modal -->
<div class="modal fade" id="loginModal" tabindex="-1" role="dialog" aria-labelledby="loginModal" aria-hidden="true">
<div class="modal-dialog" role="document">
<form [formGroup]="loginForm" (ngSubmit)="login(loginForm.value)" >
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="loginModalLabel">{{'login.title'|translate}}</h5>
<button type="button" #closebutton class="close" data-dismiss="modal" aria-label="Close" >
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<div class="form-group">
<label for="userid" id="userid-label" >{{'login.userid' | translate}}</label>
<input id="userid" aria-labelledby="userid-label" class="form-control" placeholder="userid" formControlName="userid" />
</div>
<div class="form-group">
<label for="password" id="password-label" >{{'login.password' | translate}}</label>
<input id="password" aria-labelledby="password-label" class="form-control" type="password" formControlName="password" />
</div>
</div>
<div class="form-group">
<label for="password" i18n="loginform|">Password</label>
<input type="password" class="form-control" id="password" name="password"
required="required" [(ngModel)]="model.password" #password="ngModel"
>
<small id="passwordHelp" class="form-text text-muted" i18n="inputhelp|">Enter your password.</small>
<div [hidden]="password.valid || password.pristine"
class="alert alert-danger" i18n>
Password is required
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal" >{{'modal.close'|translate}}</button>
<button type="button" class="btn btn-primary" type="submit" >{{'login.submit'|translate}}</button>
</div>
<button type="submit" class="btn btn-primary">Submit</button>
</div>
</form>
<div *ngFor="let msg of errorMessages" class="alert alert-danger" >
{{msg}}
</div>
</div>
</div>
</div>

View File

@ -15,4 +15,4 @@
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
*/

View File

@ -16,7 +16,7 @@
* specific language governing permissions and limitations
* under the License.
*/
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { LoginComponent } from './login.component';
@ -24,12 +24,12 @@ describe('LoginComponent', () => {
let component: LoginComponent;
let fixture: ComponentFixture<LoginComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ LoginComponent ]
})
.compileComponents();
}));
});
beforeEach(() => {
fixture = TestBed.createComponent(LoginComponent);

View File

@ -16,11 +16,13 @@
* specific language governing permissions and limitations
* under the License.
*/
import { Component, OnInit } from '@angular/core';
// noinspection ES6UnusedImports
import { FormsModule } from "@angular/forms";
import { Logindata } from "../../../logindata";
import { LoginService } from "../../../services/login.service";
import {Component, OnInit, ViewChild} from '@angular/core';
import { FormBuilder } from '@angular/forms';
import { AuthenticationService } from "../../../services/authentication.service";
import {AccessToken} from "../../../model/access-token";
import {ErrorMessage} from "../../../model/error-message";
import {Router} from "@angular/router";
import {ArchivaRequestService} from "../../../services/archiva-request.service";
@Component({
selector: 'app-login',
@ -29,21 +31,47 @@ import { LoginService } from "../../../services/login.service";
})
export class LoginComponent implements OnInit {
model = new Logindata('', '');
@ViewChild("closebutton") closebutton;
submitted = false;
loginForm;
userid;
password;
errorMessages : string[];
onSubmit() { this.submitted = true; }
constructor( private authenticationService: AuthenticationService,
private formBuilder: FormBuilder,
private router: Router,
private archivaRequest : ArchivaRequestService ) {
this.loginForm = this.formBuilder.group({
userid: '',
password: '',
});
this.errorMessages = [];
get diagnostic() { return JSON.stringify(this.submitted); }
login(): void {
this.loginService.login(username, password);
}
constructor(private loginService: LoginService) { }
ngOnInit(): void {
}
login(customerData) {
this.errorMessages = [];
let resultHandler = (result: string, err?: ErrorMessage[] ) => {
if (result=="OK") {
this.closebutton.nativeElement.click();
this.router.navigate(["/"]);
} else if (result=="ERROR") {
if (err != null) {
this.errorMessages = [];
for (let msg of err) {
console.log("Error "+msg.errorKey);
this.errorMessages.push(this.archivaRequest.translateError(msg));
}
}
}
}
// Process checkout data here
this.loginForm.reset();
this.authenticationService.login(customerData.userid, customerData.password, resultHandler);
}
}

View File

@ -18,51 +18,51 @@
-->
<nav class="nav flex-column nav-pills " role="tablist" aria-orientation="vertical">
<a i18n="sidemenu|" class="nav-link disabled" href="#" tabindex="-1" aria-disabled="true">Artifacts</a>
<a class="nav-link disabled" href="#" tabindex="-1" aria-disabled="true">Artifacts</a>
<a i18n="sidemenu|" class="nav-link active my-0 py-0" href="#" data-toggle="pill"
<a class="nav-link active my-0 py-0" href="#" data-toggle="pill"
role="tab" aria-controls="v-pills-search" aria-selected="true">Search</a>
<a i18n="sidemenu|" class="nav-link my-0 py-0" href="#" data-toggle="pill"
<a class="nav-link my-0 py-0" href="#" data-toggle="pill"
role="tab" aria-controls="v-pills-browse" aria-selected="false">Browse</a>
<a i18n="sidemenu|" class="nav-link my-0 py-0" href="#" data-toggle="pill"
<a class="nav-link my-0 py-0" href="#" data-toggle="pill"
role="tab" aria-controls="v-pills-browse" aria-selected="false">Upload Artifact</a>
<a i18n="sidemenu|" class="nav-link disabled" href="#" tabindex="-1" aria-disabled="true" data-toggle="pill"
<a class="nav-link disabled" href="#" tabindex="-1" aria-disabled="true" data-toggle="pill"
role="tab" aria-controls="v-pills-home" aria-selected="false">Administration</a>
<a i18n="sidemenu|" class="nav-link my-0 py-0" href="#" data-toggle="pill"
<a class="nav-link my-0 py-0" href="#" data-toggle="pill"
role="tab" aria-controls="v-pills-browse" aria-selected="false">Repository Groups</a>
<a i18n="sidemenu|" class="nav-link my-0 py-0" href="#" data-toggle="pill"
<a class="nav-link my-0 py-0" href="#" data-toggle="pill"
role="tab" aria-controls="v-pills-browse" aria-selected="false">Repositories</a>
<a i18n="sidemenu|" class="nav-link my-0 py-0" href="#" data-toggle="pill"
<a class="nav-link my-0 py-0" href="#" data-toggle="pill"
role="tab" aria-controls="v-pills-browse" aria-selected="false">Proxy Connectors</a>
<a i18n="sidemenu|" class="nav-link my-0 py-0" href="#" data-toggle="pill"
<a class="nav-link my-0 py-0" href="#" data-toggle="pill"
role="tab" aria-controls="v-pills-browse" aria-selected="false">ProxyConnector Rules</a>
<a i18n="sidemenu|" class="nav-link my-0 py-0" href="#" data-toggle="pill"
<a class="nav-link my-0 py-0" href="#" data-toggle="pill"
role="tab" aria-controls="v-pills-browse" aria-selected="false">Network Proxies</a>
<a i18n="sidemenu|" class="nav-link my-0 py-0" href="#" data-toggle="pill"
<a class="nav-link my-0 py-0" href="#" data-toggle="pill"
role="tab" aria-controls="v-pills-browse" aria-selected="false">Repository Scanning</a>
<a i18n="sidemenu|" class="nav-link my-0 py-0" href="#" data-toggle="pill"
<a class="nav-link my-0 py-0" href="#" data-toggle="pill"
role="tab" aria-controls="v-pills-browse" aria-selected="false">Runtime Configuration</a>
<a i18n="sidemenu|" class="nav-link my-0 py-0" href="#" data-toggle="pill"
<a class="nav-link my-0 py-0" href="#" data-toggle="pill"
role="tab" aria-controls="v-pills-browse" aria-selected="false">System Status</a>
<a i18n="sidemenu|" class="nav-link my-0 py-0" href="#" data-toggle="pill"
<a class="nav-link my-0 py-0" href="#" data-toggle="pill"
role="tab" aria-controls="v-pills-browse" aria-selected="false">UI Configuration</a>
<a i18n="sidemenu|" class="nav-link my-0 py-0" href="#" data-toggle="pill"
<a class="nav-link my-0 py-0" href="#" data-toggle="pill"
role="tab" aria-controls="v-pills-browse" aria-selected="false">Reports</a>
<a i18n="sidemenu|" class="nav-link disabled" href="#" tabindex="-1" aria-disabled="true" data-toggle="pill"
<a class="nav-link disabled" href="#" tabindex="-1" aria-disabled="true" data-toggle="pill"
role="tab" aria-controls="v-pills-home" aria-selected="false">Users</a>
<a i18n="sidemenu|" class="nav-link my-0 py-0" href="#" data-toggle="pill"
<a class="nav-link my-0 py-0" href="#" data-toggle="pill"
role="tab" aria-controls="v-pills-browse" aria-selected="false">Manage</a>
<a i18n="sidemenu|" class="nav-link my-0 py-0" href="#" data-toggle="pill"
<a class="nav-link my-0 py-0" href="#" data-toggle="pill"
role="tab" aria-controls="v-pills-browse" aria-selected="false">Roles</a>
<a i18n="sidemenu|" class="nav-link my-0 py-0" href="#" data-toggle="pill"
<a class="nav-link my-0 py-0" href="#" data-toggle="pill"
role="tab" aria-controls="v-pills-browse" aria-selected="false">Users Runtime Configuration</a>
<a i18n="sidemenu|" class="nav-link disabled" href="#" tabindex="-1" aria-disabled="true" data-toggle="pill"
<a class="nav-link disabled" href="#" tabindex="-1" aria-disabled="true" data-toggle="pill"
role="tab" aria-controls="v-pills-home" aria-selected="false">Documentation</a>
<a i18n="sidemenu|" class="nav-link my-0 py-0" href="#" data-toggle="pill"
<a class="nav-link my-0 py-0" href="#" data-toggle="pill"
role="tab" aria-controls="v-pills-browse" aria-selected="false">REST Api</a>
<a i18n="sidemenu|" class="nav-link my-0 py-0" href="#" data-toggle="pill"
<a class="nav-link my-0 py-0" href="#" data-toggle="pill"
role="tab" aria-controls="v-pills-browse" aria-selected="false">User Documentation</a>
</nav>

View File

@ -17,24 +17,48 @@
* under the License.
*/
import { Injectable } from '@angular/core';
import { HttpClient } from "@angular/common/http";
import {HttpClient, HttpEvent, 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";
@Injectable({
providedIn: 'root'
})
export class ArchivaRequestService {
constructor(private http : HttpClient, private translator : TranslateService) { }
executeRestCall(type: string, module: string, service: string, input: object, callback: (result: object) => void ) : void {
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 + "Service";
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 = {};
}
if (type == "get") {
this.http.get(url,)
return this.http.get<R>(url, {"headers":headers});
} else if ( type == "post") {
this.http.post(url);
return this.http.post<R>(url, input, {"headers":headers});
}
}
constructor(private http : HttpClient) { }
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);
}
}
}

View File

@ -0,0 +1,80 @@
/*
* 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 { AuthenticationService } from './authentication.service';
import {of, throwError} from 'rxjs';
import {ArchivaRequestService} from "./archiva-request.service";
import { ErrorMessage } from '../model/error-message';
import {HttpErrorResponse} from "@angular/common/http";
describe('AuthenticationService', () => {
let service: AuthenticationService;
let archivaRequestServiceSpy: jasmine.SpyObj<ArchivaRequestService>;
beforeEach(() => {
const spy = jasmine.createSpyObj('ArchivaRequestService', ['executeRestCall']);
TestBed.configureTestingModule( { providers: [
AuthenticationService,
{ provide: ArchivaRequestService, useValue: spy }
]});
service = TestBed.inject(AuthenticationService);
archivaRequestServiceSpy = TestBed.inject(ArchivaRequestService) as jasmine.SpyObj<ArchivaRequestService>;
});
it('should be created', () => {
expect(service).toBeTruthy();
});
it('#login should return success and set token', () => {
const stubValue = {'access_token':'abcdefg','refresh_token':'hijklmnop','expires_in':1000};
archivaRequestServiceSpy.executeRestCall.and.returnValue(of(stubValue));
// resultHandler: (n: string, err?: ErrorMessage[]) => void
let result:string;
let handler = (n:string, err?:ErrorMessage[]) => {
result = n;
};
service.login('admin','pass123', handler);
expect(result).toEqual("OK");
expect(archivaRequestServiceSpy.executeRestCall.calls.count()).toBe(1, 'one call');
expect(localStorage.getItem('access_token')).toEqual("abcdefg");
expect(localStorage.getItem('refresh_token')).toEqual("hijklmnop");
});
it('#login fails', () => {
const stubValue = {'access_token':'abcdefg','refresh_token':'hijklmnop','expires_in':1000};
archivaRequestServiceSpy.executeRestCall.and.returnValue(throwError(new HttpErrorResponse({'status':404, error:{'errorMessages':[
new ErrorMessage()
]}})));
// resultHandler: (n: string, err?: ErrorMessage[]) => void
let result : string;
let messages: ErrorMessage[];
let handler = (n:string, err?:ErrorMessage[]) => {
result = n;
messages = err;
};
service.login('admin', 'test', handler);
expect(result).toEqual("ERROR");
expect(messages).toBeTruthy();
expect(messages.length).toEqual(1);
});
});

View File

@ -0,0 +1,76 @@
/*
* 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 {AccessToken} from "../model/access-token";
import { environment } from "../../environments/environment";
import {ErrorMessage} from "../model/error-message";
import {ErrorResult} from "../model/error-result";
import {HttpErrorResponse} from "@angular/common/http";
@Injectable({
providedIn: 'root'
})
export class AuthenticationService {
constructor(private rest: ArchivaRequestService) { }
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);
}
},
// complete: () => console.log('Observer got a complete notification'),
};
authObserver.subscribe(tokenObserver)
}
logout() {
localStorage.removeItem("access_token");
localStorage.removeItem("refresh_token");
localStorage.removeItem("token_expire");
}
}

View File

@ -0,0 +1,20 @@
{
"login": {
"title": "Anmelden bei Archiva",
"password": "Passwort",
"userid": "User Id",
"submit": "Anmelden"
},
"modal" : {
"close":"Schließen"
},
"menu": {
"home": "Home",
"login": "Anmelden",
"about": "Über",
"contact": "Kontakt"
},
"api" : {
"rb.auth.invalid_credentials": "Anmeldedaten ungültig"
}
}

View File

@ -0,0 +1,20 @@
{
"login": {
"title": "Login to Archiva",
"password": "Password",
"userid": "User Id",
"submit": "Login"
},
"modal" : {
"close": "Close"
},
"menu": {
"home": "Home",
"login": "Login",
"about": "About",
"contact": "Contact"
},
"api" : {
"rb.auth.invalid_credentials": "Invalid credentials given"
}
}

View File

@ -25,15 +25,16 @@ export const environment = {
production: false,
application:
{
client_id:'archiva_web_client',
baseUrl: 'http://localhost:8080',
restPath: '/restServices',
restPath: '/archiva/api/v2',
servicePaths: {
archiva:"archivaServices",
redback:"redbackServices",
ui:"archivaUiServices"
archiva:"archiva",
redback:"redback",
ui:"archivaUi"
},
name: 'archiva-starter',
angular: 'Angular 10.0.2',
angular: 'Angular 10.2.0',
bootstrap: 'Bootstrap 4.5.0',
fontawesome: 'Font Awesome 5.13.1'
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 948 B

After

Width:  |  Height:  |  Size: 3.6 KiB

View File

@ -1,3 +1,21 @@
<!--
~ 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.
-->
<!doctype html>
<html lang="en">
<!--

View File

@ -1,102 +0,0 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!--
/*
* 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.
*/
-->
<xliff version="2.0" xmlns="urn:oasis:names:tc:xliff:document:2.0" srcLang="en-US">
<file original="ng.template" id="ngi18n">
<unit id="3533320232395919182">
<notes>
<note category="meaning">sidenav</note>
<note category="location">src/app/app.component.html:33,35</note>
</notes>
<segment>
<source>
<pc id="0" equivStart="START_ITALIC_TEXT" equivEnd="CLOSE_ITALIC_TEXT" type="fmt" dispStart="&lt;i&gt;" dispEnd="&lt;/i&gt;"></pc>Home
</source>
</segment>
</unit>
<unit id="1283530642863181542">
<notes>
<note category="meaning">sidenav</note>
<note category="location">src/app/app.component.html:38,40</note>
</notes>
<segment>
<source>
<pc id="0" equivStart="START_ITALIC_TEXT" equivEnd="CLOSE_ITALIC_TEXT" type="fmt" dispStart="&lt;i&gt;" dispEnd="&lt;/i&gt;"></pc>About
</source>
</segment>
</unit>
<unit id="4789449765292606242">
<notes>
<note category="meaning">sidenav</note>
<note category="location">src/app/app.component.html:43,45</note>
</notes>
<segment>
<source>
<pc id="0" equivStart="START_ITALIC_TEXT" equivEnd="CLOSE_ITALIC_TEXT" type="fmt" dispStart="&lt;i&gt;" dispEnd="&lt;/i&gt;"></pc>Contact
</source>
</segment>
</unit>
<unit id="1912174959280479645">
<notes>
<note category="meaning">sidenav</note>
<note category="location">src/app/app.component.html:48,50</note>
</notes>
<segment>
<source>
<pc id="0" equivStart="START_ITALIC_TEXT" equivEnd="CLOSE_ITALIC_TEXT" type="fmt" dispStart="&lt;i&gt;" dispEnd="&lt;/i&gt;"></pc>Login
</source>
</segment>
</unit>
<unit id="6511475824238507681">
<notes>
<note category="location">src/app/modules/general/home/home.component.html:22</note>
</notes>
<segment>
<source>Artifacts</source>
</segment>
</unit>
<unit id="4580988005648117665">
<notes>
<note category="location">src/app/modules/general/home/home.component.html:24</note>
</notes>
<segment>
<source>Search</source>
</segment>
</unit>
<unit id="787163983066183218">
<notes>
<note category="location">src/app/modules/general/home/home.component.html:26</note>
</notes>
<segment>
<source>Browse</source>
</segment>
</unit>
<unit id="7844706011418789951">
<notes>
<note category="location">src/app/modules/general/home/home.component.html:28</note>
</notes>
<segment>
<source>Administration</source>
</segment>
</unit>
</file>
</xliff>

View File

@ -1,7 +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.
*/
/***************************************************************************************************
* Load `$localize` onto the global scope - used if i18n tags appear in Angular templates.
*/
import '@angular/localize/init';
// import '@angular/localize/init';
/**
* This file includes polyfills needed by Angular and is loaded before the app.
* You can add your own extra polyfills to this file.

View File

@ -43,6 +43,7 @@ body {
}
@import "~bootstrap/scss/bootstrap";
@import "~flag-icon-css/css/flag-icon.min.css";

View File

@ -1,6 +1,6 @@
/* To learn more about this file see: https://angular.io/config/tsconfig. */
{
"extends": "./tsconfig.base.json",
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "./out-tsc/app",
"types": []

View File

@ -1,20 +0,0 @@
/* To learn more about this file see: https://angular.io/config/tsconfig. */
{
"compileOnSave": false,
"compilerOptions": {
"baseUrl": "./",
"outDir": "./dist/out-tsc",
"sourceMap": true,
"declaration": false,
"downlevelIteration": true,
"experimentalDecorators": true,
"moduleResolution": "node",
"importHelpers": true,
"target": "es2015",
"module": "es2020",
"lib": [
"es2018",
"dom"
]
}
}

View File

@ -1,20 +1,20 @@
/*
This is a "Solution Style" tsconfig.json file, and is used by editors and TypeScripts language server to improve development experience.
It is not intended to be used to perform a compilation.
To learn more about this file see: https://angular.io/config/solution-tsconfig.
*/
/* To learn more about this file see: https://angular.io/config/tsconfig. */
{
"files": [],
"references": [
{
"path": "./tsconfig.app.json"
},
{
"path": "./tsconfig.spec.json"
},
{
"path": "./e2e/tsconfig.json"
}
]
"compileOnSave": false,
"compilerOptions": {
"baseUrl": "./",
"outDir": "./dist/out-tsc",
"sourceMap": true,
"declaration": false,
"downlevelIteration": true,
"experimentalDecorators": true,
"moduleResolution": "node",
"importHelpers": true,
"target": "es2015",
"module": "es2020",
"lib": [
"es2018",
"dom"
]
}
}

View File

@ -1,6 +1,6 @@
/* To learn more about this file see: https://angular.io/config/tsconfig. */
{
"extends": "./tsconfig.base.json",
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "./out-tsc/spec",
"types": [

View File

@ -91,12 +91,6 @@
<logger name="net.sf.ehcache" level="warn"/>
<!-- retained for Redback -->
<logger name="JPOX" level="warn"/>
<logger name="JPOX.MetaData" level="error"/>
<logger name="JPOX.RDBMS.SQL" level="error"/>
<logger name="SQL" level="error"/>
<logger name="org.apache.commons.configuration.DefaultConfigurationBuilder" level="error"/>
@ -117,7 +111,9 @@
-->
<root level="info" includeLocation="true">
<logger name="com.fasterxml" level="trace"/>
<root level="debug" includeLocation="true">
<appender-ref ref="rolling"/>
</root>
</loggers>

View File

@ -0,0 +1,32 @@
#
# 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.
#
# --------------------------------------------------------------------
# Email Settings
# The subject line for the email message.
email.validation.subject=Welcome to Archiva
# Feedback page
email.feedback.path=http://archiva.apache.org/mail-lists.html
email.url.path=index.html
rest.csrffilter.enabled=false