diff --git a/angular.json b/angular.json index ac9c723..d2d43c0 100644 --- a/angular.json +++ b/angular.json @@ -49,6 +49,7 @@ "src/assets/common-assets/library-css/material.scss" ], "scripts": [ + "src/assets/common-assets/js/copy.js", "node_modules/uikit/dist/js/uikit.min.js", "node_modules/uikit/dist/js/uikit-icons.min.js", "node_modules/jquery/dist/jquery.js" diff --git a/src/app/apis/apis.component.less b/src/app/apis/apis.component.less new file mode 100644 index 0000000..e5f71cd --- /dev/null +++ b/src/app/apis/apis.component.less @@ -0,0 +1,10 @@ +.uk-border-circle { + width: 100px; + height: 100px; + position: relative; + + & > img { + max-width: 64px; + max-height: 64px; + } +} diff --git a/src/app/apis/apis.component.ts b/src/app/apis/apis.component.ts new file mode 100644 index 0000000..fa66f1b --- /dev/null +++ b/src/app/apis/apis.component.ts @@ -0,0 +1,400 @@ +import {ChangeDetectorRef, Component, OnDestroy, OnInit, ViewChild} from "@angular/core"; +import {FormBuilder, FormGroup, Validators} from "@angular/forms"; +import {Subscription} from "rxjs"; +import {UserManagementService} from "../openaireLibrary/services/user-management.service"; +import {User} from "../openaireLibrary/login/utils/helper.class"; +import {PersonalInfo, PersonalService} from "../services/personal.service"; +import {NotificationHandler} from "../openaireLibrary/utils/notification-handler"; +import {API, ApisService} from "../services/apis.service"; +import {AlertModal} from "../openaireLibrary/utils/modal/alert"; +import {StringUtils} from "../openaireLibrary/utils/string-utils.class"; +import {properties} from "../../environments/environment"; +import {Stakeholder} from "../openaireLibrary/monitor/entities/stakeholder"; +import {CommunityInfo} from "../openaireLibrary/connect/community/communityInfo"; +import {UtilitiesService} from "../openaireLibrary/services/utilities.service"; + +declare var copy; + +@Component({ + selector: `apis`, + template: ` +
+
+ +
+
+
+ +
+
+
+ + You can register up to 5 services. + For more information please read the OpenAIRE API Authentication documentation. +
+
+
You have not registered any service yet!
+
+ + + + + + + + + + + + + + + + + +
NameClient IDCreation DateActions
{{api.service.name}}{{api.service.clientId}}{{api.service.creationDate | date: 'dd-MM-YYYY HH:mm'}} + + + + + +
+
+
+
+ +
+
+
+
+
+
+ +
Register your service to get a client id and a client + secret. Use the client id and secret to make your requests. Read more... +
+
+
+ +
+
+ +
+
+
+ +
+
+ +
+
+ +
+
+ +
+
+
+
+
+
+
+
+
+ + +
+ +
+
+ `, + styleUrls: ['apis.component.less'] +}) +export class ApisComponent implements OnInit, OnDestroy { + form: FormGroup; + loading: boolean = true; + subscriptions: any[] = []; + user: User; + apis: API[] = []; + api: API; + index: number = -1; + advanced: boolean = false; + byValue: boolean = true; + hint = '{"kty": ..., "e": ... , "use": ... , "kid": ..., "alg": ... , "n": ...}'; + @ViewChild("editModal") editModal: AlertModal; + @ViewChild("deleteModal") deleteModal: AlertModal; + @ViewChild("infoModal") infoModal: AlertModal; + + constructor(private fb: FormBuilder, + private apisService: ApisService, + private utilsService: UtilitiesService, + private cdr: ChangeDetectorRef, + private userManagementService: UserManagementService) { + } + + ngOnInit() { + this.loading = true; + this.subscriptions.push(this.userManagementService.getUserInfo().subscribe(user => { + this.user = user; + this.subscriptions.push(this.apisService.getMyServices().subscribe(apis => { + this.apis = apis; + this.loading = false; + }, error => { + console.error(error); + this.apis = []; + this.loading = false; + })); + })); + } + + ngOnDestroy() { + this.subscriptions.forEach(subscription => { + if(subscription instanceof Subscription) { + subscription.unsubscribe(); + } + }) + } + + openEditModal(index: number = -1) { + let api = new API(); + this.index = index; + this.editModal.okButtonLeft = false; + if(index !== -1) { + api = this.apis[index]; + this.editModal.alertTitle = "Edit Service"; + this.editModal.okButtonText = "Update"; + } else { + this.editModal.alertTitle = "Create Service"; + this.editModal.okButtonText = "Create"; + } + this.form = this.fb.group({ + name: this.fb.control(api.service.name, Validators.required), + keyType: this.fb.control(api.service.keyType), + logoURL: this.fb.control(api.details.logoUri, [StringUtils.urlValidator()]), + url: this.fb.control(api.service.url, [Validators.required, StringUtils.urlValidator()]), + uri: this.fb.control(api.details.jwksUri, [StringUtils.urlValidator()]), + value: this.fb.control(null, [StringUtils.jsonValidator('The format should be {"kty": ..., "e": ... , "use": ... , "kid": ..., "alg": ... , "n": ...}')]) + }); + if(api.details?.jwks?.keys) { + this.form.get('value').setValue(JSON.stringify(api.details.jwks.keys[0])); + } + this.advanced = !!this.form.get('keyType').getRawValue(); + this.byValue = this.advanced && this.form.get('keyType').getRawValue() === 'value'; + this.formChanges(); + this.editModal.open(); + } + + openDeleteModal(index: number) { + this.index = index; + let api = this.apis[this.index]; + this.deleteModal.alertTitle = 'Delete Service'; + this.deleteModal.alertMessage = true; + this.deleteModal.message = 'You are going to delete ' + api.service.name + '. Are you sure you want to proceed?'; + this.deleteModal.okButtonText = 'Yes'; + this.deleteModal.cancelButtonText = 'No'; + this.deleteModal.open(); + } + + openInfoModal(index: number) { + this.api = this.apis[index]; + this.infoModal.okButton = false; + this.infoModal.cancelButtonText = 'Close'; + this.infoModal.alertTitle = 'Details for ' + this.api.details.clientName; + this.infoModal.open(); + } + + save() { + this.loading = true; + if(this.index === -1) { + this.subscriptions.push(this.apisService.create(this.form.getRawValue()).subscribe(api => { + this.apis.push(api) + NotificationHandler.rise('Your service has been created successfully.'); + this.loading = false; + }, error => { + console.error(error); + NotificationHandler.rise('An error has occurred. Please try again later.', 'danger'); + this.loading = false; + })); + } else { + this.subscriptions.push(this.apisService.save(this.apis[this.index].service.id, this.form.getRawValue()).subscribe(api => { + this.apis[this.index] = api; + NotificationHandler.rise('Your service has been created successfully.'); + this.loading = false; + }, error => { + console.error(error); + NotificationHandler.rise('An error has occurred. Please try again later.', 'danger'); + this.loading = false; + })); + } + } + + confirm() { + this.loading = true; + this.subscriptions.push(this.apisService.delete(this.apis[this.index].service.id).subscribe(() => { + this.apis.splice(this.index, 1); + NotificationHandler.rise('Your service has been created deleted.'); + this.loading = false; + }, error => { + console.error(error); + NotificationHandler.rise('An error has occurred. Please try again later.', 'danger'); + this.loading = false; + })); + } + + securityChanged(event) { + this.advanced = event; + if(this.advanced) { + this.keyTypeChanged(true); + } else { + this.form.get('keyType').setValue(null); + } + } + + keyTypeChanged(event) { + this.byValue = event; + if(this.byValue) { + this.form.get('keyType').setValue('value'); + } else { + this.form.get('keyType').setValue('uri'); + } + } + + formChanges() { + this.subscriptions.push(this.form.get('keyType').valueChanges.subscribe(value => { + if(value === 'value') { + this.form.get('value').addValidators(Validators.required); + this.form.get('uri').removeValidators(Validators.required); + } else if(value === 'uri') { + this.form.get('value').removeValidators(Validators.required); + this.form.get('uri').addValidators(Validators.required); + } else { + this.form.get('value').removeValidators(Validators.required); + this.form.get('uri').removeValidators(Validators.required); + } + this.form.get('value').updateValueAndValidity(); + this.form.get('uri').updateValueAndValidity(); + this.cdr.detectChanges(); + })); + } + + jsonPretty(value: any) { + return JSON.stringify(value, null, 2); + } + + public static getLogoUrl(api: API): string { + return api?.details?.logoUri?api.details.logoUri:"assets/common-assets/placeholder.png"; + } + + copyToClipboard(id: string) { + if(copy.exec(id)) { + NotificationHandler.rise('Copied to clipboard!'); + } else { + NotificationHandler.rise('Unable to copy to clipboard!', 'danger'); + } + } +} diff --git a/src/app/apis/apis.module.ts b/src/app/apis/apis.module.ts new file mode 100644 index 0000000..0b68ee5 --- /dev/null +++ b/src/app/apis/apis.module.ts @@ -0,0 +1,29 @@ +import {NgModule} from "@angular/core"; +import {CommonModule} from "@angular/common"; +import {ApisComponent} from "./apis.component"; +import {RouterModule} from "@angular/router"; +import {PreviousRouteRecorder} from "../openaireLibrary/utils/piwik/previousRouteRecorder.guard"; +import {LoginGuard} from "../openaireLibrary/login/loginGuard.guard"; +import {PageContentModule} from "../openaireLibrary/dashboard/sharedComponents/page-content/page-content.module"; +import {InputModule} from "../openaireLibrary/sharedComponents/input/input.module"; +import {LoadingModule} from "../openaireLibrary/utils/loading/loading.module"; +import {HasPersonalInfoGuard} from "../services/hasPersonalInfo.guard"; +import {IconsModule} from "../openaireLibrary/utils/icons/icons.module"; +import {AlertModalModule} from "../openaireLibrary/utils/modal/alertModal.module"; +import {FormsModule} from "@angular/forms"; + +@NgModule({ + imports: [CommonModule, RouterModule.forChild([ + { + path: '', + component: ApisComponent, + canActivate: [LoginGuard, HasPersonalInfoGuard], + canDeactivate: [PreviousRouteRecorder] + } + ]), PageContentModule, InputModule, LoadingModule, IconsModule, AlertModalModule, FormsModule], + declarations: [ApisComponent], + exports: [ApisComponent] +}) +export class ApisModule { + +} diff --git a/src/app/app-routing.module.ts b/src/app/app-routing.module.ts index 0297262..9beec1c 100644 --- a/src/app/app-routing.module.ts +++ b/src/app/app-routing.module.ts @@ -1,10 +1,55 @@ -import { NgModule } from '@angular/core'; -import { RouterModule, Routes } from '@angular/router'; +import {NgModule} from '@angular/core'; +import {RouterModule, Routes} from '@angular/router'; +import {ErrorPageComponent} from "./error/errorPage.component"; +import {AdminLoginGuard} from "./openaireLibrary/login/adminLoginGuard.guard"; -const routes: Routes = []; +const routes: Routes = [ + { + path: '', + loadChildren: () => import('./home/home.module').then(m => m.HomeModule), + data: {hasSidebar: false} + }, + { + path: 'personal-info', + loadChildren: () => import('./personal-info/personal-info.module').then(m => m.PersonalInfoModule) + }, + { + path: 'personal-token', + loadChildren: () => import('./personal-token/personal-token.module').then(m => m.PersonalTokenModule) + }, + { + path: 'apis', + loadChildren: () => import('./apis/apis.module').then(m => m.ApisModule) + }, + { + path: 'user-info', + loadChildren: () => import('./login/libUser.module').then(m => m.LibUserModule), + data: {hasSidebar: false} + }, + { + path: 'reload', + loadChildren: () => import('./reload/libReload.module').then(m => m.LibReloadModule), + data: {hasSidebar: false, hasHeader: false} + }, + { + path: 'theme', + loadChildren: () => import('./openaireLibrary/utils/theme/theme.module').then(m => m.ThemeModule), + canActivateChild: [AdminLoginGuard], + data: {hasSidebar: false, hasHeader: false} + }, + { + path: 'error', + pathMatch: 'full', + component: ErrorPageComponent, + data: {hasSidebar: false} + }, +]; @NgModule({ - imports: [RouterModule.forRoot(routes)], + imports: [RouterModule.forRoot(routes, { + onSameUrlNavigation: 'reload' + })], exports: [RouterModule] }) -export class AppRoutingModule { } +export class AppRoutingModule { +} diff --git a/src/app/app.component.ts b/src/app/app.component.ts index 4f71899..a74057c 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -1,11 +1,114 @@ -import { Component } from '@angular/core'; +import {ChangeDetectorRef, Component, OnDestroy, OnInit} from '@angular/core'; +import {EnvProperties} from "./openaireLibrary/utils/properties/env-properties"; +import {properties} from "../environments/environment"; +import {LayoutService} from "./openaireLibrary/dashboard/sharedComponents/sidebar/layout.service"; +import {SmoothScroll} from "./openaireLibrary/utils/smooth-scroll"; +import {UserManagementService} from "./openaireLibrary/services/user-management.service"; +import {Subscriber} from "rxjs"; +import {User} from "./openaireLibrary/login/utils/helper.class"; +import {MenuItem} from "./openaireLibrary/sharedComponents/menu"; +import {Header} from "./openaireLibrary/sharedComponents/navigationBar.component"; @Component({ - selector: 'app-root', - template: ` -
test
- ` + selector: 'app-root', + template: ` +
+ +
+
+ +
+ ` }) -export class AppComponent { - title = 'client-management-portal'; +export class AppComponent implements OnInit, OnDestroy { + title = 'OpenAIRE APIs Authentication'; + properties: EnvProperties = properties; + user: User; + loading = true; + hasSidebar: boolean = false; + hasHeader: boolean = true; + sideBarItems: MenuItem[] = []; + menuItems: MenuItem[] = []; + backItem: MenuItem; + menuHeader: Header = { + route: "/", + url: null, + title: "OpenAIRE APIs Authentication", + logoUrl: 'assets/common-assets/common/Logo_Horizontal.png', + logoSmallUrl: 'assets/common-assets/common/Logo_Small.png', + position: 'center', + badge: false + }; + userMenuItems: MenuItem[] = []; + private subscriptions: any[] = []; + + constructor(private cdr: ChangeDetectorRef, + private smoothScroll: SmoothScroll, + private layoutService: LayoutService, + private userManagementService: UserManagementService) { + } + + ngOnInit() { + this.subscriptions.push(this.layoutService.hasSidebar.subscribe(hasSidebar => { + this.hasSidebar = hasSidebar; + this.cdr.detectChanges(); + })); + this.subscriptions.push(this.layoutService.hasHeader.subscribe(hasHeader => { + this.hasHeader = hasHeader; + this.cdr.detectChanges(); + })); + this.subscriptions.push(this.userManagementService.getUserInfo().subscribe(user => { + if (user) { + this.user = user; + } else if (this.user) { + this.user = user; + } + this.buildMenu(); + this.layoutService.setOpen(true); + this.loading = false; + })); + } + + public ngOnDestroy() { + this.subscriptions.forEach(value => { + if (value instanceof Subscriber) { + value.unsubscribe(); + } + }); + this.userManagementService.clearSubscriptions(); + this.layoutService.clearSubscriptions(); + this.smoothScroll.clearSubscriptions(); + } + + public get open() { + return this.layoutService.open; + } + + public get hover() { + return this.layoutService.hover; + } + + buildMenu() { + this.backItem = new MenuItem('overview', 'Home', '', '/', false, [], [], null, {name: 'west'}); + let items: MenuItem[] = [ + new MenuItem('personal-info', 'Personal Info', '', '/personal-info', false, [], [], null, {name: 'person'}), + new MenuItem('personal-token', 'Personal Token', '', '/personal-token', false, [], [], null, {name: 'verified_user'}), + new MenuItem('apis', 'Registered Services', '', '/apis', false, [], [], null, {name: 'apps'}), + ]; + this.sideBarItems = items; + this.userMenuItems = items; + this.hasSidebar = this.hasSidebar && this.sideBarItems.length > 1; + } } diff --git a/src/app/app.module.ts b/src/app/app.module.ts index b1c6c96..0e4808c 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -3,14 +3,30 @@ import { BrowserModule } from '@angular/platform-browser'; import { AppRoutingModule } from './app-routing.module'; import { AppComponent } from './app.component'; +import {ErrorPageComponent} from "./error/errorPage.component"; +import {ErrorModule} from "./openaireLibrary/error/error.module"; +import {SharedModule} from "./openaireLibrary/shared/shared.module"; +import {BrowserAnimationsModule} from "@angular/platform-browser/animations"; +import {LoadingModule} from "./openaireLibrary/utils/loading/loading.module"; +import {SideBarModule} from "./openaireLibrary/dashboard/sharedComponents/sidebar/sideBar.module"; +import {NavigationBarModule} from "./openaireLibrary/sharedComponents/navigationBar.module"; +import {HttpClientModule} from "@angular/common/http"; @NgModule({ declarations: [ - AppComponent + AppComponent, + ErrorPageComponent ], imports: [ + BrowserAnimationsModule, + LoadingModule, + HttpClientModule, + SideBarModule, BrowserModule, - AppRoutingModule + AppRoutingModule, + ErrorModule, + SharedModule, + NavigationBarModule ], providers: [], bootstrap: [AppComponent] diff --git a/src/app/error/errorPage.component.ts b/src/app/error/errorPage.component.ts new file mode 100644 index 0000000..4352b7e --- /dev/null +++ b/src/app/error/errorPage.component.ts @@ -0,0 +1,13 @@ +import {Component} from '@angular/core'; + +@Component({ + selector: 'openaire-error', + template: ` +
+ +
+ ` +}) +export class ErrorPageComponent { + +} diff --git a/src/app/home/home.component.ts b/src/app/home/home.component.ts new file mode 100644 index 0000000..c6c6482 --- /dev/null +++ b/src/app/home/home.component.ts @@ -0,0 +1,38 @@ +import {Component, OnInit} from "@angular/core"; + +@Component({ + selector: 'home', + template: ` +
+

OpenAIRE APIs Authentication

+
The OpenAIRE APIs can be accessed over HTTPS both by authenticated and unauthenticated requests. To achieve better rate limits you need to make authenticated requests.
+
+ + For more information please read the OpenAIRE API Authentication documentation. +
+
+
+ +
Personal Token
+
+ Get access to the OpenAIRE APIs with your personal access and refresh token. +
+
+
+
+ +
Registered Services
+
+ Register your services to get access to the OpenAIRE APIs. +
+
+
+
+
+ ` +}) +export class HomeComponent implements OnInit { + + ngOnInit() { + } +} diff --git a/src/app/home/home.module.ts b/src/app/home/home.module.ts new file mode 100644 index 0000000..748974a --- /dev/null +++ b/src/app/home/home.module.ts @@ -0,0 +1,17 @@ +import {NgModule} from "@angular/core"; +import {CommonModule} from "@angular/common"; +import {RouterModule} from "@angular/router"; +import {PreviousRouteRecorder} from "../openaireLibrary/utils/piwik/previousRouteRecorder.guard"; +import {HomeComponent} from "./home.component"; +import {IconsModule} from "../openaireLibrary/utils/icons/icons.module"; + +@NgModule({ + declarations: [HomeComponent], + imports: [CommonModule, RouterModule.forChild([ + {path: '', component: HomeComponent, canDeactivate: [PreviousRouteRecorder]} + ]), IconsModule], + exports: [HomeComponent] +}) +export class HomeModule { + +} diff --git a/src/app/login/libUser.module.ts b/src/app/login/libUser.module.ts new file mode 100644 index 0000000..ab727fa --- /dev/null +++ b/src/app/login/libUser.module.ts @@ -0,0 +1,24 @@ +import {NgModule} from '@angular/core'; +import {CommonModule} from '@angular/common'; +import {FormsModule} from '@angular/forms'; + +import {OpenaireUserComponent} from './user.component'; +import {UserRoutingModule} from './user-routing.module'; +import {UserModule} from '../openaireLibrary/login/user.module'; + +import {PreviousRouteRecorder} from '../openaireLibrary/utils/piwik/previousRouteRecorder.guard'; +import {SubscribeService} from '../openaireLibrary/utils/subscribe/subscribe.service'; +import {EmailService} from "../openaireLibrary/utils/email/email.service"; + +@NgModule({ + imports: [ + CommonModule, FormsModule, + UserRoutingModule, UserModule + ], + providers: [PreviousRouteRecorder, SubscribeService, EmailService], + declarations: [ + OpenaireUserComponent + ] +}) +export class LibUserModule { +} diff --git a/src/app/login/user-routing.module.ts b/src/app/login/user-routing.module.ts new file mode 100644 index 0000000..1a8ad90 --- /dev/null +++ b/src/app/login/user-routing.module.ts @@ -0,0 +1,15 @@ +import { NgModule } from '@angular/core'; +import { RouterModule } from '@angular/router'; + +import {OpenaireUserComponent } from './user.component'; +import {PreviousRouteRecorder} from '../openaireLibrary/utils/piwik/previousRouteRecorder.guard'; + +@NgModule({ + imports: [ + RouterModule.forChild([ + { path: '', component: OpenaireUserComponent, canDeactivate: [PreviousRouteRecorder]}, + + ]) + ] +}) +export class UserRoutingModule { } diff --git a/src/app/login/user.component.ts b/src/app/login/user.component.ts new file mode 100644 index 0000000..edead77 --- /dev/null +++ b/src/app/login/user.component.ts @@ -0,0 +1,10 @@ +import {Component} from '@angular/core'; + +@Component({ + selector: 'openaire-user', + template: `` +}) + +export class OpenaireUserComponent { + +} diff --git a/src/app/openaireLibrary b/src/app/openaireLibrary index e6d4be5..7d2eb68 160000 --- a/src/app/openaireLibrary +++ b/src/app/openaireLibrary @@ -1 +1 @@ -Subproject commit e6d4be54540ff1b23acb88728be9e0e9dd1ebdd1 +Subproject commit 7d2eb68914b5fc53a4e3dbf8fe22c4f67b8bff7f diff --git a/src/app/personal-info/personal-info.component.ts b/src/app/personal-info/personal-info.component.ts new file mode 100644 index 0000000..b9d3846 --- /dev/null +++ b/src/app/personal-info/personal-info.component.ts @@ -0,0 +1,129 @@ +import {Component, OnDestroy, OnInit} from "@angular/core"; +import {FormBuilder, FormGroup, Validators} from "@angular/forms"; +import {Subscription} from "rxjs"; +import {UserManagementService} from "../openaireLibrary/services/user-management.service"; +import {User} from "../openaireLibrary/login/utils/helper.class"; +import {PersonalInfo, PersonalService} from "../services/personal.service"; +import {NotificationHandler} from "../openaireLibrary/utils/notification-handler"; +import {NavigationEnd, Router} from "@angular/router"; + +@Component({ + selector: `personal-form`, + template: ` +
+
+
+ +
+ +
+ In order to visit Personal Token and Registered Services you have to fill the personal info form below. In case you already have registered services, they are still active but you cannot edit them. +
+
+

Personal Info

+
+
+
+
+
+
+
+
+
+ Cancel + +
+
+
+
+ ` +}) +export class PersonalInfoComponent implements OnInit, OnDestroy { + form: FormGroup; + loading: boolean = true; + subscriptions: any[] = []; + user: User; + info: PersonalInfo = null; + message: boolean = false; + + constructor(private fb: FormBuilder, + private personalService: PersonalService, + private userManagementService: UserManagementService, + private router: Router) { + this.subscriptions.push(this.router.events.subscribe(event => { + if(event instanceof NavigationEnd) { + const navigation = this.router.getCurrentNavigation(); + if (navigation && navigation.extras.state) { + this.message = navigation.extras.state.message; + } else { + this.message = false; + } + } + })); + } + + ngOnInit() { + this.subscriptions.push(this.personalService.getPersonalInfo().subscribe(info => { + this.info = info; + this.initForm(); + this.message = false; + }, error => { + this.subscriptions.push(this.userManagementService.getUserInfo().subscribe(user => { + this.user = user; + this.initForm(); + })); + })); + } + + initForm() { + if(this.info) { + this.form = this.fb.group({ + name: this.fb.control(this.info.name, Validators.required), + surname: this.fb.control(this.info.surname, Validators.required), + email: this.fb.control({value: this.info.email, disabled: true}, Validators.required), + affiliation: this.fb.control(this.info.affiliation, Validators.required), + position: this.fb.control(this.info.position, Validators.required), + }); + } else { + this.form = this.fb.group({ + name: this.fb.control(this.user.firstname, Validators.required), + surname: this.fb.control(this.user.lastname, Validators.required), + email: this.fb.control({value: this.user.email, disabled: true}, Validators.required), + affiliation: this.fb.control('', Validators.required), + position: this.fb.control('', Validators.required), + }); + } + this.form.markAsPristine(); + this.loading = false; + } + + save() { + this.loading = true; + this.subscriptions.push(this.personalService.savePersonalInfo(this.form.getRawValue()).subscribe(info => { + this.info = info; + NotificationHandler.rise('Your personal info has been saved successfully.'); + this.initForm(); + this.loading = false; + this.message = false; + }, error => { + console.error(error); + NotificationHandler.rise('An error has occurred. Please try again later.', 'danger'); + this.initForm(); + this.loading = false; + })) + } + + ngOnDestroy() { + this.subscriptions.forEach(subscription => { + if(subscription instanceof Subscription) { + subscription.unsubscribe(); + } + }) + } +} diff --git a/src/app/personal-info/personal-info.module.ts b/src/app/personal-info/personal-info.module.ts new file mode 100644 index 0000000..8bfcd49 --- /dev/null +++ b/src/app/personal-info/personal-info.module.ts @@ -0,0 +1,20 @@ +import {NgModule} from "@angular/core"; +import {CommonModule} from "@angular/common"; +import {PersonalInfoComponent} from "./personal-info.component"; +import {RouterModule} from "@angular/router"; +import {PreviousRouteRecorder} from "../openaireLibrary/utils/piwik/previousRouteRecorder.guard"; +import {LoginGuard} from "../openaireLibrary/login/loginGuard.guard"; +import {PageContentModule} from "../openaireLibrary/dashboard/sharedComponents/page-content/page-content.module"; +import {InputModule} from "../openaireLibrary/sharedComponents/input/input.module"; +import {LoadingModule} from "../openaireLibrary/utils/loading/loading.module"; + +@NgModule({ + imports: [CommonModule, RouterModule.forChild([ + {path: '', component: PersonalInfoComponent, canActivate: [LoginGuard], canDeactivate: [PreviousRouteRecorder]} + ]), PageContentModule, InputModule, LoadingModule], + declarations: [PersonalInfoComponent], + exports: [PersonalInfoComponent] +}) +export class PersonalInfoModule { + +} diff --git a/src/app/personal-token/personal-token.component.ts b/src/app/personal-token/personal-token.component.ts new file mode 100644 index 0000000..7c00e43 --- /dev/null +++ b/src/app/personal-token/personal-token.component.ts @@ -0,0 +1,112 @@ +import {Component, OnInit, ViewChild} from "@angular/core"; +import {UserManagementService} from "../openaireLibrary/services/user-management.service"; +import {NotificationHandler} from "../openaireLibrary/utils/notification-handler"; +import {AlertModal} from "../openaireLibrary/utils/modal/alert"; + +declare var copy; + +@Component({ + selector: 'home', + template: ` +
+
+
+ + + For further information on how to use the tokens please visit the + OpenAIRE API Authentication documentation. + +
+
+
Your personal access token is
+
+
+
+              {{accessToken}}
+            
+
+ + + +
+
+ + Your access token is valid for an hour. +
+
+ + Do not share your personal access token. Send your personal access token only over HTTPS. +
+
+
+
Do you need a refresh token?
+
+ + OpenAIRE refresh token expires after 1 month and allows you to programmatically get a new access token. +
+ +
+
+
Your refresh token is
+
+
+
+              {{refreshToken}}
+            
+
+ + + +
+
+ + OpenAIRE refresh token expires after 1 month and allows you to programmatically get a new access token. +
+
+ + Please copy your refresh token and store it confidentially. You will not be able to retrieve it. + Do not share your refresh token. Send your refresh token only over HTTPS. +
+
+
+
+ + ` +}) +export class PersonalTokenComponent implements OnInit { + accessToken: string; + refreshToken: string; + showRefreshToken: boolean = false; + subscriptions: any[] = []; + @ViewChild("refreshTokenModal") modal: AlertModal; + + constructor(private userManagementService: UserManagementService) { + } + + ngOnInit() { + this.subscriptions.push(this.userManagementService.getUserInfo().subscribe(user => { + this.accessToken = user.accessToken; + this.refreshToken = user.refreshToken; + })) + } + + getRefreshToken() { + this.showRefreshToken = true; + } + + copyToClipboard(id: string) { + if(copy.exec(id)) { + NotificationHandler.rise('Copied to clipboard!'); + } else { + NotificationHandler.rise('Unable to copy to clipboard!', 'danger'); + } + } + + openRefreshTokenModal() { + this.modal.alertTitle = 'Get refresh token'; + this.modal.message = 'In case you already have a refresh token, it will no longer be valid. Do you want to proceed?'; + this.modal.okButtonText = 'Get Refresh Token'; + this.modal.okButtonLeft = false; + this.modal.open(); + } +} diff --git a/src/app/personal-token/personal-token.module.ts b/src/app/personal-token/personal-token.module.ts new file mode 100644 index 0000000..664b33b --- /dev/null +++ b/src/app/personal-token/personal-token.module.ts @@ -0,0 +1,21 @@ +import {NgModule} from "@angular/core"; +import {CommonModule} from "@angular/common"; +import {RouterModule} from "@angular/router"; +import {PreviousRouteRecorder} from "../openaireLibrary/utils/piwik/previousRouteRecorder.guard"; +import {PersonalTokenComponent} from "./personal-token.component"; +import {IconsModule} from "../openaireLibrary/utils/icons/icons.module"; +import {LoginGuard} from "../openaireLibrary/login/loginGuard.guard"; +import {PageContentModule} from "../openaireLibrary/dashboard/sharedComponents/page-content/page-content.module"; +import {AlertModalModule} from "../openaireLibrary/utils/modal/alertModal.module"; +import {HasPersonalInfoGuard} from "../services/hasPersonalInfo.guard"; + +@NgModule({ + declarations: [PersonalTokenComponent], + imports: [CommonModule, RouterModule.forChild([ + {path: '', component: PersonalTokenComponent, canActivate: [LoginGuard, HasPersonalInfoGuard], canDeactivate: [PreviousRouteRecorder]} + ]), IconsModule, PageContentModule, AlertModalModule], + exports: [PersonalTokenComponent] +}) +export class PersonalTokenModule { + +} diff --git a/src/app/reload/libReload.module.ts b/src/app/reload/libReload.module.ts new file mode 100644 index 0000000..18cc6ab --- /dev/null +++ b/src/app/reload/libReload.module.ts @@ -0,0 +1,13 @@ +import { NgModule} from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { FormsModule } from '@angular/forms'; +import { RouterModule } from '@angular/router'; +import { ReloadModule } from '../openaireLibrary/reload/reload.module'; + + +@NgModule({ + imports: [ + CommonModule, FormsModule, RouterModule, ReloadModule + ] +}) +export class LibReloadModule { } diff --git a/src/app/services/apis.service.ts b/src/app/services/apis.service.ts new file mode 100644 index 0000000..f521787 --- /dev/null +++ b/src/app/services/apis.service.ts @@ -0,0 +1,86 @@ +import {Injectable} from "@angular/core"; +import {HttpClient} from "@angular/common/http"; +import {Observable} from "rxjs"; +import {properties} from "../../environments/environment"; +import {CustomOptions} from "../openaireLibrary/services/servicesUtils/customOptions.class"; + +export class RegisteredService { + id: number; + clientId: string; + creationDate: Date; + keyType: "url" | "value" | null; + name: string; + owner: string; + registrationAccessToken: string; + url: string; +} + +export class Details { + clientId: string; + clientIdIssuedAt: number; + clientName: string; + clientSecret: string; + clientSecretExpiresAt: number; + contacts: string[]; + grantTypes: string[]; + jwks: { + keys: [ + { + alg: string; + e: string; + kid: string; + kty: string; + n: string + } + ]; + valid: boolean + }; + jwksUri: string; + logoUri: string; + policyUri: string; + redirectUris: string[]; + registrationAccessToken: string; + registrationClientUri: string; + scope: string; + tokenEndpointAuthMethod: string; + tokenEndpointAuthSigningAlg: string; +} + +export class API { + service: RegisteredService; + details: Details; + + constructor() { + this.service = new RegisteredService(); + this.details = new Details(); + } +} + +@Injectable({ + providedIn: 'root' +}) +export class ApisService { + + constructor(private http: HttpClient) { + } + + public getAll(): Observable { + return this.http.get(properties.clientManagementUrl + "/apis/all", CustomOptions.registryOptions()); + } + + public getMyServices(): Observable { + return this.http.get(properties.clientManagementUrl + "/apis/my-services", CustomOptions.registryOptions()); + } + + public create(form: any): Observable { + return this.http.post(properties.clientManagementUrl + "/apis/save/new", form, CustomOptions.registryOptions()); + } + + public save(id: number, form: any): Observable { + return this.http.post(properties.clientManagementUrl + "/apis/save/" + id, form, CustomOptions.registryOptions()); + } + + public delete(id: number): Observable { + return this.http.delete(properties.clientManagementUrl + "/apis/delete/" + id, CustomOptions.registryOptions()); + } +} diff --git a/src/app/services/hasPersonalInfo.guard.ts b/src/app/services/hasPersonalInfo.guard.ts new file mode 100644 index 0000000..a0883c6 --- /dev/null +++ b/src/app/services/hasPersonalInfo.guard.ts @@ -0,0 +1,24 @@ +import {ActivatedRouteSnapshot, CanActivate, Router, RouterStateSnapshot, UrlTree} from "@angular/router"; +import {Injectable} from "@angular/core"; +import {Observable, of} from "rxjs"; +import {PersonalService} from "./personal.service"; +import {catchError, map, tap} from "rxjs/operators"; + +@Injectable({ + providedIn: 'root' +}) +export class HasPersonalInfoGuard implements CanActivate { + + constructor(private personalService: PersonalService, + private router: Router) { + } + + canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable | Promise | boolean | UrlTree { + return this.personalService.getPersonalInfo().pipe(catchError(() => of(null)), map(info => !!info), tap(hasInfo => { + if(!hasInfo) { + this.router.navigate(['/personal-info'], {skipLocationChange: true, state: {message: true}}); + } + })); + } + +} diff --git a/src/app/services/personal.service.ts b/src/app/services/personal.service.ts new file mode 100644 index 0000000..0f76d96 --- /dev/null +++ b/src/app/services/personal.service.ts @@ -0,0 +1,31 @@ +import {Injectable} from "@angular/core"; +import {HttpClient} from "@angular/common/http"; +import {properties} from "../../environments/environment"; +import {CustomOptions} from "../openaireLibrary/services/servicesUtils/customOptions.class"; + +export interface PersonalInfo { + id: string; + email: string; + name: string; + surname: string; + affiliation: string; + position: string; +} + +@Injectable({ + providedIn: 'root' +}) +export class PersonalService { + + + constructor(private http: HttpClient) { + } + + public getPersonalInfo() { + return this.http.get(properties.clientManagementUrl + '/personal/my-info', CustomOptions.registryOptions()); + } + + public savePersonalInfo(personalInfo: PersonalInfo) { + return this.http.post(properties.clientManagementUrl + '/personal/save', personalInfo, CustomOptions.registryOptions()); + } +} diff --git a/src/assets/common-assets b/src/assets/common-assets index f15bbfa..39cb4e5 160000 --- a/src/assets/common-assets +++ b/src/assets/common-assets @@ -1 +1 @@ -Subproject commit f15bbfa7265f170e06b256f086f2acedfa3f72e0 +Subproject commit 39cb4e56d0320975fbd08fb5e9c68b87b0c82a10 diff --git a/src/assets/openaire-theme b/src/assets/openaire-theme index 468b2b0..1e06310 160000 --- a/src/assets/openaire-theme +++ b/src/assets/openaire-theme @@ -1 +1 @@ -Subproject commit 468b2b008f474c88dacc52991a5231693833f1f9 +Subproject commit 1e06310b8b2b56f131754eaded615b6cf32c5848 diff --git a/src/environments/environment.beta.ts b/src/environments/environment.beta.ts new file mode 100644 index 0000000..90bf2a0 --- /dev/null +++ b/src/environments/environment.beta.ts @@ -0,0 +1,14 @@ +import {EnvProperties} from '../app/openaireLibrary/utils/properties/env-properties'; + +export let properties: EnvProperties = { + environment: 'production', + dashboard: 'client-management-portal', + useCache: false, + loginUrl: "https://beta.services.openaire.eu/client-management/openid_connect_login", + userInfoUrl: "https://beta.services.openaire.eu/client-management/userInfo", + clientManagementUrl: "https://beta.services.openaire.eu/client-management", + logoutUrl: "https://beta.services.openaire.eu/client-management/openid_logout", + cookieDomain: '.openaire.eu', + errorLink: '/error', +}; + diff --git a/src/environments/environment.prod.ts b/src/environments/environment.prod.ts index a116e1b..dfff31c 100644 --- a/src/environments/environment.prod.ts +++ b/src/environments/environment.prod.ts @@ -4,9 +4,10 @@ export let properties: EnvProperties = { environment: 'production', dashboard: 'client-management-portal', useCache: false, - loginUrl: "https://services.openaire.eu/login-service/openid_connect_login", - userInfoUrl: "https://services.openaire.eu/login-service/userInfo", - logoutUrl: "https://services.openaire.eu/login-service/openid_logout", + loginUrl: "https://services.openaire.eu/client-management/openid_connect_login", + userInfoUrl: "https://services.openaire.eu/client-management/userInfo", + clientManagementUrl: "https://services.openaire.eu/client-management", + logoutUrl: "https://services.openaire.eu/client-management/openid_logout", cookieDomain: '.openaire.eu', errorLink: '/error', }; diff --git a/src/environments/environment.ts b/src/environments/environment.ts index 07f86b7..ec46107 100644 --- a/src/environments/environment.ts +++ b/src/environments/environment.ts @@ -9,9 +9,12 @@ export let properties: EnvProperties = { environment: 'development', dashboard: 'client-management-portal', useCache: false, - loginUrl: "http://mpagasas.di.uoa.gr:19080/login-service/openid_connect_login", - userInfoUrl: "http://mpagasas.di.uoa.gr:19080/login-service/userInfo", - logoutUrl: "http://mpagasas.di.uoa.gr:19080/login-service/openid_logout", + loginUrl: "http://mpagasas.di.uoa.gr:19580/client-management/openid_connect_login", + userInfoUrl: "http://mpagasas.di.uoa.gr:19580/client-management/userInfo", + clientManagementUrl: "http://mpagasas.di.uoa.gr:19580/client-management", + logoutUrl: "http://mpagasas.di.uoa.gr:19580/client-management/openid_logout", cookieDomain: '.di.uoa.gr', errorLink: '/error', + domain: 'http://mpagasas.di.uoa.gr:5001', + baseLink: '' }; diff --git a/src/styles.less b/src/styles.less index 90d4ee0..f4d65c9 100644 --- a/src/styles.less +++ b/src/styles.less @@ -1 +1,12 @@ /* You can add global styles to this file, and also import other style files */ +@import "~src/assets/openaire-theme/less/_import"; +@import "~src/assets/common-assets/less/general"; +@import "~src/assets/common-assets/less/user"; +@import "~src/assets/common-assets/less/dashboard"; + + +.uk-alert-primary { + a, .uk-link { + text-decoration: underline; + } +}