diff --git a/projects/dashboard-core/assets/src/asset-card/asset-card.component.ts b/projects/dashboard-core/assets/src/asset-card/asset-card.component.ts index ba7c8598f..b18799fe4 100644 --- a/projects/dashboard-core/assets/src/asset-card/asset-card.component.ts +++ b/projects/dashboard-core/assets/src/asset-card/asset-card.component.ts @@ -37,6 +37,6 @@ export class AssetCardComponent implements OnChanges { ngOnChanges() { this.name = this.asset?.properties.optionalValue('edc', 'name'); this.contentType = this.asset?.properties.optionalValue('edc', 'contenttype'); - this.type = this.asset?.dataAddress.mandatoryValue('edc', 'type'); + this.type = 'DataplaneMetadata'; } } diff --git a/projects/dashboard-core/assets/src/asset-create/asset-create.component.html b/projects/dashboard-core/assets/src/asset-create/asset-create.component.html index a9369e7a4..8acc5fb14 100644 --- a/projects/dashboard-core/assets/src/asset-create/asset-create.component.html +++ b/projects/dashboard-core/assets/src/asset-create/asset-create.component.html @@ -51,11 +51,9 @@

Asset

- Data Address + Dataplane Metadata
diff --git a/projects/dashboard-core/assets/src/asset-create/asset-create.component.ts b/projects/dashboard-core/assets/src/asset-create/asset-create.component.ts index c50027d58..83c78018b 100644 --- a/projects/dashboard-core/assets/src/asset-create/asset-create.component.ts +++ b/projects/dashboard-core/assets/src/asset-create/asset-create.component.ts @@ -13,15 +13,7 @@ */ import { Component, EventEmitter, Input, OnChanges, Output, inject } from '@angular/core'; -import { - Asset, - AssetInput, - BaseDataAddress, - compact, - DataAddress, - EdcConnectorClientError, - IdResponse, -} from '@think-it-labs/edc-connector-client'; +import { Asset, compact, EdcConnectorClientError, IdResponse } from '@think-it-labs/edc-connector-client'; import { NgClass } from '@angular/common'; import { AssetService } from '../asset.service'; import { @@ -34,6 +26,15 @@ import { import { FormBuilder, FormGroup, ReactiveFormsModule } from '@angular/forms'; import { JsonValue } from '@angular-devkit/core'; +type DataplaneMetadataFormValue = { + type?: string; + method?: string; + baseUrl?: string; + ttl?: number | string; + username?: string; + password?: string; +}; + @Component({ selector: 'lib-asset-create', standalone: true, @@ -62,7 +63,6 @@ export class AssetCreateComponent implements OnChanges { properties: Record = {}; privateProperties: Record = {}; - dataAddress?: DataAddress; assetForm: FormGroup; @@ -85,15 +85,31 @@ export class AssetCreateComponent implements OnChanges { private async updateAssetAndSyncForm() { this.properties = await compact(this.asset!.properties); this.privateProperties = await compact(this.asset!.privateProperties); - this.dataAddress = (await compact(this.asset!.dataAddress)) as unknown as BaseDataAddress; this.assetForm.get('id')?.setValue(this.asset!.id); - this.assetForm.get('name')?.setValue(this.properties['name']); - this.assetForm.get('contenttype')?.setValue(this.properties['contenttype']); + this.assetForm.get('name')?.setValue(this.propertyValue('name')); + this.assetForm.get('contenttype')?.setValue(this.propertyValue('contenttype')); + + setTimeout(async () => { + const dpm = await this.compactDataplaneMetadata(); + + if (dpm) { + this.assetForm.patchValue({ + dataplaneMetadata: { + type: dpm['type'] || 'HttpData', + method: dpm['method'] || 'GET', + baseUrl: dpm['baseUrl'] || dpm['url'] || '', + ttl: dpm['ttl'] || 600, + username: dpm['auth.username'] || dpm['auth']?.['username'] || '', + password: dpm['auth.password'] || dpm['auth']?.['password'] || '', + }, + }); + } + }); } createAsset(): void { if (this.assetForm.valid) { - const assetInput: AssetInput = this.createAssetInput(); + const assetInput = this.createAssetInput(); if (this.mode === 'create') { this.assetService .createAsset(assetInput) @@ -114,21 +130,81 @@ export class AssetCreateComponent implements OnChanges { } } - private createAssetInput(): AssetInput { - const asset: AssetInput = { - dataAddress: this.dataAddress!, - properties: this.properties, - privateProperties: this.privateProperties, + private propertyValue(key: string): JsonValue | undefined { + const properties = this.properties as any; + const raw = + properties[key] ?? + properties[`edc:${key}`] ?? + properties[`asset:prop:${key}`] ?? + properties[`https://w3id.org/edc/v0.0.1/ns/${key}`]; + + if (Array.isArray(raw) && raw.length === 1) { + return raw[0]?.['@value'] ?? raw[0]?.['@id'] ?? raw[0]; + } + + return raw?.['@value'] ?? raw?.['@id'] ?? raw; + } + + private createAssetInput(): any { + const formValue = this.assetForm.getRawValue(); + const dataplaneMetadata = this.createDataplaneMetadataProperties( + this.assetForm.get('dataplaneMetadata')?.value as DataplaneMetadataFormValue, + ); + + const asset: any = { + '@context': ['https://w3id.org/edc/connector/management/v2'], + '@type': 'Asset', + properties: { + ...this.properties, + }, + privateProperties: { + ...this.privateProperties, + }, + dataplaneMetadata: { + '@type': 'DataplaneMetadata', + properties: dataplaneMetadata, + }, }; - if (this.assetForm.value.id) { - asset['@id'] = this.assetForm.value.id; + + if (formValue.id) { + asset['@id'] = formValue.id; } - if (this.assetForm.value.name) { - asset.properties['name'] = this.assetForm.value.name; + if (formValue.name) { + asset.properties['name'] = formValue.name; } - if (this.assetForm.value.contenttype) { - asset.properties['contenttype'] = this.assetForm.value.contenttype; + if (formValue.contenttype) { + asset.properties['contenttype'] = formValue.contenttype; } + return asset; } -} + + private createDataplaneMetadataProperties(dpm?: DataplaneMetadataFormValue): Record { + const url = String(dpm?.baseUrl ?? '').trim(); + const properties: Record = { + type: dpm?.type || 'HttpData', + method: dpm?.method || 'GET', + url, + ttl: Number(dpm?.ttl ?? 600), + }; + + if (dpm?.username && dpm.password) { + properties['auth.type'] = 'basic'; + properties['auth.username'] = dpm.username; + properties['auth.password'] = dpm.password; + } + + return properties; + } + + private async compactDataplaneMetadata(): Promise { + const raw = (this.asset as any)?.dataplaneMetadata ?? (this.properties as any)?.dataplaneMetadata; + if (!raw) { + return undefined; + } + + const compacted = await compact(raw); + return compacted?.properties?.['@value'] ?? compacted?.properties ?? raw?.properties?.['@value'] ?? raw?.properties; + } + +} \ No newline at end of file diff --git a/projects/dashboard-core/assets/src/asset-view/asset-view.component.ts b/projects/dashboard-core/assets/src/asset-view/asset-view.component.ts index d1c463d80..9c038b60f 100644 --- a/projects/dashboard-core/assets/src/asset-view/asset-view.component.ts +++ b/projects/dashboard-core/assets/src/asset-view/asset-view.component.ts @@ -67,7 +67,7 @@ export class AssetViewComponent implements OnInit, OnDestroy { asset.id.includes(searchText) || asset.properties.optionalValue('edc', 'name')?.toLowerCase().includes(lower) || asset.properties.optionalValue('edc', 'contenttype')?.toLowerCase().includes(lower) || - asset.dataAddress.mandatoryValue('edc', 'type').toLowerCase().includes(lower), + asset.properties.optionalValue('edc', 'contenttype'), ), ), ); diff --git a/projects/dashboard-core/assets/src/asset.service.ts b/projects/dashboard-core/assets/src/asset.service.ts index c9805e72a..057656499 100644 --- a/projects/dashboard-core/assets/src/asset.service.ts +++ b/projects/dashboard-core/assets/src/asset.service.ts @@ -13,8 +13,10 @@ */ import { Injectable, inject } from '@angular/core'; -import { EdcClientService } from '@eclipse-edc/dashboard-core'; +import { DashboardStateService, EdcClientService } from '@eclipse-edc/dashboard-core'; import { Asset, AssetInput, IdResponse } from '@think-it-labs/edc-connector-client'; +import { HttpClient } from '@angular/common/http'; +import { firstValueFrom } from 'rxjs'; /** * Service to manage and retrieve assets. @@ -23,40 +25,139 @@ import { Asset, AssetInput, IdResponse } from '@think-it-labs/edc-connector-clie providedIn: 'root', }) export class AssetService { + private readonly http = inject(HttpClient); + private readonly stateService = inject(DashboardStateService); private readonly edc = inject(EdcClientService); /** * Retrieves all assets from the management API. * @returns A promise that resolves to an array of assets. */ + // public async getAllAssets(): Promise { + // const assets = await firstValueFrom( + // this.http.post(await this.assetEndpoint('/request'), { '@type': 'QuerySpec' }, await this.options()), + // ); + // return assets.map(asset => this.toAssetEntity(asset)); + // } + public async getAllAssets(): Promise { - return (await this.edc.getClient()).management.assets.queryAll(); + const client = await this.edc.getClient(); + return client.management.assets.queryAll(); } - /** - * Creates a new asset using the provided asset input. - * @param assetInput - The input data required to create a new asset. - * @returns A promise that resolves to the ID response of the created asset. - */ + // /** + // * Creates a new asset using the provided asset input. + // * @param assetInput - The input data required to create a new asset. + // * @returns A promise that resolves to the ID response of the created asset. + // */ + // public async createAsset(assetInput: AssetInput): Promise { + // const response = await firstValueFrom(this.http.post(await this.assetEndpoint(), assetInput, await this.options())); + // return this.toIdResponse(response); + // } + + // /** + // * Updates an existing asset with the provided asset input. + // * @param assetInput - The input data required to update the asset. + // * @returns A promise that resolves when the asset is successfully updated. + // */ + // public async updateAsset(assetInput: AssetInput): Promise { + // await firstValueFrom(this.http.put(await this.assetEndpoint(), assetInput, await this.options())); + // } + + // /** + // * Deletes an asset based on the provided ID. + // * @param id - The unique identifier of the asset to be deleted. + // * @returns A promise that resolves when the asset is successfully deleted. + // */ + // public async deleteAsset(id: string): Promise { + // await firstValueFrom(this.http.delete(await this.assetEndpoint(`/${encodeURIComponent(id)}`), await this.options())); + // } + + // private toAssetEntity(rawAsset: any): Asset { + // const properties = rawAsset.properties ?? {}; + // const privateProperties = rawAsset.privateProperties ?? {}; + + // return { + // ...rawAsset, + // id: rawAsset['@id'] ?? rawAsset.id, + // properties: this.withValueAccessors(properties), + // privateProperties: this.withValueAccessors(privateProperties), + // } as Asset; + // } + + // private withValueAccessors(values: Record) { + // return { + // ...values, + // optionalValue: (_ns: string, key: string): T | undefined => this.value(values, key), + // mandatoryValue: (_ns: string, key: string): T => this.value(values, key), + // }; + // } + + // private value(entity: Record, key: string): any { + // const raw = + // entity[`edc:${key}`] ?? + // entity[`asset:prop:${key}`] ?? + // entity[`https://w3id.org/edc/v0.0.1/ns/${key}`] ?? + // entity[key]; + + // if (Array.isArray(raw) && raw.length === 1) { + // return raw[0]?.['@value'] ?? raw[0]?.['@id'] ?? raw[0]; + // } + + // return raw?.['@value'] ?? raw?.['@id'] ?? raw; + // } + + // private toIdResponse(response: any): IdResponse { + // return { + // ...response, + // id: response.id ?? response['@id'], + // } as IdResponse; + // } + + // private async assetEndpoint(path = ''): Promise { + // const config = await firstValueFrom(this.stateService.currentEdcConfig$); + // if (!config) { + // throw new Error('No EDC configuration available.'); + // } + // return `${config.managementUrl}/v4/assets${path}`; + // } + + // private async options() { + // const config = await firstValueFrom(this.stateService.currentEdcConfig$); + // return { + // headers: config?.apiToken ? { 'x-api-key': config.apiToken } : undefined, + // }; + // } + public async createAsset(assetInput: AssetInput): Promise { - return (await this.edc.getClient()).management.assets.create(assetInput); + // return (await this.edc.getClient()).management.assets.create(assetInput); + const { url, options } = await this.assetEndpoint(); + return firstValueFrom(this.http.post(url, assetInput, options)); } - - /** - * Updates an existing asset with the provided asset input. - * @param assetInput - The input data required to update the asset. - * @returns A promise that resolves when the asset is successfully updated. - */ + public async updateAsset(assetInput: AssetInput): Promise { - return (await this.edc.getClient()).management.assets.update(assetInput); + //return (await this.edc.getClient()).management.assets.update(assetInput); + const { url, options } = await this.assetEndpoint(); + return (await firstValueFrom(this.http.put(url, assetInput, options))); } - - /** - * Deletes an asset based on the provided ID. - * @param id - The unique identifier of the asset to be deleted. - * @returns A promise that resolves when the asset is successfully deleted. - */ + public async deleteAsset(id: string): Promise { return (await this.edc.getClient()).management.assets.delete(id); + + } + + private async assetEndpoint() { + const config = await firstValueFrom(this.stateService.currentEdcConfig$); + if (!config) { + throw new Error('No EDC configuration available.'); + } + + return { + url: `${config.managementUrl}/v4/assets`, + options: { + headers: config.apiToken ? { 'x-api-key': config.apiToken } : undefined, + }, + }; } + } diff --git a/projects/dashboard-core/catalog/src/catalog-view/catalog-view.component.ts b/projects/dashboard-core/catalog/src/catalog-view/catalog-view.component.ts index 432706ef0..63baebc7a 100644 --- a/projects/dashboard-core/catalog/src/catalog-view/catalog-view.component.ts +++ b/projects/dashboard-core/catalog/src/catalog-view/catalog-view.component.ts @@ -110,10 +110,14 @@ export class CatalogViewComponent implements OnInit, OnDestroy { } async getCatalog(catalogRequest: CatalogRequest) { + if (this.isFederatedCatalogEnabled) { + return await this.getFederatedCatalogs(); + } if (catalogRequest.counterPartyAddress) { return await this.getCatalogByAddress(catalogRequest); - } else if (!catalogRequest.counterPartyAddress && this.isFederatedCatalogEnabled) { - return await this.getFederatedCatalogs(); + // } else if (!catalogRequest.counterPartyAddress && this.isFederatedCatalogEnabled) { + // return await this.getFederatedCatalogs(); + // } } } diff --git a/projects/dashboard-core/catalog/src/catalog.service.spec.ts b/projects/dashboard-core/catalog/src/catalog.service.spec.ts deleted file mode 100644 index e69de29bb..000000000 diff --git a/projects/dashboard-core/catalog/src/catalog.service.ts b/projects/dashboard-core/catalog/src/catalog.service.ts index af4e768fc..938e59acf 100644 --- a/projects/dashboard-core/catalog/src/catalog.service.ts +++ b/projects/dashboard-core/catalog/src/catalog.service.ts @@ -13,7 +13,7 @@ */ import { Injectable, inject } from '@angular/core'; -import { EdcClientService } from '@eclipse-edc/dashboard-core'; +import { DashboardStateService, EdcClientService } from '@eclipse-edc/dashboard-core'; import { Catalog, ContractNegotiationState, @@ -29,6 +29,8 @@ import { ContractNegotiationRequest } from '@think-it-labs/edc-connector-client/ import { PolicyType } from '@think-it-labs/edc-connector-client/dist/src/entities/policy/policy'; import { CatalogDataset } from './catalog-dataset'; import { CatalogRequest } from '@think-it-labs/edc-connector-client/dist/src/entities/catalog'; +import { HttpClient } from '@angular/common/http'; +import { firstValueFrom } from 'rxjs'; /** * Service to manage and retrieve catalogs. @@ -37,6 +39,8 @@ import { CatalogRequest } from '@think-it-labs/edc-connector-client/dist/src/ent providedIn: 'root', }) export class CatalogService { + private readonly http = inject(HttpClient); + private readonly stateService = inject(DashboardStateService); private readonly edc = inject(EdcClientService); /** @@ -45,15 +49,27 @@ export class CatalogService { * @returns A promise that resolves to an array of catalog. */ private async getCatalog(catalogRequest: CatalogRequest): Promise { + // const catalog = await firstValueFrom( + // this.http.post(await this.managementEndpoint('/v4/catalog/request'), catalogRequest, await this.options()), + // ); + // return this.toCatalogEntity(catalog); return (await this.edc.getClient()).management.catalog.request(catalogRequest); } /** - * Retrieves all catalogs from the federated catalog API. + * Retrieves all catalogs from the federated catalog API using the EDC 0.17 management v4 endpoint. * @returns A promise that resolves to an array of catalog. */ private async getAllFederatedCatalogs(): Promise { - return (await this.edc.getClient()).federatedCatalog.queryAll(); + const catalogs = await firstValueFrom( + this.http.post( + await this.managementEndpoint('/v4/catalogs/request'), + { '@type': 'QuerySpec' }, + await this.options(), + ), + ); + + return catalogs.map(catalog => this.toCatalogEntity(catalog)); } /** @@ -72,6 +88,12 @@ export class CatalogService { * @return {Promise} A promise resolving to the current state of the contract negotiation. */ public async getNegotiationState(id: string): Promise { + // return firstValueFrom( + // this.http.get( + // await this.managementEndpoint(`/v4/contractnegotiations/${encodeURIComponent(id)}/state`), + // await this.options(), + // ), + // ); return (await this.edc.getClient()).management.contractNegotiations.getState(id); } @@ -111,6 +133,93 @@ export class CatalogService { }); } + private toCatalogEntity(rawCatalog: any): Catalog { + const rawDatasets = this.asArray( + rawCatalog['dcat:dataset'] ?? rawCatalog['http://www.w3.org/ns/dcat#dataset'], + ); + const datasets = rawDatasets.filter(Boolean).map(dataset => this.toDatasetEntity(dataset)); + + return { + ...rawCatalog, + id: rawCatalog['@id'] ?? rawCatalog.id, + datasets, + mandatoryValue: (_ns: string, key: string) => { + if (key === 'originator') { + return ( + rawCatalog['edc:originator'] ?? + rawCatalog['https://w3id.org/edc/v0.0.1/ns/originator'] ?? + rawCatalog.originator ?? + rawCatalog['@id'] + ); + } + return this.value(rawCatalog, key); + }, + optionalValue: (_ns: string, key: string) => this.value(rawCatalog, key), + } as Catalog; + } + + private toDatasetEntity(rawDataset: any): Dataset { + const offers = this.asArray( + rawDataset['odrl:hasPolicy'] ?? rawDataset['http://www.w3.org/ns/odrl/2/hasPolicy'], + ).filter(Boolean); + + return { + ...rawDataset, + id: rawDataset['@id'] ?? rawDataset.id, + offers, + properties: { + ...rawDataset, + optionalValue: (_ns: string, key: string) => this.value(rawDataset, key), + mandatoryValue: (_ns: string, key: string) => this.value(rawDataset, key), + }, + optionalValue: (_ns: string, key: string) => this.value(rawDataset, key), + mandatoryValue: (_ns: string, key: string) => this.value(rawDataset, key), + } as Dataset; + } + + private value(entity: any, key: string): any { + const raw = + entity[`edc:${key}`] ?? + entity[`asset:prop:${key}`] ?? + entity[`https://w3id.org/edc/v0.0.1/ns/${key}`] ?? + entity[key]; + + if (Array.isArray(raw) && raw.length === 1) { + return raw[0]?.['@value'] ?? raw[0]?.['@id'] ?? raw[0]; + } + + return raw?.['@value'] ?? raw?.['@id'] ?? raw; + } + + private asArray(value: T | T[] | undefined): T[] { + if (value === undefined || value === null) { + return []; + } + return Array.isArray(value) ? value : [value]; + } + + private async managementEndpoint(path: string): Promise { + const config = await firstValueFrom(this.stateService.currentEdcConfig$); + if (!config) { + throw new Error('No EDC configuration available.'); + } + return `${config.managementUrl}${path}`; + } + + private async options() { + const config = await firstValueFrom(this.stateService.currentEdcConfig$); + return { + headers: config?.apiToken ? { 'x-api-key': config.apiToken } : undefined, + }; + } + + private toIdResponse(response: any): IdResponse { + return { + ...response, + id: response.id ?? response['@id'], + } as IdResponse; + } + /** * Converts the catalog and its dataset into a CatalogDataset * @param catalog - The catalog @@ -120,6 +229,8 @@ export class CatalogService { */ private generateCatalogDataset(catalog: Catalog, dataset: Dataset, originator: string): CatalogDataset { const participantId: string = this.getCatalogParticipantId(catalog, originator); + console.log('[CatalogService] participantId:', participantId); + const offers = this.getOfferMap(participantId, dataset); return { @@ -138,8 +249,14 @@ export class CatalogService { * @param defaultValue - A default value to return in case of participantId is undefined * @returns the participant id. If undefined then returns the default value */ + // private getCatalogParticipantId(catalog: Catalog, defaultValue: string): string { + // return catalog?.['https://w3id.org/dspace/2025/1/participantId']?.[0]?.['@id'] ?? defaultValue; + // } private getCatalogParticipantId(catalog: Catalog, defaultValue: string): string { - return catalog?.['https://w3id.org/dspace/2025/1/participantId']?.[0]?.['@id'] ?? defaultValue; + return ( + catalog?.['https://w3id.org/dspace/2025/1/participantId']?.['@id'] ?? + defaultValue + ); } /** diff --git a/projects/dashboard-core/src/lib/common/connector-config-form/connector-config-form.component.ts b/projects/dashboard-core/src/lib/common/connector-config-form/connector-config-form.component.ts index f06672436..f43d8017d 100644 --- a/projects/dashboard-core/src/lib/common/connector-config-form/connector-config-form.component.ts +++ b/projects/dashboard-core/src/lib/common/connector-config-form/connector-config-form.component.ts @@ -38,6 +38,7 @@ export class ConnectorConfigFormComponent { managementUrl: new FormControl('', [Validators.required, Validators.pattern(URL_REGEX)]), defaultUrl: new FormControl('', [Validators.required, Validators.pattern(URL_REGEX)]), protocolUrl: new FormControl('', [Validators.required, Validators.pattern(URL_REGEX)]), + gateplaneUrl: new FormControl('', [Validators.required, Validators.pattern(URL_REGEX)]), apiToken: new FormControl(''), federatedCatalogEnabled: new FormControl(false), identityHubEnabled: new FormControl(false), @@ -78,6 +79,7 @@ export class ConnectorConfigFormComponent { managementUrl: this.connectorForm.value.managementUrl, defaultUrl: this.connectorForm.value.defaultUrl, protocolUrl: this.connectorForm.value.protocolUrl, + gateplaneUrl: this.connectorForm.value.gateplaneUrl, federatedCatalogEnabled: this.connectorForm.value.federatedCatalogEnabled, }; if (this.connectorForm.value.apiToken) { diff --git a/projects/dashboard-core/src/lib/common/data-address/data-address-form/data-address-form.component.html b/projects/dashboard-core/src/lib/common/data-address/data-address-form/data-address-form.component.html index 5611034b5..dc6be6987 100644 --- a/projects/dashboard-core/src/lib/common/data-address/data-address-form/data-address-form.component.html +++ b/projects/dashboard-core/src/lib/common/data-address/data-address-form/data-address-form.component.html @@ -11,28 +11,48 @@ Fraunhofer-Gesellschaft zur Förderung der angewandten Forschung e.V. - initial API and implementation --> -
- @if (!dataType) { - @if (allowedTypes$ | async) { - Data types your connector supports - - @if (dataTypeForm.get('dataType')?.invalid) { -

Mandatory

- } @else { -
- } - } @else { -
- -
- } +
+ Data types your connector supports + + + Method + + + Base URL + + @if (dataplaneMetadataForm.get('baseUrl')?.invalid) { +

Enter a valid URL

} - + + TTL + + + Basic Auth Username + + + Basic Auth Password +
diff --git a/projects/dashboard-core/src/lib/common/data-address/data-address-form/data-address-form.component.ts b/projects/dashboard-core/src/lib/common/data-address/data-address-form/data-address-form.component.ts index 5a70c24c3..13ad7d59c 100644 --- a/projects/dashboard-core/src/lib/common/data-address/data-address-form/data-address-form.component.ts +++ b/projects/dashboard-core/src/lib/common/data-address/data-address-form/data-address-form.component.ts @@ -12,91 +12,52 @@ * */ -import { - Component, - EventEmitter, - Input, - OnChanges, - OnDestroy, - OnInit, - Output, - ViewChild, - ViewContainerRef, - inject, -} from '@angular/core'; -import { AsyncPipe, NgClass } from '@angular/common'; -import { from, Observable, of, Subject, takeUntil } from 'rxjs'; +import { Component, EventEmitter, Input, OnChanges, OnDestroy, Output, inject } from '@angular/core'; +import { NgClass } from '@angular/common'; +import { Subject, takeUntil } from 'rxjs'; +import { FormBuilder, FormGroup, ReactiveFormsModule, Validators } from '@angular/forms'; import { DataAddress } from '@think-it-labs/edc-connector-client'; -import { FormBuilder, FormControl, FormGroup, ReactiveFormsModule, Validators } from '@angular/forms'; -import { DataTypeRegistryService } from '../../../services/data-type-registry.service'; +import { URL_REGEX } from '../../../models/constants'; @Component({ selector: 'lib-data-address-form', templateUrl: './data-address-form.component.html', - imports: [AsyncPipe, ReactiveFormsModule, NgClass], + imports: [ReactiveFormsModule, NgClass], }) -export class DataAddressFormComponent implements OnInit, OnChanges, OnDestroy { - private readonly dataTypeService = inject(DataTypeRegistryService); +export class DataAddressFormComponent implements OnChanges, OnDestroy { private readonly formBuilder = inject(FormBuilder); private readonly destroy$ = new Subject(); - @ViewChild('dataAddressComponent', { read: ViewContainerRef, static: true }) - private readonly dataAddressComponent!: ViewContainerRef; - @Input() showDivider = true; @Input() parentForm?: FormGroup; - @Input() dataType?: string; - @Input() dataAddress?: DataAddress; @Output() dataAddressChange = new EventEmitter(); - allowedTypes$: Observable> = of(); - private validDataAddress = false; - - private readonly FORM_GROUP_NAME = 'dataAddress'; - dataTypeForm: FormGroup; + private readonly FORM_GROUP_NAME = 'dataplaneMetadata'; + dataplaneMetadataForm: FormGroup; constructor() { - this.dataTypeForm = this.formBuilder.group({ - dataType: new FormControl(undefined, { - validators: [Validators.required, () => (this.validDataAddress ? null : { invalidDataType: true })], - }), + this.dataplaneMetadataForm = this.formBuilder.group({ + type: ['HttpData', Validators.required], + method: ['GET'], + baseUrl: ['', [Validators.required, Validators.pattern(URL_REGEX)]], + ttl: [600], + username: [''], + password: [''], }); - this.dataTypeForm - .get('dataType') - ?.valueChanges.pipe(takeUntil(this.destroy$)) - .subscribe(type => { - this.onDataTypeChange(type); - }); - } - async ngOnInit() { - if (!this.dataType) { - this.allowedTypes$ = from(this.dataTypeService.getAllowedSourceTypes()); - } + this.dataplaneMetadataForm.valueChanges.pipe(takeUntil(this.destroy$)).subscribe(value => { + const dataAddress = { + type: value.type || 'HttpData', + method: value.method || 'GET', + baseUrl: value.baseUrl, + } as DataAddress; + this.dataAddressChange.emit(dataAddress); + }); } ngOnChanges() { - if (this.dataAddress?.type != this.dataTypeForm.value.dataType) { - this.dataTypeForm.get('dataType')?.setValue(this.dataAddress?.type); - } - if (this.dataType) { - this.dataTypeForm.get('dataType')?.setValue(this.dataType); - } - this.parentForm?.addControl(this.FORM_GROUP_NAME, this.dataTypeForm); - } - - onDataTypeChange(type: string) { - this.validDataAddress = false; - this.dataAddressComponent.clear(); - const typeComponent = this.dataTypeService.getComponent(type); - const ref = this.dataAddressComponent.createComponent(typeComponent); - ref.setInput('type', type); - ref.setInput('dataAddress', this.dataAddress); - ref.setInput('parentForm', this.dataTypeForm); - const sub = ref.instance.changed.subscribe(address => this.dataAddressChange.emit(address)); - ref.onDestroy(() => sub.unsubscribe()); - this.validDataAddress = ref.instance.formGroup.valid; + this.parentForm?.setControl(this.FORM_GROUP_NAME, this.dataplaneMetadataForm); } ngOnDestroy() { diff --git a/projects/dashboard-core/src/lib/models/edc-config.ts b/projects/dashboard-core/src/lib/models/edc-config.ts index 2d64e72a7..fcf05b385 100644 --- a/projects/dashboard-core/src/lib/models/edc-config.ts +++ b/projects/dashboard-core/src/lib/models/edc-config.ts @@ -19,6 +19,7 @@ export interface EdcConfig { protocolUrl: string; apiToken?: string; controlUrl?: string; + gateplaneUrl: string; federatedCatalogEnabled: boolean; federatedCatalogUrl?: string; did?: string; diff --git a/projects/dashboard-core/src/lib/services/dashboard-state.service.ts b/projects/dashboard-core/src/lib/services/dashboard-state.service.ts index 2a0537390..d690be509 100644 --- a/projects/dashboard-core/src/lib/services/dashboard-state.service.ts +++ b/projects/dashboard-core/src/lib/services/dashboard-state.service.ts @@ -187,7 +187,7 @@ export class DashboardStateService implements OnDestroy { */ public setCurrentEdcConfig(config: EdcConfig): void { this.edc.setDashboardClient(config); - this.setFederatedCatalogEnabled(config.federatedCatalogUrl !== undefined && config.federatedCatalogEnabled); + this.setFederatedCatalogEnabled(this._isFederatedCatalogEnabled.getValue() || config.federatedCatalogEnabled); this._currentEdcConfig.next(config); localStorage.setItem(this.LOCAL_STORAGE_CURRENT_CONNECTOR, JSON.stringify(config)); } diff --git a/projects/dashboard-core/transfer/src/contract-and-transfer.service.ts b/projects/dashboard-core/transfer/src/contract-and-transfer.service.ts index 9e655dab2..4bc482f91 100644 --- a/projects/dashboard-core/transfer/src/contract-and-transfer.service.ts +++ b/projects/dashboard-core/transfer/src/contract-and-transfer.service.ts @@ -13,7 +13,8 @@ */ import { Injectable, inject } from '@angular/core'; -import { EdcClientService } from '@eclipse-edc/dashboard-core'; +import { EdcClientService, DashboardStateService } from '@eclipse-edc/dashboard-core'; + import { compact, ContractAgreement, @@ -26,7 +27,7 @@ import { TransferProcessInput, TransferProcessState, } from '@think-it-labs/edc-connector-client'; -import { Observable } from 'rxjs'; +import { Observable, firstValueFrom } from 'rxjs'; import { HttpClient, HttpEvent } from '@angular/common/http'; /** @@ -39,6 +40,7 @@ import { HttpClient, HttpEvent } from '@angular/common/http'; export class ContractAndTransferService { private readonly edc = inject(EdcClientService); private readonly http = inject(HttpClient); + private readonly stateService = inject(DashboardStateService); /** * Retrieves all contract negotiations based on the optional query specification. @@ -142,27 +144,44 @@ export class ContractAndTransferService { } } + // /** + // * Downloads a dataset based on the provided transfer ID. + // * + // * @param {string} transferId - The unique identifier for the dataset transfer. + // * @return {Promise>>} - A promise resolving to an Observable that emits HttpEvent objects + // * for the Blob being downloaded, with progress tracking capabilities. + // * @throws {Error} - Throws an error if no EDR (Endpoint Data Reference) is found for the provided transfer ID. + // */ + // public async downloadDataset(transferId: string): Promise>> { + // const edr = await (await this.edc.getClient()).management.edrs.dataAddress(transferId); + // if (!edr) { + // throw new Error('No EDR found for transfer ID ' + transferId); + // } + // return this.http.get(edr.mandatoryValue('edc', 'endpoint'), { + // headers: { + // Authorization: edr.mandatoryValue('edc', 'authorization'), + // }, + // responseType: 'blob', + // reportProgress: true, + // observe: 'events', + // }); + // } + /** - * Downloads a dataset based on the provided transfer ID. - * - * @param {string} transferId - The unique identifier for the dataset transfer. - * @return {Promise>>} - A promise resolving to an Observable that emits HttpEvent objects - * for the Blob being downloaded, with progress tracking capabilities. - * @throws {Error} - Throws an error if no EDR (Endpoint Data Reference) is found for the provided transfer ID. - */ + * Downloads a dataset through Gateplane using the transfer ID. + */ public async downloadDataset(transferId: string): Promise>> { - const edr = await (await this.edc.getClient()).management.edrs.dataAddress(transferId); - if (!edr) { - throw new Error('No EDR found for transfer ID ' + transferId); + const config = await firstValueFrom(this.stateService.currentEdcConfig$); + if (!config) { + throw new Error('No EDC configuration available.'); } - return this.http.get(edr.mandatoryValue('edc', 'endpoint'), { - headers: { - Authorization: edr.mandatoryValue('edc', 'authorization'), - }, - responseType: 'blob', - reportProgress: true, - observe: 'events', - }); + const gateplaneUrl = `${config.gateplaneUrl}/${transferId}`; + + return this.http.get(gateplaneUrl, { + responseType: 'blob', + reportProgress: true, + observe: 'events', + }); } /** diff --git a/projects/dashboard-core/transfer/src/transfer-create/transfer-create.component.html b/projects/dashboard-core/transfer/src/transfer-create/transfer-create.component.html index a395bcf4d..c2e2e5181 100644 --- a/projects/dashboard-core/transfer/src/transfer-create/transfer-create.component.html +++ b/projects/dashboard-core/transfer/src/transfer-create/transfer-create.component.html @@ -57,7 +57,6 @@

Transfer

Destination Data Address
diff --git a/public/config/edc-connector-config.json b/public/config/edc-connector-config.json index 79cc4ca9a..3c8f991f6 100644 --- a/public/config/edc-connector-config.json +++ b/public/config/edc-connector-config.json @@ -1,20 +1,24 @@ [ { - "connectorName": "Consumer", - "managementUrl": "http://cp-consumer.dataspace/management", - "defaultUrl": "http://cp-consumer.dataspace/api", - "protocolUrl": "http://cp-consumer.dataspace/protocol/2025-1", - "federatedCatalogEnabled": false, - "federatedCatalogUrl": "http://fc-consumer.dataspace/catalog", - "did": "did:web:ih-consumer.dataspace" + "connectorName": "Provider", + "managementUrl": "http://localhost:3109/management", + "protocolUrl": "http://localhost:3111/dsp/2025-1", + "controlUrl": "http://localhost:3109/control", + "defaultUrl": "http://localhost:3109", + "gateplaneUrl": "http://localhost:3198/data/consume", + "apiToken": "password", + "federatedCatalogEnabled": true, + "did": "did:web:localhost%3A3102:provider" }, { - "connectorName": "Provider", - "managementUrl": "http://cp-provider.dataspace/management", - "defaultUrl": "http://cp-provider.dataspace/api", - "protocolUrl": "http://cp-provider.dataspace/protocol/2025-1", - "federatedCatalogEnabled": false, - "federatedCatalogUrl": "http://fc-provider.dataspace/catalog", - "did": "did:web:ih-provider.dataspace" + "connectorName": "Consumer", + "managementUrl": "http://localhost:3009/management", + "protocolUrl": "http://localhost:3011/dsp/2025-1", + "controlUrl": "http://localhost:3009/control", + "defaultUrl": "http://localhost:3009", + "gateplaneUrl": "http://localhost:3098/data/consume", + "apiToken": "password", + "federatedCatalogEnabled": true, + "did": "did:web:localhost%3A3002:consumer" } ]