From cefde2e2ef004f7ec53850993f619d42da88e5b9 Mon Sep 17 00:00:00 2001 From: Richard Treier Date: Wed, 31 May 2023 15:13:18 +0200 Subject: [PATCH] broker connector page (#295) * chore: initial broker server model * feat: broker connector page --- fake-backend/json/brokerCatalog.json | 163 ------------------ fake-backend/json/brokerCatalogPage.json | 79 +++++++++ fake-backend/json/brokerConnectorPage.json | 30 ++++ fake-backend/serve.js | 8 +- package-lock.json | 8 +- package.json | 2 +- .../asset-detail-dialog-data.service.ts | 108 ++++++++++++ .../asset-detail-dialog-data.ts | 85 ++------- .../asset-detail-dialog.component.html | 20 +-- .../asset-detail-dialog.component.ts | 11 +- .../asset-property-grid-group-builder.ts | 120 ++++++------- .../catalog/catalog.module.ts | 3 + .../ui-elements/ago/ago.pipe.ts | 12 +- .../ui-elements/ago/formatDateAgo.ts | 13 ++ .../broker-ui/broker-ui-routing.module.ts | 15 +- .../routes/broker-ui/broker-ui.component.html | 27 +-- src/app/routes/broker-ui/broker-ui.module.ts | 4 +- .../catalog-page.module.ts | 6 +- .../catalog-page/catalog-page-data.service.ts | 2 +- .../catalog-page/catalog-page-data.ts | 0 .../catalog-page/catalog-page.component.html} | 9 +- .../catalog-page/catalog-page.component.scss} | 0 .../catalog-page/catalog-page.component.ts} | 19 +- .../mapping/broker-catalog-mapper.ts | 6 +- .../mapping/broker-catalog-page-result.ts | 0 .../catalog-page/mapping/broker-data-offer.ts | 6 +- .../data-offer-cards.component.html | 0 .../data-offer-cards.component.ts | 0 .../connector-cards.component.html | 52 ++++++ .../connector-cards.component.ts | 16 ++ .../connector-page/connector-page.module.ts | 81 +++++++++ .../connector-page-data.service.ts | 33 ++++ .../connector-page/connector-page-data.ts | 12 ++ .../connector-page.component.html | 45 +++++ .../connector-page.component.scss | 13 ++ .../connector-page.component.ts | 50 ++++++ .../asset-page/asset-page.component.ts | 5 +- .../catalog-browser-page.component.ts | 6 +- .../connector-ui/connector-ui.module.ts | 8 +- .../contract-agreement-page.component.ts | 8 +- .../asset-select/asset-select.component.ts | 9 +- .../contract-definition-cards.component.ts | 5 +- 42 files changed, 711 insertions(+), 388 deletions(-) delete mode 100644 fake-backend/json/brokerCatalog.json create mode 100644 fake-backend/json/brokerCatalogPage.json create mode 100644 fake-backend/json/brokerConnectorPage.json create mode 100644 src/app/component-library/catalog/asset-detail-dialog/asset-detail-dialog-data.service.ts create mode 100644 src/app/component-library/ui-elements/ago/formatDateAgo.ts rename src/app/routes/broker-ui/{catalog-browser-page => catalog-page}/catalog-page.module.ts (93%) rename src/app/routes/broker-ui/{catalog-browser-page => catalog-page}/catalog-page/catalog-page-data.service.ts (98%) rename src/app/routes/broker-ui/{catalog-browser-page => catalog-page}/catalog-page/catalog-page-data.ts (100%) rename src/app/routes/broker-ui/{catalog-browser-page/catalog-page/catalog-browser-page.component.html => catalog-page/catalog-page/catalog-page.component.html} (80%) rename src/app/routes/broker-ui/{catalog-browser-page/catalog-page/catalog-browser-page.component.scss => catalog-page/catalog-page/catalog-page.component.scss} (100%) rename src/app/routes/broker-ui/{catalog-browser-page/catalog-page/catalog-browser-page.component.ts => catalog-page/catalog-page/catalog-page.component.ts} (76%) rename src/app/routes/broker-ui/{catalog-browser-page => catalog-page}/catalog-page/mapping/broker-catalog-mapper.ts (80%) rename src/app/routes/broker-ui/{catalog-browser-page => catalog-page}/catalog-page/mapping/broker-catalog-page-result.ts (100%) rename src/app/routes/broker-ui/{catalog-browser-page => catalog-page}/catalog-page/mapping/broker-data-offer.ts (51%) rename src/app/routes/broker-ui/{catalog-browser-page => catalog-page}/data-offer-cards/data-offer-cards.component.html (100%) rename src/app/routes/broker-ui/{catalog-browser-page => catalog-page}/data-offer-cards/data-offer-cards.component.ts (100%) create mode 100644 src/app/routes/broker-ui/connector-page/connector-cards/connector-cards.component.html create mode 100644 src/app/routes/broker-ui/connector-page/connector-cards/connector-cards.component.ts create mode 100644 src/app/routes/broker-ui/connector-page/connector-page.module.ts create mode 100644 src/app/routes/broker-ui/connector-page/connector-page/connector-page-data.service.ts create mode 100644 src/app/routes/broker-ui/connector-page/connector-page/connector-page-data.ts create mode 100644 src/app/routes/broker-ui/connector-page/connector-page/connector-page.component.html create mode 100644 src/app/routes/broker-ui/connector-page/connector-page/connector-page.component.scss create mode 100644 src/app/routes/broker-ui/connector-page/connector-page/connector-page.component.ts diff --git a/fake-backend/json/brokerCatalog.json b/fake-backend/json/brokerCatalog.json deleted file mode 100644 index e0e2d6b1b..000000000 --- a/fake-backend/json/brokerCatalog.json +++ /dev/null @@ -1,163 +0,0 @@ -{ - "availableFilters": { - "fields": [] - }, - "availableSortings": [ - { - "sorting": "MATCH", - "title": "by match" - }, - { - "sorting": "TITLE", - "title": "by title" - }, - { - "sorting": "MOST_RECENT", - "title": "most recent" - }, - { - "sorting": "ORIGINATOR", - "title": "by connector" - } - ], - "paginationMetadata": { - "numTotal": 1, - "numVisible": 1, - "pageOneBased": 1, - "pageSize": 1 - }, - "dataOffers": [ - { - "connectorInfo": { - "endpoint": "http://my-connector/ids/data", - "title": "Example Connector", - "description": "My example Connector...", - "onlineStatus": "ONLINE", - "offlineSinceOrLastUpdatedAt": "2023-05-25T16:23:34+02:00" - }, - "asset": { - "assetId": "urn:artifact:db-rail-network-2023-jan", - "createdAt": "2023-05-20T16:23:34+02:00", - "properties": { - "asset:prop:id": "urn:artifact:db-rail-network-2023-jan", - "asset:prop:name": "Rail Network DB 2023 January", - "asset:prop:version": "1.1", - "asset:prop:originator": "https://example-connector.rail-mgmt.bahn.de/api/v1/ids/data", - "asset:prop:originatorOrganization": "Deutsche Bahn AG", - "asset:prop:keywords": "db, bahn, rail, Rail-Designer", - "asset:prop:contenttype": "application/json", - "asset:prop:description": "Train Network Map released on 10.01.2023, valid until 31.02.2023. \nFile format is xyz as exported by Rail-Designer.", - "asset:prop:language": "https://w3id.org/idsa/code/EN", - "asset:prop:publisher": "https://my.cool-api.gg/about", - "asset:prop:standardLicense": "https://my.cool-api.gg/license", - "asset:prop:endpointDocumentation": "https://my.cool-api.gg/docs", - "http://w3id.org/mds#dataCategory": "Infrastructure and Logistics", - "http://w3id.org/mds#dataSubcategory": "General Information About Planning Of Routes", - "http://w3id.org/mds#dataModel": "my-data-model-001", - "http://w3id.org/mds#geoReferenceMethod": "my-geo-reference-method", - "http://w3id.org/mds#transportMode": "Rail" - } - }, - "policy": [ - { - "legacyPolicy": { - "permissions": [ - { - "edctype": "dataspaceconnector:permission", - "uid": null, - "target": "urn:artifact:bitcoin", - "action": { - "type": "USE", - "includedIn": null, - "constraint": null - }, - "assignee": null, - "assigner": null, - "constraints": [ - { - "edctype": "AtomicConstraint", - "leftExpression": { - "edctype": "dataspaceconnector:literalexpression", - "value": "POLICY_EVALUATION_TIME" - }, - "rightExpression": { - "edctype": "dataspaceconnector:literalexpression", - "value": "2023-01-01T23:00:00.000Z" - }, - "operator": "GT" - }, - { - "edctype": "AtomicConstraint", - "leftExpression": { - "edctype": "dataspaceconnector:literalexpression", - "value": "POLICY_EVALUATION_TIME" - }, - "rightExpression": { - "edctype": "dataspaceconnector:literalexpression", - "value": "2023-01-04T23:00:00.000Z" - }, - "operator": "LT" - } - ], - "duties": [] - } - ], - "prohibitions": [], - "obligations": [], - "extensibleProperties": {}, - "inheritsFrom": null, - "assigner": null, - "assignee": null, - "target": "urn:artifact:bitcoin", - "@type": { - "@policytype": "set" - } - } - }, - { - "legacyPolicy": { - "permissions": [ - { - "edctype": "dataspaceconnector:permission", - "uid": null, - "target": "urn:artifact:bitcoin", - "action": { - "type": "USE", - "includedIn": null, - "constraint": null - }, - "assignee": null, - "assigner": null, - "constraints": [ - { - "edctype": "AtomicConstraint", - "leftExpression": { - "edctype": "dataspaceconnector:literalexpression", - "value": "ALWAYS_TRUE" - }, - "rightExpression": { - "edctype": "dataspaceconnector:literalexpression", - "value": "true" - }, - "operator": "EQ" - } - ], - "duties": [] - } - ], - "prohibitions": [], - "obligations": [], - "extensibleProperties": {}, - "inheritsFrom": null, - "assigner": null, - "assignee": null, - "target": "urn:artifact:bitcoin", - "@type": { - "@policytype": "set" - } - } - } - ] - } - ] -} diff --git a/fake-backend/json/brokerCatalogPage.json b/fake-backend/json/brokerCatalogPage.json new file mode 100644 index 000000000..3195a6dfb --- /dev/null +++ b/fake-backend/json/brokerCatalogPage.json @@ -0,0 +1,79 @@ +{ + "availableFilters": {"fields": []}, + "availableSortings": [ + {"sorting": "MOST_RECENT", "title": "Most Recent"}, + {"sorting": "TITLE", "title": "By Title"}, + {"sorting": "ORIGINATOR", "title": "By Connector"} + ], + "paginationMetadata": { + "numTotal": 1, + "numVisible": 1, + "pageOneBased": 1, + "pageSize": 1 + }, + "dataOffers": [ + { + "assetId": "urn:artifact:my-asset", + "connectorEndpoint": "http://my-connector/ids/data", + "connectorOnlineStatus": "ONLINE", + "connectorOfflineSinceOrLastUpdatedAt": "2023-05-31T12:02:41+02:00", + "createdAt": "2023-05-26T12:02:41+02:00", + "updatedAt": "2023-05-31T12:02:41+02:00", + "properties": { + "asset:prop:id": "urn:artifact:db-rail-network-2023-jan", + "asset:prop:name": "Rail Network DB 2023 January", + "asset:prop:version": "1.1", + "asset:prop:originator": "https://example-connector.rail-mgmt.bahn.de/api/v1/ids/data", + "asset:prop:originatorOrganization": "Deutsche Bahn AG", + "asset:prop:keywords": "db, bahn, rail, Rail-Designer", + "asset:prop:contenttype": "application/json", + "asset:prop:description": "Train Network Map released on 10.01.2023, valid until 31.02.2023. \nFile format is xyz as exported by Rail-Designer.", + "asset:prop:language": "https://w3id.org/idsa/code/EN", + "asset:prop:publisher": "https://my.cool-api.gg/about", + "asset:prop:standardLicense": "https://my.cool-api.gg/license", + "asset:prop:endpointDocumentation": "https://my.cool-api.gg/docs", + "http://w3id.org/mds#dataCategory": "Infrastructure and Logistics", + "http://w3id.org/mds#dataSubcategory": "General Information About Planning Of Routes", + "http://w3id.org/mds#dataModel": "my-data-model-001", + "http://w3id.org/mds#geoReferenceMethod": "my-geo-reference-method", + "http://w3id.org/mds#transportMode": "Rail" + }, + "contractOffers": [ + { + "contractOfferId": "my-contract-offer-1", + "createdAt": "2023-05-26T12:02:41+02:00", + "updatedAt": "2023-05-31T12:02:41+02:00", + "contractPolicy": { + "legacyPolicy": { + "permissions": [ + { + "edctype": "dataspaceconnector:permission", + "action": { + "type": "USE" + }, + "constraints": [ + { + "edctype": "AtomicConstraint", + "leftExpression": { + "edctype": "dataspaceconnector:literalexpression", + "value": "ALWAYS_TRUE" + }, + "rightExpression": { + "edctype": "dataspaceconnector:literalexpression", + "value": "true" + }, + "operator": "EQ" + } + ] + } + ], + "@type": { + "@policytype": "set" + } + } + } + } + ] + } + ] +} diff --git a/fake-backend/json/brokerConnectorPage.json b/fake-backend/json/brokerConnectorPage.json new file mode 100644 index 000000000..51b7a5298 --- /dev/null +++ b/fake-backend/json/brokerConnectorPage.json @@ -0,0 +1,30 @@ +{ + "availableSortings": [ + {"sorting": "MOST_RECENT", "title": "Most Recent"}, + {"sorting": "TITLE", "title": "By Title"} + ], + "paginationMetadata": { + "numTotal": 2, + "numVisible": 2, + "pageOneBased": 2, + "pageSize": 1 + }, + "connectors": [ + { + "id": "https://example.com", + "endpoint": "https://example.com/ids/data", + "createdAt": "2023-05-31T14:06:21.536182+02:00", + "onlineStatus": "OFFLINE", + "numContractOffers": 0 + }, + { + "id": "https://other-connector.com", + "endpoint": "https://other-connector.com/ids/data", + "createdAt": "2023-05-31T14:06:21.536182+02:00", + "lastSuccessfulRefreshAt": "2023-05-31T14:06:21.536182+02:00", + "lastRefreshAttemptAt": "2023-05-31T14:06:21.536182+02:00", + "onlineStatus": "ONLINE", + "numContractOffers": 2 + } + ] +} diff --git a/fake-backend/serve.js b/fake-backend/serve.js index e0eeaa288..737a53dd9 100644 --- a/fake-backend/serve.js +++ b/fake-backend/serve.js @@ -102,9 +102,13 @@ app.get('/api/v1/data/wrapper/ui/pages/contract-agreement-page', (_, res) => { }); // Broker API Wrapper -const brokerCatalog = json('json/brokerCatalog.json'); +const brokerCatalogPage = json('json/brokerCatalogPage.json'); app.post('/api/v1/data/wrapper/broker/catalog-page', (_, res) => { - res.json(brokerCatalog); + res.json(brokerCatalogPage); +}); +const brokerConnectorPage = json('json/brokerConnectorPage.json'); +app.post('/api/v1/data/wrapper/broker/connector-page', (_, res) => { + res.json(brokerConnectorPage); }); app.listen(3000, function () { diff --git a/package-lock.json b/package-lock.json index 003933ef9..e2af1e486 100644 --- a/package-lock.json +++ b/package-lock.json @@ -21,7 +21,7 @@ "@angular/platform-browser-dynamic": "^14.3.0", "@angular/router": "^14.3.0", "@ng-apimock/core": "^3.6.0", - "@sovity.de/edc-client": "0.20230517.123454-main-2f926aa6", + "@sovity.de/edc-client": "0.20230531.73811-main-e140ef56", "clean-deep": "^3.4.0", "date-fns": "^2.29.3", "dotenv": "^16.0.3", @@ -3420,9 +3420,9 @@ "dev": true }, "node_modules/@sovity.de/edc-client": { - "version": "0.20230517.123454-main-2f926aa6", - "resolved": "https://registry.npmjs.org/@sovity.de/edc-client/-/edc-client-0.20230517.123454-main-2f926aa6.tgz", - "integrity": "sha512-XC0drJPKZYRCWoUrwPsRBPZ8TwE5gRKpgA6lOSKoCmdPpTWv1iSLbWBc6apPq7wglG3/FC5DZ/eJPaQVPNv6SA==" + "version": "0.20230531.73811-main-e140ef56", + "resolved": "https://registry.npmjs.org/@sovity.de/edc-client/-/edc-client-0.20230531.73811-main-e140ef56.tgz", + "integrity": "sha512-VzO0sNV5rrEMbXk4OCeC/QXCadkqlkSLzOnXy7O4NVsFoU8RziICGVtX9yyUeqrWdad7dB2R3nt16kgbQ9kXaQ==" }, "node_modules/@tootallnate/once": { "version": "2.0.0", diff --git a/package.json b/package.json index 7f0dfd7f7..9d57310df 100644 --- a/package.json +++ b/package.json @@ -26,7 +26,7 @@ "@angular/platform-browser-dynamic": "^14.3.0", "@angular/router": "^14.3.0", "@ng-apimock/core": "^3.6.0", - "@sovity.de/edc-client": "0.20230517.123454-main-2f926aa6", + "@sovity.de/edc-client": "0.20230531.73811-main-e140ef56", "clean-deep": "^3.4.0", "date-fns": "^2.29.3", "dotenv": "^16.0.3", diff --git a/src/app/component-library/catalog/asset-detail-dialog/asset-detail-dialog-data.service.ts b/src/app/component-library/catalog/asset-detail-dialog/asset-detail-dialog-data.service.ts new file mode 100644 index 000000000..26386f382 --- /dev/null +++ b/src/app/component-library/catalog/asset-detail-dialog/asset-detail-dialog-data.service.ts @@ -0,0 +1,108 @@ +import {Injectable} from '@angular/core'; +import {Policy} from '../../../core/services/api/legacy-managent-api-client'; +import {Asset} from '../../../core/services/models/asset'; +import {ContractOffer} from '../../../core/services/models/contract-offer'; +import {BrokerDataOffer} from '../../../routes/broker-ui/catalog-page/catalog-page/mapping/broker-data-offer'; +import {ContractAgreementCardMapped} from '../../../routes/connector-ui/contract-agreement-page/contract-agreement-cards/contract-agreement-card-mapped'; +import {AssetDetailDialogData} from './asset-detail-dialog-data'; +import {AssetPropertyGridGroupBuilder} from './asset-property-grid-group-builder'; + +@Injectable() +export class AssetDetailDialogDataService { + constructor( + private assetPropertyGridGroupBuilder: AssetPropertyGridGroupBuilder, + ) {} + + assetDetails(asset: Asset, allowDelete: boolean): AssetDetailDialogData { + const propertyGridGroups = [ + this.assetPropertyGridGroupBuilder.buildAssetPropertiesGroup(asset, null), + this.assetPropertyGridGroupBuilder.buildAdditionalPropertiesGroup(asset), + ].filter((it) => it.properties.length); + + return { + type: 'asset-details', + asset, + showDeleteButton: allowDelete, + propertyGridGroups, + }; + } + + contractOfferDetails(contractOffer: ContractOffer): AssetDetailDialogData { + let asset = contractOffer.asset; + let contractPolicy = contractOffer.policy; + + const propertyGridGroups = [ + this.assetPropertyGridGroupBuilder.buildAssetPropertiesGroup(asset, null), + this.assetPropertyGridGroupBuilder.buildAdditionalPropertiesGroup(asset), + this.assetPropertyGridGroupBuilder.buildPolicyGroup( + asset, + contractPolicy, + ), + ].filter((it) => it.properties.length); + + return { + type: 'contract-offer', + asset: contractOffer.asset, + contractOffer, + propertyGridGroups, + }; + } + + contractAgreementDetails( + contractAgreement: ContractAgreementCardMapped, + ): AssetDetailDialogData { + let asset = contractAgreement.asset; + let contractPolicy = contractAgreement.contractPolicy + .legacyPolicy as Policy; + + const propertyGridGroups = [ + this.assetPropertyGridGroupBuilder.buildContractAgreementGroup( + contractAgreement, + ), + this.assetPropertyGridGroupBuilder.buildPolicyGroup( + asset, + contractPolicy, + ), + this.assetPropertyGridGroupBuilder.buildAssetPropertiesGroup( + asset, + 'Asset', + ), + this.assetPropertyGridGroupBuilder.buildAdditionalPropertiesGroup(asset), + ].filter((it) => it.properties.length); + + return { + type: 'contract-agreement', + asset: contractAgreement.asset, + contractAgreement, + propertyGridGroups, + }; + } + + brokerDataOfferDetails(dataOffer: BrokerDataOffer): AssetDetailDialogData { + let asset = dataOffer.asset; + + const propertyGridGroups = [ + this.assetPropertyGridGroupBuilder.buildBrokerDataOfferGroup(dataOffer), + this.assetPropertyGridGroupBuilder.buildAssetPropertiesGroup( + asset, + 'Asset', + ), + this.assetPropertyGridGroupBuilder.buildAdditionalPropertiesGroup(asset), + ...dataOffer.contractOffers.map((contractOffer, i) => + this.assetPropertyGridGroupBuilder.buildContractOfferGroup( + asset, + contractOffer, + i, + dataOffer.contractOffers.length, + ), + ), + ].filter((it) => it.properties.length); + + return { + type: 'broker-data-offer', + asset: dataOffer.asset, + brokerDataOffer: dataOffer, + propertyGridGroups, + }; + } +} diff --git a/src/app/component-library/catalog/asset-detail-dialog/asset-detail-dialog-data.ts b/src/app/component-library/catalog/asset-detail-dialog/asset-detail-dialog-data.ts index 39f48c650..c896e769f 100644 --- a/src/app/component-library/catalog/asset-detail-dialog/asset-detail-dialog-data.ts +++ b/src/app/component-library/catalog/asset-detail-dialog/asset-detail-dialog-data.ts @@ -1,74 +1,19 @@ -import {Policy} from '../../../core/services/api/legacy-managent-api-client'; -import {Asset} from '../../../core/services/models/asset'; +import {Asset} from 'src/app/core/services/models/asset'; import {ContractOffer} from '../../../core/services/models/contract-offer'; -import {BrokerDataOffer} from '../../../routes/broker-ui/catalog-browser-page/catalog-page/mapping/broker-data-offer'; +import {BrokerDataOffer} from '../../../routes/broker-ui/catalog-page/catalog-page/mapping/broker-data-offer'; import {ContractAgreementCardMapped} from '../../../routes/connector-ui/contract-agreement-page/contract-agreement-cards/contract-agreement-card-mapped'; +import {PropertyGridGroup} from '../../property-grid/property-grid-group/property-grid-group'; -export class AssetDetailDialogData { - constructor( - public mode: - | 'asset-details' - | 'contract-offer' - | 'contract-agreement' - | 'broker-data-offer', - public asset: Asset, - public contractOffer: ContractOffer | null, - public contractAgreement: ContractAgreementCardMapped | null, - public brokerDataOffer: BrokerDataOffer | null, - public policy: Policy | null, - public allowDelete: boolean, - ) {} - - static forAssetDetails( - asset: Asset, - allowDelete: boolean, - ): AssetDetailDialogData { - return new AssetDetailDialogData( - 'asset-details', - asset, - null, - null, - null, - null, - allowDelete, - ); - } - - static forContractOffer(contractOffer: ContractOffer): AssetDetailDialogData { - return new AssetDetailDialogData( - 'contract-offer', - contractOffer.asset, - contractOffer, - null, - null, - contractOffer.policy, - false, - ); - } - - static forContractAgreement( - contractAgreement: ContractAgreementCardMapped, - ): AssetDetailDialogData { - return new AssetDetailDialogData( - 'contract-agreement', - contractAgreement.asset, - null, - contractAgreement, - null, - contractAgreement.contractPolicy.legacyPolicy, - false, - ); - } - - static forBrokerDataOffer(dataOffer: BrokerDataOffer): AssetDetailDialogData { - return new AssetDetailDialogData( - 'broker-data-offer', - dataOffer.asset, - null, - null, - dataOffer, - dataOffer.policy[0].legacyPolicy as Policy, - false, - ); - } +export interface AssetDetailDialogData { + type: + | 'asset-details' + | 'contract-offer' + | 'contract-agreement' + | 'broker-data-offer'; + propertyGridGroups: PropertyGridGroup[]; + asset: Asset; + contractOffer?: ContractOffer; + contractAgreement?: ContractAgreementCardMapped; + brokerDataOffer?: BrokerDataOffer; + showDeleteButton?: boolean; } diff --git a/src/app/component-library/catalog/asset-detail-dialog/asset-detail-dialog.component.html b/src/app/component-library/catalog/asset-detail-dialog/asset-detail-dialog.component.html index ec23e2acf..7ecd571d7 100644 --- a/src/app/component-library/catalog/asset-detail-dialog/asset-detail-dialog.component.html +++ b/src/app/component-library/catalog/asset-detail-dialog/asset-detail-dialog.component.html @@ -1,17 +1,17 @@
- + upload - + sim_card - + {{ data.contractAgreement!!.direction === 'PROVIDING' ? 'upload' : 'download' }}
@@ -27,9 +27,9 @@ - +
@@ -70,7 +70,7 @@
- @@ -38,7 +31,7 @@ !data.fetchedDataOffers.data.dataOffers.length " class="min-h-[35rem]" - emptyMessage="No contract offers found with this filter"> + emptyMessage="No data offers found.">
+ + link + + {{ connector.endpoint }} + + EDC Connector + + +
+ +
+
Created
+
+ +
+
+ + +
+
Status
+
+ {{ connector.onlineStatus === 'ONLINE' ? 'Online' : 'Offline' }} +
+
+
+
+ +
+
+ Last Refresh +
+
+ + + Never + +
+
+ + +
+
+ Data Offers +
+
+ {{ connector.numContractOffers | number }} +
+
+
+
+ diff --git a/src/app/routes/broker-ui/connector-page/connector-cards/connector-cards.component.ts b/src/app/routes/broker-ui/connector-page/connector-cards/connector-cards.component.ts new file mode 100644 index 000000000..ee3064f08 --- /dev/null +++ b/src/app/routes/broker-ui/connector-page/connector-cards/connector-cards.component.ts @@ -0,0 +1,16 @@ +import {Component, HostBinding, Input} from '@angular/core'; +import {ConnectorListEntry} from '@sovity.de/edc-client'; + +@Component({ + selector: 'connector-cards', + templateUrl: './connector-cards.component.html', +}) +export class ConnectorCardsComponent { + @HostBinding('class.flex') + @HostBinding('class.flex-wrap') + @HostBinding('class.gap-[10px]') + cls = true; + + @Input() + connectors: ConnectorListEntry[] = []; +} diff --git a/src/app/routes/broker-ui/connector-page/connector-page.module.ts b/src/app/routes/broker-ui/connector-page/connector-page.module.ts new file mode 100644 index 000000000..eff661f16 --- /dev/null +++ b/src/app/routes/broker-ui/connector-page/connector-page.module.ts @@ -0,0 +1,81 @@ +import {ClipboardModule} from '@angular/cdk/clipboard'; +import {CommonModule} from '@angular/common'; +import {HttpClientModule} from '@angular/common/http'; +import {NgModule} from '@angular/core'; +import {FormsModule, ReactiveFormsModule} from '@angular/forms'; +import {MatBadgeModule} from '@angular/material/badge'; +import {MatButtonModule} from '@angular/material/button'; +import {MatCardModule} from '@angular/material/card'; +import {MatCheckboxModule} from '@angular/material/checkbox'; +import {MatChipsModule} from '@angular/material/chips'; +import {MatDatepickerModule} from '@angular/material/datepicker'; +import {MatDialogModule} from '@angular/material/dialog'; +import {MatDividerModule} from '@angular/material/divider'; +import {MatExpansionModule} from '@angular/material/expansion'; +import {MatFormFieldModule} from '@angular/material/form-field'; +import {MatGridListModule} from '@angular/material/grid-list'; +import {MatIconModule} from '@angular/material/icon'; +import {MatInputModule} from '@angular/material/input'; +import {MatListModule} from '@angular/material/list'; +import {MatPaginatorModule} from '@angular/material/paginator'; +import {MatProgressBarModule} from '@angular/material/progress-bar'; +import {MatProgressSpinnerModule} from '@angular/material/progress-spinner'; +import {MatSelectModule} from '@angular/material/select'; +import {MatSlideToggleModule} from '@angular/material/slide-toggle'; +import {MatStepperModule} from '@angular/material/stepper'; +import {MatTableModule} from '@angular/material/table'; +import {MatTabsModule} from '@angular/material/tabs'; +import {MatTooltipModule} from '@angular/material/tooltip'; +import {RouterModule} from '@angular/router'; +import {CatalogModule} from '../../../component-library/catalog/catalog.module'; +import {PipesAndDirectivesModule} from '../../../component-library/pipes-and-directives/pipes-and-directives.module'; +import {UiElementsModule} from '../../../component-library/ui-elements/ui-elements.module'; +import {ConnectorCardsComponent} from './connector-cards/connector-cards.component'; +import {ConnectorPageComponent} from './connector-page/connector-page.component'; + +@NgModule({ + imports: [ + // Angular + CommonModule, + HttpClientModule, + FormsModule, + ReactiveFormsModule, + RouterModule, + + // Angular CDK + ClipboardModule, + + // Angular Material + MatBadgeModule, + MatButtonModule, + MatCardModule, + MatCheckboxModule, + MatChipsModule, + MatDatepickerModule, + MatDialogModule, + MatDividerModule, + MatExpansionModule, + MatFormFieldModule, + MatGridListModule, + MatIconModule, + MatInputModule, + MatListModule, + MatPaginatorModule, + MatProgressBarModule, + MatProgressSpinnerModule, + MatSelectModule, + MatSlideToggleModule, + MatStepperModule, + MatTableModule, + MatTabsModule, + MatTooltipModule, + + // Feature Modules + CatalogModule, + PipesAndDirectivesModule, + UiElementsModule, + ], + declarations: [ConnectorPageComponent, ConnectorCardsComponent], + exports: [ConnectorPageComponent], +}) +export class ConnectorPageModule {} diff --git a/src/app/routes/broker-ui/connector-page/connector-page/connector-page-data.service.ts b/src/app/routes/broker-ui/connector-page/connector-page/connector-page-data.service.ts new file mode 100644 index 000000000..b5091ce8b --- /dev/null +++ b/src/app/routes/broker-ui/connector-page/connector-page/connector-page-data.service.ts @@ -0,0 +1,33 @@ +import {Injectable} from '@angular/core'; +import {Observable, combineLatest} from 'rxjs'; +import {map, switchMap} from 'rxjs/operators'; +import {ConnectorPageQuery, ConnectorPageResult} from '@sovity.de/edc-client'; +import {EdcApiService} from '../../../../core/services/api/edc-api.service'; +import {Fetched} from '../../../../core/services/models/fetched'; +import {ConnectorPageData} from './connector-page-data'; + +@Injectable({providedIn: 'root'}) +export class ConnectorPageDataService { + constructor(private edcApiService: EdcApiService) {} + + connectorPageData$( + refresh$: Observable, + searchText$: Observable, + ): Observable { + return combineLatest([refresh$, searchText$]).pipe( + switchMap(([_, searchText]) => this.fetchConnectors(searchText)), + map((fetchedConnectors): ConnectorPageData => ({fetchedConnectors})), + ); + } + + private fetchConnectors( + searchQuery: string, + ): Observable> { + const query: ConnectorPageQuery = { + searchQuery, + }; + return this.edcApiService + .brokerConnectors(query) + .pipe(Fetched.wrap({failureMessage: 'Failed fetching connectors.'})); + } +} diff --git a/src/app/routes/broker-ui/connector-page/connector-page/connector-page-data.ts b/src/app/routes/broker-ui/connector-page/connector-page/connector-page-data.ts new file mode 100644 index 000000000..e750d3aa9 --- /dev/null +++ b/src/app/routes/broker-ui/connector-page/connector-page/connector-page-data.ts @@ -0,0 +1,12 @@ +import {ConnectorPageResult} from '@sovity.de/edc-client'; +import {Fetched} from '../../../../core/services/models/fetched'; + +export interface ConnectorPageData { + fetchedConnectors: Fetched; +} + +export function emptyConnectorPageStateModel(): ConnectorPageData { + return { + fetchedConnectors: Fetched.empty(), + }; +} diff --git a/src/app/routes/broker-ui/connector-page/connector-page/connector-page.component.html b/src/app/routes/broker-ui/connector-page/connector-page/connector-page.component.html new file mode 100644 index 000000000..fb96221a9 --- /dev/null +++ b/src/app/routes/broker-ui/connector-page/connector-page/connector-page.component.html @@ -0,0 +1,45 @@ +
+
+ + + Search Connectors + search + + + + + +
+
+
+ + + +
+
+ +
+
diff --git a/src/app/routes/broker-ui/connector-page/connector-page/connector-page.component.scss b/src/app/routes/broker-ui/connector-page/connector-page/connector-page.component.scss new file mode 100644 index 000000000..dae33aa22 --- /dev/null +++ b/src/app/routes/broker-ui/connector-page/connector-page/connector-page.component.scss @@ -0,0 +1,13 @@ +#wrapper { + margin: 20px; +} + +.search-form-field { + min-width: 200px; + width: 30%; +} + +mat-paginator { + display: inline-block; + background-color: transparent; +} diff --git a/src/app/routes/broker-ui/connector-page/connector-page/connector-page.component.ts b/src/app/routes/broker-ui/connector-page/connector-page/connector-page.component.ts new file mode 100644 index 000000000..f9469fa1e --- /dev/null +++ b/src/app/routes/broker-ui/connector-page/connector-page/connector-page.component.ts @@ -0,0 +1,50 @@ +import {Component, OnDestroy, OnInit} from '@angular/core'; +import {FormControl} from '@angular/forms'; +import { + BehaviorSubject, + Observable, + Subject, + distinctUntilChanged, + sampleTime, +} from 'rxjs'; +import {map} from 'rxjs/operators'; +import {value$} from '../../../../core/utils/form-group-utils'; +import {emptyConnectorPageStateModel} from './connector-page-data'; +import {ConnectorPageDataService} from './connector-page-data.service'; + +@Component({ + selector: 'connector-page', + templateUrl: './connector-page.component.html', + styleUrls: ['./connector-page.component.scss'], +}) +export class ConnectorPageComponent implements OnInit, OnDestroy { + data = emptyConnectorPageStateModel(); + data$ = new BehaviorSubject(this.data); + searchText = new FormControl(''); + private fetch$ = new BehaviorSubject(null); + + constructor(private catalogBrowserPageService: ConnectorPageDataService) {} + + ngOnInit(): void { + this.catalogBrowserPageService + .connectorPageData$(this.fetch$.pipe(sampleTime(200)), this.searchText$()) + .subscribe((data) => { + this.data = data; + this.data$.next(data); + }); + } + + private searchText$(): Observable { + return (value$(this.searchText) as Observable).pipe( + map((it) => (it ?? '').trim()), + distinctUntilChanged(), + ); + } + + ngOnDestroy$ = new Subject(); + + ngOnDestroy() { + this.ngOnDestroy$.next(null); + this.ngOnDestroy$.complete(); + } +} diff --git a/src/app/routes/connector-ui/asset-page/asset-page/asset-page.component.ts b/src/app/routes/connector-ui/asset-page/asset-page/asset-page.component.ts index b455a3270..6a66f4c59 100644 --- a/src/app/routes/connector-ui/asset-page/asset-page/asset-page.component.ts +++ b/src/app/routes/connector-ui/asset-page/asset-page/asset-page.component.ts @@ -2,7 +2,7 @@ import {Component, OnInit} from '@angular/core'; import {MatDialog} from '@angular/material/dialog'; import {BehaviorSubject} from 'rxjs'; import {map, switchMap} from 'rxjs/operators'; -import {AssetDetailDialogData} from '../../../../component-library/catalog/asset-detail-dialog/asset-detail-dialog-data'; +import {AssetDetailDialogDataService} from '../../../../component-library/catalog/asset-detail-dialog/asset-detail-dialog-data.service'; import {AssetDetailDialogResult} from '../../../../component-library/catalog/asset-detail-dialog/asset-detail-dialog-result'; import {AssetDetailDialogComponent} from '../../../../component-library/catalog/asset-detail-dialog/asset-detail-dialog.component'; import {AssetService} from '../../../../core/services/api/legacy-managent-api-client'; @@ -28,6 +28,7 @@ export class AssetPageComponent implements OnInit { private fetch$ = new BehaviorSubject(null); constructor( + private assetDetailDialogDataService: AssetDetailDialogDataService, private assetService: AssetService, private dialog: MatDialog, private assetPropertyMapper: AssetPropertyMapper, @@ -73,7 +74,7 @@ export class AssetPageComponent implements OnInit { } onAssetClick(asset: Asset) { - const data = AssetDetailDialogData.forAssetDetails(asset, true); + const data = this.assetDetailDialogDataService.assetDetails(asset, true); const ref = this.dialog.open(AssetDetailDialogComponent, { data, maxHeight: '90vh', diff --git a/src/app/routes/connector-ui/catalog-browser-page/catalog-browser-page/catalog-browser-page.component.ts b/src/app/routes/connector-ui/catalog-browser-page/catalog-browser-page/catalog-browser-page.component.ts index cd4fd7fed..299f5118e 100644 --- a/src/app/routes/connector-ui/catalog-browser-page/catalog-browser-page/catalog-browser-page.component.ts +++ b/src/app/routes/connector-ui/catalog-browser-page/catalog-browser-page/catalog-browser-page.component.ts @@ -9,7 +9,7 @@ import { sampleTime, } from 'rxjs'; import {map} from 'rxjs/operators'; -import {AssetDetailDialogData} from '../../../../component-library/catalog/asset-detail-dialog/asset-detail-dialog-data'; +import {AssetDetailDialogDataService} from '../../../../component-library/catalog/asset-detail-dialog/asset-detail-dialog-data.service'; import {AssetDetailDialogResult} from '../../../../component-library/catalog/asset-detail-dialog/asset-detail-dialog-result'; import {AssetDetailDialogComponent} from '../../../../component-library/catalog/asset-detail-dialog/asset-detail-dialog.component'; import {CatalogApiUrlService} from '../../../../core/services/api/catalog-api-url.service'; @@ -34,6 +34,7 @@ export class CatalogBrowserPageComponent implements OnInit, OnDestroy { private fetch$ = new BehaviorSubject(null); constructor( + private assetDetailDialogDataService: AssetDetailDialogDataService, private catalogBrowserPageService: CatalogBrowserPageService, private catalogApiUrlService: CatalogApiUrlService, private matDialog: MatDialog, @@ -53,7 +54,8 @@ export class CatalogBrowserPageComponent implements OnInit, OnDestroy { } onContractOfferClick(contractOffer: ContractOffer) { - const data = AssetDetailDialogData.forContractOffer(contractOffer); + const data = + this.assetDetailDialogDataService.contractOfferDetails(contractOffer); const ref = this.matDialog.open(AssetDetailDialogComponent, {data}); ref.afterClosed().subscribe((result: AssetDetailDialogResult) => { if (result?.refreshList) { diff --git a/src/app/routes/connector-ui/connector-ui.module.ts b/src/app/routes/connector-ui/connector-ui.module.ts index 34ab4ad31..e9560e66f 100644 --- a/src/app/routes/connector-ui/connector-ui.module.ts +++ b/src/app/routes/connector-ui/connector-ui.module.ts @@ -8,6 +8,8 @@ import {MatListModule} from '@angular/material/list'; import {MatSidenavModule} from '@angular/material/sidenav'; import {MatSnackBarModule} from '@angular/material/snack-bar'; import {MatToolbarModule} from '@angular/material/toolbar'; +import {AssetDetailDialogDataService} from '../../component-library/catalog/asset-detail-dialog/asset-detail-dialog-data.service'; +import {AssetPropertyGridGroupBuilder} from '../../component-library/catalog/asset-detail-dialog/asset-property-grid-group-builder'; import {PipesAndDirectivesModule} from '../../component-library/pipes-and-directives/pipes-and-directives.module'; import {UiElementsModule} from '../../component-library/ui-elements/ui-elements.module'; import {AssetPageModule} from './asset-page/asset-page.module'; @@ -55,7 +57,11 @@ import {TransferHistoryPageModule} from './transfer-history-page/transfer-histor ConnectorUiRoutingModule, ], declarations: [ConnectorUiComponent], - providers: [PreviousRouteListener], + providers: [ + PreviousRouteListener, + AssetPropertyGridGroupBuilder, + AssetDetailDialogDataService, + ], }) export class ConnectorUiModule { constructor(previousRouteListener: PreviousRouteListener) { diff --git a/src/app/routes/connector-ui/contract-agreement-page/contract-agreement-page/contract-agreement-page.component.ts b/src/app/routes/connector-ui/contract-agreement-page/contract-agreement-page/contract-agreement-page.component.ts index f9e2c9e5b..0074d3756 100644 --- a/src/app/routes/connector-ui/contract-agreement-page/contract-agreement-page/contract-agreement-page.component.ts +++ b/src/app/routes/connector-ui/contract-agreement-page/contract-agreement-page/contract-agreement-page.component.ts @@ -11,6 +11,7 @@ import { share, } from 'rxjs'; import {filter, map, takeUntil} from 'rxjs/operators'; +import {AssetDetailDialogDataService} from 'src/app/component-library/catalog/asset-detail-dialog/asset-detail-dialog-data.service'; import {AssetDetailDialogData} from '../../../../component-library/catalog/asset-detail-dialog/asset-detail-dialog-data'; import {AssetDetailDialogComponent} from '../../../../component-library/catalog/asset-detail-dialog/asset-detail-dialog.component'; import {Fetched} from '../../../../core/services/models/fetched'; @@ -34,6 +35,7 @@ export class ContractAgreementPageComponent implements OnInit, OnDestroy { private fetch$ = new BehaviorSubject(null); constructor( + private assetDetailDialogDataService: AssetDetailDialogDataService, private matDialog: MatDialog, private contractAgreementPageService: ContractAgreementPageService, ) {} @@ -50,7 +52,11 @@ export class ContractAgreementPageComponent implements OnInit, OnDestroy { onContractAgreementClick(card: ContractAgreementCardMapped) { const data$: Observable = this.card$( card.contractAgreementId, - ).pipe(map((it) => AssetDetailDialogData.forContractAgreement(it))); + ).pipe( + map((it) => + this.assetDetailDialogDataService.contractAgreementDetails(it), + ), + ); this.matDialog.open(AssetDetailDialogComponent, { data: data$, maxHeight: '90vh', diff --git a/src/app/routes/connector-ui/contract-definition-page/asset-select/asset-select.component.ts b/src/app/routes/connector-ui/contract-definition-page/asset-select/asset-select.component.ts index 2f3a7d981..e5fa83616 100644 --- a/src/app/routes/connector-ui/contract-definition-page/asset-select/asset-select.component.ts +++ b/src/app/routes/connector-ui/contract-definition-page/asset-select/asset-select.component.ts @@ -1,7 +1,7 @@ import {Component, Input} from '@angular/core'; import {FormControl} from '@angular/forms'; import {MatDialog} from '@angular/material/dialog'; -import {AssetDetailDialogData} from '../../../../component-library/catalog/asset-detail-dialog/asset-detail-dialog-data'; +import {AssetDetailDialogDataService} from '../../../../component-library/catalog/asset-detail-dialog/asset-detail-dialog-data.service'; import {AssetDetailDialogComponent} from '../../../../component-library/catalog/asset-detail-dialog/asset-detail-dialog.component'; import {Asset} from '../../../../core/services/models/asset'; @@ -19,14 +19,17 @@ export class AssetSelectComponent { @Input() assets: Asset[] = []; - constructor(private matDialog: MatDialog) {} + constructor( + private assetDetailDialogDataService: AssetDetailDialogDataService, + private matDialog: MatDialog, + ) {} isEqualId(a: Asset | null, b: Asset | null): boolean { return a?.id === b?.id; } onAssetClick(asset: Asset) { - const data = AssetDetailDialogData.forAssetDetails(asset, false); + const data = this.assetDetailDialogDataService.assetDetails(asset, false); this.matDialog.open(AssetDetailDialogComponent, { data, maxHeight: '90vh', diff --git a/src/app/routes/connector-ui/contract-definition-page/contract-definition-cards/contract-definition-cards.component.ts b/src/app/routes/connector-ui/contract-definition-page/contract-definition-cards/contract-definition-cards.component.ts index 19a43bec6..37eb25c0f 100644 --- a/src/app/routes/connector-ui/contract-definition-page/contract-definition-cards/contract-definition-cards.component.ts +++ b/src/app/routes/connector-ui/contract-definition-page/contract-definition-cards/contract-definition-cards.component.ts @@ -8,7 +8,7 @@ import { import {MatDialog, MatDialogRef} from '@angular/material/dialog'; import {EMPTY} from 'rxjs'; import {catchError, filter, map, tap} from 'rxjs/operators'; -import {AssetDetailDialogData} from '../../../../component-library/catalog/asset-detail-dialog/asset-detail-dialog-data'; +import {AssetDetailDialogDataService} from '../../../../component-library/catalog/asset-detail-dialog/asset-detail-dialog-data.service'; import {AssetDetailDialogResult} from '../../../../component-library/catalog/asset-detail-dialog/asset-detail-dialog-result'; import {AssetDetailDialogComponent} from '../../../../component-library/catalog/asset-detail-dialog/asset-detail-dialog.component'; import {ConfirmDialogModel} from '../../../../component-library/confirmation-dialog/confirmation-dialog/confirmation-dialog.component'; @@ -43,6 +43,7 @@ export class ContractDefinitionCardsComponent { deleteDone = new EventEmitter(); constructor( + private assetDetailDialogDataService: AssetDetailDialogDataService, private matDialog: MatDialog, private contractDefinitionService: ContractDefinitionService, private notificationService: NotificationService, @@ -59,7 +60,7 @@ export class ContractDefinitionCardsComponent { } onAssetClick(asset: Asset) { - const data = AssetDetailDialogData.forAssetDetails(asset, false); + const data = this.assetDetailDialogDataService.assetDetails(asset, false); const ref = this.matDialog.open(AssetDetailDialogComponent, { data, maxHeight: '90vh',