462 lines
24 KiB
TypeScript
462 lines
24 KiB
TypeScript
import {ChangeDetectorRef, Component, OnDestroy, OnInit, ViewChild} from "@angular/core";
|
|
import {FormArray, FormBuilder, FormGroup, Validators} from "@angular/forms";
|
|
import {Subscription} from "rxjs";
|
|
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 {UserManagementService} from "../openaireLibrary/services/user-management.service";
|
|
|
|
declare var copy;
|
|
|
|
@Component({
|
|
selector: `apis`,
|
|
template: `
|
|
<div page-content>
|
|
<div header class="uk-section-small uk-container uk-container-small uk-flex uk-flex-right@m uk-flex-center">
|
|
<button (click)="openEditModal()" class="uk-button uk-button-primary uk-flex uk-flex-middle">
|
|
<icon name="add" [flex]="true"></icon>
|
|
<span class="uk-margin-xsmall-left">New Service</span>
|
|
</button>
|
|
</div>
|
|
<div inner class="uk-section-small uk-container uk-container-small">
|
|
<div *ngIf="loading" class="uk-height-large uk-position-relative">
|
|
<loading class="uk-position-center"></loading>
|
|
</div>
|
|
<div *ngIf="!loading">
|
|
<div class="uk-alert-primary uk-alert uk-margin-top-remove uk-flex uk-flex-middle">
|
|
<icon name="info" [flex]="true"></icon>
|
|
<span class="uk-margin-small-left">You can register up to 5 services.
|
|
For more information please read the <a href="https://graph.openaire.eu/docs/apis/authentication" target="_blank">OpenAIRE API Authentication documentation</a>.</span>
|
|
</div>
|
|
<div *ngIf="apis.length === 0"
|
|
class="uk-margin-large-top uk-card uk-card-default uk-height-small uk-position-relative">
|
|
<div class="uk-position-center uk-text-bold">You have not registered any service yet!</div>
|
|
</div>
|
|
<table *ngIf="apis.length > 0" class="uk-table uk-table-divider uk-margin-medium-top">
|
|
<thead>
|
|
<tr>
|
|
<th></th>
|
|
<th>Name</th>
|
|
<th>Client ID</th>
|
|
<th>Creation Date</th>
|
|
<th>Actions</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<tr *ngFor="let api of apis; let i=index">
|
|
<td><img [src]="getLogoUrl(api)" [alt]="api.service.name + ' logo'"/></td>
|
|
<td>{{api.service.name}}</td>
|
|
<td>{{api.service.clientId}}</td>
|
|
<td>{{api.service.creationDate | date: 'dd-MM-YYYY HH:mm'}}</td>
|
|
<td>
|
|
<span class="uk-flex uk-flex-middle">
|
|
<icon *ngIf="api.details" name="edit" class="clickable" customClass="uk-text-primary"
|
|
(click)="openEditModal(i)"></icon>
|
|
<icon name="delete" class="clickable uk-margin-xsmall-left"
|
|
customClass="uk-text-danger" (click)="openDeleteModal(i)"></icon>
|
|
<icon *ngIf="!api.invalid && api.details" name="info" class="clickable uk-margin-xsmall-left"
|
|
customClass="uk-text-secondary" (click)="openInfoModal(i)"></icon>
|
|
<icon *ngIf="api.invalid" name="warning" class="clickable uk-margin-xsmall-left"
|
|
customClass="uk-text-warning"
|
|
uk-tooltip="Please click edit to fill the missing information and preview your details."></icon>
|
|
<icon *ngIf="!api.details" name="warning" class="clickable uk-margin-xsmall-left"
|
|
customClass="uk-text-warning"
|
|
uk-tooltip="This service doesn't exist in our database. Please contact administrator."></icon>
|
|
</span>
|
|
</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<modal-alert #editModal (alertOutput)="save()" [large]="true"
|
|
[okDisabled]="form?.disabled || form?.invalid || form?.pristine">
|
|
<div *ngIf="form" class="uk-grid" uk-grid>
|
|
<div class="uk-width-1-2@m uk-width-1-1" input placeholder="Service Name"
|
|
[formInput]="form.get('name')"></div>
|
|
<div class="uk-width-1-2@m uk-width-1-1" input type="URL" placeholder="Service URL"
|
|
[formInput]="form.get('url')"></div>
|
|
<div class="uk-width-1-1" input type="textarea" placeholder="Service Description"
|
|
[formInput]="form.get('description')">
|
|
</div>
|
|
<div class="uk-width-1-1" input type="chips" placeholder="Contact Emails" [visibleChips]="3"
|
|
[separators]="[',']" [formInput]="contacts" [validators]="Validators.email" [addExtraChips]="true" [hint]="'Add a contact email for your service'">
|
|
<div note>Separate groups with commas</div>
|
|
</div>
|
|
<div class="uk-width-1-1" input type="chips" placeholder="Target groups"
|
|
[addExtraChips]="true" [separators]="[',']" [visibleChips]="groups.length"
|
|
[formInput]="target" [options]="groups" [showOptionsOnEmpty]="false" [hint]="'Add a target group of your service'">
|
|
<div note>Separate groups with commas</div>
|
|
</div>
|
|
<div class="uk-width-1-2@m uk-width-1-1" input type="logoURL" placeholder="Logo URL"
|
|
[formInput]="form.get('logoURL')">
|
|
</div>
|
|
<div class="uk-width-1-2@m uk-width-1-1" input type="select" placeholder="Frequency of use (statistical purposes)"
|
|
[formInput]="form.get('frequency')" [options]="frequency">
|
|
</div>
|
|
<div class="uk-width-1-1">
|
|
<label class="uk-text-bold">Security Level</label>
|
|
<div id="security-hint" class="uk-margin">Register your service to get a client id and a client
|
|
secret. Use the client id and secret to make your requests.
|
|
<a href="https://graph.openaire.eu/docs/apis/authentication/#basic-service-authentication-and-registration" target="_blank">Read more...</a>
|
|
</div>
|
|
<div class="uk-flex uk-flex-middle">
|
|
<div class="uk-margin-small-right">
|
|
<label><input type="radio" name="securityLevel" [disabled]="index !== -1"
|
|
class="uk-radio" (ngModelChange)="securityChanged($event)"
|
|
[ngModel]="advanced" [value]="false"><span
|
|
class="uk-margin-small-left" [class.uk-text-muted]="index !== -1">Basic</span></label>
|
|
</div>
|
|
<div>
|
|
<label><input type="radio" name="securityLevel" [disabled]="index !== -1"
|
|
class="uk-radio" (ngModelChange)="securityChanged($event)"
|
|
[ngModel]="advanced" [value]="true"><span
|
|
class="uk-margin-small-left" [class.uk-text-muted]="index !== -1">Advanced</span></label>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div id="advanced" class="uk-width-1-1" *ngIf="advanced">
|
|
<div class="uk-width-1-1">
|
|
<div class="uk-flex uk-flex-between">
|
|
<label class="uk-text-bold">Public Key</label>
|
|
<div class="uk-flex uk-flex-middle">
|
|
<div class="uk-margin-small-right">
|
|
<label><input type="radio" name="keyType" class="uk-radio"
|
|
(ngModelChange)="keyTypeChanged($event)" [ngModel]="byValue"
|
|
[value]="true"><span
|
|
class="uk-margin-small-left">By Value</span></label>
|
|
</div>
|
|
<div>
|
|
<label><input type="radio" name="keyType" class="uk-radio"
|
|
(ngModelChange)="keyTypeChanged($event)" [ngModel]="byValue"
|
|
[value]="false"><span
|
|
class="uk-margin-small-left">By URI</span></label>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div *ngIf="byValue" class="uk-width-1-1" input type="textarea" [formInput]="form.get('value')"
|
|
rows="5" placeholder="Key Value" [hint]="hint"></div>
|
|
<div *ngIf="!byValue" class="uk-width-1-1" type="logoURL" input [formInput]="form.get('uri')"
|
|
placeholder="Key URI"></div>
|
|
</div>
|
|
</div>
|
|
</modal-alert>
|
|
<modal-alert #deleteModal [overflowBody]="false" (alertOutput)="confirm()"></modal-alert>
|
|
<modal-alert #infoModal [large]="false">
|
|
<div *ngIf="api?.details">
|
|
<ul class="uk-list uk-list-divider">
|
|
<li>
|
|
<div class="uk-flex">
|
|
<div class="uk-text-bold uk-width-small uk-margin-xsmall-right">Service Name</div>
|
|
<div class="uk-width-expand uk-text-break">{{api.details.clientName}}</div>
|
|
</div>
|
|
</li>
|
|
<li>
|
|
<div class="uk-flex">
|
|
<div class="uk-text-bold uk-width-small uk-margin-xsmall-right">
|
|
<div class="uk-text-bold uk-flex uk-flex-middle">
|
|
<span class="uk-margin-xsmall-right">Client Id</span>
|
|
<a class="uk-link uk-link-reset" (click)="copyToClipboard('client-id')"><icon name="content_copy" customClass="uk-text-secondary" [flex]="true"></icon></a>
|
|
</div>
|
|
</div>
|
|
<div id="client-id" class="uk-width-expand uk-text-break">{{api.details.clientId}}</div>
|
|
</div>
|
|
</li>
|
|
<li>
|
|
<div class="uk-flex">
|
|
<div class="uk-text-bold uk-width-small uk-margin-xsmall-right">Scope</div>
|
|
<div class="uk-width-expand uk-text-break">openid</div>
|
|
</div>
|
|
</li>
|
|
<li>
|
|
<div class="uk-flex">
|
|
<div class="uk-text-bold uk-width-small uk-margin-xsmall-right">Grant type</div>
|
|
<div class="uk-width-expand uk-text-break">{{api.details.grantTypes.join(', ')}}</div>
|
|
</div>
|
|
</li>
|
|
<li *ngIf="api.service.contacts?.length > 0">
|
|
<div class="uk-flex">
|
|
<div class="uk-text-bold uk-width-small uk-margin-xsmall-right">Contacts</div>
|
|
<div class="uk-width-expand uk-text-break">{{api.service.contacts.join(', ')}}</div>
|
|
</div>
|
|
</li>
|
|
<li *ngIf="!api.service.contacts?.length > 0 && api.details.contacts?.length > 0">
|
|
<div class="uk-flex">
|
|
<div class="uk-text-bold uk-width-small uk-margin-xsmall-right">Contacts</div>
|
|
<div class="uk-width-expand uk-text-break">{{api.details.contacts.join(', ')}}</div>
|
|
</div>
|
|
</li>
|
|
<ng-template [ngIf]="!api.service.keyType" [ngIfElse]="key">
|
|
<div class="uk-flex">
|
|
<div class="uk-text-bold uk-width-small uk-margin-xsmall-right">
|
|
<div class="uk-text-bold uk-flex uk-flex-middle">
|
|
<span class="uk-margin-xsmall-right">Client Secret</span>
|
|
<a class="uk-link uk-link-reset" (click)="copyToClipboard('client-secret')"><icon name="content_copy" customClass="uk-text-secondary" [flex]="true"></icon></a>
|
|
</div>
|
|
</div>
|
|
<div id="client-secret" class="uk-width-expand uk-text-break">{{api.details.clientSecret}}</div>
|
|
</div>
|
|
<li>
|
|
<div class="uk-flex">
|
|
<div class="uk-text-bold uk-width-small uk-margin-xsmall-right">Authentication method</div>
|
|
<div class="uk-width-expand uk-text-break">Client Secret Basic</div>
|
|
</div>
|
|
</li>
|
|
</ng-template>
|
|
<ng-template #key>
|
|
<li>
|
|
<div class="uk-flex">
|
|
<div class="uk-text-bold uk-width-small uk-margin-xsmall-right">Authentication method</div>
|
|
<div class="uk-width-expand uk-text-break">Asymmetrically-signed JWT assertion</div>
|
|
</div>
|
|
</li>
|
|
<li>
|
|
<div class="uk-flex">
|
|
<div class="uk-text-bold uk-width-small uk-margin-xsmall-right">Token Endpoint Authentication Signing Algorithm</div>
|
|
<div class="uk-width-expand uk-text-break">RSASSA using SHA-256 hash algorithm</div>
|
|
</div>
|
|
</li>
|
|
<div class="uk-flex">
|
|
<div class="uk-text-bold uk-width-small uk-margin-xsmall-right">
|
|
<div class="uk-text-bold uk-flex uk-flex-middle">
|
|
<span class="uk-margin-xsmall-right">Public Key</span>
|
|
<a class="uk-link uk-link-reset" (click)="copyToClipboard('public-key')"><icon name="content_copy" customClass="uk-text-secondary" [flex]="true"></icon></a>
|
|
</div>
|
|
</div>
|
|
<div *ngIf="api.details.jwksUri" id="public-key" class="uk-width-expand uk-text-break">{{api.details.jwksUri}}</div>
|
|
<div *ngIf="api.details.jwks" id="public-key" class="uk-width-expand uk-text-break">
|
|
<pre><code class="uk-overflow-auto">{{api.details.jwks.keys[0] | json}}</code></pre>
|
|
</div>
|
|
</div>
|
|
</ng-template>
|
|
<li>
|
|
<div class="uk-flex">
|
|
<div class="uk-text-bold uk-width-small uk-margin-xsmall-right">Creation Date</div>
|
|
<div class="uk-width-expand uk-text-break">{{api.service.creationDate | date: 'dd-MM-YYYY HH:mm'}}</div>
|
|
</div>
|
|
</li>
|
|
</ul>
|
|
</div>
|
|
</modal-alert>
|
|
`,
|
|
styleUrls: ['apis.component.less']
|
|
})
|
|
export class ApisComponent implements OnInit, OnDestroy {
|
|
form: FormGroup;
|
|
loading: boolean = true;
|
|
subscriptions: any[] = [];
|
|
apis: API[] = [];
|
|
api: API;
|
|
index: number = -1;
|
|
advanced: boolean = false;
|
|
byValue: boolean = true;
|
|
hint = '{"kty": ..., "e": ... , "use": ... , "kid": ..., "alg": ... , "n": ...}';
|
|
frequency: string[] = ['Daily', 'Weekly', 'Monthly'];
|
|
groups: string[] = ['Commerce', 'Education', 'Health', 'Non profit', 'Public Sector'];
|
|
@ViewChild("editModal") editModal: AlertModal;
|
|
@ViewChild("deleteModal") deleteModal: AlertModal;
|
|
@ViewChild("infoModal") infoModal: AlertModal;
|
|
|
|
constructor(private fb: FormBuilder,
|
|
private apisService: ApisService,
|
|
private userManagementService: UserManagementService,
|
|
private cdr: ChangeDetectorRef) {
|
|
}
|
|
|
|
ngOnInit() {
|
|
this.loading = true;
|
|
this.subscriptions.push(this.apisService.getMyServices().subscribe(apis => {
|
|
this.apis = apis;
|
|
this.loading = false;
|
|
}, error => {
|
|
this.apis = [];
|
|
this.loading = false;
|
|
}));
|
|
}
|
|
|
|
ngOnDestroy() {
|
|
this.subscriptions.forEach(subscription => {
|
|
if(subscription instanceof Subscription) {
|
|
subscription.unsubscribe();
|
|
}
|
|
})
|
|
}
|
|
|
|
get target():FormArray {
|
|
if(this.form) {
|
|
return <FormArray>this.form.get('target');
|
|
} else {
|
|
return this.fb.array([]);
|
|
}
|
|
}
|
|
|
|
get contacts():FormArray {
|
|
if(this.form) {
|
|
return <FormArray>this.form.get('contacts');
|
|
} else {
|
|
return this.fb.array([]);
|
|
}
|
|
}
|
|
|
|
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";
|
|
api.service.contacts = [this.userManagementService.user.email];
|
|
}
|
|
this.form = this.fb.group({
|
|
name: this.fb.control(api.service.name, Validators.required),
|
|
keyType: this.fb.control(api.service.keyType),
|
|
description: this.fb.control(api.service.description, Validators.required),
|
|
contacts: this.fb.array([], Validators.required),
|
|
frequency: this.fb.control(api.service.frequency),
|
|
target: this.fb.array([], Validators.required),
|
|
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.service.contacts) {
|
|
api.service.contacts.forEach(email => {
|
|
this.contacts.push(this.fb.control(email));
|
|
});
|
|
}
|
|
if(api.service.target) {
|
|
api.service.target.forEach(target => {
|
|
this.target.push(this.fb.control(target));
|
|
});
|
|
}
|
|
if(api.details?.jwks?.keys) {
|
|
this.form.get('value').setValue(JSON.stringify(api.details.jwks.keys[0]));
|
|
}
|
|
if(api.invalid) {
|
|
this.form.markAllAsTouched();
|
|
this.cdr.detectChanges();
|
|
}
|
|
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 => {
|
|
let index = this.apis.push(api) - 1;
|
|
NotificationHandler.rise('Your service has been created successfully.');
|
|
this.openInfoModal(index);
|
|
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 saved successfully.');
|
|
this.openInfoModal(this.index);
|
|
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);
|
|
document.getElementById('advanced').scrollIntoView({behavior: 'smooth'});
|
|
} 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();
|
|
}));
|
|
}
|
|
|
|
public 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');
|
|
}
|
|
}
|
|
|
|
protected readonly Validators = Validators;
|
|
}
|