diff --git a/dynamic-mapping-service/src/main/java/dynamic/mapping/controller/ConfigurationController.java b/dynamic-mapping-service/src/main/java/dynamic/mapping/controller/ConfigurationController.java index 0d436407..53dc4e42 100644 --- a/dynamic-mapping-service/src/main/java/dynamic/mapping/controller/ConfigurationController.java +++ b/dynamic-mapping-service/src/main/java/dynamic/mapping/controller/ConfigurationController.java @@ -154,7 +154,7 @@ public ResponseEntity createConnectorConfiguration( } } - @GetMapping(value = "/connector/instances",produces = MediaType.APPLICATION_JSON_VALUE) + @GetMapping(value = "/connector/instance",produces = MediaType.APPLICATION_JSON_VALUE) public ResponseEntity> getConnectionConfigurations(@RequestParam(required = false) String name) { String tenant = contextService.getContext().getTenant(); log.debug("Tenant {} - Get connection details", tenant); diff --git a/dynamic-mapping-service/src/main/java/dynamic/mapping/core/C8YAgent.java b/dynamic-mapping-service/src/main/java/dynamic/mapping/core/C8YAgent.java index bc75244a..716b596c 100644 --- a/dynamic-mapping-service/src/main/java/dynamic/mapping/core/C8YAgent.java +++ b/dynamic-mapping-service/src/main/java/dynamic/mapping/core/C8YAgent.java @@ -624,7 +624,7 @@ private void registerExtensionInProcessor(String tenant, String id, String exten .newInstance(); // springUtil.registerBean(key, clazz); extensionEntry.setExtensionImplSource(extensionImpl); - extensionEntry.setExtensionType(ExtensionType.PROCESSOR_EXTENSION_SOURCE); + extensionEntry.setExtensionType(ExtensionType.EXTENSION_SOURCE); log.debug("Tenant {} - Successfully registered extensionImplSource : {} for key: {}", tenant, newExtensions.getProperty(key), @@ -637,7 +637,7 @@ private void registerExtensionInProcessor(String tenant, String id, String exten // springUtil.registerBean(key, clazz); extensionEntry.setExtensionImplTarget(extensionImpl); // overwrite type since it implements both - extensionEntry.setExtensionType(ExtensionType.PROCESSOR_EXTENSION_SOURCE_TARGET); + extensionEntry.setExtensionType(ExtensionType.EXTENSION_SOURCE_TARGET); log.debug("Tenant {} - Successfully registered extensionImplTarget : {} for key: {}", tenant, newExtensions.getProperty(key), diff --git a/dynamic-mapping-service/src/main/java/dynamic/mapping/core/ConfigurationRegistry.java b/dynamic-mapping-service/src/main/java/dynamic/mapping/core/ConfigurationRegistry.java index e09bf141..0ecfbe88 100644 --- a/dynamic-mapping-service/src/main/java/dynamic/mapping/core/ConfigurationRegistry.java +++ b/dynamic-mapping-service/src/main/java/dynamic/mapping/core/ConfigurationRegistry.java @@ -130,8 +130,8 @@ MappingType.JSON, new JSONProcessorInbound(this), MappingType.FLAT_FILE, new FlatFileProcessorInbound(this), MappingType.GENERIC_BINARY, new GenericBinaryProcessorInbound(this), MappingType.PROTOBUF_STATIC, new StaticProtobufProcessor(this), - MappingType.PROCESSOR_EXTENSION_SOURCE, extensibleProcessor, - MappingType.PROCESSOR_EXTENSION_SOURCE_TARGET, extensibleProcessor); + MappingType.EXTENSION_SOURCE, extensibleProcessor, + MappingType.EXTENSION_SOURCE_TARGET, extensibleProcessor); } public AConnectorClient createConnectorClient(ConnectorConfiguration connectorConfiguration, diff --git a/dynamic-mapping-service/src/main/java/dynamic/mapping/model/ExtensionType.java b/dynamic-mapping-service/src/main/java/dynamic/mapping/model/ExtensionType.java index 25860975..f7edabc5 100644 --- a/dynamic-mapping-service/src/main/java/dynamic/mapping/model/ExtensionType.java +++ b/dynamic-mapping-service/src/main/java/dynamic/mapping/model/ExtensionType.java @@ -22,6 +22,6 @@ package dynamic.mapping.model; public enum ExtensionType { - PROCESSOR_EXTENSION_SOURCE, - PROCESSOR_EXTENSION_SOURCE_TARGET + EXTENSION_SOURCE, + EXTENSION_SOURCE_TARGET } \ No newline at end of file diff --git a/dynamic-mapping-service/src/main/java/dynamic/mapping/model/Mapping.java b/dynamic-mapping-service/src/main/java/dynamic/mapping/model/Mapping.java index 6c41944a..f3f0b7d5 100644 --- a/dynamic-mapping-service/src/main/java/dynamic/mapping/model/Mapping.java +++ b/dynamic-mapping-service/src/main/java/dynamic/mapping/model/Mapping.java @@ -273,8 +273,8 @@ static public ArrayList isSubstitutionValid(Mapping mapping) { .count(); if (mapping.snoopStatus != SnoopStatus.ENABLED && mapping.snoopStatus != SnoopStatus.STARTED - && !mapping.mappingType.equals(MappingType.PROCESSOR_EXTENSION_SOURCE) - && !mapping.mappingType.equals(MappingType.PROCESSOR_EXTENSION_SOURCE_TARGET) + && !mapping.mappingType.equals(MappingType.EXTENSION_SOURCE) + && !mapping.mappingType.equals(MappingType.EXTENSION_SOURCE_TARGET) && !mapping.mappingType.equals(MappingType.PROTOBUF_STATIC) && !mapping.direction.equals(Direction.OUTBOUND)) { if (count > 1) { @@ -416,7 +416,7 @@ static Collection areJSONTemplatesValid(Mapping mapping) { result.add(ValidationError.Source_Template_Must_Be_Valid_JSON); } - if (!mapping.mappingType.equals(MappingType.PROCESSOR_EXTENSION_SOURCE) + if (!mapping.mappingType.equals(MappingType.EXTENSION_SOURCE) && !mapping.mappingType.equals(MappingType.PROTOBUF_STATIC)) { try { new JSONObject(mapping.targetTemplate); diff --git a/dynamic-mapping-service/src/main/java/dynamic/mapping/processor/model/MappingType.java b/dynamic-mapping-service/src/main/java/dynamic/mapping/processor/model/MappingType.java index 4922ed61..69379e28 100644 --- a/dynamic-mapping-service/src/main/java/dynamic/mapping/processor/model/MappingType.java +++ b/dynamic-mapping-service/src/main/java/dynamic/mapping/processor/model/MappingType.java @@ -26,8 +26,8 @@ public enum MappingType { FLAT_FILE("FLAT_FILE"), GENERIC_BINARY("GENERIC_BINARY"), PROTOBUF_STATIC("PROTOBUF_STATIC"), - PROCESSOR_EXTENSION_SOURCE("PROCESSOR_EXTENSION_SOURCE"), - PROCESSOR_EXTENSION_SOURCE_TARGET("PROCESSOR_EXTENSION_SOURCE_TARGET"); + EXTENSION_SOURCE("EXTENSION_SOURCE"), + EXTENSION_SOURCE_TARGET("EXTENSION_SOURCE_TARGET"); public final String name; diff --git a/dynamic-mapping-ui/src/connector/connector-configuration.component.html b/dynamic-mapping-ui/src/connector/connector-configuration.component.html index eb5ce307..b4b87178 100644 --- a/dynamic-mapping-ui/src/connector/connector-configuration.component.html +++ b/dynamic-mapping-ui/src/connector/connector-configuration.component.html @@ -19,7 +19,7 @@ ~ @authors Christof Strack --> -Configuration +Connectors + + + + + +
@@ -40,9 +53,9 @@ [selectable]="false">
-
+
\ No newline at end of file diff --git a/dynamic-mapping-ui/src/connector/connector-configuration.module.ts b/dynamic-mapping-ui/src/connector/connector-configuration.module.ts index 0821b1c6..7791e5c8 100644 --- a/dynamic-mapping-ui/src/connector/connector-configuration.module.ts +++ b/dynamic-mapping-ui/src/connector/connector-configuration.module.ts @@ -22,10 +22,11 @@ import { NgModule } from '@angular/core'; import { CoreModule, hookRoute } from '@c8y/ngx-components'; -import { SharedModule } from '../shared'; +import { connectorResolver, SharedModule } from '../shared'; import { ConnectorConfigurationComponent } from './connector-configuration.component'; import { BsDropdownModule } from 'ngx-bootstrap/dropdown'; import { NODE3 } from '../shared/mapping/util'; +import { ConnectorDetailsComponent } from '../shared/connector-details/connector-details.component'; @NgModule({ declarations: [ConnectorConfigurationComponent], @@ -38,7 +39,18 @@ import { NODE3 } from '../shared/mapping/util'; providers: [ hookRoute({ path: `sag-ps-pkg-dynamic-mapping/${NODE3}/connectorConfiguration`, - component: ConnectorConfigurationComponent, + children: [ + { + path: '', + pathMatch: 'full', + component: ConnectorConfigurationComponent + }, + { + path: 'details/:identifier', + component: ConnectorDetailsComponent, + resolve: { connector: connectorResolver } + } + ] }) ] }) diff --git a/dynamic-mapping-ui/src/mapping/core/facade/facade-operation.service.ts b/dynamic-mapping-ui/src/mapping/core/facade/facade-operation.service.ts index ea084a1b..9eb8f9da 100644 --- a/dynamic-mapping-ui/src/mapping/core/facade/facade-operation.service.ts +++ b/dynamic-mapping-ui/src/mapping/core/facade/facade-operation.service.ts @@ -27,7 +27,7 @@ import { } from '@c8y/client'; import { ProcessingContext } from '../processor/processor.model'; import { HttpStatusCode } from '@angular/common/http'; -import { randomIdAsString } from 'src/mapping/shared/util'; +import { randomIdAsString } from '../../../mapping/shared/util'; @Injectable({ providedIn: 'root' }) export class FacadeOperationService { diff --git a/dynamic-mapping-ui/src/mapping/core/mock/mock-inventory.service.ts b/dynamic-mapping-ui/src/mapping/core/mock/mock-inventory.service.ts index ccb9093f..a4c253a2 100644 --- a/dynamic-mapping-ui/src/mapping/core/mock/mock-inventory.service.ts +++ b/dynamic-mapping-ui/src/mapping/core/mock/mock-inventory.service.ts @@ -22,7 +22,7 @@ import { HttpStatusCode } from '@angular/common/http'; import { Injectable } from '@angular/core'; import { IdReference, IFetchResponse, IManagedObject, IResult } from '@c8y/client'; import * as _ from 'lodash'; -import { randomIdAsString } from 'src/mapping/shared/util'; +import { randomIdAsString } from '../../../mapping/shared/util'; @Injectable({ providedIn: 'root' }) export class MockInventoryService { diff --git a/dynamic-mapping-ui/src/mapping/grid/mapping.component.ts b/dynamic-mapping-ui/src/mapping/grid/mapping.component.ts index 85beaf02..b2bfaa33 100644 --- a/dynamic-mapping-ui/src/mapping/grid/mapping.component.ts +++ b/dynamic-mapping-ui/src/mapping/grid/mapping.component.ts @@ -532,17 +532,17 @@ export class MappingComponent implements OnInit, OnDestroy { ...mapping, sourceTemplate: sampleSource }; - } else if (this.mappingType == MappingType.PROCESSOR_EXTENSION_SOURCE) { + } else if (this.mappingType == MappingType.EXTENSION_SOURCE) { mapping.extension = { extensionName: undefined, eventName: undefined, - extensionType: ExtensionType.PROCESSOR_EXTENSION_SOURCE, + extensionType: ExtensionType.EXTENSION_SOURCE, }; - } else if (this.mappingType == MappingType.PROCESSOR_EXTENSION_SOURCE_TARGET) { + } else if (this.mappingType == MappingType.EXTENSION_SOURCE_TARGET) { mapping.extension = { extensionName: undefined, eventName: undefined, - extensionType: ExtensionType.PROCESSOR_EXTENSION_SOURCE_TARGET, + extensionType: ExtensionType.EXTENSION_SOURCE_TARGET, }; } diff --git a/dynamic-mapping-ui/src/shared/connector-configuration/connector-grid.component.ts b/dynamic-mapping-ui/src/shared/connector-configuration/connector-grid.component.ts index e53dd1bb..bc375954 100644 --- a/dynamic-mapping-ui/src/shared/connector-configuration/connector-grid.component.ts +++ b/dynamic-mapping-ui/src/shared/connector-configuration/connector-grid.component.ts @@ -64,6 +64,7 @@ import { StatusEnabledRendererComponent } from './renderer/status-enabled-render import { ConnectorStatusRendererComponent } from './renderer/connector-status.renderer.component'; import { CheckedRendererComponent } from './renderer/checked-renderer.component'; import { LabelRendererComponent } from '../component/renderer/label.renderer.component'; +import { ConnectorDetailCellRendererComponent } from './renderer/connector-link.renderer.component'; @Component({ selector: 'd11r-mapping-connector-configuration', @@ -165,6 +166,7 @@ export class ConnectorGridComponent implements OnInit, AfterViewInit { filterable: false, sortOrder: 'asc', visible: true, + cellRendererComponent: ConnectorDetailCellRendererComponent, gridTrackSize: '30%' }, { @@ -398,7 +400,7 @@ export class ConnectorGridComponent implements OnInit, AfterViewInit { gettext('Failed to delete connector configuration') ); } - await this.reloadData(); + this.refresh(); } confirmDeletionModalRef.hide(); } diff --git a/dynamic-mapping-ui/src/shared/connector-configuration/connector.model.ts b/dynamic-mapping-ui/src/shared/connector-configuration/connector.model.ts index 551b7492..aa24c83c 100644 --- a/dynamic-mapping-ui/src/shared/connector-configuration/connector.model.ts +++ b/dynamic-mapping-ui/src/shared/connector-configuration/connector.model.ts @@ -19,6 +19,10 @@ * @authors Christof Strack */ +import { inject } from "@angular/core"; +import { ResolveFn } from "@angular/router"; +import { ConnectorConfigurationService } from "../service/connector-configuration.service"; + export enum ConnectorPropertyType { ID_STRING_PROPERTY = 'ID_STRING_PROPERTY', STRING_PROPERTY = 'STRING_PROPERTY', @@ -60,3 +64,9 @@ export interface ConnectorSpecification { supportsWildcardInTopic: boolean; properties: { [name: string]: ConnectorProperty }; } + +export const connectorResolver: ResolveFn = (route) => { + const connectorConfigurationService = inject(ConnectorConfigurationService); + const identifier = route.paramMap.get('identifier'); + return connectorConfigurationService.getConnectorConfiguration(identifier); +}; diff --git a/dynamic-mapping-ui/src/shared/connector-configuration/renderer/connector-link.renderer.component.ts b/dynamic-mapping-ui/src/shared/connector-configuration/renderer/connector-link.renderer.component.ts new file mode 100644 index 00000000..dd23515b --- /dev/null +++ b/dynamic-mapping-ui/src/shared/connector-configuration/renderer/connector-link.renderer.component.ts @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2022 Software AG, Darmstadt, Germany and/or Software AG USA Inc., Reston, VA, USA, + * and/or its subsidiaries and/or its affiliates and/or their licensors. + * + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * @authors Christof Strack + */ +import { Component } from '@angular/core'; +import { CellRendererContext } from '@c8y/ngx-components'; + +/** + * The example component for custom cell renderer. + * It gets `context` with the current row item and the column. + * Additionally, a service is injected to provide a helper method. + * The template displays the icon and the label with additional styling. + */ +@Component({ + template: ` + + {{ context.item.name }} + + + + {{ context.item.name }} + + + ` +}) +export class ConnectorDetailCellRendererComponent { + constructor(public context: CellRendererContext) { } +} diff --git a/dynamic-mapping-ui/src/shared/connector-details/connector-details.component.html b/dynamic-mapping-ui/src/shared/connector-details/connector-details.component.html new file mode 100644 index 00000000..c71964a5 --- /dev/null +++ b/dynamic-mapping-ui/src/shared/connector-details/connector-details.component.html @@ -0,0 +1,103 @@ + + Connectors + + + + + + +
+ +
+ +
+ +
+
+ +
+ +
+
+ + + + {{ event.date | date: 'dd.MM.yy hh:mm:ss' }} + + +
+
+
+ {{ event.status ? event.status : '-' }} +
+
+
{{ event.connectorName }}
+
+
{{ event.message ? event.message : '-' }}
+
+
+
+
+
+
+
\ No newline at end of file diff --git a/dynamic-mapping-ui/src/shared/connector-details/connector-details.component.style.css b/dynamic-mapping-ui/src/shared/connector-details/connector-details.component.style.css new file mode 100644 index 00000000..7e485a52 --- /dev/null +++ b/dynamic-mapping-ui/src/shared/connector-details/connector-details.component.style.css @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2022 Software AG, Darmstadt, Germany and/or Software AG USA Inc., Reston, VA, USA, + * and/or its subsidiaries and/or its affiliates and/or their licensors. + * + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * @authors Christof Strack + */ + +.button-bottom-margin{ + margin-bottom: 8px; +} diff --git a/dynamic-mapping-ui/src/shared/connector-details/connector-details.component.ts b/dynamic-mapping-ui/src/shared/connector-details/connector-details.component.ts new file mode 100644 index 00000000..7b2e85b1 --- /dev/null +++ b/dynamic-mapping-ui/src/shared/connector-details/connector-details.component.ts @@ -0,0 +1,90 @@ +/* + * Copyright (c) 2022 Software AG, Darmstadt, Germany and/or Software AG USA Inc., Reston, VA, USA, + * and/or its subsidiaries and/or its affiliates and/or their licensors. + * + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * @authors Christof Strack + */ +import { Component, OnDestroy, OnInit } from '@angular/core'; +import { AlertService } from '@c8y/ngx-components'; +import { BsModalService } from 'ngx-bootstrap/modal'; +import { Observable, Subject, takeUntil } from 'rxjs'; +import packageJson from '../../../package.json'; +import { + ConnectorConfiguration, + ConnectorSpecification, + ConnectorStatus, + LoggingEventType, + LoggingEventTypeMap, +} from '..'; +import { ConnectorLogService } from '../service/connector-log.service'; +import { ConnectorConfigurationService } from '../service/connector-configuration.service'; +import { ActivatedRoute } from '@angular/router'; + +@Component({ + selector: 'd11r-mapping-connector-details', + styleUrls: ['./connector-details.component.style.css'], + templateUrl: 'connector-details.component.html' +}) +export class ConnectorDetailsComponent implements OnInit, OnDestroy { + version: string = packageJson.version; + monitorings$: Observable; + specifications: ConnectorSpecification[] = []; + configurations$: Observable = new Observable(); + statusLogs$: Observable ; + filterStatusLog = { + connectorIdentifier: 'ALL', + type: LoggingEventType.ALL, + }; + LoggingEventTypeMap = LoggingEventTypeMap; + LoggingEventType = LoggingEventType; + private destroy$ = new Subject(); + + constructor( + public bsModalService: BsModalService, + public connectorStatusService: ConnectorLogService, + public connectorConfigurationService: ConnectorConfigurationService, + public alertService: AlertService, + private route: ActivatedRoute + ) {} + + async ngOnInit() { + // console.log('Running version', this.version); + const {connector} = this.route.snapshot.data; + console.log('Details for connector', connector); + + this.connectorStatusService.initConnectorLogsRealtime(); + this.configurations$ = + this.connectorConfigurationService.getConnectorConfigurationsWithLiveStatus(); + this.statusLogs$ = this.connectorStatusService.getStatusLogs(); + // Subscribe to logs to verify they're coming through + this.statusLogs$.pipe( + takeUntil(this.destroy$) + ).subscribe({ + // next: (logs) => console.log('Received logs in component:', logs), + error: (error) => console.error('Error receiving logs:', error), + complete: () => console.log('Completed') // optional + }); + } + + updateStatusLogs() { + this.connectorStatusService.updateStatusLogs(this.filterStatusLog); + } + ngOnDestroy() { + this.destroy$.next(); + this.destroy$.complete(); + } +} diff --git a/dynamic-mapping-ui/src/shared/index.ts b/dynamic-mapping-ui/src/shared/index.ts index f23f3694..2794c256 100644 --- a/dynamic-mapping-ui/src/shared/index.ts +++ b/dynamic-mapping-ui/src/shared/index.ts @@ -6,6 +6,7 @@ export * from './confirmation/confirmation-modal.component'; export * from './connector-configuration/connector-grid.component'; export * from './connector-configuration/connector.model'; export * from './connector-configuration/create/connector-configuration-modal.component'; +export * from './connector-details/connector-details.component'; export * from './connector-log/connector-log.component'; export * from './connector-log/connector-log.model'; export * from './editor/jsoneditor.component'; diff --git a/dynamic-mapping-ui/src/shared/mapping/mapping.model.ts b/dynamic-mapping-ui/src/shared/mapping/mapping.model.ts index 4cd38064..72045423 100644 --- a/dynamic-mapping-ui/src/shared/mapping/mapping.model.ts +++ b/dynamic-mapping-ui/src/shared/mapping/mapping.model.ts @@ -115,8 +115,8 @@ export interface ExtensionEntry { } export enum ExtensionType { - PROCESSOR_EXTENSION_SOURCE = 'PROCESSOR_EXTENSION_SOURCE', - PROCESSOR_EXTENSION_SOURCE_TARGET = 'PROCESSOR_EXTENSION_SOURCE_TARGET', + EXTENSION_SOURCE = 'EXTENSION_SOURCE', + EXTENSION_SOURCE_TARGET = 'EXTENSION_SOURCE_TARGET', } export enum QOS { @@ -145,8 +145,8 @@ export enum MappingType { FLAT_FILE = 'FLAT_FILE', GENERIC_BINARY = 'GENERIC_BINARY', PROTOBUF_STATIC = 'PROTOBUF_STATIC', - PROCESSOR_EXTENSION_SOURCE = 'PROCESSOR_EXTENSION_SOURCE', - PROCESSOR_EXTENSION_SOURCE_TARGET = 'PROCESSOR_EXTENSION_SOURCE_TARGET' + EXTENSION_SOURCE = 'EXTENSION_SOURCE', + EXTENSION_SOURCE_TARGET = 'EXTENSION_SOURCE_TARGET' } export interface MappingTypeProperties { @@ -242,8 +242,8 @@ Use the JSONata function "$number() to parse an hexadecimal string as a number, allowTestSending: false } }, - [MappingType.PROCESSOR_EXTENSION_SOURCE]: { - key: MappingType.PROCESSOR_EXTENSION_SOURCE, + [MappingType.EXTENSION_SOURCE]: { + key: MappingType.EXTENSION_SOURCE, description: 'Mapping handles payloads in custom format. It can be used if you want to process the message yourself. This requires that a custom processor extension in Java is implemented and uploaded through the "Processor extension" tab.', properties: { @@ -262,10 +262,10 @@ Use the JSONata function "$number() to parse an hexadecimal string as a number, advanceFromStepToEndStep: 2 } }, - [MappingType.PROCESSOR_EXTENSION_SOURCE_TARGET]: { - key: MappingType.PROCESSOR_EXTENSION_SOURCE_TARGET, + [MappingType.EXTENSION_SOURCE_TARGET]: { + key: MappingType.EXTENSION_SOURCE_TARGET, description: - 'Mapping handles payloads in custom format. In contrast to the PROCESSOR_EXTENSION_SOURCE the completed processing of the payload: extract values from the incoming payload and then transform this to a Cumulocity API call. This requires that a custom processor extension in Java is implemented and uploaded through the "Processor extension" tab.', + 'Mapping handles payloads in custom format. In contrast to the EXTENSION_SOURCE the completed processing of the payload: extract values from the incoming payload and then transform this to a Cumulocity API call. This requires that a custom processor extension in Java is implemented and uploaded through the "Processor extension" tab.', properties: { [Direction.INBOUND]: { snoopSupported: false, directionSupported: true }, [Direction.OUTBOUND]: { snoopSupported: false, directionSupported: false } diff --git a/dynamic-mapping-ui/src/shared/misc/mapping-tab.factory.ts b/dynamic-mapping-ui/src/shared/misc/mapping-tab.factory.ts index 3f577298..f6438a90 100644 --- a/dynamic-mapping-ui/src/shared/misc/mapping-tab.factory.ts +++ b/dynamic-mapping-ui/src/shared/misc/mapping-tab.factory.ts @@ -68,14 +68,14 @@ export class MappingTabFactory implements TabFactory { icon: 'reflector-bulb', orientation: 'horizontal' } as Tab); - } else if (this.router.url.match(/sag-ps-pkg-dynamic-mapping\/node3/g)) { - tabs.push({ - path: `sag-ps-pkg-dynamic-mapping/${NODE3}/connectorConfiguration`, - priority: 910, - label: 'Connector', - icon: 'connected', - orientation: 'horizontal' - } as Tab); + } else if (this.router.url.match(/sag-ps-pkg-dynamic-mapping\/node3\/extension/g) || this.router.url.match(/sag-ps-pkg-dynamic-mapping\/node3\/serviceConfiguration/g)) { + // tabs.push({ + // path: `sag-ps-pkg-dynamic-mapping/${NODE3}/connectorConfiguration`, + // priority: 910, + // label: 'Connector', + // icon: 'connected', + // orientation: 'horizontal' + // } as Tab); tabs.push({ path: `sag-ps-pkg-dynamic-mapping/${NODE3}/extension`, priority: 500, diff --git a/dynamic-mapping-ui/src/shared/misc/navigation.factory.ts b/dynamic-mapping-ui/src/shared/misc/navigation.factory.ts index 86cb96e6..9b9c29d2 100644 --- a/dynamic-mapping-ui/src/shared/misc/navigation.factory.ts +++ b/dynamic-mapping-ui/src/shared/misc/navigation.factory.ts @@ -31,6 +31,9 @@ import { import { SharedService } from '../service/shared.service'; import { NODE1, NODE2, NODE3 } from '../mapping/util'; import { Router } from '@angular/router'; +import { ConnectorConfigurationService } from '../service/connector-configuration.service'; +import { combineLatest, map, Observable, of } from 'rxjs'; +import { ConnectorConfiguration } from '..'; @Injectable() export class MappingNavigationFactory implements NavigatorNodeFactory { @@ -39,11 +42,13 @@ export class MappingNavigationFactory implements NavigatorNodeFactory { appName: string; isStandaloneApp: boolean = false; + configurations: ConnectorConfiguration[] = []; constructor( private applicationService: ApplicationService, private alertService: AlertService, private sharedService: SharedService, + private connectorConfigurationService: ConnectorConfigurationService, private appStateService: AppStateService, public router: Router ) { @@ -60,59 +65,93 @@ export class MappingNavigationFactory implements NavigatorNodeFactory { }); } - get() { - let navs; + get() { + let navsFixed: NavigatorNode[]; // console.log( // 'Get: AppName in MappingNavigationFactory', // this.isStandaloneApp // ); if (this.isStandaloneApp) { - const parentMapping = new NavigatorNode({ + const rootNode = new NavigatorNode({ label: gettext('Home'), icon: 'home', path: '/sag-ps-pkg-dynamic-mapping/landing', priority: 600, preventDuplicates: true }); - const mappingConfiguration = new NavigatorNode({ + const configurationNode = new NavigatorNode({ label: gettext('Configuration'), icon: 'cog', + path: `/sag-ps-pkg-dynamic-mapping/${NODE3}/serviceConfiguration`, + priority: 500, + preventDuplicates: true + }); + const connectorNode = new NavigatorNode({ + parent: gettext('Configuration'), + label: gettext('Connectors'), + icon: 'c8y-device-management', path: `/sag-ps-pkg-dynamic-mapping/${NODE3}/connectorConfiguration`, priority: 500, preventDuplicates: true }); - const mapping = new NavigatorNode({ + const mappingNode = new NavigatorNode({ label: gettext('Mapping'), icon: 'rules', path: `/sag-ps-pkg-dynamic-mapping/${NODE1}/mappings/inbound`, priority: 400, preventDuplicates: true }); - const mappingMonitoring = new NavigatorNode({ + const monitoringNode = new NavigatorNode({ label: gettext('Monitoring'), icon: 'pie-chart', path: `/sag-ps-pkg-dynamic-mapping/${NODE2}/monitoring/grid`, priority: 300, preventDuplicates: true }); - navs = [parentMapping, mapping, mappingMonitoring, mappingConfiguration]; + navsFixed = [rootNode, mappingNode, monitoringNode, configurationNode, connectorNode]; + return combineLatest([ + of(navsFixed), + this.connectorConfigurationService.getConnectorConfigurationsAsObservable() + ]).pipe( + map(([navsFixed, connectors]) => { + const navs = [...navsFixed]; + connectors.forEach(con => { + navs.push(new NavigatorNode({ + parent: gettext('Connectors'), + label: gettext(con.name), + icon: 'connected', + path: `/sag-ps-pkg-dynamic-mapping/${NODE3}/connectorConfiguration`, + priority: 500, + preventDuplicates: true + })); + }); + return navs; + }) + ); } else { - const parentMapping = new NavigatorNode({ + const rootNode = new NavigatorNode({ label: gettext('Dynamic Data Mapper'), icon: 'compare', path: '/sag-ps-pkg-dynamic-mapping/landing', priority: 99, preventDuplicates: true }); - const mappingConfiguration = new NavigatorNode({ - parent: gettext('Dynamic Data Mapper'), + const configurationNode = new NavigatorNode({ label: gettext('Configuration'), icon: 'cog', + path: `/sag-ps-pkg-dynamic-mapping/${NODE3}/serviceConfiguration`, + priority: 500, + preventDuplicates: true + }); + const connectorNode = new NavigatorNode({ + parent: gettext('Configuration'), + label: gettext('Connectors'), + icon: 'c8y-device-management', path: `/sag-ps-pkg-dynamic-mapping/${NODE3}/connectorConfiguration`, priority: 500, preventDuplicates: true }); - const mapping = new NavigatorNode({ + const mappingNode = new NavigatorNode({ parent: gettext('Dynamic Data Mapper'), label: gettext('Mapping'), icon: 'file-type-document', @@ -120,7 +159,7 @@ export class MappingNavigationFactory implements NavigatorNodeFactory { priority: 400, preventDuplicates: true }); - const mappingMonitoring = new NavigatorNode({ + const monitoringNode = new NavigatorNode({ parent: gettext('Dynamic Data Mapper'), label: gettext('Monitoring'), icon: 'pie-chart', @@ -128,21 +167,32 @@ export class MappingNavigationFactory implements NavigatorNodeFactory { priority: 300, preventDuplicates: true }); - navs = [parentMapping, mapping, mappingMonitoring, mappingConfiguration]; + navsFixed = [rootNode, mappingNode, monitoringNode, configurationNode, connectorNode]; + this.configurations.forEach(con => { + navsFixed.push(new NavigatorNode({ + parent: gettext('Connectors'), + label: gettext(con.name), + icon: 'connected', + path: `/sag-ps-pkg-dynamic-mapping/${NODE3}/connectorConfiguration`, + priority: 500, + preventDuplicates: true + })) + }) + return of(navsFixed); } const feature: any = this.sharedService.getFeatures(); - return this.applicationService - .isAvailable(MappingNavigationFactory.APPLICATION_DYNAMIC_MAPPING_SERVICE) - .then((data) => { - if (!data.data || !feature) { - this.alertService.danger( - 'Microservice:dynamic-mapping-service not subscribed. Please subscribe this service before using the mapping editor!' - ); - console.error('dynamic-mapping-service microservice not subscribed!'); - return []; - } - return navs; - }); + // return this.applicationService + // .isAvailable(MappingNavigationFactory.APPLICATION_DYNAMIC_MAPPING_SERVICE) + // .then((data) => { + // if (!data.data || !feature) { + // this.alertService.danger( + // 'Microservice:dynamic-mapping-service not subscribed. Please subscribe this service before using the mapping editor!' + // ); + // console.error('dynamic-mapping-service microservice not subscribed!'); + // return []; + // } + // return navs; + // }); } } diff --git a/dynamic-mapping-ui/src/shared/service/connector-configuration.service.ts b/dynamic-mapping-ui/src/shared/service/connector-configuration.service.ts index eacc13cf..b8aec638 100644 --- a/dynamic-mapping-ui/src/shared/service/connector-configuration.service.ts +++ b/dynamic-mapping-ui/src/shared/service/connector-configuration.service.ts @@ -200,7 +200,7 @@ export class ConnectorConfigurationService { async getConnectorConfigurations(): Promise { const response = await this.client.fetch( - `${BASE_URL}/${PATH_CONFIGURATION_CONNECTION_ENDPOINT}/instances`, + `${BASE_URL}/${PATH_CONFIGURATION_CONNECTION_ENDPOINT}/instance`, { headers: { accept: 'application/json' @@ -213,6 +213,25 @@ export class ConnectorConfigurationService { return this._connectorConfigurations; } + async getConnectorConfiguration(identifier: string): Promise{ + const response = await this.client.fetch( + `${BASE_URL}/${PATH_CONFIGURATION_CONNECTION_ENDPOINT}/instance/${identifier}`, + { + headers: { + accept: 'application/json' + }, + method: 'GET' + } + ); + const result = await response.json(); + + return result; + } + + getConnectorConfigurationsAsObservable(): Observable { + return this.connectorConfigurations$; + } + // private getConnectorStatusEvents(): Observable { // // subscribe to event stream // this.eventRealtimeService.start(); diff --git a/dynamic-mapping-ui/src/shared/shared.module.ts b/dynamic-mapping-ui/src/shared/shared.module.ts index f7b44914..28966fff 100644 --- a/dynamic-mapping-ui/src/shared/shared.module.ts +++ b/dynamic-mapping-ui/src/shared/shared.module.ts @@ -40,6 +40,9 @@ import { FieldInputCustom } from './component/formly/input-custom.type.component import { FORMLY_CONFIG } from '@ngx-formly/core'; import { StatusEnabledRendererComponent } from './connector-configuration/renderer/status-enabled-renderer.component'; import { FilterJsonPipe } from './misc/filter-json.pipe'; +import { ConnectorDetailCellRendererComponent } from './connector-configuration/renderer/connector-link.renderer.component'; +import { RouterModule } from '@angular/router'; +import { ConnectorDetailsComponent } from './connector-details/connector-details.component'; @NgModule({ declarations: [ @@ -53,9 +56,11 @@ import { FilterJsonPipe } from './misc/filter-json.pipe'; DisableDirective, ConnectorStatusComponent, ConnectorGridComponent, + ConnectorDetailsComponent, ConnectorConfigurationModalComponent, StatusEnabledRendererComponent, ConnectorStatusRendererComponent, + ConnectorDetailCellRendererComponent, WrapperCustomFormField, FieldTextareaCustom, FieldInputCustom, @@ -64,6 +69,7 @@ import { FilterJsonPipe } from './misc/filter-json.pipe'; CoreModule, BsDatepickerModule, PaginationModule, + RouterModule, BsDropdownModule.forRoot() ], exports: [ @@ -76,6 +82,7 @@ import { FilterJsonPipe } from './misc/filter-json.pipe'; FormatStringPipe, ConnectorStatusComponent, ConnectorGridComponent, + ConnectorDetailsComponent, ConnectorConfigurationModalComponent, WrapperCustomFormField, FieldTextareaCustom, diff --git a/resources/image/Dynamic_Mapper_Mapping_Table_Add_Modal.png b/resources/image/Dynamic_Mapper_Mapping_Table_Add_Modal.png index a22cbb5b..660c8a04 100644 Binary files a/resources/image/Dynamic_Mapper_Mapping_Table_Add_Modal.png and b/resources/image/Dynamic_Mapper_Mapping_Table_Add_Modal.png differ diff --git a/resources/image/Dynamic_Mapper_Mapping_Table_Add_Modal_Snooping.png b/resources/image/Dynamic_Mapper_Mapping_Table_Add_Modal_Snooping.png index e52cab9a..8eeb0ed5 100644 Binary files a/resources/image/Dynamic_Mapper_Mapping_Table_Add_Modal_Snooping.png and b/resources/image/Dynamic_Mapper_Mapping_Table_Add_Modal_Snooping.png differ diff --git a/resources/samples/SampleMappings_13.pdf b/resources/samples/SampleMappings_13.pdf index 770400c2..f878b567 100644 Binary files a/resources/samples/SampleMappings_13.pdf and b/resources/samples/SampleMappings_13.pdf differ diff --git a/resources/samples/SampleMappings_13.xlsx b/resources/samples/SampleMappings_13.xlsx index 7b6c6e75..c9740cbd 100644 Binary files a/resources/samples/SampleMappings_13.xlsx and b/resources/samples/SampleMappings_13.xlsx differ diff --git a/resources/samples/mappings-INBOUND.json b/resources/samples/mappings-INBOUND.json index 99907c1c..3d98cc8c 100644 --- a/resources/samples/mappings-INBOUND.json +++ b/resources/samples/mappings-INBOUND.json @@ -636,7 +636,7 @@ "direction": "INBOUND", "sourceTemplate": "{\"deviceId\":\"909090\",\"description\":\"This is a new test event.\",\"time\":\"2022-08-05T00:14:49.389+02:00\",\"eventType\":\"TestEvent\"}", "targetTemplate": "{\"text\":\"This is a new test event.\",\"time\":\"2022-08-05T00:14:49.389+02:00\",\"type\":\"c8y_TestEvent\"}", - "mappingType": "PROCESSOR_EXTENSION_SOURCE", + "mappingType": "EXTENSION_SOURCE", "substitutions": [], "active": false, "debug": false, @@ -653,7 +653,7 @@ "extensionName": "dynamic-mapping-extension", "eventName": "CustomEvent", "loaded": false, - "extensionType": "PROCESSOR_EXTENSION_SOURCE" + "extensionType": "EXTENSION_SOURCE" }, "qos": "AT_LEAST_ONCE", "lastUpdate": 1735217875869, @@ -865,7 +865,7 @@ "direction": "INBOUND", "sourceTemplate": "{\"Temperature\":{\"value\":110,\"unit\":\"C\"},\"time\":\"2022-08-05T00:14:49.389+02:00\",\"deviceId\":\"909090\"}", "targetTemplate": "{\"time\":\"2022-08-05T00:14:49.389+02:00\",\"type\":\"c8y_Temperature\",\"c8y_Temperature\":\"dummy\",\"c8y_Fragment_to_remove\":\"remove_me\"}", - "mappingType": "PROCESSOR_EXTENSION_SOURCE", + "mappingType": "EXTENSION_SOURCE", "substitutions": [], "active": true, "debug": false, @@ -882,7 +882,7 @@ "extensionName": "dynamic-mapping-extension", "eventName": "CustomMeasurement", "loaded": false, - "extensionType": "PROCESSOR_EXTENSION_SOURCE" + "extensionType": "EXTENSION_SOURCE" }, "qos": "AT_LEAST_ONCE", "lastUpdate": 1735152784594, @@ -1036,7 +1036,7 @@ "direction": "INBOUND", "sourceTemplate": "{\"deviceId\":\"909090\",\"alarmType\":\"TestAlarm\",\"description\":\"This is a new test alarm!\",\"severity\":\"MAJOR\",\"status\":\"ACTIVE\",\"time\":\"2022-08-05T00:14:49.389+02:00\"}", "targetTemplate": "{\"severity\":\"MAJOR\",\"status\":\"ACTIVE\",\"text\":\"This is a new test alarm!\",\"time\":\"2022-08-05T00:14:49.389+02:00\",\"type\":\"c8y_TestAlarm\"}", - "mappingType": "PROCESSOR_EXTENSION_SOURCE_TARGET", + "mappingType": "EXTENSION_SOURCE_TARGET", "substitutions": [], "active": true, "debug": false, @@ -1053,7 +1053,7 @@ "extensionName": "dynamic-mapping-extension", "eventName": "CustomAlarm", "loaded": false, - "extensionType": "PROCESSOR_EXTENSION_SOURCE_TARGET" + "extensionType": "EXTENSION_SOURCE_TARGET" }, "qos": "AT_LEAST_ONCE", "lastUpdate": 1735221431554, diff --git a/resources/script/mgmt/dynamic-mapping-mgmt.sh b/resources/script/mgmt/dynamic-mapping-mgmt.sh index 768d6e3c..c3217a3d 100755 --- a/resources/script/mgmt/dynamic-mapping-mgmt.sh +++ b/resources/script/mgmt/dynamic-mapping-mgmt.sh @@ -11,7 +11,7 @@ set -e # 4. remove 'subscriptionTopic', instead the content of 'mappingTopic' is used for managing the subscriptions # 5. rename 'extension.event' -> 'extension.eventName' # 6. rename 'extension.name' -> 'extension.extensionName' -# 6. add 'extension.extensionType: 'PROCESSOR_EXTENSION_SOURCE' +# 6. add 'extension.extensionType: 'EXTENSION_SOURCE' # 7. remove 'extension.loaded' # 8. rename 'mapDeviceIdentifier' -> 'useExternalId' @@ -37,7 +37,7 @@ function show_usage() { echo " Step 2b transform substitutions for INBOUND to the new format" echo " Step 3 delete old mappings from tenant" echo " Step 4 create transformed mappings in tenant" - echo " deleteMappings: Delete mappings" + echo " deleteMappings: Delete mappings [direction]: deletes the mapping for a specified direction INBOUND, OUTBOUND, if not specified delete all" echo " deleteConnectors: Delete connectors" echo " importMappingsAsMO: [filename]: Import mappings from file as managedObjects, see output format generated by exportMappingsAsMO" echo " exportMappingsAsMO: [filename]: Export mappings to file as managedObjects" @@ -94,9 +94,9 @@ function migrate_mappings() { extension: (.extension + { eventName: .extension.event, extensionName: .extension.name, - extensionType: "PROCESSOR_EXTENSION_SOURCE" + extensionType: "EXTENSION_SOURCE" } | del(.event, .name, .loaded)), - "mappingType": "PROCESSOR_EXTENSION_SOURCE" + "mappingType": "EXTENSION_SOURCE" } else {} end) | del(.source, .target, .filterOutbound, .subscriptionTopic, .ident, .mapDeviceIdentifier) ]' >"${MIGRATED_MAPPINGS_name}-step1.json" @@ -167,13 +167,20 @@ function migrate_mappings() { function delete_mappings() { check_prerequisites - echo 'delete mappings' - c8y inventory list --includeAll --type d11r_mapping | c8y inventory delete + local direction="${1:-}" # Default to ALL if no argument provided + echo "Delete mappings '$direction'" + + if [ "$direction" = "INBOUND" ] || [ "$direction" = "INBOUND" ]; then + c8y inventory find --type d11r_mapping --query "d11r_mapping.direction eq '$direction'" --includeAll | c8y inventory delete + else + c8y inventory list --includeAll --type d11r_mapping | c8y inventory delete + fi + } function delete_connectors() { check_prerequisites - echo 'delete connectors' + echo 'Delete connectors' c8y tenantoptions getForCategory --category dynamic.mapper.service | jq 'keys| .[] | select(startswith("credentials.connection.configuration"))' -r | c8y tenantoptions delete --category dynamic.mapper.service --key -.key } @@ -228,7 +235,7 @@ migrateMappings) migrate_mappings ;; deleteMappings) - delete_mappings + delete_mappings "${2:-}" ;; deleteConnectors) delete_connectors