From dfd91573b7567e1dab482f17111ab8f809553d99 Mon Sep 17 00:00:00 2001 From: Ravi Pendurty Date: Thu, 7 Dec 2023 22:45:28 +0530 Subject: Create wt-odlux directory Include odlux apps, helpserver and readthedocs Issue-ID: CCSDK-3970 Change-Id: I1aee1327e7da12e8f658185b9a985a5204ad6065 Signed-off-by: Ravi Pendurty --- sdnr/wt-odlux/odlux/apps/apiDemo/.babelrc | 17 + sdnr/wt-odlux/odlux/apps/apiDemo/package.json | 46 + sdnr/wt-odlux/odlux/apps/apiDemo/pom.xml | 105 ++ .../apps/apiDemo/src/actions/modulesSuccess.ts | 25 + .../apiDemo/src/handlers/apiDemoRootHandler.ts | 41 + .../apps/apiDemo/src/handlers/modulesHandler.ts | 33 + sdnr/wt-odlux/odlux/apps/apiDemo/src/index.html | 24 + .../odlux/apps/apiDemo/src/models/module.ts | 28 + sdnr/wt-odlux/odlux/apps/apiDemo/src/plugin.tsx | 53 + sdnr/wt-odlux/odlux/apps/apiDemo/tsconfig.json | 37 + sdnr/wt-odlux/odlux/apps/apiDemo/webpack.config.js | 139 ++ sdnr/wt-odlux/odlux/apps/app-installer/pom.xml | 157 ++ .../src/assembly/assemble_mvnrepo_zip.xml | 47 + sdnr/wt-odlux/odlux/apps/configurationApp/.babelrc | 17 + .../odlux/apps/configurationApp/package.json | 47 + .../odlux/apps/configurationApp/policies.json | 12 + sdnr/wt-odlux/odlux/apps/configurationApp/pom.xml | 109 ++ .../configurationApp/src/actions/deviceActions.ts | 626 ++++++++ .../src/assets/icons/configurationAppIcon.svg | 20 + .../configurationApp/src/components/baseProps.ts | 28 + .../src/components/ifWhenTextInput.tsx | 101 ++ .../src/components/uiElementBoolean.tsx | 63 + .../src/components/uiElementLeafList.tsx | 209 +++ .../src/components/uiElementNumber.tsx | 70 + .../src/components/uiElementReference.tsx | 67 + .../src/components/uiElementSelection.tsx | 69 + .../src/components/uiElementString.tsx | 84 + .../src/components/uiElementUnion.tsx | 91 ++ .../src/handlers/configurationAppRootHandler.ts | 47 + .../handlers/connectedNetworkElementsHandler.ts | 45 + .../src/handlers/deviceDescriptionHandler.ts | 48 + .../src/handlers/valueSelectorHandler.ts | 78 + .../src/handlers/viewDescriptionHandler.ts | 82 + .../odlux/apps/configurationApp/src/index.html | 30 + .../src/models/networkElementConnection.ts | 37 + .../apps/configurationApp/src/models/uiModels.ts | 241 +++ .../odlux/apps/configurationApp/src/models/yang.ts | 71 + .../configurationApp/src/pluginConfiguration.tsx | 145 ++ .../configurationApp/src/services/restServices.ts | 164 ++ .../configurationApp/src/services/yangService.ts | 37 + .../configurationApp/src/utilities/verifyer.ts | 261 ++++ .../src/utilities/viewEngineHelper.ts | 324 ++++ .../src/views/configurationApplication.tsx | 931 +++++++++++ .../src/views/networkElementSelector.tsx | 72 + .../apps/configurationApp/src/yang/whenParser.ts | 235 +++ .../apps/configurationApp/src/yang/yangParser.ts | 1625 ++++++++++++++++++++ .../odlux/apps/configurationApp/tsconfig.json | 38 + .../odlux/apps/configurationApp/webpack.config.js | 150 ++ sdnr/wt-odlux/odlux/apps/connectApp/.babelrc | 17 + sdnr/wt-odlux/odlux/apps/connectApp/package.json | 44 + sdnr/wt-odlux/odlux/apps/connectApp/policies.json | 12 + sdnr/wt-odlux/odlux/apps/connectApp/pom.xml | 109 ++ .../src/actions/commonNetworkElementsActions.ts | 141 ++ .../src/actions/infoNetworkElementActions.ts | 82 + .../src/actions/mountedNetworkElementsActions.ts | 60 + .../src/actions/networkElementsActions.ts | 60 + .../apps/connectApp/src/actions/tlsKeyActions.ts | 59 + .../connectApp/src/assets/icons/connectAppIcon.svg | 22 + .../src/components/connectionStatusLog.tsx | 99 ++ .../src/components/editNetworkElementDialog.tsx | 425 +++++ .../src/components/infoNetworkElementDialog.tsx | 160 ++ .../connectApp/src/components/networkElements.tsx | 314 ++++ .../refreshConnectionStatusLogDialog.tsx | 114 ++ .../components/refreshNetworkElementsDialog.tsx | 114 ++ .../src/handlers/connectAppRootHandler.ts | 100 ++ .../src/handlers/connectionStatusLogHandler.ts | 36 + .../src/handlers/infoNetworkElementHandler.ts | 92 ++ .../src/handlers/networkElementsHandler.ts | 64 + .../apps/connectApp/src/handlers/tlsKeyHandler.ts | 55 + sdnr/wt-odlux/odlux/apps/connectApp/src/index.html | 28 + .../connectApp/src/models/connectionStatusLog.ts | 27 + .../apps/connectApp/src/models/guiCutTrough.ts | 22 + .../connectApp/src/models/networkElementBase.ts | 22 + .../src/models/networkElementConnection.ts | 69 + .../src/models/networkElementConnectionLog.ts | 25 + .../odlux/apps/connectApp/src/models/panelId.ts | 19 + .../apps/connectApp/src/models/topologyNetconf.ts | 59 + .../connectApp/src/models/yangCapabilitiesType.ts | 24 + .../odlux/apps/connectApp/src/pluginConnect.tsx | 109 ++ .../apps/connectApp/src/services/connectService.ts | 305 ++++ .../apps/connectApp/src/views/connectView.tsx | 102 ++ sdnr/wt-odlux/odlux/apps/connectApp/tsconfig.json | 6 + .../odlux/apps/connectApp/webpack.config.js | 202 +++ sdnr/wt-odlux/odlux/apps/demoApp/.babelrc | 17 + sdnr/wt-odlux/odlux/apps/demoApp/package.json | 46 + sdnr/wt-odlux/odlux/apps/demoApp/pom.xml | 109 ++ .../apps/demoApp/src/actions/authorActions.ts | 48 + .../odlux/apps/demoApp/src/components/counter.tsx | 29 + .../demoApp/src/handlers/demoAppRootHandler.ts | 44 + .../apps/demoApp/src/handlers/editAuthorHandler.ts | 33 + .../demoApp/src/handlers/listAuthorsHandler.ts | 57 + sdnr/wt-odlux/odlux/apps/demoApp/src/index.html | 26 + .../odlux/apps/demoApp/src/models/author.ts | 37 + sdnr/wt-odlux/odlux/apps/demoApp/src/plugin.tsx | 54 + .../apps/demoApp/src/services/authorService.ts | 72 + .../odlux/apps/demoApp/src/views/authorsList.tsx | 93 ++ .../odlux/apps/demoApp/src/views/editAuthor.tsx | 34 + sdnr/wt-odlux/odlux/apps/demoApp/tsconfig.json | 37 + sdnr/wt-odlux/odlux/apps/demoApp/webpack.config.js | 134 ++ sdnr/wt-odlux/odlux/apps/eventLogApp/.babelrc | 17 + sdnr/wt-odlux/odlux/apps/eventLogApp/package.json | 43 + sdnr/wt-odlux/odlux/apps/eventLogApp/pom.xml | 109 ++ .../src/assets/icons/eventLogAppIcon.svg | 21 + .../src/components/refreshEventLogDialog.tsx | 117 ++ .../src/handlers/eventLogAppRootHandler.ts | 45 + .../eventLogApp/src/handlers/eventLogHandler.tsx | 36 + .../wt-odlux/odlux/apps/eventLogApp/src/index.html | 26 + .../apps/eventLogApp/src/models/eventLogType.ts | 27 + .../odlux/apps/eventLogApp/src/pluginEventLog.tsx | 42 + .../odlux/apps/eventLogApp/src/views/eventLog.tsx | 102 ++ sdnr/wt-odlux/odlux/apps/eventLogApp/tsconfig.json | 37 + .../odlux/apps/eventLogApp/webpack.config.js | 165 ++ sdnr/wt-odlux/odlux/apps/faultApp/.babelrc | 17 + sdnr/wt-odlux/odlux/apps/faultApp/package.json | 46 + sdnr/wt-odlux/odlux/apps/faultApp/pom.xml | 109 ++ .../faultApp/src/actions/clearStuckAlarmsAction.ts | 37 + .../faultApp/src/actions/notificationActions.ts | 33 + .../faultApp/src/actions/panelChangeActions.ts | 37 + .../apps/faultApp/src/actions/statusActions.ts | 60 + .../faultApp/src/assets/icons/faultAppIcon.svg | 19 + .../src/components/clearStuckAlarmsDialog.tsx | 138 ++ .../apps/faultApp/src/components/dashboardHome.tsx | 473 ++++++ .../apps/faultApp/src/components/faultStatus.tsx | 106 ++ .../src/components/refreshAlarmLogDialog.tsx | 112 ++ .../src/components/refreshCurrentAlarmsDialog.tsx | 113 ++ .../src/handlers/alarmLogEntriesHandler.ts | 36 + .../src/handlers/clearStuckAlarmsHandler.ts | 37 + .../faultApp/src/handlers/currentAlarmsHandler.ts | 36 + .../faultApp/src/handlers/faultAppRootHandler.ts | 63 + .../faultApp/src/handlers/faultStatusHandler.ts | 77 + .../faultApp/src/handlers/notificationsHandler.ts | 48 + sdnr/wt-odlux/odlux/apps/faultApp/src/index.html | 26 + .../odlux/apps/faultApp/src/models/fault.ts | 97 ++ .../odlux/apps/faultApp/src/models/panelId.ts | 18 + .../odlux/apps/faultApp/src/pluginFault.tsx | 171 ++ .../faultApp/src/services/faultStatusService.ts | 69 + .../apps/faultApp/src/views/faultApplication.tsx | 247 +++ sdnr/wt-odlux/odlux/apps/faultApp/tsconfig.json | 37 + .../wt-odlux/odlux/apps/faultApp/webpack.config.js | 166 ++ sdnr/wt-odlux/odlux/apps/helpApp/.babelrc | 17 + sdnr/wt-odlux/odlux/apps/helpApp/package.json | 48 + sdnr/wt-odlux/odlux/apps/helpApp/pom.xml | 109 ++ .../odlux/apps/helpApp/src/actions/helpActions.ts | 78 + .../apps/helpApp/src/assets/icons/helpAppIcon.svg | 27 + .../apps/helpApp/src/components/helpStatus.tsx | 83 + .../odlux/apps/helpApp/src/components/markdown.tsx | 77 + .../odlux/apps/helpApp/src/components/tocEntry.tsx | 85 + .../helpApp/src/handlers/helpAppRootHandler.ts | 76 + sdnr/wt-odlux/odlux/apps/helpApp/src/index.html | 29 + .../odlux/apps/helpApp/src/models/tocNode.ts | 42 + sdnr/wt-odlux/odlux/apps/helpApp/src/plugin.tsx | 91 ++ .../odlux/apps/helpApp/src/services/helpService.ts | 65 + .../odlux/apps/helpApp/src/utilities/path.ts | 79 + .../apps/helpApp/src/views/helpApplication.tsx | 84 + .../odlux/apps/helpApp/src/views/helpTocApp.tsx | 55 + sdnr/wt-odlux/odlux/apps/helpApp/tsconfig.json | 37 + sdnr/wt-odlux/odlux/apps/helpApp/webpack.config.js | 189 +++ sdnr/wt-odlux/odlux/apps/inventoryApp/.babelrc | 17 + sdnr/wt-odlux/odlux/apps/inventoryApp/package.json | 43 + sdnr/wt-odlux/odlux/apps/inventoryApp/pom.xml | 110 ++ .../src/actions/inventoryDeviceListActions.ts | 59 + .../src/actions/inventoryTreeActions.ts | 101 ++ .../apps/inventoryApp/src/actions/panelActions.ts | 31 + .../src/assets/icons/inventoryAppIcon.svg | 23 + .../src/components/refreshInventoryDialog.tsx | 113 ++ .../odlux/apps/inventoryApp/src/fakeData/index.ts | 77 + .../src/handlers/inventoryAppRootHandler.ts | 53 + .../handlers/inventoryDeviceListActionHandler.ts | 56 + .../src/handlers/inventoryElementsHandler.ts | 36 + .../src/handlers/inventoryTreeHandler.ts | 68 + .../apps/inventoryApp/src/handlers/panelHandler.ts | 11 + .../odlux/apps/inventoryApp/src/index.html | 28 + .../apps/inventoryApp/src/models/inventory.ts | 50 + .../src/models/inventoryDeviceListType.ts | 25 + .../src/models/networkElementConnection.ts | 37 + .../odlux/apps/inventoryApp/src/models/panelId.ts | 19 + .../apps/inventoryApp/src/pluginInventory.tsx | 88 ++ .../inventoryApp/src/services/inventoryService.ts | 92 ++ .../apps/inventoryApp/src/views/dashboard.tsx | 202 +++ .../odlux/apps/inventoryApp/src/views/detail.tsx | 44 + .../odlux/apps/inventoryApp/src/views/treeview.tsx | 155 ++ .../wt-odlux/odlux/apps/inventoryApp/tsconfig.json | 37 + .../odlux/apps/inventoryApp/webpack.config.js | 178 +++ sdnr/wt-odlux/odlux/apps/maintenanceApp/.babelrc | 17 + .../odlux/apps/maintenanceApp/package.json | 46 + sdnr/wt-odlux/odlux/apps/maintenanceApp/pom.xml | 109 ++ .../src/actions/maintenenceActions.ts | 77 + .../src/assets/icons/maintenanceAppIcon.svg | 50 + .../src/components/editMaintenenceEntryDialog.tsx | 207 +++ .../src/components/refreshMaintenanceEntries.tsx | 113 ++ .../src/handlers/maintenanceAppRootHandler.ts | 39 + .../src/handlers/maintenanceEntriesHandler.ts | 35 + .../odlux/apps/maintenanceApp/src/index.html | 26 + .../src/models/maintenanceEntryType.ts | 33 + .../apps/maintenanceApp/src/pluginMaintenance.tsx | 44 + .../src/services/maintenenceService.ts | 72 + .../apps/maintenanceApp/src/utils/timeUtils.ts | 45 + .../maintenanceApp/src/views/maintenanceView.tsx | 246 +++ .../odlux/apps/maintenanceApp/tsconfig.json | 37 + .../odlux/apps/maintenanceApp/webpack.config.js | 168 ++ sdnr/wt-odlux/odlux/apps/mediatorApp/.babelrc | 17 + sdnr/wt-odlux/odlux/apps/mediatorApp/package.json | 44 + sdnr/wt-odlux/odlux/apps/mediatorApp/pom.xml | 105 ++ .../src/actions/avaliableMediatorServersActions.ts | 58 + .../src/actions/mediatorConfigActions.ts | 154 ++ .../src/actions/mediatorServerActions.ts | 114 ++ .../src/assets/icons/mediatorAppIcon.svg | 49 + .../src/components/editMediatorConfigDialog.tsx | 399 +++++ .../src/components/editMediatorServerDialog.tsx | 221 +++ .../src/components/refreshMediatorDialog.tsx | 117 ++ .../src/components/showMeditaorInfoDialog.tsx | 107 ++ .../handlers/avaliableMediatorServersHandler.ts | 36 + .../src/handlers/mediatorAppRootHandler.ts | 43 + .../src/handlers/mediatorServerHandler.ts | 120 ++ .../wt-odlux/odlux/apps/mediatorApp/src/index.html | 29 + .../apps/mediatorApp/src/models/mediatorServer.ts | 77 + .../wt-odlux/odlux/apps/mediatorApp/src/plugin.tsx | 83 + .../mediatorApp/src/services/mediatorService.ts | 204 +++ .../mediatorApp/src/views/mediatorApplication.tsx | 291 ++++ .../src/views/mediatorServerSelection.tsx | 193 +++ sdnr/wt-odlux/odlux/apps/mediatorApp/tsconfig.json | 38 + sdnr/wt-odlux/odlux/apps/mediatorApp/tslint.json | 4 + .../odlux/apps/mediatorApp/webpack.config.js | 158 ++ sdnr/wt-odlux/odlux/apps/minimumApp/.babelrc | 17 + sdnr/wt-odlux/odlux/apps/minimumApp/package.json | 43 + sdnr/wt-odlux/odlux/apps/minimumApp/pom.xml | 109 ++ .../minimumApp/src/assets/icons/minimumAppIcon.svg | 27 + .../src/handlers/minimumAppRootHandler.ts | 38 + sdnr/wt-odlux/odlux/apps/minimumApp/src/index.html | 24 + sdnr/wt-odlux/odlux/apps/minimumApp/src/plugin.tsx | 48 + sdnr/wt-odlux/odlux/apps/minimumApp/tsconfig.json | 37 + .../odlux/apps/minimumApp/webpack.config.js | 136 ++ .../odlux/apps/performanceHistoryApp/.babelrc | 17 + .../odlux/apps/performanceHistoryApp/package.json | 46 + .../odlux/apps/performanceHistoryApp/pom.xml | 105 ++ .../src/actions/deviceListActions.ts | 79 + .../performanceHistoryApp/src/actions/ltpAction.ts | 94 ++ .../src/actions/panelChangeActions.ts | 32 + .../src/actions/reloadAction.ts | 25 + .../src/actions/timeChangeAction.ts | 30 + .../src/actions/toggleActions.ts | 36 + .../src/assets/icons/performanceHistoryAppIcon.svg | 50 + .../src/components/adaptiveModulation.tsx | 489 ++++++ .../src/components/chartFilter.tsx | 75 + .../src/components/crossPolarDiscrimination.tsx | 155 ++ .../src/components/ltpSelection.tsx | 105 ++ .../src/components/performanceData.tsx | 150 ++ .../src/components/receiveLevel.tsx | 154 ++ .../src/components/signalToInterference.tsx | 156 ++ .../src/components/temperature.tsx | 156 ++ .../src/components/toggleContainer.tsx | 102 ++ .../src/components/transmissionPower.tsx | 158 ++ .../src/handlers/adaptiveModulationHandler.ts | 37 + .../src/handlers/availableLtpsActionHandler.ts | 99 ++ .../handlers/crossPolarDiscriminationHandler.ts | 38 + .../src/handlers/deviceListActionHandler.ts | 56 + .../src/handlers/performanceDataHandler.ts | 39 + .../src/handlers/performanceHistoryRootHandler.ts | 181 +++ .../src/handlers/receiveLevelHandler.ts | 38 + .../src/handlers/signalToInterferenceHandler.ts | 38 + .../src/handlers/temperatureHandler.ts | 38 + .../src/handlers/transmissionPowerHandler.ts | 38 + .../apps/performanceHistoryApp/src/index.html | 26 + .../src/models/adaptiveModulationDataType.ts | 109 ++ .../src/models/availableLtps.ts | 21 + .../performanceHistoryApp/src/models/chartTypes.ts | 49 + .../src/models/crossPolarDiscriminationDataType.ts | 44 + .../src/models/deviceListType.ts | 25 + .../performanceHistoryApp/src/models/panelId.ts | 22 + .../src/models/performanceDataType.ts | 53 + .../src/models/receiveLevelDataType.ts | 43 + .../src/models/signalToInteferenceDataType.ts | 44 + .../src/models/temperatureDataType.ts | 45 + .../src/models/toggleDataType.ts | 25 + .../src/models/topologyNetconf.ts | 26 + .../src/models/transmissionPowerDataType.ts | 44 + .../src/pluginPerformance.tsx | 147 ++ .../src/services/performanceHistoryService.ts | 107 ++ .../performanceHistoryApp/src/utils/chartUtils.tsx | 77 + .../performanceHistoryApp/src/utils/tableUtils.ts | 36 + .../src/views/performanceHistoryApplication.tsx | 456 ++++++ .../odlux/apps/performanceHistoryApp/tsconfig.json | 37 + .../apps/performanceHistoryApp/webpack.config.js | 167 ++ 283 files changed, 26256 insertions(+) create mode 100644 sdnr/wt-odlux/odlux/apps/apiDemo/.babelrc create mode 100644 sdnr/wt-odlux/odlux/apps/apiDemo/package.json create mode 100644 sdnr/wt-odlux/odlux/apps/apiDemo/pom.xml create mode 100644 sdnr/wt-odlux/odlux/apps/apiDemo/src/actions/modulesSuccess.ts create mode 100644 sdnr/wt-odlux/odlux/apps/apiDemo/src/handlers/apiDemoRootHandler.ts create mode 100644 sdnr/wt-odlux/odlux/apps/apiDemo/src/handlers/modulesHandler.ts create mode 100644 sdnr/wt-odlux/odlux/apps/apiDemo/src/index.html create mode 100644 sdnr/wt-odlux/odlux/apps/apiDemo/src/models/module.ts create mode 100644 sdnr/wt-odlux/odlux/apps/apiDemo/src/plugin.tsx create mode 100644 sdnr/wt-odlux/odlux/apps/apiDemo/tsconfig.json create mode 100644 sdnr/wt-odlux/odlux/apps/apiDemo/webpack.config.js create mode 100755 sdnr/wt-odlux/odlux/apps/app-installer/pom.xml create mode 100644 sdnr/wt-odlux/odlux/apps/app-installer/src/assembly/assemble_mvnrepo_zip.xml create mode 100644 sdnr/wt-odlux/odlux/apps/configurationApp/.babelrc create mode 100644 sdnr/wt-odlux/odlux/apps/configurationApp/package.json create mode 100644 sdnr/wt-odlux/odlux/apps/configurationApp/policies.json create mode 100644 sdnr/wt-odlux/odlux/apps/configurationApp/pom.xml create mode 100644 sdnr/wt-odlux/odlux/apps/configurationApp/src/actions/deviceActions.ts create mode 100644 sdnr/wt-odlux/odlux/apps/configurationApp/src/assets/icons/configurationAppIcon.svg create mode 100644 sdnr/wt-odlux/odlux/apps/configurationApp/src/components/baseProps.ts create mode 100644 sdnr/wt-odlux/odlux/apps/configurationApp/src/components/ifWhenTextInput.tsx create mode 100644 sdnr/wt-odlux/odlux/apps/configurationApp/src/components/uiElementBoolean.tsx create mode 100644 sdnr/wt-odlux/odlux/apps/configurationApp/src/components/uiElementLeafList.tsx create mode 100644 sdnr/wt-odlux/odlux/apps/configurationApp/src/components/uiElementNumber.tsx create mode 100644 sdnr/wt-odlux/odlux/apps/configurationApp/src/components/uiElementReference.tsx create mode 100644 sdnr/wt-odlux/odlux/apps/configurationApp/src/components/uiElementSelection.tsx create mode 100644 sdnr/wt-odlux/odlux/apps/configurationApp/src/components/uiElementString.tsx create mode 100644 sdnr/wt-odlux/odlux/apps/configurationApp/src/components/uiElementUnion.tsx create mode 100644 sdnr/wt-odlux/odlux/apps/configurationApp/src/handlers/configurationAppRootHandler.ts create mode 100644 sdnr/wt-odlux/odlux/apps/configurationApp/src/handlers/connectedNetworkElementsHandler.ts create mode 100644 sdnr/wt-odlux/odlux/apps/configurationApp/src/handlers/deviceDescriptionHandler.ts create mode 100644 sdnr/wt-odlux/odlux/apps/configurationApp/src/handlers/valueSelectorHandler.ts create mode 100644 sdnr/wt-odlux/odlux/apps/configurationApp/src/handlers/viewDescriptionHandler.ts create mode 100644 sdnr/wt-odlux/odlux/apps/configurationApp/src/index.html create mode 100644 sdnr/wt-odlux/odlux/apps/configurationApp/src/models/networkElementConnection.ts create mode 100644 sdnr/wt-odlux/odlux/apps/configurationApp/src/models/uiModels.ts create mode 100644 sdnr/wt-odlux/odlux/apps/configurationApp/src/models/yang.ts create mode 100644 sdnr/wt-odlux/odlux/apps/configurationApp/src/pluginConfiguration.tsx create mode 100644 sdnr/wt-odlux/odlux/apps/configurationApp/src/services/restServices.ts create mode 100644 sdnr/wt-odlux/odlux/apps/configurationApp/src/services/yangService.ts create mode 100644 sdnr/wt-odlux/odlux/apps/configurationApp/src/utilities/verifyer.ts create mode 100644 sdnr/wt-odlux/odlux/apps/configurationApp/src/utilities/viewEngineHelper.ts create mode 100644 sdnr/wt-odlux/odlux/apps/configurationApp/src/views/configurationApplication.tsx create mode 100644 sdnr/wt-odlux/odlux/apps/configurationApp/src/views/networkElementSelector.tsx create mode 100644 sdnr/wt-odlux/odlux/apps/configurationApp/src/yang/whenParser.ts create mode 100644 sdnr/wt-odlux/odlux/apps/configurationApp/src/yang/yangParser.ts create mode 100644 sdnr/wt-odlux/odlux/apps/configurationApp/tsconfig.json create mode 100644 sdnr/wt-odlux/odlux/apps/configurationApp/webpack.config.js create mode 100644 sdnr/wt-odlux/odlux/apps/connectApp/.babelrc create mode 100644 sdnr/wt-odlux/odlux/apps/connectApp/package.json create mode 100644 sdnr/wt-odlux/odlux/apps/connectApp/policies.json create mode 100644 sdnr/wt-odlux/odlux/apps/connectApp/pom.xml create mode 100644 sdnr/wt-odlux/odlux/apps/connectApp/src/actions/commonNetworkElementsActions.ts create mode 100644 sdnr/wt-odlux/odlux/apps/connectApp/src/actions/infoNetworkElementActions.ts create mode 100644 sdnr/wt-odlux/odlux/apps/connectApp/src/actions/mountedNetworkElementsActions.ts create mode 100644 sdnr/wt-odlux/odlux/apps/connectApp/src/actions/networkElementsActions.ts create mode 100644 sdnr/wt-odlux/odlux/apps/connectApp/src/actions/tlsKeyActions.ts create mode 100644 sdnr/wt-odlux/odlux/apps/connectApp/src/assets/icons/connectAppIcon.svg create mode 100644 sdnr/wt-odlux/odlux/apps/connectApp/src/components/connectionStatusLog.tsx create mode 100644 sdnr/wt-odlux/odlux/apps/connectApp/src/components/editNetworkElementDialog.tsx create mode 100644 sdnr/wt-odlux/odlux/apps/connectApp/src/components/infoNetworkElementDialog.tsx create mode 100644 sdnr/wt-odlux/odlux/apps/connectApp/src/components/networkElements.tsx create mode 100644 sdnr/wt-odlux/odlux/apps/connectApp/src/components/refreshConnectionStatusLogDialog.tsx create mode 100644 sdnr/wt-odlux/odlux/apps/connectApp/src/components/refreshNetworkElementsDialog.tsx create mode 100644 sdnr/wt-odlux/odlux/apps/connectApp/src/handlers/connectAppRootHandler.ts create mode 100644 sdnr/wt-odlux/odlux/apps/connectApp/src/handlers/connectionStatusLogHandler.ts create mode 100644 sdnr/wt-odlux/odlux/apps/connectApp/src/handlers/infoNetworkElementHandler.ts create mode 100644 sdnr/wt-odlux/odlux/apps/connectApp/src/handlers/networkElementsHandler.ts create mode 100644 sdnr/wt-odlux/odlux/apps/connectApp/src/handlers/tlsKeyHandler.ts create mode 100644 sdnr/wt-odlux/odlux/apps/connectApp/src/index.html create mode 100644 sdnr/wt-odlux/odlux/apps/connectApp/src/models/connectionStatusLog.ts create mode 100644 sdnr/wt-odlux/odlux/apps/connectApp/src/models/guiCutTrough.ts create mode 100644 sdnr/wt-odlux/odlux/apps/connectApp/src/models/networkElementBase.ts create mode 100644 sdnr/wt-odlux/odlux/apps/connectApp/src/models/networkElementConnection.ts create mode 100644 sdnr/wt-odlux/odlux/apps/connectApp/src/models/networkElementConnectionLog.ts create mode 100644 sdnr/wt-odlux/odlux/apps/connectApp/src/models/panelId.ts create mode 100644 sdnr/wt-odlux/odlux/apps/connectApp/src/models/topologyNetconf.ts create mode 100644 sdnr/wt-odlux/odlux/apps/connectApp/src/models/yangCapabilitiesType.ts create mode 100644 sdnr/wt-odlux/odlux/apps/connectApp/src/pluginConnect.tsx create mode 100644 sdnr/wt-odlux/odlux/apps/connectApp/src/services/connectService.ts create mode 100644 sdnr/wt-odlux/odlux/apps/connectApp/src/views/connectView.tsx create mode 100644 sdnr/wt-odlux/odlux/apps/connectApp/tsconfig.json create mode 100644 sdnr/wt-odlux/odlux/apps/connectApp/webpack.config.js create mode 100644 sdnr/wt-odlux/odlux/apps/demoApp/.babelrc create mode 100644 sdnr/wt-odlux/odlux/apps/demoApp/package.json create mode 100644 sdnr/wt-odlux/odlux/apps/demoApp/pom.xml create mode 100644 sdnr/wt-odlux/odlux/apps/demoApp/src/actions/authorActions.ts create mode 100644 sdnr/wt-odlux/odlux/apps/demoApp/src/components/counter.tsx create mode 100644 sdnr/wt-odlux/odlux/apps/demoApp/src/handlers/demoAppRootHandler.ts create mode 100644 sdnr/wt-odlux/odlux/apps/demoApp/src/handlers/editAuthorHandler.ts create mode 100644 sdnr/wt-odlux/odlux/apps/demoApp/src/handlers/listAuthorsHandler.ts create mode 100644 sdnr/wt-odlux/odlux/apps/demoApp/src/index.html create mode 100644 sdnr/wt-odlux/odlux/apps/demoApp/src/models/author.ts create mode 100644 sdnr/wt-odlux/odlux/apps/demoApp/src/plugin.tsx create mode 100644 sdnr/wt-odlux/odlux/apps/demoApp/src/services/authorService.ts create mode 100644 sdnr/wt-odlux/odlux/apps/demoApp/src/views/authorsList.tsx create mode 100644 sdnr/wt-odlux/odlux/apps/demoApp/src/views/editAuthor.tsx create mode 100644 sdnr/wt-odlux/odlux/apps/demoApp/tsconfig.json create mode 100644 sdnr/wt-odlux/odlux/apps/demoApp/webpack.config.js create mode 100644 sdnr/wt-odlux/odlux/apps/eventLogApp/.babelrc create mode 100644 sdnr/wt-odlux/odlux/apps/eventLogApp/package.json create mode 100644 sdnr/wt-odlux/odlux/apps/eventLogApp/pom.xml create mode 100644 sdnr/wt-odlux/odlux/apps/eventLogApp/src/assets/icons/eventLogAppIcon.svg create mode 100644 sdnr/wt-odlux/odlux/apps/eventLogApp/src/components/refreshEventLogDialog.tsx create mode 100644 sdnr/wt-odlux/odlux/apps/eventLogApp/src/handlers/eventLogAppRootHandler.ts create mode 100644 sdnr/wt-odlux/odlux/apps/eventLogApp/src/handlers/eventLogHandler.tsx create mode 100644 sdnr/wt-odlux/odlux/apps/eventLogApp/src/index.html create mode 100644 sdnr/wt-odlux/odlux/apps/eventLogApp/src/models/eventLogType.ts create mode 100644 sdnr/wt-odlux/odlux/apps/eventLogApp/src/pluginEventLog.tsx create mode 100644 sdnr/wt-odlux/odlux/apps/eventLogApp/src/views/eventLog.tsx create mode 100644 sdnr/wt-odlux/odlux/apps/eventLogApp/tsconfig.json create mode 100644 sdnr/wt-odlux/odlux/apps/eventLogApp/webpack.config.js create mode 100644 sdnr/wt-odlux/odlux/apps/faultApp/.babelrc create mode 100644 sdnr/wt-odlux/odlux/apps/faultApp/package.json create mode 100644 sdnr/wt-odlux/odlux/apps/faultApp/pom.xml create mode 100644 sdnr/wt-odlux/odlux/apps/faultApp/src/actions/clearStuckAlarmsAction.ts create mode 100644 sdnr/wt-odlux/odlux/apps/faultApp/src/actions/notificationActions.ts create mode 100644 sdnr/wt-odlux/odlux/apps/faultApp/src/actions/panelChangeActions.ts create mode 100644 sdnr/wt-odlux/odlux/apps/faultApp/src/actions/statusActions.ts create mode 100644 sdnr/wt-odlux/odlux/apps/faultApp/src/assets/icons/faultAppIcon.svg create mode 100644 sdnr/wt-odlux/odlux/apps/faultApp/src/components/clearStuckAlarmsDialog.tsx create mode 100644 sdnr/wt-odlux/odlux/apps/faultApp/src/components/dashboardHome.tsx create mode 100644 sdnr/wt-odlux/odlux/apps/faultApp/src/components/faultStatus.tsx create mode 100644 sdnr/wt-odlux/odlux/apps/faultApp/src/components/refreshAlarmLogDialog.tsx create mode 100644 sdnr/wt-odlux/odlux/apps/faultApp/src/components/refreshCurrentAlarmsDialog.tsx create mode 100644 sdnr/wt-odlux/odlux/apps/faultApp/src/handlers/alarmLogEntriesHandler.ts create mode 100644 sdnr/wt-odlux/odlux/apps/faultApp/src/handlers/clearStuckAlarmsHandler.ts create mode 100644 sdnr/wt-odlux/odlux/apps/faultApp/src/handlers/currentAlarmsHandler.ts create mode 100644 sdnr/wt-odlux/odlux/apps/faultApp/src/handlers/faultAppRootHandler.ts create mode 100644 sdnr/wt-odlux/odlux/apps/faultApp/src/handlers/faultStatusHandler.ts create mode 100644 sdnr/wt-odlux/odlux/apps/faultApp/src/handlers/notificationsHandler.ts create mode 100644 sdnr/wt-odlux/odlux/apps/faultApp/src/index.html create mode 100644 sdnr/wt-odlux/odlux/apps/faultApp/src/models/fault.ts create mode 100644 sdnr/wt-odlux/odlux/apps/faultApp/src/models/panelId.ts create mode 100644 sdnr/wt-odlux/odlux/apps/faultApp/src/pluginFault.tsx create mode 100644 sdnr/wt-odlux/odlux/apps/faultApp/src/services/faultStatusService.ts create mode 100644 sdnr/wt-odlux/odlux/apps/faultApp/src/views/faultApplication.tsx create mode 100644 sdnr/wt-odlux/odlux/apps/faultApp/tsconfig.json create mode 100644 sdnr/wt-odlux/odlux/apps/faultApp/webpack.config.js create mode 100644 sdnr/wt-odlux/odlux/apps/helpApp/.babelrc create mode 100644 sdnr/wt-odlux/odlux/apps/helpApp/package.json create mode 100644 sdnr/wt-odlux/odlux/apps/helpApp/pom.xml create mode 100644 sdnr/wt-odlux/odlux/apps/helpApp/src/actions/helpActions.ts create mode 100644 sdnr/wt-odlux/odlux/apps/helpApp/src/assets/icons/helpAppIcon.svg create mode 100644 sdnr/wt-odlux/odlux/apps/helpApp/src/components/helpStatus.tsx create mode 100644 sdnr/wt-odlux/odlux/apps/helpApp/src/components/markdown.tsx create mode 100644 sdnr/wt-odlux/odlux/apps/helpApp/src/components/tocEntry.tsx create mode 100644 sdnr/wt-odlux/odlux/apps/helpApp/src/handlers/helpAppRootHandler.ts create mode 100644 sdnr/wt-odlux/odlux/apps/helpApp/src/index.html create mode 100644 sdnr/wt-odlux/odlux/apps/helpApp/src/models/tocNode.ts create mode 100644 sdnr/wt-odlux/odlux/apps/helpApp/src/plugin.tsx create mode 100644 sdnr/wt-odlux/odlux/apps/helpApp/src/services/helpService.ts create mode 100644 sdnr/wt-odlux/odlux/apps/helpApp/src/utilities/path.ts create mode 100644 sdnr/wt-odlux/odlux/apps/helpApp/src/views/helpApplication.tsx create mode 100644 sdnr/wt-odlux/odlux/apps/helpApp/src/views/helpTocApp.tsx create mode 100644 sdnr/wt-odlux/odlux/apps/helpApp/tsconfig.json create mode 100644 sdnr/wt-odlux/odlux/apps/helpApp/webpack.config.js create mode 100644 sdnr/wt-odlux/odlux/apps/inventoryApp/.babelrc create mode 100644 sdnr/wt-odlux/odlux/apps/inventoryApp/package.json create mode 100644 sdnr/wt-odlux/odlux/apps/inventoryApp/pom.xml create mode 100644 sdnr/wt-odlux/odlux/apps/inventoryApp/src/actions/inventoryDeviceListActions.ts create mode 100644 sdnr/wt-odlux/odlux/apps/inventoryApp/src/actions/inventoryTreeActions.ts create mode 100644 sdnr/wt-odlux/odlux/apps/inventoryApp/src/actions/panelActions.ts create mode 100644 sdnr/wt-odlux/odlux/apps/inventoryApp/src/assets/icons/inventoryAppIcon.svg create mode 100644 sdnr/wt-odlux/odlux/apps/inventoryApp/src/components/refreshInventoryDialog.tsx create mode 100644 sdnr/wt-odlux/odlux/apps/inventoryApp/src/fakeData/index.ts create mode 100644 sdnr/wt-odlux/odlux/apps/inventoryApp/src/handlers/inventoryAppRootHandler.ts create mode 100644 sdnr/wt-odlux/odlux/apps/inventoryApp/src/handlers/inventoryDeviceListActionHandler.ts create mode 100644 sdnr/wt-odlux/odlux/apps/inventoryApp/src/handlers/inventoryElementsHandler.ts create mode 100644 sdnr/wt-odlux/odlux/apps/inventoryApp/src/handlers/inventoryTreeHandler.ts create mode 100644 sdnr/wt-odlux/odlux/apps/inventoryApp/src/handlers/panelHandler.ts create mode 100644 sdnr/wt-odlux/odlux/apps/inventoryApp/src/index.html create mode 100644 sdnr/wt-odlux/odlux/apps/inventoryApp/src/models/inventory.ts create mode 100644 sdnr/wt-odlux/odlux/apps/inventoryApp/src/models/inventoryDeviceListType.ts create mode 100644 sdnr/wt-odlux/odlux/apps/inventoryApp/src/models/networkElementConnection.ts create mode 100644 sdnr/wt-odlux/odlux/apps/inventoryApp/src/models/panelId.ts create mode 100644 sdnr/wt-odlux/odlux/apps/inventoryApp/src/pluginInventory.tsx create mode 100644 sdnr/wt-odlux/odlux/apps/inventoryApp/src/services/inventoryService.ts create mode 100644 sdnr/wt-odlux/odlux/apps/inventoryApp/src/views/dashboard.tsx create mode 100644 sdnr/wt-odlux/odlux/apps/inventoryApp/src/views/detail.tsx create mode 100644 sdnr/wt-odlux/odlux/apps/inventoryApp/src/views/treeview.tsx create mode 100644 sdnr/wt-odlux/odlux/apps/inventoryApp/tsconfig.json create mode 100644 sdnr/wt-odlux/odlux/apps/inventoryApp/webpack.config.js create mode 100644 sdnr/wt-odlux/odlux/apps/maintenanceApp/.babelrc create mode 100644 sdnr/wt-odlux/odlux/apps/maintenanceApp/package.json create mode 100644 sdnr/wt-odlux/odlux/apps/maintenanceApp/pom.xml create mode 100644 sdnr/wt-odlux/odlux/apps/maintenanceApp/src/actions/maintenenceActions.ts create mode 100644 sdnr/wt-odlux/odlux/apps/maintenanceApp/src/assets/icons/maintenanceAppIcon.svg create mode 100644 sdnr/wt-odlux/odlux/apps/maintenanceApp/src/components/editMaintenenceEntryDialog.tsx create mode 100644 sdnr/wt-odlux/odlux/apps/maintenanceApp/src/components/refreshMaintenanceEntries.tsx create mode 100644 sdnr/wt-odlux/odlux/apps/maintenanceApp/src/handlers/maintenanceAppRootHandler.ts create mode 100644 sdnr/wt-odlux/odlux/apps/maintenanceApp/src/handlers/maintenanceEntriesHandler.ts create mode 100644 sdnr/wt-odlux/odlux/apps/maintenanceApp/src/index.html create mode 100644 sdnr/wt-odlux/odlux/apps/maintenanceApp/src/models/maintenanceEntryType.ts create mode 100644 sdnr/wt-odlux/odlux/apps/maintenanceApp/src/pluginMaintenance.tsx create mode 100644 sdnr/wt-odlux/odlux/apps/maintenanceApp/src/services/maintenenceService.ts create mode 100644 sdnr/wt-odlux/odlux/apps/maintenanceApp/src/utils/timeUtils.ts create mode 100644 sdnr/wt-odlux/odlux/apps/maintenanceApp/src/views/maintenanceView.tsx create mode 100644 sdnr/wt-odlux/odlux/apps/maintenanceApp/tsconfig.json create mode 100644 sdnr/wt-odlux/odlux/apps/maintenanceApp/webpack.config.js create mode 100644 sdnr/wt-odlux/odlux/apps/mediatorApp/.babelrc create mode 100644 sdnr/wt-odlux/odlux/apps/mediatorApp/package.json create mode 100644 sdnr/wt-odlux/odlux/apps/mediatorApp/pom.xml create mode 100644 sdnr/wt-odlux/odlux/apps/mediatorApp/src/actions/avaliableMediatorServersActions.ts create mode 100644 sdnr/wt-odlux/odlux/apps/mediatorApp/src/actions/mediatorConfigActions.ts create mode 100644 sdnr/wt-odlux/odlux/apps/mediatorApp/src/actions/mediatorServerActions.ts create mode 100644 sdnr/wt-odlux/odlux/apps/mediatorApp/src/assets/icons/mediatorAppIcon.svg create mode 100644 sdnr/wt-odlux/odlux/apps/mediatorApp/src/components/editMediatorConfigDialog.tsx create mode 100644 sdnr/wt-odlux/odlux/apps/mediatorApp/src/components/editMediatorServerDialog.tsx create mode 100644 sdnr/wt-odlux/odlux/apps/mediatorApp/src/components/refreshMediatorDialog.tsx create mode 100644 sdnr/wt-odlux/odlux/apps/mediatorApp/src/components/showMeditaorInfoDialog.tsx create mode 100644 sdnr/wt-odlux/odlux/apps/mediatorApp/src/handlers/avaliableMediatorServersHandler.ts create mode 100644 sdnr/wt-odlux/odlux/apps/mediatorApp/src/handlers/mediatorAppRootHandler.ts create mode 100644 sdnr/wt-odlux/odlux/apps/mediatorApp/src/handlers/mediatorServerHandler.ts create mode 100644 sdnr/wt-odlux/odlux/apps/mediatorApp/src/index.html create mode 100644 sdnr/wt-odlux/odlux/apps/mediatorApp/src/models/mediatorServer.ts create mode 100644 sdnr/wt-odlux/odlux/apps/mediatorApp/src/plugin.tsx create mode 100644 sdnr/wt-odlux/odlux/apps/mediatorApp/src/services/mediatorService.ts create mode 100644 sdnr/wt-odlux/odlux/apps/mediatorApp/src/views/mediatorApplication.tsx create mode 100644 sdnr/wt-odlux/odlux/apps/mediatorApp/src/views/mediatorServerSelection.tsx create mode 100644 sdnr/wt-odlux/odlux/apps/mediatorApp/tsconfig.json create mode 100644 sdnr/wt-odlux/odlux/apps/mediatorApp/tslint.json create mode 100644 sdnr/wt-odlux/odlux/apps/mediatorApp/webpack.config.js create mode 100644 sdnr/wt-odlux/odlux/apps/minimumApp/.babelrc create mode 100644 sdnr/wt-odlux/odlux/apps/minimumApp/package.json create mode 100644 sdnr/wt-odlux/odlux/apps/minimumApp/pom.xml create mode 100644 sdnr/wt-odlux/odlux/apps/minimumApp/src/assets/icons/minimumAppIcon.svg create mode 100644 sdnr/wt-odlux/odlux/apps/minimumApp/src/handlers/minimumAppRootHandler.ts create mode 100644 sdnr/wt-odlux/odlux/apps/minimumApp/src/index.html create mode 100644 sdnr/wt-odlux/odlux/apps/minimumApp/src/plugin.tsx create mode 100644 sdnr/wt-odlux/odlux/apps/minimumApp/tsconfig.json create mode 100644 sdnr/wt-odlux/odlux/apps/minimumApp/webpack.config.js create mode 100644 sdnr/wt-odlux/odlux/apps/performanceHistoryApp/.babelrc create mode 100644 sdnr/wt-odlux/odlux/apps/performanceHistoryApp/package.json create mode 100644 sdnr/wt-odlux/odlux/apps/performanceHistoryApp/pom.xml create mode 100644 sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/actions/deviceListActions.ts create mode 100644 sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/actions/ltpAction.ts create mode 100644 sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/actions/panelChangeActions.ts create mode 100644 sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/actions/reloadAction.ts create mode 100644 sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/actions/timeChangeAction.ts create mode 100644 sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/actions/toggleActions.ts create mode 100644 sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/assets/icons/performanceHistoryAppIcon.svg create mode 100644 sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/components/adaptiveModulation.tsx create mode 100644 sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/components/chartFilter.tsx create mode 100644 sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/components/crossPolarDiscrimination.tsx create mode 100644 sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/components/ltpSelection.tsx create mode 100644 sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/components/performanceData.tsx create mode 100644 sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/components/receiveLevel.tsx create mode 100644 sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/components/signalToInterference.tsx create mode 100644 sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/components/temperature.tsx create mode 100644 sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/components/toggleContainer.tsx create mode 100644 sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/components/transmissionPower.tsx create mode 100644 sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/handlers/adaptiveModulationHandler.ts create mode 100644 sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/handlers/availableLtpsActionHandler.ts create mode 100644 sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/handlers/crossPolarDiscriminationHandler.ts create mode 100644 sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/handlers/deviceListActionHandler.ts create mode 100644 sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/handlers/performanceDataHandler.ts create mode 100644 sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/handlers/performanceHistoryRootHandler.ts create mode 100644 sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/handlers/receiveLevelHandler.ts create mode 100644 sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/handlers/signalToInterferenceHandler.ts create mode 100644 sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/handlers/temperatureHandler.ts create mode 100644 sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/handlers/transmissionPowerHandler.ts create mode 100644 sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/index.html create mode 100644 sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/models/adaptiveModulationDataType.ts create mode 100644 sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/models/availableLtps.ts create mode 100644 sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/models/chartTypes.ts create mode 100644 sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/models/crossPolarDiscriminationDataType.ts create mode 100644 sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/models/deviceListType.ts create mode 100644 sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/models/panelId.ts create mode 100644 sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/models/performanceDataType.ts create mode 100644 sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/models/receiveLevelDataType.ts create mode 100644 sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/models/signalToInteferenceDataType.ts create mode 100644 sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/models/temperatureDataType.ts create mode 100644 sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/models/toggleDataType.ts create mode 100644 sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/models/topologyNetconf.ts create mode 100644 sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/models/transmissionPowerDataType.ts create mode 100644 sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/pluginPerformance.tsx create mode 100644 sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/services/performanceHistoryService.ts create mode 100644 sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/utils/chartUtils.tsx create mode 100644 sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/utils/tableUtils.ts create mode 100644 sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/views/performanceHistoryApplication.tsx create mode 100644 sdnr/wt-odlux/odlux/apps/performanceHistoryApp/tsconfig.json create mode 100644 sdnr/wt-odlux/odlux/apps/performanceHistoryApp/webpack.config.js (limited to 'sdnr/wt-odlux/odlux/apps') diff --git a/sdnr/wt-odlux/odlux/apps/apiDemo/.babelrc b/sdnr/wt-odlux/odlux/apps/apiDemo/.babelrc new file mode 100644 index 000000000..3d8cd1260 --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/apiDemo/.babelrc @@ -0,0 +1,17 @@ +{ + "presets": [ + ["@babel/preset-react"], + ["@babel/preset-env", { + "targets": { + "chrome": "66" + }, + "spec": true, + "loose": false, + "modules": false, + "debug": false, + "useBuiltIns": "usage", + "forceAllTransforms": true + }] + ], + "plugins": [] +} diff --git a/sdnr/wt-odlux/odlux/apps/apiDemo/package.json b/sdnr/wt-odlux/odlux/apps/apiDemo/package.json new file mode 100644 index 000000000..ff9e3c47e --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/apiDemo/package.json @@ -0,0 +1,46 @@ +{ + "name": "@odlux/api-demo", + "version": "0.1.0", + "description": "A react based modular UI framework", + "main": "index.js", + "scripts": { + "start": "webpack-dev-server --env debug", + "build": "webpack --env release --config webpack.config.js", + "build:dev": "webpack --env debug --config webpack.config.js" + }, + "repository": { + "type": "git", + "url": "https://git.mfico.de/highstreet-technologies/odlux.git" + }, + "keywords": [ + "reactjs", + "redux", + "ui", + "framework" + ], + "author": "Matthias Fischer", + "license": "Apache-2.0", + "dependencies": { + "@emotion/react": "^11.7.0", + "@emotion/styled": "^11.6.0", + "@fortawesome/fontawesome-svg-core": "1.2.35", + "@fortawesome/free-solid-svg-icons": "5.6.3", + "@fortawesome/react-fontawesome": "0.1.14", + "@mui/icons-material": "^5.2.0", + "@mui/material": "^5.2.2", + "@mui/styles": "^5.2.2", + "@odlux/framework": "*" + }, + "peerDependencies": { + "@types/classnames": "2.2.6", + "@types/flux": "3.1.8", + "@types/jquery": "3.3.10", + "@types/react": "17.0.37", + "@types/react-dom": "17.0.11", + "@types/react-router-dom": "5.1.7", + "jquery": "3.3.1", + "react": "17.0.2", + "react-dom": "17.0.2", + "react-router-dom": "5.2.0" + } +} diff --git a/sdnr/wt-odlux/odlux/apps/apiDemo/pom.xml b/sdnr/wt-odlux/odlux/apps/apiDemo/pom.xml new file mode 100644 index 000000000..c7088fa49 --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/apiDemo/pom.xml @@ -0,0 +1,105 @@ + + + + + 4.0.0 + + org.onap.ccsdk.features.sdnr.odlux + sdnr-odlux-app-apiDemo + 1.7.0-SNAPSHOT + jar + + SDNR ODLUX :: ${project.artifactId} + + + Apache License, Version 2.0 + http://www.apache.org/licenses/LICENSE-2.0 + + + + + + + dist + odlux + + + + + maven-clean-plugin + + + + dist + false + + + node + false + + + node_modules + false + + + ../node_modules + false + + + + bin + false + + + + + + de.jacks-it-lab + frontend-maven-plugin + 1.7.2 + + + install node and yarn + + install-node-and-yarn + + + initialize + + v16.17.0 + v1.22.19 + + + + yarn build + + yarn + + + run build + + + + + + + diff --git a/sdnr/wt-odlux/odlux/apps/apiDemo/src/actions/modulesSuccess.ts b/sdnr/wt-odlux/odlux/apps/apiDemo/src/actions/modulesSuccess.ts new file mode 100644 index 000000000..12fd3fcaf --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/apiDemo/src/actions/modulesSuccess.ts @@ -0,0 +1,25 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * 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. + * ============LICENSE_END========================================================================== + */ +import { Action } from '../../../../framework/src/flux/action'; +import { ModuleResult } from '../models/module'; +export class ModulesRequestSuccess extends Action { + constructor(public result: ModuleResult) { + super(); + } +} +// error will be handled by the framework \ No newline at end of file diff --git a/sdnr/wt-odlux/odlux/apps/apiDemo/src/handlers/apiDemoRootHandler.ts b/sdnr/wt-odlux/odlux/apps/apiDemo/src/handlers/apiDemoRootHandler.ts new file mode 100644 index 000000000..128a03286 --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/apiDemo/src/handlers/apiDemoRootHandler.ts @@ -0,0 +1,41 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * 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. + * ============LICENSE_END========================================================================== + */ + +import { combineActionHandler } from '../../../../framework/src/flux/middleware'; + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +import { IApplicationStoreState } from '../../../../framework/src/store/applicationStore'; + +import { moduleHandler, IModules } from './modulesHandler'; + +export interface IApiDemoStoreState { + modules: IModules; +} + +declare module '../../../../framework/src/store/applicationStore' { + interface IApplicationStoreState { + apiDemo: IApiDemoStoreState; + } +} + +const actionHandlers = { + modules: moduleHandler, +}; + +export const apiDemoRootHandler = combineActionHandler(actionHandlers); +export default apiDemoRootHandler; diff --git a/sdnr/wt-odlux/odlux/apps/apiDemo/src/handlers/modulesHandler.ts b/sdnr/wt-odlux/odlux/apps/apiDemo/src/handlers/modulesHandler.ts new file mode 100644 index 000000000..1984a2d10 --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/apiDemo/src/handlers/modulesHandler.ts @@ -0,0 +1,33 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * 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. + * ============LICENSE_END========================================================================== + */ +import { IActionHandler } from '../../../../framework/src/flux/action'; + +import { ModulesRequestSuccess } from '../actions/modulesSuccess'; +import { Module } from '../models/module'; + +export type IModules = Module[]; + +const modulesInit: IModules = []; + +export const moduleHandler: IActionHandler = (state = modulesInit, action) => { + if (action instanceof ModulesRequestSuccess) { + return action.result.modules.module; + } + + return state; +}; diff --git a/sdnr/wt-odlux/odlux/apps/apiDemo/src/index.html b/sdnr/wt-odlux/odlux/apps/apiDemo/src/index.html new file mode 100644 index 000000000..c01df6b13 --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/apiDemo/src/index.html @@ -0,0 +1,24 @@ + + + + + + + + + API Demo App + + + +
+ + + + + + \ No newline at end of file diff --git a/sdnr/wt-odlux/odlux/apps/apiDemo/src/models/module.ts b/sdnr/wt-odlux/odlux/apps/apiDemo/src/models/module.ts new file mode 100644 index 000000000..48772a785 --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/apiDemo/src/models/module.ts @@ -0,0 +1,28 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * 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. + * ============LICENSE_END========================================================================== + */ +export type Module = { + name: string; + revision: string; + namespace: string; +}; + +export type ModuleResult = { + modules: { + module: Module[]; + }; +}; \ No newline at end of file diff --git a/sdnr/wt-odlux/odlux/apps/apiDemo/src/plugin.tsx b/sdnr/wt-odlux/odlux/apps/apiDemo/src/plugin.tsx new file mode 100644 index 000000000..2f70d8e2d --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/apiDemo/src/plugin.tsx @@ -0,0 +1,53 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * 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. + * ============LICENSE_END========================================================================== + */ +import React from 'react'; +import { withRouter, RouteComponentProps } from 'react-router-dom'; + +import { faNewspaper } from '@fortawesome/free-solid-svg-icons/faNewspaper'; + +import applicationManager from '../../../framework/src/services/applicationManager'; +import { connect, Connect } from '../../../framework/src/flux/connect'; +import { ApiAction } from '../../../framework/src/middleware/api'; // for RestConf + +import { apiDemoRootHandler } from './handlers/apiDemoRootHandler'; +import { ModulesRequestSuccess } from './actions/modulesSuccess'; +import { Module } from './models/module'; + +type AppProps = RouteComponentProps & Connect & { modules: Module[]; requestModules: () => void }; + +const App = (props: AppProps ) => ( + <> + +
    { props.modules.map((mod, ind) => (
  • { mod.name }
  • )) }
+ +); + +const FinalApp = withRouter(connect((state) => ({ + modules: state.apiDemo.modules, +}), (dispatcher => ({ + requestModules: () => { dispatcher.dispatch(new ApiAction('restconf/modules', ModulesRequestSuccess, true)); }, +})))(App)); + +applicationManager.registerApplication({ + name: 'apiDemo', + icon: faNewspaper, + rootComponent: FinalApp, + rootActionHandler: apiDemoRootHandler, + menuEntry: 'API Demo', +}); + diff --git a/sdnr/wt-odlux/odlux/apps/apiDemo/tsconfig.json b/sdnr/wt-odlux/odlux/apps/apiDemo/tsconfig.json new file mode 100644 index 000000000..ca65092e0 --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/apiDemo/tsconfig.json @@ -0,0 +1,37 @@ +{ + "compilerOptions": { + "baseUrl": "./src", + "outDir": "./dist", + "sourceMap": true, + "forceConsistentCasingInFileNames": true, + "allowSyntheticDefaultImports": true, + "allowUnreachableCode": false, + "allowUnusedLabels": false, + "noFallthroughCasesInSwitch": true, + "noImplicitAny": true, + "noImplicitReturns": true, + "noImplicitThis": true, + "strictNullChecks": true, + "pretty": true, + "newLine": "LF", + "module": "es2015", + "target": "es2016", + "moduleResolution": "node", + "experimentalDecorators": true, + "jsx": "preserve", + "lib": [ + "dom", + "es2015", + "es2016" + ], + "types": [ + "prop-types", + "react", + "react-dom" + ] + }, + "exclude": [ + "dist", + "node_modules" + ] +} diff --git a/sdnr/wt-odlux/odlux/apps/apiDemo/webpack.config.js b/sdnr/wt-odlux/odlux/apps/apiDemo/webpack.config.js new file mode 100644 index 000000000..6564bef26 --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/apiDemo/webpack.config.js @@ -0,0 +1,139 @@ +/** + * Webpack 4 configuration file + * see https://webpack.js.org/configuration/ + * see https://webpack.js.org/configuration/dev-server/ + */ + +"use strict"; + +const path = require("path"); +const webpack = require("webpack"); +const CopyWebpackPlugin = require("copy-webpack-plugin"); +const TerserPlugin = require('terser-webpack-plugin'); + +// const __dirname = (path => path.replace(/^([a-z]\:)/, c => c.toUpperCase()))(process.__dirname()); + +module.exports = (env) => { + const distPath = path.resolve(__dirname, env === "release" ? "." : "../..", "dist"); + const frameworkPath = path.resolve(__dirname, env === "release" ? "../../framework" : "../..", "dist"); + return [{ + name: "App", + + mode: "none", //disable default behavior + + target: "web", + + context: path.resolve(__dirname, "src"), + + entry: { + apiDemo: ["./plugin.tsx"] + }, + + devtool: env === "release" ? false : "source-map", + + resolve: { + extensions: [".ts", ".tsx", ".js", ".jsx"] + }, + + output: { + path: distPath, + filename: "[name].js", + library: "[name]", + libraryTarget: "umd2", + chunkFilename: "[name].js" + }, + module: { + rules: [{ + test: /\.tsx?$/, + exclude: /node_modules/, + use: [{ + loader: "babel-loader" + }, { + loader: "ts-loader" + }] + }, { + test: /\.jsx?$/, + exclude: /node_modules/, + use: [{ + loader: "babel-loader" + }] + }] + }, + optimization: { + noEmitOnErrors: true, + namedModules: env !== "release", + minimize: env === "release", + minimizer: env !== "release" ? [] : [new TerserPlugin({ + terserOptions: { + warnings: false, // false, true, "verbose" + compress: { + drop_console: true, + drop_debugger: true, + } + } + })], + }, + plugins: [ + // new CopyWebpackPlugin([{ + // from: '../../../dist/**.*', + // to: path.resolve(__dirname, "dist") + // }]), + new webpack.DllReferencePlugin({ + context: path.resolve(__dirname, "../../framework/src"), + manifest: require(path.resolve(frameworkPath, "vendor-manifest.json")), + sourceType: "umd2" + }), + new webpack.DllReferencePlugin({ + context: path.resolve(__dirname, "../../framework/src"), + manifest: require(path.resolve(frameworkPath, "app-manifest.json")), + sourceType: "umd2" + }), + ...(env === "release" ? [ + new webpack.DefinePlugin({ + "process.env": { + NODE_ENV: "'production'", + VERSION: JSON.stringify(require("./package.json").version) + } + }) + ] : [ + new webpack.DefinePlugin({ + "process.env": { + NODE_ENV: "'development'", + VERSION: JSON.stringify(require("./package.json").version) + } + }), + new CopyWebpackPlugin([{ + from: 'index.html', + to: distPath + }]), + ]) + ], + + devServer: { + public: "http://localhost:3100", + contentBase: frameworkPath, + + compress: true, + headers: { + "Access-Control-Allow-Origin": "*" + }, + host: "0.0.0.0", + port: 3100, + disableHostCheck: true, + historyApiFallback: true, + inline: true, + hot: false, + quiet: false, + stats: { + colors: true + }, + proxy: { + "/restconf/**/*": { + target: "https://dlux.just-run.it", + secure: false, + changeOrigin: true + } + } + } + }]; +} diff --git a/sdnr/wt-odlux/odlux/apps/app-installer/pom.xml b/sdnr/wt-odlux/odlux/apps/app-installer/pom.xml new file mode 100755 index 000000000..978f1e81b --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/app-installer/pom.xml @@ -0,0 +1,157 @@ + + + + + 4.0.0 + + org.onap.ccsdk.features.sdnr.odlux + sdnr-odlux-apps-installer + 1.7.0-SNAPSHOT + pom + + SDNR ODLUX :: ${project.artifactId} + + + Apache License, Version 2.0 + http://www.apache.org/licenses/LICENSE-2.0 + + + + + sdnr-odlux-apps + false + + + + + ${project.groupId} + sdnr-odlux-app-apiDemo + ${project.version} + + + ${project.groupId} + sdnr-odlux-app-connectApp + ${project.version} + + + ${project.groupId} + sdnr-odlux-app-demoApp + ${project.version} + + + ${project.groupId} + sdnr-odlux-app-faultApp + ${project.version} + + + ${project.groupId} + sdnr-odlux-app-helpApp + ${project.version} + + + ${project.groupId} + sdnr-odlux-app-inventoryApp + ${project.version} + + + ${project.groupId} + sdnr-odlux-app-minimumApp + ${project.version} + + + + ${project.groupId} + sdnr-odlux-app-maintenanceApp + ${project.version} + + + ${project.groupId} + sdnr-odlux-app-performanceHistoryApp + ${project.version} + + + ${project.groupId} + sdnr-odlux-app-eventLogApp + ${project.version} + + + ${project.groupId} + sdnr-odlux-app-configurationApp + ${project.version} + + + + + + + + maven-assembly-plugin + + + maven-repo-zip + + single + + package + + true + stage/${application.name}-${project.version} + + src/assembly/assemble_mvnrepo_zip.xml + + true + + + + + + org.apache.maven.plugins + maven-dependency-plugin + + + copy-nested-dependencies + + copy-dependencies + + prepare-package + + true + ${project.build.directory}/assembly/system + false + true + true + true + false + false + + + + + + + + + diff --git a/sdnr/wt-odlux/odlux/apps/app-installer/src/assembly/assemble_mvnrepo_zip.xml b/sdnr/wt-odlux/odlux/apps/app-installer/src/assembly/assemble_mvnrepo_zip.xml new file mode 100644 index 000000000..dfe5060bf --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/app-installer/src/assembly/assemble_mvnrepo_zip.xml @@ -0,0 +1,47 @@ + + + + + + repo + + zip + + + + false + + + + target/assembly/ + . + + + + + + diff --git a/sdnr/wt-odlux/odlux/apps/configurationApp/.babelrc b/sdnr/wt-odlux/odlux/apps/configurationApp/.babelrc new file mode 100644 index 000000000..3d8cd1260 --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/configurationApp/.babelrc @@ -0,0 +1,17 @@ +{ + "presets": [ + ["@babel/preset-react"], + ["@babel/preset-env", { + "targets": { + "chrome": "66" + }, + "spec": true, + "loose": false, + "modules": false, + "debug": false, + "useBuiltIns": "usage", + "forceAllTransforms": true + }] + ], + "plugins": [] +} diff --git a/sdnr/wt-odlux/odlux/apps/configurationApp/package.json b/sdnr/wt-odlux/odlux/apps/configurationApp/package.json new file mode 100644 index 000000000..b1d7d95d5 --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/configurationApp/package.json @@ -0,0 +1,47 @@ +{ + "name": "@odlux/configuration-app", + "version": "0.1.0", + "description": "A react based modular UI for the configuration app.", + "main": "index.js", + "scripts": { + "start": "webpack-dev-server --env debug", + "build": "webpack --env release --config webpack.config.js", + "build:dev": "webpack --env debug --config webpack.config.js" + }, + "repository": { + "type": "git", + "url": "https://git.mfico.de/highstreet-technologies/odlux.git" + }, + "keywords": [ + "reactjs", + "redux", + "ui", + "framework" + ], + "author": "Matthias Fischer", + "license": "Apache-2.0", + "dependencies": { + "@emotion/react": "^11.7.0", + "@emotion/styled": "^11.6.0", + "@mui/icons-material": "^5.2.0", + "@mui/material": "^5.2.2", + "@mui/styles": "^5.2.2", + "@odlux/framework": "*", + "@fortawesome/fontawesome-svg-core": "1.2.35", + "@fortawesome/free-solid-svg-icons": "5.6.3", + "@fortawesome/react-fontawesome": "0.1.14", + "material-ui-confirm": "3.0.2" + }, + "peerDependencies": { + "@types/classnames": "2.2.6", + "@types/flux": "3.1.8", + "@types/jquery": "3.3.10", + "@types/react": "17.0.37", + "@types/react-dom": "17.0.11", + "@types/react-router-dom": "5.1.7", + "jquery": "3.3.1", + "react": "17.0.2", + "react-dom": "17.0.2", + "react-router-dom": "5.2.0" + } +} diff --git a/sdnr/wt-odlux/odlux/apps/configurationApp/policies.json b/sdnr/wt-odlux/odlux/apps/configurationApp/policies.json new file mode 100644 index 000000000..91a0abf96 --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/configurationApp/policies.json @@ -0,0 +1,12 @@ +[ + { + "path": "/rests/**/node=Sim2230**", + "methods": { + "get": false, + "post": false, + "put": false, + "delete": false, + "patch": false + } + } +] \ No newline at end of file diff --git a/sdnr/wt-odlux/odlux/apps/configurationApp/pom.xml b/sdnr/wt-odlux/odlux/apps/configurationApp/pom.xml new file mode 100644 index 000000000..ae60d2811 --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/configurationApp/pom.xml @@ -0,0 +1,109 @@ + + + + + 4.0.0 + + org.onap.ccsdk.features.sdnr.odlux + sdnr-odlux-app-configurationApp + 1.7.0-SNAPSHOT + jar + + SDNR ODLUX :: ${project.artifactId} + + + Apache License, Version 2.0 + http://www.apache.org/licenses/LICENSE-2.0 + + + + + true + + + + + + dist + odlux + + + + + maven-clean-plugin + + + + dist + false + + + node + false + + + node_modules + false + + + ../node_modules + false + + + + bin + false + + + + + + de.jacks-it-lab + frontend-maven-plugin + 1.7.2 + + + install node and yarn + + install-node-and-yarn + + + initialize + + v16.17.0 + v1.22.19 + + + + yarn build + + yarn + + + run build + + + + + + + diff --git a/sdnr/wt-odlux/odlux/apps/configurationApp/src/actions/deviceActions.ts b/sdnr/wt-odlux/odlux/apps/configurationApp/src/actions/deviceActions.ts new file mode 100644 index 000000000..d8ec4bfd9 --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/configurationApp/src/actions/deviceActions.ts @@ -0,0 +1,626 @@ +import { Action } from '../../../../framework/src/flux/action'; +import { Dispatch } from '../../../../framework/src/flux/store'; +import { IApplicationStoreState } from '../../../../framework/src/store/applicationStore'; +import { PushAction, ReplaceAction } from '../../../../framework/src/actions/navigationActions'; +import { AddErrorInfoAction } from '../../../../framework/src/actions/errorActions'; + +import { DisplayModeType, DisplaySpecification } from '../handlers/viewDescriptionHandler'; + +import { restService } from '../services/restServices'; +import { YangParser } from '../yang/yangParser'; +import { Module } from '../models/yang'; +import { + ViewSpecification, + ViewElement, + isViewElementReference, + isViewElementList, + ViewElementString, +} from '../models/uiModels'; + +import { + checkResponseCode, + splitVPath, + filterViewElements, + flattenViewElements, + getReferencedDataList, + resolveViewDescription, +} from '../utilities/viewEngineHelper'; + +export class EnableValueSelector extends Action { + constructor(public listSpecification: ViewSpecification, public listData: any[], public keyProperty: string, public onValueSelected : (value: any) => void ) { + super(); + } +} + +export class SetCollectingSelectionData extends Action { + constructor(public busy: boolean) { + super(); + } +} + +export class SetSelectedValue extends Action { + constructor(public value: any) { + super(); + } +} + +export class UpdateDeviceDescription extends Action { + constructor( public nodeId: string, public modules: { [name:string]: Module }, public views: ViewSpecification[]) { + super(); + } +} + +export class UpdateViewDescription extends Action { + constructor(public vPath: string, public viewData: any, public displaySpecification: DisplaySpecification = { displayMode: DisplayModeType.doNotDisplay }) { + super(); + } +} + +export class UpdateOutputData extends Action { + constructor(public outputData: any) { + super(); + } +} + +export const updateNodeIdAsyncActionCreator = (nodeId: string) => async (dispatch: Dispatch, _getState: () => IApplicationStoreState ) => { + + dispatch(new UpdateDeviceDescription('', {}, [])); + dispatch(new SetCollectingSelectionData(true)); + + const { availableCapabilities, unavailableCapabilities, importOnlyModules } = await restService.getCapabilitiesByMountId(nodeId); + + if (!availableCapabilities || availableCapabilities.length <= 0) { + dispatch(new SetCollectingSelectionData(false)); + dispatch(new UpdateDeviceDescription(nodeId, {}, [])); + dispatch(new UpdateViewDescription('', [], { + displayMode: DisplayModeType.displayAsMessage, + renderMessage: `NetworkElement : "${nodeId}" has no capabilities.`, + })); + throw new Error(`NetworkElement : [${nodeId}] has no capabilities.`); + } + + const parser = new YangParser( + nodeId, + availableCapabilities.reduce((acc, cur) => { + acc[cur.capability] = cur.version; + return acc; + }, {} as { [key: string]: string }), + unavailableCapabilities || undefined, + importOnlyModules || undefined, + ); + + for (let i = 0; i < availableCapabilities.length; ++i) { + const capRaw = availableCapabilities[i]; + try { + await parser.addCapability(capRaw.capability, capRaw.version); + } catch (err) { + console.error(`Error in ${capRaw.capability} ${capRaw.version}`, err); + } + } + + parser.postProcess(); + + dispatch(new SetCollectingSelectionData(false)); + + if (process.env.NODE_ENV === 'development' ) { + console.log(parser, parser.modules, parser.views); + } + + return dispatch(new UpdateDeviceDescription(nodeId, parser.modules, parser.views)); +}; + +export const postProcessDisplaySpecificationActionCreator = (vPath: string, viewData: any, displaySpecification: DisplaySpecification) => async (dispatch: Dispatch, _getState: () => IApplicationStoreState) => { + + if (displaySpecification.displayMode === DisplayModeType.displayAsObject) { + displaySpecification = { + ...displaySpecification, + viewSpecification: await filterViewElements(vPath, viewData, displaySpecification.viewSpecification), + }; + } + + dispatch(new UpdateViewDescription(vPath, viewData, displaySpecification)); +}; + +export const updateViewActionAsyncCreator = (vPath: string) => async (dispatch: Dispatch, getState: () => IApplicationStoreState) => { + const pathParts = splitVPath(vPath, /(?:([^\/\["]+)(?:\[([^\]]*)\])?)/g); // 1 = property / 2 = optional key + const { configuration: { deviceDescription: { nodeId, modules, views } } } = getState(); + let dataPath = `/rests/data/network-topology:network-topology/topology=topology-netconf/node=${nodeId}/yang-ext:mount`; + + let inputViewSpecification: ViewSpecification | undefined = undefined; + let outputViewSpecification: ViewSpecification | undefined = undefined; + + let viewSpecification: ViewSpecification = views[0]; + let viewElement: ViewElement; + + let dataMember: string; + let extractList: boolean = false; + + let currentNS: string | null = null; + let defaultNS: string | null = null; + + dispatch(new SetCollectingSelectionData(true)); + try { + for (let ind = 0; ind < pathParts.length; ++ind) { + const [property, key] = pathParts[ind]; + const namespaceInd = property && property.indexOf(':') || -1; + const namespace: string | null = namespaceInd > -1 ? (currentNS = property.slice(0, namespaceInd)) : currentNS; + + if (ind === 0) { defaultNS = namespace; } + + viewElement = viewSpecification.elements[property] || viewSpecification.elements[`${namespace}:${property}`]; + if (!viewElement) throw Error('Property [' + property + '] does not exist.'); + + if (viewElement.isList && !key) { + if (pathParts.length - 1 > ind) { + dispatch(new SetCollectingSelectionData(false)); + throw new Error('No key for list [' + property + ']'); + } else if (vPath.endsWith('[]') && pathParts.length - 1 === ind) { + + // empty key is used for new element + if (viewElement && 'viewId' in viewElement) viewSpecification = views[+viewElement.viewId]; + const data = Object.keys(viewSpecification.elements).reduce<{ [name: string]: any }>((acc, cur) => { + const elm = viewSpecification.elements[cur]; + if (elm.default) { + acc[elm.id] = elm.default || ''; + } + return acc; + }, {}); + + // create display specification + const ds: DisplaySpecification = { + displayMode: DisplayModeType.displayAsObject, + viewSpecification: resolveViewDescription(defaultNS, vPath, viewSpecification), + keyProperty: isViewElementList(viewElement!) && viewElement.key || undefined, + }; + + // update display specification + return dispatch(postProcessDisplaySpecificationActionCreator(vPath, data, ds)); + } + if (viewElement && isViewElementList(viewElement) && viewSpecification.parentView === '0') { + // check if there is a reference as key + const listSpecification = views[+viewElement.viewId]; + const keyElement = viewElement.key && listSpecification.elements[viewElement.key]; + if (keyElement && isViewElementReference(keyElement)) { + const refList = await getReferencedDataList(keyElement.referencePath, dataPath, modules, views); + if (!refList) { + throw new Error(`Could not find refList for [${keyElement.referencePath}].`); + } + if (!refList.key) { + throw new Error(`Key property not found for [${keyElement.referencePath}].`); + } + dispatch(new EnableValueSelector(refList.view, refList.data, refList.key, (refKey) => { + window.setTimeout(() => dispatch(new PushAction(`${vPath}[${refKey.replace(/\//ig, '%2F')}]`))); + })); + } else { + // Found a list at root level of a module w/o a reference key. + dataPath += `?&fields=${encodeURIComponent(viewElement.id)}(${encodeURIComponent(viewElement.key || '')})`; + const restResult = (await restService.getConfigData(dataPath)); + if (restResult && restResult.status === 200 && restResult.data && restResult.data[viewElement.id] ) { + // spoof the not existing view here + const refData = restResult.data[viewElement.id]; + if (!Array.isArray(refData) || !refData.length) { + throw new Error('Found a list at root level of a module containing no keys.'); + } + if (refData.length > 1) { + const refView : ViewSpecification = { + id: '-1', + canEdit: false, + config: false, + language: 'en-US', + elements: { + [viewElement.key!] : { + uiType: 'string', + config: false, + id: viewElement.key, + label: viewElement.key, + isList: true, + } as ViewElementString, + }, + }; + dispatch(new EnableValueSelector(refView, refData, viewElement.key!, (refKey) => { + window.setTimeout(() => dispatch(new PushAction(`${vPath}[${refKey.replace(/\//ig, '%2F')}]`))); + })); + } else { + window.setTimeout(() => dispatch(new PushAction(`${vPath}[${refData[0]?.id.replace(/\//ig, '%2F')}]`))); + } + } else { + throw new Error('Found a list at root level of a module and could not determine the keys.'); + } + dispatch(new SetCollectingSelectionData(false)); + } + return; + } + extractList = true; + } else { + // normal case & replaces unicode %2C if present + dataPath += `/${property}${key ? `=${key.replace(/\%2C/g, ',').replace(/\//ig, '%2F')}` : ''}`; + + // in case of the root element the required namespace will be added later, + // while extracting the data + dataMember = namespace === defaultNS + ? viewElement.label + : `${namespace}:${viewElement.label}`; + extractList = false; + } + + if (viewElement && 'viewId' in viewElement) { + viewSpecification = views[+viewElement.viewId]; + } else if (viewElement.uiType === 'rpc') { + viewSpecification = views[+(viewElement.inputViewId || 0)]; + + // create new instance & flaten + inputViewSpecification = viewElement.inputViewId != null && { + ...views[+(viewElement.inputViewId || 0)], + elements: flattenViewElements(defaultNS, '', views[+(viewElement.inputViewId || 0)].elements, views, viewElement.label), + } || undefined; + outputViewSpecification = viewElement.outputViewId != null && { + ...views[+(viewElement.outputViewId || 0)], + elements: flattenViewElements(defaultNS, '', views[+(viewElement.outputViewId || 0)].elements, views, viewElement.label), + } || undefined; + + } + } + + let data: any = {}; + // do not get any data from netconf if there is no view specified || this is the root element [0] || this is an rpc + if (viewSpecification && !(viewSpecification.id === '0' || viewElement!.uiType === 'rpc')) { + const restResult = (await restService.getConfigData(dataPath)); + if (!restResult.data) { + // special case: if this is a list without any response + if (extractList && restResult.status === 404) { + if (!isViewElementList(viewElement!)) { + throw new Error(`vPath: [${vPath}]. ViewElement has no key.`); + } + // create display specification + const ds: DisplaySpecification = { + displayMode: extractList ? DisplayModeType.displayAsList : DisplayModeType.displayAsObject, + viewSpecification: resolveViewDescription(defaultNS, vPath, viewSpecification), + keyProperty: viewElement.key, + }; + + // update display specification + return dispatch(postProcessDisplaySpecificationActionCreator(vPath, [], ds)); + } + throw new Error(`Did not get response from Server. Status: [${restResult.status}]`); + } else if (checkResponseCode(restResult)) { + const message = restResult.data.errors && restResult.data.errors.error && restResult.data.errors.error[0] && restResult.data.errors.error[0]['error-message'] || ''; + throw new Error(`Server Error. Status: [${restResult.status}]\n${message}`); + } else { + // https://tools.ietf.org/html/rfc7951#section-4 the root element may contain a namespace or not ! + data = restResult.data[`${defaultNS}:${dataMember!}`]; + if (data === undefined) { + data = restResult.data[dataMember!]; // extract dataMember w/o namespace + } + } + + // extract the first element list[key] + data = data instanceof Array + ? data[0] + : data; + + // extract the list -> key: list + data = extractList + ? data[viewElement!.id] || data[viewElement!.label] || [] // if the list is empty, it does not exist + : data; + + } else if (viewElement! && viewElement!.uiType === 'rpc') { + // set data to defaults + data = {}; + if (inputViewSpecification) { + Object.keys(inputViewSpecification.elements).forEach(key => { + const elm = inputViewSpecification && inputViewSpecification.elements[key]; + if (elm && elm.default != undefined) { + data[elm.id] = elm.default; + } + }); + } + } + + // create display specification + const ds: DisplaySpecification = viewElement! && viewElement!.uiType === 'rpc' + ? { + dataPath, + displayMode: DisplayModeType.displayAsRPC, + inputViewSpecification: inputViewSpecification && resolveViewDescription(defaultNS, vPath, inputViewSpecification), + outputViewSpecification: outputViewSpecification && resolveViewDescription(defaultNS, vPath, outputViewSpecification), + } + : { + dataPath, + displayMode: extractList ? DisplayModeType.displayAsList : DisplayModeType.displayAsObject, + viewSpecification: resolveViewDescription(defaultNS, vPath, viewSpecification), + keyProperty: isViewElementList(viewElement!) && viewElement.key || undefined, + + // eslint-disable-next-line max-len + apidocPath: isViewElementList(viewElement!) && `/apidoc/explorer/index.html?urls.primaryName=$$$standard$$$#/mounted%20${nodeId}%20${viewElement!.module || 'MODULE_NOT_DEFINED'}/$$$action$$$_${dataPath.replace(/^\//, '').replace(/[\/=\-\:]/g, '_')}_${viewElement! != null ? `${viewElement.id.replace(/[\/=\-\:]/g, '_')}_` : '' }` || undefined, + }; + + // update display specification + return dispatch(postProcessDisplaySpecificationActionCreator(vPath, data, ds)); + // https://server.com/#/configuration/Sim12600/core-model:network-element/ltp[LTP-MWPS-TTP-01] + // https://server.com/#/configuration/Sim12600/core-model:network-element/ltp[LTP-MWPS-TTP-01]/lp + } catch (error) { + history.back(); + dispatch(new AddErrorInfoAction({ title: 'Problem', message: error.message || `Could not process ${dataPath}` })); + dispatch(new SetCollectingSelectionData(false)); + } finally { + return; + } +}; + +export const updateDataActionAsyncCreator = (vPath: string, data: any) => async (dispatch: Dispatch, getState: () => IApplicationStoreState) => { + const pathParts = splitVPath(vPath, /(?:([^\/\["]+)(?:\[([^\]]*)\])?)/g); // 1 = property / 2 = optional key + const { configuration: { deviceDescription: { nodeId, views } } } = getState(); + let dataPath = `/rests/data/network-topology:network-topology/topology=topology-netconf/node=${nodeId}/yang-ext:mount`; + let viewSpecification: ViewSpecification = views[0]; + let viewElement: ViewElement; + let dataMember: string; + let embedList: boolean = false; + let isNew: string | false = false; + + let currentNS: string | null = null; + let defaultNS: string | null = null; + + dispatch(new SetCollectingSelectionData(true)); + try { + for (let ind = 0; ind < pathParts.length; ++ind) { + let [property, key] = pathParts[ind]; + const namespaceInd = property && property.indexOf(':') || -1; + const namespace: string | null = namespaceInd > -1 ? (currentNS = property.slice(0, namespaceInd)) : currentNS; + + if (ind === 0) { defaultNS = namespace; } + viewElement = viewSpecification.elements[property] || viewSpecification.elements[`${namespace}:${property}`]; + if (!viewElement) throw Error('Property [' + property + '] does not exist.'); + + if (isViewElementList(viewElement) && !key) { + embedList = true; + if (viewElement && viewElement.isList && viewSpecification.parentView === '0') { + throw new Error('Found a list at root level of a module w/o a refenrece key.'); + } + if (pathParts.length - 1 > ind) { + dispatch(new SetCollectingSelectionData(false)); + throw new Error('No key for list [' + property + ']'); + } else if (vPath.endsWith('[]') && pathParts.length - 1 === ind) { + // handle new element with any number of arguments + let keyList = viewElement.key?.split(' '); + let dataPathParam = keyList?.map(id => data[id]).join(','); + key = viewElement.key && String(dataPathParam) || ''; + isNew = key; + if (!key) { + dispatch(new SetCollectingSelectionData(false)); + throw new Error('No value for key [' + viewElement.key + '] in list [' + property + ']'); + } + } + } + + dataPath += `/${property}${key ? `=${key.replace(/\//ig, '%2F')}` : ''}`; + dataMember = viewElement.label; + embedList = false; + + if (viewElement && 'viewId' in viewElement) { + viewSpecification = views[+viewElement.viewId]; + } + } + + // remove read-only elements + const removeReadOnlyElements = (pViewSpecification: ViewSpecification, isList: boolean, pData: any) => { + if (isList) { + return pData.map((elm : any) => removeReadOnlyElements(pViewSpecification, false, elm)); + } else { + return Object.keys(pData).reduce<{ [key: string]: any }>((acc, cur)=>{ + const [nsOrName, name] = cur.split(':', 1); + const element = pViewSpecification.elements[cur] || pViewSpecification.elements[nsOrName] || pViewSpecification.elements[name]; + if (!element && process.env.NODE_ENV === 'development' ) { + throw new Error('removeReadOnlyElements: Could not determine elment for data.'); + } + if (element && element.config) { + if (element.uiType === 'object') { + const view = views[+element.viewId]; + if (!view) { + throw new Error('removeReadOnlyElements: Internal Error could not determine viewId: ' + element.viewId); + } + acc[cur] = removeReadOnlyElements(view, element.isList != null && element.isList, pData[cur]); + } else { + acc[cur] = pData[cur]; + } + } + return acc; + }, {}); + } + }; + data = removeReadOnlyElements(viewSpecification, embedList, data); + + + // embed the list -> key: list + data = embedList + ? { [viewElement!.label]: data } + : data; + + // embed the first element list[key] + data = isNew + ? [data] + : data; + + // do not extract root member (0) + if (viewSpecification && viewSpecification.id !== '0') { + const updateResult = await restService.setConfigData(dataPath, { [`${currentNS}:${dataMember!}`]: data }); // addDataMember using currentNS + if (checkResponseCode(updateResult)) { + const message = updateResult.data && updateResult.data.errors && updateResult.data.errors.error && updateResult.data.errors.error[0] && updateResult.data.errors.error[0]['error-message'] || ''; + throw new Error(`Server Error. Status: [${updateResult.status}]\n${message || updateResult.message || ''}`); + } + } + + if (isNew) { + return dispatch(new ReplaceAction(`/configuration/${nodeId}/${vPath.replace(/\[\]$/i, `[${isNew}]`)}`)); // navigate to new element + } + + // create display specification + const ds: DisplaySpecification = { + displayMode: embedList ? DisplayModeType.displayAsList : DisplayModeType.displayAsObject, + viewSpecification: resolveViewDescription(defaultNS, vPath, viewSpecification), + keyProperty: isViewElementList(viewElement!) && viewElement.key || undefined, + }; + + // update display specification + return dispatch(new UpdateViewDescription(vPath, data, ds)); + } catch (error) { + history.back(); + dispatch(new AddErrorInfoAction({ title: 'Problem', message: error.message || `Could not change ${dataPath}` })); + + } finally { + dispatch(new SetCollectingSelectionData(false)); + return; + } +}; + +export const removeElementActionAsyncCreator = (vPath: string) => async (dispatch: Dispatch, getState: () => IApplicationStoreState) => { + const pathParts = splitVPath(vPath, /(?:([^\/\["]+)(?:\[([^\]]*)\])?)/g); // 1 = property / 2 = optional key + const { configuration: { deviceDescription: { nodeId, views } } } = getState(); + let dataPath = `/rests/data/network-topology:network-topology/topology=topology-netconf/node=${nodeId}/yang-ext:mount`; + let viewSpecification: ViewSpecification = views[0]; + let viewElement: ViewElement; + + let currentNS: string | null = null; + + dispatch(new SetCollectingSelectionData(true)); + try { + for (let ind = 0; ind < pathParts.length; ++ind) { + let [property, key] = pathParts[ind]; + const namespaceInd = property && property.indexOf(':') || -1; + const namespace: string | null = namespaceInd > -1 ? (currentNS = property.slice(0, namespaceInd)) : currentNS; + + viewElement = viewSpecification.elements[property] || viewSpecification.elements[`${namespace}:${property}`]; + if (!viewElement) throw Error('Property [' + property + '] does not exist.'); + + if (isViewElementList(viewElement) && !key) { + if (viewElement && viewElement.isList && viewSpecification.parentView === '0') { + throw new Error('Found a list at root level of a module w/o a reference key.'); + } + if (pathParts.length - 1 > ind) { + dispatch(new SetCollectingSelectionData(false)); + throw new Error('No key for list [' + property + ']'); + } else if (vPath.endsWith('[]') && pathParts.length - 1 === ind) { + // remove the whole table + } + } + + dataPath += `/${property}${key ? `=${key.replace(/\//ig, '%2F')}` : ''}`; + + if (viewElement && 'viewId' in viewElement) { + viewSpecification = views[+viewElement.viewId]; + } else if (viewElement.uiType === 'rpc') { + viewSpecification = views[+(viewElement.inputViewId || 0)]; + } + } + + const updateResult = await restService.removeConfigElement(dataPath); + if (checkResponseCode(updateResult)) { + const message = updateResult.data && updateResult.data.errors && updateResult.data.errors.error && updateResult.data.errors.error[0] && updateResult.data.errors.error[0]['error-message'] || ''; + throw new Error(`Server Error. Status: [${updateResult.status}]\n${message || updateResult.message || ''}`); + } + } catch (error) { + dispatch(new AddErrorInfoAction({ title: 'Problem', message: error.message || `Could not remove ${dataPath}` })); + } finally { + dispatch(new SetCollectingSelectionData(false)); + } +}; + +export const executeRpcActionAsyncCreator = (vPath: string, data: any) => async (dispatch: Dispatch, getState: () => IApplicationStoreState) => { + const pathParts = splitVPath(vPath, /(?:([^\/\["]+)(?:\[([^\]]*)\])?)/g); // 1 = property / 2 = optional key + const { configuration: { deviceDescription: { nodeId, views } } } = getState(); + let dataPath = `/rests/operations/network-topology:network-topology/topology=topology-netconf/node=${nodeId}/yang-ext:mount`; + let viewSpecification: ViewSpecification = views[0]; + let viewElement: ViewElement; + let dataMember: string; + let embedList: boolean = false; + let isNew: string | false = false; + + let currentNS: string | null = null; + let defaultNS: string | null = null; + + dispatch(new SetCollectingSelectionData(true)); + try { + for (let ind = 0; ind < pathParts.length; ++ind) { + let [property, key] = pathParts[ind]; + const namespaceInd = property && property.indexOf(':') || -1; + const namespace: string | null = namespaceInd > -1 ? (currentNS = property.slice(0, namespaceInd)) : currentNS; + + if (ind === 0) { defaultNS = namespace; } + viewElement = viewSpecification.elements[property] || viewSpecification.elements[`${namespace}:${property}`]; + if (!viewElement) throw Error('Property [' + property + '] does not exist.'); + + if (isViewElementList(viewElement) && !key) { + embedList = true; + // if (viewElement && viewElement.isList && viewSpecification.parentView === "0") { + // throw new Error("Found a list at root level of a module w/o a reference key."); + // } + // if (pathParts.length - 1 > ind) { + // dispatch(new SetCollectingSelectionData(false)); + // throw new Error("No key for list [" + property + "]"); + // } else if (vPath.endsWith("[]") && pathParts.length - 1 === ind) { + // // handle new element + // key = viewElement.key && String(data[viewElement.key]) || ""; + // isNew = key; + // if (!key) { + // dispatch(new SetCollectingSelectionData(false)); + // throw new Error("No value for key [" + viewElement.key + "] in list [" + property + "]"); + // } + // } + } + + dataPath += `/${property}${key ? `=${key.replace(/\//ig, '%2F')}` : ''}`; + dataMember = viewElement.label; + embedList = false; + + if (viewElement && 'viewId' in viewElement) { + viewSpecification = views[+viewElement.viewId]; + } else if (viewElement.uiType === 'rpc') { + viewSpecification = views[+(viewElement.inputViewId || 0)]; + } + } + + // re-inflate formerly flatten rpc data + data = data && Object.keys(data).reduce < { [name: string ]: any }>((acc, cur) => { + const innerPathParts = cur.split('.'); + let pos = 0; + const updatePath = (obj: any, key: string) => { + obj[key] = (pos >= innerPathParts.length) + ? data[cur] + : updatePath(obj[key] || {}, innerPathParts[pos++]); + return obj; + }; + updatePath(acc, innerPathParts[pos++]); + return acc; + }, {}) || null; + + // embed the list -> key: list + data = embedList + ? { [viewElement!.label]: data } + : data; + + // embed the first element list[key] + data = isNew + ? [data] + : data; + + // do not post root member (0) + if ((viewSpecification && viewSpecification.id !== '0') || (dataMember! && !data)) { + const updateResult = await restService.executeRpc(dataPath, { [`${defaultNS}:input`]: data || {} }); + if (checkResponseCode(updateResult)) { + const message = updateResult.data && updateResult.data.errors && updateResult.data.errors.error && updateResult.data.errors.error[0] && updateResult.data.errors.error[0]['error-message'] || ''; + throw new Error(`Server Error. Status: [${updateResult.status}]\n${message || updateResult.message || ''}`); + } + dispatch(new UpdateOutputData(updateResult.data)); + } else { + throw new Error('There is NO RPC specified.'); + } + + + // // update display specification + // return + } catch (error) { + dispatch(new AddErrorInfoAction({ title: 'Problem', message: error.message || `Could not change ${dataPath}` })); + + } finally { + dispatch(new SetCollectingSelectionData(false)); + } +}; diff --git a/sdnr/wt-odlux/odlux/apps/configurationApp/src/assets/icons/configurationAppIcon.svg b/sdnr/wt-odlux/odlux/apps/configurationApp/src/assets/icons/configurationAppIcon.svg new file mode 100644 index 000000000..1b74cc479 --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/configurationApp/src/assets/icons/configurationAppIcon.svg @@ -0,0 +1,20 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/sdnr/wt-odlux/odlux/apps/configurationApp/src/components/baseProps.ts b/sdnr/wt-odlux/odlux/apps/configurationApp/src/components/baseProps.ts new file mode 100644 index 000000000..7187c0a4e --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/configurationApp/src/components/baseProps.ts @@ -0,0 +1,28 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * 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. + * ============LICENSE_END========================================================================== + */ + +import { ViewElement } from '../models/uiModels'; + +export type BaseProps = { + value: ViewElement; + inputValue: TValue; + readOnly: boolean; + disabled: boolean; + onChange(newValue: TValue): void; + isKey?: boolean; +}; \ No newline at end of file diff --git a/sdnr/wt-odlux/odlux/apps/configurationApp/src/components/ifWhenTextInput.tsx b/sdnr/wt-odlux/odlux/apps/configurationApp/src/components/ifWhenTextInput.tsx new file mode 100644 index 000000000..b176e5db5 --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/configurationApp/src/components/ifWhenTextInput.tsx @@ -0,0 +1,101 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * 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. + * ============LICENSE_END========================================================================== + */ + +import React from 'react'; +import InputAdornment from '@mui/material/InputAdornment'; +import Input, { InputProps } from '@mui/material/Input'; +import Tooltip from '@mui/material/Tooltip'; +import FormControl from '@mui/material/FormControl'; +import InputLabel from '@mui/material/InputLabel'; +import FormHelperText from '@mui/material/FormHelperText'; + +import makeStyles from '@mui/styles/makeStyles'; +import createStyles from '@mui/styles/createStyles'; + +import { faAdjust } from '@fortawesome/free-solid-svg-icons/faAdjust'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; + +import { ViewElementBase } from '../models/uiModels'; + +const useStyles = makeStyles(() => + createStyles({ + iconDark: { + color: '#ff8800', + }, + iconLight: { + color: 'orange', + }, + padding: { + paddingLeft: 10, + paddingRight: 10, + }, + }), +); + +type IfWhenProps = InputProps & { + label: string; + element: ViewElementBase; + helperText: string; + error: boolean; + onChangeTooltipVisibility(value: boolean): void; +}; + +export const IfWhenTextInput = (props: IfWhenProps) => { + + const { element, id, label, helperText: errorText, error, style, ...otherProps } = props; + const classes = useStyles(); + + const ifFeature = element.ifFeature + ? ( + props.onChangeTooltipVisibility(false)} + onMouseOut={() => props.onChangeTooltipVisibility(true)} + > + + + + + ) + : null; + + const whenFeature = element.when + ? ( + props.onChangeTooltipVisibility(false)} + onMouseOut={() => props.onChangeTooltipVisibility(true)} + > + + + + + ) + : null; + + return ( + + {label} + {ifFeature}{whenFeature}} {...otherProps} /> + {errorText} + + ); +}; \ No newline at end of file diff --git a/sdnr/wt-odlux/odlux/apps/configurationApp/src/components/uiElementBoolean.tsx b/sdnr/wt-odlux/odlux/apps/configurationApp/src/components/uiElementBoolean.tsx new file mode 100644 index 000000000..56fb93cea --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/configurationApp/src/components/uiElementBoolean.tsx @@ -0,0 +1,63 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * 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. + * ============LICENSE_END========================================================================== + */ + +import React from 'react'; + +import MenuItem from '@mui/material/MenuItem'; +import FormHelperText from '@mui/material/FormHelperText'; +import Select from '@mui/material/Select'; +import FormControl from '@mui/material/FormControl'; +import InputLabel from '@mui/material/InputLabel'; + +import { ViewElementBoolean } from '../models/uiModels'; +import { BaseProps } from './baseProps'; + +type BooleanInputProps = BaseProps; + +export const UiElementBoolean = (props: BooleanInputProps) => { + + const element = props.value as ViewElementBoolean; + + const value = String(props.inputValue).toLowerCase(); + const mandatoryError = element.mandatory && value !== 'true' && value !== 'false'; + + return (!props.readOnly || element.id != null + ? ( + {element.label} + + {mandatoryError ? 'Value is mandatory' : ''} + ) + : null + ); +}; \ No newline at end of file diff --git a/sdnr/wt-odlux/odlux/apps/configurationApp/src/components/uiElementLeafList.tsx b/sdnr/wt-odlux/odlux/apps/configurationApp/src/components/uiElementLeafList.tsx new file mode 100644 index 000000000..669ddff63 --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/configurationApp/src/components/uiElementLeafList.tsx @@ -0,0 +1,209 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * 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. + * ============LICENSE_END========================================================================== + */ + +import React from 'react'; +import FormControl from '@mui/material/FormControl'; +import InputLabel from '@mui/material/InputLabel'; +import Chip from '@mui/material/Chip'; +import Dialog from '@mui/material/Dialog'; +import DialogTitle from '@mui/material/DialogTitle'; +import DialogContent from '@mui/material/DialogContent'; +import DialogActions from '@mui/material/DialogActions'; +import Button from '@mui/material/Button'; + +import makeStyles from '@mui/styles/makeStyles'; +import AddIcon from '@mui/icons-material/Add'; + +import { Theme } from '@mui/material/styles'; +import { ViewElement } from '../models/uiModels'; + +import { BaseProps } from './baseProps'; + +const useStyles = makeStyles((theme: Theme) => { + const light = theme.palette.mode === 'light'; + const bottomLineColor = light ? 'rgba(0, 0, 0, 0.42)' : 'rgba(255, 255, 255, 0.7)'; + + return ({ + root: { + display: 'flex', + justifyContent: 'left', + verticalAlign: 'bottom', + flexWrap: 'wrap', + listStyle: 'none', + margin: 0, + padding: 0, + paddingTop: theme.spacing(0.5), + marginTop: theme.spacing(1), + }, + chip: { + margin: theme.spacing(0.5), + }, + underline: { + '&:after': { + borderBottom: `2px solid ${theme.palette.primary.main}`, + left: 0, + bottom: 0, + // Doing the other way around crash on IE 11 "''" https://github.com/cssinjs/jss/issues/242 + content: '""', + position: 'absolute', + right: 0, + transform: 'scaleX(0)', + transition: theme.transitions.create('transform', { + duration: theme.transitions.duration.shorter, + easing: theme.transitions.easing.easeOut, + }), + pointerEvents: 'none', // Transparent to the hover style. + }, + '&.Mui-focused:after': { + transform: 'scaleX(1)', + }, + '&.Mui-error:after': { + borderBottomColor: theme.palette.error.main, + transform: 'scaleX(1)', // error is always underlined in red + }, + '&:before': { + borderBottom: `1px solid ${bottomLineColor}`, + left: 0, + bottom: 0, + // Doing the other way around crash on IE 11 "''" https://github.com/cssinjs/jss/issues/242 + content: '"\\00a0"', + position: 'absolute', + right: 0, + transition: theme.transitions.create('border-bottom-color', { + duration: theme.transitions.duration.shorter, + }), + pointerEvents: 'none', // Transparent to the hover style. + }, + '&:hover:not($disabled):before': { + borderBottom: `2px solid ${theme.palette.text.primary}`, + // Reset on touch devices, it doesn't add specificity + // eslint-disable-next-line @typescript-eslint/naming-convention + '@media (hover: none)': { + borderBottom: `1px solid ${bottomLineColor}`, + }, + }, + '&.Mui-disabled:before': { + borderBottomStyle: 'dotted', + }, + }, + }); +}); + +type LeafListProps = BaseProps & { + getEditorForViewElement: (uiElement: ViewElement) => (null | React.ComponentType>); +}; + +export const UiElementLeafList = (props: LeafListProps) => { + const { value: element, inputValue, onChange } = props; + + const classes = useStyles(); + + const [open, setOpen] = React.useState(false); + const [editorValue, setEditorValue] = React.useState(''); + const [editorValueIndex, setEditorValueIndex] = React.useState(-1); + + const handleClose = () => { + setOpen(false); + }; + + const onApplyButton = () => { + if (editorValue != null && editorValue != '' && editorValueIndex < 0) { + props.onChange([ + ...inputValue, + editorValue, + ]); + } else if (editorValue != null && editorValue != '') { + props.onChange([ + ...inputValue.slice(0, editorValueIndex), + editorValue, + ...inputValue.slice(editorValueIndex + 1), + ]); + } + setOpen(false); + }; + + const onDelete = (index : number) => { + const newValue : any[] = [ + ...inputValue.slice(0, index), + ...inputValue.slice(index + 1), + ]; + onChange(newValue); + }; + + const ValueEditor = props.getEditorForViewElement(props.value); + + return ( + <> + + {element.label} +
    + { !props.readOnly ?
  • + } + label={'Add'} + className={classes.chip} + size="small" + color="secondary" + onClick={ () => { + setOpen(true); + setEditorValue(''); + setEditorValueIndex(-1); + } + } + /> +
  • : null } + { inputValue.map((val, ind) => ( +
  • + { onDelete(ind); } : undefined } + onClick={ !props.readOnly ? () => { + setOpen(true); + setEditorValue(val); + setEditorValueIndex(ind); + } : undefined + } + /> +
  • + )) + } +
+ {/* { "Value is mandetory"} */} +
+ + {editorValueIndex < 0 ? 'Add new value' : 'Edit value' } + + { ValueEditor && || null } + + + + + + + + ); +}; \ No newline at end of file diff --git a/sdnr/wt-odlux/odlux/apps/configurationApp/src/components/uiElementNumber.tsx b/sdnr/wt-odlux/odlux/apps/configurationApp/src/components/uiElementNumber.tsx new file mode 100644 index 000000000..b0342788f --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/configurationApp/src/components/uiElementNumber.tsx @@ -0,0 +1,70 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * 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. + * ============LICENSE_END========================================================================== + */ + +import React from 'react'; +import { ViewElementNumber } from "../models/uiModels"; +import { Tooltip, InputAdornment } from "@mui/material"; +import { BaseProps } from "./baseProps"; +import { IfWhenTextInput } from "./ifWhenTextInput"; +import { checkRange } from "../utilities/verifyer"; + +type numberInputProps = BaseProps; + +export const UiElementNumber = (props: numberInputProps) => { + + + const [error, setError] = React.useState(false); + const [helperText, setHelperText] = React.useState(""); + const [isTooltipVisible, setTooltipVisibility] = React.useState(true); + + const element = props.value as ViewElementNumber; + + const verifyValue = (data: string) => { + const num = Number(data); + if (!isNaN(num)) { + const result = checkRange(element, num); + if (result.length > 0) { + setError(true); + setHelperText(result); + } else { + setError(false); + setHelperText(""); + } + } else { + setError(true); + setHelperText("Input is not a number."); + } + props.onChange(num); + } + + return ( + + { verifyValue(e.target.value) }} + error={error} + readOnly={props.readOnly} + disabled={props.disabled} + helperText={helperText} + startAdornment={element.units != null ? {element.units} : undefined} + /> + + ); +} \ No newline at end of file diff --git a/sdnr/wt-odlux/odlux/apps/configurationApp/src/components/uiElementReference.tsx b/sdnr/wt-odlux/odlux/apps/configurationApp/src/components/uiElementReference.tsx new file mode 100644 index 000000000..e3bb8f048 --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/configurationApp/src/components/uiElementReference.tsx @@ -0,0 +1,67 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * 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. + * ============LICENSE_END========================================================================== + */ + +import React, { useState } from 'react'; +import { Tooltip, Button, FormControl } from '@mui/material'; + +import createStyles from '@mui/styles/createStyles'; +import makeStyles from '@mui/styles/makeStyles'; + +import { ViewElement } from '../models/uiModels'; + +const useStyles = makeStyles(() => createStyles({ + button: { + 'justifyContent': 'left', + }, +})); + +type UIElementReferenceProps = { + element: ViewElement; + disabled: boolean; + onOpenReference(element: ViewElement): void; +}; + +export const UIElementReference: React.FC = (props) => { + const { element } = props; + const [disabled, setDisabled] = useState(true); + const classes = useStyles(); + return ( + { + ev.preventDefault(); + ev.stopPropagation(); + if (ev.button === 1) { + setDisabled(!disabled); + } + }}> + + + + + ); +}; \ No newline at end of file diff --git a/sdnr/wt-odlux/odlux/apps/configurationApp/src/components/uiElementSelection.tsx b/sdnr/wt-odlux/odlux/apps/configurationApp/src/components/uiElementSelection.tsx new file mode 100644 index 000000000..ebd04dab4 --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/configurationApp/src/components/uiElementSelection.tsx @@ -0,0 +1,69 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * 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. + * ============LICENSE_END========================================================================== + */ + +import React from 'react'; +import { BaseProps } from './baseProps'; +import { ViewElementSelection } from '../models/uiModels'; +import { FormControl, InputLabel, Select, FormHelperText, MenuItem, Tooltip } from '@mui/material'; + +type selectionProps = BaseProps; + +export const UiElementSelection = (props: selectionProps) => { + + const element = props.value as ViewElementSelection; + + let error = ''; + const value = String(props.inputValue); + if (element.mandatory && Boolean(!value)) { + error = 'Error'; + } + + return (props.readOnly || props.inputValue != null + ? ( + {element.label} + + {error} + ) + : null + ); +}; \ No newline at end of file diff --git a/sdnr/wt-odlux/odlux/apps/configurationApp/src/components/uiElementString.tsx b/sdnr/wt-odlux/odlux/apps/configurationApp/src/components/uiElementString.tsx new file mode 100644 index 000000000..8381d99a4 --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/configurationApp/src/components/uiElementString.tsx @@ -0,0 +1,84 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * 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. + * ============LICENSE_END========================================================================== + */ + +import * as React from "react" +import { Tooltip, TextField } from "@mui/material"; +import { ViewElementString } from "../models/uiModels"; +import { BaseProps } from "./baseProps"; +import { IfWhenTextInput } from "./ifWhenTextInput"; +import { checkRange, checkPattern } from "../utilities/verifyer"; + +type stringEntryProps = BaseProps ; + +export const UiElementString = (props: stringEntryProps) => { + + const [isError, setError] = React.useState(false); + const [helperText, setHelperText] = React.useState(""); + const [isTooltipVisible, setTooltipVisibility] = React.useState(true); + + const element = props.value as ViewElementString; + + const verifyValues = (data: string) => { + + if (data.trim().length > 0) { + + let errorMessage = ""; + const result = checkRange(element, data.length); + + if (result.length > 0) { + errorMessage += result; + } + + const patternResult = checkPattern(element.pattern, data) + + if (patternResult.error) { + errorMessage += patternResult.error; + } + + if (errorMessage.length > 0) { + setError(true); + setHelperText(errorMessage); + } else { + setError(false); + setHelperText(""); + } + } else { + setError(false); + setHelperText(""); + } + + + props.onChange(data); + + } + + return ( + + { verifyValues(e.target.value) }} + error={isError} + readOnly={props.readOnly} + disabled={props.disabled} + helperText={helperText} + /> + + ); +} \ No newline at end of file diff --git a/sdnr/wt-odlux/odlux/apps/configurationApp/src/components/uiElementUnion.tsx b/sdnr/wt-odlux/odlux/apps/configurationApp/src/components/uiElementUnion.tsx new file mode 100644 index 000000000..8d232f5ee --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/configurationApp/src/components/uiElementUnion.tsx @@ -0,0 +1,91 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * 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. + * ============LICENSE_END========================================================================== + */ + +import * as React from 'react' +import { BaseProps } from './baseProps'; +import { Tooltip } from '@mui/material'; +import { IfWhenTextInput } from './ifWhenTextInput'; +import { ViewElementUnion, isViewElementString, isViewElementNumber, isViewElementObject, ViewElementNumber } from '../models/uiModels'; +import { checkRange, checkPattern } from '../utilities/verifyer'; + +type UiElementUnionProps = { isKey: boolean } & BaseProps; + +export const UIElementUnion = (props: UiElementUnionProps) => { + + const [isError, setError] = React.useState(false); + const [helperText, setHelperText] = React.useState(""); + const [isTooltipVisible, setTooltipVisibility] = React.useState(true); + + const element = props.value as ViewElementUnion; + + const verifyValues = (data: string) => { + + let foundObjectElements = 0; + let errorMessage = ""; + let isPatternCorrect = null; + + for (let i = 0; i < element.elements.length; i++) { + const unionElement = element.elements[i]; + + if (isViewElementNumber(unionElement)) { + + errorMessage = checkRange(unionElement, Number(data)); + + } else if (isViewElementString(unionElement)) { + errorMessage += checkRange(unionElement, data.length); + isPatternCorrect = checkPattern(unionElement.pattern, data).isValid; + + + } else if (isViewElementObject(unionElement)) { + foundObjectElements++; + } + + if (isPatternCorrect || errorMessage.length === 0) { + break; + } + } + + if (errorMessage.length > 0 || isPatternCorrect !== null && !isPatternCorrect) { + setError(true); + setHelperText("Input is wrong."); + } else { + setError(false); + setHelperText(""); + } + + if (foundObjectElements > 0 && foundObjectElements != element.elements.length) { + throw new Error(`The union element ${element.id} can't be changed.`); + + } else { + props.onChange(data); + } + }; + + return + { verifyValues(e.target.value) }} + error={isError} + style={{ width: 485, marginLeft: 20, marginRight: 20 }} + readOnly={props.readOnly} + disabled={props.disabled} + helperText={helperText} + /> + ; +} \ No newline at end of file diff --git a/sdnr/wt-odlux/odlux/apps/configurationApp/src/handlers/configurationAppRootHandler.ts b/sdnr/wt-odlux/odlux/apps/configurationApp/src/handlers/configurationAppRootHandler.ts new file mode 100644 index 000000000..9cbd9163e --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/configurationApp/src/handlers/configurationAppRootHandler.ts @@ -0,0 +1,47 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * 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. + * ============LICENSE_END========================================================================== + */ + +import { combineActionHandler } from '../../../../framework/src/flux/middleware'; + +import { IConnectedNetworkElementsState, connectedNetworkElementsActionHandler } from './connectedNetworkElementsHandler'; +import { IDeviceDescriptionState, deviceDescriptionHandler } from './deviceDescriptionHandler'; +import { IViewDescriptionState, viewDescriptionHandler } from './viewDescriptionHandler'; +import { IValueSelectorState, valueSelectorHandler } from './valueSelectorHandler'; + +interface IConfigurationAppStoreState { + connectedNetworkElements: IConnectedNetworkElementsState; // used for ne selection + deviceDescription: IDeviceDescriptionState; // contains ui and device descriptions + viewDescription: IViewDescriptionState; // contains current ui description + valueSelector: IValueSelectorState; +} + +declare module '../../../../framework/src/store/applicationStore' { + interface IApplicationStoreState { + configuration: IConfigurationAppStoreState; + } +} + +const actionHandlers = { + connectedNetworkElements: connectedNetworkElementsActionHandler, + deviceDescription: deviceDescriptionHandler, + viewDescription: viewDescriptionHandler, + valueSelector: valueSelectorHandler, +}; + +export const configurationAppRootHandler = combineActionHandler(actionHandlers); +export default configurationAppRootHandler; diff --git a/sdnr/wt-odlux/odlux/apps/configurationApp/src/handlers/connectedNetworkElementsHandler.ts b/sdnr/wt-odlux/odlux/apps/configurationApp/src/handlers/connectedNetworkElementsHandler.ts new file mode 100644 index 000000000..d2863dd2e --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/configurationApp/src/handlers/connectedNetworkElementsHandler.ts @@ -0,0 +1,45 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * 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. + * ============LICENSE_END========================================================================== + */ + +import { createExternal, IExternalTableState } from '../../../../framework/src/components/material-table/utilities'; +import { createSearchDataHandler } from '../../../../framework/src/utilities/elasticSearch'; +import { getAccessPolicyByUrl } from '../../../../framework/src/services/restService'; + +import { NetworkElementConnection } from '../models/networkElementConnection'; +import { restService } from '../services/restServices'; + +export interface IConnectedNetworkElementsState extends IExternalTableState { } + +// create elastic search material data fetch handler +const connectedNetworkElementsSearchHandler = createSearchDataHandler('network-element-connection', false, { status: 'Connected' }); + +export const { + actionHandler: connectedNetworkElementsActionHandler, + createActions: createConnectedNetworkElementsActions, + createProperties: createConnectedNetworkElementsProperties, + reloadAction: connectedNetworkElementsReloadAction, + + // set value action, to change a value +} = createExternal(connectedNetworkElementsSearchHandler, appState => appState.configuration.connectedNetworkElements, + (ne) => { + if (!ne || !ne.id) return true; + const neUrl = restService.getNetworkElementUri(ne.id); + const policy = getAccessPolicyByUrl(neUrl); + return !(policy.GET && policy.POST); + }, +); diff --git a/sdnr/wt-odlux/odlux/apps/configurationApp/src/handlers/deviceDescriptionHandler.ts b/sdnr/wt-odlux/odlux/apps/configurationApp/src/handlers/deviceDescriptionHandler.ts new file mode 100644 index 000000000..cd01b0988 --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/configurationApp/src/handlers/deviceDescriptionHandler.ts @@ -0,0 +1,48 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * 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. + * ============LICENSE_END========================================================================== + */ + +import { Module } from '../models/yang'; +import { ViewSpecification } from '../models/uiModels'; +import { IActionHandler } from '../../../../framework/src/flux/action'; +import { UpdateDeviceDescription } from '../actions/deviceActions'; + +export interface IDeviceDescriptionState { + nodeId: string; + modules: { + [name: string]: Module; + }; + views: ViewSpecification[]; +} + +const deviceDescriptionStateInit: IDeviceDescriptionState = { + nodeId: '', + modules: {}, + views: [], +}; + +export const deviceDescriptionHandler: IActionHandler = (state = deviceDescriptionStateInit, action) => { + if (action instanceof UpdateDeviceDescription) { + state = { + ...state, + nodeId: action.nodeId, + modules: action.modules, + views: action.views, + }; + } + return state; +}; diff --git a/sdnr/wt-odlux/odlux/apps/configurationApp/src/handlers/valueSelectorHandler.ts b/sdnr/wt-odlux/odlux/apps/configurationApp/src/handlers/valueSelectorHandler.ts new file mode 100644 index 000000000..70d5eb253 --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/configurationApp/src/handlers/valueSelectorHandler.ts @@ -0,0 +1,78 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * 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. + * ============LICENSE_END========================================================================== + */ + +import { IActionHandler } from '../../../../framework/src/flux/action'; +import { ViewSpecification } from '../models/uiModels'; +import { EnableValueSelector, SetSelectedValue, UpdateDeviceDescription, SetCollectingSelectionData, UpdateViewDescription, UpdateOutputData } from '../actions/deviceActions'; + +export interface IValueSelectorState { + collectingData: boolean; + keyProperty: string | undefined; + listSpecification: ViewSpecification | null; + listData: any[]; + onValueSelected: (value: any) => void; +} + +const dummyFunc = () => { }; +const valueSelectorStateInit: IValueSelectorState = { + collectingData: false, + keyProperty: undefined, + listSpecification: null, + listData: [], + onValueSelected: dummyFunc, +}; + +export const valueSelectorHandler: IActionHandler = (state = valueSelectorStateInit, action) => { + if (action instanceof SetCollectingSelectionData) { + state = { + ...state, + collectingData: action.busy, + }; + } else if (action instanceof EnableValueSelector) { + state = { + ...state, + collectingData: false, + keyProperty: action.keyProperty, + listSpecification: action.listSpecification, + onValueSelected: action.onValueSelected, + listData: action.listData, + }; + } else if (action instanceof SetSelectedValue) { + if (state.keyProperty) { + state.onValueSelected(action.value[state.keyProperty]); + } + state = { + ...state, + collectingData: false, + keyProperty: undefined, + listSpecification: null, + onValueSelected: dummyFunc, + listData: [], + }; + } else if (action instanceof UpdateDeviceDescription || action instanceof UpdateViewDescription || action instanceof UpdateOutputData) { + state = { + ...state, + collectingData: false, + keyProperty: undefined, + listSpecification: null, + onValueSelected: dummyFunc, + listData: [], + }; + } + return state; +}; diff --git a/sdnr/wt-odlux/odlux/apps/configurationApp/src/handlers/viewDescriptionHandler.ts b/sdnr/wt-odlux/odlux/apps/configurationApp/src/handlers/viewDescriptionHandler.ts new file mode 100644 index 000000000..39b47be84 --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/configurationApp/src/handlers/viewDescriptionHandler.ts @@ -0,0 +1,82 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * 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. + * ============LICENSE_END========================================================================== + */ + +import { IActionHandler } from '../../../../framework/src/flux/action'; + +import { UpdateViewDescription, UpdateOutputData } from '../actions/deviceActions'; +import { ViewSpecification } from '../models/uiModels'; + +export enum DisplayModeType { + doNotDisplay = 0, + displayAsObject = 1, + displayAsList = 2, + displayAsRPC = 3, + displayAsMessage = 4, +} + +export type DisplaySpecification = { + displayMode: DisplayModeType.doNotDisplay; +} | { + displayMode: DisplayModeType.displayAsObject | DisplayModeType.displayAsList ; + viewSpecification: ViewSpecification; + keyProperty?: string; + apidocPath?: string; + dataPath?: string; +} | { + displayMode: DisplayModeType.displayAsRPC; + inputViewSpecification?: ViewSpecification; + outputViewSpecification?: ViewSpecification; + dataPath?: string; +} | { + displayMode: DisplayModeType.displayAsMessage; + renderMessage: string; +}; + +export interface IViewDescriptionState { + vPath: string | null; + displaySpecification: DisplaySpecification; + viewData: any; + outputData?: any; +} + +const viewDescriptionStateInit: IViewDescriptionState = { + vPath: null, + displaySpecification: { + displayMode: DisplayModeType.doNotDisplay, + }, + viewData: null, + outputData: undefined, +}; + +export const viewDescriptionHandler: IActionHandler = (state = viewDescriptionStateInit, action) => { + if (action instanceof UpdateViewDescription) { + state = { + ...state, + vPath: action.vPath, + viewData: action.viewData, + outputData: undefined, + displaySpecification: action.displaySpecification, + }; + } else if (action instanceof UpdateOutputData) { + state = { + ...state, + outputData: action.outputData, + }; + } + return state; +}; diff --git a/sdnr/wt-odlux/odlux/apps/configurationApp/src/index.html b/sdnr/wt-odlux/odlux/apps/configurationApp/src/index.html new file mode 100644 index 000000000..4a0496bff --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/configurationApp/src/index.html @@ -0,0 +1,30 @@ + + + + + + + + + Configuration App + + + +
+ + + + + + \ No newline at end of file diff --git a/sdnr/wt-odlux/odlux/apps/configurationApp/src/models/networkElementConnection.ts b/sdnr/wt-odlux/odlux/apps/configurationApp/src/models/networkElementConnection.ts new file mode 100644 index 000000000..e1ef1ea2d --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/configurationApp/src/models/networkElementConnection.ts @@ -0,0 +1,37 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * 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. + * ============LICENSE_END========================================================================== + */ + +export type NetworkElementConnection = { + id?: string; + nodeId: string; + host: string; + port: number; + username?: string; + password?: string; + isRequired?: boolean; + status?: 'connected' | 'mounted' | 'unmounted' | 'connecting' | 'disconnected' | 'idle'; + coreModelCapability?: string; + deviceType?: string; + nodeDetails?: { + availableCapabilities: string[]; + unavailableCapabilities: { + failureReason: string; + capability: string; + }[]; + }; +}; diff --git a/sdnr/wt-odlux/odlux/apps/configurationApp/src/models/uiModels.ts b/sdnr/wt-odlux/odlux/apps/configurationApp/src/models/uiModels.ts new file mode 100644 index 000000000..7d9e63caf --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/configurationApp/src/models/uiModels.ts @@ -0,0 +1,241 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * 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. + * ============LICENSE_END========================================================================== + */ + +import type { WhenAST } from '../yang/whenParser'; + +export type ViewElementBase = { + 'id': string; + 'label': string; + 'module': string; + 'path': string; + 'config': boolean; + 'ifFeature'?: string; + 'when'?: WhenAST; + 'mandatory'?: boolean; + 'description'?: string; + 'isList'?: boolean; + 'default'?: string; + 'status'?: 'current' | 'deprecated' | 'obsolete'; + 'reference'?: string; // https://tools.ietf.org/html/rfc7950#section-7.21.4 +}; + +// https://tools.ietf.org/html/rfc7950#section-9.8 +export type ViewElementBinary = ViewElementBase & { + 'uiType': 'binary'; + 'length'?: Expression; // number of octets +}; + +// https://tools.ietf.org/html/rfc7950#section-9.7.4 +export type ViewElementBits = ViewElementBase & { + 'uiType': 'bits'; + 'flags': { + [name: string]: number | undefined; // 0 - 4294967295 + }; +}; + +// https://tools.ietf.org/html/rfc7950#section-9 +export type ViewElementString = ViewElementBase & { + 'uiType': 'string'; + 'pattern'?: Expression; + 'length'?: Expression; + 'invertMatch'?: true; +}; + +// special case derived from +export type ViewElementDate = ViewElementBase & { + 'uiType': 'date'; + 'pattern'?: Expression; + 'length'?: Expression; + 'invertMatch'?: true; +}; + +// https://tools.ietf.org/html/rfc7950#section-9.3 +export type ViewElementNumber = ViewElementBase & { + 'uiType': 'number'; + 'min': number; + 'max': number; + 'range'?: Expression; + 'units'?: string; + 'format'?: string; + 'fDigits'?: number; +}; + +// https://tools.ietf.org/html/rfc7950#section-9.5 +export type ViewElementBoolean = ViewElementBase & { + 'uiType': 'boolean'; + 'trueValue'?: string; + 'falseValue'?: string; +}; + +// https://tools.ietf.org/html/rfc7950#section-9.6.4 +export type ViewElementSelection = ViewElementBase & { + 'uiType': 'selection'; + 'multiSelect'?: boolean; + 'options': { + 'key': string; + 'value': string; + 'description'?: string; + 'status'?: 'current' | 'deprecated' | 'obsolete'; + 'reference'?: string; + }[]; +}; + +// is a list if isList is true ;-) +export type ViewElementObject = ViewElementBase & { + 'uiType': 'object'; + 'isList'?: false; + 'viewId': string; +}; + +// Hint: read only lists do not need a key +export type ViewElementList = (ViewElementBase & { + 'uiType': 'object'; + 'isList': true; + 'viewId': string; + 'key'?: string; +}); + +export type ViewElementReference = ViewElementBase & { + 'uiType': 'reference'; + 'referencePath': string; + 'ref': (currentPath: string) => [ViewElement, string] | undefined; +}; + +export type ViewElementUnion = ViewElementBase & { + 'uiType': 'union'; + 'elements': ViewElement[]; +}; + +export type ViewElementChoiceCase = { id: string; label: string; description?: string; elements: { [name: string]: ViewElement } }; + +export type ViewElementChoice = ViewElementBase & { + 'uiType': 'choice'; + 'cases': { + [name: string]: ViewElementChoiceCase; + }; +}; + +// https://tools.ietf.org/html/rfc7950#section-7.14.1 +export type ViewElementRpc = ViewElementBase & { + 'uiType': 'rpc'; + 'inputViewId'?: string; + 'outputViewId'?: string; +}; + +export type ViewElementEmpty = ViewElementBase & { + 'uiType': 'empty'; +}; + +export type ViewElement = + | ViewElementEmpty + | ViewElementBits + | ViewElementBinary + | ViewElementString + | ViewElementDate + | ViewElementNumber + | ViewElementBoolean + | ViewElementObject + | ViewElementList + | ViewElementSelection + | ViewElementReference + | ViewElementUnion + | ViewElementChoice + | ViewElementRpc; + +export const isViewElementString = (viewElement: ViewElement): viewElement is ViewElementString => { + return viewElement && (viewElement.uiType === 'string' || viewElement.uiType === 'date'); +}; + +export const isViewElementDate = (viewElement: ViewElement): viewElement is ViewElementDate => { + return viewElement && (viewElement.uiType === 'date'); +}; + +export const isViewElementNumber = (viewElement: ViewElement): viewElement is ViewElementNumber => { + return viewElement && viewElement.uiType === 'number'; +}; + +export const isViewElementBoolean = (viewElement: ViewElement): viewElement is ViewElementBoolean => { + return viewElement && viewElement.uiType === 'boolean'; +}; + +export const isViewElementObject = (viewElement: ViewElement): viewElement is ViewElementObject => { + return viewElement && viewElement.uiType === 'object' && viewElement.isList === false; +}; + +export const isViewElementList = (viewElement: ViewElement): viewElement is ViewElementList => { + return viewElement && viewElement.uiType === 'object' && viewElement.isList === true; +}; + +export const isViewElementObjectOrList = (viewElement: ViewElement): viewElement is ViewElementObject | ViewElementList => { + return viewElement && viewElement.uiType === 'object'; +}; + +export const isViewElementSelection = (viewElement: ViewElement): viewElement is ViewElementSelection => { + return viewElement && viewElement.uiType === 'selection'; +}; + +export const isViewElementReference = (viewElement: ViewElement): viewElement is ViewElementReference => { + return viewElement && viewElement.uiType === 'reference'; +}; + +export const isViewElementUnion = (viewElement: ViewElement): viewElement is ViewElementUnion => { + return viewElement && viewElement.uiType === 'union'; +}; + +export const isViewElementChoice = (viewElement: ViewElement): viewElement is ViewElementChoice => { + return viewElement && viewElement.uiType === 'choice'; +}; + +export const isViewElementRpc = (viewElement: ViewElement): viewElement is ViewElementRpc => { + return viewElement && viewElement.uiType === 'rpc'; +}; + +export const isViewElementEmpty = (viewElement: ViewElement): viewElement is ViewElementRpc => { + return viewElement && viewElement.uiType === 'empty'; +}; + +export const ResolveFunction = Symbol('IsResolved'); + +export type ViewSpecification = { + id: string; + ns?: string; + name?: string; + title?: string; + parentView?: string; + language: string; + ifFeature?: string; + when?: WhenAST; + uses?: (string[]) & { [ResolveFunction]?: (parent: string) => void }; + elements: { [name: string]: ViewElement }; + config: boolean; + readonly canEdit: boolean; +}; + +export type YangRange = { + min: number; + max: number; +}; + +export type Expression = + | T + | Operator; + +export type Operator = { + operation: 'AND' | 'OR'; + arguments: Expression[]; +}; \ No newline at end of file diff --git a/sdnr/wt-odlux/odlux/apps/configurationApp/src/models/yang.ts b/sdnr/wt-odlux/odlux/apps/configurationApp/src/models/yang.ts new file mode 100644 index 000000000..e4e59fb96 --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/configurationApp/src/models/yang.ts @@ -0,0 +1,71 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * 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. + * ============LICENSE_END========================================================================== + */ + +import { ViewElement, ViewSpecification } from './uiModels'; + +export enum ModuleState { + stable, + instable, + importOnly, + unavailable, +} + +export type Token = { + name: string; + value: string; + start: number; + end: number; +}; + +export type Statement = { + key: string; + arg?: string; + sub?: Statement[]; +}; + +export type Identity = { + id: string; + label: string; + base?: string; + description?: string; + reference?: string; + children?: Identity[]; + values?: Identity[]; +}; + +export type Revision = { + description?: string; + reference?: string; +}; + +export type Module = { + name: string; + namespace?: string; + prefix?: string; + state: ModuleState; + identities: { [name: string]: Identity }; + revisions: { [version: string]: Revision }; + imports: { [prefix: string]: string }; + features: { [feature: string]: { description?: string } }; + typedefs: { [type: string]: ViewElement }; + augments: { [path: string]: ViewSpecification[] }; + groupings: { [group: string]: ViewSpecification }; + views: { [view: string]: ViewSpecification }; + elements: { [view: string]: ViewElement }; + executionOrder?: number; +}; \ No newline at end of file diff --git a/sdnr/wt-odlux/odlux/apps/configurationApp/src/pluginConfiguration.tsx b/sdnr/wt-odlux/odlux/apps/configurationApp/src/pluginConfiguration.tsx new file mode 100644 index 000000000..7dd2d6ae4 --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/configurationApp/src/pluginConfiguration.tsx @@ -0,0 +1,145 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * 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. + * ============LICENSE_END========================================================================== + */ + +import React from 'react'; +import { withRouter, RouteComponentProps, Route, Switch, Redirect } from 'react-router-dom'; + +import { connect, Connect, IDispatcher } from '../../../framework/src/flux/connect'; +import applicationManager from '../../../framework/src/services/applicationManager'; + +import { configurationAppRootHandler } from './handlers/configurationAppRootHandler'; +import { NetworkElementSelector } from './views/networkElementSelector'; + +import ConfigurationApplication from './views/configurationApplication'; +import { updateNodeIdAsyncActionCreator, updateViewActionAsyncCreator } from './actions/deviceActions'; +import { DisplayModeType } from './handlers/viewDescriptionHandler'; +import { ViewSpecification } from './models/uiModels'; + +const appIcon = require('./assets/icons/configurationAppIcon.svg'); // select app icon + +let currentNodeId: string | null | undefined = undefined; +let currentVirtualPath: string | null | undefined = undefined; +let lastUrl: string | undefined = undefined; + +const mapDispatch = (dispatcher: IDispatcher) => ({ + updateNodeId: (nodeId: string) => dispatcher.dispatch(updateNodeIdAsyncActionCreator(nodeId)), + updateView: (vPath: string) => dispatcher.dispatch(updateViewActionAsyncCreator(vPath)), +}); + +// eslint-disable-next-line @typescript-eslint/naming-convention +const ConfigurationApplicationRouteAdapter = connect(undefined, mapDispatch)((props: RouteComponentProps<{ nodeId?: string; 0: string }> & Connect) => { + React.useEffect(() => { + return () => { + lastUrl = undefined; + currentNodeId = undefined; + currentVirtualPath = undefined; + }; + }, []); + if (props.location.pathname !== lastUrl) { + // ensure the asynchronous update will only be called once per path + lastUrl = props.location.pathname; + window.setTimeout(async () => { + + // check if the nodeId has changed + let enableDump = false; + if (currentNodeId !== props.match.params.nodeId) { + currentNodeId = props.match.params.nodeId || undefined; + if (currentNodeId && currentNodeId.endsWith('|dump')) { + enableDump = true; + currentNodeId = currentNodeId.replace(/\|dump$/i, ''); + } + currentVirtualPath = null; + if (currentNodeId) { + await props.updateNodeId(currentNodeId); + } + } + + if (currentVirtualPath !== props.match.params[0]) { + currentVirtualPath = props.match.params[0]; + if (currentVirtualPath && currentVirtualPath.endsWith('|dump')) { + enableDump = true; + currentVirtualPath = currentVirtualPath.replace(/\|dump$/i, ''); + } + await props.updateView(currentVirtualPath); + } + + if (enableDump) { + const device = props.state.configuration.deviceDescription; + const ds = props.state.configuration.viewDescription.displaySpecification; + + const createDump = (view: ViewSpecification | null, level: number = 0) => { + if (view === null) return 'Empty'; + const indention = Array(level * 4).fill(' ').join(''); + let result = ''; + + if (!view) debugger; + // result += `${indention} [${view.canEdit ? 'rw' : 'ro'}] ${view.ns}:${view.name} ${ds.displayMode === DisplayModeType.displayAsList ? '[LIST]' : ''}\r\n`; + result += Object.keys(view.elements).reduce((acc, cur) => { + const elm = view.elements[cur]; + acc += `${indention} [${elm.uiType === 'rpc' ? 'x' : elm.config ? 'rw' : 'ro'}:${elm.id}] (${elm.module}:${elm.label}) {${elm.uiType}} ${elm.uiType === 'object' && elm.isList ? `as LIST with KEY [${elm.key}]` : ''}\r\n`; + // acc += `${indention} +${elm.mandatory ? "mandatory" : "none"} - ${elm.path} \r\n`; + + switch (elm.uiType) { + case 'object': + acc += createDump(device.views[(elm as any).viewId], level + 1); + break; + default: + } + return acc; + }, ''); + return `${result}`; + }; + + const dump = createDump(ds.displayMode === DisplayModeType.displayAsObject || ds.displayMode === DisplayModeType.displayAsList ? ds.viewSpecification : null, 0); + const element = document.createElement('a'); + element.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(dump)); + element.setAttribute('download', currentNodeId + '.txt'); + + element.style.display = 'none'; + document.body.appendChild(element); + + element.click(); + + document.body.removeChild(element); + } + + }); + } + return ( + + ); +}); + +const App = withRouter((props: RouteComponentProps) => ( + + + + + + +)); + +export function register() { + applicationManager.registerApplication({ + name: 'configuration', + icon: appIcon, + rootComponent: App, + rootActionHandler: configurationAppRootHandler, + menuEntry: 'Configuration', + }); +} diff --git a/sdnr/wt-odlux/odlux/apps/configurationApp/src/services/restServices.ts b/sdnr/wt-odlux/odlux/apps/configurationApp/src/services/restServices.ts new file mode 100644 index 000000000..07e263559 --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/configurationApp/src/services/restServices.ts @@ -0,0 +1,164 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * 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. + * ============LICENSE_END========================================================================== + */ + +import { requestRest, requestRestExt } from '../../../../framework/src/services/restService'; +import { convertPropertyNames, replaceHyphen } from '../../../../framework/src/utilities/yangHelper'; + +import { NetworkElementConnection } from '../models/networkElementConnection'; + +type ImportOnlyResponse = { + 'ietf-yang-library:yang-library': { + 'module-set': { + 'import-only-module': { + 'name': string; + 'revision': string; + }[]; + }[]; + }; +}; + + +type CapabilityResponse = { + 'network-topology:node': { + 'node-id': string; + 'netconf-node-topology:available-capabilities': { + 'available-capability': { + 'capability-origin': string; + 'capability': string; + }[]; + }; + 'netconf-node-topology:unavailable-capabilities': { + 'unavailable-capability': { + 'capability': string; + 'failure-reason': string; + }[]; + }; + }[]; +}; + +type CapabilityAnswer = { + availableCapabilities: { + capabilityOrigin: string; + capability: string; + version: string; + }[] | null; + unavailableCapabilities: { + failureReason: string; + capability: string; + version: string; + }[] | null; + importOnlyModules: { + name: string; + revision: string; + }[] | null; +}; + +const capParser = /^\(.*\?revision=(\d{4}-\d{2}-\d{2})\)(\S+)$/i; + +class RestService { + public getNetworkElementUri = (nodeId: string) => '/rests/data/network-topology:network-topology/topology=topology-netconf/node=' + nodeId; + + public async getImportOnlyModules(nodeId: string): Promise<{ name: string; revision: string }[]> { + const path = `${this.getNetworkElementUri(nodeId)}/yang-ext:mount/ietf-yang-library:yang-library?content=nonconfig&fields=module-set(import-only-module(name;revision))`; + const importOnlyResult = await requestRest(path, { method: 'GET' }); + const importOnlyModules = importOnlyResult + ? importOnlyResult['ietf-yang-library:yang-library']['module-set'][0]['import-only-module'] + : []; + return importOnlyModules; + } + + public async getCapabilitiesByMountId(nodeId: string): Promise { + const path = this.getNetworkElementUri(nodeId); + const capabilitiesResult = await requestRest(path, { method: 'GET' }); + const availableCapabilities = capabilitiesResult && capabilitiesResult['network-topology:node'] && capabilitiesResult['network-topology:node'].length > 0 && + (capabilitiesResult['network-topology:node'][0]['netconf-node-topology:available-capabilities'] && + capabilitiesResult['network-topology:node'][0]['netconf-node-topology:available-capabilities']['available-capability'] && + capabilitiesResult['network-topology:node'][0]['netconf-node-topology:available-capabilities']['available-capability'].map(obj => convertPropertyNames(obj, replaceHyphen)) || []) + .map(cap => { + const capMatch = cap && capParser.exec(cap.capability); + return capMatch ? { + capabilityOrigin: cap.capabilityOrigin, + capability: capMatch && capMatch[2] || '', + version: capMatch && capMatch[1] || '', + } : null ; + }).filter((cap) => cap != null) || [] as any; + + const unavailableCapabilities = capabilitiesResult && capabilitiesResult['network-topology:node'] && capabilitiesResult['network-topology:node'].length > 0 && + (capabilitiesResult['network-topology:node'][0]['netconf-node-topology:unavailable-capabilities'] && + capabilitiesResult['network-topology:node'][0]['netconf-node-topology:unavailable-capabilities']['unavailable-capability'] && + capabilitiesResult['network-topology:node'][0]['netconf-node-topology:unavailable-capabilities']['unavailable-capability'].map(obj => convertPropertyNames(obj, replaceHyphen)) || []) + .map(cap => { + const capMatch = cap && capParser.exec(cap.capability); + return capMatch ? { + failureReason: cap.failureReason, + capability: capMatch && capMatch[2] || '', + version: capMatch && capMatch[1] || '', + } : null ; + }).filter((cap) => cap != null) || [] as any; + + const importOnlyModules = availableCapabilities && availableCapabilities.findIndex((ac: { capability: string }) => ac.capability && ac.capability.toLowerCase() === 'ietf-yang-library') > -1 + ? await this.getImportOnlyModules(nodeId) + : null; + + return { availableCapabilities, unavailableCapabilities, importOnlyModules }; + } + + public async getMountedNetworkElementByMountId(nodeId: string): Promise { + // const path = 'restconf/operational/network-topology:network-topology/topology/topology-netconf/node/' + nodeId; + // const connectedNetworkElement = await requestRest(path, { method: "GET" }); + // return connectedNetworkElement || null; + + const path = '/rests/operations/data-provider:read-network-element-connection-list'; + const body = { 'data-provider:input': { 'filter': [{ 'property': 'node-id', 'filtervalue': nodeId }], 'sortorder': [], 'pagination': { 'size': 1, 'page': 1 } } }; + const networkElementResult = await requestRest<{ 'data-provider:output': { data: NetworkElementConnection[] } }>(path, { method: 'POST', body: JSON.stringify(body) }); + return networkElementResult && networkElementResult['data-provider:output'] && networkElementResult['data-provider:output'].data && + networkElementResult['data-provider:output'].data.map(obj => convertPropertyNames(obj, replaceHyphen))[0] || null; + } + + /** Reads the config data by restconf path. + * @param path The restconf path to be used for read. + * @returns The data. + */ + public getConfigData(path: string) { + return requestRestExt<{ [key: string]: any }>(path, { method: 'GET' }); + } + + /** Updates or creates the config data by restconf path using data. + * @param path The restconf path to identify the note to update. + * @param data The data to be updated. + * @returns The written data. + */ + public setConfigData(path: string, data: any) { + return requestRestExt<{ [key: string]: any }>(path, { method: 'PUT', body: JSON.stringify(data) }); + } + + public executeRpc(path: string, data: any) { + return requestRestExt<{ [key: string]: any }>(path, { method: 'POST', body: JSON.stringify(data) }); + } + + /** Removes the element by restconf path. + * @param path The restconf path to identify the note to update. + * @returns The restconf result. + */ + public removeConfigElement(path: string) { + return requestRestExt<{ [key: string]: any }>(path, { method: 'DELETE' }); + } +} + +export const restService = new RestService(); +export default restService; \ No newline at end of file diff --git a/sdnr/wt-odlux/odlux/apps/configurationApp/src/services/yangService.ts b/sdnr/wt-odlux/odlux/apps/configurationApp/src/services/yangService.ts new file mode 100644 index 000000000..bbd051aeb --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/configurationApp/src/services/yangService.ts @@ -0,0 +1,37 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * 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. + * ============LICENSE_END========================================================================== + */ + +const cache: { [path: string]: string } = { }; +const getCapability = async (capability: string, nodeId: string, version?: string) => { + const url = `/yang-schema/${capability}${version ? `/${version}` : ''}?node=${nodeId}`; + + const cacheHit = cache[url]; + if (cacheHit) return cacheHit; + + const res = await fetch(url); + const yangFile = res.ok && (await res.text()); + if (yangFile !== false && yangFile !== null) { + cache[url] = yangFile; + } + return yangFile; +}; + +export const yangService = { + getCapability, +}; +export default yangService; \ No newline at end of file diff --git a/sdnr/wt-odlux/odlux/apps/configurationApp/src/utilities/verifyer.ts b/sdnr/wt-odlux/odlux/apps/configurationApp/src/utilities/verifyer.ts new file mode 100644 index 000000000..9dd12031f --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/configurationApp/src/utilities/verifyer.ts @@ -0,0 +1,261 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * 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. + * ============LICENSE_END========================================================================== + */ + +import { YangRange, Operator, ViewElementNumber, ViewElementString, isViewElementNumber, isViewElementString } from '../models/uiModels'; + +export type validated = { isValid: boolean; error?: string }; + +export type validatedRange = { isValid: boolean; error?: string }; + +const rangeErrorStartNumber = 'The entered number must be'; +const rangeErrorInnerMinTextNumber = 'greater or equals than'; +const rangeErrorInnerMaxTextNumber = 'less or equals than'; +const rangeErrorEndTextNumber = '.'; + +const rangeErrorStartString = 'The entered text must have'; +const rangeErrorInnerMinTextString = 'no more than'; +const rangeErrorInnerMaxTextString = 'less than'; +const rangeErrorEndTextString = ' characters.'; + +let errorMessageStart = ''; +let errorMessageMiddleMinPart = ''; +let errorMessageMiddleMaxPart = ''; +let errorMessageEnd = ''; + +const isYangRange = (val: YangRange | Operator): val is YangRange => (val as YangRange).min !== undefined; + +const isYangOperator = (val: YangRange | Operator): val is Operator => (val as Operator).operation !== undefined; + +const isRegExp = (val: RegExp | Operator): val is RegExp => (val as RegExp).source !== undefined; + +const isRegExpOperator = (val: RegExp | Operator): val is Operator => (val as Operator).operation !== undefined; + +const getRangeErrorMessagesRecursively = (value: Operator, data: number): string[] => { + let currentIteration: string[] = []; + + // iterate over all elements + for (let i = 0; i < value.arguments.length; i++) { + const element = value.arguments[i]; + + let min = undefined; + let max = undefined; + + let isNumberCorrect = false; + + if (isYangRange(element)) { + + //check found min values + if (!isNaN(element.min)) { + if (data < element.min) { + min = element.min; + } else { + isNumberCorrect = true; + } + } + + // check found max values + if (!isNaN(element.max)) { + if (data > element.max) { + max = element.max; + } else { + isNumberCorrect = true; + } + } + + // construct error messages + if (min != undefined) { + currentIteration.push(`${value.operation.toLocaleLowerCase()} ${errorMessageMiddleMinPart} ${min}`); + } else if (max != undefined) { + currentIteration.push(`${value.operation.toLocaleLowerCase()} ${errorMessageMiddleMaxPart} ${max}`); + + } + + } else if (isYangOperator(element)) { + + //get error_message from expression + const result = getRangeErrorMessagesRecursively(element, data); + if (result.length === 0) { + isNumberCorrect = true; + } + currentIteration = currentIteration.concat(result); + } + + // if its an OR operation, the number has been checked and min/max are empty (thus not violated) + // delete everything found (because at least one found is correct, therefore all are correct) and break from loop + if (min === undefined && max === undefined && isNumberCorrect && value.operation === 'OR') { + + currentIteration.splice(0, currentIteration.length); + break; + } + } + + return currentIteration; +}; + +const createStartMessage = (element: string) => { + //remove leading or or and from text + if (element.startsWith('and')) { + element = element.replace('and', ''); + } else if (element.startsWith('or')) { + element = element.replace('or', ''); + } + return `${errorMessageStart} ${element}`; +}; + +const getRangeErrorMessages = (value: Operator, data: number): string => { + + const currentIteration = getRangeErrorMessagesRecursively(value, data); + + // build complete error message from found parts + let errorMessage = ''; + if (currentIteration.length > 1) { + + currentIteration.forEach((element, index) => { + if (index === 0) { + errorMessage = createStartMessage(element); + } else if (index === currentIteration.length - 1) { + errorMessage += ` ${element}${errorMessageEnd}`; + } else { + errorMessage += `, ${element}`; + } + }); + } else if (currentIteration.length == 1) { + errorMessage = `${createStartMessage(currentIteration[0])}${errorMessageEnd}`; + } + + return errorMessage; +}; + +export const checkRange = (element: ViewElementNumber | ViewElementString, data: number): string => { + const number = data; + + let expression = undefined; + + if (isViewElementString(element)) { + expression = element.length; + + errorMessageStart = rangeErrorStartString; + errorMessageMiddleMaxPart = rangeErrorInnerMaxTextString; + errorMessageMiddleMinPart = rangeErrorInnerMinTextString; + errorMessageEnd = rangeErrorEndTextString; + + } else if (isViewElementNumber(element)) { + expression = element.range; + + errorMessageStart = rangeErrorStartNumber; + errorMessageMiddleMaxPart = rangeErrorInnerMaxTextNumber; + errorMessageMiddleMinPart = rangeErrorInnerMinTextNumber; + errorMessageEnd = rangeErrorEndTextNumber; + } + + if (expression) { + if (isYangOperator(expression)) { + + const errorMessage = getRangeErrorMessages(expression, data); + return errorMessage; + + } else + if (isYangRange(expression)) { + + if (!isNaN(expression.min)) { + if (number < expression.min) { + return `${errorMessageStart} ${errorMessageMiddleMinPart} ${expression.min}${errorMessageEnd}`; + } + } + + if (!isNaN(expression.max)) { + if (number > expression.max) { + return `${errorMessageStart} ${errorMessageMiddleMaxPart} ${expression.max}${errorMessageEnd}`; + } + } + } + } + + return ''; +}; + +const getRegexRecursively = (value: Operator, data: string): boolean[] => { + let currentItteration: boolean[] = []; + for (let i = 0; i < value.arguments.length; i++) { + const element = value.arguments[i]; + if (isRegExp(element)) { + // if regex is found, add it to list + currentItteration.push(element.test(data)); + } else if (isRegExpOperator(element)) { + //if RegexExpression is found, try to get regex from it + currentItteration = currentItteration.concat(getRegexRecursively(element, data)); + } + } + + if (value.operation === 'OR') { + // if one is true, all are true, all found items can be discarded + let result = currentItteration.find(element => element); + if (result) { + return []; + } + } + return currentItteration; +}; + +const isPatternValid = (value: Operator, data: string): boolean => { + // get all regex + const result = getRegexRecursively(value, data); + + if (value.operation === 'AND') { + // if AND operation is executed... + // no element can be false + const check = result.find(element => element !== true); + if (check) + return false; + else + return true; + } else { + // if OR operation is executed... + // ... just one element must be true + const check = result.find(element => element === true); + if (check) + return true; + else + return false; + + } +}; + +export const checkPattern = (expression: RegExp | Operator | undefined, data: string): validated => { + + if (expression) { + if (isRegExp(expression)) { + const isValid = expression.test(data); + if (!isValid) + return { isValid: isValid, error: 'The input is in a wrong format.' }; + + } else if (isRegExpOperator(expression)) { + const result = isPatternValid(expression, data); + + if (!result) { + return { isValid: false, error: 'The input is in a wrong format.' }; + } + } + } + + return { isValid: true }; +}; + + + + diff --git a/sdnr/wt-odlux/odlux/apps/configurationApp/src/utilities/viewEngineHelper.ts b/sdnr/wt-odlux/odlux/apps/configurationApp/src/utilities/viewEngineHelper.ts new file mode 100644 index 000000000..ad34c83b9 --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/configurationApp/src/utilities/viewEngineHelper.ts @@ -0,0 +1,324 @@ +import { storeService } from '../../../../framework/src/services/storeService'; +import { WhenAST, WhenTokenType } from '../yang/whenParser'; + +import { + ViewSpecification, + ViewElement, + isViewElementReference, + isViewElementList, + isViewElementObjectOrList, + isViewElementRpc, + isViewElementChoice, + ViewElementChoiceCase, +} from '../models/uiModels'; + +import { Module } from '../models/yang'; + +import { restService } from '../services/restServices'; + +export type HttpResult = { + status: number; + message?: string | undefined; + data: { + [key: string]: any; + } | null | undefined; +}; + +export const checkResponseCode = (restResult: HttpResult) =>{ + //403 gets handled by the framework from now on + return restResult.status !== 403 && ( restResult.status < 200 || restResult.status > 299); +}; + +export const resolveVPath = (current: string, vPath: string): string => { + if (vPath.startsWith('/')) { + return vPath; + } + const parts = current.split('/'); + const vPathParts = vPath.split('/'); + for (const part of vPathParts) { + if (part === '.') { + continue; + } else if (part === '..') { + parts.pop(); + } else { + parts.push(part); + } + } + return parts.join('/'); +}; + +export const splitVPath = (vPath: string, vPathParser : RegExp): [string, string?][] => { + const pathParts: [string, string?][] = []; + let partMatch: RegExpExecArray | null; + if (vPath) do { + partMatch = vPathParser.exec(vPath); + if (partMatch) { + pathParts.push([partMatch[1], partMatch[2] || undefined]); + } + } while (partMatch); + return pathParts; +}; + +const derivedFrom = (vPath: string, when: WhenAST, viewData: any, includeSelf = false) => { + if (when.args?.length !== 2) { + throw new Error('derived-from or derived-from-or-self requires 2 arguments.'); + } + const [arg1, arg2] = when.args; + if (arg1.type !== WhenTokenType.IDENTIFIER || arg2.type !== WhenTokenType.STRING) { + throw new Error('derived-from or derived-from-or-self requires first argument IDENTIFIER and second argument STRING.'); + } + + if (!storeService.applicationStore) { + throw new Error('storeService.applicationStore is not defined.'); + } + + const pathParts = splitVPath(arg1.value as string || '', /(?:(?:([^\/\:]+):)?([^\/]+))/g); + const referenceValueParts = /(?:(?:([^\/\:]+):)?([^\/]+))/g.exec(arg2.value as string || ''); + + if (!pathParts || !referenceValueParts || pathParts.length === 0 || referenceValueParts.length === 0) { + throw new Error('derived-from or derived-from-or-self requires first argument PATH and second argument IDENTITY.'); + } + + if (pathParts[0][1]?.startsWith('..') || pathParts[0][1]?.startsWith('/')) { + throw new Error('derived-from or derived-from-or-self currently only supports relative paths.'); + } + + const { configuration: { deviceDescription: { modules } } } = storeService.applicationStore.state; + const dataValue = pathParts.reduce((acc, [ns, prop]) => { + if (prop === '.') { + return acc; + } + if (acc && prop) { + const moduleName = ns && (Object.values(modules).find((m: Module) => m.prefix === ns) || Object.values(modules).find((m: Module) => m.name === ns))?.name; + return (moduleName) ? acc[`${moduleName}:${prop}`] || acc[prop] : acc[prop]; + } + return undefined; + }, viewData); + + let dataValueParts = dataValue && /(?:(?:([^\/\:]+):)?([^\/]+))/g.exec(dataValue); + if (!dataValueParts || dataValueParts.length < 2) { + throw new Error(`derived-from or derived-from-or-self value referenced by first argument [${arg1.value}] not found.`); + } + let [, dataValueNs, dataValueProp] = dataValueParts; + let dataValueModule: Module = dataValueNs && (Object.values(modules).find((m: Module) => m.name === dataValueNs)); + let dataValueIdentity = dataValueModule && dataValueModule.identities && (Object.values(dataValueModule.identities).find((i) => i.label === dataValueProp)); + + if (!dataValueIdentity) { + throw new Error(`derived-from or derived-from-or-self identity [${dataValue}] referenced by first argument [${arg1.value}] not found.`); + } + + const [, referenceValueNs, referenceValueProp] = referenceValueParts; + const referenceValueModule = referenceValueNs && (Object.values(modules).find((m: Module) => m.prefix === referenceValueNs)); + const referenceValueIdentity = referenceValueModule && referenceValueModule.identities && (Object.values(referenceValueModule.identities).find((i) => i.label === referenceValueProp)); + + if (!referenceValueIdentity) { + throw new Error(`derived-from or derived-from-or-self identity [${arg2.value}] referenced by second argument not found.`); + } + + let result = includeSelf && (referenceValueIdentity === dataValueIdentity); + while (dataValueIdentity && dataValueIdentity.base && !result) { + dataValueParts = dataValue && /(?:(?:([^\/\:]+):)?([^\/]+))/g.exec(dataValueIdentity.base); + const [, innerDataValueNs, innerDataValueProp] = dataValueParts; + dataValueModule = innerDataValueNs && (Object.values(modules).find((m: Module) => m.prefix === innerDataValueNs)) || dataValueModule; + dataValueIdentity = dataValueModule && dataValueModule.identities && (Object.values(dataValueModule.identities).find((i) => i.label === innerDataValueProp)) ; + result = (referenceValueIdentity === dataValueIdentity); + } + + return result; +}; + +const evaluateWhen = async (vPath: string, when: WhenAST, viewData: any): Promise => { + switch (when.type) { + case WhenTokenType.FUNCTION: + switch (when.name) { + case 'derived-from-or-self': + return derivedFrom(vPath, when, viewData, true); + case 'derived-from': + return derivedFrom(vPath, when, viewData, false); + default: + throw new Error(`Unknown function ${when.name}`); + } + case WhenTokenType.AND: + return !when.left || !when.right || (await evaluateWhen(vPath, when.left, viewData) && await evaluateWhen(vPath, when.right, viewData)); + case WhenTokenType.OR: + return !when.left || !when.right || (await evaluateWhen(vPath, when.left, viewData) || await evaluateWhen(vPath, when.right, viewData)); + case WhenTokenType.NOT: + return !when.right || ! await evaluateWhen(vPath, when.right, viewData); + case WhenTokenType.EXPRESSION: + return !(when.value && typeof when.value !== 'string') || await evaluateWhen(vPath, when.value, viewData); + } + return true; +}; + +export const getReferencedDataList = async (refPath: string, dataPath: string, modules: { [name: string]: Module }, views: ViewSpecification[]) => { + const pathParts = splitVPath(refPath, /(?:(?:([^\/\:]+):)?([^\/]+))/g); // 1 = opt: namespace / 2 = property + const defaultNS = pathParts[0][0]; + let referencedModule = modules[defaultNS]; + + let dataMember: string; + let view: ViewSpecification; + let currentNS: string | null = null; + let dataUrls = [dataPath]; + let data: any; + + for (let i = 0; i < pathParts.length; ++i) { + const [pathPartNS, pathPart] = pathParts[i]; + const namespace = pathPartNS != null ? (currentNS = pathPartNS) : currentNS; + + const viewElement = i === 0 + ? views[0].elements[`${referencedModule.name}:${pathPart}`] + : view!.elements[`${pathPart}`] || view!.elements[`${namespace}:${pathPart}`]; + + if (!viewElement) throw new Error(`Could not find ${pathPart} in ${refPath}`); + if (i < pathParts.length - 1) { + if (!isViewElementObjectOrList(viewElement)) { + throw Error(`Module: [${referencedModule.name}].[${viewElement.label}]. View element is not list or object.`); + } + view = views[+viewElement.viewId]; + const resultingDataUrls : string[] = []; + if (isViewElementList(viewElement)) { + for (let j = 0; j < dataUrls.length; ++j) { + const dataUrl = dataUrls[j]; + const restResult = (await restService.getConfigData(dataUrl)); + if (restResult.data == null || checkResponseCode(restResult)) { + const message = restResult.data && restResult.data.errors && restResult.data.errors.error && restResult.data.errors.error[0] && restResult.data.errors.error[0]['error-message'] || ''; + throw new Error(`Server Error. Status: [${restResult.status}]\n${message || restResult.message || ''}`); + } + + let dataRaw = restResult.data[`${defaultNS}:${dataMember!}`]; + if (dataRaw === undefined) { + dataRaw = restResult.data[dataMember!]; + } + dataRaw = dataRaw instanceof Array + ? dataRaw[0] + : dataRaw; + + data = dataRaw && dataRaw[viewElement.label] || []; + const keys: string[] = data.map((entry: { [key: string]: any } )=> entry[viewElement.key!]); + resultingDataUrls.push(...keys.map(key => `${dataUrl}/${viewElement.label.replace(/\//ig, '%2F')}=${key.replace(/\//ig, '%2F')}`)); + } + dataMember = viewElement.label; + } else { + // just a member, not a list + const pathSegment = (i === 0 + ? `/${referencedModule.name}:${viewElement.label.replace(/\//ig, '%2F')}` + : `/${viewElement.label.replace(/\//ig, '%2F')}`); + resultingDataUrls.push(...dataUrls.map(dataUrl => dataUrl + pathSegment)); + dataMember = viewElement.label; + } + dataUrls = resultingDataUrls; + } else { + data = []; + for (let j = 0; j < dataUrls.length; ++j) { + const dataUrl = dataUrls[j]; + const restResult = (await restService.getConfigData(dataUrl)); + if (restResult.data == null || checkResponseCode(restResult)) { + const message = restResult.data && restResult.data.errors && restResult.data.errors.error && restResult.data.errors.error[0] && restResult.data.errors.error[0]['error-message'] || ''; + throw new Error(`Server Error. Status: [${restResult.status}]\n${message || restResult.message || ''}`); + } + let dataRaw = restResult.data[`${defaultNS}:${dataMember!}`]; + if (dataRaw === undefined) { + dataRaw = restResult.data[dataMember!]; + } + dataRaw = dataRaw instanceof Array + ? dataRaw[0] + : dataRaw; + data.push(dataRaw); + } + // BUG UUID ist nicht in den elements enthalten !!!!!! + const key = viewElement && viewElement.label || pathPart; + return { + view: view!, + data: data, + key: key, + }; + } + } + return null; +}; + +export const resolveViewDescription = (defaultNS: string | null, vPath: string, view: ViewSpecification): ViewSpecification =>{ + + // resolve all references. + view = { ...view }; + view.elements = Object.keys(view.elements).reduce<{ [name: string]: ViewElement }>((acc, cur) => { + const resolveHistory : ViewElement[] = []; + let elm = view.elements[cur]; + const key = defaultNS && cur.replace(new RegExp(`^${defaultNS}:`, 'i'), '') || cur; + while (isViewElementReference(elm)) { + const result = (elm.ref(vPath)); + if (result) { + const [referencedElement, referencedPath] = result; + if (resolveHistory.some(hist => hist === referencedElement)) { + console.error(`Circle reference found at: ${vPath}`, resolveHistory); + break; + } + elm = referencedElement; + vPath = referencedPath; + resolveHistory.push(elm); + } + } + + acc[key] = { ...elm, id: key }; + + return acc; + }, {}); + return view; +}; + +export const flattenViewElements = (defaultNS: string | null, parentPath: string, elements: { [name: string]: ViewElement }, views: ViewSpecification[], currentPath: string ): { [name: string]: ViewElement } => { + if (!elements) return {}; + return Object.keys(elements).reduce<{ [name: string]: ViewElement }>((acc, cur) => { + const elm = elements[cur]; + + // remove the default namespace, and only the default namespace, sine it seems that this is also not in the restconf response + const elmKey = defaultNS && elm.id.replace(new RegExp(`^${defaultNS}:`, 'i'), '') || elm.id; + const key = parentPath ? `${parentPath}.${elmKey}` : elmKey; + + if (isViewElementRpc(elm)) { + console.warn(`Flatten of RFC not supported ! [${currentPath}][${elm.label}]`); + return acc; + } else if (isViewElementObjectOrList(elm)) { + const view = views[+elm.viewId]; + const inner = view && flattenViewElements(defaultNS, key, view.elements, views, `${currentPath}/${view.name}`); + if (inner) { + Object.keys(inner).forEach(k => (acc[k] = inner[k])); + } + } else if (isViewElementChoice(elm)) { + acc[key] = { + ...elm, + id: key, + cases: Object.keys(elm.cases).reduce<{ [name: string]: ViewElementChoiceCase }>((accCases, curCases) => { + const caseElement = elm.cases[curCases]; + accCases[curCases] = { + ...caseElement, + // Hint: do not use key it contains elmKey, which shell be omitted for cases. + elements: flattenViewElements(defaultNS, /*key*/ parentPath, caseElement.elements, views, `${currentPath}/${elm.label}`), + }; + return accCases; + }, {}), + }; + } else { + acc[key] = { + ...elm, + id: key, + }; + } + return acc; + }, {}); +}; + +export const filterViewElements = async (vPath: string, viewData: any, viewSpecification: ViewSpecification) => { + // filter elements of viewSpecification by evaluating when property + return Object.keys(viewSpecification.elements).reduce(async (accPromise, cur) => { + const acc = await accPromise; + const elm = viewSpecification.elements[cur]; + if (!elm.when || await evaluateWhen(vPath || '', elm.when, viewData).catch((ex) => { + console.warn(`Error evaluating when clause at: ${viewSpecification.name} for element: ${cur}`, ex); + return true; + })) { + acc.elements[cur] = elm; + } + return acc; + }, Promise.resolve({ ...viewSpecification, elements: {} as { [key: string]: ViewElement } })); +}; \ No newline at end of file diff --git a/sdnr/wt-odlux/odlux/apps/configurationApp/src/views/configurationApplication.tsx b/sdnr/wt-odlux/odlux/apps/configurationApp/src/views/configurationApplication.tsx new file mode 100644 index 000000000..0f143d818 --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/configurationApp/src/views/configurationApplication.tsx @@ -0,0 +1,931 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * 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. + * ============LICENSE_END========================================================================== + */ + +import React, { useState } from 'react'; +import { RouteComponentProps, withRouter } from 'react-router-dom'; + +import { Theme } from '@mui/material/styles'; + +import { WithStyles } from '@mui/styles'; +import withStyles from '@mui/styles/withStyles'; +import createStyles from '@mui/styles/createStyles'; + +import { useConfirm } from 'material-ui-confirm'; + +import { connect, IDispatcher, Connect } from '../../../../framework/src/flux/connect'; +import { IApplicationStoreState } from '../../../../framework/src/store/applicationStore'; +import MaterialTable, { ColumnModel, ColumnType, MaterialTableCtorType } from '../../../../framework/src/components/material-table'; +import { Loader } from '../../../../framework/src/components/material-ui/loader'; +import { renderObject } from '../../../../framework/src/components/objectDump'; + +import { DisplayModeType } from '../handlers/viewDescriptionHandler'; +import { + SetSelectedValue, + updateDataActionAsyncCreator, + updateViewActionAsyncCreator, + removeElementActionAsyncCreator, + executeRpcActionAsyncCreator, +} from '../actions/deviceActions'; + +import { + ViewElement, + ViewSpecification, + ViewElementChoice, + ViewElementRpc, + isViewElementString, + isViewElementNumber, + isViewElementBoolean, + isViewElementObjectOrList, + isViewElementSelection, + isViewElementChoice, + isViewElementUnion, + isViewElementRpc, + isViewElementEmpty, + isViewElementDate, +} from '../models/uiModels'; + +import { getAccessPolicyByUrl } from '../../../../framework/src/services/restService'; + +import Fab from '@mui/material/Fab'; +import AddIcon from '@mui/icons-material/Add'; +import PostAdd from '@mui/icons-material/PostAdd'; +import ArrowBack from '@mui/icons-material/ArrowBack'; +import RemoveIcon from '@mui/icons-material/RemoveCircleOutline'; +import SaveIcon from '@mui/icons-material/Save'; +import EditIcon from '@mui/icons-material/Edit'; +import Tooltip from '@mui/material/Tooltip'; +import FormControl from '@mui/material/FormControl'; +import IconButton from '@mui/material/IconButton'; + +import InputLabel from '@mui/material/InputLabel'; +import Select from '@mui/material/Select'; +import MenuItem from '@mui/material/MenuItem'; +import Breadcrumbs from '@mui/material/Breadcrumbs'; +import Button from '@mui/material/Button'; +import Link from '@mui/material/Link'; +import Accordion from '@mui/material/Accordion'; +import AccordionSummary from '@mui/material/AccordionSummary'; +import AccordionDetails from '@mui/material/AccordionDetails'; +import Typography from '@mui/material/Typography'; +import ExpandMoreIcon from '@mui/icons-material/ExpandMore'; + +import { BaseProps } from '../components/baseProps'; +import { UIElementReference } from '../components/uiElementReference'; +import { UiElementNumber } from '../components/uiElementNumber'; +import { UiElementString } from '../components/uiElementString'; +import { UiElementBoolean } from '../components/uiElementBoolean'; +import { UiElementSelection } from '../components/uiElementSelection'; +import { UIElementUnion } from '../components/uiElementUnion'; +import { UiElementLeafList } from '../components/uiElementLeafList'; + +import { splitVPath } from '../utilities/viewEngineHelper'; + +const styles = (theme: Theme) => createStyles({ + header: { + 'display': 'flex', + 'justifyContent': 'space-between', + }, + leftButton: { + 'justifyContent': 'left', + }, + outer: { + 'flex': '1', + 'height': '100%', + 'display': 'flex', + 'alignItems': 'center', + 'justifyContent': 'center', + }, + inner: { + + }, + container: { + 'height': '100%', + 'display': 'flex', + 'flexDirection': 'column', + }, + 'icon': { + 'marginRight': theme.spacing(0.5), + 'width': 20, + 'height': 20, + }, + 'fab': { + 'margin': theme.spacing(1), + }, + button: { + margin: 0, + padding: '6px 6px', + minWidth: 'unset', + }, + readOnly: { + '& label.Mui-focused': { + color: 'green', + }, + '& .MuiInput-underline:after': { + borderBottomColor: 'green', + }, + '& .MuiOutlinedInput-root': { + '& fieldset': { + borderColor: 'red', + }, + '&:hover fieldset': { + borderColor: 'yellow', + }, + '&.Mui-focused fieldset': { + borderColor: 'green', + }, + }, + }, + uiView: { + overflowY: 'auto', + }, + section: { + padding: '15px', + borderBottom: `2px solid ${theme.palette.divider}`, + }, + viewElements: { + width: 485, marginLeft: 20, marginRight: 20, + }, + verificationElements: { + width: 485, marginLeft: 20, marginRight: 20, + }, + heading: { + fontSize: theme.typography.pxToRem(15), + fontWeight: theme.typography.fontWeightRegular, + }, + moduleCollection: { + marginTop: '16px', + overflow: 'auto', + }, + objectReult: { + overflow: 'auto', + }, +}); + +const mapProps = (state: IApplicationStoreState) => ({ + collectingData: state.configuration.valueSelector.collectingData, + listKeyProperty: state.configuration.valueSelector.keyProperty, + listSpecification: state.configuration.valueSelector.listSpecification, + listData: state.configuration.valueSelector.listData, + vPath: state.configuration.viewDescription.vPath, + nodeId: state.configuration.deviceDescription.nodeId, + viewData: state.configuration.viewDescription.viewData, + outputData: state.configuration.viewDescription.outputData, + displaySpecification: state.configuration.viewDescription.displaySpecification, +}); + +const mapDispatch = (dispatcher: IDispatcher) => ({ + onValueSelected: (value: any) => dispatcher.dispatch(new SetSelectedValue(value)), + onUpdateData: (vPath: string, data: any) => dispatcher.dispatch(updateDataActionAsyncCreator(vPath, data)), + reloadView: (vPath: string) => dispatcher.dispatch(updateViewActionAsyncCreator(vPath)), + removeElement: (vPath: string) => dispatcher.dispatch(removeElementActionAsyncCreator(vPath)), + executeRpc: (vPath: string, data: any) => dispatcher.dispatch(executeRpcActionAsyncCreator(vPath, data)), +}); + +const SelectElementTable = MaterialTable as MaterialTableCtorType<{ [key: string]: any }>; + +type ConfigurationApplicationComponentProps = RouteComponentProps & Connect & WithStyles; + +type ConfigurationApplicationComponentState = { + isNew: boolean; + editMode: boolean; + canEdit: boolean; + viewData: { [key: string]: any } | null; + choices: { [path: string]: { selectedCase: string; data: { [property: string]: any } } }; +}; + +type GetStatelessComponentProps = T extends (props: infer P & { children?: React.ReactNode }) => any ? P : any; +const AccordionSummaryExt: React.FC> = (props) => { + const [disabled, setDisabled] = useState(true); + const onMouseDown = (ev: React.MouseEvent) => { + if (ev.button === 1) { + setDisabled(!disabled); + ev.preventDefault(); + } + }; + return ( +
+ +
+ ); +}; + +const OldProps = Symbol('OldProps'); +class ConfigurationApplicationComponent extends React.Component { + + /** + * + */ + constructor(props: ConfigurationApplicationComponentProps) { + super(props); + + this.state = { + isNew: false, + canEdit: false, + editMode: false, + viewData: null, + choices: {}, + }; + } + + private static getChoicesFromElements = (elements: { [name: string]: ViewElement }, viewData: any) => { + return Object.keys(elements).reduce((acc, cur) => { + const elm = elements[cur]; + if (isViewElementChoice(elm)) { + const caseKeys = Object.keys(elm.cases); + + // find the right case for this choice, use the first one with data, at least use index 0 + const selectedCase = caseKeys.find(key => { + const caseElm = elm.cases[key]; + return Object.keys(caseElm.elements).some(caseElmKey => { + const caseElmElm = caseElm.elements[caseElmKey]; + return viewData[caseElmElm.label] !== undefined || viewData[caseElmElm.id] != undefined; + }); + }) || caseKeys[0]; + + // extract all data of the active case + const caseElements = elm.cases[selectedCase].elements; + const data = Object.keys(caseElements).reduce((dataAcc, dataCur) => { + const dataElm = caseElements[dataCur]; + if (isViewElementEmpty(dataElm)) { + dataAcc[dataElm.label] = null; + } else if (viewData[dataElm.label] !== undefined) { + dataAcc[dataElm.label] = viewData[dataElm.label]; + } else if (viewData[dataElm.id] !== undefined) { + dataAcc[dataElm.id] = viewData[dataElm.id]; + } + return dataAcc; + }, {} as { [name: string]: any }); + + acc[elm.id] = { + selectedCase, + data, + }; + } + return acc; + }, {} as { [path: string]: { selectedCase: string; data: { [property: string]: any } } }) || {}; + }; + + static getDerivedStateFromProps(nextProps: ConfigurationApplicationComponentProps, prevState: ConfigurationApplicationComponentState & { [OldProps]: ConfigurationApplicationComponentProps }) { + + if (!prevState || !prevState[OldProps] || (prevState[OldProps].viewData !== nextProps.viewData)) { + const isNew: boolean = nextProps.vPath?.endsWith('[]') || false; + const state = { + ...prevState, + isNew: isNew, + editMode: isNew, + viewData: nextProps.viewData || null, + [OldProps]: nextProps, + choices: nextProps.displaySpecification.displayMode === DisplayModeType.doNotDisplay + || nextProps.displaySpecification.displayMode === DisplayModeType.displayAsMessage + ? null + : nextProps.displaySpecification.displayMode === DisplayModeType.displayAsRPC + ? nextProps.displaySpecification.inputViewSpecification && ConfigurationApplicationComponent.getChoicesFromElements(nextProps.displaySpecification.inputViewSpecification.elements, nextProps.viewData) || [] + : ConfigurationApplicationComponent.getChoicesFromElements(nextProps.displaySpecification.viewSpecification.elements, nextProps.viewData), + }; + return state; + } + return null; + } + + private navigate = (path: string) => { + this.props.history.push(`${this.props.match.url}${path}`); + }; + + private changeValueFor = (property: string, value: any) => { + this.setState({ + viewData: { + ...this.state.viewData, + [property]: value, + }, + }); + }; + + private collectData = (elements: { [name: string]: ViewElement }) => { + // ensure only active choices will be contained + const viewData: { [key: string]: any } = { ...this.state.viewData }; + const choiceKeys = Object.keys(elements).filter(elmKey => isViewElementChoice(elements[elmKey])); + const elementsToRemove = choiceKeys.reduce((acc, curChoiceKey) => { + const currentChoice = elements[curChoiceKey] as ViewElementChoice; + const selectedCase = this.state.choices[curChoiceKey].selectedCase; + Object.keys(currentChoice.cases).forEach(caseKey => { + const caseElements = currentChoice.cases[caseKey].elements; + if (caseKey === selectedCase) { + Object.keys(caseElements).forEach(caseElementKey => { + const elm = caseElements[caseElementKey]; + if (isViewElementEmpty(elm)) { + // insert null for all empty elements + viewData[elm.id] = null; + } + }); + return; + } + Object.keys(caseElements).forEach(caseElementKey => { + acc.push(caseElements[caseElementKey]); + }); + }); + return acc; + }, [] as ViewElement[]); + + return viewData && Object.keys(viewData).reduce((acc, cur) => { + if (!elementsToRemove.some(elm => elm.label === cur || elm.id === cur)) { + acc[cur] = viewData[cur]; + } + return acc; + }, {} as { [key: string]: any }); + }; + + private isPolicyViewElementForbidden = (element: ViewElement, dataPath: string): boolean => { + const policy = getAccessPolicyByUrl(`${dataPath}/${element.id}`); + return !(policy.GET && policy.POST); + }; + + private isPolicyModuleForbidden = (moduleName: string, dataPath: string): boolean => { + const policy = getAccessPolicyByUrl(`${dataPath}/${moduleName}`); + return !(policy.GET && policy.POST); + }; + + private getEditorForViewElement = (uiElement: ViewElement): (null | React.ComponentType>) => { + if (isViewElementEmpty(uiElement)) { + return null; + } else if (isViewElementSelection(uiElement)) { + return UiElementSelection; + } else if (isViewElementBoolean(uiElement)) { + return UiElementBoolean; + } else if (isViewElementString(uiElement)) { + return UiElementString; + } else if (isViewElementDate(uiElement)) { + return UiElementString; + } else if (isViewElementNumber(uiElement)) { + return UiElementNumber; + } else if (isViewElementUnion(uiElement)) { + return UIElementUnion; + } else { + if (process.env.NODE_ENV !== 'production') { + console.error(`Unknown element type - ${(uiElement as any).uiType} in ${(uiElement as any).id}.`); + } + return null; + } + }; + + private renderUIElement = (uiElement: ViewElement, viewData: { [key: string]: any }, keyProperty: string | undefined, editMode: boolean, isNew: boolean) => { + const isKey = (uiElement.label === keyProperty); + const canEdit = editMode && (isNew || (uiElement.config && !isKey)); + + // do not show elements w/o any value from the backend + if (viewData[uiElement.id] == null && !editMode) { + return null; + } else if (isViewElementEmpty(uiElement)) { + return null; + } else if (uiElement.isList) { + /* element is a leaf-list */ + return { this.changeValueFor(uiElement.id, e); }} + getEditorForViewElement={this.getEditorForViewElement} + />; + } else { + const Element = this.getEditorForViewElement(uiElement); + return Element != null + ? ( + { this.changeValueFor(uiElement.id, e); }} + />) + : null; + } + }; + + // private renderUIReference = (uiElement: ViewElement, viewData: { [key: string]: any }, keyProperty: string | undefined, editMode: boolean, isNew: boolean) => { + // const isKey = (uiElement.label === keyProperty); + // const canEdit = editMode && (isNew || (uiElement.config && !isKey)); + // if (isViewElementObjectOrList(uiElement)) { + // return ( + // + // + // + // + // + // ); + // } else { + // if (process.env.NODE_ENV !== "production") { + // console.error(`Unknown reference type - ${(uiElement as any).uiType} in ${(uiElement as any).id}.`) + // } + // return null; + // } + // }; + + private renderUIChoice = (uiElement: ViewElementChoice, viewData: { [key: string]: any }, keyProperty: string | undefined, editMode: boolean, isNew: boolean) => { + const isKey = (uiElement.label === keyProperty); + + const currentChoice = this.state.choices[uiElement.id]; + const currentCase = currentChoice && uiElement.cases[currentChoice.selectedCase]; + + const canEdit = editMode && (isNew || (uiElement.config && !isKey)); + if (isViewElementChoice(uiElement)) { + const subElements = currentCase?.elements; + return ( + <> + + {uiElement.label} + + + {subElements + ? Object.keys(subElements).map(elmKey => { + const elm = subElements[elmKey]; + return this.renderUIElement(elm, viewData, keyProperty, editMode, isNew); + }) + :

Invalid Choice

+ } + + ); + } else { + if (process.env.NODE_ENV !== 'production') { + console.error(`Unknown type - ${(uiElement as any).uiType} in ${(uiElement as any).id}.`); + } + return null; + } + }; + + private renderUIView = (viewSpecification: ViewSpecification, dataPath: string, viewData: { [key: string]: any }, keyProperty: string | undefined, editMode: boolean, isNew: boolean) => { + const { classes } = this.props; + + const orderFunc = (vsA: ViewElement, vsB: ViewElement) => { + if (keyProperty) { + // if (vsA.label === vsB.label) return 0; + if (vsA.label === keyProperty) return -1; + if (vsB.label === keyProperty) return +1; + } + + // if (vsA.uiType === vsB.uiType) return 0; + // if (vsA.uiType !== "object" && vsB.uiType !== "object") return 0; + // if (vsA.uiType === "object") return +1; + return -1; + }; + + const sections = Object.keys(viewSpecification.elements).reduce((acc, cur) => { + const elm = viewSpecification.elements[cur]; + if (isViewElementObjectOrList(elm)) { + acc.references.push(elm); + } else if (isViewElementChoice(elm)) { + acc.choices.push(elm); + } else if (isViewElementRpc(elm)) { + acc.rpcs.push(elm); + } else { + acc.elements.push(elm); + } + return acc; + }, { elements: [] as ViewElement[], references: [] as ViewElement[], choices: [] as ViewElementChoice[], rpcs: [] as ViewElementRpc[] }); + + sections.elements = sections.elements.sort(orderFunc); + + return ( +
+
+ {sections.elements.length > 0 + ? ( +
+ {sections.elements.map(element => this.renderUIElement(element, viewData, keyProperty, editMode, isNew))} +
+ ) : null + } + {sections.references.length > 0 + ? ( +
+ {sections.references.map(element => ( + { this.navigate(`/${elm.id}`); }} /> + ))} +
+ ) : null + } + {sections.choices.length > 0 + ? ( +
+ {sections.choices.map(element => this.renderUIChoice(element, viewData, keyProperty, editMode, isNew))} +
+ ) : null + } + {sections.rpcs.length > 0 + ? ( +
+ {sections.rpcs.map(element => ( + { this.navigate(`/${elm.id}`); }} /> + ))} +
+ ) : null + } +
+ ); + }; + + private renderUIViewSelector = (viewSpecification: ViewSpecification, dataPath: string, viewData: { [key: string]: any }, keyProperty: string | undefined, editMode: boolean, isNew: boolean) => { + const { classes } = this.props; + + // group by module name + const modules = Object.keys(viewSpecification.elements).reduce<{ [key: string]: ViewSpecification }>((acc, cur) => { + const elm = viewSpecification.elements[cur]; + const moduleView = (acc[elm.module] = acc[elm.module] || { ...viewSpecification, elements: {} }); + moduleView.elements[cur] = elm; + return acc; + }, {}); + + const moduleKeys = Object.keys(modules).sort(); + + return ( +
+ { + moduleKeys.map(key => { + const moduleView = modules[key]; + + return ( + + } aria-controls={`content-${key}`} id={`header-${key}`} disabled={this.isPolicyModuleForbidden(`${key}:`, dataPath)} > + {key} + + + {this.renderUIView(moduleView, dataPath, viewData, keyProperty, editMode, isNew)} + + + ); + }) + } +
+ ); + }; + + private renderUIViewList(listSpecification: ViewSpecification, dataPath: string, listKeyProperty: string, apiDocPath: string, listData: { [key: string]: any }[]) { + const listElements = listSpecification.elements; + const apiDocPathCreate = apiDocPath ? `${location.origin}${apiDocPath + .replace('$$$standard$$$', 'topology-netconfnode%20resources%20-%20RestConf%20RFC%208040') + .replace('$$$action$$$', 'put')}${listKeyProperty ? `_${listKeyProperty.replace(/[\/=\-\:]/g, '_')}_` : '' }` : undefined; + + const config = listSpecification.config && listKeyProperty; // We can not configure a list with no key. + + const navigate = (path: string) => { + this.props.history.push(`${this.props.match.url}${path}`); + }; + + const addNewElementAction = { + icon: AddIcon, + tooltip: 'Add', + ariaLabel:'add-element', + onClick: () => { + navigate('[]'); // empty key means new element + }, + disabled: !config, + }; + + const addWithApiDocElementAction = { + icon: PostAdd, + tooltip: 'Add', + ariaLabel:'add-element-via-api-doc', + onClick: () => { + window.open(apiDocPathCreate, '_blank'); + }, + disabled: !config, + }; + + const { classes, removeElement } = this.props; + + const DeleteIconWithConfirmation: React.FC<{ disabled?: boolean; rowData: { [key: string]: any }; onReload: () => void }> = (props) => { + const confirm = useConfirm(); + + return ( + + { + e.stopPropagation(); + e.preventDefault(); + confirm({ title: 'Do you really want to delete this element ?', description: 'This action is permanent!', confirmationButtonProps: { color: 'secondary' }, cancellationButtonProps: { color:'inherit' } }) + .then(() => { + let keyId = ''; + if (listKeyProperty && listKeyProperty.split(' ').length > 1) { + keyId += listKeyProperty.split(' ').map(id => props.rowData[id]).join(','); + } else { + keyId = props.rowData[listKeyProperty]; + } + return removeElement(`${this.props.vPath}[${keyId}]`); + }).then(props.onReload); + }} + size="large"> + + + + ); + }; + + return ( + []>((acc, cur) => { + const elm = listElements[cur]; + if (elm.uiType !== 'object' && listData.every(entry => entry[elm.label] != null)) { + if (elm.label !== listKeyProperty) { + acc.push(elm.uiType === 'boolean' + ? { property: elm.label, type: ColumnType.boolean } + : elm.uiType === 'date' + ? { property: elm.label, type: ColumnType.date } + : { property: elm.label, type: elm.uiType === 'number' ? ColumnType.numeric : ColumnType.text }); + } else { + acc.unshift(elm.uiType === 'boolean' + ? { property: elm.label, type: ColumnType.boolean } + : elm.uiType === 'date' + ? { property: elm.label, type: ColumnType.date } + : { property: elm.label, type: elm.uiType === 'number' ? ColumnType.numeric : ColumnType.text }); + } + } + return acc; + }, []).concat([{ + property: 'Actions', disableFilter: true, disableSorting: true, type: ColumnType.custom, customControl: (({ rowData }) => { + return ( + this.props.vPath && this.props.reloadView(this.props.vPath)} /> + ); + }), + }]) + } onHandleClick={(ev, row) => { + ev.preventDefault(); + let keyId = ''; + if (listKeyProperty && listKeyProperty.split(' ').length > 1) { + keyId += listKeyProperty.split(' ').map(id => row[id]).join(','); + } else { + keyId = row[listKeyProperty]; + } + if (listKeyProperty) { + navigate(`[${encodeURIComponent(keyId)}]`); // Do not navigate without key. + } + }} > + ); + } + + private renderUIViewRPC(inputViewSpecification: ViewSpecification | undefined, dataPath: string, inputViewData: { [key: string]: any }, outputViewData: { [key: string]: any }, keyProperty: string | undefined, editMode: boolean, isNew: boolean) { + const { classes } = this.props; + + const orderFunc = (vsA: ViewElement, vsB: ViewElement) => { + if (keyProperty) { + // if (vsA.label === vsB.label) return 0; + if (vsA.label === keyProperty) return -1; + if (vsB.label === keyProperty) return +1; + } + + // if (vsA.uiType === vsB.uiType) return 0; + // if (vsA.uiType !== "object" && vsB.uiType !== "object") return 0; + // if (vsA.uiType === "object") return +1; + return -1; + }; + + const sections = inputViewSpecification && Object.keys(inputViewSpecification.elements).reduce((acc, cur) => { + const elm = inputViewSpecification.elements[cur]; + if (isViewElementObjectOrList(elm)) { + console.error('Object should not appear in RPC view !'); + } else if (isViewElementChoice(elm)) { + acc.choices.push(elm); + } else if (isViewElementRpc(elm)) { + console.error('RPC should not appear in RPC view !'); + } else { + acc.elements.push(elm); + } + return acc; + }, { elements: [] as ViewElement[], references: [] as ViewElement[], choices: [] as ViewElementChoice[], rpcs: [] as ViewElementRpc[] }) + || { elements: [] as ViewElement[], references: [] as ViewElement[], choices: [] as ViewElementChoice[], rpcs: [] as ViewElementRpc[] }; + + sections.elements = sections.elements.sort(orderFunc); + + return ( + <> +
+ { sections.elements.length > 0 + ? ( +
+ {sections.elements.map(element => this.renderUIElement(element, inputViewData, keyProperty, editMode, isNew))} +
+ ) : null + } + { sections.choices.length > 0 + ? ( +
+ {sections.choices.map(element => this.renderUIChoice(element, inputViewData, keyProperty, editMode, isNew))} +
+ ) : null + } + +
+ {outputViewData !== undefined + ? renderObject(outputViewData) + : null + } +
+ + ); + } + + private renderBreadCrumps() { + const { editMode } = this.state; + const { displaySpecification, vPath, nodeId } = this.props; + const pathParts = splitVPath(vPath!, /(?:([^\/\["]+)(?:\[([^\]]*)\])?)/g); // 1 = property / 2 = optional key + let lastPath = '/configuration'; + let basePath = `/configuration/${nodeId}`; + return ( +
+
+ + ) => { + ev.preventDefault(); + this.props.history.push(lastPath); + }}>Back + ) => { + ev.preventDefault(); + this.props.history.push(`/configuration/${nodeId}`); + }}>{nodeId} + { + pathParts.map(([prop, key], ind) => { + const path = `${basePath}/${prop}`; + const keyPath = key && `${basePath}/${prop}[${key}]`; + const propTitle = prop.replace(/^[^:]+:/, ''); + const ret = ( + + ) => { + ev.preventDefault(); + this.props.history.push(path); + }}>{propTitle} + { + keyPath && ) => { + ev.preventDefault(); + this.props.history.push(keyPath); + }}>{`[${key && key.replace(/\%2C/g, ',')}]`} || null + } + + ); + lastPath = basePath; + basePath = keyPath || path; + return ret; + }) + } + +
+ {this.state.editMode && ( + { + if (this.props.vPath) { + await this.props.reloadView(this.props.vPath); + } + this.setState({ editMode: false }); + }} > + ) || null} + { /* do not show edit if this is a list or it can't be edited */ + displaySpecification.displayMode === DisplayModeType.displayAsObject && displaySpecification.viewSpecification.canEdit && (
+ { + if (this.state.editMode) { + // ensure only active choices will be contained + const resultingViewData = this.collectData(displaySpecification.viewSpecification.elements); + this.props.onUpdateData(this.props.vPath!, resultingViewData); + } + this.setState({ editMode: !editMode }); + }}> + {editMode + ? + : + } + +
|| null) + } +
+ ); + } + + private renderValueSelector() { + const { listKeyProperty, listSpecification, listData, onValueSelected } = this.props; + if (!listKeyProperty || !listSpecification) { + throw new Error('ListKex ot view not specified.'); + } + + return ( +
+ []>((acc, cur) => { + const elm = listSpecification.elements[cur]; + if (elm.uiType !== 'object' && listData.every(entry => entry[elm.label] != null)) { + if (elm.label !== listKeyProperty) { + acc.push({ property: elm.label, type: elm.uiType === 'number' ? ColumnType.numeric : ColumnType.text }); + } else { + acc.unshift({ property: elm.label, type: elm.uiType === 'number' ? ColumnType.numeric : ColumnType.text }); + } + } + return acc; + }, []) + } onHandleClick={(ev, row) => { ev.preventDefault(); onValueSelected(row); }} > +
+ ); + } + + private renderValueEditor() { + const { displaySpecification: ds, outputData } = this.props; + const { viewData, editMode, isNew } = this.state; + + return ( +
+ {this.renderBreadCrumps()} + {ds.displayMode === DisplayModeType.doNotDisplay + ? null + : ds.displayMode === DisplayModeType.displayAsList && viewData instanceof Array + ? this.renderUIViewList(ds.viewSpecification, ds.dataPath!, ds.keyProperty!, ds.apidocPath!, viewData) + : ds.displayMode === DisplayModeType.displayAsRPC + ? this.renderUIViewRPC(ds.inputViewSpecification, ds.dataPath!, viewData!, outputData, undefined, true, false) + : ds.displayMode === DisplayModeType.displayAsMessage + ? this.renderMessage(ds.renderMessage) + : this.renderUIViewSelector(ds.viewSpecification, ds.dataPath!, viewData!, ds.keyProperty, editMode, isNew) + } +
+ ); + } + + private renderMessage(renderMessage: string) { + return ( +
+

{renderMessage}

+
+ ); + } + + private renderCollectingData() { + return ( +
+
+ +

Processing ...

+
+
+ ); + } + + render() { + return this.props.collectingData || !this.state.viewData + ? this.renderCollectingData() + : this.props.listSpecification + ? this.renderValueSelector() + : this.renderValueEditor(); + } +} + +export const ConfigurationApplication = withStyles(styles)(withRouter(connect(mapProps, mapDispatch)(ConfigurationApplicationComponent))); +export default ConfigurationApplication; \ No newline at end of file diff --git a/sdnr/wt-odlux/odlux/apps/configurationApp/src/views/networkElementSelector.tsx b/sdnr/wt-odlux/odlux/apps/configurationApp/src/views/networkElementSelector.tsx new file mode 100644 index 000000000..e96f40d61 --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/configurationApp/src/views/networkElementSelector.tsx @@ -0,0 +1,72 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * 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. + * ============LICENSE_END========================================================================== + */ + +import React from 'react'; +import { RouteComponentProps, withRouter } from 'react-router-dom'; + +import { connect, IDispatcher, Connect } from '../../../../framework/src/flux/connect'; +import { IApplicationStoreState } from '../../../../framework/src/store/applicationStore'; +import { MaterialTable, MaterialTableCtorType, ColumnType } from '../../../../framework/src/components/material-table'; + +import { NetworkElementConnection } from '../models/networkElementConnection'; +import { createConnectedNetworkElementsProperties, createConnectedNetworkElementsActions } from '../../../configurationApp/src/handlers/connectedNetworkElementsHandler'; + + +const mapProps = (state: IApplicationStoreState) => ({ + connectedNetworkElementsProperties: createConnectedNetworkElementsProperties(state), +}); + +const mapDispatch = (dispatcher: IDispatcher) => ({ + connectedNetworkElementsActions: createConnectedNetworkElementsActions(dispatcher.dispatch), +}); + +const ConnectedElementTable = MaterialTable as MaterialTableCtorType; + +type NetworkElementSelectorComponentProps = RouteComponentProps & Connect; + +let initialSorted = false; + +class NetworkElementSelectorComponent extends React.Component { + + componentDidMount() { + + if (!initialSorted) { + initialSorted = true; + this.props.connectedNetworkElementsActions.onHandleRequestSort('node-id'); + } else + this.props.connectedNetworkElementsActions.onRefresh(); + } + + render() { + return ( + { this.props.history.push(`${this.props.match.path}/${row.nodeId}`); }} columns={[ + { property: 'nodeId', title: 'Node Name', type: ColumnType.text }, + { property: 'isRequired', title: 'Required', type: ColumnType.boolean }, + { property: 'host', title: 'Host', type: ColumnType.text }, + { property: 'port', title: 'Port', type: ColumnType.numeric }, + { property: 'coreModelCapability', title: 'Core Model', type: ColumnType.text }, + { property: 'deviceType', title: 'Type', type: ColumnType.text }, + ]} idProperty="id" {...this.props.connectedNetworkElementsActions} {...this.props.connectedNetworkElementsProperties} asynchronus > + + ); + } +} + +export const NetworkElementSelector = withRouter(connect(mapProps, mapDispatch)(NetworkElementSelectorComponent)); +export default NetworkElementSelector; + diff --git a/sdnr/wt-odlux/odlux/apps/configurationApp/src/yang/whenParser.ts b/sdnr/wt-odlux/odlux/apps/configurationApp/src/yang/whenParser.ts new file mode 100644 index 000000000..fa2968c9c --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/configurationApp/src/yang/whenParser.ts @@ -0,0 +1,235 @@ +enum WhenTokenType { + AND = 'AND', + OR = 'OR', + NOT = 'NOT', + EQUALS = 'EQUALS', + COMMA = 'COMMA', + STRING = 'STRING', + FUNCTION = 'FUNCTION', + IDENTIFIER = 'IDENTIFIER', + OPEN_PAREN = 'OPEN_PAREN', + CLOSE_PAREN = 'CLOSE_PAREN', + EXPRESSION = 'EXPRESSION', +} + +type Token = { + type: WhenTokenType; + value: string; +}; + +const isAlpha = (char: string) => /[a-z]/i.test(char); + +const isAlphaNumeric = (char: string) => /[A-Za-z0-9_\-/:\.]/i.test(char); + +const lex = (input: string) : Token[] => { + let tokens = [] as any[]; + let current = 0; + + while (current < input.length) { + let char = input[current]; + + if (char === ' ') { + current++; + continue; + } + + if (char === '(') { + tokens.push({ type: WhenTokenType.OPEN_PAREN, value: char }); + current++; + continue; + } + + if (char === ')') { + tokens.push({ type: WhenTokenType.CLOSE_PAREN, value: char }); + current++; + continue; + } + + if (char === '=') { + tokens.push({ type: WhenTokenType.EQUALS, value: char }); + current++; + continue; + } + + if (char === ',') { + tokens.push({ type: WhenTokenType.COMMA, value: char }); + current++; + continue; + } + + if (char === '\"' || char === '\'') { + let value = ''; + let start = current; + current++; + + while (current < input.length) { + let innerChar = input[current]; + if (innerChar === '\\') { + value += input[current] + input[current + 1]; + current += 2; + } else if (innerChar === input[start]) { + current++; + break; + } else { + value += innerChar; + current++; + } + } + + tokens.push({ type: WhenTokenType.STRING, value }); + continue; + } + + if (isAlpha(char)) { + let value = ''; + while (isAlpha(char)) { + value += char; + char = input[++current]; + } + + switch (value) { + case 'and': + tokens.push({ type: WhenTokenType.AND }); + break; + case 'or': + tokens.push({ type: WhenTokenType.OR }); + break; + case 'not': + tokens.push({ type: WhenTokenType.NOT }); + break; + case 'eq': + tokens.push({ type: WhenTokenType.EQUALS }); + break; + default: + while (isAlphaNumeric(char)) { + value += char; + char = input[++current]; + } + tokens.push({ type: WhenTokenType.IDENTIFIER, value }); + } + + continue; + } + if (isAlphaNumeric(char)) { + let value = ''; + while (isAlphaNumeric(char)) { + value += char; + char = input[++current]; + } + + tokens.push({ type: WhenTokenType.IDENTIFIER, value }); + continue; + } + throw new TypeError(`I don't know what this character is: ${char}`); + } + return tokens; +}; + +type WhenAST = { + type: WhenTokenType; + left?: WhenAST; + right?: WhenAST; + value?: string | WhenAST; + name?: string; + args?: WhenAST[]; +}; + +const precedence : { [index: string] : number } = { + 'EQUALS': 4, + 'NOT': 3, + 'AND': 2, + 'OR': 1, +}; + +const parseWhen = (whenExpression: string) => { + const tokens = lex(whenExpression); + let current = 0; + + const walk = (precedenceLevel = 0) : WhenAST => { + let token = tokens[current]; + let node: WhenAST | null = null; + + if (token.type === WhenTokenType.OPEN_PAREN) { + token = tokens[++current]; + let innerNode: WhenAST = { type: WhenTokenType.EXPRESSION, value: walk() }; + token = tokens[current]; + + while (token.type !== WhenTokenType.CLOSE_PAREN) { + innerNode = { + type: token.type, + value: token.value, + left: innerNode, + right: walk(), + }; + token = tokens[current]; + } + current++; + return innerNode; + } + + if (token.type === WhenTokenType.STRING ) { + current++; + node = { type: token.type, value: token.value }; + } + + if (token.type === WhenTokenType.NOT) { + token = tokens[++current]; + node = { type: WhenTokenType.NOT, value: token.value, right: walk() }; + } + + if (token.type === WhenTokenType.IDENTIFIER) { + const nextToken = tokens[current + 1]; + if (nextToken.type === WhenTokenType.OPEN_PAREN) { + let name = token.value; + token = tokens[++current]; + + let args = []; + token = tokens[++current]; + + while (token.type !== WhenTokenType.CLOSE_PAREN) { + if (token.type === WhenTokenType.COMMA) { + current++; + } else { + args.push(walk()); + } + token = tokens[current]; + } + + current++; + node = { type: WhenTokenType.FUNCTION, name, args }; + } else { + current++; + node = { type: WhenTokenType.IDENTIFIER, value: token.value }; + } + } + + if (!node) throw new TypeError('Unexpected token: ' + token.type); + + token = tokens[current]; + while (current < tokens.length && precedence[token.type] >= precedenceLevel) { + console.log(current, tokens[current], tokens[current].type, precedenceLevel, precedence[token.type]); + token = tokens[current]; + if (token.type === WhenTokenType.EQUALS || token.type === WhenTokenType.AND || token.type === WhenTokenType.OR) { + current++; + node = { + type: token.type, + left: node, + right: walk(precedence[token.type]), + }; + } else { + break; + } + } + + return node; + + }; + + return walk(); +}; + +export { + parseWhen, + WhenAST, + WhenTokenType, +}; \ No newline at end of file diff --git a/sdnr/wt-odlux/odlux/apps/configurationApp/src/yang/yangParser.ts b/sdnr/wt-odlux/odlux/apps/configurationApp/src/yang/yangParser.ts new file mode 100644 index 000000000..2dbbae274 --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/configurationApp/src/yang/yangParser.ts @@ -0,0 +1,1625 @@ +/* eslint-disable @typescript-eslint/no-loss-of-precision */ +/* eslint-disable @typescript-eslint/no-unused-expressions */ +/* eslint-disable @typescript-eslint/naming-convention */ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * 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. + * ============LICENSE_END========================================================================== + */ +import { Token, Statement, Module, Identity, ModuleState } from '../models/yang'; +import { + Expression, + ViewElement, + ViewElementBase, + ViewSpecification, + ViewElementNumber, + ViewElementString, + ViewElementChoice, + ViewElementUnion, + ViewElementRpc, + isViewElementObjectOrList, + isViewElementNumber, + isViewElementString, + isViewElementRpc, + ResolveFunction, + YangRange, +} from '../models/uiModels'; +import { yangService } from '../services/yangService'; + +const LOGLEVEL = +(localStorage.getItem('log.odlux.app.configuration.yang.yangParser') || 0); + +import { LogLevel } from '../../../../framework/src/utilities/logLevel'; +import { parseWhen, WhenAST, WhenTokenType } from './whenParser'; + +export const splitVPath = (vPath: string, vPathParser: RegExp): RegExpMatchArray[] => { + const pathParts: RegExpMatchArray[] = []; + let partMatch: RegExpExecArray | null; + if (vPath) do { + partMatch = vPathParser.exec(vPath); + if (partMatch) { + pathParts.push(partMatch); + } + } while (partMatch); + return pathParts; +}; + +class YangLexer { + + private pos: number = 0; + + private buf: string = ''; + + constructor(input: string) { + this.pos = 0; + this.buf = input; + } + + private _opTable: { [key: string]: string } = { + ';': 'SEMI', + '{': 'L_BRACE', + '}': 'R_BRACE', + }; + + private _isNewline(char: string): boolean { + return char === '\r' || char === '\n'; + } + + private _isWhitespace(char: string): boolean { + return char === ' ' || char === '\t' || this._isNewline(char); + } + + private _isDigit(char: string): boolean { + return char >= '0' && char <= '9'; + } + + private _isAlpha(char: string): boolean { + return (char >= 'a' && char <= 'z') || + (char >= 'A' && char <= 'Z'); + } + + private _isAlphanum(char: string): boolean { + return this._isAlpha(char) || this._isDigit(char) || + char === '_' || char === '-' || char === '.'; + } + + private _skipNonTokens() { + while (this.pos < this.buf.length) { + const char = this.buf.charAt(this.pos); + if (this._isWhitespace(char)) { + this.pos++; + } else { + break; + } + } + } + + private _processString(terminator: string | null): Token { + // this.pos points at the opening quote. Find the ending quote. + let end_index = this.pos + 1; + while (end_index < this.buf.length) { + const char = this.buf.charAt(end_index); + if (char === '\\') { + end_index += 2; + continue; + } + if (terminator === null && (this._isWhitespace(char) || this._opTable[char] !== undefined) || char === terminator) { + break; + } + end_index++; + } + + if (end_index >= this.buf.length) { + throw Error('Unterminated quote at ' + this.pos); + } else { + const start = this.pos + (terminator ? 1 : 0); + const end = end_index; + const tok = { + name: 'STRING', + value: this.buf.substring(start, end), + start, + end, + }; + this.pos = terminator ? end + 1 : end; + return tok; + } + } + + private _processIdentifier(): Token { + let endpos = this.pos + 1; + while (endpos < this.buf.length && this._isAlphanum(this.buf.charAt(endpos))) { + ++endpos; + } + + let name = 'IDENTIFIER'; + if (this.buf.charAt(endpos) === ':') { + name = 'IDENTIFIERREF'; + ++endpos; + while (endpos < this.buf.length && this._isAlphanum(this.buf.charAt(endpos))) { + ++endpos; + } + } + + const tok = { + name: name, + value: this.buf.substring(this.pos, endpos), + start: this.pos, + end: endpos, + }; + + this.pos = endpos; + return tok; + } + + private _processNumber(): Token { + let endpos = this.pos + 1; + while (endpos < this.buf.length && + this._isDigit(this.buf.charAt(endpos))) { + endpos++; + } + + const tok = { + name: 'NUMBER', + value: this.buf.substring(this.pos, endpos), + start: this.pos, + end: endpos, + }; + this.pos = endpos; + return tok; + } + + private _processLineComment() { + var endpos = this.pos + 2; + // Skip until the end of the line + while (endpos < this.buf.length && !this._isNewline(this.buf.charAt(endpos))) { + endpos++; + } + this.pos = endpos + 1; + } + + private _processBlockComment() { + var endpos = this.pos + 2; + // Skip until the end of the line + while (endpos < this.buf.length && !((this.buf.charAt(endpos) === '/' && this.buf.charAt(endpos - 1) === '*'))) { + endpos++; + } + this.pos = endpos + 1; + } + + public tokenize(): Token[] { + const result: Token[] = []; + this._skipNonTokens(); + while (this.pos < this.buf.length) { + + const char = this.buf.charAt(this.pos); + const op = this._opTable[char]; + + if (op !== undefined) { + result.push({ name: op, value: char, start: this.pos, end: ++this.pos }); + } else if (this._isAlpha(char)) { + result.push(this._processIdentifier()); + this._skipNonTokens(); + const peekChar = this.buf.charAt(this.pos); + if (this._opTable[peekChar] === undefined) { + result.push((peekChar !== '\'' && peekChar !== '"') + ? this._processString(null) + : this._processString(peekChar)); + } + } else if (char === '/' && this.buf.charAt(this.pos + 1) === '/') { + this._processLineComment(); + } else if (char === '/' && this.buf.charAt(this.pos + 1) === '*') { + this._processBlockComment(); + } else { + throw Error('Token error at ' + this.pos + ' ' + this.buf[this.pos]); + } + this._skipNonTokens(); + } + return result; + } + + public tokenize2(): Statement { + let stack: Statement[] = [{ key: 'ROOT', sub: [] }]; + let current: Statement | null = null; + + this._skipNonTokens(); + while (this.pos < this.buf.length) { + + const char = this.buf.charAt(this.pos); + const op = this._opTable[char]; + + if (op !== undefined) { + if (op === 'L_BRACE') { + current && stack.unshift(current); + current = null; + } else if (op === 'R_BRACE') { + current = stack.shift() || null; + } + this.pos++; + } else if (this._isAlpha(char) || char === '_') { + const key = this._processIdentifier().value; + this._skipNonTokens(); + let peekChar = this.buf.charAt(this.pos); + let arg = undefined; + if (this._opTable[peekChar] === undefined) { + arg = (peekChar === '"' || peekChar === '\'') + ? this._processString(peekChar).value + : this._processString(null).value; + } + do { + this._skipNonTokens(); + peekChar = this.buf.charAt(this.pos); + if (peekChar !== '+') break; + this.pos++; + this._skipNonTokens(); + peekChar = this.buf.charAt(this.pos); + arg += (peekChar === '"' || peekChar === '\'') + ? this._processString(peekChar).value + : this._processString(null).value; + } while (true); + current = { key, arg, sub: [] }; + stack[0].sub!.push(current); + } else if (char === '/' && this.buf.charAt(this.pos + 1) === '/') { + this._processLineComment(); + } else if (char === '/' && this.buf.charAt(this.pos + 1) === '*') { + this._processBlockComment(); + } else { + throw Error('Token error at ' + this.pos + ' ' + this.buf.slice(this.pos - 10, this.pos + 10)); + } + this._skipNonTokens(); + } + if (stack[0].key !== 'ROOT' || !stack[0].sub![0]) { + throw new Error('Internal Perser Error'); + } + return stack[0].sub![0]; + } +} + +export class YangParser { + private _groupingsToResolve: ViewSpecification[] = []; + + private _typeRefToResolve: (() => void)[] = []; + + private _identityToResolve: (() => void)[] = []; + + private _unionsToResolve: (() => void)[] = []; + + private _modulesToResolve: (() => void)[] = []; + + private _modules: { [name: string]: Module } = {}; + + private _views: ViewSpecification[] = [{ + id: '0', + name: 'root', + language: 'en-US', + canEdit: false, + config: true, + parentView: '0', + title: 'root', + elements: {}, + }]; + + public static ResolveStack = Symbol('ResolveStack'); + + constructor( + private nodeId: string, + private _capabilityRevisionMap: { [capability: string]: string } = {}, + private _unavailableCapabilities: { failureReason: string; capability: string }[] = [], + private _importOnlyModules: { name: string; revision: string }[] = [], + ) { + + } + + public get modules() { + return this._modules; + } + + public get views() { + return this._views; + } + + public async addCapability(capability: string, version?: string, parentImportOnlyModule?: boolean) { + // do not add twice + const existingCapability = this._modules[capability]; + const latestVersionExisting = existingCapability && Object.keys(existingCapability.revisions).sort().reverse()[0]; + if ((latestVersionExisting && version) && (version <= latestVersionExisting)) { + if (LOGLEVEL == LogLevel.Warning) { + console.warn(`Skipped capability: ${capability}:${version || ''} since already contained.`); + } + return; + } + + // // do not add unavailable capabilities + // if (this._unavailableCapabilities.some(c => c.capability === capability)) { + // // console.warn(`Skipped capability: ${capability} since it is marked as unavailable.` ); + // return; + // } + + const data = await yangService.getCapability(capability, this.nodeId, version); + if (!data) { + throw new Error(`Could not load yang file for ${capability}:${version || ''}.`); + } + + const rootStatement = new YangLexer(data).tokenize2(); + + if (rootStatement.key !== 'module') { + throw new Error(`Root element of ${capability} is not a module.`); + } + if (rootStatement.arg !== capability) { + throw new Error(`Root element capability ${rootStatement.arg} does not requested ${capability}.`); + } + + const isUnavailable = this._unavailableCapabilities.some(c => c.capability === capability); + const isImportOnly = parentImportOnlyModule === true || this._importOnlyModules.some(c => c.name === capability); + + // extract revisions + const revisions = this.extractNodes(rootStatement, 'revision').reduce<{ [version: string]: {} }>((acc, revision) => { + if (!revision.arg) { + throw new Error(`Module [${rootStatement.arg}] has a version w/o version number.`); + } + const description = this.extractValue(revision, 'description'); + const reference = this.extractValue(revision, 'reference'); + acc[revision.arg] = { + description, + reference, + }; + return acc; + }, {}); + + const latestVersionLoaded = Object.keys(revisions).sort().reverse()[0]; + if (existingCapability && latestVersionExisting >= latestVersionLoaded) { + if (LOGLEVEL == LogLevel.Warning) { + console.warn(`Skipped capability: ${capability}:${latestVersionLoaded} since ${capability}:${latestVersionExisting} already contained.`); + } + return; + } + + const module = this._modules[capability] = { + name: rootStatement.arg, + revisions, + imports: {}, + features: {}, + identities: {}, + augments: {}, + groupings: {}, + typedefs: {}, + views: {}, + elements: {}, + state: isUnavailable + ? ModuleState.unavailable + : isImportOnly + ? ModuleState.importOnly + : ModuleState.stable, + }; + + await this.handleModule(module, rootStatement, capability); + } + + private async handleModule(module: Module, rootStatement: Statement, capability: string) { + + // extract namespace && prefix + module.namespace = this.extractValue(rootStatement, 'namespace'); + module.prefix = this.extractValue(rootStatement, 'prefix'); + if (module.prefix) { + module.imports[module.prefix] = capability; + } + + // extract features + const features = this.extractNodes(rootStatement, 'feature'); + module.features = { + ...module.features, + ...features.reduce<{ [version: string]: {} }>((acc, feature) => { + if (!feature.arg) { + throw new Error(`Module [${module.name}] has a feature w/o name.`); + } + const description = this.extractValue(feature, 'description'); + acc[feature.arg] = { + description, + }; + return acc; + }, {}), + }; + + // extract imports + const imports = this.extractNodes(rootStatement, 'import'); + module.imports = { + ...module.imports, + ...imports.reduce<{ [key: string]: string }>((acc, imp) => { + const prefix = imp.sub && imp.sub.filter(s => s.key === 'prefix'); + if (!imp.arg) { + throw new Error(`Module [${module.name}] has an import with neither name nor prefix.`); + } + acc[prefix && prefix.length === 1 && prefix[0].arg || imp.arg] = imp.arg; + return acc; + }, {}), + }; + + // import all required files and set module state + if (imports) for (let ind = 0; ind < imports.length; ++ind) { + const moduleName = imports[ind].arg!; + + const revision = this._capabilityRevisionMap[moduleName] || undefined; + await this.addCapability(moduleName, revision, module.state === ModuleState.importOnly); + const importedModule = this._modules[imports[ind].arg!]; + if (importedModule && importedModule.state > ModuleState.stable) { + module.state = Math.max(module.state, ModuleState.instable); + } + } + + this.extractTypeDefinitions(rootStatement, module, ''); + + this.extractIdentities(rootStatement, 0, module, ''); + + const groupings = this.extractGroupings(rootStatement, 0, module, ''); + this._views.push(...groupings); + + const augments = this.extractAugments(rootStatement, 0, module, ''); + this._views.push(...augments); + + // the default for config on module level is config = true; + const [currentView, subViews] = this.extractSubViews(rootStatement, 0, module, ''); + this._views.push(currentView, ...subViews); + + // create the root elements for this module + module.elements = currentView.elements; + this._modulesToResolve.push(() => { + Object.keys(module.elements).forEach(key => { + const viewElement = module.elements[key]; + if (!(isViewElementObjectOrList(viewElement) || isViewElementRpc(viewElement))) { + console.error(new Error(`Module: [${module}]. Only Object, List or RPC are allowed on root level.`)); + } + if (isViewElementObjectOrList(viewElement)) { + const viewIdIndex = Number(viewElement.viewId); + module.views[key] = this._views[viewIdIndex]; + } + + // add only the UI View if the module is available + if (module.state === ModuleState.stable || module.state === ModuleState.instable) this._views[0].elements[key] = module.elements[key]; + }); + }); + return module; + } + + public postProcess() { + + // execute all post processes like resolving in proper order + this._unionsToResolve.forEach(cb => { + try { cb(); } catch (error) { + console.warn(error.message); + } + }); + + // process all groupings + this._groupingsToResolve.filter(vs => vs.uses && vs.uses[ResolveFunction]).forEach(vs => { + try { vs.uses![ResolveFunction] !== undefined && vs.uses![ResolveFunction]!('|'); } catch (error) { + console.warn(`Error resolving: [${vs.name}] [${error.message}]`); + } + }); + + /** + * This is to fix the issue for sequential execution of modules based on their child and parent relationship + * We are sorting the module object based on their augment status + */ + Object.keys(this.modules) + .map(elem => { + if (this.modules[elem].augments && Object.keys(this.modules[elem].augments).length > 0) { + const { augments, ..._rest } = this.modules[elem]; + const partsOfKeys = Object.keys(augments).map((key) => (key.split('/').length - 1)); + this.modules[elem].executionOrder = Math.max(...partsOfKeys); + } else { + this.modules[elem].executionOrder = 0; + } + }); + + // process all augmentations / sort by namespace changes to ensure proper order + Object.keys(this.modules).sort((a, b) => this.modules[a].executionOrder! - this.modules[b].executionOrder!).forEach(modKey => { + const module = this.modules[modKey]; + const augmentKeysWithCounter = Object.keys(module.augments).map((key) => { + const pathParts = splitVPath(key, /(?:(?:([^\/\:]+):)?([^\/]+))/g); // 1 = opt: namespace / 2 = property + let nameSpaceChangeCounter = 0; + let currentNS = module.name; // init namespace + pathParts.forEach(([ns, _]) => { + if (ns === currentNS) { + currentNS = ns; + nameSpaceChangeCounter++; + } + }); + return { + key, + nameSpaceChangeCounter, + }; + }); + + const augmentKeys = augmentKeysWithCounter + .sort((a, b) => a.nameSpaceChangeCounter > b.nameSpaceChangeCounter ? 1 : a.nameSpaceChangeCounter === b.nameSpaceChangeCounter ? 0 : -1) + .map((a) => a.key); + + augmentKeys.forEach(augKey => { + const augments = module.augments[augKey]; + const viewSpec = this.resolveView(augKey); + if (!viewSpec) console.warn(`Could not find view to augment [${augKey}] in [${module.name}].`); + if (augments && viewSpec) { + augments.forEach(augment => Object.keys(augment.elements).forEach(key => { + const elm = augment.elements[key]; + + const when = elm.when && augment.when + ? { + type: WhenTokenType.AND, + left: elm.when, + right: augment.when, + } + : elm.when || augment.when; + + const ifFeature = elm.ifFeature + ? `(${augment.ifFeature}) and (${elm.ifFeature})` + : augment.ifFeature; + + viewSpec.elements[key] = { + ...augment.elements[key], + when, + ifFeature, + }; + })); + } + }); + }); + + // process Identities + const traverseIdentity = (identities: Identity[]) => { + const result: Identity[] = []; + for (let identity of identities) { + if (identity.children && identity.children.length > 0) { + result.push(...traverseIdentity(identity.children)); + } else { + result.push(identity); + } + } + return result; + }; + + const baseIdentities: Identity[] = []; + Object.keys(this.modules).forEach(modKey => { + const module = this.modules[modKey]; + Object.keys(module.identities).forEach(idKey => { + const identity = module.identities[idKey]; + if (identity.base != null) { + const base = this.resolveIdentity(identity.base, module); + base.children?.push(identity); + } else { + baseIdentities.push(identity); + } + }); + }); + baseIdentities.forEach(identity => { + identity.values = identity.children && traverseIdentity(identity.children) || []; + }); + + this._identityToResolve.forEach(cb => { + try { cb(); } catch (error) { + console.warn(error.message); + } + }); + + this._typeRefToResolve.forEach(cb => { + try { cb(); } catch (error) { + console.warn(error.message); + } + }); + + this._modulesToResolve.forEach(cb => { + try { cb(); } catch (error) { + console.warn(error.message); + } + }); + + // resolve readOnly + const resolveReadOnly = (view: ViewSpecification, parentConfig: boolean) => { + + // update view config + view.config = view.config && parentConfig; + + Object.keys(view.elements).forEach((key) => { + const elm = view.elements[key]; + + // update element config + elm.config = elm.config && view.config; + + // update all sub-elements of objects + if (elm.uiType === 'object') { + resolveReadOnly(this.views[+elm.viewId], elm.config); + } + + }); + }; + + const dump = resolveReadOnly(this.views[0], true); + if (LOGLEVEL > 2) { + console.log('Resolved views:', dump); + } + } + + private _nextId = 1; + + private get nextId() { + return this._nextId++; + } + + private extractNodes(statement: Statement, key: string): Statement[] { + return statement.sub && statement.sub.filter(s => s.key === key) || []; + } + + private extractValue(statement: Statement, key: string): string | undefined; + private extractValue(statement: Statement, key: string, parser: RegExp): RegExpExecArray | undefined; + private extractValue(statement: Statement, key: string, parser?: RegExp): string | RegExpExecArray | undefined { + const typeNodes = this.extractNodes(statement, key); + const rawValue = typeNodes.length > 0 && typeNodes[0].arg || undefined; + return parser + ? rawValue && parser.exec(rawValue) || undefined + : rawValue; + } + + private extractTypeDefinitions(statement: Statement, module: Module, currentPath: string): void { + const typedefs = this.extractNodes(statement, 'typedef'); + typedefs && typedefs.forEach(def => { + if (!def.arg) { + throw new Error(`Module: [${module.name}]. Found typedef without name.`); + } + module.typedefs[def.arg] = this.getViewElement(def, module, 0, currentPath, false); + }); + } + + /** Handles groupings like named Container */ + private extractGroupings(statement: Statement, parentId: number, module: Module, currentPath: string): ViewSpecification[] { + const subViews: ViewSpecification[] = []; + const groupings = this.extractNodes(statement, 'grouping'); + if (groupings && groupings.length > 0) { + subViews.push(...groupings.reduce((acc, cur) => { + if (!cur.arg) { + throw new Error(`Module: [${module.name}][${currentPath}]. Found grouping without name.`); + } + const grouping = cur.arg; + + // the default for config on module level is config = true; + const [currentView, currentSubViews] = this.extractSubViews(cur, /* parentId */ -1, module, currentPath); + grouping && (module.groupings[grouping] = currentView); + acc.push(currentView, ...currentSubViews); + return acc; + }, [])); + } + + return subViews; + } + + /** Handles augments also like named container */ + private extractAugments(statement: Statement, parentId: number, module: Module, currentPath: string): ViewSpecification[] { + const subViews: ViewSpecification[] = []; + const augments = this.extractNodes(statement, 'augment'); + if (augments && augments.length > 0) { + subViews.push(...augments.reduce((acc, cur) => { + if (!cur.arg) { + throw new Error(`Module: [${module.name}][${currentPath}]. Found augment without path.`); + } + const augment = this.resolveReferencePath(cur.arg, module); + + // the default for config on module level is config = true; + const [currentView, currentSubViews] = this.extractSubViews(cur, parentId, module, currentPath); + if (augment) { + module.augments[augment] = module.augments[augment] || []; + module.augments[augment].push(currentView); + } + acc.push(currentView, ...currentSubViews); + return acc; + }, [])); + } + + return subViews; + } + + /** Handles identities */ + private extractIdentities(statement: Statement, parentId: number, module: Module, currentPath: string) { + const identities = this.extractNodes(statement, 'identity'); + module.identities = identities.reduce<{ [name: string]: Identity }>((acc, cur) => { + if (!cur.arg) { + throw new Error(`Module: [${module.name}][${currentPath}]. Found identity without name.`); + } + acc[cur.arg] = { + id: `${module.name}:${cur.arg}`, + label: cur.arg, + base: this.extractValue(cur, 'base'), + description: this.extractValue(cur, 'description'), + reference: this.extractValue(cur, 'reference'), + children: [], + }; + return acc; + }, {}); + } + + // Hint: use 0 as parentId for rootElements and -1 for rootGroupings. + private extractSubViews(statement: Statement, parentId: number, module: Module, currentPath: string): [ViewSpecification, ViewSpecification[]] { + // used for scoped definitions + const context: Module = { + ...module, + typedefs: { + ...module.typedefs, + }, + }; + + const currentId = this.nextId; + const subViews: ViewSpecification[] = []; + let elements: ViewElement[] = []; + + const configValue = this.extractValue(statement, 'config'); + const config = configValue == null ? true : configValue.toLocaleLowerCase() !== 'false'; + + // extract conditions + const ifFeature = this.extractValue(statement, 'if-feature'); + const whenCondition = this.extractValue(statement, 'when'); + if (whenCondition) console.warn('Found in [' + context.name + ']' + currentPath + ' when: ' + whenCondition); + + // extract all scoped typedefs + this.extractTypeDefinitions(statement, context, currentPath); + + // extract all scoped groupings + subViews.push( + ...this.extractGroupings(statement, parentId, context, currentPath), + ); + + // extract all container + const container = this.extractNodes(statement, 'container'); + if (container && container.length > 0) { + subViews.push(...container.reduce((acc, cur) => { + if (!cur.arg) { + throw new Error(`Module: [${context.name}]${currentPath}. Found container without name.`); + } + const [currentView, currentSubViews] = this.extractSubViews(cur, currentId, context, `${currentPath}/${context.name}:${cur.arg}`); + elements.push({ + id: parentId === 0 ? `${context.name}:${cur.arg}` : cur.arg, + label: cur.arg, + path: currentPath, + module: context.name || module.name || '', + uiType: 'object', + viewId: currentView.id, + config: currentView.config, + }); + acc.push(currentView, ...currentSubViews); + return acc; + }, [])); + } + + // process all lists + // a list is a list of containers with the leafs contained in the list + const lists = this.extractNodes(statement, 'list'); + if (lists && lists.length > 0) { + subViews.push(...lists.reduce((acc, cur) => { + let elmConfig = config; + if (!cur.arg) { + throw new Error(`Module: [${context.name}]${currentPath}. Found list without name.`); + } + const key = this.extractValue(cur, 'key') || undefined; + if (elmConfig && !key) { + console.warn(`Module: [${context.name}]${currentPath}. Found configurable list without key. Assume config shell be false.`); + elmConfig = false; + } + const [currentView, currentSubViews] = this.extractSubViews(cur, currentId, context, `${currentPath}/${context.name}:${cur.arg}`); + elements.push({ + id: parentId === 0 ? `${context.name}:${cur.arg}` : cur.arg, + label: cur.arg, + path: currentPath, + module: context.name || module.name || '', + isList: true, + uiType: 'object', + viewId: currentView.id, + key: key, + config: elmConfig && currentView.config, + }); + acc.push(currentView, ...currentSubViews); + return acc; + }, [])); + } + + // process all leaf-lists + // a leaf-list is a list of some type + const leafLists = this.extractNodes(statement, 'leaf-list'); + if (leafLists && leafLists.length > 0) { + elements.push(...leafLists.reduce((acc, cur) => { + const element = this.getViewElement(cur, context, parentId, currentPath, true); + element && acc.push(element); + return acc; + }, [])); + } + + // process all leafs + // a leaf is mainly a property of an object + const leafs = this.extractNodes(statement, 'leaf'); + if (leafs && leafs.length > 0) { + elements.push(...leafs.reduce((acc, cur) => { + const element = this.getViewElement(cur, context, parentId, currentPath, false); + element && acc.push(element); + return acc; + }, [])); + } + + + const choiceStms = this.extractNodes(statement, 'choice'); + if (choiceStms && choiceStms.length > 0) { + elements.push(...choiceStms.reduce((accChoice, curChoice) => { + if (!curChoice.arg) { + throw new Error(`Module: [${context.name}]${currentPath}. Found choise without name.`); + } + // extract all cases like containers + const cases: { id: string; label: string; description?: string; elements: { [name: string]: ViewElement } }[] = []; + const caseStms = this.extractNodes(curChoice, 'case'); + if (caseStms && caseStms.length > 0) { + cases.push(...caseStms.reduce((accCase, curCase) => { + if (!curCase.arg) { + throw new Error(`Module: [${context.name}]${currentPath}/${curChoice.arg}. Found case without name.`); + } + const description = this.extractValue(curCase, 'description') || undefined; + const [caseView, caseSubViews] = this.extractSubViews(curCase, parentId, context, `${currentPath}/${context.name}:${curChoice.arg}`); + subViews.push(caseView, ...caseSubViews); + + const caseDef: { id: string; label: string; description?: string; elements: { [name: string]: ViewElement } } = { + id: parentId === 0 ? `${context.name}:${curCase.arg}` : curCase.arg, + label: curCase.arg, + description: description, + elements: caseView.elements, + }; + accCase.push(caseDef); + return accCase; + }, [] as { id: string; label: string; description?: string; elements: { [name: string]: ViewElement } }[])); + } + + // extract all simple cases (one case per leaf, container, etc.) + const [choiceView, choiceSubViews] = this.extractSubViews(curChoice, parentId, context, `${currentPath}/${context.name}:${curChoice.arg}`); + subViews.push(choiceView, ...choiceSubViews); + cases.push(...Object.keys(choiceView.elements).reduce((accElm, curElm) => { + const elm = choiceView.elements[curElm]; + const caseDef: { id: string; label: string; description?: string; elements: { [name: string]: ViewElement } } = { + id: elm.id, + label: elm.label, + description: elm.description, + elements: { [elm.id]: elm }, + }; + accElm.push(caseDef); + return accElm; + }, [] as { id: string; label: string; description?: string; elements: { [name: string]: ViewElement } }[])); + + const choiceDescription = this.extractValue(curChoice, 'description') || undefined; + const choiceConfigValue = this.extractValue(curChoice, 'config'); + const choiceConfig = choiceConfigValue == null ? true : choiceConfigValue.toLocaleLowerCase() !== 'false'; + + const mandatory = this.extractValue(curChoice, 'mandatory') === 'true' || false; + + const element: ViewElementChoice = { + uiType: 'choice', + id: parentId === 0 ? `${context.name}:${curChoice.arg}` : curChoice.arg, + label: curChoice.arg, + path: currentPath, + module: context.name || module.name || '', + config: choiceConfig, + mandatory: mandatory, + description: choiceDescription, + cases: cases.reduce((acc, cur) => { + acc[cur.id] = cur; + return acc; + }, {} as { [name: string]: { id: string; label: string; description?: string; elements: { [name: string]: ViewElement } } }), + }; + + accChoice.push(element); + return accChoice; + }, [])); + } + + const rpcStms = this.extractNodes(statement, 'rpc'); + if (rpcStms && rpcStms.length > 0) { + elements.push(...rpcStms.reduce((accRpc, curRpc) => { + if (!curRpc.arg) { + throw new Error(`Module: [${context.name}]${currentPath}. Found rpc without name.`); + } + + const rpcDescription = this.extractValue(curRpc, 'description') || undefined; + const rpcConfigValue = this.extractValue(curRpc, 'config'); + const rpcConfig = rpcConfigValue == null ? true : rpcConfigValue.toLocaleLowerCase() !== 'false'; + + let inputViewId: string | undefined = undefined; + let outputViewId: string | undefined = undefined; + + const input = this.extractNodes(curRpc, 'input') || undefined; + const output = this.extractNodes(curRpc, 'output') || undefined; + + if (input && input.length > 0) { + const [inputView, inputSubViews] = this.extractSubViews(input[0], parentId, context, `${currentPath}/${context.name}:${curRpc.arg}`); + subViews.push(inputView, ...inputSubViews); + inputViewId = inputView.id; + } + + if (output && output.length > 0) { + const [outputView, outputSubViews] = this.extractSubViews(output[0], parentId, context, `${currentPath}/${context.name}:${curRpc.arg}`); + subViews.push(outputView, ...outputSubViews); + outputViewId = outputView.id; + } + + const element: ViewElementRpc = { + uiType: 'rpc', + id: parentId === 0 ? `${context.name}:${curRpc.arg}` : curRpc.arg, + label: curRpc.arg, + path: currentPath, + module: context.name || module.name || '', + config: rpcConfig, + description: rpcDescription, + inputViewId: inputViewId, + outputViewId: outputViewId, + }; + + accRpc.push(element); + + return accRpc; + }, [])); + } + + if (!statement.arg) { + console.error(new Error(`Module: [${context.name}]. Found statement without name.`)); + } + + let whenParsed: WhenAST | undefined = undefined; + try { + whenParsed = whenCondition && parseWhen(whenCondition) || undefined; + } catch (e) { + console.error(new Error(`Module: [${context.name}]. Found invalid when condition: ${whenCondition}`)); + } + + const viewSpec: ViewSpecification = { + id: String(currentId), + parentView: String(parentId), + ns: context.name, + name: statement.arg != null ? statement.arg : undefined, + title: statement.arg != null ? statement.arg : undefined, + language: 'en-us', + canEdit: false, + config: config, + ifFeature: ifFeature, + when: whenParsed, + elements: elements.reduce<{ [name: string]: ViewElement }>((acc, cur) => { + acc[cur.id] = cur; + return acc; + }, {}), + }; + + // evaluate canEdit depending on all conditions + Object.defineProperty(viewSpec, 'canEdit', { + get: () => { + return Object.keys(viewSpec.elements).some(key => { + const elm = viewSpec.elements[key]; + return (!isViewElementObjectOrList(elm) && elm.config); + }); + }, + }); + + // merge in all uses references and resolve groupings + const usesRefs = this.extractNodes(statement, 'uses'); + if (usesRefs && usesRefs.length > 0) { + + viewSpec.uses = (viewSpec.uses || []); + const resolveFunctions: ((parentElementPath: string) => void)[] = []; + + for (let i = 0; i < usesRefs.length; ++i) { + const groupingName = usesRefs[i].arg; + if (!groupingName) { + throw new Error(`Module: [${context.name}]. Found an uses statement without a grouping name.`); + } + + viewSpec.uses.push(this.resolveReferencePath(groupingName, context)); + + resolveFunctions.push((parentElementPath: string) => { + const groupingViewSpec = this.resolveGrouping(groupingName, context); + if (groupingViewSpec) { + + // resolve recursive + const resolveFunc = groupingViewSpec.uses && groupingViewSpec.uses[ResolveFunction]; + resolveFunc && resolveFunc(parentElementPath); + + Object.keys(groupingViewSpec.elements).forEach(key => { + const elm = groupingViewSpec.elements[key]; + // a useRef on root level need a namespace + const resolvedWhen = elm.when && groupingViewSpec.when + ? { + type: WhenTokenType.AND, + left: elm.when, + right: groupingViewSpec.when, + } + : elm.when || groupingViewSpec.when; + + const resolvedIfFeature = elm.ifFeature + ? `(${groupingViewSpec.ifFeature}) and (${elm.ifFeature})` + : groupingViewSpec.ifFeature; + + viewSpec.elements[parentId === 0 ? `${module.name}:${key}` : key] = { + ...elm, + when: resolvedWhen, + ifFeature: resolvedIfFeature, + }; + }); + } + }); + } + + viewSpec.uses[ResolveFunction] = (parentElementPath: string) => { + const currentElementPath = `${parentElementPath} -> ${viewSpec.ns}:${viewSpec.name}`; + resolveFunctions.forEach(resolve => { + try { + resolve(currentElementPath); + } catch (error) { + console.error(error); + } + }); + // console.log("Resolved "+currentElementPath, viewSpec); + if (viewSpec?.uses) { + viewSpec.uses[ResolveFunction] = undefined; + } + }; + + this._groupingsToResolve.push(viewSpec); + } + + return [viewSpec, subViews]; + } + + // https://tools.ietf.org/html/rfc7950#section-9.3.4 + private static decimalRange = [ + { min: -9223372036854775808, max: 9223372036854775807 }, + { min: -922337203685477580.8, max: 922337203685477580.7 }, + { min: -92233720368547758.08, max: 92233720368547758.07 }, + { min: -9223372036854775.808, max: 9223372036854775.807 }, + { min: -922337203685477.5808, max: 922337203685477.5807 }, + { min: -92233720368547.75808, max: 92233720368547.75807 }, + { min: -9223372036854.775808, max: 9223372036854.775807 }, + { min: -922337203685.4775808, max: 922337203685.4775807 }, + { min: -92233720368.54775808, max: 92233720368.54775807 }, + { min: -9223372036.854775808, max: 9223372036.854775807 }, + { min: -922337203.6854775808, max: 922337203.6854775807 }, + { min: -92233720.36854775808, max: 92233720.36854775807 }, + { min: -9223372.036854775808, max: 9223372.036854775807 }, + { min: -922337.2036854775808, max: 922337.2036854775807 }, + { min: -92233.72036854775808, max: 92233.72036854775807 }, + { min: -9223.372036854775808, max: 9223.372036854775807 }, + { min: -922.3372036854775808, max: 922.3372036854775807 }, + { min: -92.23372036854775808, max: 92.23372036854775807 }, + { min: -9.223372036854775808, max: 9.223372036854775807 }, + ]; + + /** Extracts the UI View from the type in the cur statement. */ + private getViewElement(cur: Statement, module: Module, parentId: number, currentPath: string, isList: boolean): ViewElement { + + const type = this.extractValue(cur, 'type'); + const defaultVal = this.extractValue(cur, 'default') || undefined; + const description = this.extractValue(cur, 'description') || undefined; + + const configValue = this.extractValue(cur, 'config'); + const config = configValue == null ? true : configValue.toLocaleLowerCase() !== 'false'; + + const extractRange = (min: number, max: number, property: string = 'range'): { expression: Expression | undefined; min: number; max: number } => { + const ranges = this.extractValue(this.extractNodes(cur, 'type')[0]!, property) || undefined; + const range = ranges?.replace(/min/i, String(min)).replace(/max/i, String(max)).split('|').map(r => { + let minValue: number; + let maxValue: number; + + if (r.indexOf('..') > -1) { + const [minStr, maxStr] = r.split('..'); + minValue = Number(minStr); + maxValue = Number(maxStr); + } else if (!isNaN(maxValue = Number(r && r.trim()))) { + minValue = maxValue; + } else { + minValue = min, + maxValue = max; + } + + if (minValue > min) min = minValue; + if (maxValue < max) max = maxValue; + + return { + min: minValue, + max: maxValue, + }; + }); + return { + min: min, + max: max, + expression: range && range.length === 1 + ? range[0] + : range && range.length > 1 + ? { operation: 'OR', arguments: range } + : undefined, + }; + }; + + const extractPattern = (): Expression | undefined => { + // 2023.01.26 decision MF & SKO: we will no longer remove the backslashes from the pattern, seems to be a bug in the original code + const pattern = this.extractNodes(this.extractNodes(cur, 'type')[0]!, 'pattern').map(p => p.arg!).filter(p => !!p).map(p => `^${p/*.replace(/(?:\\(.))/g, '$1')*/}$`); + return pattern && pattern.length == 1 + ? new RegExp(pattern[0]) + : pattern && pattern.length > 1 + ? { operation: 'AND', arguments: pattern.map(p => new RegExp(p)) } + : undefined; + }; + + const mandatory = this.extractValue(cur, 'mandatory') === 'true' || false; + + if (!cur.arg) { + throw new Error(`Module: [${module.name}]. Found element without name.`); + } + + if (!type) { + throw new Error(`Module: [${module.name}].[${cur.arg}]. Found element without type.`); + } + + const element: ViewElementBase = { + id: parentId === 0 ? `${module.name}:${cur.arg}` : cur.arg, + label: cur.arg, + path: currentPath, + module: module.name || '', + config: config, + mandatory: mandatory, + isList: isList, + default: defaultVal, + description: description, + }; + + if (type === 'string') { + const length = extractRange(0, +18446744073709551615, 'length'); + return ({ + ...element, + uiType: 'string', + length: length.expression, + pattern: extractPattern(), + }); + } else if (type === 'boolean') { + return ({ + ...element, + uiType: 'boolean', + }); + } else if (type === 'uint8') { + const range = extractRange(0, +255); + return ({ + ...element, + uiType: 'number', + range: range.expression, + min: range.min, + max: range.max, + units: this.extractValue(cur, 'units') || undefined, + format: this.extractValue(cur, 'format') || undefined, + }); + } else if (type === 'uint16') { + const range = extractRange(0, +65535); + return ({ + ...element, + uiType: 'number', + range: range.expression, + min: range.min, + max: range.max, + units: this.extractValue(cur, 'units') || undefined, + format: this.extractValue(cur, 'format') || undefined, + }); + } else if (type === 'uint32') { + const range = extractRange(0, +4294967295); + return ({ + ...element, + uiType: 'number', + range: range.expression, + min: range.min, + max: range.max, + units: this.extractValue(cur, 'units') || undefined, + format: this.extractValue(cur, 'format') || undefined, + }); + } else if (type === 'uint64') { + const range = extractRange(0, +18446744073709551615); + return ({ + ...element, + uiType: 'number', + range: range.expression, + min: range.min, + max: range.max, + units: this.extractValue(cur, 'units') || undefined, + format: this.extractValue(cur, 'format') || undefined, + }); + } else if (type === 'int8') { + const range = extractRange(-128, +127); + return ({ + ...element, + uiType: 'number', + range: range.expression, + min: range.min, + max: range.max, + units: this.extractValue(cur, 'units') || undefined, + format: this.extractValue(cur, 'format') || undefined, + }); + } else if (type === 'int16') { + const range = extractRange(-32768, +32767); + return ({ + ...element, + uiType: 'number', + range: range.expression, + min: range.min, + max: range.max, + units: this.extractValue(cur, 'units') || undefined, + format: this.extractValue(cur, 'format') || undefined, + }); + } else if (type === 'int32') { + const range = extractRange(-2147483648, +2147483647); + return ({ + ...element, + uiType: 'number', + range: range.expression, + min: range.min, + max: range.max, + units: this.extractValue(cur, 'units') || undefined, + format: this.extractValue(cur, 'format') || undefined, + }); + } else if (type === 'int64') { + const range = extractRange(-9223372036854775808, +9223372036854775807); + return ({ + ...element, + uiType: 'number', + range: range.expression, + min: range.min, + max: range.max, + units: this.extractValue(cur, 'units') || undefined, + format: this.extractValue(cur, 'format') || undefined, + }); + } else if (type === 'decimal64') { + // decimalRange + const fDigits = Number(this.extractValue(this.extractNodes(cur, 'type')[0]!, 'fraction-digits')) || -1; + if (fDigits === -1) { + throw new Error(`Module: [${module.name}][${currentPath}][${cur.arg}]. Found decimal64 with invalid fraction-digits.`); + } + const range = extractRange(YangParser.decimalRange[fDigits].min, YangParser.decimalRange[fDigits].max); + return ({ + ...element, + uiType: 'number', + fDigits: fDigits, + range: range.expression, + min: range.min, + max: range.max, + units: this.extractValue(cur, 'units') || undefined, + format: this.extractValue(cur, 'format') || undefined, + }); + } else if (type === 'enumeration') { + const typeNode = this.extractNodes(cur, 'type')[0]!; + const enumNodes = this.extractNodes(typeNode, 'enum'); + return ({ + ...element, + uiType: 'selection', + options: enumNodes.reduce<{ key: string; value: string; description?: string }[]>((acc, enumNode) => { + if (!enumNode.arg) { + throw new Error(`Module: [${module.name}][${currentPath}][${cur.arg}]. Found option without name.`); + } + // const ifClause = this.extractValue(enumNode, 'if-feature'); + const value = this.extractValue(enumNode, 'value'); + const enumOption = { + key: enumNode.arg, + value: value != null ? value : enumNode.arg, + description: this.extractValue(enumNode, 'description') || undefined, + }; + // todo: ❗ handle the if clause ⚡ + acc.push(enumOption); + return acc; + }, []), + }); + } else if (type === 'leafref') { + const typeNode = this.extractNodes(cur, 'type')[0]!; + const vPath = this.extractValue(typeNode, 'path'); + if (!vPath) { + throw new Error(`Module: [${module.name}][${currentPath}][${cur.arg}]. Found leafref without path.`); + } + const refPath = this.resolveReferencePath(vPath, module); + const resolve = this.resolveReference.bind(this); + const res: ViewElement = { + ...element, + uiType: 'reference', + referencePath: refPath, + ref(this: ViewElement, basePath: string) { + const elementPath = `${basePath}/${cur.arg}`; + + const result = resolve(refPath, elementPath); + if (!result) return undefined; + + const [resolvedElement, resolvedPath] = result; + return resolvedElement && [{ + ...resolvedElement, + id: this.id, + label: this.label, + config: this.config, + mandatory: this.mandatory, + isList: this.isList, + default: this.default, + description: this.description, + } as ViewElement, resolvedPath] || undefined; + }, + }; + return res; + } else if (type === 'identityref') { + const typeNode = this.extractNodes(cur, 'type')[0]!; + const base = this.extractValue(typeNode, 'base'); + if (!base) { + throw new Error(`Module: [${module.name}][${currentPath}][${cur.arg}]. Found identityref without base.`); + } + const res: ViewElement = { + ...element, + uiType: 'selection', + options: [], + }; + this._identityToResolve.push(() => { + const identity: Identity = this.resolveIdentity(base, module); + if (!identity) { + throw new Error(`Module: [${module.name}][${currentPath}][${cur.arg}]. Could not resolve identity [${base}].`); + } + if (!identity.values || identity.values.length === 0) { + throw new Error(`Identity: [${base}] has no values.`); + } + res.options = identity.values.map(val => ({ + key: val.id, + value: val.id, + description: val.description, + })); + }); + return res; + } else if (type === 'empty') { + // todo: ❗ handle empty ⚡ + /* 9.11. The empty Built-In Type + The empty built-in type represents a leaf that does not have any + value, it conveys information by its presence or absence. */ + return { + ...element, + uiType: 'empty', + }; + } else if (type === 'union') { + // todo: ❗ handle union ⚡ + /* 9.12. The union Built-In Type */ + const typeNode = this.extractNodes(cur, 'type')[0]!; + const typeNodes = this.extractNodes(typeNode, 'type'); + + const resultingElement = { + ...element, + uiType: 'union', + elements: [], + } as ViewElementUnion; + + const resolveUnion = () => { + resultingElement.elements.push(...typeNodes.map(node => { + const stm: Statement = { + ...cur, + sub: [ + ...(cur.sub?.filter(s => s.key !== 'type') || []), + node, + ], + }; + return { + ...this.getViewElement(stm, module, parentId, currentPath, isList), + id: node.arg!, + }; + })); + }; + + this._unionsToResolve.push(resolveUnion); + + return resultingElement; + } else if (type === 'bits') { + const typeNode = this.extractNodes(cur, 'type')[0]!; + const bitNodes = this.extractNodes(typeNode, 'bit'); + return { + ...element, + uiType: 'bits', + flags: bitNodes.reduce<{ [name: string]: number | undefined }>((acc, bitNode) => { + if (!bitNode.arg) { + throw new Error(`Module: [${module.name}][${currentPath}][${cur.arg}]. Found bit without name.`); + } + // const ifClause = this.extractValue(bitNode, 'if-feature'); + const pos = Number(this.extractValue(bitNode, 'position')); + acc[bitNode.arg] = pos === pos ? pos : undefined; + return acc; + }, {}), + }; + } else if (type === 'binary') { + return { + ...element, + uiType: 'binary', + length: extractRange(0, +18446744073709551615, 'length'), + }; + } else if (type === 'instance-identifier') { + // https://tools.ietf.org/html/rfc7950#page-168 + return { + ...element, + uiType: 'string', + length: extractRange(0, +18446744073709551615, 'length'), + }; + } else { + // not a build in type, need to resolve type + let typeRef = this.resolveType(type, module); + if (typeRef == null) console.error(new Error(`Could not resolve type ${type} in [${module.name}][${currentPath}].`)); + + if (isViewElementString(typeRef)) { + typeRef = this.resolveStringType(typeRef, extractPattern(), extractRange(0, +18446744073709551615)); + } else if (isViewElementNumber(typeRef)) { + typeRef = this.resolveNumberType(typeRef, extractRange(typeRef.min, typeRef.max)); + } + + const res = { + id: element.id, + } as ViewElement; + + this._typeRefToResolve.push(() => { + // spoof date type here from special string type + if ((type === 'date-and-time' || type.endsWith(':date-and-time')) && typeRef.module === 'ietf-yang-types') { + Object.assign(res, { + ...typeRef, + ...element, + description: description, + uiType: 'date', + }); + } else { + Object.assign(res, { + ...typeRef, + ...element, + description: description, + }); + } + }); + + return res; + } + } + + private resolveStringType(parentElement: ViewElementString, pattern: Expression | undefined, length: { expression: Expression | undefined; min: number; max: number }) { + return { + ...parentElement, + pattern: pattern != null && parentElement.pattern + ? { operation: 'AND', arguments: [pattern, parentElement.pattern] } + : parentElement.pattern + ? parentElement.pattern + : pattern, + length: length.expression != null && parentElement.length + ? { operation: 'AND', arguments: [length.expression, parentElement.length] } + : parentElement.length + ? parentElement.length + : length?.expression, + } as ViewElementString; + } + + private resolveNumberType(parentElement: ViewElementNumber, range: { expression: Expression | undefined; min: number; max: number }) { + return { + ...parentElement, + range: range.expression != null && parentElement.range + ? { operation: 'AND', arguments: [range.expression, parentElement.range] } + : parentElement.range + ? parentElement.range + : range, + min: range.min, + max: range.max, + } as ViewElementNumber; + } + + private resolveReferencePath(vPath: string, module: Module) { + const vPathParser = /(?:(?:([^\/\:]+):)?([^\/]+))/g; // 1 = opt: namespace / 2 = property + return vPath.replace(vPathParser, (_, ns, property) => { + const nameSpace = ns && module.imports[ns] || module.name; + return `${nameSpace}:${property}`; + }); + } + + private resolveReference(vPath: string, currentPath: string) { + const vPathParser = /(?:(?:([^\/\[\]\:]+):)?([^\/\[\]]+)(\[[^\]]+\])?)/g; // 1 = opt: namespace / 2 = property / 3 = opt: indexPath + let element: ViewElement | null = null; + let moduleName = ''; + + const vPathParts = splitVPath(vPath, vPathParser).map(p => ({ ns: p[1], property: p[2], ind: p[3] })); + const resultPathParts = !vPath.startsWith('/') + ? splitVPath(currentPath, vPathParser).map(p => { moduleName = p[1] || moduleName; return { ns: moduleName, property: p[2], ind: p[3] }; }) + : []; + + for (let i = 0; i < vPathParts.length; ++i) { + const vPathPart = vPathParts[i]; + if (vPathPart.property === '..') { + resultPathParts.pop(); + } else if (vPathPart.property !== '.') { + resultPathParts.push(vPathPart); + } + } + + // resolve element by path + for (let j = 0; j < resultPathParts.length; ++j) { + const pathPart = resultPathParts[j]; + if (j === 0) { + moduleName = pathPart.ns; + const rootModule = this._modules[moduleName]; + if (!rootModule) throw new Error('Could not resolve module [' + moduleName + '].\r\n' + vPath); + element = rootModule.elements[`${pathPart.ns}:${pathPart.property}`]; + } else if (element && isViewElementObjectOrList(element)) { + const view: ViewSpecification = this._views[+element.viewId]; + if (moduleName !== pathPart.ns) { + moduleName = pathPart.ns; + } + element = view.elements[pathPart.property] || view.elements[`${moduleName}:${pathPart.property}`]; + } else { + throw new Error('Could not resolve reference.\r\n' + vPath); + } + if (!element) throw new Error('Could not resolve path [' + pathPart.property + '] in [' + currentPath + '] \r\n' + vPath); + } + + moduleName = ''; // create the vPath for the resolved element, do not add the element itself this will be done later in the res(...) function + return [element, resultPathParts.slice(0, -1).map(p => `${moduleName !== p.ns ? `${moduleName = p.ns}:` : ''}${p.property}${p.ind || ''}`).join('/')]; + } + + private resolveView(vPath: string) { + const vPathParser = /(?:(?:([^\/\[\]\:]+):)?([^\/\[\]]+)(\[[^\]]+\])?)/g; // 1 = opt: namespace / 2 = property / 3 = opt: indexPath + let element: ViewElement | null = null; + let partMatch: RegExpExecArray | null; + let view: ViewSpecification | null = null; + let moduleName = ''; + if (vPath) do { + partMatch = vPathParser.exec(vPath); + if (partMatch) { + if (element === null) { + moduleName = partMatch[1]!; + const rootModule = this._modules[moduleName]; + if (!rootModule) return null; + element = rootModule.elements[`${moduleName}:${partMatch[2]!}`]; + } else if (isViewElementObjectOrList(element)) { + view = this._views[+element.viewId]; + if (moduleName !== partMatch[1]) { + moduleName = partMatch[1]; + element = view.elements[`${moduleName}:${partMatch[2]}`]; + } else { + element = view.elements[partMatch[2]]; + } + } else { + return null; + } + if (!element) return null; + } + } while (partMatch); + return element && isViewElementObjectOrList(element) && this._views[+element.viewId] || null; + } + + private resolveType(type: string, module: Module) { + const colonInd = type.indexOf(':'); + const preFix = colonInd > -1 ? type.slice(0, colonInd) : ''; + const typeName = colonInd > -1 ? type.slice(colonInd + 1) : type; + + const res = preFix + ? this._modules[module.imports[preFix]].typedefs[typeName] + : module.typedefs[typeName]; + return res; + } + + private resolveGrouping(grouping: string, module: Module) { + const collonInd = grouping.indexOf(':'); + const preFix = collonInd > -1 ? grouping.slice(0, collonInd) : ''; + const groupingName = collonInd > -1 ? grouping.slice(collonInd + 1) : grouping; + + return preFix + ? this._modules[module.imports[preFix]].groupings[groupingName] + : module.groupings[groupingName]; + + } + + private resolveIdentity(identity: string, module: Module) { + const collonInd = identity.indexOf(':'); + const preFix = collonInd > -1 ? identity.slice(0, collonInd) : ''; + const identityName = collonInd > -1 ? identity.slice(collonInd + 1) : identity; + + return preFix + ? this._modules[module.imports[preFix]].identities[identityName] + : module.identities[identityName]; + + } +} \ No newline at end of file diff --git a/sdnr/wt-odlux/odlux/apps/configurationApp/tsconfig.json b/sdnr/wt-odlux/odlux/apps/configurationApp/tsconfig.json new file mode 100644 index 000000000..c95005660 --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/configurationApp/tsconfig.json @@ -0,0 +1,38 @@ +{ + "compilerOptions": { + "baseUrl": "./src", + "outDir": "./dist", + "sourceMap": true, + "forceConsistentCasingInFileNames": true, + "allowSyntheticDefaultImports": true, + "allowUnreachableCode": false, + "allowUnusedLabels": false, + "noFallthroughCasesInSwitch": true, + "noImplicitAny": true, + "noImplicitReturns": true, + "noImplicitThis": true, + "strictNullChecks": true, + "pretty": true, + "newLine": "LF", + "module": "es2015", + "target": "es2016", + "moduleResolution": "node", + "experimentalDecorators": true, + "jsx": "preserve", + "lib": [ + "dom", + "es2015", + "es2016" + ], + "types": [ + "node", + "prop-types", + "react", + "react-dom" + ] + }, + "exclude": [ + "dist", + "node_modules" + ] +} diff --git a/sdnr/wt-odlux/odlux/apps/configurationApp/webpack.config.js b/sdnr/wt-odlux/odlux/apps/configurationApp/webpack.config.js new file mode 100644 index 000000000..2f7ab11b4 --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/configurationApp/webpack.config.js @@ -0,0 +1,150 @@ +/** + * Webpack 4 configuration file + * see https://webpack.js.org/configuration/ + * see https://webpack.js.org/configuration/dev-server/ + */ + +"use strict"; + +const path = require("path"); +const webpack = require("webpack"); +const CopyWebpackPlugin = require("copy-webpack-plugin"); +const TerserPlugin = require('terser-webpack-plugin'); +const proxyConf = require('../../proxy.conf'); + +const policies = require('./policies.json'); + +// const __dirname = (path => path.replace(/^([a-z]\:)/, c => c.toUpperCase()))(process.__dirname()); + +module.exports = (env) => { + const distPath = path.resolve(__dirname, env === "release" ? "." : "../..", "dist"); + const frameworkPath = path.resolve(__dirname, env === "release" ? "../../framework" : "../..", "dist"); + return [{ + name: "App", + + mode: "none", //disable default behavior + + target: "web", + + context: path.resolve(__dirname, "src"), + + entry: { + configurationApp: ["./pluginConfiguration.tsx"] + }, + + devtool: env === "release" ? false : "source-map", + + resolve: { + extensions: [".ts", ".tsx", ".js", ".jsx"] + }, + + output: { + path: distPath, + filename: "[name].js", + library: "configurationApp", + libraryTarget: "umd2", + chunkFilename: "[name].js" + }, + module: { + rules: [{ + test: /\.tsx?$/, + exclude: /node_modules/, + use: [{ + loader: "babel-loader" + }, { + loader: "ts-loader" + }] + }, { + test: /\.jsx?$/, + exclude: /node_modules/, + use: [{ + loader: "babel-loader" + }] + },{ + //don't minify images + test: /\.(png|gif|jpg|svg)$/, + use: [{ + loader: 'url-loader', + options: { + limit: 10, + name: './images/[name].[ext]' + } + }] + }] + }, + + optimization: { + noEmitOnErrors: true, + namedModules: env !== "release", + minimize: env === "release", + minimizer: env !== "release" ? [] : [new TerserPlugin({ + terserOptions: { + warnings: false, // false, true, "verbose" + compress: { + drop_console: true, + drop_debugger: true, + } + } + })], + }, + + plugins: [ + new webpack.DllReferencePlugin({ + context: path.resolve(__dirname, "../../framework/src"), + manifest: require(path.resolve(frameworkPath, "vendor-manifest.json")), + sourceType: "umd2" + }), + new webpack.DllReferencePlugin({ + context: path.resolve(__dirname, "../../framework/src"), + manifest: require(path.resolve(frameworkPath, "app-manifest.json")), + sourceType: "umd2" + }), + ...(env === "release" ? [ + new webpack.DefinePlugin({ + "process.env": { + NODE_ENV: "'production'", + VERSION: JSON.stringify(require("./package.json").version) + } + }), + ] : [ + new webpack.DefinePlugin({ + "process.env": { + NODE_ENV: "'development'", + VERSION: JSON.stringify(require("./package.json").version) + } + }), + new CopyWebpackPlugin([{ + from: 'index.html', + to: distPath + }]), + ]) + ], + + watchOptions: { + ignored: /node_modules/ + }, + + devServer: { + contentBase: frameworkPath, + + compress: true, + headers: { + "Access-Control-Allow-Origin": "*" + }, + host: "0.0.0.0", + port: 3100, + disableHostCheck: true, + historyApiFallback: true, + inline: true, + hot: false, + quiet: false, + stats: { + colors: true + }, + before: function(app, server, compiler) { + app.get('/oauth/policies',(_, res) => res.json(policies)); + }, + proxy: proxyConf, + } + }]; +} diff --git a/sdnr/wt-odlux/odlux/apps/connectApp/.babelrc b/sdnr/wt-odlux/odlux/apps/connectApp/.babelrc new file mode 100644 index 000000000..3d8cd1260 --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/connectApp/.babelrc @@ -0,0 +1,17 @@ +{ + "presets": [ + ["@babel/preset-react"], + ["@babel/preset-env", { + "targets": { + "chrome": "66" + }, + "spec": true, + "loose": false, + "modules": false, + "debug": false, + "useBuiltIns": "usage", + "forceAllTransforms": true + }] + ], + "plugins": [] +} diff --git a/sdnr/wt-odlux/odlux/apps/connectApp/package.json b/sdnr/wt-odlux/odlux/apps/connectApp/package.json new file mode 100644 index 000000000..9a3c35baa --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/connectApp/package.json @@ -0,0 +1,44 @@ +{ + "name": "@odlux/connect-app", + "version": "0.1.1", + "description": "A react based modular UI to display network element/node connect data from a database.", + "main": "index.js", + "scripts": { + "start": "webpack-dev-server --env debug", + "build": "webpack --env release --config webpack.config.js", + "build:dev": "webpack --env debug --config webpack.config.js" + }, + "repository": { + "type": "git", + "url": "https://git.mfico.de/highstreet-technologies/odlux.git" + }, + "keywords": [ + "reactjs", + "redux", + "ui", + "framework" + ], + "author": "Matthias Fischer", + "license": "Apache-2.0", + "dependencies": { + "@emotion/react": "^11.7.0", + "@emotion/styled": "^11.6.0", + "@mui/icons-material": "^5.2.0", + "@mui/material": "^5.2.2", + "@mui/styles": "^5.2.2", + "@odlux/framework": "*" + }, + "peerDependencies": { + "@fortawesome/free-solid-svg-icons": "5.6.3", + "@types/classnames": "2.2.6", + "@types/flux": "3.1.8", + "@types/jquery": "3.3.10", + "@types/react": "17.0.37", + "@types/react-dom": "17.0.11", + "@types/react-router-dom": "5.1.7", + "jquery": "3.3.1", + "react": "17.0.2", + "react-dom": "17.0.2", + "react-router-dom": "5.2.0" + } +} diff --git a/sdnr/wt-odlux/odlux/apps/connectApp/policies.json b/sdnr/wt-odlux/odlux/apps/connectApp/policies.json new file mode 100644 index 000000000..2ec73612e --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/connectApp/policies.json @@ -0,0 +1,12 @@ +[ + { + "path": "/rests/**/node=Sim2230**", + "methods": { + "get": true, + "post": false, + "put": false, + "delete": false, + "patch": false + } + } +] \ No newline at end of file diff --git a/sdnr/wt-odlux/odlux/apps/connectApp/pom.xml b/sdnr/wt-odlux/odlux/apps/connectApp/pom.xml new file mode 100644 index 000000000..12df827e0 --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/connectApp/pom.xml @@ -0,0 +1,109 @@ + + + + + 4.0.0 + + org.onap.ccsdk.features.sdnr.odlux + sdnr-odlux-app-connectApp + 1.7.0-SNAPSHOT + jar + + SDNR ODLUX :: ${project.artifactId} + + + Apache License, Version 2.0 + http://www.apache.org/licenses/LICENSE-2.0 + + + + + true + + + + + + dist + odlux + + + + + maven-clean-plugin + + + + dist + false + + + node + false + + + node_modules + false + + + ../node_modules + false + + + + bin + false + + + + + + de.jacks-it-lab + frontend-maven-plugin + 1.7.2 + + + install node and yarn + + install-node-and-yarn + + + initialize + + v16.17.0 + v1.22.19 + + + + yarn build + + yarn + + + run build + + + + + + + diff --git a/sdnr/wt-odlux/odlux/apps/connectApp/src/actions/commonNetworkElementsActions.ts b/sdnr/wt-odlux/odlux/apps/connectApp/src/actions/commonNetworkElementsActions.ts new file mode 100644 index 000000000..948f2aada --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/connectApp/src/actions/commonNetworkElementsActions.ts @@ -0,0 +1,141 @@ + +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * 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. + * ============LICENSE_END========================================================================== + */ + +/** + * Create an update action that can distinguish whether one or the other view is currently active and update it. + * This action is then used for each update in the other actions and when notifications arrive. + * create an update action that can distinguish whether one or the other view is currently active and update it. + * This action is then used for each update in the other actions and when notifications arrive. + */ + +import { Action } from '../../../../framework/src/flux/action'; +import { Dispatch } from '../../../../framework/src/flux/store'; +import { IApplicationStoreState } from '../../../../framework/src/store/applicationStore'; + +import { connectionStatusLogReloadAction } from '../handlers/connectionStatusLogHandler'; +import { networkElementsReloadAction } from '../handlers/networkElementsHandler'; +import { guiCutThrough } from '../models/guiCutTrough'; +import { PanelId } from '../models/panelId'; +import { connectService } from '../services/connectService'; + + +export class SetPanelAction extends Action { + constructor(public panelId: PanelId) { + super(); + } +} + +export class AddWebUriList extends Action { + constructor(public searchedElements: guiCutThrough[], public notSearchedElements: string[], public unsupportedElements: string[], public newlySearchedElements?: string[]) { + super(); + } +} + +export class RemoveWebUri extends Action { + constructor(public element: string) { + super(); + } +} + +export const removeWebUriAction = (nodeId: string) => { + return new RemoveWebUri(nodeId); +}; + +export class SetWeburiSearchBusy extends Action { + constructor(public isbusy: boolean) { + super(); + } +} + +let isBusy = false; +export const findWebUrisForGuiCutThroughAsyncAction = (networkElementIds: string[]) => async (dispatcher: Dispatch, getState: () => IApplicationStoreState) => { + + // keep method from executing simultanously; state not used because change of iu isn't needed + + if (isBusy) + return; + isBusy = true; + + const { connect: { guiCutThrough: guiCutThrough2, networkElements } } = getState(); + + let notConnectedElements: string[] = []; + let elementsToSearch: string[] = []; + let prevFoundElements: string[] = []; + let unsupportedElements: string[] = []; + + networkElementIds.forEach(id => { + const item = networkElements.rows.find((ne) => ne.id === id); + if (item) { + if (item.status === 'Connected') { + + // if (item.coreModelCapability !== "Unsupported") { + // element is connected and is added to search list, if it doesn't exist already + const exists = guiCutThrough2.searchedElements.filter(element => element.id === id).length > 0; + if (!exists) { + elementsToSearch.push(id); + + //element was found previously, but wasn't connected + if (guiCutThrough2.notSearchedElements.length > 0 && guiCutThrough2.notSearchedElements.includes(id)) { + prevFoundElements.push(id); + } + } + // } else { + // // element does not support core model and must not be searched for a weburi + // const id = item.id as string; + // const exists = guiCutThrough.unsupportedElements.filter(element => element === id).length > 0; + // if (!exists) { + // unsupportedElements.push(id); + + // //element was found previously, but wasn't connected + // if (guiCutThrough.notSearchedElements.length > 0 && guiCutThrough.notSearchedElements.includes(id)) { + // prevFoundElements.push(id); + // } + // } + // } + } else { + // element isn't connected and cannot be searched for a weburi + if (!guiCutThrough2.notSearchedElements.includes(id)) { + notConnectedElements.push(item.id as string); + } + } + } + }); + + + if (elementsToSearch.length > 0 || notConnectedElements.length > 0 || unsupportedElements.length > 0) { + const result = await connectService.getAllWebUriExtensionsForNetworkElementListAsync(elementsToSearch); + dispatcher(new AddWebUriList(result, notConnectedElements, unsupportedElements, prevFoundElements)); + } + isBusy = false; + +}; + +export const setPanelAction = (panelId: PanelId) => { + return new SetPanelAction(panelId); +}; + +export const updateCurrentViewAsyncAction = () => (dispatch: Dispatch, getState: () => IApplicationStoreState) => { + const { connect: { currentOpenPanel } } = getState(); + if (currentOpenPanel === 'NetworkElements') { + return dispatch(networkElementsReloadAction); + } else { + return dispatch(connectionStatusLogReloadAction); + } +}; + diff --git a/sdnr/wt-odlux/odlux/apps/connectApp/src/actions/infoNetworkElementActions.ts b/sdnr/wt-odlux/odlux/apps/connectApp/src/actions/infoNetworkElementActions.ts new file mode 100644 index 000000000..120f9916f --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/connectApp/src/actions/infoNetworkElementActions.ts @@ -0,0 +1,82 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * 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. + * ============LICENSE_END========================================================================== + */ +import { Action } from '../../../../framework/src/flux/action'; +import { Dispatch } from '../../../../framework/src/flux/store'; + +import { Module, TopologyNode } from '../models/topologyNetconf'; +import { connectService } from '../services/connectService'; + +/** + * Represents the base action. + */ +export class BaseAction extends Action { } + +/** + * Represents an action causing the store to load all element Yang capabilities. + */ +export class LoadAllElementInfoAction extends BaseAction { } + +/** + * Represents an action causing the store to update element Yang capabilities. + */ +export class AllElementInfoLoadedAction extends BaseAction { + /** + * Initialize this instance. + * @param elementInfo The information of the element which is returned. + */ + constructor(public elementInfo: TopologyNode | null, public error?: string) { + super(); + } +} + +/** + * Represents an action causing the store to update element Yang capabilities Module Features. + */ +export class AllElementInfoFeatureLoadedAction extends BaseAction { + /** + * Initialize this instance. + * @param elementFeatureInfo The information of the element which is returned. + */ + constructor(public elementFeatureInfo: Module[] | null | undefined, public error?: string) { + super(); + } +} + +/** + * Represents an asynchronous thunk action to load all yang capabilities. + */ +export const loadAllInfoElementAsync = (nodeId: string) => (dispatch: Dispatch) => { + dispatch(new LoadAllElementInfoAction()); + connectService.infoNetworkElement(nodeId).then(info => { + dispatch(new AllElementInfoLoadedAction(info)); + }, error => { + dispatch(new AllElementInfoLoadedAction(null, error)); + }); +}; + +/** + * Represents an asynchronous thunk action to load all yang features. + */ +export const loadAllInfoElementFeaturesAsync = (nodeId: string) => (dispatch: Dispatch) => { + dispatch(new LoadAllElementInfoAction()); + connectService.infoNetworkElementFeatures(nodeId).then(infoFeatures => { + dispatch(new AllElementInfoFeatureLoadedAction(infoFeatures)); + }, error => { + dispatch(new AllElementInfoFeatureLoadedAction(null, error)); + }); +}; \ No newline at end of file diff --git a/sdnr/wt-odlux/odlux/apps/connectApp/src/actions/mountedNetworkElementsActions.ts b/sdnr/wt-odlux/odlux/apps/connectApp/src/actions/mountedNetworkElementsActions.ts new file mode 100644 index 000000000..11bac10e4 --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/connectApp/src/actions/mountedNetworkElementsActions.ts @@ -0,0 +1,60 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * 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. + * ============LICENSE_END========================================================================== + */ + +import { Action } from '../../../../framework/src/flux/action'; +import { Dispatch } from '../../../../framework/src/flux/store'; + +import { connectService } from '../services/connectService'; +import { NetworkElementConnection } from '../models/networkElementConnection'; +import { AddSnackbarNotification } from '../../../../framework/src/actions/snackbarActions'; +import { updateCurrentViewAsyncAction } from './commonNetworkElementsActions'; + +/** Represents the base action. */ +export class BaseAction extends Action { } + +/** Represents an action creator for a async thunk action to mount a network element/node. */ +export const mountNetworkElementAsyncActionCreator = (networkElement: NetworkElementConnection) => (dispatch: Dispatch) => { + return connectService.mountNetworkElement(networkElement).then((success) => { + if (success) { + dispatch(updateCurrentViewAsyncAction()); + dispatch(new AddSnackbarNotification({ message: `Requesting mount [${networkElement.nodeId}]`, options: { variant: 'info' } })); + } else { + dispatch(new AddSnackbarNotification({ message: `Failed to mount [${networkElement.nodeId}]`, options: { variant: 'warning' } })); + } + }).catch(error => { + dispatch(new AddSnackbarNotification({ message: `Failed to mount [${networkElement.nodeId}]`, options: { variant: 'error' } })); + console.error(error); + }); +}; + +/** Represents an action creator for a async thunk action to unmount a network element/node. */ +export const unmountNetworkElementAsyncActionCreator = (nodeId: string) => (dispatch: Dispatch) => { + return connectService.unmountNetworkElement(nodeId).then((success) => { + if (success) { + dispatch(updateCurrentViewAsyncAction()); + dispatch(new AddSnackbarNotification({ message: `Requesting unmount [${nodeId}]`, options: { variant: 'info' } })); + } else { + dispatch(new AddSnackbarNotification({ message: `Failed to unmount [${nodeId}]`, options: { variant: 'warning' } })); + } + }).catch(error => { + dispatch(new AddSnackbarNotification({ message: `Failed to unmount [${nodeId}]`, options: { variant: 'error' } })); + console.error(error); + }); +}; + + diff --git a/sdnr/wt-odlux/odlux/apps/connectApp/src/actions/networkElementsActions.ts b/sdnr/wt-odlux/odlux/apps/connectApp/src/actions/networkElementsActions.ts new file mode 100644 index 000000000..d22a6c645 --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/connectApp/src/actions/networkElementsActions.ts @@ -0,0 +1,60 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * 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. + * ============LICENSE_END========================================================================== + */ +import { Action } from '../../../../framework/src/flux/action'; +import { Dispatch } from '../../../../framework/src/flux/store'; + +import { AddSnackbarNotification } from '../../../../framework/src/actions/snackbarActions'; + +import { NetworkElementConnection, ConnectionStatus, UpdateNetworkElement } from '../models/networkElementConnection'; +import { connectService } from '../services/connectService'; +import { updateCurrentViewAsyncAction } from './commonNetworkElementsActions'; +import { unmountNetworkElementAsyncActionCreator } from './mountedNetworkElementsActions'; + +/** Represents the base action. */ +export class BaseAction extends Action { } + +/** Represents an async thunk action creator to add an element to the network elements/nodes. */ +export const addNewNetworkElementAsyncActionCreator = (element: NetworkElementConnection) => async (dispatch: Dispatch) => { + await connectService.createNetworkElement({ ...element }); + dispatch(updateCurrentViewAsyncAction()); + dispatch(new AddSnackbarNotification({ message: `Successfully added [${element.nodeId}]`, options: { variant: 'success' } })); +}; + +/** Represents an async thunk action creator to edit network element/node. */ +export const editNetworkElementAsyncActionCreator = (element: UpdateNetworkElement) => async (dispatch: Dispatch) => { + const connectionStatus: ConnectionStatus[] = (await connectService.getNetworkElementConnectionStatus(element.id).then(ne => (ne))) || []; + const currentConnectionStatus = connectionStatus[0].status; + if (currentConnectionStatus === 'Disconnected') { + await connectService.deleteNetworkElement(element); + } else { + await connectService.updateNetworkElement(element); + } + dispatch(updateCurrentViewAsyncAction()); + dispatch(new AddSnackbarNotification({ message: `Successfully modified [${element.id}]`, options: { variant: 'success' } })); +}; + + +/** Represents an async thunk action creator to delete an element from network elements/nodes. */ +export const removeNetworkElementAsyncActionCreator = (element: UpdateNetworkElement) => async (dispatch: Dispatch) => { + await connectService.deleteNetworkElement(element); + await dispatch(unmountNetworkElementAsyncActionCreator(element && element.id)); + await dispatch(updateCurrentViewAsyncAction()); +}; + + + diff --git a/sdnr/wt-odlux/odlux/apps/connectApp/src/actions/tlsKeyActions.ts b/sdnr/wt-odlux/odlux/apps/connectApp/src/actions/tlsKeyActions.ts new file mode 100644 index 000000000..65d23c439 --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/connectApp/src/actions/tlsKeyActions.ts @@ -0,0 +1,59 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2021 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * 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. + * ============LICENSE_END========================================================================== + */ +import { Action } from '../../../../framework/src/flux/action'; +import { Dispatch } from '../../../../framework/src/flux/store'; + +import { TlsKeys } from '../models/networkElementConnection'; +import { connectService } from '../services/connectService'; + +/** + * Represents the base action. + */ +export class BaseAction extends Action { } + +/** + * Represents an action causing the store to load all TLS Keys. + */ +export class LoadAllTlsKeyListAction extends BaseAction { } + +/** + * Represents an action causing the store to get all TLS Keys. + */ +export class AllTlsKeyListLoadedAction extends BaseAction { + /** + * Initialize this instance. + * + * @param gets all the tlsKey list from the database. + */ + constructor(public tlsList: TlsKeys[] | null, public error?: string) { + super(); + } +} + +/** + * Represents an asynchronous thunk action to load all tlsKeys + */ + +export const loadAllTlsKeyListAsync = () => async (dispatch: Dispatch) => { + dispatch(new LoadAllTlsKeyListAction()); + connectService.getTlsKeys().then(TlsKeyList => { + dispatch(new AllTlsKeyListLoadedAction(TlsKeyList)); + }).catch(error => { + dispatch(new AllTlsKeyListLoadedAction(null, error)); + }); +}; diff --git a/sdnr/wt-odlux/odlux/apps/connectApp/src/assets/icons/connectAppIcon.svg b/sdnr/wt-odlux/odlux/apps/connectApp/src/assets/icons/connectAppIcon.svg new file mode 100644 index 000000000..5aca4fae7 --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/connectApp/src/assets/icons/connectAppIcon.svg @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + diff --git a/sdnr/wt-odlux/odlux/apps/connectApp/src/components/connectionStatusLog.tsx b/sdnr/wt-odlux/odlux/apps/connectApp/src/components/connectionStatusLog.tsx new file mode 100644 index 000000000..6a8c92438 --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/connectApp/src/components/connectionStatusLog.tsx @@ -0,0 +1,99 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * 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. + * ============LICENSE_END========================================================================== + */ +import React from 'react'; + +import Refresh from '@mui/icons-material/Refresh'; + +import { ColumnType, MaterialTable, MaterialTableCtorType } from '../../../../framework/src/components/material-table'; +import { connect, Connect, IDispatcher } from '../../../../framework/src/flux/connect'; +import { IApplicationStoreState } from '../../../../framework/src/store/applicationStore'; + +import { createConnectionStatusLogActions, createConnectionStatusLogProperties } from '../handlers/connectionStatusLogHandler'; +import { NetworkElementConnectionLog } from '../models/networkElementConnectionLog'; +import RefreshConnectionStatusLogDialog, { RefreshConnectionStatusLogDialogMode } from './refreshConnectionStatusLogDialog'; + +const mapProps = (state: IApplicationStoreState) => ({ + connectionStatusLogProperties: createConnectionStatusLogProperties(state), +}); + +const mapDispatch = (dispatcher: IDispatcher) => ({ + connectionStatusLogActions: createConnectionStatusLogActions(dispatcher.dispatch), +}); + +const ConnectionStatusTable = MaterialTable as MaterialTableCtorType; + +type ConnectionStatusLogComponentProps = Connect; +type ConnectionStatusLogComponentState = { + refreshConnectionStatusLogEditorMode: RefreshConnectionStatusLogDialogMode; +}; + +let initialSorted = false; + + +class ConnectionStatusLogComponent extends React.Component { + constructor(props: ConnectionStatusLogComponentProps) { + super(props); + + this.state = { + refreshConnectionStatusLogEditorMode: RefreshConnectionStatusLogDialogMode.None, + }; + } + + render(): JSX.Element { + const refreshConnectionStatusLogAction = { + icon: Refresh, tooltip: 'Refresh Connection Status Log Table', ariaLabel:'refresh', onClick: () => { + this.setState({ + refreshConnectionStatusLogEditorMode: RefreshConnectionStatusLogDialogMode.RefreshConnectionStatusLogTable, + }); + }, + }; + + return ( + <> + + + + + ); + } + + private onCloseRefreshConnectionStatusLogDialog = () => { + this.setState({ + refreshConnectionStatusLogEditorMode: RefreshConnectionStatusLogDialogMode.None, + }); + }; + + componentDidMount() { + if (!initialSorted) { + initialSorted = true; + this.props.connectionStatusLogActions.onHandleExplicitRequestSort('timestamp', 'desc'); + } else { + this.props.connectionStatusLogActions.onRefresh(); + } + } +} + +export const ConnectionStatusLog = connect(mapProps, mapDispatch)(ConnectionStatusLogComponent); +export default ConnectionStatusLog; \ No newline at end of file diff --git a/sdnr/wt-odlux/odlux/apps/connectApp/src/components/editNetworkElementDialog.tsx b/sdnr/wt-odlux/odlux/apps/connectApp/src/components/editNetworkElementDialog.tsx new file mode 100644 index 000000000..b0db63476 --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/connectApp/src/components/editNetworkElementDialog.tsx @@ -0,0 +1,425 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * 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. + * ============LICENSE_END========================================================================== + */ +import React from 'react'; + +import Button from '@mui/material/Button'; +import Dialog from '@mui/material/Dialog'; +import DialogActions from '@mui/material/DialogActions'; +import DialogContent from '@mui/material/DialogContent'; +import DialogContentText from '@mui/material/DialogContentText'; +import DialogTitle from '@mui/material/DialogTitle'; +import FormControl from '@mui/material/FormControl'; +import FormControlLabel from '@mui/material/FormControlLabel'; +import InputLabel from '@mui/material/InputLabel'; +import MenuItem from '@mui/material/MenuItem'; +import Radio from '@mui/material/Radio'; +import RadioGroup from '@mui/material/RadioGroup'; +import Select from '@mui/material/Select'; +import TextField from '@mui/material/TextField'; +import Typography from '@mui/material/Typography'; + +import { connect, Connect, IDispatcher } from '../../../../framework/src/flux/connect'; + +import { removeWebUriAction } from '../actions/commonNetworkElementsActions'; +import { mountNetworkElementAsyncActionCreator, unmountNetworkElementAsyncActionCreator } from '../actions/mountedNetworkElementsActions'; +import { + addNewNetworkElementAsyncActionCreator, editNetworkElementAsyncActionCreator, removeNetworkElementAsyncActionCreator, +} from '../actions/networkElementsActions'; +import { loadAllTlsKeyListAsync } from '../actions/tlsKeyActions'; +import { NetworkElementConnection, propertyOf, UpdateNetworkElement } from '../models/networkElementConnection'; + +export enum EditNetworkElementDialogMode { + None = 'none', + EditNetworkElement = 'editNetworkElement', + RemoveNetworkElement = 'removeNetworkElement', + AddNewNetworkElement = 'addNewNetworkElement', + MountNetworkElement = 'mountNetworkElement', + UnmountNetworkElement = 'unmountNetworkElement', +} + +const mapDispatch = (dispatcher: IDispatcher) => ({ + addNewNetworkElement: async (element: NetworkElementConnection) => { + await dispatcher.dispatch(addNewNetworkElementAsyncActionCreator(element)); + await dispatcher.dispatch(mountNetworkElementAsyncActionCreator(element)); + }, + mountNetworkElement: (element: NetworkElementConnection) => dispatcher.dispatch(mountNetworkElementAsyncActionCreator(element)), + unmountNetworkElement: (element: NetworkElementConnection) => { + dispatcher.dispatch(unmountNetworkElementAsyncActionCreator(element && element.nodeId)); + }, + editNetworkElement: async (element: UpdateNetworkElement, mountElement: NetworkElementConnection) => { + + const values = Object.keys(element); + console.log('edit element'); + console.log(values); + + //make sure properties are there in case they get renamed + const idProperty = propertyOf('id'); + const isRequiredProperty = propertyOf('isRequired'); + + + if (values.length === 2 && values.includes(idProperty as string) && values.includes(isRequiredProperty as string)) { + // do not mount network element/node, if only isRequired is changed + await dispatcher.dispatch(editNetworkElementAsyncActionCreator(element)); + + } else if (!(values.length === 1 && values.includes(idProperty as string))) { //do not edit or mount network element/node , if only id was saved into object (no changes made!) + await dispatcher.dispatch(editNetworkElementAsyncActionCreator(element)); + await dispatcher.dispatch(mountNetworkElementAsyncActionCreator(mountElement)); + } + }, + removeNetworkElement: async (element: UpdateNetworkElement) => { + await dispatcher.dispatch(removeNetworkElementAsyncActionCreator(element)); + dispatcher.dispatch(removeWebUriAction(element.id)); + }, + getAvailableTlsKeys: async () => dispatcher.dispatch(loadAllTlsKeyListAsync()), +}); + +type DialogSettings = { + dialogTitle: string; + dialogDescription: string; + applyButtonText: string; + cancelButtonText: string; + enableMountIdEditor: boolean; + enableUsernameEditor: boolean; + enableExtendedEditor: boolean; +}; + +const settings: { [key: string]: DialogSettings } = { + [EditNetworkElementDialogMode.None]: { + dialogTitle: '', + dialogDescription: '', + applyButtonText: '', + cancelButtonText: '', + enableMountIdEditor: false, + enableUsernameEditor: false, + enableExtendedEditor: false, + }, + + [EditNetworkElementDialogMode.AddNewNetworkElement]: { + dialogTitle: 'Add New Node', + dialogDescription: 'Add this new node:', + applyButtonText: 'Add node', + cancelButtonText: 'Cancel', + enableMountIdEditor: true, + enableUsernameEditor: true, + enableExtendedEditor: true, + }, + [EditNetworkElementDialogMode.MountNetworkElement]: { + dialogTitle: 'Mount Node', + dialogDescription: 'Mount this node:', + applyButtonText: 'Mount node', + cancelButtonText: 'Cancel', + enableMountIdEditor: false, + enableUsernameEditor: false, + enableExtendedEditor: false, + }, + [EditNetworkElementDialogMode.UnmountNetworkElement]: { + dialogTitle: 'Unmount Node', + dialogDescription: 'Unmount this node:', + applyButtonText: 'Unmount node', + cancelButtonText: 'Cancel', + enableMountIdEditor: false, + enableUsernameEditor: false, + enableExtendedEditor: false, + }, + [EditNetworkElementDialogMode.EditNetworkElement]: { + dialogTitle: 'Modify Node', + dialogDescription: 'Modify this node', + applyButtonText: 'Modify', + cancelButtonText: 'Cancel', + enableMountIdEditor: false, + enableUsernameEditor: true, + enableExtendedEditor: false, + }, + [EditNetworkElementDialogMode.RemoveNetworkElement]: { + dialogTitle: 'Remove Node', + dialogDescription: 'Do you really want to remove this node?', + applyButtonText: 'Remove node', + cancelButtonText: 'Cancel', + enableMountIdEditor: false, + enableUsernameEditor: false, + enableExtendedEditor: false, + }, +}; + +type EditNetworkElementDialogComponentProps = Connect & { + mode: EditNetworkElementDialogMode; + initialNetworkElement: NetworkElementConnection; + onClose: () => void; + radioChecked: string; +}; + +type EditNetworkElementDialogComponentState = NetworkElementConnection & { + isNameValid: boolean; + isHostSet: boolean; + isPasswordSelected: boolean; + isTlsSelected: boolean; + radioSelected: string; + showPasswordTextField: boolean; + showTlsDropdown: boolean; +}; + +class EditNetworkElementDialogComponent extends React.Component { + constructor(props: EditNetworkElementDialogComponentProps) { + super(props); + this.handleRadioChange = this.handleRadioChange.bind(this); + // Initialization of state is partly overwritten by update via react getDerivedStateFromProps() below. + // Change initialization values in parent "networkElements.tsx" in "const emptyRequireNetworkElement" + this.state = { + nodeId: this.props.initialNetworkElement.nodeId, + isRequired: this.props.initialNetworkElement.isRequired, + host: this.props.initialNetworkElement.host, + port: this.props.initialNetworkElement.port, + isNameValid: true, + isHostSet: true, + isPasswordSelected: true, + isTlsSelected: false, + radioSelected: '', + showPasswordTextField: true, + showTlsDropdown: false, + }; + } + + public handleRadioChange = (event: any) => { + this.setState({ + radioSelected: event.target.value, + showPasswordTextField: event.target.value === 'password', + showTlsDropdown: event.target.value === 'tlsKey', + }); + }; + + render(): JSX.Element { + const setting = settings[this.props.mode]; + let { showPasswordTextField, showTlsDropdown, radioSelected } = this.state; + radioSelected = this.state.radioSelected.length > 0 ? this.state.radioSelected : this.props.radioChecked; + + if (radioSelected === 'password') { + radioSelected = 'password'; + showPasswordTextField = true; + showTlsDropdown = false; + } else if (radioSelected === 'tlsKey') { + radioSelected = 'tlsKey'; + showPasswordTextField = false; + showTlsDropdown = true; + } + + let tlsKeysList = this.props.state.connect.availableTlsKeys ? this.props.state.connect.availableTlsKeys.tlsKeysList ? this.props.state.connect.availableTlsKeys.tlsKeysList : [] : []; + + return ( + + {setting.dialogTitle} + + + {setting.dialogDescription} + + { this.setState({ nodeId: event.target.value }); }} /> + {!this.state.isNameValid && Node ID cannot be empty.} + { this.setState({ host: event.target.value }); }} /> + {!this.state.isHostSet && Host/IP address cannot be empty.} + + { this.setState({ port: +event.target.value }); }} /> + {setting.enableUsernameEditor && { this.setState({ username: event.target.value }); }} /> || null} + + {setting.enableUsernameEditor && + + } label="Password" onChange={this.onRadioSelect} /> + } label="TlsKey" onChange={this.onRadioSelect} /> + || null} + + {setting.enableUsernameEditor && showPasswordTextField && + { this.setState({ password: event.target.value }); }} + /> || null} + + + {setting.enableUsernameEditor && showTlsDropdown && +
+ --Select tls-key-- + +
+ } +
+ + + Required + + +
+ + + + +
+ ); + } + + public renderTlsKeys = () => { + try { + this.props.getAvailableTlsKeys(); + } catch (err) { + console.log(err); + } + }; + + public componentDidMount() { + this.renderTlsKeys(); + } + + public onRadioSelect = (e: any) => { + if (e.target.value == 'password') { + this.setState({ isPasswordSelected: true, isTlsSelected: false }); + } else if (e.target.value == 'tlsKey') { + this.setState({ isPasswordSelected: false, isTlsSelected: true }); + } + }; + + private onApply = (element: NetworkElementConnection) => { + if (this.props.onClose) this.props.onClose(); + let updateElement: UpdateNetworkElement = { + id: this.state.nodeId, + }; + if (this.state.isPasswordSelected) { + element.tlsKey = ''; + } else if (this.state.isTlsSelected) { //check here + element.password = ''; + } + + switch (this.props.mode) { + case EditNetworkElementDialogMode.AddNewNetworkElement: + if (element) this.props.addNewNetworkElement(element); + this.setState({ + radioSelected: '', + isPasswordSelected: true, + }); + break; + case EditNetworkElementDialogMode.MountNetworkElement: + if (element) this.props.mountNetworkElement(element); + break; + case EditNetworkElementDialogMode.UnmountNetworkElement: + if (element) this.props.unmountNetworkElement(element); + break; + case EditNetworkElementDialogMode.EditNetworkElement: + if (this.props.initialNetworkElement.isRequired !== this.state.isRequired) + updateElement.isRequired = this.state.isRequired; + if (this.props.initialNetworkElement.username !== this.state.username) + updateElement.username = this.state.username; + if (this.props.initialNetworkElement.password !== this.state.password && this.state.isPasswordSelected) { + updateElement.password = this.state.password; + updateElement.tlsKey = ''; + } + if (this.props.initialNetworkElement.tlsKey !== this.state.tlsKey && this.state.isTlsSelected) { + updateElement.tlsKey = this.state.tlsKey; + updateElement.password = ''; + } + if (element) this.props.editNetworkElement(updateElement, element); + this.setState({ + radioSelected: '', + }); + break; + case EditNetworkElementDialogMode.RemoveNetworkElement: + if (element) this.props.removeNetworkElement(updateElement); + break; + } + + this.setState({ password: '', username: '', tlsKey: '' }); + this.resetRequieredFields(); + }; + + private onCancel = () => { + if (this.props.onClose) this.props.onClose(); + this.setState({ password: '', username: '', tlsKey: '', radioSelected: '' }); + this.resetRequieredFields(); + }; + + private resetRequieredFields() { + this.setState({ isNameValid: true, isHostSet: true }); + } + + private areRequieredFieldsValid() { + let areFieldsValid = true; + + if (this.state.nodeId == undefined || this.state.nodeId.trim().length === 0) { + this.setState({ isNameValid: false }); + areFieldsValid = false; + } else { + this.setState({ isNameValid: true }); + } + + if (this.state.host == undefined || this.state.host.trim().length === 0) { + this.setState({ isHostSet: false }); + areFieldsValid = false; + } else { + this.setState({ isHostSet: true }); + } + + return areFieldsValid; + } + + static getDerivedStateFromProps(props: EditNetworkElementDialogComponentProps, state: EditNetworkElementDialogComponentState & { initialNetworkElement: NetworkElementConnection }): EditNetworkElementDialogComponentState & { initialNetworkElement: NetworkElementConnection } { + let returnState = state; + if (props.initialNetworkElement !== state.initialNetworkElement) { + returnState = { + ...state, + ...props.initialNetworkElement, + initialNetworkElement: props.initialNetworkElement, + }; + } + return returnState; + } +} + +export const EditNetworkElementDialog = connect(undefined, mapDispatch)(EditNetworkElementDialogComponent); +export default EditNetworkElementDialog; \ No newline at end of file diff --git a/sdnr/wt-odlux/odlux/apps/connectApp/src/components/infoNetworkElementDialog.tsx b/sdnr/wt-odlux/odlux/apps/connectApp/src/components/infoNetworkElementDialog.tsx new file mode 100644 index 000000000..4841b9389 --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/connectApp/src/components/infoNetworkElementDialog.tsx @@ -0,0 +1,160 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * 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. + * ============LICENSE_END========================================================================== + */ +import * as React from 'react'; + +import Button from '@mui/material/Button'; +import Dialog from '@mui/material/Dialog'; +import DialogActions from '@mui/material/DialogActions'; +import DialogTitle from '@mui/material/DialogTitle'; + +import { ColumnType, MaterialTable, MaterialTableCtorType } from '../../../../framework/src/components/material-table'; +import { connect, Connect } from '../../../../framework/src/flux/connect'; + +import { NetworkElementConnection } from '../models/networkElementConnection'; +import { AvailableCapabilities } from '../models/yangCapabilitiesType'; + +export enum InfoNetworkElementDialogMode { + None = 'none', + InfoNetworkElement = 'infoNetworkElement', +} + +const mapDispatch = () => ({ +}); + +const InfoElementTable = MaterialTable as MaterialTableCtorType; + +type DialogSettings = { + dialogTitle: string; + dialogDescription: string; + cancelButtonText: string; +}; + +const settings: { [key: string]: DialogSettings } = { + [InfoNetworkElementDialogMode.None]: { + dialogTitle: '', + dialogDescription: '', + cancelButtonText: '', + }, + [InfoNetworkElementDialogMode.InfoNetworkElement]: { + dialogTitle: 'YANG Capabilities of the Node', + dialogDescription: '', + cancelButtonText: 'OK', + }, +}; + +type InfoNetworkElementDialogComponentProps = Connect & { + mode: InfoNetworkElementDialogMode; + initialNetworkElement: NetworkElementConnection; + onClose: () => void; +}; + +type InfoNetworkElementDialogComponentState = NetworkElementConnection; + +class InfoNetworkElementDialogComponent extends React.Component { + constructor(props: InfoNetworkElementDialogComponentProps) { + super(props); + + this.state = { + nodeId: this.props.initialNetworkElement.nodeId, + isRequired: false, + host: this.props.initialNetworkElement.host, + port: this.props.initialNetworkElement.port, + }; + } + + render(): JSX.Element { + const setting = settings[this.props.mode]; + const availableCapabilities = this.props.state.connect.elementInfo.elementInfo['netconf-node-topology:available-capabilities']['available-capability']; + let yangFeatures = this.props.state.connect.elementFeatureInfo.elementFeatureInfo; + let yangCapabilities: AvailableCapabilities[] = []; + + availableCapabilities.forEach(value => { + const capabilty = value.capability; + const indexRevision = capabilty.indexOf('revision='); + const indexModule = capabilty.indexOf(')', indexRevision); + if (indexRevision > 0 && indexModule > 0) { + let moduleName = capabilty.substring(indexModule + 1); + let ModuleFeaturesList; + for (let index = 0; index < yangFeatures.length; index++) { + if (yangFeatures[index].name == moduleName) { + ModuleFeaturesList = yangFeatures[index].feature ? yangFeatures[index].feature : null; + break; + } + } + const featuresListCommaSeparated = ModuleFeaturesList ? ModuleFeaturesList.toString() : ''; + let featuresList = featuresListCommaSeparated.replace(',', ', '); + + yangCapabilities.push({ + module: moduleName, + revision: capabilty.substring(indexRevision + 9, indexRevision + 19), + features: featuresList, + }); + } + }); + + yangCapabilities = yangCapabilities.sort((a, b) => a.module === b.module ? 0 : a.module > b.module ? 1 : -1); + + return ( + <> + + {`${setting.dialogTitle}: "${this.state.nodeId}"`} + { + return ( + + ); + }, + }, + { property: 'features', title: 'Features', type: ColumnType.text, width: 500 }, + ]} idProperty="id" rows={yangCapabilities} > + + + + + + + ); + } + + private onCancel = () => { + this.props.onClose(); + }; + + static getDerivedStateFromProps(props: InfoNetworkElementDialogComponentProps, state: InfoNetworkElementDialogComponentState & { initialNetworkElement: NetworkElementConnection }): InfoNetworkElementDialogComponentState & { initialNetworkElement: NetworkElementConnection } { + let returnState = state; + if (props.initialNetworkElement !== state.initialNetworkElement) { + returnState = { + ...state, + ...props.initialNetworkElement, + initialNetworkElement: props.initialNetworkElement, + }; + } + return returnState; + } +} + +export const InfoNetworkElementDialog = connect(undefined, mapDispatch)(InfoNetworkElementDialogComponent); +export default InfoNetworkElementDialog; \ No newline at end of file diff --git a/sdnr/wt-odlux/odlux/apps/connectApp/src/components/networkElements.tsx b/sdnr/wt-odlux/odlux/apps/connectApp/src/components/networkElements.tsx new file mode 100644 index 000000000..1ce8f0c3b --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/connectApp/src/components/networkElements.tsx @@ -0,0 +1,314 @@ +/** +* ============LICENSE_START======================================================================== +* ONAP : ccsdk feature sdnr wt odlux +* ================================================================================================= +* Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved. +* ================================================================================================= +* 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. +* ============LICENSE_END========================================================================== +*/ +import React from 'react'; + +import AddIcon from '@mui/icons-material/Add'; +import ComputerIcon from '@mui/icons-material/Computer'; +import EditIcon from '@mui/icons-material/Edit'; +import Info from '@mui/icons-material/Info'; +import LinkIcon from '@mui/icons-material/Link'; +import LinkOffIcon from '@mui/icons-material/LinkOff'; +import Refresh from '@mui/icons-material/Refresh'; +import RemoveIcon from '@mui/icons-material/RemoveCircleOutline'; +import { Divider, MenuItem, Typography } from '@mui/material'; +import { Theme } from '@mui/material/styles'; +import { WithStyles } from '@mui/styles'; +import createStyles from '@mui/styles/createStyles'; +import withStyles from '@mui/styles/withStyles'; + +import { NavigateToApplication } from '../../../../framework/src/actions/navigationActions'; +import { ColumnType, MaterialTable, MaterialTableCtorType } from '../../../../framework/src/components/material-table'; +import { connect, Connect, IDispatcher } from '../../../../framework/src/flux/connect'; +import { IApplicationStoreState } from '../../../../framework/src/store/applicationStore'; +import { getAccessPolicyByUrl } from '../../../../framework/src/services/restService'; + +import { loadAllInfoElementAsync, loadAllInfoElementFeaturesAsync } from '../actions/infoNetworkElementActions'; +import { createNetworkElementsActions, createNetworkElementsProperties } from '../handlers/networkElementsHandler'; +import { NetworkElementConnection } from '../models/networkElementConnection'; +import { ModuleSet, TopologyNode } from '../models/topologyNetconf'; +import { connectService } from '../services/connectService'; + +import EditNetworkElementDialog, { EditNetworkElementDialogMode } from './editNetworkElementDialog'; +import InfoNetworkElementDialog, { InfoNetworkElementDialogMode } from './infoNetworkElementDialog'; +import RefreshNetworkElementsDialog, { RefreshNetworkElementsDialogMode } from './refreshNetworkElementsDialog'; + +const styles = (theme: Theme) => createStyles({ + connectionStatusConnected: { + color: 'darkgreen', + }, + connectionStatusConnecting: { + color: 'blue', + }, + connectionStatusDisconnected: { + color: 'red', + }, + button: { + margin: 0, + padding: '6px 6px', + minWidth: 'unset', + }, + spacer: { + marginLeft: theme.spacing(1), + marginRight: theme.spacing(1), + display: 'inline', + }, +}); + +type GetStatelessComponentProps = T extends (props: infer P & { children?: React.ReactNode }) => any ? P : any; +const MenuItemExt: React.FC> = (props) => { + const [disabled, setDisabled] = React.useState(true); + const onMouseDown = (ev: React.MouseEvent) => { + if (ev.button === 1) { + setDisabled(!disabled); + ev.preventDefault(); + } + }; + return ( +
+ +
+ ); +}; + +const mapProps = (state: IApplicationStoreState) => ({ + networkElementsProperties: createNetworkElementsProperties(state), + applicationState: state, +}); + +const mapDispatch = (dispatcher: IDispatcher) => ({ + networkElementsActions: createNetworkElementsActions(dispatcher.dispatch), + navigateToApplication: (applicationName: string, path?: string) => dispatcher.dispatch(new NavigateToApplication(applicationName, path)), + networkElementInfo: async (nodeId: string) => dispatcher.dispatch(loadAllInfoElementAsync(nodeId)), + networkElementFeaturesInfo: async (nodeId: string) => dispatcher.dispatch(loadAllInfoElementFeaturesAsync(nodeId)), +}); + +type NetworkElementsListComponentProps = WithStyles & Connect; +type NetworkElementsListComponentState = { + networkElementToEdit: NetworkElementConnection; + networkElementEditorMode: EditNetworkElementDialogMode; + refreshNetworkElementsEditorMode: RefreshNetworkElementsDialogMode; + infoNetworkElementEditorMode: InfoNetworkElementDialogMode; + elementInfo: TopologyNode | null; + elementInfoFeature: ModuleSet | null; +}; + +const emptyRequireNetworkElement: NetworkElementConnection = { id: '', nodeId: '', host: '', port: 830, status: 'Disconnected', isRequired: true }; +let initialSorted = false; +const NetworkElementTable = MaterialTable as MaterialTableCtorType; + +export class NetworkElementsListComponent extends React.Component { + + constructor(props: NetworkElementsListComponentProps) { + super(props); + + this.state = { + networkElementToEdit: emptyRequireNetworkElement, + networkElementEditorMode: EditNetworkElementDialogMode.None, + refreshNetworkElementsEditorMode: RefreshNetworkElementsDialogMode.None, + elementInfo: null, + elementInfoFeature: null, + infoNetworkElementEditorMode: InfoNetworkElementDialogMode.None, + }; + } + + getContextMenu(rowData: NetworkElementConnection): JSX.Element[] { + const mountUri = rowData.id && connectService.getNetworkElementUri(rowData.id); + const mountPolicy = mountUri && getAccessPolicyByUrl(mountUri); + const canMount = mountPolicy && mountPolicy.POST || false; + + const { configuration } = this.props.applicationState as any; + const buttonArray = [ + this.onOpenMountdNetworkElementsDialog(event, rowData)} disabled={!canMount} >Mount, + this.onOpenUnmountdNetworkElementsDialog(event, rowData)} disabled={!canMount} >Unmount, + , + this.onOpenInfoNetworkElementDialog(event, rowData)} disabled={rowData.status !== 'Connected'} >Info, + this.onOpenEditNetworkElementDialog(event, rowData)}>Edit, + this.onOpenRemoveNetworkElementDialog(event, rowData)} >Remove, + , + this.props.navigateToApplication('inventory', rowData.nodeId)}>Inventory, + , + this.props.navigateToApplication('fault', rowData.nodeId)} >Fault, + this.props.navigateToApplication('configuration', rowData.nodeId)} disabled={rowData.status === 'Connecting' || rowData.status === 'Disconnected' || !configuration}>Configure, + this.props.navigateToApplication('accounting', rowData.nodeId)} disabled={true}>Accounting, + this.props.navigateToApplication('performanceHistory', rowData.nodeId)}>Performance, + this.props.navigateToApplication('security', rowData.nodeId)} disabled={true} >Security, + ]; + + if (rowData.weburi) { + // add an icon for gui cuttrough, if weburi is available + return [ window.open(rowData.weburi, '_blank')} >Web Client].concat(buttonArray); + } else { + return buttonArray; + } + } + + // private navigationCreator + + render(): JSX.Element { + //const { classes } = this.props; + const { networkElementToEdit } = this.state; + let savedRadio = 'password'; + if (this.state.networkElementToEdit.password && this.state.networkElementToEdit.password.length > 0) { + savedRadio = 'password'; + } else if (this.state.networkElementToEdit.tlsKey && this.state.networkElementToEdit.tlsKey.length > 0) { + savedRadio = 'tlsKey'; + } + + // const mountUri = rowData.id && connectService.getNetworkElementUri(rowData.id); + // const mountPolicy = mountUri && getAccessPolicyByUrl(mountUri); + // const canAdd = mountPolicy && mountPolicy.POST || false; + const canAdd = true; + + const addRequireNetworkElementAction = { + icon: AddIcon, tooltip: 'Add node', ariaLabel: 'add-element', onClick: () => { + this.setState({ + networkElementEditorMode: EditNetworkElementDialogMode.AddNewNetworkElement, + networkElementToEdit: emptyRequireNetworkElement, + }); + }, + }; + + const refreshNetworkElementsAction = { + icon: Refresh, tooltip: 'Refresh table', ariaLabel: 'refresh', onClick: () => { + this.setState({ + refreshNetworkElementsEditorMode: RefreshNetworkElementsDialogMode.RefreshNetworkElementsTable, + }); + }, + }; + + return <> + { + + return this.getContextMenu(rowData); + }} > + + + + + ; + } + + public componentDidMount() { + if (!initialSorted) { + initialSorted = true; + this.props.networkElementsActions.onHandleRequestSort('node-id'); + } else { + this.props.networkElementsActions.onRefresh(); + } + } + + private onOpenAddNetworkElementDialog = (event: React.MouseEvent, element: NetworkElementConnection) => { + this.setState({ + networkElementToEdit: element, + networkElementEditorMode: EditNetworkElementDialogMode.AddNewNetworkElement, + }); + }; + + private onOpenRemoveNetworkElementDialog = (event: React.MouseEvent, element: NetworkElementConnection) => { + this.setState({ + networkElementToEdit: element, + networkElementEditorMode: EditNetworkElementDialogMode.RemoveNetworkElement, + }); + }; + + private onOpenEditNetworkElementDialog = (event: React.MouseEvent, element: NetworkElementConnection) => { + //let radioSaved; + //if (element.password && element.password.length > 0) + // radioSaved = 'password'; + //else if (element.tlsKey && element.tlsKey.length > 0) + // radioSaved = 'tlsKey'; + this.setState({ + networkElementToEdit: { + nodeId: element.nodeId, + isRequired: element.isRequired, + host: element.host, + port: element.port, + username: element.username, + password: element.password, + tlsKey: element.tlsKey, + }, + networkElementEditorMode: EditNetworkElementDialogMode.EditNetworkElement, + }); + }; + + private onOpenUnmountdNetworkElementsDialog = (event: React.MouseEvent, element: NetworkElementConnection) => { + this.setState({ + networkElementToEdit: element, + networkElementEditorMode: EditNetworkElementDialogMode.UnmountNetworkElement, + }); + }; + + private onOpenMountdNetworkElementsDialog = (event: React.MouseEvent, element: NetworkElementConnection) => { + this.setState({ + networkElementToEdit: element, + networkElementEditorMode: EditNetworkElementDialogMode.MountNetworkElement, + }); + }; + + private onOpenInfoNetworkElementDialog = (event: React.MouseEvent, element: NetworkElementConnection) => { + this.props.networkElementInfo(element.nodeId); + this.props.networkElementFeaturesInfo(element.nodeId); + this.setState({ + networkElementToEdit: element, + infoNetworkElementEditorMode: InfoNetworkElementDialogMode.InfoNetworkElement, + }); + }; + + private onCloseEditNetworkElementDialog = () => { + this.setState({ + networkElementEditorMode: EditNetworkElementDialogMode.None, + networkElementToEdit: emptyRequireNetworkElement, + }); + }; + + private onCloseInfoNetworkElementDialog = () => { + this.setState({ + infoNetworkElementEditorMode: InfoNetworkElementDialogMode.None, + networkElementToEdit: emptyRequireNetworkElement, + }); + }; + + private onCloseRefreshNetworkElementsDialog = () => { + this.setState({ + refreshNetworkElementsEditorMode: RefreshNetworkElementsDialogMode.None, + }); + }; +} + +export const NetworkElementsList = withStyles(styles)(connect(mapProps, mapDispatch)(NetworkElementsListComponent)); diff --git a/sdnr/wt-odlux/odlux/apps/connectApp/src/components/refreshConnectionStatusLogDialog.tsx b/sdnr/wt-odlux/odlux/apps/connectApp/src/components/refreshConnectionStatusLogDialog.tsx new file mode 100644 index 000000000..a4aea7f82 --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/connectApp/src/components/refreshConnectionStatusLogDialog.tsx @@ -0,0 +1,114 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * 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. + * ============LICENSE_END========================================================================== + */ +import React from 'react'; + +import Button from '@mui/material/Button'; +import Dialog from '@mui/material/Dialog'; +import DialogActions from '@mui/material/DialogActions'; +import DialogContent from '@mui/material/DialogContent'; +import DialogContentText from '@mui/material/DialogContentText'; +import DialogTitle from '@mui/material/DialogTitle'; + +import { connect, Connect, IDispatcher } from '../../../../framework/src/flux/connect'; + +import { connectionStatusLogReloadAction } from '../handlers/connectionStatusLogHandler'; +import { ConnectionStatusLogType } from '../models/connectionStatusLog'; + +export enum RefreshConnectionStatusLogDialogMode { + None = 'none', + RefreshConnectionStatusLogTable = 'RefreshConnectionStatusLogTable', +} + +const mapDispatch = (dispatcher: IDispatcher) => ({ + refreshConnectionStatusLog: () => dispatcher.dispatch(connectionStatusLogReloadAction), +}); + +type DialogSettings = { + dialogTitle: string; + dialogDescription: string; + applyButtonText: string; + cancelButtonText: string; + enableMountIdEditor: boolean; + enableUsernameEditor: boolean; + enableExtendedEditor: boolean; +}; + +const settings: { [key: string]: DialogSettings } = { + [RefreshConnectionStatusLogDialogMode.None]: { + dialogTitle: '', + dialogDescription: '', + applyButtonText: '', + cancelButtonText: '', + enableMountIdEditor: false, + enableUsernameEditor: false, + enableExtendedEditor: false, + }, + [RefreshConnectionStatusLogDialogMode.RefreshConnectionStatusLogTable]: { + dialogTitle: 'Do you want to refresh the Connection Status Log table?', + dialogDescription: '', + applyButtonText: 'Yes', + cancelButtonText: 'Cancel', + enableMountIdEditor: true, + enableUsernameEditor: true, + enableExtendedEditor: true, + }, +}; + +type RefreshConnectionStatusLogDialogComponentProps = Connect & { + mode: RefreshConnectionStatusLogDialogMode; + onClose: () => void; +}; + +type RefreshConnectionStatusLogDialogComponentState = ConnectionStatusLogType & { isNameValid: boolean; isHostSet: boolean }; + +class RefreshConnectionStatusLogDialogComponent extends React.Component { + + render(): JSX.Element { + const setting = settings[this.props.mode]; + return ( + + {setting.dialogTitle} + + + {setting.dialogDescription} + + + + + + + + ); + } + + private onRefresh = () => { + this.props.refreshConnectionStatusLog(); + this.props.onClose(); + }; + + private onCancel = () => { + this.props.onClose(); + }; +} + +export const RefreshConnectionStatusLogDialog = connect(undefined, mapDispatch)(RefreshConnectionStatusLogDialogComponent); +export default RefreshConnectionStatusLogDialog; \ No newline at end of file diff --git a/sdnr/wt-odlux/odlux/apps/connectApp/src/components/refreshNetworkElementsDialog.tsx b/sdnr/wt-odlux/odlux/apps/connectApp/src/components/refreshNetworkElementsDialog.tsx new file mode 100644 index 000000000..e41fd27aa --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/connectApp/src/components/refreshNetworkElementsDialog.tsx @@ -0,0 +1,114 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * 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. + * ============LICENSE_END========================================================================== + */ +import React from 'react'; + +import Button from '@mui/material/Button'; +import Dialog from '@mui/material/Dialog'; +import DialogActions from '@mui/material/DialogActions'; +import DialogContent from '@mui/material/DialogContent'; +import DialogContentText from '@mui/material/DialogContentText'; +import DialogTitle from '@mui/material/DialogTitle'; + +import { connect, Connect, IDispatcher } from '../../../../framework/src/flux/connect'; + +import { networkElementsReloadAction } from '../handlers/networkElementsHandler'; +import { NetworkElementConnection } from '../models/networkElementConnection'; + +export enum RefreshNetworkElementsDialogMode { + None = 'none', + RefreshNetworkElementsTable = 'RefreshNetworkElementsTable', +} + +const mapDispatch = (dispatcher: IDispatcher) => ({ + refreshNetworkElement: () => dispatcher.dispatch(networkElementsReloadAction), +}); + +type DialogSettings = { + dialogTitle: string; + dialogDescription: string; + applyButtonText: string; + cancelButtonText: string; + enableMountIdEditor: boolean; + enableUsernameEditor: boolean; + enableExtendedEditor: boolean; +}; + +const settings: { [key: string]: DialogSettings } = { + [RefreshNetworkElementsDialogMode.None]: { + dialogTitle: '', + dialogDescription: '', + applyButtonText: '', + cancelButtonText: '', + enableMountIdEditor: false, + enableUsernameEditor: false, + enableExtendedEditor: false, + }, + [RefreshNetworkElementsDialogMode.RefreshNetworkElementsTable]: { + dialogTitle: 'Do you want to refresh the nodes table?', + dialogDescription: '', + applyButtonText: 'Yes', + cancelButtonText: 'Cancel', + enableMountIdEditor: true, + enableUsernameEditor: true, + enableExtendedEditor: true, + }, +}; + +type RefreshNetworkElementsDialogComponentProps = Connect & { + mode: RefreshNetworkElementsDialogMode; + onClose: () => void; +}; + +type RefreshNetworkElementsDialogComponentState = NetworkElementConnection & { isNameValid: boolean; isHostSet: boolean }; + +class RefreshNetworkElementsDialogComponent extends React.Component { + + render(): JSX.Element { + const setting = settings[this.props.mode]; + return ( + + {setting.dialogTitle} + + + {setting.dialogDescription} + + + + + + + + ); + } + + private onRefresh = () => { + this.props.refreshNetworkElement(); + this.props.onClose(); + }; + + private onCancel = () => { + this.props.onClose(); + }; +} + +export const RefreshNetworkElementsDialog = connect(undefined, mapDispatch)(RefreshNetworkElementsDialogComponent); +export default RefreshNetworkElementsDialog; \ No newline at end of file diff --git a/sdnr/wt-odlux/odlux/apps/connectApp/src/handlers/connectAppRootHandler.ts b/sdnr/wt-odlux/odlux/apps/connectApp/src/handlers/connectAppRootHandler.ts new file mode 100644 index 000000000..b386dcdef --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/connectApp/src/handlers/connectAppRootHandler.ts @@ -0,0 +1,100 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * 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. + * ============LICENSE_END========================================================================== + */ + +import { IActionHandler } from '../../../../framework/src/flux/action'; +import { combineActionHandler } from '../../../../framework/src/flux/middleware'; + +import { AddWebUriList, RemoveWebUri, SetPanelAction } from '../actions/commonNetworkElementsActions'; +import { guiCutThrough } from '../models/guiCutTrough'; +import { PanelId } from '../models/panelId'; +import { connectionStatusLogActionHandler, IConnectionStatusLogState } from './connectionStatusLogHandler'; +import { IInfoNetworkElementFeaturesState, IInfoNetworkElementsState, infoNetworkElementFeaturesActionHandler, infoNetworkElementsActionHandler } from './infoNetworkElementHandler'; +import { INetworkElementsState, networkElementsActionHandler } from './networkElementsHandler'; +import { availableTlsKeysActionHandler, IAvailableTlsKeysState } from './tlsKeyHandler'; + +export interface IConnectAppStoreState { + networkElements: INetworkElementsState; + connectionStatusLog: IConnectionStatusLogState; + currentOpenPanel: PanelId; + elementInfo: IInfoNetworkElementsState; + elementFeatureInfo: IInfoNetworkElementFeaturesState; + guiCutThrough: guiCutThroughState; + availableTlsKeys: IAvailableTlsKeysState; +} + +const currentOpenPanelHandler: IActionHandler = (state = null, action) => { + if (action instanceof SetPanelAction) { + state = action.panelId; + } + return state; +}; + +interface guiCutThroughState { + searchedElements: guiCutThrough[]; + notSearchedElements: string[]; + unsupportedElements: string[]; +} + +const guiCutThroughHandler: IActionHandler = (state = { searchedElements: [], notSearchedElements: [], unsupportedElements: [] }, action) => { + if (action instanceof AddWebUriList) { + let notSearchedElements: string[]; + let searchedElements: guiCutThrough[]; + let unsupportedElements: string[]; + + notSearchedElements = state.notSearchedElements.concat(action.notSearchedElements); + unsupportedElements = state.unsupportedElements.concat(action.unsupportedElements); + + //remove elements, which were just searched + if (action.newlySearchedElements) { + action.newlySearchedElements.forEach(item => { + notSearchedElements = notSearchedElements.filter(id => id !== item); + }); + } + + searchedElements = state.searchedElements.concat(action.searchedElements); + + state = { searchedElements: searchedElements, notSearchedElements: notSearchedElements, unsupportedElements: unsupportedElements }; + + } else if (action instanceof RemoveWebUri) { + const nodeId = action.element; + const webUris = state.searchedElements.filter(item => item.id !== nodeId); + const knownElements = state.notSearchedElements.filter(item => item !== nodeId); + const unsupportedElement = state.unsupportedElements.filter(item => item != nodeId); + state = { notSearchedElements: knownElements, searchedElements: webUris, unsupportedElements: unsupportedElement }; + } + return state; +}; + +declare module '../../../../framework/src/store/applicationStore' { + interface IApplicationStoreState { + connect: IConnectAppStoreState; + } +} + +const actionHandlers = { + networkElements: networkElementsActionHandler, + connectionStatusLog: connectionStatusLogActionHandler, + currentOpenPanel: currentOpenPanelHandler, + elementInfo: infoNetworkElementsActionHandler, + elementFeatureInfo: infoNetworkElementFeaturesActionHandler, + guiCutThrough: guiCutThroughHandler, + availableTlsKeys: availableTlsKeysActionHandler, +}; + +export const connectAppRootHandler = combineActionHandler(actionHandlers); +export default connectAppRootHandler; diff --git a/sdnr/wt-odlux/odlux/apps/connectApp/src/handlers/connectionStatusLogHandler.ts b/sdnr/wt-odlux/odlux/apps/connectApp/src/handlers/connectionStatusLogHandler.ts new file mode 100644 index 000000000..264b6c198 --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/connectApp/src/handlers/connectionStatusLogHandler.ts @@ -0,0 +1,36 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * 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. + * ============LICENSE_END========================================================================== + */ +import { createExternal, IExternalTableState } from '../../../../framework/src/components/material-table/utilities'; +import { createSearchDataHandler } from '../../../../framework/src/utilities/elasticSearch'; + +import { NetworkElementConnectionLog } from '../models/networkElementConnectionLog'; + +export interface IConnectionStatusLogState extends IExternalTableState { } + +// create eleactic search material data fetch handler +const connectionStatusLogSearchHandler = createSearchDataHandler('connectionlog'); + +export const { + actionHandler: connectionStatusLogActionHandler, + createActions: createConnectionStatusLogActions, + createProperties: createConnectionStatusLogProperties, + reloadAction: connectionStatusLogReloadAction, + + // set value action, to change a value +} = createExternal(connectionStatusLogSearchHandler, appState => appState.connect.connectionStatusLog); + diff --git a/sdnr/wt-odlux/odlux/apps/connectApp/src/handlers/infoNetworkElementHandler.ts b/sdnr/wt-odlux/odlux/apps/connectApp/src/handlers/infoNetworkElementHandler.ts new file mode 100644 index 000000000..692e63a5c --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/connectApp/src/handlers/infoNetworkElementHandler.ts @@ -0,0 +1,92 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * 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. + * ============LICENSE_END========================================================================== + */ +import { IActionHandler } from '../../../../framework/src/flux/action'; + +import { AllElementInfoFeatureLoadedAction, AllElementInfoLoadedAction, LoadAllElementInfoAction } from '../actions/infoNetworkElementActions'; +import { Module, TopologyNode } from '../models/topologyNetconf'; + +export interface IInfoNetworkElementsState { + elementInfo: TopologyNode; + busy: boolean; +} + +export interface IInfoNetworkElementFeaturesState { + elementFeatureInfo: Module[]; + busy: boolean; +} + +const infoNetworkElementsStateInit: IInfoNetworkElementsState = { + elementInfo: { + 'node-id': '', + 'netconf-node-topology:available-capabilities': { + 'available-capability': [], + }, + }, + busy: false, +}; + +const infoNetworkElementFeaturesStateInit: IInfoNetworkElementFeaturesState = { + elementFeatureInfo: [], + busy: false, +}; + +export const infoNetworkElementsActionHandler: IActionHandler = (state = infoNetworkElementsStateInit, action) => { + if (action instanceof LoadAllElementInfoAction) { + state = { + ...state, + busy: true, + }; + } else if (action instanceof AllElementInfoLoadedAction) { + if (!action.error && action.elementInfo) { + state = { + ...state, + elementInfo: action.elementInfo, + busy: false, + }; + } else { + state = { + ...state, + busy: false, + }; + } + } + return state; +}; + +export const infoNetworkElementFeaturesActionHandler: IActionHandler = (state = infoNetworkElementFeaturesStateInit, action) => { + if (action instanceof LoadAllElementInfoAction) { + state = { + ...state, + busy: true, + }; + } else if (action instanceof AllElementInfoFeatureLoadedAction) { + if (!action.error && action.elementFeatureInfo) { + state = { + ...state, + elementFeatureInfo: action.elementFeatureInfo, + busy: false, + }; + } else { + state = { + ...state, + busy: false, + }; + } + } + return state; +}; \ No newline at end of file diff --git a/sdnr/wt-odlux/odlux/apps/connectApp/src/handlers/networkElementsHandler.ts b/sdnr/wt-odlux/odlux/apps/connectApp/src/handlers/networkElementsHandler.ts new file mode 100644 index 000000000..42d2824b9 --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/connectApp/src/handlers/networkElementsHandler.ts @@ -0,0 +1,64 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * 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. + * ============LICENSE_END========================================================================== + */ +import { createExternal, IExternalTableState } from '../../../../framework/src/components/material-table/utilities'; +import { getAccessPolicyByUrl } from '../../../../framework/src/services/restService'; +import { createSearchDataHandler } from '../../../../framework/src/utilities/elasticSearch'; + +import { NetworkElementConnection } from '../models/networkElementConnection'; +import { connectService } from '../services/connectService'; + +export interface INetworkElementsState extends IExternalTableState { } + +// create eleactic search material data fetch handler +const networkElementsSearchHandler = createSearchDataHandler('network-element-connection'); + +export const { + actionHandler: networkElementsActionHandler, + createActions: createNetworkElementsActions, + createProperties: createNetworkElementsProperties, + reloadAction: networkElementsReloadAction, + + // set value action, to change a value +} = createExternal(networkElementsSearchHandler, appState => { + + const webUris = appState.connect.guiCutThrough.searchedElements; + // add weburi links, if element is connected & weburi available + if (appState.connect.networkElements.rows && webUris.length > 0) { + + appState.connect.networkElements.rows.forEach(element => { + + if (element.status === 'Connected') { + const webUri = webUris.find(item => item.id === element.id as string); + if (webUri) { + element.weburi = webUri.weburi; + element.isWebUriUnreachable = false; + } else { + element.isWebUriUnreachable = true; + } + } + }); + } + + return appState.connect.networkElements; +}, (ne) => { + if (!ne || !ne.id) return true; + const neUrl = connectService.getNetworkElementUri(ne.id); + const policy = getAccessPolicyByUrl(neUrl); + return !(policy.GET || policy.POST); +}); + diff --git a/sdnr/wt-odlux/odlux/apps/connectApp/src/handlers/tlsKeyHandler.ts b/sdnr/wt-odlux/odlux/apps/connectApp/src/handlers/tlsKeyHandler.ts new file mode 100644 index 000000000..20badcba0 --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/connectApp/src/handlers/tlsKeyHandler.ts @@ -0,0 +1,55 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2021 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * 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. + * ============LICENSE_END========================================================================== + */ +import { IActionHandler } from '../../../../framework/src/flux/action'; + +import { AllTlsKeyListLoadedAction, LoadAllTlsKeyListAction } from '../actions/tlsKeyActions'; +import { TlsKeys } from '../models/networkElementConnection'; + +export interface IAvailableTlsKeysState { + tlsKeysList: TlsKeys[]; + busy: boolean; +} + +const tlsKeysStateInit: IAvailableTlsKeysState = { + tlsKeysList: [], + busy: false, +}; + +export const availableTlsKeysActionHandler: IActionHandler = (state = tlsKeysStateInit, action) => { + if (action instanceof LoadAllTlsKeyListAction) { + state = { + ...state, + busy: true, + }; + + } else if (action instanceof AllTlsKeyListLoadedAction) { + if (!action.error && action.tlsList) { + state = { + ...state, + tlsKeysList: action.tlsList, + busy: false, + }; + } else { + state = { + ...state, + busy: false, + }; + } + } + return state; +}; \ No newline at end of file diff --git a/sdnr/wt-odlux/odlux/apps/connectApp/src/index.html b/sdnr/wt-odlux/odlux/apps/connectApp/src/index.html new file mode 100644 index 000000000..1a16876c9 --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/connectApp/src/index.html @@ -0,0 +1,28 @@ + + + + + + + + + connectApp + + + +
+ + + + + + \ No newline at end of file diff --git a/sdnr/wt-odlux/odlux/apps/connectApp/src/models/connectionStatusLog.ts b/sdnr/wt-odlux/odlux/apps/connectApp/src/models/connectionStatusLog.ts new file mode 100644 index 000000000..82b49a081 --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/connectApp/src/models/connectionStatusLog.ts @@ -0,0 +1,27 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * 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. + * ============LICENSE_END========================================================================== + */ + +export type ConnectionStatusLogType = { + _id: string; + elementStatus: string; + timeStamp: string; + objectId: string; + type: string; + newValue: string; +}; + diff --git a/sdnr/wt-odlux/odlux/apps/connectApp/src/models/guiCutTrough.ts b/sdnr/wt-odlux/odlux/apps/connectApp/src/models/guiCutTrough.ts new file mode 100644 index 000000000..0fd46a82d --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/connectApp/src/models/guiCutTrough.ts @@ -0,0 +1,22 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * 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. + * ============LICENSE_END========================================================================== + */ + +export type guiCutThrough = { + id: string; + weburi?: string; +}; \ No newline at end of file diff --git a/sdnr/wt-odlux/odlux/apps/connectApp/src/models/networkElementBase.ts b/sdnr/wt-odlux/odlux/apps/connectApp/src/models/networkElementBase.ts new file mode 100644 index 000000000..a46a30e2b --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/connectApp/src/models/networkElementBase.ts @@ -0,0 +1,22 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * 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. + * ============LICENSE_END========================================================================== + */ +export type NetworkElementBaseType = { + mountId: string; + host: string; + port: number; +}; \ No newline at end of file diff --git a/sdnr/wt-odlux/odlux/apps/connectApp/src/models/networkElementConnection.ts b/sdnr/wt-odlux/odlux/apps/connectApp/src/models/networkElementConnection.ts new file mode 100644 index 000000000..bb076c720 --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/connectApp/src/models/networkElementConnection.ts @@ -0,0 +1,69 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * 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. + * ============LICENSE_END========================================================================== + */ + +export type NetworkElementConnection = { + id?: string; + nodeId: string; + isRequired: boolean; + host: string; + port: number; + username?: string; + password?: string; + tlsKey?: string; + weburi?: string; + isWebUriUnreachable?: boolean; + status?: 'Connected' | 'mounted' | 'unmounted' | 'Connecting' | 'Disconnected' | 'idle'; + coreModelCapability?: string; + deviceType?: string; + deviceFunction?: string; + nodeDetails?: { + availableCapabilites: { + capabilityOrigin: string; + capability: string; + }[]; + unavailableCapabilities: { + failureReason: string; + capability: string; + }[]; + }; +}; + + +export type UpdateNetworkElement = { + id: string; + isRequired?: boolean; + username?: string; + password?: string; + tlsKey?: string; +}; + +export type ConnectionStatus = { + status: string; +}; + +export type TlsKeys = { + key: string; +}; + + +/** + * Checks if a object has a given propertyname, if yes, the name is returned as string. + * @throws at compile time if property is not available + * @param name propertyname + */ +export const propertyOf = (name: keyof TObj) => name; \ No newline at end of file diff --git a/sdnr/wt-odlux/odlux/apps/connectApp/src/models/networkElementConnectionLog.ts b/sdnr/wt-odlux/odlux/apps/connectApp/src/models/networkElementConnectionLog.ts new file mode 100644 index 000000000..4b4e28325 --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/connectApp/src/models/networkElementConnectionLog.ts @@ -0,0 +1,25 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * 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. + * ============LICENSE_END========================================================================== + */ + +export type NetworkElementConnectionLog = { + id: string; + nodeId: string; + status: 'connected' | 'mounted' | 'unmounted' | 'connecting' | 'disconnected' | 'idle'; + timestamp: string; +}; + diff --git a/sdnr/wt-odlux/odlux/apps/connectApp/src/models/panelId.ts b/sdnr/wt-odlux/odlux/apps/connectApp/src/models/panelId.ts new file mode 100644 index 000000000..2861f107f --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/connectApp/src/models/panelId.ts @@ -0,0 +1,19 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * 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. + * ============LICENSE_END========================================================================== + */ + +export type PanelId = null | 'NetworkElements' | 'ConnectionStatusLog'; \ No newline at end of file diff --git a/sdnr/wt-odlux/odlux/apps/connectApp/src/models/topologyNetconf.ts b/sdnr/wt-odlux/odlux/apps/connectApp/src/models/topologyNetconf.ts new file mode 100644 index 000000000..85a1a71fd --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/connectApp/src/models/topologyNetconf.ts @@ -0,0 +1,59 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * 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. + * ============LICENSE_END========================================================================== + */ + +export interface AvailableCapability { + 'capability-origin': string; + capability: string; +} + +export interface NetconfNodeTopologyAvailableCapabilities { + 'available-capability': AvailableCapability[]; +} + +export interface TopologyNode { + 'node-id': string; + 'netconf-node-topology:available-capabilities': NetconfNodeTopologyAvailableCapabilities; +} + +export interface Topology { + 'topology-id': string; + 'network-topology:node': TopologyNode[]; +} + +/** + * Represents the type of the features of the Module. + */ +export interface Module { + feature?: string[]; + location?: string[]; + name: string; + namespace?: string; + revision?: string; +} + +export interface ModuleFeatures { + module: Module[]; +} + +export interface ModuleSet { + 'module-set': ModuleFeatures[]; +} + +export interface FeatureTopology { + 'ietf-yang-library:yang-library' : ModuleSet; +} diff --git a/sdnr/wt-odlux/odlux/apps/connectApp/src/models/yangCapabilitiesType.ts b/sdnr/wt-odlux/odlux/apps/connectApp/src/models/yangCapabilitiesType.ts new file mode 100644 index 000000000..b69993f7d --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/connectApp/src/models/yangCapabilitiesType.ts @@ -0,0 +1,24 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * 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. + * ============LICENSE_END========================================================================== + */ + +export type AvailableCapabilities = { + id?: string; + module: string; + revision: string; + features: string; +}; \ No newline at end of file diff --git a/sdnr/wt-odlux/odlux/apps/connectApp/src/pluginConnect.tsx b/sdnr/wt-odlux/odlux/apps/connectApp/src/pluginConnect.tsx new file mode 100644 index 000000000..c2907166c --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/connectApp/src/pluginConnect.tsx @@ -0,0 +1,109 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * 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. + * ============LICENSE_END========================================================================== + */ + +import React from 'react'; +import { Redirect, Route, RouteComponentProps, Switch, withRouter } from 'react-router-dom'; + +import { AddSnackbarNotification } from '../../../framework/src/actions/snackbarActions'; +import { connect, Connect, IDispatcher } from '../../../framework/src/flux/connect'; +import applicationManager from '../../../framework/src/services/applicationManager'; +import { IFormatedMessage, subscribe } from '../../../framework/src/services/notificationService'; +import { IApplicationStoreState } from '../../../framework/src/store/applicationStore'; + +import { findWebUrisForGuiCutThroughAsyncAction, SetPanelAction, updateCurrentViewAsyncAction } from './actions/commonNetworkElementsActions'; +import { NetworkElementsList } from './components/networkElements'; +import connectAppRootHandler from './handlers/connectAppRootHandler'; +import { createNetworkElementsActions, createNetworkElementsProperties, networkElementsReloadAction } from './handlers/networkElementsHandler'; +import { PanelId } from './models/panelId'; +import ConnectApplication from './views/connectView'; + +const appIcon = require('./assets/icons/connectAppIcon.svg'); // select app icon + +let currentStatus: string | undefined = undefined; + +const mapProps = (state: IApplicationStoreState) => ({ + networkElementDashboardProperties: createNetworkElementsProperties(state), +}); + +const mapDisp = (dispatcher: IDispatcher) => ({ + networkElementsDashboardActions: createNetworkElementsActions(dispatcher.dispatch, true), + setCurrentPanel: (panelId: PanelId) => dispatcher.dispatch(new SetPanelAction(panelId)), +}); + +const ConnectApplicationRouteAdapter = connect(mapProps, mapDisp)((props: RouteComponentProps<{ status?: string }> & Connect) => { + + // TODO: move into useEffect! + if (currentStatus !== props.match.params.status) { + currentStatus = props.match.params.status || undefined; + window.setTimeout(() => { + if (currentStatus) { + props.setCurrentPanel('NetworkElements'); + props.networkElementsDashboardActions.onFilterChanged('status', currentStatus); + if (!props.networkElementDashboardProperties.showFilter) { + props.networkElementsDashboardActions.onToggleFilter(false); + props.networkElementsDashboardActions.onRefresh(); + } else + props.networkElementsDashboardActions.onRefresh(); + } + }); + } + return ( + + ); +}); + + +const App = withRouter((props: RouteComponentProps) => ( + + + + + +)); + +export function register() { + const applicationApi = applicationManager.registerApplication({ + name: 'connect', + icon: appIcon, + rootComponent: App, + rootActionHandler: connectAppRootHandler, + menuEntry: 'Connect', + }); + + // subscribe to the websocket notifications + subscribe(['object-creation-notification', 'object-deletion-notification', 'attribute-value-changed-notification'], (msg => { + const store = applicationApi.applicationStore; + if (msg && msg.type.type === 'object-creation-notification' && store) { + store.dispatch(new AddSnackbarNotification({ message: `Adding node [${msg.data['object-id-ref']}]`, options: { variant: 'info' } })); + } else if (msg && (msg.type.type === 'object-deletion-notification' || msg.type.type === 'attribute-value-changed-notification') && store) { + store.dispatch(new AddSnackbarNotification({ message: `Updating node [${msg.data['object-id-ref']}]`, options: { variant: 'info' } })); + } + if (store) { + store.dispatch(updateCurrentViewAsyncAction() as any).then(() => { + if (msg['node-id']) { + store.dispatch(findWebUrisForGuiCutThroughAsyncAction([msg['node-id']])); + } + }); + } + })); + + applicationApi.applicationStoreInitialized.then(store => { + store.dispatch(networkElementsReloadAction); + }); + +} \ No newline at end of file diff --git a/sdnr/wt-odlux/odlux/apps/connectApp/src/services/connectService.ts b/sdnr/wt-odlux/odlux/apps/connectApp/src/services/connectService.ts new file mode 100644 index 000000000..1d74f859a --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/connectApp/src/services/connectService.ts @@ -0,0 +1,305 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * 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. + * ============LICENSE_END========================================================================== + */ + +import { requestRest } from '../../../../framework/src/services/restService'; +import { NetworkElementConnection, ConnectionStatus, UpdateNetworkElement } from '../models/networkElementConnection'; +import { TlsKeys } from '../models/networkElementConnection'; +import { convertPropertyNames, replaceUpperCase } from '../../../../framework/src/utilities/yangHelper'; +import { Result } from '../../../../framework/src/models/elasticSearch'; + +import { FeatureTopology, Topology, TopologyNode, Module } from '../models/topologyNetconf'; +import { guiCutThrough } from '../models/guiCutTrough'; + +/** +* Represents a web api accessor service for all network element/node actions. +*/ +class ConnectService { + public getNetworkElementUri = (nodeId: string) => '/rests/data/network-topology:network-topology/topology=topology-netconf/node=' + nodeId; + + public getNetworkElementConnectDataProviderUri = (operation: 'create' | 'update' | 'delete') => `/rests/operations/data-provider:${operation}-network-element-connection`; + + public getAllWebUriExtensionsForNetworkElementListUri = (nodeId: string) => this.getNetworkElementUri(nodeId) + '/yang-ext:mount/core-model:network-element'; + + public getNetworkElementYangLibraryFeature = (nodeId: string) => '/rests/data/network-topology:network-topology/topology=topology-netconf/node=' + nodeId + '/yang-ext:mount/ietf-yang-library:yang-library?content=nonconfig'; + + /** + * Inserts a network element/node. + */ + public async createNetworkElement(element: NetworkElementConnection): Promise { + const path = this.getNetworkElementConnectDataProviderUri('create'); + const result = await requestRest(path, { + method: 'POST', body: JSON.stringify(convertPropertyNames({ 'data-provider:input': element }, replaceUpperCase)), + }); + return result || null; + } + + /** + * Updates a network element/node. + */ + public async updateNetworkElement(element: UpdateNetworkElement): Promise { + const path = this.getNetworkElementConnectDataProviderUri('update'); + const result = await requestRest(path, { + method: 'POST', body: JSON.stringify(convertPropertyNames({ 'data-provider:input': element }, replaceUpperCase)), + }); + return result || null; + } + + /** + * Deletes a network element/node. + */ + public async deleteNetworkElement(element: UpdateNetworkElement): Promise { + const query = { + 'id': element.id, + }; + const path = this.getNetworkElementConnectDataProviderUri('delete'); + const result = await requestRest(path, { + method: 'POST', body: JSON.stringify(convertPropertyNames({ 'data-provider:input': query }, replaceUpperCase)), + }); + return result || null; + } + + /** Mounts network element/node */ + public async mountNetworkElement(networkElement: NetworkElementConnection): Promise { + const path = this.getNetworkElementUri(networkElement.nodeId); + const mountXml = [ + '', + `${networkElement.nodeId}`, + `${networkElement.host}`, + `${networkElement.port}`, + `${networkElement.username}`, + `${networkElement.password}`, + ' false', + + ' ', + ' false', + ' 20000', + ' 100', + ' 2000', + ' 1.5', + + ' ', + ' 120', + ''].join(''); + + const tlsXml = [ + '', + `${networkElement.nodeId}`, + '', + `${networkElement.tlsKey}`, + `${networkElement.username}`, + '', + `${networkElement.host}`, + `${networkElement.port}`, + 'false', + '', + 'TLS', + ' ', + '2', + ''].join(''); + let bodyXml; + if (networkElement.password) { + bodyXml = mountXml; + } else { + bodyXml = tlsXml; + } + + try { + const result = await requestRest(path, { + method: 'PUT', + headers: { + 'Content-Type': 'application/xml', + 'Accept': 'application/xml', + }, + body: bodyXml, + }); + // expect an empty answer + return result !== null; + } catch { + return false; + } + } + + /** Unmounts a network element by its id. */ + public async unmountNetworkElement(nodeId: string): Promise { + const path = this.getNetworkElementUri(nodeId); + + try { + const result = await requestRest(path, { + method: 'DELETE', + headers: { + 'Content-Type': 'application/xml', + 'Accept': 'application/xml', + }, + }); + // expect an empty answer + return result !== null; + + } catch { + return false; + } + } + + /** Yang capabilities of the selected network element/node */ + public async infoNetworkElement(nodeId: string): Promise { + const path = this.getNetworkElementUri(nodeId); + const topologyRequestPomise = requestRest(path, { method: 'GET' }); + + return topologyRequestPomise && topologyRequestPomise.then(result => { + return result && result['network-topology:node'] && result['network-topology:node'][0] || null; + }); + } + + + /** Yang features of the selected network element/node module */ + public async infoNetworkElementFeatures(nodeId: string): Promise { + const path = this.getNetworkElementYangLibraryFeature(nodeId); + const topologyRequestPomise = requestRest(path, { method: 'GET' }); + + return topologyRequestPomise && topologyRequestPomise.then(result => { + const resultFinal = result && result['ietf-yang-library:yang-library'] + && result['ietf-yang-library:yang-library']['module-set'] && + result['ietf-yang-library:yang-library']['module-set'][0] && + result['ietf-yang-library:yang-library']['module-set'][0].module || null; + return resultFinal; + }); + } + + + + /** + * Get the connection state of the network element/ node + */ + public async getNetworkElementConnectionStatus(element: string): Promise<(ConnectionStatus)[] | null> { + const path = '/rests/operations/data-provider:read-network-element-connection-list'; + const query = { + 'data-provider:input': { + 'filter': [{ + 'property': 'node-id', + 'filtervalue': element, + }], + 'pagination': { + 'size': 20, + 'page': 1, + }, + }, + }; + const result = await requestRest>(path, { method: 'POST', body: JSON.stringify(query) }); + return result && result['data-provider:output'] && result['data-provider:output'].data && result['data-provider:output'].data.map(ne => ({ + status: ne.status, + })) || null; + } + + /** + * Gets all available tlsKeys. + */ + + public async getTlsKeys(): Promise<(TlsKeys)[] | null> { + const path = '/rests/operations/data-provider:read-tls-key-entry'; + const query = { + 'data-provider:input': { + 'filter': [], + 'sortorder': [], + 'pagination': { + 'size': 20, + 'page': 1, + }, + }, + }; + + const result = await requestRest>(path, { method: 'POST', body: JSON.stringify(query) }); + return result && result['data-provider:output'] && result['data-provider:output'].data && result['data-provider:output'].data.map(ne => ({ + key: ne, + })) || null; + } + + public async getAllWebUriExtensionsForNetworkElementListAsync(neList: string[]): Promise<(guiCutThrough)[]> { + const path = '/rests/operations/data-provider:read-gui-cut-through-entry'; + let webUriList: guiCutThrough[] = []; + const query = { + 'data-provider:input': { + 'filter': [{ + 'property': 'id', + 'filtervalues': neList, + }], + 'pagination': { + 'size': 20, + 'page': 1, + }, + }, + }; + + const result = await requestRest>(path, { method: 'POST', body: JSON.stringify(query) }); + const resultData = result && result['data-provider:output'] && result['data-provider:output'].data; + neList.forEach(nodeId => { + let entryNotFound = true; + if (resultData) { + try { + resultData.forEach(entry => { + if (entry.id == nodeId) { + entryNotFound = false; + if (entry.weburi) { + webUriList.push({ id: nodeId, weburi: entry.weburi }); + } else { + webUriList.push({ id: nodeId, weburi: undefined }); + } + throw new Error(); + } + }); + } catch (e) { } + } + if (entryNotFound) + webUriList.push({ id: nodeId, weburi: undefined }); + }); + return webUriList; + } + + // public async getAllWebUriExtensionsForNetworkElementListAsync(ne: string[]): Promise<(guiCutThrough)[] | null> { + + // let promises: any[] = []; + // let webUris: guiCutThrough[] = [] + + // ne.forEach(nodeId => { + // const path = this.getAllWebUriExtensionsForNetworkElementListUri(nodeId); + + // // add search request to array + // promises.push(requestRest(path, { method: "GET" }) + // .then(result => { + // if (result != null && result['core-model:network-element'] && result['core-model:network-element'].extension) { + // const webUri = result['core-model:network-element'].extension.find((item: any) => item['value-name'] === "webUri") + // if (webUri) { + // webUris.push({ weburi: webUri.value, id: nodeId }); + // } else { + // webUris.push({ weburi: undefined, id: nodeId }); + // } + // } else { + // webUris.push({ weburi: undefined, id: nodeId }); + // } + // }) + // .catch(error => { + // webUris.push({ weburi: undefined, id: nodeId }); + // })) + // }) + // // wait until all promises are done and return weburis + // return Promise.all(promises).then(result => { return webUris }); + // } + +} + + + +export const connectService = new ConnectService(); diff --git a/sdnr/wt-odlux/odlux/apps/connectApp/src/views/connectView.tsx b/sdnr/wt-odlux/odlux/apps/connectApp/src/views/connectView.tsx new file mode 100644 index 000000000..a6fcb7c32 --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/connectApp/src/views/connectView.tsx @@ -0,0 +1,102 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * 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. + * ============LICENSE_END========================================================================== + */ +import React from 'react'; + +import { AppBar, Tab, Tabs } from '@mui/material'; + +import { useApplicationDispatch, useSelectApplicationState } from '../../../../framework/src/flux/connect'; + +import { findWebUrisForGuiCutThroughAsyncAction, setPanelAction } from '../actions/commonNetworkElementsActions'; +import { ConnectionStatusLog } from '../components/connectionStatusLog'; +import { NetworkElementsList } from '../components/networkElements'; +import { connectionStatusLogReloadAction } from '../handlers/connectionStatusLogHandler'; +import { networkElementsReloadAction } from '../handlers/networkElementsHandler'; +import { NetworkElementConnection } from '../models/networkElementConnection'; +import { PanelId } from '../models/panelId'; + +const ConnectApplicationComponent: React.FC<{}> = () => { + + const panelId = useSelectApplicationState(state => state.connect.currentOpenPanel); + const netWorkElements = useSelectApplicationState(state => state.connect.networkElements); + + const dispatch = useApplicationDispatch(); + const onLoadNetworkElements = () => dispatch(networkElementsReloadAction); + const loadWebUris = (networkElements: NetworkElementConnection[]) => dispatch(findWebUrisForGuiCutThroughAsyncAction(networkElements.map((ne) => ne.id!))); + const onLoadConnectionStatusLog = () => dispatch(connectionStatusLogReloadAction); + const switchActivePanel = (panelId2: PanelId) => dispatch(setPanelAction(panelId2)); + + const onTogglePanel = (panelId2: PanelId) => { + const nextActivePanel = panelId2; + switchActivePanel(nextActivePanel); + + switch (nextActivePanel) { + case 'NetworkElements': + onLoadNetworkElements(); + break; + case 'ConnectionStatusLog': + onLoadConnectionStatusLog(); + break; + case null: + // do nothing if all panels are closed + break; + default: + console.warn('Unknown nextActivePanel [' + nextActivePanel + '] in connectView'); + break; + } + }; + + const onHandleTabChange = (event: React.SyntheticEvent, newValue: PanelId) => { + switchActivePanel(newValue); + }; + + React.useEffect(()=>{ + if (panelId === null) { //don't change tabs, if one is selected already + onTogglePanel('NetworkElements'); + } + }, []); + + React.useEffect(()=>{ + const networkElements = netWorkElements; + + if (networkElements.rows.length > 0) { + // Search for weburi client for all netWorkElements in case of table data changes. + // e.G: Pagination of the table data (there is no event) + loadWebUris(networkElements.rows); + } + }, [netWorkElements]); + + return ( + <> + + + + + + + {panelId === 'NetworkElements' + ? + : panelId === 'ConnectionStatusLog' + ? + : null + } + + ); +}; + +export const ConnectApplication = ConnectApplicationComponent; +export default ConnectApplication; \ No newline at end of file diff --git a/sdnr/wt-odlux/odlux/apps/connectApp/tsconfig.json b/sdnr/wt-odlux/odlux/apps/connectApp/tsconfig.json new file mode 100644 index 000000000..18956db7c --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/connectApp/tsconfig.json @@ -0,0 +1,6 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": "./dist", + }, +} diff --git a/sdnr/wt-odlux/odlux/apps/connectApp/webpack.config.js b/sdnr/wt-odlux/odlux/apps/connectApp/webpack.config.js new file mode 100644 index 000000000..b7aebb9df --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/connectApp/webpack.config.js @@ -0,0 +1,202 @@ +/** + * Webpack 4 configuration file + * see https://webpack.js.org/configuration/ + * see https://webpack.js.org/configuration/dev-server/ + */ + +"use strict"; + +const path = require("path"); +const webpack = require("webpack"); +const CopyWebpackPlugin = require("copy-webpack-plugin"); +const TerserPlugin = require('terser-webpack-plugin'); + +const policies = require('./policies.json'); + +// const __dirname = (path => path.replace(/^([a-z]\:)/, c => c.toUpperCase()))(process.__dirname()); + +module.exports = (env) => { + const distPath = path.resolve(__dirname, env === "release" ? "." : "../..", "dist"); + const frameworkPath = path.resolve(__dirname, env === "release" ? "../../framework" : "../..", "dist"); + return [{ + name: "App", + + mode: "none", //disable default behavior + + target: "web", + + context: path.resolve(__dirname, "src"), + + entry: { + connectApp: ["./pluginConnect.tsx"] + }, + + devtool: env === "release" ? false : "source-map", + + resolve: { + extensions: [".ts", ".tsx", ".js", ".jsx"] + }, + + output: { + path: distPath, + filename: "[name].js", + library: "[name]", + libraryTarget: "umd2", + chunkFilename: "[name].js" + }, + module: { + rules: [{ + test: /\.tsx?$/, + exclude: /node_modules/, + use: [{ + loader: "babel-loader" + }, { + loader: "ts-loader" + }] + }, { + test: /\.jsx?$/, + exclude: /node_modules/, + use: [{ + loader: "babel-loader" + }] + },{ + //don't minify images + test: /\.(png|gif|jpg|svg)$/, + use: [{ + loader: 'url-loader', + options: { + limit: 10, + name: './images/[name].[ext]' + } + }] + }] + }, + + optimization: { + noEmitOnErrors: true, + namedModules: env !== "release", + minimize: env === "release", + minimizer: env !== "release" ? [] : [new TerserPlugin({ + terserOptions: { + warnings: false, // false, true, "verbose" + compress: { + drop_console: true, + drop_debugger: true, + } + } + })], + }, + plugins: [ + new webpack.DllReferencePlugin({ + context: path.resolve(__dirname, "../../framework/src"), + manifest: require(path.resolve(frameworkPath, "vendor-manifest.json")), + sourceType: "umd2" + }), + new webpack.DllReferencePlugin({ + context: path.resolve(__dirname, "../../framework/src"), + manifest: require(path.resolve(frameworkPath, "app-manifest.json")), + sourceType: "umd2" + }), + ...(env === "release" ? [ + new webpack.DefinePlugin({ + "process.env": { + NODE_ENV: "'production'", + VERSION: JSON.stringify(require("./package.json").version) + } + }), + ] : [ + new webpack.DefinePlugin({ + "process.env": { + NODE_ENV: "'development'", + VERSION: JSON.stringify(require("./package.json").version) + } + }), + new CopyWebpackPlugin([{ + from: 'index.html', + to: distPath + }]), + ]) + ], + + devServer: { + public: "http://localhost:3100", + contentBase: frameworkPath, + + compress: true, + headers: { + "Access-Control-Allow-Origin": "*" + }, + host: "0.0.0.0", + port: 3100, + disableHostCheck: true, + historyApiFallback: true, + inline: true, + hot: false, + quiet: false, + stats: { + colors: true + }, + before: function(app, server, compiler) { + app.get('/oauth/policies',(_, res) => res.json(policies)); + }, + proxy: { + "/about": { + target: "http://sdnr:8181", + secure: false + }, + "/yang-schema/": { + target: "http://sdnr:8181", + secure: false + }, + "/oauth/": { + target: "http://sdnr:8181", + secure: false + }, + "/database/": { + target: "http://sdnr:8181", + secure: false + }, + "/restconf/": { + target: "http://sdnr:8181", + secure: false + }, + "/rests/": { + target: "http://sdnr:8181", + secure: false + }, + "/userdata": { + target: "http://sdnr:8181", + secure: false + }, + "/userdata/": { + target: "http://sdnr:8181", + secure: false + }, + "/help/": { + target: "http://sdnr:8181", + secure: false + }, + "/about/": { + target: "http://sdnr:8181", + secure: false + }, + "/tree/": { + target: "http://sdnr:8181", + secure: false + }, + "/websocket": { + target: "http://sdnr:8181", + ws: true, + changeOrigin: true, + secure: false + }, + "/apidoc": { + target: "http://sdnr:8181", + ws: true, + changeOrigin: true, + secure: false + } + } + } + }]; +} diff --git a/sdnr/wt-odlux/odlux/apps/demoApp/.babelrc b/sdnr/wt-odlux/odlux/apps/demoApp/.babelrc new file mode 100644 index 000000000..3d8cd1260 --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/demoApp/.babelrc @@ -0,0 +1,17 @@ +{ + "presets": [ + ["@babel/preset-react"], + ["@babel/preset-env", { + "targets": { + "chrome": "66" + }, + "spec": true, + "loose": false, + "modules": false, + "debug": false, + "useBuiltIns": "usage", + "forceAllTransforms": true + }] + ], + "plugins": [] +} diff --git a/sdnr/wt-odlux/odlux/apps/demoApp/package.json b/sdnr/wt-odlux/odlux/apps/demoApp/package.json new file mode 100644 index 000000000..6a31bc3ec --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/demoApp/package.json @@ -0,0 +1,46 @@ +{ + "name": "@odlux/demo-app", + "version": "0.1.0", + "description": "A react based modular UI framework", + "main": "index.js", + "scripts": { + "start": "webpack-dev-server --env debug", + "build": "webpack --env release --config webpack.config.js", + "build:dev": "webpack --env debug --config webpack.config.js" + }, + "repository": { + "type": "git", + "url": "https://git.mfico.de/highstreet-technologies/odlux.git" + }, + "keywords": [ + "reactjs", + "redux", + "ui", + "framework" + ], + "author": "Matthias Fischer", + "license": "Apache-2.0", + "dependencies": { + "@fortawesome/fontawesome-svg-core": "1.2.35", + "@fortawesome/free-solid-svg-icons": "5.6.3", + "@fortawesome/react-fontawesome": "0.1.14", + "@emotion/react": "^11.7.0", + "@emotion/styled": "^11.6.0", + "@mui/icons-material": "^5.2.0", + "@mui/material": "^5.2.2", + "@mui/styles": "^5.2.2", + "@odlux/framework": "*" + }, + "peerDependencies": { + "@types/classnames": "2.2.6", + "@types/flux": "3.1.8", + "@types/jquery": "3.3.10", + "@types/react": "17.0.37", + "@types/react-dom": "17.0.11", + "@types/react-router-dom": "5.1.7", + "jquery": "3.3.1", + "react": "17.0.2", + "react-dom": "17.0.2", + "react-router-dom": "5.2.0" + } +} diff --git a/sdnr/wt-odlux/odlux/apps/demoApp/pom.xml b/sdnr/wt-odlux/odlux/apps/demoApp/pom.xml new file mode 100644 index 000000000..71e4c10eb --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/demoApp/pom.xml @@ -0,0 +1,109 @@ + + + + + 4.0.0 + + org.onap.ccsdk.features.sdnr.odlux + sdnr-odlux-app-demoApp + 1.7.0-SNAPSHOT + jar + + SDNR ODLUX :: ${project.artifactId} + + + Apache License, Version 2.0 + http://www.apache.org/licenses/LICENSE-2.0 + + + + + true + + + + + + dist + odlux + + + + + maven-clean-plugin + + + + dist + false + + + node + false + + + node_modules + false + + + ../node_modules + false + + + + bin + false + + + + + + de.jacks-it-lab + frontend-maven-plugin + 1.7.2 + + + install node and yarn + + install-node-and-yarn + + + initialize + + v16.17.0 + v1.22.19 + + + + yarn build + + yarn + + + run build + + + + + + + diff --git a/sdnr/wt-odlux/odlux/apps/demoApp/src/actions/authorActions.ts b/sdnr/wt-odlux/odlux/apps/demoApp/src/actions/authorActions.ts new file mode 100644 index 000000000..f22d1e0a3 --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/demoApp/src/actions/authorActions.ts @@ -0,0 +1,48 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * 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. + * ============LICENSE_END========================================================================== + */ +import { Action } from '../../../../framework/src/flux/action'; +import { Dispatch } from '../../../../framework/src/flux/store'; +import { AddErrorInfoAction } from '../../../../framework/src/actions/errorActions'; + +import { IAuthor } from '../models/author'; +import { authorService } from '../services/authorService'; + +export class ApplicationBaseAction extends Action { } + + +export class LoadAllAuthorsAction extends ApplicationBaseAction { + +} + +// in React Action is most times a Message +export class AllAuthorsLoadedAction extends ApplicationBaseAction { + constructor(public authors: IAuthor[] | null, public error?: string) { + super(); + } +} + +export const loadAllAuthorsAsync = (dispatch: Dispatch) => { + dispatch(new LoadAllAuthorsAction()); + authorService.getAllAuthors().then(authors => { + dispatch(new AllAuthorsLoadedAction(authors)); + }, error => { + dispatch(new AllAuthorsLoadedAction(null, error)); + dispatch(new AddErrorInfoAction(error)); + }); +}; + diff --git a/sdnr/wt-odlux/odlux/apps/demoApp/src/components/counter.tsx b/sdnr/wt-odlux/odlux/apps/demoApp/src/components/counter.tsx new file mode 100644 index 000000000..1aad97451 --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/demoApp/src/components/counter.tsx @@ -0,0 +1,29 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * 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. + * ============LICENSE_END========================================================================== + */ +import React, { FC, useState } from 'react'; + +const Counter: FC = () => { + const [counter, setCounter] = useState(0); + return ( + + ); +}; + +Counter.displayName = 'Counter'; + +export { Counter }; \ No newline at end of file diff --git a/sdnr/wt-odlux/odlux/apps/demoApp/src/handlers/demoAppRootHandler.ts b/sdnr/wt-odlux/odlux/apps/demoApp/src/handlers/demoAppRootHandler.ts new file mode 100644 index 000000000..1f920f2a8 --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/demoApp/src/handlers/demoAppRootHandler.ts @@ -0,0 +1,44 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * 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. + * ============LICENSE_END========================================================================== + */ + +import { combineActionHandler } from '../../../../framework/src/flux/middleware'; + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +import { IApplicationStoreState } from '../../../../framework/src/store/applicationStore'; + +import { listAuthorsHandler, IListAuthors } from './listAuthorsHandler'; +import { editAuthorHandler, IEditAuthor } from './editAuthorHandler'; + +export interface IDemoAppStoreState { + listAuthors: IListAuthors; + editAuthor: IEditAuthor; +} + +declare module '../../../../framework/src/store/applicationStore' { + interface IApplicationStoreState { + demo: IDemoAppStoreState; + } +} + +const actionHandlers = { + listAuthors: listAuthorsHandler, + editAuthor: editAuthorHandler, +}; + +export const demoAppRootHandler = combineActionHandler (actionHandlers); +export default demoAppRootHandler; diff --git a/sdnr/wt-odlux/odlux/apps/demoApp/src/handlers/editAuthorHandler.ts b/sdnr/wt-odlux/odlux/apps/demoApp/src/handlers/editAuthorHandler.ts new file mode 100644 index 000000000..1d37a36cc --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/demoApp/src/handlers/editAuthorHandler.ts @@ -0,0 +1,33 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * 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. + * ============LICENSE_END========================================================================== + */ +import { IActionHandler } from '../../../../framework/src/flux/action'; + +import { IAuthor } from '../models/author'; +export interface IEditAuthor { + author: IAuthor | null; + isDirty: boolean; +} + +const editAuthorInit: IEditAuthor = { + author: null, + isDirty: false, +}; + +export const editAuthorHandler: IActionHandler = (state = editAuthorInit, _action) => { + return state; +}; diff --git a/sdnr/wt-odlux/odlux/apps/demoApp/src/handlers/listAuthorsHandler.ts b/sdnr/wt-odlux/odlux/apps/demoApp/src/handlers/listAuthorsHandler.ts new file mode 100644 index 000000000..c85eaff04 --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/demoApp/src/handlers/listAuthorsHandler.ts @@ -0,0 +1,57 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * 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. + * ============LICENSE_END========================================================================== + */ +import { IActionHandler } from '../../../../framework/src/flux/action'; + +import { IAuthor } from '../models/author'; +import { LoadAllAuthorsAction, AllAuthorsLoadedAction } from '../actions/authorActions'; + +export interface IListAuthors { + authors: IAuthor[]; + busy: boolean; +} + +const listAuthorsInit: IListAuthors = { + authors: [], + busy: false, +}; + +export const listAuthorsHandler: IActionHandler = (state = listAuthorsInit, action) => { + if (action instanceof LoadAllAuthorsAction) { + + state = { + ...state, + busy: true, + }; + + } else if (action instanceof AllAuthorsLoadedAction) { + if (!action.error && action.authors) { + state = { + ...state, + authors: action.authors, + busy: false, + }; + } else { + state = { + ...state, + busy: false, + }; + } + } + + return state; +}; \ No newline at end of file diff --git a/sdnr/wt-odlux/odlux/apps/demoApp/src/index.html b/sdnr/wt-odlux/odlux/apps/demoApp/src/index.html new file mode 100644 index 000000000..521c8902c --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/demoApp/src/index.html @@ -0,0 +1,26 @@ + + + + + + + + + Demo App + + + +
+ + + + + + \ No newline at end of file diff --git a/sdnr/wt-odlux/odlux/apps/demoApp/src/models/author.ts b/sdnr/wt-odlux/odlux/apps/demoApp/src/models/author.ts new file mode 100644 index 000000000..bdd414cba --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/demoApp/src/models/author.ts @@ -0,0 +1,37 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * 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. + * ============LICENSE_END========================================================================== + */ + +/** + * Represents an author. + */ +export interface IAuthor { + /** + * Defines the unique id of the author. + */ + id: number; + + /** + * Defines the first name of this author. + */ + firstName: string; + + /** + * Defines the last name of this author. + */ + lastName: string; +} diff --git a/sdnr/wt-odlux/odlux/apps/demoApp/src/plugin.tsx b/sdnr/wt-odlux/odlux/apps/demoApp/src/plugin.tsx new file mode 100644 index 000000000..7b29b4045 --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/demoApp/src/plugin.tsx @@ -0,0 +1,54 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * 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. + * ============LICENSE_END========================================================================== + */ +import React from 'react'; +import { withRouter, RouteComponentProps, Route, Switch, Redirect } from 'react-router-dom'; + +import { faAddressBook } from '@fortawesome/free-solid-svg-icons/faAddressBook'; + +import applicationManager from '../../../framework/src/services/applicationManager'; +import { connect, Connect } from '../../../framework/src/flux/connect'; + +import { demoAppRootHandler } from './handlers/demoAppRootHandler'; + +import AuthorsList from './views/authorsList'; +import EditAuthor from './views/editAuthor'; + +import { Counter } from './components/counter'; + +type AppProps = RouteComponentProps & Connect; + +const App = (props: AppProps) => ( + + + + + +); + +const FinalApp = withRouter(connect()(App)); + +export function register() { + applicationManager.registerApplication({ + name: 'demo', + icon: faAddressBook, + rootComponent: FinalApp, + rootActionHandler: demoAppRootHandler, + exportedComponents: { counter: Counter }, + menuEntry: 'Demo', + }); +} diff --git a/sdnr/wt-odlux/odlux/apps/demoApp/src/services/authorService.ts b/sdnr/wt-odlux/odlux/apps/demoApp/src/services/authorService.ts new file mode 100644 index 000000000..deaa2ff76 --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/demoApp/src/services/authorService.ts @@ -0,0 +1,72 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * 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. + * ============LICENSE_END========================================================================== + */ +import { IAuthor } from '../models/author'; + +import * as $ from 'jquery'; + +const base_url = 'https://api.mfico.de/v1/authors'; + +/** + * Represents a web api accessor service for all author related actions. + */ +class AuthorService { + + /** + * Gets all known authors from the backend. + * @returns A promise of the type array of @see {@link IAuthor} containing all known authors. + */ + public getAllAuthors(): Promise { + return new Promise((resolve: (value: IAuthor[]) => void, reject: (err: any) => void) => { + $.ajax({ method: 'GET', url: base_url }) + .then((data) => { resolve(data); }, (err) => { reject(err); }); + }); + } + + /** + * Gets an author by its id from the backend. + * @returns A promise of the type @see {@link IAuthor} containing the author to get. + */ + public getAuthorById(id: string | number): Promise { + return new Promise((resolve: (value: IAuthor) => void, reject: (err: any) => void) => { + $.ajax({ method: 'GET', url: base_url + '/' + id }) + .then((data) => { resolve(data); }, (err) => { reject(err); }); + }); + } + + + /** + * Saves the given author to the backend api. + * @returns A promise of the type @see {@link IAuthor} containing the autor returned by the backend api. + */ + public saveAuthor(author: IAuthor): Promise { + return new Promise((resolve: (value: IAuthor) => void, reject: (err: any) => void) => { + // simulate server save + window.setTimeout(() => { + if (Math.random() > 0.8) { + reject('Could not save author.'); + } else { + resolve(author); + } + }, 800); // simulate a short network delay + }); + } +} + +// return as a singleton +export const authorService = new AuthorService(); +export default authorService; diff --git a/sdnr/wt-odlux/odlux/apps/demoApp/src/views/authorsList.tsx b/sdnr/wt-odlux/odlux/apps/demoApp/src/views/authorsList.tsx new file mode 100644 index 000000000..5d9f13a55 --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/demoApp/src/views/authorsList.tsx @@ -0,0 +1,93 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * 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. + * ============LICENSE_END========================================================================== + */ +import React from 'react'; +import { withRouter, RouteComponentProps } from 'react-router-dom'; + +import Table from '@mui/material/Table'; +import TableBody from '@mui/material/TableBody'; +import TableCell from '@mui/material/TableCell'; +import TableHead from '@mui/material/TableHead'; +import TableRow from '@mui/material/TableRow'; +import Paper from '@mui/material/Paper'; // means border + +import { connect } from '../../../../framework/src/flux/connect'; + +import { loadAllAuthorsAsync } from '../actions/authorActions'; +import { IAuthor } from '../models/author'; + +interface IAuthorsListProps { + authors: IAuthor[]; + busy: boolean; + onLoadAllAuthors: () => void; +} + +class AuthorsListComponent extends React.Component { + + render(): JSX.Element { + const { authors, busy } = this.props; + return busy + ? ( + + Loading + + ) + : ( + + + + + Id + First Name + Last Name + + + + {authors.map(author => ( + this.editAuthor(author)}> + {author.id} + {author.firstName} + {author.lastName} + + ))} + +
+
+ ); + } + + public componentDidMount() { + this.props.onLoadAllAuthors(); + } + + private editAuthor = (author: IAuthor) => { + if (author) this.props.history.push(this.props.match.path + '/' + author.id); + }; +} + +export const AuthorsList = withRouter( + connect( + ({ demo: state }) => ({ + authors: state.listAuthors.authors, + busy: state.listAuthors.busy, + }), + (dispatcher) => ({ + onLoadAllAuthors: () => { + dispatcher.dispatch(loadAllAuthorsAsync); + }, + }))(AuthorsListComponent)); +export default AuthorsList; diff --git a/sdnr/wt-odlux/odlux/apps/demoApp/src/views/editAuthor.tsx b/sdnr/wt-odlux/odlux/apps/demoApp/src/views/editAuthor.tsx new file mode 100644 index 000000000..0da619ba2 --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/demoApp/src/views/editAuthor.tsx @@ -0,0 +1,34 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * 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. + * ============LICENSE_END========================================================================== + */ +import React from 'react'; +import { withRouter, RouteComponentProps } from 'react-router-dom'; + +type EditAuthorProps = RouteComponentProps<{ authorId: string }>; + +class EditAuthorComponent extends React.Component { + render(): JSX.Element { + return ( +
+

Edit Author { this.props.match.params.authorId }

+
+ ); + } +} + +export const EditAuthor = withRouter(EditAuthorComponent); +export default EditAuthor; \ No newline at end of file diff --git a/sdnr/wt-odlux/odlux/apps/demoApp/tsconfig.json b/sdnr/wt-odlux/odlux/apps/demoApp/tsconfig.json new file mode 100644 index 000000000..ca65092e0 --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/demoApp/tsconfig.json @@ -0,0 +1,37 @@ +{ + "compilerOptions": { + "baseUrl": "./src", + "outDir": "./dist", + "sourceMap": true, + "forceConsistentCasingInFileNames": true, + "allowSyntheticDefaultImports": true, + "allowUnreachableCode": false, + "allowUnusedLabels": false, + "noFallthroughCasesInSwitch": true, + "noImplicitAny": true, + "noImplicitReturns": true, + "noImplicitThis": true, + "strictNullChecks": true, + "pretty": true, + "newLine": "LF", + "module": "es2015", + "target": "es2016", + "moduleResolution": "node", + "experimentalDecorators": true, + "jsx": "preserve", + "lib": [ + "dom", + "es2015", + "es2016" + ], + "types": [ + "prop-types", + "react", + "react-dom" + ] + }, + "exclude": [ + "dist", + "node_modules" + ] +} diff --git a/sdnr/wt-odlux/odlux/apps/demoApp/webpack.config.js b/sdnr/wt-odlux/odlux/apps/demoApp/webpack.config.js new file mode 100644 index 000000000..0476c301e --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/demoApp/webpack.config.js @@ -0,0 +1,134 @@ +/** + * Webpack 4 configuration file + * see https://webpack.js.org/configuration/ + * see https://webpack.js.org/configuration/dev-server/ + */ + +"use strict"; + +const path = require("path"); +const webpack = require("webpack"); +const CopyWebpackPlugin = require("copy-webpack-plugin"); +const TerserPlugin = require('terser-webpack-plugin'); + +// const __dirname = (path => path.replace(/^([a-z]\:)/, c => c.toUpperCase()))(process.__dirname()); + +module.exports = (env) => { + const distPath = path.resolve(__dirname, env === "release" ? "." : "../..", "dist"); + const frameworkPath = path.resolve(__dirname, env === "release" ? "../../framework" : "../..", "dist"); + return [{ + name: "App", + + mode: "none", //disable default behavior + + target: "web", + + context: path.resolve(__dirname, "src"), + + entry: { + demoApp: ["./plugin.tsx"] + }, + + devtool: env === "release" ? false : "source-map", + + resolve: { + extensions: [".ts", ".tsx", ".js", ".jsx"] + }, + + output: { + path: distPath, + filename: "[name].js", + library: "[name]", + libraryTarget: "umd2", + chunkFilename: "[name].js" + }, + module: { + rules: [{ + test: /\.tsx?$/, + exclude: /node_modules/, + use: [{ + loader: "babel-loader" + }, { + loader: "ts-loader" + }] + }, { + test: /\.jsx?$/, + exclude: /node_modules/, + use: [{ + loader: "babel-loader" + }] + }] + }, + optimization: { + noEmitOnErrors: true, + namedModules: env !== "release", + minimize: env === "release", + minimizer: env !== "release" ? [] : [new TerserPlugin({ + terserOptions: { + warnings: false, // false, true, "verbose" + compress: { + drop_console: true, + drop_debugger: true, + } + } + })], + }, + plugins: [ + new webpack.DllReferencePlugin({ + context: path.resolve(__dirname, "../../framework/src"), + manifest: require(path.resolve(frameworkPath, "vendor-manifest.json")), + sourceType: "umd2" + }), + new webpack.DllReferencePlugin({ + context: path.resolve(__dirname, "../../framework/src"), + manifest: require(path.resolve(frameworkPath, "app-manifest.json")), + sourceType: "umd2" + }), + ...(env === "release" ? [ + new webpack.DefinePlugin({ + "process.env": { + NODE_ENV: "'production'", + VERSION: JSON.stringify(require("./package.json").version) + } + }), + ] : [ + new webpack.DefinePlugin({ + "process.env": { + NODE_ENV: "'development'", + VERSION: JSON.stringify(require("./package.json").version) + } + }), + new CopyWebpackPlugin([{ + from: 'index.html', + to: distPath + }]), + ]) + ], + + devServer: { + public: "http://localhost:3100", + contentBase: frameworkPath, + + compress: true, + headers: { + "Access-Control-Allow-Origin": "*" + }, + host: "0.0.0.0", + port: 3100, + disableHostCheck: true, + historyApiFallback: true, + inline: true, + hot: false, + quiet: false, + stats: { + colors: true + }, + proxy: { + "/api": { + target: "http://localhost:3001", + secure: false + } + } + } + }]; +} diff --git a/sdnr/wt-odlux/odlux/apps/eventLogApp/.babelrc b/sdnr/wt-odlux/odlux/apps/eventLogApp/.babelrc new file mode 100644 index 000000000..3d8cd1260 --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/eventLogApp/.babelrc @@ -0,0 +1,17 @@ +{ + "presets": [ + ["@babel/preset-react"], + ["@babel/preset-env", { + "targets": { + "chrome": "66" + }, + "spec": true, + "loose": false, + "modules": false, + "debug": false, + "useBuiltIns": "usage", + "forceAllTransforms": true + }] + ], + "plugins": [] +} diff --git a/sdnr/wt-odlux/odlux/apps/eventLogApp/package.json b/sdnr/wt-odlux/odlux/apps/eventLogApp/package.json new file mode 100644 index 000000000..fb6cedf56 --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/eventLogApp/package.json @@ -0,0 +1,43 @@ +{ + "name": "@odlux/eventlog-app", + "version": "0.1.0", + "description": "A react based modular UI to display event log from a database.", + "main": "index.js", + "scripts": { + "start": "webpack-dev-server --env debug", + "build": "webpack --env release --config webpack.config.js", + "build:dev": "webpack --env debug --config webpack.config.js" + }, + "repository": { + "type": "git", + "url": "https://git.mfico.de/highstreet-technologies/odlux.git" + }, + "keywords": [ + "reactjs", + "redux", + "ui", + "framework" + ], + "author": "Sai Neetha Phulmali", + "license": "Apache-2.0", + "dependencies": { + "@emotion/react": "^11.7.0", + "@emotion/styled": "^11.6.0", + "@mui/icons-material": "^5.2.0", + "@mui/material": "^5.2.2", + "@mui/styles": "^5.2.2", + "@odlux/framework": "*" + }, + "peerDependencies": { + "@types/classnames": "2.2.6", + "@types/flux": "3.1.8", + "@types/jquery": "3.3.10", + "@types/react": "17.0.37", + "@types/react-dom": "17.0.11", + "@types/react-router-dom": "5.1.7", + "jquery": "3.3.1", + "react": "17.0.2", + "react-dom": "17.0.2", + "react-router-dom": "5.2.0" + } +} diff --git a/sdnr/wt-odlux/odlux/apps/eventLogApp/pom.xml b/sdnr/wt-odlux/odlux/apps/eventLogApp/pom.xml new file mode 100644 index 000000000..c3ab9ac47 --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/eventLogApp/pom.xml @@ -0,0 +1,109 @@ + + + + + 4.0.0 + + org.onap.ccsdk.features.sdnr.odlux + sdnr-odlux-app-eventLogApp + 1.7.0-SNAPSHOT + jar + + SDNR ODLUX :: ${project.artifactId} + + + Apache License, Version 2.0 + http://www.apache.org/licenses/LICENSE-2.0 + + + + + true + + + + + + dist + odlux + + + + + maven-clean-plugin + + + + dist + false + + + node + false + + + node_modules + false + + + ../node_modules + false + + + + bin + false + + + + + + de.jacks-it-lab + frontend-maven-plugin + 1.7.2 + + + install node and yarn + + install-node-and-yarn + + + initialize + + v16.17.0 + v1.22.19 + + + + yarn build + + yarn + + + run build + + + + + + + diff --git a/sdnr/wt-odlux/odlux/apps/eventLogApp/src/assets/icons/eventLogAppIcon.svg b/sdnr/wt-odlux/odlux/apps/eventLogApp/src/assets/icons/eventLogAppIcon.svg new file mode 100644 index 000000000..743167d2c --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/eventLogApp/src/assets/icons/eventLogAppIcon.svg @@ -0,0 +1,21 @@ + + + + + + + + + + + + diff --git a/sdnr/wt-odlux/odlux/apps/eventLogApp/src/components/refreshEventLogDialog.tsx b/sdnr/wt-odlux/odlux/apps/eventLogApp/src/components/refreshEventLogDialog.tsx new file mode 100644 index 000000000..8b5d95173 --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/eventLogApp/src/components/refreshEventLogDialog.tsx @@ -0,0 +1,117 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * 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. + * ============LICENSE_END========================================================================== + */ +import * as React from 'react'; + +import Button from '@mui/material/Button'; +import Dialog from '@mui/material/Dialog'; +import DialogActions from '@mui/material/DialogActions'; +import DialogContent from '@mui/material/DialogContent'; +import DialogContentText from '@mui/material/DialogContentText'; +import DialogTitle from '@mui/material/DialogTitle'; + +import { eventLogReloadAction } from '../handlers/eventLogHandler'; +import { IDispatcher, connect, Connect } from '../../../../framework/src/flux/connect'; + +import { EventLogType } from '../models/eventLogType'; + +export enum RefreshEventLogDialogMode { + None = "none", + RefreshEventLogTable = "RefreshEventLogTable", +} + +const mapDispatch = (dispatcher: IDispatcher) => ({ + refreshEventLog: () => dispatcher.dispatch(eventLogReloadAction) +}); + +type DialogSettings = { + dialogTitle: string, + dialogDescription: string, + applyButtonText: string, + cancelButtonText: string, + enableMountIdEditor: boolean, + enableUsernameEditor: boolean, + enableExtendedEditor: boolean, +} + +const settings: { [key: string]: DialogSettings } = { + [RefreshEventLogDialogMode.None]: { + dialogTitle: "", + dialogDescription: "", + applyButtonText: "", + cancelButtonText: "", + enableMountIdEditor: false, + enableUsernameEditor: false, + enableExtendedEditor: false, + }, + [RefreshEventLogDialogMode.RefreshEventLogTable]: { + dialogTitle: "Do you want to refresh the Event Log?", + dialogDescription: "", + applyButtonText: "Yes", + cancelButtonText: "Cancel", + enableMountIdEditor: true, + enableUsernameEditor: true, + enableExtendedEditor: true, + } +} + +type RefreshEventLogDialogComponentProps = Connect & { + mode: RefreshEventLogDialogMode; + onClose: () => void; +}; + +type RefreshEventLogDialogComponentState = EventLogType & { isNameValid: boolean, isHostSet: boolean }; + +class RefreshEventLogDialogComponent extends React.Component { + constructor(props: RefreshEventLogDialogComponentProps) { + super(props); + } + + render(): JSX.Element { + const setting = settings[this.props.mode]; + return ( + + {setting.dialogTitle} + + + {setting.dialogDescription} + + + + + + + + ); + } + + private onRefresh = () => { + this.props.refreshEventLog(); + this.props.onClose(); + }; + + private onCancel = () => { + this.props.onClose(); + } +} + +export const RefreshEventLogDialog = connect(undefined, mapDispatch)(RefreshEventLogDialogComponent); +export default RefreshEventLogDialog; \ No newline at end of file diff --git a/sdnr/wt-odlux/odlux/apps/eventLogApp/src/handlers/eventLogAppRootHandler.ts b/sdnr/wt-odlux/odlux/apps/eventLogApp/src/handlers/eventLogAppRootHandler.ts new file mode 100644 index 000000000..6e2d40efd --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/eventLogApp/src/handlers/eventLogAppRootHandler.ts @@ -0,0 +1,45 @@ +/** +* ============LICENSE_START======================================================================== +* ONAP : ccsdk feature sdnr wt odlux +* ================================================================================================= +* Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved. +* ================================================================================================= +* 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. +* ============LICENSE_END========================================================================== +*/ +// main state handler + +import { combineActionHandler } from '../../../../framework/src/flux/middleware'; + +// ** do not remove ** +import { IApplicationStoreState } from '../../../../framework/src/store/applicationStore'; +import { IActionHandler } from '../../../../framework/src/flux/action'; +import { IEventLogState, eventLogActionHandler } from './eventLogHandler'; + + +export interface IEventLogAppStateState { + logEntries: IEventLogState +} + + +declare module '../../../../framework/src/store/applicationStore' { + interface IApplicationStoreState { + eventLog: IEventLogAppStateState; + } +} + +const actionHandlers = { + logEntries: eventLogActionHandler +}; + +export const EventLogAppRootHandler = combineActionHandler(actionHandlers); +export default EventLogAppRootHandler; + diff --git a/sdnr/wt-odlux/odlux/apps/eventLogApp/src/handlers/eventLogHandler.tsx b/sdnr/wt-odlux/odlux/apps/eventLogApp/src/handlers/eventLogHandler.tsx new file mode 100644 index 000000000..300a8f7b3 --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/eventLogApp/src/handlers/eventLogHandler.tsx @@ -0,0 +1,36 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * 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. + * ============LICENSE_END========================================================================== + */ +import { createExternal,IExternalTableState } from '../../../../framework/src/components/material-table/utilities'; +import { createSearchDataHandler } from '../../../../framework/src/utilities/elasticSearch'; + +import { EventLogType } from '../models/eventLogType'; + +export interface IEventLogState extends IExternalTableState { } + +// create eleactic search material data fetch handler +const eventLogSearchHandler = createSearchDataHandler("eventlog"); + +export const { + actionHandler: eventLogActionHandler, + createActions: createEventLogActions, + createProperties: createEventLogProperties, + reloadAction: eventLogReloadAction, + + // set value action, to change a value +} = createExternal(eventLogSearchHandler, appState => appState.eventLog.logEntries); + diff --git a/sdnr/wt-odlux/odlux/apps/eventLogApp/src/index.html b/sdnr/wt-odlux/odlux/apps/eventLogApp/src/index.html new file mode 100644 index 000000000..8027509e6 --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/eventLogApp/src/index.html @@ -0,0 +1,26 @@ + + + + + + + + + EventLog App + + + +
+ + + + + + \ No newline at end of file diff --git a/sdnr/wt-odlux/odlux/apps/eventLogApp/src/models/eventLogType.ts b/sdnr/wt-odlux/odlux/apps/eventLogApp/src/models/eventLogType.ts new file mode 100644 index 000000000..7c68e6497 --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/eventLogApp/src/models/eventLogType.ts @@ -0,0 +1,27 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * 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. + * ============LICENSE_END========================================================================== + */ +export { HitEntry, Result } from '../../../../framework/src/models'; +export type EventLogType = { + nodeId: string; + counter: number; + timestamp: string; + objectId: string; + attributeName: string; + newValue: string; + sourceType: string; +} diff --git a/sdnr/wt-odlux/odlux/apps/eventLogApp/src/pluginEventLog.tsx b/sdnr/wt-odlux/odlux/apps/eventLogApp/src/pluginEventLog.tsx new file mode 100644 index 000000000..8ee322a8e --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/eventLogApp/src/pluginEventLog.tsx @@ -0,0 +1,42 @@ +/** +* ============LICENSE_START======================================================================== +* ONAP : ccsdk feature sdnr wt odlux +* ================================================================================================= +* Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved. +* ================================================================================================= +* 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. +* ============LICENSE_END========================================================================== +*/ +// app configuration and main entry point for the app + +import React, { FC } from 'react'; + +import applicationManager from '../../../framework/src/services/applicationManager'; + +import { EventLog } from './views/eventLog'; +import eventLogAppRootHandler from './handlers/eventLogAppRootHandler'; + +const appIcon = require('./assets/icons/eventLogAppIcon.svg'); // select app icon + +const App : FC = () => { + return ; +}; + +export function register() { + applicationManager.registerApplication({ + name: 'eventLog', + icon: appIcon, + rootActionHandler: eventLogAppRootHandler, + rootComponent: App, + menuEntry: 'EventLog', + }); +} + diff --git a/sdnr/wt-odlux/odlux/apps/eventLogApp/src/views/eventLog.tsx b/sdnr/wt-odlux/odlux/apps/eventLogApp/src/views/eventLog.tsx new file mode 100644 index 000000000..1fc53f284 --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/eventLogApp/src/views/eventLog.tsx @@ -0,0 +1,102 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * 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. + * ============LICENSE_END========================================================================== + */ +import * as React from "react"; + +import { Connect, connect, IDispatcher } from '../../../../framework/src/flux/connect'; +import { MaterialTable, MaterialTableCtorType } from '../../../../framework/src/components/material-table'; +import Refresh from '@mui/icons-material/Refresh'; + +import { EventLogType } from '../models/eventLogType'; +import { IApplicationStoreState } from "../../../../framework/src/store/applicationStore"; +import { createEventLogProperties, createEventLogActions } from "../handlers/eventLogHandler"; +import RefreshEventLogDialog, { RefreshEventLogDialogMode } from '../components/refreshEventLogDialog'; + +const EventLogTable = MaterialTable as MaterialTableCtorType; + +const mapProps = (state: IApplicationStoreState) => ({ + eventLogProperties: createEventLogProperties(state), + eventLog: state.eventLog.logEntries +}); + +const mapDispatch = (dispatcher: IDispatcher) => ({ + eventLogActions: createEventLogActions(dispatcher.dispatch) +}); + +type EventLogComponentProps = Connect; +type EventLogComponentState = { + refreshEventLogEditorMode: RefreshEventLogDialogMode +} +let initalSorted = false; + +class EventLogComponent extends React.Component { + constructor(props: EventLogComponentProps) { + super(props); + + this.state = { + refreshEventLogEditorMode: RefreshEventLogDialogMode.None + }; + } + + render(): JSX.Element { + + const refreshEventLogAction = { + icon: Refresh, tooltip: 'Refresh Event log', ariaLabel:'refresh', onClick: () => { + this.setState({ + refreshEventLogEditorMode: RefreshEventLogDialogMode.RefreshEventLogTable + }); + } + }; + return ( + <> + + + + + ) + } + + private onCloseRefreshEventLogDialog = () => { + this.setState({ + refreshEventLogEditorMode: RefreshEventLogDialogMode.None + }); + } + componentDidMount() { + + if (!initalSorted) { + initalSorted = true; + this.props.eventLogActions.onHandleExplicitRequestSort("timestamp", "desc"); + } else { + this.props.eventLogActions.onRefresh(); + } + } +} + +export const EventLog = connect(mapProps, mapDispatch)(EventLogComponent); +export default EventLog; \ No newline at end of file diff --git a/sdnr/wt-odlux/odlux/apps/eventLogApp/tsconfig.json b/sdnr/wt-odlux/odlux/apps/eventLogApp/tsconfig.json new file mode 100644 index 000000000..ca65092e0 --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/eventLogApp/tsconfig.json @@ -0,0 +1,37 @@ +{ + "compilerOptions": { + "baseUrl": "./src", + "outDir": "./dist", + "sourceMap": true, + "forceConsistentCasingInFileNames": true, + "allowSyntheticDefaultImports": true, + "allowUnreachableCode": false, + "allowUnusedLabels": false, + "noFallthroughCasesInSwitch": true, + "noImplicitAny": true, + "noImplicitReturns": true, + "noImplicitThis": true, + "strictNullChecks": true, + "pretty": true, + "newLine": "LF", + "module": "es2015", + "target": "es2016", + "moduleResolution": "node", + "experimentalDecorators": true, + "jsx": "preserve", + "lib": [ + "dom", + "es2015", + "es2016" + ], + "types": [ + "prop-types", + "react", + "react-dom" + ] + }, + "exclude": [ + "dist", + "node_modules" + ] +} diff --git a/sdnr/wt-odlux/odlux/apps/eventLogApp/webpack.config.js b/sdnr/wt-odlux/odlux/apps/eventLogApp/webpack.config.js new file mode 100644 index 000000000..3d056ece1 --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/eventLogApp/webpack.config.js @@ -0,0 +1,165 @@ +/** + * Webpack 4 configuration file + * see https://webpack.js.org/configuration/ + * see https://webpack.js.org/configuration/dev-server/ + */ + +"use strict"; + +const path = require("path"); +const webpack = require("webpack"); +const CopyWebpackPlugin = require("copy-webpack-plugin"); +const TerserPlugin = require('terser-webpack-plugin'); + +// const __dirname = (path => path.replace(/^([a-z]\:)/, c => c.toUpperCase()))(process.__dirname()); + +module.exports = (env) => { + const distPath = path.resolve(__dirname, env === "release" ? "." : "../..", "dist"); + const frameworkPath = path.resolve(__dirname, env === "release" ? "../../framework" : "../..", "dist"); + return [{ + name: "App", + + mode: "none", //disable default behavior + + target: "web", + + context: path.resolve(__dirname, "src"), + + entry: { + eventLogApp: ["./pluginEventLog.tsx"] + }, + + devtool: env === "release" ? false : "source-map", + + resolve: { + extensions: [".ts", ".tsx", ".js", ".jsx"] + }, + + output: { + path: distPath, + filename: "[name].js", + library: "[name]", + libraryTarget: "umd2", + chunkFilename: "[name].js" + }, + module: { + rules: [{ + test: /\.tsx?$/, + exclude: /node_modules/, + use: [{ + loader: "babel-loader" + }, { + loader: "ts-loader" + }] + }, { + test: /\.jsx?$/, + exclude: /node_modules/, + use: [{ + loader: "babel-loader" + }] + }, { + //don't minify images + test: /\.(png|gif|jpg|svg)$/, + use: [{ + loader: 'url-loader', + options: { + limit: 10, + name: './images/[name].[ext]' + } + }] + }] + }, + + optimization: { + noEmitOnErrors: true, + namedModules: env !== "release", + minimize: env === "release", + minimizer: env !== "release" ? [] : [new TerserPlugin({ + terserOptions: { + warnings: false, // false, true, "verbose" + compress: { + drop_console: true, + drop_debugger: true, + } + } + })], + }, + + plugins: [ + new webpack.DllReferencePlugin({ + context: path.resolve(__dirname, "../../framework/src"), + manifest: require(path.resolve(frameworkPath, "vendor-manifest.json")), + sourceType: "umd2" + }), + new webpack.DllReferencePlugin({ + context: path.resolve(__dirname, "../../framework/src"), + manifest: require(path.resolve(frameworkPath, "app-manifest.json")), + sourceType: "umd2" + }), + ...(env === "release" ? [ + new webpack.DefinePlugin({ + "process.env": { + NODE_ENV: "'production'", + VERSION: JSON.stringify(require("./package.json").version) + } + }), + ] : [ + new webpack.DefinePlugin({ + "process.env": { + NODE_ENV: "'development'", + VERSION: JSON.stringify(require("./package.json").version) + } + }), + new CopyWebpackPlugin([{ + from: 'index.html', + to: distPath + }]), + ]) + ], + + devServer: { + public: "http://localhost:3100", + contentBase: frameworkPath, + + compress: true, + headers: { + "Access-Control-Allow-Origin": "*" + }, + host: "0.0.0.0", + port: 3100, + disableHostCheck: true, + historyApiFallback: true, + inline: true, + hot: false, + quiet: false, + stats: { + colors: true + }, + proxy: { + "/oauth2/": { + target: "http://localhost:28181", + secure: false + }, + "/database/": { + target: "http://localhost:28181", + secure: false + }, + "/restconf/": { + target: "http://localhost:28181", + secure: false + }, + "/help/": { + target: "http://localhost:28181", + secure: false + }, + "/websocket/": { + target: "http://localhost:28181", + ws: true, + changeOrigin: true, + secure: false + } + } + + } + }]; +} diff --git a/sdnr/wt-odlux/odlux/apps/faultApp/.babelrc b/sdnr/wt-odlux/odlux/apps/faultApp/.babelrc new file mode 100644 index 000000000..3d8cd1260 --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/faultApp/.babelrc @@ -0,0 +1,17 @@ +{ + "presets": [ + ["@babel/preset-react"], + ["@babel/preset-env", { + "targets": { + "chrome": "66" + }, + "spec": true, + "loose": false, + "modules": false, + "debug": false, + "useBuiltIns": "usage", + "forceAllTransforms": true + }] + ], + "plugins": [] +} diff --git a/sdnr/wt-odlux/odlux/apps/faultApp/package.json b/sdnr/wt-odlux/odlux/apps/faultApp/package.json new file mode 100644 index 000000000..953105584 --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/faultApp/package.json @@ -0,0 +1,46 @@ +{ + "name": "@odlux/fault-app", + "version": "0.1.0", + "description": "A react based modular UI to demo the fault management.", + "main": "index.js", + "scripts": { + "start": "webpack-dev-server --env debug", + "build": "webpack --env release --config webpack.config.js", + "build:dev": "webpack --env debug --config webpack.config.js" + }, + "repository": { + "type": "git", + "url": "https://git.mfico.de/highstreet-technologies/odlux.git" + }, + "keywords": [ + "reactjs", + "redux", + "ui", + "framework" + ], + "author": "Matthias Fischer", + "license": "Apache-2.0", + "dependencies": { + "@emotion/react": "^11.7.0", + "@emotion/styled": "^11.6.0", + "@mui/icons-material": "^5.2.0", + "@mui/material": "^5.2.2", + "@mui/styles": "^5.2.2", + "@odlux/framework": "*", + "@fortawesome/free-solid-svg-icons": "5.6.3", + "@fortawesome/react-fontawesome": "0.1.14" + }, + "peerDependencies": { + "@types/classnames": "2.2.6", + "@types/flux": "3.1.8", + "@types/jquery": "3.3.10", + "@types/react": "17.0.37", + "@types/react-dom": "17.0.11", + "@types/react-router-dom": "5.1.7", + "jquery": "3.3.1", + "react": "17.0.2", + "react-dom": "17.0.2", + "react-router-dom": "5.2.0", + "react-chartjs-2": "^3.0.3" + } +} diff --git a/sdnr/wt-odlux/odlux/apps/faultApp/pom.xml b/sdnr/wt-odlux/odlux/apps/faultApp/pom.xml new file mode 100644 index 000000000..3e7491833 --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/faultApp/pom.xml @@ -0,0 +1,109 @@ + + + + + 4.0.0 + + org.onap.ccsdk.features.sdnr.odlux + sdnr-odlux-app-faultApp + 1.7.0-SNAPSHOT + jar + + SDNR ODLUX :: ${project.artifactId} + + + Apache License, Version 2.0 + http://www.apache.org/licenses/LICENSE-2.0 + + + + + true + + + + + + dist + odlux + + + + + maven-clean-plugin + + + + dist + false + + + node + false + + + node_modules + false + + + ../node_modules + false + + + + bin + false + + + + + + de.jacks-it-lab + frontend-maven-plugin + 1.7.2 + + + install node and yarn + + install-node-and-yarn + + + initialize + + v16.17.0 + v1.22.19 + + + + yarn build + + yarn + + + run build + + + + + + + diff --git a/sdnr/wt-odlux/odlux/apps/faultApp/src/actions/clearStuckAlarmsAction.ts b/sdnr/wt-odlux/odlux/apps/faultApp/src/actions/clearStuckAlarmsAction.ts new file mode 100644 index 000000000..7aac8ba35 --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/faultApp/src/actions/clearStuckAlarmsAction.ts @@ -0,0 +1,37 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * 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. + * ============LICENSE_END========================================================================== + */ + + +import { Dispatch } from '../../../../framework/src/flux/store'; + +import { clearStuckAlarms } from '../services/faultStatusService'; +import { FaultApplicationBaseAction } from './notificationActions'; + +export class AreStuckAlarmsCleared extends FaultApplicationBaseAction { + constructor(public isBusy: boolean) { + super(); + } +} + + +export const clearStuckAlarmAsyncAction = (dispatch: Dispatch) => async (nodeNames: string[]) => { + dispatch(new AreStuckAlarmsCleared(true)); + const result = await clearStuckAlarms(nodeNames).catch(error => { console.error(error); return undefined; }); + dispatch(new AreStuckAlarmsCleared(false)); + return result; +}; \ No newline at end of file diff --git a/sdnr/wt-odlux/odlux/apps/faultApp/src/actions/notificationActions.ts b/sdnr/wt-odlux/odlux/apps/faultApp/src/actions/notificationActions.ts new file mode 100644 index 000000000..584e7cd8b --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/faultApp/src/actions/notificationActions.ts @@ -0,0 +1,33 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * 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. + * ============LICENSE_END========================================================================== + */ +import { Action } from '../../../../framework/src/flux/action'; + +import { FaultAlarmNotification } from '../models/fault'; + +export class FaultApplicationBaseAction extends Action { } + + +export class AddFaultNotificationAction extends FaultApplicationBaseAction { + constructor(public fault:FaultAlarmNotification) { + super(); + } +} + +export class ResetFaultNotificationsAction extends FaultApplicationBaseAction { + +} diff --git a/sdnr/wt-odlux/odlux/apps/faultApp/src/actions/panelChangeActions.ts b/sdnr/wt-odlux/odlux/apps/faultApp/src/actions/panelChangeActions.ts new file mode 100644 index 000000000..fb29e9c1b --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/faultApp/src/actions/panelChangeActions.ts @@ -0,0 +1,37 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * 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. + * ============LICENSE_END========================================================================== + */ +import { Action } from '../../../../framework/src/flux/action'; + +import { PanelId } from '../models/panelId'; + +export class SetPanelAction extends Action { + constructor(public panelId: PanelId) { + super(); + } +} + +export class RememberCurrentPanelAction extends Action { + constructor(public panelId: PanelId) { + super(); + } +} + +export const setPanelAction = (panelId: PanelId) => { + return new SetPanelAction(panelId); +}; + diff --git a/sdnr/wt-odlux/odlux/apps/faultApp/src/actions/statusActions.ts b/sdnr/wt-odlux/odlux/apps/faultApp/src/actions/statusActions.ts new file mode 100644 index 000000000..8b631b96d --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/faultApp/src/actions/statusActions.ts @@ -0,0 +1,60 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * 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. + * ============LICENSE_END========================================================================== + */ +import { Dispatch } from '../../../../framework/src/flux/store'; + +import { getFaultStateFromDatabase } from '../services/faultStatusService'; +import { FaultApplicationBaseAction } from './notificationActions'; + + +export class SetFaultStatusAction extends FaultApplicationBaseAction { + constructor(public criticalFaults: number, public majorFaults: number, public minorFaults: number, public warnings: number, + public isLoadingAlarmStatusChart: boolean, public ConnectedCount: number, public ConnectingCount: number, public DisconnectedCount: number, + public MountedCount: number, public UnableToConnectCount: number, public UndefinedCount: number, public UnmountedCount: number, + public totalCount: number, public isLoadingConnectionStatusChart: boolean) { + super(); + } +} + + +export const refreshFaultStatusAsyncAction = async (dispatch: Dispatch) => { + + // dispatch(new SetFaultStatusAction(0, 0, 0, 0, true, 0, 0, 0, 0, 0, 0, 0, 0, true)); + const result = await getFaultStateFromDatabase().catch(_ => null); + if (result) { + const statusAction = new SetFaultStatusAction( + result.Critical || 0, + result.Major || 0, + result.Minor || 0, + result.Warning || 0, + false, + result.Connected || 0, + result.Connecting || 0, + result.Disconnected || 0, + result.Mounted || 0, + result.UnableToConnect || 0, + result.Undefined || 0, + result.Unmounted || 0, + result.total || 0, + false, + ); + dispatch(statusAction); + return; + } else { + dispatch(new SetFaultStatusAction(0, 0, 0, 0, false, 0, 0, 0, 0, 0, 0, 0, 0, false)); + } +}; diff --git a/sdnr/wt-odlux/odlux/apps/faultApp/src/assets/icons/faultAppIcon.svg b/sdnr/wt-odlux/odlux/apps/faultApp/src/assets/icons/faultAppIcon.svg new file mode 100644 index 000000000..aabbf4cf4 --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/faultApp/src/assets/icons/faultAppIcon.svg @@ -0,0 +1,19 @@ + + + + + + + + + + + + diff --git a/sdnr/wt-odlux/odlux/apps/faultApp/src/components/clearStuckAlarmsDialog.tsx b/sdnr/wt-odlux/odlux/apps/faultApp/src/components/clearStuckAlarmsDialog.tsx new file mode 100644 index 000000000..e86b756a7 --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/faultApp/src/components/clearStuckAlarmsDialog.tsx @@ -0,0 +1,138 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * 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. + * ============LICENSE_END========================================================================== + */ + +import React from 'react'; + +import { Button, Dialog, DialogActions, DialogContent, DialogContentText, DialogTitle } from '@mui/material'; + +import { connect, Connect, IDispatcher } from '../../../../framework/src/flux/connect'; + +import { clearStuckAlarmAsyncAction } from '../actions/clearStuckAlarmsAction'; +import { currentAlarmsReloadAction } from '../handlers/currentAlarmsHandler'; + +export enum ClearStuckAlarmsDialogMode { + None = 'none', + Show = 'show', +} + +const mapDispatch = (dispatcher: IDispatcher) => ({ + clearStuckAlarmsAsync: clearStuckAlarmAsyncAction(dispatcher.dispatch), + reloadCurrentAlarmsAction: () => dispatcher.dispatch(currentAlarmsReloadAction), +}); + +type clearStuckAlarmsProps = Connect & { + numberDevices: Number; + mode: ClearStuckAlarmsDialogMode; + stuckAlarms: string[]; + onClose: () => void; +}; + +type ClearStuckAlarmsState = { + clearAlarmsSuccessful: boolean; + errormessage: string; + unclearedAlarms: string[]; +}; + +class ClearStuckAlarmsDialogComponent extends React.Component { + constructor(props: clearStuckAlarmsProps) { + super(props); + this.state = { + clearAlarmsSuccessful: true, + errormessage: '', + unclearedAlarms: [], + }; + } + + onClose = (event: React.MouseEvent) => { + event.stopPropagation(); + event.preventDefault(); + this.props.onClose(); + }; + + onRefresh = async (event: React.MouseEvent) => { + event.stopPropagation(); + event.preventDefault(); + const result = await this.props.clearStuckAlarmsAsync(this.props.stuckAlarms); + + if (result && result['devicemanager:output'].nodenames && result['devicemanager:output'].nodenames.length !== this.props.stuckAlarms.length) { //show errormessage if not all devices were cleared + const undeletedAlarm = this.props.stuckAlarms.filter(item => !result['devicemanager:output'].nodenames.includes(item)); + const error = 'The alarms of the following devices couldn\'t be refreshed: '; + this.setState({ clearAlarmsSuccessful: false, errormessage: error, unclearedAlarms: undeletedAlarm }); + return; + + } else { //show errormessage if no devices were cleared + this.setState({ clearAlarmsSuccessful: false, errormessage: 'Alarms couldn\'t be refreshed.', unclearedAlarms: [] }); + } + + this.props.reloadCurrentAlarmsAction(); + this.props.onClose(); + }; + + onOk = (event: React.MouseEvent) => { + + event.stopPropagation(); + event.preventDefault(); + if (this.state.unclearedAlarms.length > 0) + this.props.reloadCurrentAlarmsAction(); + this.props.onClose(); + }; + + render() { + console.log(this.props.stuckAlarms); + const device = this.props.numberDevices > 1 ? 'devices' : 'device'; + const defaultMessage = 'Are you sure you want to refresh all alarms for ' + this.props.numberDevices + ' ' + device + '?'; + const message = this.state.clearAlarmsSuccessful ? defaultMessage : this.state.errormessage; + + const defaultTitle = 'Refresh Confirmation'; + const title = this.state.clearAlarmsSuccessful ? defaultTitle : 'Refresh Result'; + + return ( + + {title} + + + {message} + + { + this.state.unclearedAlarms.map(item => + + {item} + , + ) + } + + + { + this.state.clearAlarmsSuccessful && + <> + + + + } + + { + !this.state.clearAlarmsSuccessful && + } + + + ); + } +} + +const ClearStuckAlarmsDialog = connect(undefined, mapDispatch)(ClearStuckAlarmsDialogComponent); +export default ClearStuckAlarmsDialog; diff --git a/sdnr/wt-odlux/odlux/apps/faultApp/src/components/dashboardHome.tsx b/sdnr/wt-odlux/odlux/apps/faultApp/src/components/dashboardHome.tsx new file mode 100644 index 000000000..a3e32c42c --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/faultApp/src/components/dashboardHome.tsx @@ -0,0 +1,473 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * 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. + * ============LICENSE_END========================================================================== + */ +import React from 'react'; +import { RouteComponentProps, withRouter } from 'react-router-dom'; + +import { WithStyles } from '@mui/styles'; +import createStyles from '@mui/styles/createStyles'; +import { Doughnut } from 'react-chartjs-2'; +import { connect, Connect, IDispatcher } from '../../../../framework/src/flux/connect'; + +import { NavigateToApplication } from '../../../../framework/src/actions/navigationActions'; +import { IApplicationStoreState } from '../../../../framework/src/store/applicationStore'; + +const styles = () => createStyles({ + pageWidthSettings: { + width: '50%', + float: 'left', + }, +}); + +const scrollbar = { overflow: 'auto', paddingRight: '20px' }; + +let connectionStatusinitialLoad = true; +let connectionStatusinitialStateChanged = false; +let connectionStatusDataLoad: number[] = [0, 0, 0, 0]; +let connectionTotalCount = 0; + +let alarmStatusinitialLoad = true; +let alarmStatusinitialStateChanged = false; +let alarmStatusDataLoad: number[] = [0, 0, 0, 0]; +let alarmTotalCount = 0; + +const mapProps = (state: IApplicationStoreState) => ({ + alarmStatus: state.fault.faultStatus, +}); + +const mapDispatch = (dispatcher: IDispatcher) => ({ + navigateToApplication: (applicationName: string, path?: string) => dispatcher.dispatch(new NavigateToApplication(applicationName, path)), +}); + +type HomeComponentProps = RouteComponentProps & Connect & WithStyles; + +class DashboardHome extends React.Component { + constructor(props: HomeComponentProps) { + super(props); + this.state = { + }; + } + + render(): JSX.Element { + + if (!this.props.alarmStatus.isLoadingConnectionStatusChart) { + connectionStatusDataLoad = [ + this.props.alarmStatus.Connected, + this.props.alarmStatus.Connecting, + this.props.alarmStatus.Disconnected, + this.props.alarmStatus.UnableToConnect, + this.props.alarmStatus.Undefined, + ]; + connectionTotalCount = this.props.alarmStatus.Connected + this.props.alarmStatus.Connecting + + this.props.alarmStatus.Disconnected + this.props.alarmStatus.UnableToConnect + this.props.alarmStatus.Undefined; + + } + + if (!this.props.alarmStatus.isLoadingAlarmStatusChart) { + alarmStatusDataLoad = [ + this.props.alarmStatus.critical, + this.props.alarmStatus.major, + this.props.alarmStatus.minor, + this.props.alarmStatus.warning, + ]; + alarmTotalCount = this.props.alarmStatus.critical + this.props.alarmStatus.major + + this.props.alarmStatus.minor + this.props.alarmStatus.warning; + } + + /** Available Network Connection Status chart data */ + const connectionStatusData = { + labels: [ + 'Connected: ' + this.props.alarmStatus.Connected, + 'Connecting: ' + this.props.alarmStatus.Connecting, + 'Disconnected: ' + this.props.alarmStatus.Disconnected, + 'UnableToConnect: ' + this.props.alarmStatus.UnableToConnect, + 'Undefined: ' + this.props.alarmStatus.Undefined, + ], + datasets: [{ + labels: ['Connected', 'Connecting', 'Disconnected', 'UnableToConnect', 'Undefined'], + data: connectionStatusDataLoad, + backgroundColor: [ + 'rgb(0, 153, 51)', + 'rgb(255, 102, 0)', + 'rgb(191, 191, 191)', + 'rgb(191, 191, 191)', + 'rgb(242, 240, 240)', + ], + }], + }; + + + /** No Devices available */ + const connectionStatusUnavailableData = { + labels: ['No Devices available'], + datasets: [{ + data: [1], + backgroundColor: [ + 'rgb(255, 255, 255)', + ], + }], + }; + + /** Loading Connection Status chart */ + const connectionStatusisLoading = { + labels: ['Loading chart...'], + datasets: [{ + data: [1], + backgroundColor: [ + 'rgb(255, 255, 255)', + ], + }], + }; + + /** Loading Alarm Status chart */ + const alarmStatusisLoading = { + labels: ['Loading chart...'], + datasets: [{ + data: [1], + backgroundColor: [ + 'rgb(255, 255, 255)', + ], + }], + }; + + /** Connection status options */ + let labels: String[] = ['Connected', 'Connecting', 'Disconnected', 'UnableToConnect', 'Undefined']; + const connectionStatusOptions = { + tooltips: { + callbacks: { + label: (tooltipItem: any, data: any) => { + let label = + (data.datasets[tooltipItem.datasetIndex].labels && + data.datasets[tooltipItem.datasetIndex].labels[ + tooltipItem.index + ]) || + data.labels[tooltipItem.index] || + ''; + if (label) { + label += ': '; + } + label += + data.datasets[tooltipItem.datasetIndex].data[tooltipItem.index] + + (data.datasets[tooltipItem.datasetIndex].labelSuffix || ''); + + return label; + }, + }, + }, + responsive: true, + maintainAspectRatio: false, + animation: { + duration: 0, + }, + plugins: { + legend: { + display: true, + position: 'top', + }, + }, + onClick: (event: MouseEvent, item: any) => { + if (item[0]) { + let connectionStatus = labels[item[0]._index] + ''; + this.props.navigateToApplication('connect', '/connectionStatus/' + connectionStatus); + } + }, + }; + + /** Connection status unavailable options */ + const connectionStatusUnavailableOptions = { + responsive: true, + maintainAspectRatio: false, + animation: { + duration: 0, + }, + plugins: { + legend: { + display: true, + position: 'top', + }, + tooltip: { + enabled: false, + }, + }, + }; + + /** Add text inside the doughnut chart for Connection Status */ + const connectionStatusPlugins = [{ + beforeDraw: function (chart: any) { + var width = chart.width, + height = chart.height, + ctx = chart.ctx; + ctx.restore(); + var fontSize = (height / 480).toFixed(2); + ctx.font = fontSize + 'em sans-serif'; + ctx.textBaseline = 'top'; + var text = 'Network Connection Status', + textX = Math.round((width - ctx.measureText(text).width) / 2), + textY = height / 2; + ctx.fillText(text, textX, textY); + ctx.save(); + }, + }]; + + /** Alarm status Data */ + const alarmStatusData = { + labels: [ + 'Critical : ' + this.props.alarmStatus.critical, + 'Major : ' + this.props.alarmStatus.major, + 'Minor : ' + this.props.alarmStatus.minor, + 'Warning : ' + this.props.alarmStatus.warning, + ], + datasets: [{ + labels: ['Critical', 'Major', 'Minor', 'Warning'], + data: alarmStatusDataLoad, + backgroundColor: [ + 'rgb(240, 25, 10)', + 'rgb(240, 133, 10)', + 'rgb(240, 240, 10)', + 'rgb(46, 115, 176)', + ], + }], + }; + + /** No Alarm status available */ + const alarmStatusUnavailableData = { + labels: ['No Alarms available'], + datasets: [{ + data: [1], + backgroundColor: [ + 'rgb(0, 153, 51)', + ], + }], + }; + + /** Alarm status Options */ + let alarmLabels: String[] = ['Critical', 'Major', 'Minor', 'Warning']; + const alarmStatusOptions = { + tooltips: { + callbacks: { + label: (tooltipItem: any, data: any) => { + let label = + (data.datasets[tooltipItem.datasetIndex].labels && + data.datasets[tooltipItem.datasetIndex].labels[ + tooltipItem.index + ]) || + data.labels[tooltipItem.index] || + ''; + if (label) { + label += ': '; + } + label += + data.datasets[tooltipItem.datasetIndex].data[tooltipItem.index] + + (data.datasets[tooltipItem.datasetIndex].labelSuffix || ''); + + return label; + }, + }, + }, + responsive: true, + maintainAspectRatio: false, + animation: { + duration: 0, + }, + plugins: { + legend: { + display: true, + position: 'top', + }, + }, + onClick: (event: MouseEvent, item: any) => { + if (item[0]) { + let severity = alarmLabels[item[0]._index] + ''; + this.props.navigateToApplication('fault', '/alarmStatus/' + severity); + } + }, + }; + + /** Alarm status unavailable options */ + const alarmStatusUnavailableOptions = { + responsive: true, + maintainAspectRatio: false, + animation: { + duration: 0, + }, + plugins: { + legend: { + display: true, + position: 'top', + }, + tooltip: { + enabled: false, + }, + }, + }; + /** Add text inside the doughnut chart for Alarm Status */ + const alarmStatusPlugins = [{ + beforeDraw: function (chart: any) { + var width = chart.width, + height = chart.height, + ctx = chart.ctx; + ctx.restore(); + var fontSize = (height / 480).toFixed(2); + ctx.font = fontSize + 'em sans-serif'; + ctx.textBaseline = 'top'; + var text = 'Network Alarm Status', + textX = Math.round((width - ctx.measureText(text).width) / 2), + textY = height / 2; + ctx.fillText(text, textX, textY); + ctx.save(); + }, + }]; + + return ( + <> +
+

Welcome to ODLUX

+
+ {this.checkElementsAreLoaded() ? + this.checkConnectionStatus() && connectionTotalCount != 0 ? + + : + : + } +
+
+ {this.checkAlarmsAreLoaded() ? + this.checkAlarmStatus() && alarmTotalCount != 0 ? + + : + : + } +
+
+ + ); + } + + /** Check if connection status data available */ + public checkConnectionStatus = () => { + let statusCount = this.props.alarmStatus; + if (statusCount.isLoadingConnectionStatusChart) { + return true; + } + if (statusCount.Connected == 0 && statusCount.Connecting == 0 && statusCount.Disconnected == 0 + && statusCount.UnableToConnect == 0 && statusCount.Undefined == 0) { + return false; + } else { + return true; + } + }; + + /** Check if connection status chart data is loaded */ + public checkElementsAreLoaded = () => { + let isLoadingCheck = this.props.alarmStatus; + if (connectionStatusinitialLoad && !isLoadingCheck.isLoadingConnectionStatusChart) { + if (this.checkConnectionStatus()) { + connectionStatusinitialLoad = false; + return true; + } + return false; + } else if (connectionStatusinitialLoad && isLoadingCheck.isLoadingConnectionStatusChart) { + connectionStatusinitialLoad = false; + connectionStatusinitialStateChanged = true; + return !isLoadingCheck.isLoadingConnectionStatusChart; + } else if (connectionStatusinitialStateChanged) { + if (!isLoadingCheck.isLoadingConnectionStatusChart) { + connectionStatusinitialStateChanged = false; + } + return !isLoadingCheck.isLoadingConnectionStatusChart; + } + return true; + }; + + /** Check if alarms data available */ + public checkAlarmStatus = () => { + let alarmCount = this.props.alarmStatus; + if (alarmCount.isLoadingAlarmStatusChart) { + return true; + } + if (alarmCount.critical == 0 && alarmCount.major == 0 && alarmCount.minor == 0 && alarmCount.warning == 0) { + return false; + } else { + return true; + } + }; + + /** Check if alarm status chart data is loaded */ + public checkAlarmsAreLoaded = () => { + let isLoadingCheck = this.props.alarmStatus; + if (alarmStatusinitialLoad && !isLoadingCheck.isLoadingAlarmStatusChart) { + if (this.checkAlarmStatus()) { + alarmStatusinitialLoad = false; + return true; + } + return false; + } else if (alarmStatusinitialLoad && isLoadingCheck.isLoadingAlarmStatusChart) { + alarmStatusinitialLoad = false; + alarmStatusinitialStateChanged = true; + return !isLoadingCheck.isLoadingAlarmStatusChart; + } else if (alarmStatusinitialStateChanged) { + if (!isLoadingCheck.isLoadingAlarmStatusChart) { + alarmStatusinitialStateChanged = false; + } + return !isLoadingCheck.isLoadingAlarmStatusChart; + } + return true; + }; +} + +export default (withRouter(connect(mapProps, mapDispatch)(DashboardHome))); \ No newline at end of file diff --git a/sdnr/wt-odlux/odlux/apps/faultApp/src/components/faultStatus.tsx b/sdnr/wt-odlux/odlux/apps/faultApp/src/components/faultStatus.tsx new file mode 100644 index 000000000..57374dd77 --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/faultApp/src/components/faultStatus.tsx @@ -0,0 +1,106 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * 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. + * ============LICENSE_END========================================================================== + */ +import React from 'react'; + +import { faExclamationTriangle } from '@fortawesome/free-solid-svg-icons'; // select app icon +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import Typography from '@mui/material/Typography'; +import { WithStyles } from '@mui/styles'; +import createStyles from '@mui/styles/createStyles'; +import withStyles from '@mui/styles/withStyles'; +import Tooltip from '@mui/material/Tooltip'; + +import { connect, Connect } from '../../../../framework/src/flux/connect'; +import { IApplicationStoreState } from '../../../../framework/src/store/applicationStore'; + + +const styles = () => createStyles({ + icon: { + marginLeft: 8, + marginRight: 8, + }, + critical: { + color: 'red', + }, + major: { + color: 'orange', + }, + minor: { + color: '#f7f700', + }, + warning: { + color: '#428bca', + }, +}); + +const mapProps = (state: IApplicationStoreState) => ({ + faultStatus: state.fault.faultStatus, +}); + + +type FaultStatusComponentProps = & WithStyles & Connect; + +class FaultStatusComponent extends React.Component { + render(): JSX.Element { + const { classes, faultStatus } = this.props; + + return ( + <> + + Alarm Status: + + + + + + {faultStatus.critical} | + + + + + + + + + {faultStatus.major} | + + + + + + + + + {faultStatus.minor} | + + + + + + + + + {faultStatus.warning} | + + + ); + } +} + +export const FaultStatus = withStyles(styles)(connect(mapProps)(FaultStatusComponent)); +export default FaultStatus; \ No newline at end of file diff --git a/sdnr/wt-odlux/odlux/apps/faultApp/src/components/refreshAlarmLogDialog.tsx b/sdnr/wt-odlux/odlux/apps/faultApp/src/components/refreshAlarmLogDialog.tsx new file mode 100644 index 000000000..59657d8de --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/faultApp/src/components/refreshAlarmLogDialog.tsx @@ -0,0 +1,112 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * 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. + * ============LICENSE_END========================================================================== + */ +import * as React from 'react'; + +import Button from '@mui/material/Button'; +import Dialog from '@mui/material/Dialog'; +import DialogActions from '@mui/material/DialogActions'; +import DialogContent from '@mui/material/DialogContent'; +import DialogContentText from '@mui/material/DialogContentText'; +import DialogTitle from '@mui/material/DialogTitle'; + +import { connect, Connect, IDispatcher } from '../../../../framework/src/flux/connect'; +import { alarmLogEntriesReloadAction } from '../handlers/alarmLogEntriesHandler'; +import { Fault } from '../models/fault'; + +export enum RefreshAlarmLogDialogMode { + None = 'none', + RefreshAlarmLogTable = 'RefreshAlarmLogTable', +} + +const mapDispatch = (dispatcher: IDispatcher) => ({ + refreshAlarmLog: () => dispatcher.dispatch(alarmLogEntriesReloadAction), +}); + +type DialogSettings = { + dialogTitle: string; + dialogDescription: string; + applyButtonText: string; + cancelButtonText: string; + enableMountIdEditor: boolean; + enableUsernameEditor: boolean; + enableExtendedEditor: boolean; +}; + +const settings: { [key: string]: DialogSettings } = { + [RefreshAlarmLogDialogMode.None]: { + dialogTitle: '', + dialogDescription: '', + applyButtonText: '', + cancelButtonText: '', + enableMountIdEditor: false, + enableUsernameEditor: false, + enableExtendedEditor: false, + }, + [RefreshAlarmLogDialogMode.RefreshAlarmLogTable]: { + dialogTitle: 'Do you want to refresh the Alarm Log?', + dialogDescription: '', + applyButtonText: 'Yes', + cancelButtonText: 'Cancel', + enableMountIdEditor: true, + enableUsernameEditor: true, + enableExtendedEditor: true, + }, +}; + +type RefreshAlarmLogDialogComponentProps = Connect & { + mode: RefreshAlarmLogDialogMode; + onClose: () => void; +}; + +type RefreshAlarmLogDialogComponentState = Fault & { isNameValid: boolean; isHostSet: boolean }; + +class RefreshAlarmLogDialogComponent extends React.Component { + render(): JSX.Element { + const setting = settings[this.props.mode]; + return ( + + {setting.dialogTitle} + + + {setting.dialogDescription} + + + + + + + + ); + } + + private onRefresh = () => { + this.props.refreshAlarmLog(); + this.props.onClose(); + }; + + private onCancel = () => { + this.props.onClose(); + }; +} + +export const RefreshAlarmLogDialog = connect(undefined, mapDispatch)(RefreshAlarmLogDialogComponent); +export default RefreshAlarmLogDialog; \ No newline at end of file diff --git a/sdnr/wt-odlux/odlux/apps/faultApp/src/components/refreshCurrentAlarmsDialog.tsx b/sdnr/wt-odlux/odlux/apps/faultApp/src/components/refreshCurrentAlarmsDialog.tsx new file mode 100644 index 000000000..20cd514cd --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/faultApp/src/components/refreshCurrentAlarmsDialog.tsx @@ -0,0 +1,113 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * 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. + * ============LICENSE_END========================================================================== + */ +import * as React from 'react'; + +import Button from '@mui/material/Button'; +import Dialog from '@mui/material/Dialog'; +import DialogActions from '@mui/material/DialogActions'; +import DialogContent from '@mui/material/DialogContent'; +import DialogContentText from '@mui/material/DialogContentText'; +import DialogTitle from '@mui/material/DialogTitle'; + +import { connect, Connect, IDispatcher } from '../../../../framework/src/flux/connect'; + +import { currentAlarmsReloadAction } from '../handlers/currentAlarmsHandler'; +import { Fault } from '../models/fault'; + +export enum RefreshCurrentAlarmsDialogMode { + None = 'none', + RefreshCurrentAlarmsTable = 'RefreshCurrentAlarmsTable', +} + +const mapDispatch = (dispatcher: IDispatcher) => ({ + refreshCurrentAlarms: () => dispatcher.dispatch(currentAlarmsReloadAction), +}); + +type DialogSettings = { + dialogTitle: string; + dialogDescription: string; + applyButtonText: string; + cancelButtonText: string; + enableMountIdEditor: boolean; + enableUsernameEditor: boolean; + enableExtendedEditor: boolean; +}; + +const settings: { [key: string]: DialogSettings } = { + [RefreshCurrentAlarmsDialogMode.None]: { + dialogTitle: '', + dialogDescription: '', + applyButtonText: '', + cancelButtonText: '', + enableMountIdEditor: false, + enableUsernameEditor: false, + enableExtendedEditor: false, + }, + [RefreshCurrentAlarmsDialogMode.RefreshCurrentAlarmsTable]: { + dialogTitle: 'Do you want to refresh the Current Alarms List?', + dialogDescription: '', + applyButtonText: 'Yes', + cancelButtonText: 'Cancel', + enableMountIdEditor: true, + enableUsernameEditor: true, + enableExtendedEditor: true, + }, +}; + +type RefreshCurrentAlarmsDialogComponentProps = Connect & { + mode: RefreshCurrentAlarmsDialogMode; + onClose: () => void; +}; + +type RefreshCurrentAlarmsDialogComponentState = Fault & { isNameValid: boolean; isHostSet: boolean }; + +class RefreshCurrentAlarmsDialogComponent extends React.Component { + render(): JSX.Element { + const setting = settings[this.props.mode]; + return ( + + {setting.dialogTitle} + + + {setting.dialogDescription} + + + + + + + + ); + } + + private onRefresh = () => { + this.props.refreshCurrentAlarms(); + this.props.onClose(); + }; + + private onCancel = () => { + this.props.onClose(); + }; +} + +export const RefreshCurrentAlarmsDialog = connect(undefined, mapDispatch)(RefreshCurrentAlarmsDialogComponent); +export default RefreshCurrentAlarmsDialog; \ No newline at end of file diff --git a/sdnr/wt-odlux/odlux/apps/faultApp/src/handlers/alarmLogEntriesHandler.ts b/sdnr/wt-odlux/odlux/apps/faultApp/src/handlers/alarmLogEntriesHandler.ts new file mode 100644 index 000000000..bdd459669 --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/faultApp/src/handlers/alarmLogEntriesHandler.ts @@ -0,0 +1,36 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * 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. + * ============LICENSE_END========================================================================== + */ +import { createExternal, IExternalTableState } from '../../../../framework/src/components/material-table/utilities'; +import { createSearchDataHandler } from '../../../../framework/src/utilities/elasticSearch'; + +import { Fault } from '../models/fault'; + +export interface IAlarmLogEntriesState extends IExternalTableState { } + +// create eleactic search data fetch handler +const alarmLogEntriesSearchHandler = createSearchDataHandler< Fault>('faultlog'); + +export const { + actionHandler: alarmLogEntriesActionHandler, + createActions: createAlarmLogEntriesActions, + createProperties: createAlarmLogEntriesProperties, + reloadAction: alarmLogEntriesReloadAction, + + // set value action, to change a value +} = createExternal(alarmLogEntriesSearchHandler, appState => appState.fault.alarmLogEntries); + diff --git a/sdnr/wt-odlux/odlux/apps/faultApp/src/handlers/clearStuckAlarmsHandler.ts b/sdnr/wt-odlux/odlux/apps/faultApp/src/handlers/clearStuckAlarmsHandler.ts new file mode 100644 index 000000000..0d5a8c70d --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/faultApp/src/handlers/clearStuckAlarmsHandler.ts @@ -0,0 +1,37 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * 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. + * ============LICENSE_END========================================================================== + */ + +import { IActionHandler } from '../../../../framework/src/flux/action'; + +import { AreStuckAlarmsCleared } from '../actions/clearStuckAlarmsAction'; + +export interface IStuckAlarms { + areAlarmsCleared: boolean; +} + +const initialState: IStuckAlarms = { + areAlarmsCleared: false, +}; + +export const stuckAlarmHandler: IActionHandler = (state = initialState, action) => { + if (action instanceof AreStuckAlarmsCleared) { + state = { ...state, areAlarmsCleared: action.isBusy }; + } + + return state; +}; \ No newline at end of file diff --git a/sdnr/wt-odlux/odlux/apps/faultApp/src/handlers/currentAlarmsHandler.ts b/sdnr/wt-odlux/odlux/apps/faultApp/src/handlers/currentAlarmsHandler.ts new file mode 100644 index 000000000..70aa1c27e --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/faultApp/src/handlers/currentAlarmsHandler.ts @@ -0,0 +1,36 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * 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. + * ============LICENSE_END========================================================================== + */ +import { createExternal, IExternalTableState } from '../../../../framework/src/components/material-table/utilities'; +import { createSearchDataHandler } from '../../../../framework/src/utilities/elasticSearch'; + +import { Fault } from '../models/fault'; + +export interface ICurrentAlarmsState extends IExternalTableState { } + +// create eleactic search data fetch handler +const currentAlarmsSearchHandler = createSearchDataHandler('faultcurrent'); + +export const { + actionHandler: currentAlarmsActionHandler, + createActions: createCurrentAlarmsActions, + createProperties: createCurrentAlarmsProperties, + reloadAction: currentAlarmsReloadAction, + + // set value action, to change a value +} = createExternal(currentAlarmsSearchHandler, appState => appState.fault.currentAlarms); + diff --git a/sdnr/wt-odlux/odlux/apps/faultApp/src/handlers/faultAppRootHandler.ts b/sdnr/wt-odlux/odlux/apps/faultApp/src/handlers/faultAppRootHandler.ts new file mode 100644 index 000000000..e4a19ae5c --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/faultApp/src/handlers/faultAppRootHandler.ts @@ -0,0 +1,63 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * 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. + * ============LICENSE_END========================================================================== + */ +// main state handler + +import { IActionHandler } from '../../../../framework/src/flux/action'; +import { combineActionHandler } from '../../../../framework/src/flux/middleware'; +// ** do not remove ** +// eslint-disable-next-line @typescript-eslint/no-unused-vars +import { IApplicationStoreState } from '../../../../framework/src/store/applicationStore'; + +import { SetPanelAction } from '../actions/panelChangeActions'; +import { PanelId } from '../models/panelId'; +import { alarmLogEntriesActionHandler, IAlarmLogEntriesState } from './alarmLogEntriesHandler'; +import { currentAlarmsActionHandler, ICurrentAlarmsState } from './currentAlarmsHandler'; +import { faultStatusHandler, IFaultStatus } from './faultStatusHandler'; +import { faultNotificationsHandler, IFaultNotifications } from './notificationsHandler'; + +export interface IFaultAppStoreState { + currentAlarms: ICurrentAlarmsState; + faultNotifications: IFaultNotifications; + alarmLogEntries: IAlarmLogEntriesState; + currentOpenPanel: PanelId | null; + faultStatus: IFaultStatus; +} + +const currentOpenPanelHandler: IActionHandler = (state = null, action) => { + if (action instanceof SetPanelAction) { + state = action.panelId; + } + return state; +}; + +declare module '../../../../framework/src/store/applicationStore' { + interface IApplicationStoreState { + fault: IFaultAppStoreState; + } +} + +const actionHandlers = { + currentAlarms: currentAlarmsActionHandler, + faultNotifications: faultNotificationsHandler, + alarmLogEntries: alarmLogEntriesActionHandler, + currentOpenPanel: currentOpenPanelHandler, + faultStatus: faultStatusHandler, +}; + +export const faultAppRootHandler = combineActionHandler(actionHandlers); +export default faultAppRootHandler; diff --git a/sdnr/wt-odlux/odlux/apps/faultApp/src/handlers/faultStatusHandler.ts b/sdnr/wt-odlux/odlux/apps/faultApp/src/handlers/faultStatusHandler.ts new file mode 100644 index 000000000..21b033e6a --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/faultApp/src/handlers/faultStatusHandler.ts @@ -0,0 +1,77 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * 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. + * ============LICENSE_END========================================================================== + */ +import { IActionHandler } from '../../../../framework/src/flux/action'; + +import { SetFaultStatusAction } from '../actions/statusActions'; + +export interface IFaultStatus { + critical: number; + major: number; + minor: number; + warning: number; + isLoadingAlarmStatusChart: boolean; + Connected: number; + Connecting: number; + Disconnected: number; + Mounted: number; + UnableToConnect: number; + Undefined: number; + Unmounted: number; + total: number; + isLoadingConnectionStatusChart: boolean; +} + +const faultStatusInit: IFaultStatus = { + critical: 0, + major: 0, + minor: 0, + warning: 0, + isLoadingAlarmStatusChart: false, + Connected: 0, + Connecting: 0, + Disconnected: 0, + Mounted: 0, + UnableToConnect: 0, + Undefined: 0, + Unmounted: 0, + total: 0, + isLoadingConnectionStatusChart: false, +}; + +export const faultStatusHandler: IActionHandler = (state = faultStatusInit, action) => { + if (action instanceof SetFaultStatusAction) { + state = { + critical: action.criticalFaults, + major: action.majorFaults, + minor: action.minorFaults, + warning: action.warnings, + isLoadingAlarmStatusChart: action.isLoadingAlarmStatusChart, + Connected: action.ConnectedCount, + Connecting: action.ConnectingCount, + Disconnected: action.DisconnectedCount, + Mounted: action.MountedCount, + UnableToConnect: action.UnableToConnectCount, + Undefined: action.UndefinedCount, + Unmounted: action.UnmountedCount, + total: action.totalCount, + isLoadingConnectionStatusChart: action.isLoadingConnectionStatusChart, + }; + } + + return state; +}; \ No newline at end of file diff --git a/sdnr/wt-odlux/odlux/apps/faultApp/src/handlers/notificationsHandler.ts b/sdnr/wt-odlux/odlux/apps/faultApp/src/handlers/notificationsHandler.ts new file mode 100644 index 000000000..3d960bfc4 --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/faultApp/src/handlers/notificationsHandler.ts @@ -0,0 +1,48 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * 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. + * ============LICENSE_END========================================================================== + */ +import { IActionHandler } from '../../../../framework/src/flux/action'; + +import { AddFaultNotificationAction, ResetFaultNotificationsAction } from '../actions/notificationActions'; +import { FaultAlarmNotification } from '../models/fault'; + +export interface IFaultNotifications { + faults: FaultAlarmNotification[]; + since: Date; +} + +const faultNotoficationsInit: IFaultNotifications = { + faults: [], + since: new Date(), +}; + +export const faultNotificationsHandler: IActionHandler = (state = faultNotoficationsInit, action) => { + if (action instanceof AddFaultNotificationAction) { + state = { + ...state, + faults: [...state.faults, action.fault], + }; + } else if (action instanceof ResetFaultNotificationsAction) { + state = { + ...state, + faults: [], + since: new Date(), + }; + } + + return state; +}; \ No newline at end of file diff --git a/sdnr/wt-odlux/odlux/apps/faultApp/src/index.html b/sdnr/wt-odlux/odlux/apps/faultApp/src/index.html new file mode 100644 index 000000000..c37968074 --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/faultApp/src/index.html @@ -0,0 +1,26 @@ + + + + + + + + + Minimal App + + + +
+ + + + + + \ No newline at end of file diff --git a/sdnr/wt-odlux/odlux/apps/faultApp/src/models/fault.ts b/sdnr/wt-odlux/odlux/apps/faultApp/src/models/fault.ts new file mode 100644 index 000000000..c70253e58 --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/faultApp/src/models/fault.ts @@ -0,0 +1,97 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * 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. + * ============LICENSE_END========================================================================== + */ +export type Fault = { + id: string; + nodeId: string; + counter: number; + timestamp: string; + objectId: string; + problem: string; + severity: null | 'Warning' | 'Minor' | 'Major' | 'Critical' | 'NonAlarmed'; + type: string; + sourceType?: string; +}; + +export type FaultAlarmNotification = { + id: string; + timeStamp: string; + nodeName: string; + counter: number; + objectId: string; + problem: string; + severity: string; +}; + +export type FaultAlarmNotificationWS = { + 'node-id': string; + 'data': { + 'counter': number; + 'time-stamp': string; + 'object-id-ref': string; + 'problem': string; + 'severity': null | 'Warning' | 'Minor' | 'Major' | 'Critical' | 'NonAlarmed'; + }; + 'type': { + 'namespace': string; + 'revision': string; + 'type': string; + }; + 'event-time': string; +}; + +/** + * Fault status return type + */ +export type FaultsReturnType = { + criticals: number; + majors: number; + minors: number; + warnings: number; + Connected: number; + Connecting: number; + Disconnected: number; + Mounted: number; + UnableToConnect: number; + Undefined: number; + Unmounted: number; + total: number; +}; + +export type FaultType = { + Critical: number; + Major: number; + Minor: number; + Warning: number; + Connected: number; + Connecting: number; + Disconnected: number; + Mounted: number; + UnableToConnect: number; + Undefined: number; + Unmounted: number; + total: number; +}; + +export type Faults = { + faults: FaultsReturnType; + 'network-element-connections': FaultsReturnType; +}; + +export type DeletedStuckAlarms = { + nodenames: string[]; +}; \ No newline at end of file diff --git a/sdnr/wt-odlux/odlux/apps/faultApp/src/models/panelId.ts b/sdnr/wt-odlux/odlux/apps/faultApp/src/models/panelId.ts new file mode 100644 index 000000000..daebad0e5 --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/faultApp/src/models/panelId.ts @@ -0,0 +1,18 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * 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. + * ============LICENSE_END========================================================================== + */ +export type PanelId = null | 'CurrentAlarms' | 'AlarmNotifications' | 'AlarmLog'; \ No newline at end of file diff --git a/sdnr/wt-odlux/odlux/apps/faultApp/src/pluginFault.tsx b/sdnr/wt-odlux/odlux/apps/faultApp/src/pluginFault.tsx new file mode 100644 index 000000000..2ef243c2d --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/faultApp/src/pluginFault.tsx @@ -0,0 +1,171 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * 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. + * ============LICENSE_END========================================================================== + */ +// app configuration and main entry point for the app + +import React from 'react'; +import { Redirect, Route, RouteComponentProps, Switch, withRouter } from 'react-router-dom'; + +import { connect, Connect, IDispatcher } from '../../../framework/src/flux/connect'; +import applicationManager from '../../../framework/src/services/applicationManager'; +import { IFormatedMessage, subscribe } from '../../../framework/src/services/notificationService'; +import { IApplicationStoreState } from '../../../framework/src/store/applicationStore'; + +import { AddFaultNotificationAction } from './actions/notificationActions'; +import { SetPanelAction } from './actions/panelChangeActions'; +import { refreshFaultStatusAsyncAction, SetFaultStatusAction } from './actions/statusActions'; +import DashboardHome from './components/dashboardHome'; +import { FaultStatus } from './components/faultStatus'; +import { createCurrentAlarmsActions, createCurrentAlarmsProperties, currentAlarmsReloadAction } from './handlers/currentAlarmsHandler'; +import { faultAppRootHandler } from './handlers/faultAppRootHandler'; +import { FaultAlarmNotificationWS } from './models/fault'; +import { PanelId } from './models/panelId'; +import { FaultApplication } from './views/faultApplication'; + +const appIcon = require('./assets/icons/faultAppIcon.svg'); // select app icon + +let currentMountId: string | undefined = undefined; +let currentSeverity: string | undefined = undefined; +let refreshInterval: ReturnType | null = null; + +const mapProps = (state: IApplicationStoreState) => ({ + currentAlarmsProperties: createCurrentAlarmsProperties(state), +}); + +const mapDispatch = (dispatcher: IDispatcher) => ({ + currentAlarmsActions: createCurrentAlarmsActions(dispatcher.dispatch, true), + setCurrentPanel: (panelId: PanelId) => dispatcher.dispatch(new SetPanelAction(panelId)), +}); + +const FaultApplicationRouteAdapter = connect(mapProps, mapDispatch)((props: RouteComponentProps<{ mountId?: string }> & Connect) => { + if (currentMountId !== props.match.params.mountId) { + // route parameter has changed + currentMountId = props.match.params.mountId || undefined; + // Hint: This timeout is need, since it is not recommended to change the state while rendering is in progress ! + window.setTimeout(() => { + if (currentMountId) { + props.setCurrentPanel('CurrentAlarms'); + props.currentAlarmsActions.onFilterChanged('nodeId', currentMountId); + if (!props.currentAlarmsProperties.showFilter) { + props.currentAlarmsActions.onToggleFilter(false); + props.currentAlarmsActions.onRefresh(); + } else + props.currentAlarmsActions.onRefresh(); + } + }); + } + return ( + + ); +}); + +const FaultApplicationAlarmStatusRouteAdapter = connect(mapProps, mapDispatch)((props: RouteComponentProps<{ severity?: string }> & Connect) => { + if (currentSeverity !== props.match.params.severity) { + currentSeverity = props.match.params.severity || undefined; + window.setTimeout(() => { + if (currentSeverity) { + props.setCurrentPanel('CurrentAlarms'); + props.currentAlarmsActions.onFilterChanged('severity', currentSeverity); + if (!props.currentAlarmsProperties.showFilter) { + props.currentAlarmsActions.onToggleFilter(false); + props.currentAlarmsActions.onRefresh(); + } else + props.currentAlarmsActions.onRefresh(); + } + }); + } + return ( + + ); +}); + +const App = withRouter((props: RouteComponentProps) => ( + + + + + +)); + +export function register() { + const applicationApi = applicationManager.registerApplication({ + name: 'fault', + icon: appIcon, + rootComponent: App, + rootActionHandler: faultAppRootHandler, + statusBarElement: FaultStatus, + dashbaordElement: DashboardHome, + menuEntry: 'Fault', + }); + + let counter = 0; + // subscribe to the websocket notifications + subscribe('problem-notification', (fault => { + const store = applicationApi && applicationApi.applicationStore; + if (fault && store) { + + store.dispatch(new AddFaultNotificationAction({ + id: String(counter++), + nodeName: fault['node-id'], + counter: +fault.data.counter, + objectId: fault.data['object-id-ref'], + problem: fault.data.problem, + severity: fault.data.severity || '', + timeStamp: fault.data['time-stamp'], + })); + } + })); + + applicationApi.applicationStoreInitialized.then(store => { + store.dispatch(currentAlarmsReloadAction); + }); + + applicationApi.applicationStoreInitialized.then(store => { + store.dispatch(refreshFaultStatusAsyncAction); + }); + + applicationApi.logoutEvent.addHandler(()=>{ + + applicationApi.applicationStoreInitialized.then(store => { + store.dispatch(new SetFaultStatusAction(0, 0, 0, 0, false, 0, 0, 0, 0, 0, 0, 0, 0, false)); + clearInterval(refreshInterval!); + }); + }); + + function startRefreshInterval() { + const refreshFaultStatus = window.setInterval(() => { + applicationApi.applicationStoreInitialized.then(store => { + + store.dispatch(refreshFaultStatusAsyncAction); + }); + }, 15000); + + return refreshFaultStatus; + } + + applicationApi.loginEvent.addHandler(()=>{ + if (refreshInterval) { + clearInterval(refreshInterval); + } + refreshInterval = startRefreshInterval() as any; + }); + + applicationApi.logoutEvent.addHandler(()=>{ + refreshInterval && window.clearInterval(refreshInterval); + refreshInterval = null; + }); +} diff --git a/sdnr/wt-odlux/odlux/apps/faultApp/src/services/faultStatusService.ts b/sdnr/wt-odlux/odlux/apps/faultApp/src/services/faultStatusService.ts new file mode 100644 index 000000000..0c7a215f8 --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/faultApp/src/services/faultStatusService.ts @@ -0,0 +1,69 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * 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. + * ============LICENSE_END========================================================================== + */ +import { Result } from '../../../../framework/src/models/elasticSearch'; +import { requestRest } from '../../../../framework/src/services/restService'; + +import { Faults, FaultType } from '../models/fault'; + + +export const getFaultStateFromDatabase = async (): Promise => { + const path = 'rests/operations/data-provider:read-status'; + const result = await requestRest>(path, { method: 'POST' }); + + let faultType: FaultType = { + Critical: 0, + Major: 0, + Minor: 0, + Warning: 0, + Connected: 0, + Connecting: 0, + Disconnected: 0, + Mounted: 0, + UnableToConnect: 0, + Undefined: 0, + Unmounted: 0, + total: 0, + }; + let faults: Faults[] | null = null; + + if (result && result['data-provider:output'] && result['data-provider:output'].data) { + faults = result['data-provider:output'].data; + faultType = { + Critical: faults[0].faults.criticals, + Major: faults[0].faults.majors, + Minor: faults[0].faults.minors, + Warning: faults[0].faults.warnings, + Connected: faults[0]['network-element-connections'].Connected, + Connecting: faults[0]['network-element-connections'].Connecting, + Disconnected: faults[0]['network-element-connections'].Disconnected, + Mounted: faults[0]['network-element-connections'].Mounted, + UnableToConnect: faults[0]['network-element-connections'].UnableToConnect, + Undefined: faults[0]['network-element-connections'].Undefined, + Unmounted: faults[0]['network-element-connections'].Unmounted, + total: faults[0]['network-element-connections'].total, + }; + } + + return faultType; +}; + +export const clearStuckAlarms = async (nodeNames: string[]) => { + const path = 'rests/operations/devicemanager:clear-current-fault-by-nodename'; + const result = await requestRest(path, { method: 'Post', body: JSON.stringify({ input: { nodenames: nodeNames } }) }); + return result; +}; \ No newline at end of file diff --git a/sdnr/wt-odlux/odlux/apps/faultApp/src/views/faultApplication.tsx b/sdnr/wt-odlux/odlux/apps/faultApp/src/views/faultApplication.tsx new file mode 100644 index 000000000..b9f115ed7 --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/faultApp/src/views/faultApplication.tsx @@ -0,0 +1,247 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * 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. + * ============LICENSE_END========================================================================== + */ +import React from 'react'; +import { RouteComponentProps, withRouter } from 'react-router-dom'; + +import Refresh from '@mui/icons-material/Refresh'; +import Sync from '@mui/icons-material/Sync'; +import { AppBar, Tab, Tabs } from '@mui/material'; + +import { ColumnType, MaterialTable, MaterialTableCtorType } from '../../../../framework/src/components/material-table'; +import { connect, Connect, IDispatcher } from '../../../../framework/src/flux/connect'; +import { IApplicationStoreState } from '../../../../framework/src/store/applicationStore'; + +import { setPanelAction } from '../actions/panelChangeActions'; +import ClearStuckAlarmsDialog, { ClearStuckAlarmsDialogMode } from '../components/clearStuckAlarmsDialog'; +import RefreshAlarmLogDialog, { RefreshAlarmLogDialogMode } from '../components/refreshAlarmLogDialog'; +import RefreshCurrentAlarmsDialog, { RefreshCurrentAlarmsDialogMode } from '../components/refreshCurrentAlarmsDialog'; +import { alarmLogEntriesReloadAction, createAlarmLogEntriesActions, createAlarmLogEntriesProperties } from '../handlers/alarmLogEntriesHandler'; +import { createCurrentAlarmsActions, createCurrentAlarmsProperties, currentAlarmsReloadAction } from '../handlers/currentAlarmsHandler'; +import { Fault, FaultAlarmNotification } from '../models/fault'; +import { PanelId } from '../models/panelId'; + +const mapProps = (state: IApplicationStoreState) => ({ + panelId: state.fault.currentOpenPanel, + currentAlarmsProperties: createCurrentAlarmsProperties(state), + faultNotifications: state.fault.faultNotifications, + alarmLogEntriesProperties: createAlarmLogEntriesProperties(state), +}); + +const mapDisp = (dispatcher: IDispatcher) => ({ + currentAlarmsActions: createCurrentAlarmsActions(dispatcher.dispatch), + alarmLogEntriesActions: createAlarmLogEntriesActions(dispatcher.dispatch), + reloadCurrentAlarms: () => dispatcher.dispatch(currentAlarmsReloadAction), + reloadAlarmLogEntries: () => dispatcher.dispatch(alarmLogEntriesReloadAction), + switchActivePanel: (panelId: PanelId) => { + dispatcher.dispatch(setPanelAction(panelId)); + }, +}); + +type FaultApplicationComponentProps = RouteComponentProps & Connect; + +type FaultApplicationState = { + clearAlarmDialogMode: ClearStuckAlarmsDialogMode; + stuckAlarms: string[]; + refreshAlarmLogEditorMode: RefreshAlarmLogDialogMode; + refreshCurrentAlarmsEditorMode: RefreshCurrentAlarmsDialogMode; +}; + + +const FaultTable = MaterialTable as MaterialTableCtorType; +const FaultAlarmNotificationTable = MaterialTable as MaterialTableCtorType; + +let currentAlarmsInitalSorted = false; +let alarmLogInitialSorted = false; + +class FaultApplicationComponent extends React.Component { + constructor(props: FaultApplicationComponentProps) { + super(props); + this.state = { + clearAlarmDialogMode: ClearStuckAlarmsDialogMode.None, + stuckAlarms: [], + refreshAlarmLogEditorMode: RefreshAlarmLogDialogMode.None, + refreshCurrentAlarmsEditorMode: RefreshCurrentAlarmsDialogMode.None, + }; + } + + onDialogClose = () => { + this.setState({ clearAlarmDialogMode: ClearStuckAlarmsDialogMode.None, stuckAlarms: [] }); + }; + + onDialogOpen = () => { + const stuckAlarms = [...new Set(this.props.currentAlarmsProperties.rows.map(item => item.nodeId))]; + this.setState({ clearAlarmDialogMode: ClearStuckAlarmsDialogMode.Show, stuckAlarms: stuckAlarms }); + }; + + private onHandleTabChange = (event: React.SyntheticEvent, newValue: PanelId) => { + this.onToggleTabs(newValue); + }; + + private onToggleTabs = (panelId: PanelId) => { + const nextActivePanel = panelId; + this.props.switchActivePanel(nextActivePanel); + switch (nextActivePanel) { + case 'CurrentAlarms': + if (!currentAlarmsInitalSorted) { + currentAlarmsInitalSorted = true; + this.props.currentAlarmsActions.onHandleExplicitRequestSort('timestamp', 'desc'); + } else { + this.props.reloadCurrentAlarms(); + } + break; + case 'AlarmLog': + if (!alarmLogInitialSorted) { + alarmLogInitialSorted = true; + this.props.alarmLogEntriesActions.onHandleExplicitRequestSort('timestamp', 'desc'); + } else { + this.props.reloadAlarmLogEntries(); + } + break; + case 'AlarmNotifications': + case null: + default: + // nothing to do + break; + } + }; + + + + render(): JSX.Element { + + const clearAlarmsAction = { + icon: Sync, tooltip: 'Clear stuck alarms', ariaLabel:'clear-stuck-alarms', onClick: this.onDialogOpen, + }; + + const refreshCurrentAlarmsAction = { + icon: Refresh, tooltip: 'Refresh Current Alarms List', ariaLabel:'refresh', onClick: () => { + this.setState({ + refreshCurrentAlarmsEditorMode: RefreshCurrentAlarmsDialogMode.RefreshCurrentAlarmsTable, + }); + }, + }; + + const refreshAlarmLogAction = { + icon: Refresh, tooltip: 'Refresh Alarm log table', ariaLabel:'refresh', onClick: () => { + this.setState({ + refreshAlarmLogEditorMode: RefreshAlarmLogDialogMode.RefreshAlarmLogTable, + }); + }, + }; + + const areFaultsAvailable = this.props.currentAlarmsProperties.rows && this.props.currentAlarmsProperties.rows.length > 0; + const customActions = areFaultsAvailable ? [clearAlarmsAction, refreshCurrentAlarmsAction] : [refreshCurrentAlarmsAction]; + + const { panelId: activePanelId } = this.props; + + return ( + <> + + + + + + + + { + activePanelId === 'CurrentAlarms' && + <> + + + + } + {activePanelId === 'AlarmNotifications' && + + + } + + {activePanelId === 'AlarmLog' && + <> + + + + + } + { + this.state.clearAlarmDialogMode !== ClearStuckAlarmsDialogMode.None && + } + + ); + } + + public componentDidMount() { + if (this.props.panelId === null) { //set default tab if none is set + this.onToggleTabs('CurrentAlarms'); + } else { + this.onToggleTabs(this.props.panelId); + } + } + + // private renderIcon = (props: { rowData: Fault | FaultAlarmNotification }) => { + // return ( + // + // ); + // }; + + private onCloseRefreshAlarmLogDialog = () => { + this.setState({ + refreshAlarmLogEditorMode: RefreshAlarmLogDialogMode.None, + }); + }; + + private onCloseRefreshCurrentAlarmsDialog = () => { + this.setState({ + refreshCurrentAlarmsEditorMode: RefreshCurrentAlarmsDialogMode.None, + }); + }; +} + +export const FaultApplication = withRouter(connect(mapProps, mapDisp)(FaultApplicationComponent)); +export default FaultApplication; diff --git a/sdnr/wt-odlux/odlux/apps/faultApp/tsconfig.json b/sdnr/wt-odlux/odlux/apps/faultApp/tsconfig.json new file mode 100644 index 000000000..ca65092e0 --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/faultApp/tsconfig.json @@ -0,0 +1,37 @@ +{ + "compilerOptions": { + "baseUrl": "./src", + "outDir": "./dist", + "sourceMap": true, + "forceConsistentCasingInFileNames": true, + "allowSyntheticDefaultImports": true, + "allowUnreachableCode": false, + "allowUnusedLabels": false, + "noFallthroughCasesInSwitch": true, + "noImplicitAny": true, + "noImplicitReturns": true, + "noImplicitThis": true, + "strictNullChecks": true, + "pretty": true, + "newLine": "LF", + "module": "es2015", + "target": "es2016", + "moduleResolution": "node", + "experimentalDecorators": true, + "jsx": "preserve", + "lib": [ + "dom", + "es2015", + "es2016" + ], + "types": [ + "prop-types", + "react", + "react-dom" + ] + }, + "exclude": [ + "dist", + "node_modules" + ] +} diff --git a/sdnr/wt-odlux/odlux/apps/faultApp/webpack.config.js b/sdnr/wt-odlux/odlux/apps/faultApp/webpack.config.js new file mode 100644 index 000000000..bc26de1cb --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/faultApp/webpack.config.js @@ -0,0 +1,166 @@ +/** + * Webpack 4 configuration file + * see https://webpack.js.org/configuration/ + * see https://webpack.js.org/configuration/dev-server/ + */ + +"use strict"; + +const path = require("path"); +const webpack = require("webpack"); +const CopyWebpackPlugin = require("copy-webpack-plugin"); +const TerserPlugin = require('terser-webpack-plugin'); + +// const __dirname = (path => path.replace(/^([a-z]\:)/, c => c.toUpperCase()))(process.__dirname()); + +module.exports = (env) => { + const distPath = path.resolve(__dirname, env === "release" ? "." : "../..", "dist"); + const frameworkPath = path.resolve(__dirname, env === "release" ? "../../framework" : "../..", "dist"); + return [{ + name: "App", + + mode: "none", //disable default behavior + + target: "web", + + context: path.resolve(__dirname, "src"), + + entry: { + faultApp: ["./pluginFault.tsx"] + }, + + devtool: env === "release" ? false : "source-map", + + resolve: { + extensions: [".ts", ".tsx", ".js", ".jsx"] + }, + + output: { + path: distPath, + filename: "[name].js", + library: "[name]", + libraryTarget: "umd2", + chunkFilename: "[name].js" + }, + module: { + rules: [{ + test: /\.tsx?$/, + exclude: /node_modules/, + use: [{ + loader: "babel-loader" + }, { + loader: "ts-loader" + }] + }, { + test: /\.jsx?$/, + exclude: /node_modules/, + use: [{ + loader: "babel-loader" + }] + },{ + //don't minify images + test: /\.(png|gif|jpg|svg)$/, + use: [{ + loader: 'url-loader', + options: { + limit: 10, + name: './images/[name].[ext]' + } + }] + }] + }, + optimization: { + noEmitOnErrors: true, + namedModules: env !== "release", + minimize: env === "release", + minimizer: env !== "release" ? [] : [new TerserPlugin({ + terserOptions: { + warnings: false, // false, true, "verbose" + compress: { + drop_console: true, + drop_debugger: true, + } + } + })], + }, + plugins: [ + new webpack.DllReferencePlugin({ + context: path.resolve(__dirname, "../../framework/src"), + manifest: require(path.resolve(frameworkPath, "vendor-manifest.json")), + sourceType: "umd2" + }), + new webpack.DllReferencePlugin({ + context: path.resolve(__dirname, "../../framework/src"), + manifest: require(path.resolve(frameworkPath, "app-manifest.json")), + sourceType: "umd2" + }), + ...(env === "release" ? [ + new webpack.DefinePlugin({ + "process.env": { + NODE_ENV: "'production'", + VERSION: JSON.stringify(require("./package.json").version) + } + }) + ] : [ + new webpack.DefinePlugin({ + "process.env": { + NODE_ENV: "'development'", + VERSION: JSON.stringify(require("./package.json").version) + } + }), + new CopyWebpackPlugin([{ + from: 'index.html', + to: distPath + }]), + ]) + ], + + devServer: { + public: "http://localhost:3100", + contentBase: frameworkPath, + + compress: true, + headers: { + "Access-Control-Allow-Origin": "*" + }, + host: "0.0.0.0", + port: 3100, + disableHostCheck: true, + historyApiFallback: true, + inline: true, + hot: false, + quiet: false, + stats: { + colors: true + }, + proxy: { + "/oauth2/": { + target: "http://sdnc-web:8080", + secure: false + }, + "/database/": { + target: "http://sdnc-web:8080", + secure: false + }, + "/restconf/": { + target: "http://sdnc-web:8080", + secure: false + }, + "/rests/": { + target: "http://sdnc-web:8080", + secure: false + }, + "/help/": { + target: "http://sdnc-web:8080", + secure: false + }, + "/websocket": { + target: "http://sdnc-web:8080", + ws: true, + changeOrigin: true, + secure: false + } + } + } + }]; +} diff --git a/sdnr/wt-odlux/odlux/apps/helpApp/.babelrc b/sdnr/wt-odlux/odlux/apps/helpApp/.babelrc new file mode 100644 index 000000000..3d8cd1260 --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/helpApp/.babelrc @@ -0,0 +1,17 @@ +{ + "presets": [ + ["@babel/preset-react"], + ["@babel/preset-env", { + "targets": { + "chrome": "66" + }, + "spec": true, + "loose": false, + "modules": false, + "debug": false, + "useBuiltIns": "usage", + "forceAllTransforms": true + }] + ], + "plugins": [] +} diff --git a/sdnr/wt-odlux/odlux/apps/helpApp/package.json b/sdnr/wt-odlux/odlux/apps/helpApp/package.json new file mode 100644 index 000000000..5bcfdce7b --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/helpApp/package.json @@ -0,0 +1,48 @@ +{ + "name": "@odlux/help-app", + "version": "0.1.0", + "description": "A react based modular UI providing the help functionaliy.", + "main": "index.js", + "scripts": { + "start": "webpack-dev-server --env debug", + "build": "webpack --env release --config webpack.config.js", + "build:dev": "webpack --env debug --config webpack.config.js" + }, + "repository": { + "type": "git", + "url": "https://git.mfico.de/highstreet-technologies/odlux.git" + }, + "keywords": [ + "reactjs", + "redux", + "ui", + "framework" + ], + "author": "Matthias Fischer", + "license": "Apache-2.0", + "dependencies": { + "@emotion/react": "^11.7.0", + "@emotion/styled": "^11.6.0", + "@mui/icons-material": "^5.2.0", + "@mui/material": "^5.2.2", + "@mui/styles": "^5.2.2", + "@odlux/framework": "*", + "@types/highlight.js": "9.12.3", + "@types/marked": "0.6.0", + "github-markdown-css": "2.10.0", + "highlight.js": "9.13.1", + "marked": "0.6.0" + }, + "peerDependencies": { + "@types/classnames": "2.2.6", + "@types/flux": "3.1.8", + "@types/jquery": "3.3.10", + "@types/react": "17.0.37", + "@types/react-dom": "17.0.11", + "@types/react-router-dom": "5.1.7", + "jquery": "3.3.1", + "react": "17.0.2", + "react-dom": "17.0.2", + "react-router-dom": "5.2.0" + } +} diff --git a/sdnr/wt-odlux/odlux/apps/helpApp/pom.xml b/sdnr/wt-odlux/odlux/apps/helpApp/pom.xml new file mode 100644 index 000000000..aad2e968f --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/helpApp/pom.xml @@ -0,0 +1,109 @@ + + + + + 4.0.0 + + org.onap.ccsdk.features.sdnr.odlux + sdnr-odlux-app-helpApp + 1.7.0-SNAPSHOT + jar + + SDNR ODLUX :: ${project.artifactId} + + + Apache License, Version 2.0 + http://www.apache.org/licenses/LICENSE-2.0 + + + + + true + + + + + + dist + odlux + + + + + maven-clean-plugin + + + + dist + false + + + node + false + + + node_modules + false + + + ../node_modules + false + + + + bin + false + + + + + + de.jacks-it-lab + frontend-maven-plugin + 1.7.2 + + + install node and yarn + + install-node-and-yarn + + + initialize + + v16.17.0 + v1.22.19 + + + + yarn build + + yarn + + + run build + + + + + + + diff --git a/sdnr/wt-odlux/odlux/apps/helpApp/src/actions/helpActions.ts b/sdnr/wt-odlux/odlux/apps/helpApp/src/actions/helpActions.ts new file mode 100644 index 000000000..3cebfd61d --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/helpApp/src/actions/helpActions.ts @@ -0,0 +1,78 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * 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. + * ============LICENSE_END========================================================================== + */ +import { Action } from '../../../../framework/src/flux/action'; +import { Dispatch } from '../../../../framework/src/flux/store'; + +import { TocTreeNode } from '../models/tocNode'; +import helpService from '../services/helpService'; + +export class LoadTocAction extends Action { + constructor() { + super(); + + } +} + +export class TocLoadedAction extends Action { + constructor(public toc?: TocTreeNode[], error?: string) { + super(); + + } +} + +export const requestTocAsyncAction = async (dispatch: Dispatch) => { + dispatch(new LoadTocAction); + try { + const toc = await helpService.getTableOfContents(); + if (toc) { + dispatch(new TocLoadedAction(toc)); + } else { + dispatch(new TocLoadedAction(undefined, "Could not load TOC.")); + } + } catch (err) { + dispatch(new TocLoadedAction(undefined, err)); + } +} + +export class LoadDocumentAction extends Action { + constructor() { + super(); + + } +} + +export class DocumentLoadedAction extends Action { + constructor(public document?: string, public documentPath?: string, error?: string) { + super(); + + } +} + +export const requestDocumentAsyncActionCreator = (path: string) => async (dispatch: Dispatch) => { + dispatch(new LoadDocumentAction); + try { + const doc = await helpService.getDocument(path); + if (doc) { + dispatch(new DocumentLoadedAction(doc, path)); + } else { + dispatch(new DocumentLoadedAction(undefined, undefined, "Could not load document.")); + } + } catch (err) { + dispatch(new DocumentLoadedAction(undefined, undefined, err)); + } +} \ No newline at end of file diff --git a/sdnr/wt-odlux/odlux/apps/helpApp/src/assets/icons/helpAppIcon.svg b/sdnr/wt-odlux/odlux/apps/helpApp/src/assets/icons/helpAppIcon.svg new file mode 100644 index 000000000..298eaa162 --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/helpApp/src/assets/icons/helpAppIcon.svg @@ -0,0 +1,27 @@ + + + + + + + + + + + + diff --git a/sdnr/wt-odlux/odlux/apps/helpApp/src/components/helpStatus.tsx b/sdnr/wt-odlux/odlux/apps/helpApp/src/components/helpStatus.tsx new file mode 100644 index 000000000..985b404a7 --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/helpApp/src/components/helpStatus.tsx @@ -0,0 +1,83 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * 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. + * ============LICENSE_END========================================================================== + */ +import React from 'react'; +import { withRouter, RouteComponentProps } from 'react-router'; + +import { Theme } from '@mui/material/styles'; +import { WithStyles } from '@mui/styles'; +import withStyles from '@mui/styles/withStyles'; +import createStyles from '@mui/styles/createStyles'; +import { faExclamationTriangle } from '@fortawesome/free-solid-svg-icons'; // select app icon + +import Typography from '@mui/material/Typography'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { faQuestionCircle } from '@fortawesome/free-solid-svg-icons'; + +import { connect, Connect } from '../../../../framework/src/flux/connect'; +import { IApplicationStoreState } from '../../../../framework/src/store/applicationStore'; + +const styles = (theme: Theme) => createStyles({ + icon: { + marginLeft: 8, + marginRight: 8 + }, + disabled: { + color: theme.palette.grey[400] + }, + link: { + cursor: "pointer", + '&:hover': { + textDecoration: "underline" + } + } +}); + +const mapProps = (state: IApplicationStoreState) => ({ + appId: state.framework.applicationState.appId, + toc: state.help.toc +}); + + +type HelpStatusComponentProps = & RouteComponentProps & WithStyles & Connect; + +class HelpStatusComponent extends React.Component { + render() { + const { classes, history, toc, appId } = this.props; + const rootNode = toc && toc.find(t => t.id === "sdnr"); + const helpNode = appId + ? rootNode && rootNode.nodes && rootNode.nodes.find(n => n.id === appId || n.id === appId + "App") + : rootNode; + return helpNode + ? ( + { event.stopPropagation(); history.push(`/help/${helpNode.uri}`) }} > + + Help + + ) + : ( + + + Help + + ); + }; + +} + +export const HelpStatus = withRouter(withStyles(styles)(connect(mapProps)(HelpStatusComponent) as any) as any); +export default HelpStatus; \ No newline at end of file diff --git a/sdnr/wt-odlux/odlux/apps/helpApp/src/components/markdown.tsx b/sdnr/wt-odlux/odlux/apps/helpApp/src/components/markdown.tsx new file mode 100644 index 000000000..a7137836e --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/helpApp/src/components/markdown.tsx @@ -0,0 +1,77 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * 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. + * ============LICENSE_END========================================================================== + */ +import * as React from 'react'; + +import * as marked from 'marked'; +import * as hljs from 'highlight.js'; + +type MarkdownComponentProps = { + text: string; + className?: string; + markedOptions?: marked.MarkedOptions; + style?: React.CSSProperties +} + +const defaultRenderer = new marked.Renderer(); +defaultRenderer.link = (href, title, text) => ( + `${ text }` +); + + +class MarkdownComponent extends React.Component { + constructor(props: MarkdownComponentProps) { + super(props); + + const markedOptions: marked.MarkedOptions = { + gfm: true, + tables: true, + breaks: false, + pedantic: false, + sanitize: true, + smartLists: true, + smartypants: false, + langPrefix: 'hljs ', + ...(this.props.markedOptions || {}), + highlight: (code, lang) => { + if (!!(lang && hljs.getLanguage(lang))) { + return hljs.highlight(lang, code).value; + } + return code; + } + }; + + marked.setOptions(markedOptions); + } + render() { + const { text, className, style } = this.props; + + + const html = (marked(text || '', { renderer: this.props.markedOptions && this.props.markedOptions.renderer || defaultRenderer })); + + return ( +
+ ); + } +} + +export const Markdown = MarkdownComponent; + diff --git a/sdnr/wt-odlux/odlux/apps/helpApp/src/components/tocEntry.tsx b/sdnr/wt-odlux/odlux/apps/helpApp/src/components/tocEntry.tsx new file mode 100644 index 000000000..295b3caf9 --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/helpApp/src/components/tocEntry.tsx @@ -0,0 +1,85 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * 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. + * ============LICENSE_END========================================================================== + */ + +import * as React from "react" +import { TocTreeNode } from "../models/tocNode" +import { Typography, Link, Theme } from "@mui/material"; + +import makeStyles from '@mui/styles/makeStyles'; +import createStyles from '@mui/styles/createStyles'; + +const useStyles = makeStyles((theme: Theme) => + createStyles({ + link: { + color: "blue", + }, + sublink: { + margin: theme.spacing(1), + color: "blue", + }, + container: { + display: "flex", + flexDirection: "row", + flexWrap: "wrap", + } + }), +); + +type tocEntryProps = { + label: string, + overviewUri: string, + nodes?: TocTreeNode[], + loadDocument(uri: string): any +} + +const TocEntry: React.FunctionComponent = (props) => { + const classes = useStyles(); + const areNodesEmpty = !props.nodes || props.nodes.length === 0 + + const navigate = (event: React.SyntheticEvent, uri: string) => { + event.preventDefault(); + event.stopPropagation(); + props.loadDocument(uri); + } + + return (
+ { + areNodesEmpty ? + navigate(event, props.overviewUri)} className={classes.link}> {props.label} + : + <> + + {props.label} + +
+ + navigate(event, props.overviewUri)} className={classes.sublink}>Overview + + {props.nodes !== undefined && props.nodes.map((item, index) => + + navigate(event, item.uri)} className={classes.sublink}>{item.label} + + )} +
+ + } +
) +} + + +export default TocEntry; \ No newline at end of file diff --git a/sdnr/wt-odlux/odlux/apps/helpApp/src/handlers/helpAppRootHandler.ts b/sdnr/wt-odlux/odlux/apps/helpApp/src/handlers/helpAppRootHandler.ts new file mode 100644 index 000000000..29e07959a --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/helpApp/src/handlers/helpAppRootHandler.ts @@ -0,0 +1,76 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * 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. + * ============LICENSE_END========================================================================== + */ +// main state handler + +import { IApplicationStoreState } from '../../../../framework/src/store/applicationStore'; +import { IActionHandler } from '../../../../framework/src/flux/action'; + +import { LoadTocAction, TocLoadedAction, LoadDocumentAction, DocumentLoadedAction } from '../actions/helpActions'; +import { TocTreeNode } from '../models/tocNode'; + +export interface IHelpAppStoreState { + busy: boolean; + toc: TocTreeNode[] | undefined; + content: string | undefined; + currentPath: string | undefined; +} + +declare module '../../../../framework/src/store/applicationStore' { + interface IApplicationStoreState { + help: IHelpAppStoreState + } +} + +const helpAppStoreStatcurrentPatheInit: IHelpAppStoreState = { + busy: false, + toc: undefined, + content: undefined, + currentPath: undefined +}; + +export const helpAppRootHandler: IActionHandler = (state = helpAppStoreStatcurrentPatheInit, action) => { + if (action instanceof LoadTocAction) { + state = { + ...state, + busy: true + }; + } else if (action instanceof TocLoadedAction) { + state = { + ...state, + busy: false, + toc: action.toc + }; + } else if (action instanceof LoadDocumentAction) { + state = { + ...state, + busy: true + }; + } else if (action instanceof DocumentLoadedAction) { + state = { + ...state, + busy: false, + content: action.document, + currentPath: action.documentPath + }; + } + + return state; +} + + +export default helpAppRootHandler; diff --git a/sdnr/wt-odlux/odlux/apps/helpApp/src/index.html b/sdnr/wt-odlux/odlux/apps/helpApp/src/index.html new file mode 100644 index 000000000..2344708c8 --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/helpApp/src/index.html @@ -0,0 +1,29 @@ + + + + + + + + + Minimal App + + + +
+ + + + + + \ No newline at end of file diff --git a/sdnr/wt-odlux/odlux/apps/helpApp/src/models/tocNode.ts b/sdnr/wt-odlux/odlux/apps/helpApp/src/models/tocNode.ts new file mode 100644 index 000000000..dbefeec77 --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/helpApp/src/models/tocNode.ts @@ -0,0 +1,42 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * 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. + * ============LICENSE_END========================================================================== + */ +export type VersionInfo = { + label: string, + path: string, + date: string +} + +export type TocNode = { + label: string; + versions: { + [versionKey: string]: VersionInfo, + current: VersionInfo + }; + nodes?: TocNodeCollection; +} + +export type TocNodeCollection = { [tocNodeKey: string]: TocNode }; + + +export type TocTreeNode = { + id: string; + label: string; + uri: string; + nodes?: TocTreeNode[]; + disabled?: boolean; +} diff --git a/sdnr/wt-odlux/odlux/apps/helpApp/src/plugin.tsx b/sdnr/wt-odlux/odlux/apps/helpApp/src/plugin.tsx new file mode 100644 index 000000000..5d860e530 --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/helpApp/src/plugin.tsx @@ -0,0 +1,91 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * 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. + * ============LICENSE_END========================================================================== + */ +// app configuration and main entry point for the app + +import React from "react"; +import { withRouter, RouteComponentProps, Route, Switch, Redirect } from 'react-router-dom'; + +import applicationManager from '../../../framework/src/services/applicationManager'; +import { IApplicationStoreState } from "../../../framework/src/store/applicationStore"; +import { connect, Connect, IDispatcher } from '../../../framework/src/flux/connect'; + +import { requestTocAsyncAction, requestDocumentAsyncActionCreator } from "./actions/helpActions"; +import { helpAppRootHandler } from './handlers/helpAppRootHandler'; + +import { HelpApplication } from './views/helpApplication'; +import { HelpStatus } from "./components/helpStatus"; + +import '!style-loader!css-loader!highlight.js/styles/default.css'; +import HelpTocApp from "./views/helpTocApp"; + +const appIcon = require('./assets/icons/helpAppIcon.svg'); // select app icon + +const mapProps = (state: IApplicationStoreState) => ({ + +}); + +const mapDispatch = (dispatcher: IDispatcher) => ({ + requestDocument: (path: string) => { + dispatcher.dispatch(requestDocumentAsyncActionCreator(path)); + } +}); + +let currentHelpPath: string | undefined = undefined; + +const HelpApplicationRouteAdapter = connect(mapProps, mapDispatch)((props: RouteComponentProps<{ '0'?: string }> & Connect) => { + + if (currentHelpPath !== props.match.params["0"]) { + // route parameter has changed + currentHelpPath = props.match.params["0"] || undefined; + // Hint: This timeout is need, since it is not recommended to change the state while rendering is in progress ! + window.setTimeout(() => { + if (currentHelpPath) { + props.requestDocument(currentHelpPath); + } + }); + } + + return ( + + ) +}); + +const App = withRouter((props: RouteComponentProps) => ( + + + + + +)); + +export async function register() { + const applicationApi = applicationManager.registerApplication({ + name: "help", + icon: appIcon, + rootComponent: App, + rootActionHandler: helpAppRootHandler, + statusBarElement: HelpStatus, + menuEntry: "Help", + //subMenuEntry: SubMenuEntry + }); + + // start the initial toc request after the application store is initialized + const store = await applicationApi.applicationStoreInitialized; + store.dispatch(requestTocAsyncAction); + +} \ No newline at end of file diff --git a/sdnr/wt-odlux/odlux/apps/helpApp/src/services/helpService.ts b/sdnr/wt-odlux/odlux/apps/helpApp/src/services/helpService.ts new file mode 100644 index 000000000..728f243a0 --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/helpApp/src/services/helpService.ts @@ -0,0 +1,65 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * 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. + * ============LICENSE_END========================================================================== + */ +import { requestRest } from '../../../../framework/src/services/restService'; +import { TocTreeNode, TocNodeCollection } from '../models/tocNode'; + +class HelpService { + + private tocNodeCollection: TocTreeNode[] | null = null; + private documents: { [path: string]: string | null } = {}; + + public async getDocument(path: string): Promise { + // check if the result is allready in the cache + if (this.documents[path]) return Promise.resolve(this.documents[path]); + + // request the document + const result = await requestRest(`/help/${path}`.replace(/\/{2,}/i, '/')); + if (result) { + this.documents[path] = result; + } + return this.documents[path] || null; + } + + public async getTableOfContents(): Promise { + // check if the result is allready in the cache + if (this.tocNodeCollection) return Promise.resolve(this.tocNodeCollection); + + // request the table of contents + const result = await requestRest('/help/?meta', undefined, false); + if (result !== null) { + const mapNodesCollection = (col: TocNodeCollection): TocTreeNode[] => { + return Object.keys(col).reduce((acc, key) => { + const current = col[key]; + acc.push({ + id: key, + label: current.label, + uri: current.versions.current.path, + nodes: current.nodes && mapNodesCollection(current.nodes) || undefined + }); + return acc; + }, []); + } + + this.tocNodeCollection = result && mapNodesCollection(result) || null; + } + return this.tocNodeCollection || null; + } +} + +export const helpService = new HelpService(); +export default helpService; \ No newline at end of file diff --git a/sdnr/wt-odlux/odlux/apps/helpApp/src/utilities/path.ts b/sdnr/wt-odlux/odlux/apps/helpApp/src/utilities/path.ts new file mode 100644 index 000000000..412bdfb1e --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/helpApp/src/utilities/path.ts @@ -0,0 +1,79 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * 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. + * ============LICENSE_END========================================================================== + */ +export const resolvePath = (...paths: string[]): string => { + function resolve(pathA: string, pathB: string) { + // ‘a’ => ['a'] + // 'a/b' => ['a', 'b'] + // '/a/b' => ['', 'a', 'b'] + // '/a/b/' => ['', 'a', 'b', ''] + const pathBParts = pathB.split('/'); + if (pathBParts[0] === '') { + return pathBParts.join('/'); + } + const pathAParts = pathA.split('/'); + const aLastIndex = pathAParts.length - 1; + if (pathAParts[aLastIndex] !== '') { + pathAParts[aLastIndex] = ''; + } + + let part: string; + let i = 0; + while (typeof (part = pathBParts[i]) === 'string') { + switch (part) { + case '..': + pathAParts.pop(); + pathAParts.pop(); + pathAParts.push(''); + break; + case '.': + pathAParts.pop(); + pathAParts.push(''); + break; + default: + pathAParts.pop(); + pathAParts.push(part); + pathAParts.push(''); + break; + } + i++; + } + if (pathBParts[pathBParts.length - 1] !== '') pathAParts.pop(); + return pathAParts.join('/'); + } + + let i = 0; + let path: string; + let r = location.pathname; + + const urlRegex = /^https?\:\/\/([^\/?#]+)(?:[\/?#]|$)/i; + const multiSlashReg = /\/\/+/g; + + while (typeof (path = paths[i]) === 'string') { + debugger; + const matches = path && path.match(urlRegex); + if (matches || !i) { + r = path; + } else { + path = path.replace(multiSlashReg, '/'); + r = resolve(r, path); + } + i++; + } + + return r; +}; \ No newline at end of file diff --git a/sdnr/wt-odlux/odlux/apps/helpApp/src/views/helpApplication.tsx b/sdnr/wt-odlux/odlux/apps/helpApp/src/views/helpApplication.tsx new file mode 100644 index 000000000..5940517b4 --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/helpApp/src/views/helpApplication.tsx @@ -0,0 +1,84 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * 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. + * ============LICENSE_END========================================================================== + */ +import React from 'react'; +import * as marked from 'marked'; + +import { resolvePath } from '../utilities/path'; + +import { IApplicationStoreState } from '../../../../framework/src/store/applicationStore'; +import { connect, Connect } from '../../../../framework/src/flux/connect'; + +import { Markdown } from "../components/markdown"; + +import '!style-loader!css-loader!github-markdown-css/github-markdown.css' + +const mapProps = (state: IApplicationStoreState) => ({ + content: state.help.content, + currentPath: state.help.currentPath +}); + +const containerStyle = { + overflow: "auto", + height: "100%", + width: "100%" +}; + +const styles = { + maxWidth: "960px", + margin: "1.5em auto", + +}; + +type HelpApplicationComponentProps = Connect; + +class HelpApplicationComponent extends React.Component { + + /** + * Initializes a new instance. + */ + constructor(props: HelpApplicationComponentProps) { + super(props); + + this.renderer = new marked.Renderer(); + + this.renderer.link = (href: string, title: string, text: string) => { + // check if href is rel or abs + const absUrlMatch = href.trim().match(/^https?:\/\//i); + return `${text}` + }; + + this.renderer.image = (href: string, title: string) => { + return `${title}` + }; + + } + + render(): JSX.Element { + return this.props.content ? ( +
+ +
+ ) : (

Loading ...

) + } + + private renderer: marked.Renderer; +} + +export const HelpApplication = connect(mapProps)(HelpApplicationComponent); +export default HelpApplication; \ No newline at end of file diff --git a/sdnr/wt-odlux/odlux/apps/helpApp/src/views/helpTocApp.tsx b/sdnr/wt-odlux/odlux/apps/helpApp/src/views/helpTocApp.tsx new file mode 100644 index 000000000..6a6a89123 --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/helpApp/src/views/helpTocApp.tsx @@ -0,0 +1,55 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * 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. + * ============LICENSE_END========================================================================== + */ + +import React from 'react' +import {connect, Connect, IDispatcher } from "../../../../framework/src/flux/connect"; + +import { NavigateToApplication } from "../../../../framework/src/actions/navigationActions"; +import { FunctionComponent } from "react"; +import { IApplicationStoreState } from "../../../../framework/src/store/applicationStore"; +import TocEntry from "../components/tocEntry"; +import { Typography } from "@mui/material"; + +const mapProps = (state: IApplicationStoreState) => ({ + helpToc: state.help.toc, +}) + +const mapDisp = (dispatcher: IDispatcher) => ({ + requestDocument: (uri: string) => dispatcher.dispatch(new NavigateToApplication("help", uri)) +}); + +const HelpTocComponent: FunctionComponent> = (props) => { + + return ( +
+ + Help & FAQ + + + On our Help site, you can find general information about SDN-R, detailed information about our applications, frequently asked questions and a list of used abbreviations. + + { + props.helpToc && props.helpToc.map((item, index) => ) + } +
+ ) +} + +export const HelpTocApp = connect(mapProps, mapDisp)(HelpTocComponent) + +export default HelpTocApp; \ No newline at end of file diff --git a/sdnr/wt-odlux/odlux/apps/helpApp/tsconfig.json b/sdnr/wt-odlux/odlux/apps/helpApp/tsconfig.json new file mode 100644 index 000000000..ca65092e0 --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/helpApp/tsconfig.json @@ -0,0 +1,37 @@ +{ + "compilerOptions": { + "baseUrl": "./src", + "outDir": "./dist", + "sourceMap": true, + "forceConsistentCasingInFileNames": true, + "allowSyntheticDefaultImports": true, + "allowUnreachableCode": false, + "allowUnusedLabels": false, + "noFallthroughCasesInSwitch": true, + "noImplicitAny": true, + "noImplicitReturns": true, + "noImplicitThis": true, + "strictNullChecks": true, + "pretty": true, + "newLine": "LF", + "module": "es2015", + "target": "es2016", + "moduleResolution": "node", + "experimentalDecorators": true, + "jsx": "preserve", + "lib": [ + "dom", + "es2015", + "es2016" + ], + "types": [ + "prop-types", + "react", + "react-dom" + ] + }, + "exclude": [ + "dist", + "node_modules" + ] +} diff --git a/sdnr/wt-odlux/odlux/apps/helpApp/webpack.config.js b/sdnr/wt-odlux/odlux/apps/helpApp/webpack.config.js new file mode 100644 index 000000000..a48f7b976 --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/helpApp/webpack.config.js @@ -0,0 +1,189 @@ +/** + * Webpack 4 configuration file + * see https://webpack.js.org/configuration/ + * see https://webpack.js.org/configuration/dev-server/ + */ + +"use strict"; + +const path = require("path"); +const webpack = require("webpack"); +const autoprefixer = require('autoprefixer'); +const CopyWebpackPlugin = require("copy-webpack-plugin"); +const TerserPlugin = require('terser-webpack-plugin'); + +// const __dirname = (path => path.replace(/^([a-z]\:)/, c => c.toUpperCase()))(process.__dirname()); + +module.exports = (env) => { + const distPath = path.resolve(__dirname, env === "release" ? "." : "../..", "dist"); + const frameworkPath = path.resolve(__dirname, env === "release" ? "../../framework" : "../..", "dist"); + return [{ + name: "App", + + mode: "none", //disable default behavior + + target: "web", + + context: path.resolve(__dirname, "src"), + + entry: { + helpApp: ["./plugin.tsx"] + }, + + devtool: env === "release" ? false : "source-map", + + resolve: { + extensions: [".ts", ".tsx", ".js", ".jsx"] + }, + + output: { + path: distPath, + filename: "[name].js", + library: "[name]", + libraryTarget: "umd2", + chunkFilename: "[name].js" + }, + module: { + rules: [{ + test: /\.tsx?$/, + exclude: /node_modules/, + use: [{ + loader: "babel-loader" + }, { + loader: "ts-loader" + }] + }, { + test: /\.jsx?$/, + exclude: /node_modules/, + use: [{ + loader: "babel-loader" + }] + }, { + test: /\.css$/, + use: [{ + loader: 'style-loader' + }, { + loader: 'css-loader', + options: { + modules: true, + localIdentName: env !== "release" ? '[name]_[local]_[hash:base64:5]' : '[hash]' + } + }, { + loader: 'postcss-loader', + options: { + plugins: () => [autoprefixer] + } + }] + }, { + //don't minify images + test: /\.(png|gif|jpg|svg)$/, + use: [{ + loader: 'url-loader', + options: { + limit: 10, + name: './images/[name].[ext]' + } + }] + }] + }, + + optimization: { + noEmitOnErrors: true, + namedModules: env !== "release", + minimize: env === "release", + minimizer: env !== "release" ? [] : [new TerserPlugin({ + terserOptions: { + warnings: false, // false, true, "verbose" + compress: { + drop_console: true, + drop_debugger: true, + } + } + })], + }, + + plugins: [ + new webpack.DllReferencePlugin({ + context: path.resolve(__dirname, "../../framework/src"), + manifest: require(path.resolve(frameworkPath, "vendor-manifest.json")), + sourceType: "umd2" + }), + new webpack.DllReferencePlugin({ + context: path.resolve(__dirname, "../../framework/src"), + manifest: require(path.resolve(frameworkPath, "app-manifest.json")), + sourceType: "umd2" + }), + ...(env === "release" ? [ + new webpack.DefinePlugin({ + "process.env": { + NODE_ENV: "'production'", + VERSION: JSON.stringify(require("./package.json").version) + } + }), + ] : [ + new webpack.DefinePlugin({ + "process.env": { + NODE_ENV: "'development'", + VERSION: JSON.stringify(require("./package.json").version) + } + }), + new CopyWebpackPlugin([{ + from: 'index.html', + to: distPath + }]), + ]) + ], + devServer: { + public: "http://localhost:3100", + contentBase: frameworkPath, + + compress: true, + headers: { + "Access-Control-Allow-Origin": "*" + }, + host: "0.0.0.0", + port: 3100, + disableHostCheck: true, + historyApiFallback: true, + inline: true, + hot: false, + quiet: false, + stats: { + colors: true + }, + proxy: { + "/oauth2/": { + //target: "http://10.20.6.29:48181", + target: "http://sdnr:8181", + secure: false + }, + "/database/": { + //target: "http://10.20.6.29:48181", + target: "http://sdnr:8181", + secure: false + }, + "/restconf/": { + //target: "http://10.20.6.29:48181", + target: "http://sdnr:8181", + secure: false + }, + "/rests/": { + target: "http://sdnr:8181", + secure: false + }, + "/help/": { + //target: "http://10.20.6.29:48181", + target: "http://sdnr:8181", + secure: false + }, + "/websocket/": { + //target: "http://10.20.6.29:48181", + target: "http://sdnr:8181", + ws: true, + changeOrigin: true, + secure: false + } + } + } + }]; +} diff --git a/sdnr/wt-odlux/odlux/apps/inventoryApp/.babelrc b/sdnr/wt-odlux/odlux/apps/inventoryApp/.babelrc new file mode 100644 index 000000000..3d8cd1260 --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/inventoryApp/.babelrc @@ -0,0 +1,17 @@ +{ + "presets": [ + ["@babel/preset-react"], + ["@babel/preset-env", { + "targets": { + "chrome": "66" + }, + "spec": true, + "loose": false, + "modules": false, + "debug": false, + "useBuiltIns": "usage", + "forceAllTransforms": true + }] + ], + "plugins": [] +} diff --git a/sdnr/wt-odlux/odlux/apps/inventoryApp/package.json b/sdnr/wt-odlux/odlux/apps/inventoryApp/package.json new file mode 100644 index 000000000..5e37c52bb --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/inventoryApp/package.json @@ -0,0 +1,43 @@ +{ + "name": "@odlux/inventory-app", + "version": "0.1.0", + "description": "A react based modular UI to display network inventory data from a database.", + "main": "index.js", + "scripts": { + "start": "webpack-dev-server --env debug", + "build": "webpack --env release --config webpack.config.js", + "build:dev": "webpack --env debug --config webpack.config.js" + }, + "repository": { + "type": "git", + "url": "https://git.mfico.de/highstreet-technologies/odlux.git" + }, + "keywords": [ + "reactjs", + "redux", + "ui", + "framework" + ], + "author": "Matthias Fischer", + "license": "Apache-2.0", + "dependencies": { + "@emotion/react": "^11.7.0", + "@emotion/styled": "^11.6.0", + "@mui/icons-material": "^5.2.0", + "@mui/material": "^5.2.2", + "@mui/styles": "^5.2.2", + "@odlux/framework": "*" + }, + "peerDependencies": { + "@types/classnames": "2.2.6", + "@types/flux": "3.1.8", + "@types/jquery": "3.3.10", + "@types/react": "17.0.37", + "@types/react-dom": "17.0.11", + "@types/react-router-dom": "5.1.7", + "jquery": "3.3.1", + "react": "17.0.2", + "react-dom": "17.0.2", + "react-router-dom": "5.2.0" + } +} diff --git a/sdnr/wt-odlux/odlux/apps/inventoryApp/pom.xml b/sdnr/wt-odlux/odlux/apps/inventoryApp/pom.xml new file mode 100644 index 000000000..da2023ce7 --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/inventoryApp/pom.xml @@ -0,0 +1,110 @@ + + + + + 4.0.0 + + org.onap.ccsdk.features.sdnr.odlux + sdnr-odlux-app-inventoryApp + 1.7.0-SNAPSHOT + jar + + SDNR ODLUX :: ${project.artifactId} + + + Apache License, Version 2.0 + http://www.apache.org/licenses/LICENSE-2.0 + + + + + true + + + + + + + dist + odlux + + + + + maven-clean-plugin + + + + dist + false + + + node + false + + + node_modules + false + + + ../node_modules + false + + + + bin + false + + + + + + de.jacks-it-lab + frontend-maven-plugin + 1.7.2 + + + install node and yarn + + install-node-and-yarn + + + initialize + + v16.17.0 + v1.22.19 + + + + yarn build + + yarn + + + run build + + + + + + + diff --git a/sdnr/wt-odlux/odlux/apps/inventoryApp/src/actions/inventoryDeviceListActions.ts b/sdnr/wt-odlux/odlux/apps/inventoryApp/src/actions/inventoryDeviceListActions.ts new file mode 100644 index 000000000..710959a2a --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/inventoryApp/src/actions/inventoryDeviceListActions.ts @@ -0,0 +1,59 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * 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. + * ============LICENSE_END========================================================================== + */ +import { Action } from '../../../../framework/src/flux/action'; +import { Dispatch } from '../../../../framework/src/flux/store'; +// eslint-disable-next-line @typescript-eslint/no-unused-vars +import { IApplicationStoreState } from '../../../../framework/src/store/applicationStore'; + +import { InventoryDeviceListType } from '../models/inventoryDeviceListType'; +import { inventoryService } from '../services/inventoryService'; + +/** + * Represents the base action. + */ +export class BaseAction extends Action { } + +/** + * Represents an action causing the store to load all nodes. + */ +export class LoadAllInventoryDeviceListAction extends BaseAction { } + +/** + * Represents an action causing the store to update all nodes. + */ +export class AllInventoryDeviceListLoadedAction extends BaseAction { + /** + * Initialize this instance. + * + * @param inventoryDeviceList All the distinct nodes from the Inventory database. + */ + constructor(public inventoryDeviceList: InventoryDeviceListType[] | null, public error?: string) { + super(); + } +} + +/** + * Represents an asynchronous thunk action to load all nodes. + */ +export const loadAllInventoryDeviceListAsync = async (dispatch: Dispatch) => { + dispatch(new LoadAllInventoryDeviceListAction()); + const inventoryDeviceList: InventoryDeviceListType[] = (await inventoryService.getInventoryDeviceList().then(ne => + (ne))) || []; + return inventoryDeviceList && dispatch(new AllInventoryDeviceListLoadedAction(inventoryDeviceList)); +}; + diff --git a/sdnr/wt-odlux/odlux/apps/inventoryApp/src/actions/inventoryTreeActions.ts b/sdnr/wt-odlux/odlux/apps/inventoryApp/src/actions/inventoryTreeActions.ts new file mode 100644 index 000000000..2c6a0ed65 --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/inventoryApp/src/actions/inventoryTreeActions.ts @@ -0,0 +1,101 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * 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. + * ============LICENSE_END========================================================================== + */ + +import { AddErrorInfoAction } from '../../../../framework/src/actions/errorActions'; +import { NavigateToApplication } from '../../../../framework/src/actions/navigationActions'; +import { Action } from '../../../../framework/src/flux/action'; +import { Dispatch } from '../../../../framework/src/flux/store'; + +import { InventoryTreeNode, InventoryType, TreeDemoItem } from '../models/inventory'; +import { inventoryService } from '../services/inventoryService'; + +/** + * Represents the base action. + */ +export class BaseAction extends Action { } + +export class SetBusyAction extends BaseAction { + constructor(public busy: boolean = true) { + super(); + + } +} + +export class SetSearchTextAction extends BaseAction { + constructor(public searchTerm: string = '') { + super(); + + } +} + +export class UpdateInventoryTreeAction extends BaseAction { + constructor(public rootNode: InventoryTreeNode) { + super(); + + } +} + +export class UpdateSelectedNodeAction extends BaseAction { + constructor(public selectedNode?: InventoryType) { + super(); + + } +} + +export class UpdateExpandedNodesAction extends BaseAction { + constructor(public expandedNodes?: TreeDemoItem[]) { + super(); + + } +} + +export const setSearchTermAction = (searchTerm: string) => (dispatch: Dispatch) =>{ + dispatch(new SetSearchTextAction(searchTerm)); +}; + + +export const updateInventoryTreeAsyncAction = (mountId: string, searchTerm?: string) => async (dispatch: Dispatch) => { + dispatch(new SetBusyAction(true)); + dispatch(new SetSearchTextAction(searchTerm)); + try { + const result = await inventoryService.getInventoryTree(mountId, searchTerm); + if (!result) { + dispatch(new AddErrorInfoAction({ title: 'Error', message: `Could not load inventory tree for [${mountId}]. Please check you connection to the server and try later.` })); + dispatch(new NavigateToApplication('inventory')); + } else { + dispatch(new UpdateInventoryTreeAction(result)); + } + } catch (err) { + throw new Error('Could not load inventory tree from server.'); + } finally { + dispatch(new SetBusyAction(false)); + } +}; + +export const selectInventoryNodeAsyncAction = (nodeId: string) => async (dispatch: Dispatch) => { + dispatch(new SetBusyAction(true)); + try { + const result = await inventoryService.getInventoryEntry(nodeId); + if (!result) throw new Error('Could not load inventory tree from server.'); + dispatch(new UpdateSelectedNodeAction(result)); + } catch (err) { + throw new Error('Could not load inventory tree from server.'); + } finally { + dispatch(new SetBusyAction(false)); + } +}; diff --git a/sdnr/wt-odlux/odlux/apps/inventoryApp/src/actions/panelActions.ts b/sdnr/wt-odlux/odlux/apps/inventoryApp/src/actions/panelActions.ts new file mode 100644 index 000000000..d66608296 --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/inventoryApp/src/actions/panelActions.ts @@ -0,0 +1,31 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2020 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * 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. + * ============LICENSE_END========================================================================== + */ + +import { Action } from '../../../../framework/src/flux/action'; + +import { PanelId } from '../models/panelId'; + +export class SetPanelAction extends Action { + constructor(public panelId: PanelId) { + super(); + } +} + +export const setPanelAction = (panelId: PanelId) => { + return new SetPanelAction(panelId); +}; \ No newline at end of file diff --git a/sdnr/wt-odlux/odlux/apps/inventoryApp/src/assets/icons/inventoryAppIcon.svg b/sdnr/wt-odlux/odlux/apps/inventoryApp/src/assets/icons/inventoryAppIcon.svg new file mode 100644 index 000000000..507a835ab --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/inventoryApp/src/assets/icons/inventoryAppIcon.svg @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + diff --git a/sdnr/wt-odlux/odlux/apps/inventoryApp/src/components/refreshInventoryDialog.tsx b/sdnr/wt-odlux/odlux/apps/inventoryApp/src/components/refreshInventoryDialog.tsx new file mode 100644 index 000000000..027622249 --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/inventoryApp/src/components/refreshInventoryDialog.tsx @@ -0,0 +1,113 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * 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. + * ============LICENSE_END========================================================================== + */ +import * as React from 'react'; + +import Button from '@mui/material/Button'; +import Dialog from '@mui/material/Dialog'; +import DialogActions from '@mui/material/DialogActions'; +import DialogContent from '@mui/material/DialogContent'; +import DialogContentText from '@mui/material/DialogContentText'; +import DialogTitle from '@mui/material/DialogTitle'; + +import { connect, Connect, IDispatcher } from '../../../../framework/src/flux/connect'; +import { inventoryElementsReloadAction } from '../handlers/inventoryElementsHandler'; + +import { InventoryType } from '../models/inventory'; + +export enum RefreshInventoryDialogMode { + None = 'none', + RefreshInventoryTable = 'RefreshInventoryTable', +} + +const mapDispatch = (dispatcher: IDispatcher) => ({ + refreshInventory: () => dispatcher.dispatch(inventoryElementsReloadAction), +}); + +type DialogSettings = { + dialogTitle: string; + dialogDescription: string; + applyButtonText: string; + cancelButtonText: string; + enableMountIdEditor: boolean; + enableUsernameEditor: boolean; + enableExtendedEditor: boolean; +}; + +const settings: { [key: string]: DialogSettings } = { + [RefreshInventoryDialogMode.None]: { + dialogTitle: '', + dialogDescription: '', + applyButtonText: '', + cancelButtonText: '', + enableMountIdEditor: false, + enableUsernameEditor: false, + enableExtendedEditor: false, + }, + [RefreshInventoryDialogMode.RefreshInventoryTable]: { + dialogTitle: 'Do you want to refresh the Inventory table?', + dialogDescription: '', + applyButtonText: 'Yes', + cancelButtonText: 'Cancel', + enableMountIdEditor: true, + enableUsernameEditor: true, + enableExtendedEditor: true, + }, +}; + +type RefreshInventoryDialogComponentProps = Connect & { + mode: RefreshInventoryDialogMode; + onClose: () => void; +}; + +type RefreshInventoryDialogComponentState = InventoryType & { isNameValid: boolean; isHostSet: boolean }; + +class RefreshInventoryDialogComponent extends React.Component { + render(): JSX.Element { + const setting = settings[this.props.mode]; + return ( + + {setting.dialogTitle} + + + {setting.dialogDescription} + + + + + + + + ); + } + + private onRefresh = () => { + this.props.refreshInventory(); + this.props.onClose(); + }; + + private onCancel = () => { + this.props.onClose(); + }; +} + +export const RefreshInventoryDialog = connect(undefined, mapDispatch)(RefreshInventoryDialogComponent); +export default RefreshInventoryDialog; \ No newline at end of file diff --git a/sdnr/wt-odlux/odlux/apps/inventoryApp/src/fakeData/index.ts b/sdnr/wt-odlux/odlux/apps/inventoryApp/src/fakeData/index.ts new file mode 100644 index 000000000..136b908dd --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/inventoryApp/src/fakeData/index.ts @@ -0,0 +1,77 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * 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. + * ============LICENSE_END========================================================================== + */ + +import { convertPropertyNames, replaceHyphen } from "../../../../framework/src/utilities/yangHelper"; + +import { InventoryTreeNode, InventoryType } from "../models/inventory"; + +const data = [ + { "manufacturer-identifier": "ONF-Wireless-Transport", "version": "a2.module-newest", "uuid": "a2.module-1.1.1.5", "part-type-id": "3FE25774AA01", "model-identifier": "VAUIAEYAAA", "tree-level": 2, "node-id": "robot_sim_2_equipment", "description": "WS/CORE-MAIN/a2.module#5", "type-name": "a2.module", "serial": "0003548168", "id": "robot_sim_2_equipment/a2.module-1.1.1.5", "parent-uuid": "CARD-1.1.1.0", "contained-holder": ["SUBRACK-1.15.0.0"], "date": "2005-11-09T00:00:00.0Z" }, + { "manufacturer-identifier": "SAN", "version": "234", "uuid": "CARD-1.1.6.0", "part-type-id": "part-number-12", "model-identifier": "model-id-12", "tree-level": 1, "node-id": "robot_sim_2_equipment", "description": "WS/p8.module", "type-name": "p8.module", "serial": "serial-number-124", "id": "robot_sim_2_equipment/CARD-1.1.6.0", "parent-uuid": "SHELF-1.1.0.0", "contained-holder": ["PORT-1.1.6.5", "PORT-1.1.6.8", "PORT-1.1.6.7", "PORT-1.1.6.6"], "date": "2013-11-23T00:00:00.0Z" }, + { "manufacturer-identifier": "ONF-Wireless-Transport", "version": "a2.module-newest", "uuid": "a2.module-1.1.6.5", "part-type-id": "3EM23141AD01", "model-identifier": "CRPQABVFAA", "tree-level": 2, "node-id": "robot_sim_2_equipment", "description": "WS/p8.module/a2.module#5", "type-name": "a2.module", "serial": "310330008", "id": "robot_sim_2_equipment/a2.module-1.1.6.5", "parent-uuid": "CARD-1.1.6.0", "contained-holder": ["SUBRACK-1.65.0.0"], "date": "2013-04-13T00:00:00.0Z" }, + { "manufacturer-identifier": "ONF-Wireless-Transport", "version": "2017", "uuid": "CARD-1.55.1.4", "part-type-id": "partNo2017-12", "model-identifier": "model-id-s3s", "tree-level": 1, "node-id": "robot_sim_2_equipment", "description": "MWR#55Ch#1/RxDiv", "type-name": "RxDiv", "serial": "Serie2017-12", "id": "robot_sim_2_equipment/CARD-1.55.1.4", "parent-uuid": "IDU-1.55.0.0", "date": "2014-01-07T00:00:00.0Z" }, + { "manufacturer-identifier": "ONF-Wireless-Transport", "version": "a2.module-newest", "uuid": "a2.module-1.56.1.2", "part-type-id": "Partnumber", "model-identifier": "model-id", "tree-level": 1, "node-id": "robot_sim_2_equipment", "description": "MWR#56Ch#1/a2.moduletraff", "type-name": "a2.module", "serial": "Serial1", "id": "robot_sim_2_equipment/a2.module-1.56.1.2", "parent-uuid": "ODU-1.56.0.0", "date": "2017-09-09T00:00:00.0Z" }, + { "manufacturer-identifier": "SAN", "version": "123", "uuid": "CARD-1.1.1.0", "part-type-id": "part-number-2", "model-identifier": "model-id-2", "tree-level": 1, "node-id": "robot_sim_2_equipment", "description": "WS/CORE-MAIN", "type-name": "latest", "serial": "asdf-asdasd-asd", "id": "robot_sim_2_equipment/CARD-1.1.1.0", "parent-uuid": "SHELF-1.1.0.0", "contained-holder": ["PORT-1.1.1.8", "PORT-1.1.1.7", "PORT-1.1.1.6", "PORT-1.1.1.5"], "date": "2015-08-17T00:00:00.0Z" }, + { "manufacturer-identifier": "ONF-Wireless-Transport", "version": "a2.module-newest", "uuid": "a2.module-1.1.1.8", "part-type-id": "1AB376720002", "model-identifier": "NGI7AMLMAA", "tree-level": 2, "node-id": "robot_sim_2_equipment", "description": "WS/CORE-MAIN/a2.module#8", "type-name": "a2.module", "serial": "01T441601301", "id": "robot_sim_2_equipment/a2.module-1.1.1.8", "parent-uuid": "CARD-1.1.1.0", "contained-holder": ["SUBRACK-1.18.0.0"], "date": "2010-02-05T00:00:00.0Z" }, + { "manufacturer-identifier": "SAN", "version": "234", "uuid": "CARD-1.1.5.0", "part-type-id": "part-number-12", "model-identifier": "model-id-12", "tree-level": 1, "node-id": "robot_sim_2_equipment", "description": "WS/p8.module", "type-name": "p8.module", "serial": "africa", "id": "robot_sim_2_equipment/CARD-1.1.5.0", "parent-uuid": "SHELF-1.1.0.0", "contained-holder": ["PORT-1.1.5.6", "PORT-1.1.5.5", "PORT-1.1.5.8", "PORT-1.1.5.7"], "date": "2013-10-21T00:00:00.0Z" }, + { "manufacturer-identifier": "", "version": "", "uuid": "a2.module-1.1.5.6", "part-type-id": "", "model-identifier": "", "tree-level": 2, "node-id": "robot_sim_2_equipment", "description": "WS/p8.module/a2.module#6", "type-name": "a2.module", "serial": "", "id": "robot_sim_2_equipment/a2.module-1.1.5.6", "parent-uuid": "CARD-1.1.5.0", "contained-holder": ["SUBRACK-1.56.0.0"] }, { "manufacturer-identifier": "ONF-Wireless-Transport", "version": "MWR-ng", "uuid": "IDU-1.65.0.0", "part-type-id": "3DB76047BAAA02", "model-identifier": "model-id-s3s", "tree-level": 0, "node-id": "robot_sim_2_equipment", "description": "MWR-ng Dir#6.5-Ch#1", "type-name": "MWR-ng", "serial": "WAUZZI", "id": "robot_sim_2_equipment/IDU-1.65.0.0", "parent-uuid": "network-element", "contained-holder": ["PORT-1.65.1.4", "PORT-1.65.1.2"], "date": "2014-01-16T00:00:00.0Z" }, + { "manufacturer-identifier": "ONF-Wireless-Transport", "version": "a2.module-newest", "uuid": "a2.module-1.65.1.2", "part-type-id": "3EM23141AD01", "model-identifier": "CRPQABVFAA", "tree-level": 1, "node-id": "robot_sim_2_equipment", "description": "MWR#65Ch#1/a2.moduletraff", "type-name": "a2.module", "serial": "310330008", "id": "robot_sim_2_equipment/a2.module-1.65.1.2", "parent-uuid": "IDU-1.65.0.0", "date": "2013-04-13T00:00:00.0Z" }, + { "manufacturer-identifier": "ONF-Wireless-Transport", "version": "a2.module-newest", "uuid": "a2.module-1.1.5.5", "part-type-id": "3EM23141AD01", "model-identifier": "CRPQABVFAA", "tree-level": 2, "node-id": "robot_sim_2_equipment", "description": "WS/p8.module/a2.module#5", "type-name": "a2.module", "serial": "310330015", "id": "robot_sim_2_equipment/a2.module-1.1.5.5", "parent-uuid": "CARD-1.1.5.0", "contained-holder": ["SUBRACK-1.55.0.0"], "date": "2013-04-13T00:00:00.0Z" }, + { "manufacturer-identifier": "ONF-Wireless-Transport", "version": "unknown", "uuid": "CARD-1.1.8.0", "part-type-id": "unknown", "model-identifier": "model-id-s3s", "tree-level": 1, "node-id": "robot_sim_2_equipment", "description": "WS/DS3", "type-name": "p4.module", "serial": "sd-dsa-eqw", "id": "robot_sim_2_equipment/CARD-1.1.8.0", "parent-uuid": "SHELF-1.1.0.0", "date": "2008-10-21T00:00:00.0Z" }, + { "manufacturer-identifier": "CIT", "version": "wind", "uuid": "CARD-1.1.9.0", "part-type-id": "party-yea", "model-identifier": "model-id-s3s", "tree-level": 1, "node-id": "robot_sim_2_equipment", "description": "WS/wind", "type-name": "wind", "serial": "proto-type", "id": "robot_sim_2_equipment/CARD-1.1.9.0", "parent-uuid": "SHELF-1.1.0.0", "date": "2007-02-19T00:00:00.0Z" }, + { "manufacturer-identifier": "ONF-Wireless-Transport", "version": "a2.module-newest", "uuid": "a2.module-1.55.1.2", "part-type-id": "3EM23141AD01", "model-identifier": "CRPQABVFAA", "tree-level": 1, "node-id": "robot_sim_2_equipment", "description": "MWR#55Ch#1/a2.moduletraff", "type-name": "a2.module", "serial": "310330015", "id": "robot_sim_2_equipment/a2.module-1.55.1.2", "parent-uuid": "IDU-1.55.0.0", "date": "2013-04-13T00:00:00.0Z" }, + { "manufacturer-identifier": "ONF-Wireless-Transport", "version": "a2.module-newest", "uuid": "SHELF-1.1.0.0", "part-type-id": "Partnumber", "model-identifier": "model-id", "tree-level": 0, "node-id": "robot_sim_2_equipment", "description": "WS-8", "type-name": "WS-8", "serial": "Serial1", "id": "robot_sim_2_equipment/SHELF-1.1.0.0", "parent-uuid": "network-element", "contained-holder": ["SLOT-1.1.9.0", "SLOT-1.1.7.0", "SLOT-1.1.8.0", "SLOT-1.1.5.0", "SLOT-1.1.6.0", "SLOT-1.1.3.0", "SLOT-1.1.4.0", "SLOT-1.1.2.0", "SLOT-1.1.1.0"], "date": "2017-09-09T00:00:00.0Z" }, + { "manufacturer-identifier": "ONF-Wireless-Transport", "version": "MWR-ng", "uuid": "IDU-1.55.0.0", "part-type-id": "3DB76047BAAA02", "model-identifier": "model-id-s3s", "tree-level": 0, "node-id": "robot_sim_2_equipment", "description": "MWR-ng Dir#5.5-Ch#1", "type-name": "MWR-ng", "serial": "Serie2017-14", "id": "robot_sim_2_equipment/IDU-1.55.0.0", "parent-uuid": "network-element", "contained-holder": ["PORT-1.55.1.2", "PORT-1.55.1.4"], "date": "2014-01-15T00:00:00.0Z" }, + { "manufacturer-identifier": "ONF-Wireless-Transport", "version": "2017", "uuid": "CARD-1.65.1.4", "part-type-id": "partNo2017-12", "model-identifier": "model-id-s3s", "tree-level": 1, "node-id": "robot_sim_2_equipment", "description": "MWR#55Ch#0/RxDiv", "type-name": "RxDiv", "serial": "Serie2017-13", "id": "robot_sim_2_equipment/CARD-1.65.1.4", "parent-uuid": "IDU-1.65.0.0", "date": "2014-01-08T00:00:00.0Z" }, { "manufacturer-identifier": "ONF-Wireless-Transport", "version": "a2.module-newest", "uuid": "a2.module-1.1.1.7", "part-type-id": "1AB187280031", "model-identifier": "mod2", "tree-level": 2, "node-id": "robot_sim_2_equipment", "description": "WS/CORE-MAIN/a2.module#7", "type-name": "a2.module", "serial": "91T403003322", "id": "robot_sim_2_equipment/a2.module-1.1.1.7", "parent-uuid": "CARD-1.1.1.0", "contained-holder": ["SUBRACK-1.17.0.0"], "date": "2009-01-19T00:00:00.0Z" }, + { "manufacturer-identifier": "CIT", "version": "p1.module", "uuid": "CARD-1.1.7.0", "part-type-id": "part-number-s3s", "model-identifier": "model-id-s3s", "tree-level": 1, "node-id": "robot_sim_2_equipment", "description": "WS/DS1", "type-name": "p1.module_A", "serial": "serial-number-s3s", "id": "robot_sim_2_equipment/CARD-1.1.7.0", "parent-uuid": "SHELF-1.1.0.0", "date": "2007-08-27T00:00:00.0Z" }, + { "manufacturer-identifier": "", "version": "extrem-hyper", "uuid": "ODU-1.56.0.0", "part-type-id": "", "model-identifier": "", "tree-level": 0, "node-id": "robot_sim_2_equipment", "description": "MWR-hyper Dir#5.6-Ch#1", "type-name": "MWR-hyper", "serial": "", "id": "robot_sim_2_equipment/ODU-1.56.0.0", "parent-uuid": "network-element", "contained-holder": ["PORT-1.56.1.3", "PORT-1.56.1.4", "PORT-1.56.1.2"] } +]; + +const deleay = (time: number) => () => new Promise(resolve => setTimeout(resolve, time, time)); + +const getTreeElements = (searchTerm: string | null, treeLevel: number = 0, parentUUID: string | null = null): [InventoryTreeNode, boolean] => { + const elements = (data.filter(e => e["tree-level"] === treeLevel && (!parentUUID || e["parent-uuid"] === parentUUID)) || []) + let elementMatch = false; + const treeNode = elements.reduce((acc, cur) => { + const [children, childMatch] = getTreeElements(searchTerm, treeLevel + 1, cur["uuid"]); + const isMatch = searchTerm ? Object.keys(cur).some(k => String((cur as any)[k]).indexOf(searchTerm) > -1) : false; + elementMatch = elementMatch || isMatch || childMatch; + if (!searchTerm || isMatch || childMatch) { + acc[cur["uuid"]] = { + label: cur["uuid"], + children: children, + isMatch: isMatch, + }; + } + return acc; + }, {}); + + return [treeNode, elementMatch] +}; + +export const getTree = async (searchTerm: string | null = null): Promise => { + await deleay(600); + const [node] = getTreeElements(searchTerm); + return node; +}; + +export const getElement = async (id: string): Promise => { + await deleay(600); + const res = data.find(e => e.uuid === id); + return res && convertPropertyNames(res, replaceHyphen) as unknown as InventoryType; +}; diff --git a/sdnr/wt-odlux/odlux/apps/inventoryApp/src/handlers/inventoryAppRootHandler.ts b/sdnr/wt-odlux/odlux/apps/inventoryApp/src/handlers/inventoryAppRootHandler.ts new file mode 100644 index 000000000..b1a0c581f --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/inventoryApp/src/handlers/inventoryAppRootHandler.ts @@ -0,0 +1,53 @@ +/** +* ============LICENSE_START======================================================================== +* ONAP : ccsdk feature sdnr wt odlux +* ================================================================================================= +* Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved. +* ================================================================================================= +* 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. +* ============LICENSE_END========================================================================== +*/ +// main state handler + +import { combineActionHandler } from '../../../../framework/src/flux/middleware'; +// ** do not remove ** +// eslint-disable-next-line @typescript-eslint/no-unused-vars +import { IApplicationStoreState } from '../../../../framework/src/store/applicationStore'; + +import { PanelId } from '../models/panelId'; +import { IInventoryDeviceListState, inventoryDeviceListActionHandler } from './inventoryDeviceListActionHandler'; +import { IInventoryElementsState, inventoryElementsActionHandler } from './inventoryElementsHandler'; +import { IInvenroryTree, inventoryTreeHandler } from './inventoryTreeHandler'; +import { currentOpenPanelHandler } from './panelHandler'; + +export interface IInventoryAppStateState { + inventoryTree: IInvenroryTree; + currentOpenPanel: PanelId; + inventoryElements: IInventoryElementsState; + inventoryDeviceList: IInventoryDeviceListState; +} + +declare module '../../../../framework/src/store/applicationStore' { + interface IApplicationStoreState { + inventory: IInventoryAppStateState; + } +} + +const actionHandlers = { + inventoryTree: inventoryTreeHandler, + currentOpenPanel: currentOpenPanelHandler, + inventoryElements: inventoryElementsActionHandler, + inventoryDeviceList: inventoryDeviceListActionHandler, +}; + +export const inventoryAppRootHandler = combineActionHandler(actionHandlers); +export default inventoryAppRootHandler; + diff --git a/sdnr/wt-odlux/odlux/apps/inventoryApp/src/handlers/inventoryDeviceListActionHandler.ts b/sdnr/wt-odlux/odlux/apps/inventoryApp/src/handlers/inventoryDeviceListActionHandler.ts new file mode 100644 index 000000000..7c06cad99 --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/inventoryApp/src/handlers/inventoryDeviceListActionHandler.ts @@ -0,0 +1,56 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * 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. + * ============LICENSE_END========================================================================== + */ +import { IActionHandler } from '../../../../framework/src/flux/action'; + +import { AllInventoryDeviceListLoadedAction, LoadAllInventoryDeviceListAction } from '../actions/inventoryDeviceListActions'; +import { InventoryDeviceListType } from '../models/inventoryDeviceListType'; + +export interface IInventoryDeviceListState { + inventoryDeviceList: InventoryDeviceListType[]; + busy: boolean; +} + +const inventoryDeviceListListStateInit: IInventoryDeviceListState = { + inventoryDeviceList: [], + busy: false, +}; + +export const inventoryDeviceListActionHandler: IActionHandler = (state = inventoryDeviceListListStateInit, action) => { + if (action instanceof LoadAllInventoryDeviceListAction) { + + state = { + ...state, + busy: true, + }; + + } else if (action instanceof AllInventoryDeviceListLoadedAction) { + if (!action.error && action.inventoryDeviceList) { + state = { + ...state, + inventoryDeviceList: action.inventoryDeviceList, + busy: false, + }; + } else { + state = { + ...state, + busy: false, + }; + } + } + return state; +}; \ No newline at end of file diff --git a/sdnr/wt-odlux/odlux/apps/inventoryApp/src/handlers/inventoryElementsHandler.ts b/sdnr/wt-odlux/odlux/apps/inventoryApp/src/handlers/inventoryElementsHandler.ts new file mode 100644 index 000000000..7bac8f632 --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/inventoryApp/src/handlers/inventoryElementsHandler.ts @@ -0,0 +1,36 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * 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. + * ============LICENSE_END========================================================================== + */ +import { createExternal, IExternalTableState } from '../../../../framework/src/components/material-table/utilities'; +import { createSearchDataHandler } from '../../../../framework/src/utilities/elasticSearch'; + +import { InventoryType } from '../models/inventory'; + +export interface IInventoryElementsState extends IExternalTableState { } + +// create eleactic search material data fetch handler +const inventoryElementsSearchHandler = createSearchDataHandler('inventory'); + +export const { + actionHandler: inventoryElementsActionHandler, + createActions: createInventoryElementsActions, + createProperties: createInventoryElementsProperties, + reloadAction: inventoryElementsReloadAction, + + // set value action, to change a value +} = createExternal(inventoryElementsSearchHandler, appState => appState.inventory.inventoryElements); + diff --git a/sdnr/wt-odlux/odlux/apps/inventoryApp/src/handlers/inventoryTreeHandler.ts b/sdnr/wt-odlux/odlux/apps/inventoryApp/src/handlers/inventoryTreeHandler.ts new file mode 100644 index 000000000..fe90d9820 --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/inventoryApp/src/handlers/inventoryTreeHandler.ts @@ -0,0 +1,68 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * 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. + * ============LICENSE_END========================================================================== + */ + +import { IActionHandler } from '../../../../framework/src/flux/action'; + +import { SetBusyAction, SetSearchTextAction, UpdateExpandedNodesAction, UpdateInventoryTreeAction, UpdateSelectedNodeAction } from '../actions/inventoryTreeActions'; +import { InventoryTreeNode, InventoryType, TreeDemoItem } from '../models/inventory'; + + +export interface IInvenroryTree { + isBusy: boolean; + rootNodes: TreeDemoItem[]; + selectedNode?: InventoryType; + expandedItems: TreeDemoItem[]; + searchTerm: string; +} + +const initialState: IInvenroryTree = { + isBusy: false, + rootNodes: [], + searchTerm: '', + selectedNode: undefined, + expandedItems: [], +}; + + +const getTreeDataFromInvetoryTreeNode = (node: InventoryTreeNode): TreeDemoItem[] => Object.keys(node).reduce((acc, key) => { + const cur = node[key]; + acc.push({ + isMatch: cur.isMatch, + content: cur.label || key, + value: key, + children: cur.children && getTreeDataFromInvetoryTreeNode(cur.children), + }); + return acc; +}, []); + +export const inventoryTreeHandler: IActionHandler = (state = initialState, action) => { + if (action instanceof SetBusyAction) { + state = { ...state, isBusy: action.busy }; + } else if (action instanceof SetSearchTextAction) { + state = { ...state, searchTerm: action.searchTerm }; + } else if (action instanceof UpdateInventoryTreeAction) { + const rootNodes = getTreeDataFromInvetoryTreeNode(action.rootNode); + state = { ...state, rootNodes: rootNodes, expandedItems: [], selectedNode: undefined }; + } else if (action instanceof UpdateSelectedNodeAction) { + state = { ...state, selectedNode: action.selectedNode }; + } else if (action instanceof UpdateExpandedNodesAction) { + state = { ...state, expandedItems: action.expandedNodes || [] }; + } + + return state; +}; \ No newline at end of file diff --git a/sdnr/wt-odlux/odlux/apps/inventoryApp/src/handlers/panelHandler.ts b/sdnr/wt-odlux/odlux/apps/inventoryApp/src/handlers/panelHandler.ts new file mode 100644 index 000000000..7912d0ea5 --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/inventoryApp/src/handlers/panelHandler.ts @@ -0,0 +1,11 @@ +import { IActionHandler } from '../../../../framework/src/flux/action'; + +import { SetPanelAction } from '../actions/panelActions'; +import { PanelId } from '../models/panelId'; + +export const currentOpenPanelHandler: IActionHandler = (state = null, action) => { + if (action instanceof SetPanelAction) { + state = action.panelId; + } + return state; +}; \ No newline at end of file diff --git a/sdnr/wt-odlux/odlux/apps/inventoryApp/src/index.html b/sdnr/wt-odlux/odlux/apps/inventoryApp/src/index.html new file mode 100644 index 000000000..2c44424dd --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/inventoryApp/src/index.html @@ -0,0 +1,28 @@ + + + + + + + + + Inventory App + + + +
+ + + + + + \ No newline at end of file diff --git a/sdnr/wt-odlux/odlux/apps/inventoryApp/src/models/inventory.ts b/sdnr/wt-odlux/odlux/apps/inventoryApp/src/models/inventory.ts new file mode 100644 index 000000000..a09fd7e41 --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/inventoryApp/src/models/inventory.ts @@ -0,0 +1,50 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * 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. + * ============LICENSE_END========================================================================== + */ + +import { ExternalTreeItem } from '../../../../framework/src/components/material-ui/treeView'; + +export { HitEntry, Result } from '../../../../framework/src/models'; + +export type InventoryType = { + treeLevel: number; + parentUuid: string; + nodeId: string; + uuid: string; + containedHolder?: (string)[] | null; + manufacturerName?: string; + manufacturerIdentifier: string; + serial: string; + date: string; + version: string; + description: string; + partTypeId: string; + modelIdentifier: string; + typeName: string; +}; + +export type InventoryTreeNode = { + [key: string]: { + label: string; + children?: InventoryTreeNode; + isMatch?: boolean; + ownSeverity?: string; + childrenSeveritySummary?: string; + }; +}; + +export type TreeDemoItem = ExternalTreeItem; \ No newline at end of file diff --git a/sdnr/wt-odlux/odlux/apps/inventoryApp/src/models/inventoryDeviceListType.ts b/sdnr/wt-odlux/odlux/apps/inventoryApp/src/models/inventoryDeviceListType.ts new file mode 100644 index 000000000..ab2411401 --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/inventoryApp/src/models/inventoryDeviceListType.ts @@ -0,0 +1,25 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * 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. + * ============LICENSE_END========================================================================== + */ + +/** + * Represents all the distinct devices from the inventory history data. + */ + +export type InventoryDeviceListType = { + nodeId: string; +}; diff --git a/sdnr/wt-odlux/odlux/apps/inventoryApp/src/models/networkElementConnection.ts b/sdnr/wt-odlux/odlux/apps/inventoryApp/src/models/networkElementConnection.ts new file mode 100644 index 000000000..e1ef1ea2d --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/inventoryApp/src/models/networkElementConnection.ts @@ -0,0 +1,37 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * 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. + * ============LICENSE_END========================================================================== + */ + +export type NetworkElementConnection = { + id?: string; + nodeId: string; + host: string; + port: number; + username?: string; + password?: string; + isRequired?: boolean; + status?: 'connected' | 'mounted' | 'unmounted' | 'connecting' | 'disconnected' | 'idle'; + coreModelCapability?: string; + deviceType?: string; + nodeDetails?: { + availableCapabilities: string[]; + unavailableCapabilities: { + failureReason: string; + capability: string; + }[]; + }; +}; diff --git a/sdnr/wt-odlux/odlux/apps/inventoryApp/src/models/panelId.ts b/sdnr/wt-odlux/odlux/apps/inventoryApp/src/models/panelId.ts new file mode 100644 index 000000000..8f8224c8c --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/inventoryApp/src/models/panelId.ts @@ -0,0 +1,19 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2020 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * 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. + * ============LICENSE_END========================================================================== + */ + +export type PanelId = null | 'Equipment' | 'TreeView'; \ No newline at end of file diff --git a/sdnr/wt-odlux/odlux/apps/inventoryApp/src/pluginInventory.tsx b/sdnr/wt-odlux/odlux/apps/inventoryApp/src/pluginInventory.tsx new file mode 100644 index 000000000..819859919 --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/inventoryApp/src/pluginInventory.tsx @@ -0,0 +1,88 @@ +/** +* ============LICENSE_START======================================================================== +* ONAP : ccsdk feature sdnr wt odlux +* ================================================================================================= +* Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved. +* ================================================================================================= +* 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. +* ============LICENSE_END========================================================================== +*/ +// app configuration and main entry point for the app +import React from 'react'; +import { Redirect, Route, RouteComponentProps, Switch, withRouter } from 'react-router-dom'; + +import { connect, Connect, IDispatcher } from '../../../framework/src/flux/connect'; +import applicationManager from '../../../framework/src/services/applicationManager'; +import { IApplicationStoreState } from '../../../framework/src/store/applicationStore'; +import { SetPanelAction } from './actions/panelActions'; +import inventoryAppRootHandler from './handlers/inventoryAppRootHandler'; +import { createInventoryElementsActions, createInventoryElementsProperties } from './handlers/inventoryElementsHandler'; +import { PanelId } from './models/panelId'; +import Dashboard from './views/dashboard'; +import { InventoryTreeView } from './views/treeview'; + +const appIcon = require('./assets/icons/inventoryAppIcon.svg'); // select app icon + +let currentMountId: string | undefined = undefined; +const mapProps = (state: IApplicationStoreState) => ({ + inventoryProperties: createInventoryElementsProperties(state), + panelId: state.inventory.currentOpenPanel, +}); + +const mapDispatch = (dispatcher: IDispatcher) => ({ + inventoryActions: createInventoryElementsActions(dispatcher.dispatch, true), + setCurrentPanel: (panelId: PanelId) => dispatcher.dispatch(new SetPanelAction(panelId)), +}); + +const InventoryTableApplicationRouteAdapter = connect(mapProps, mapDispatch)((props: RouteComponentProps<{ mountId?: string }> & Connect) => { + if (currentMountId !== props.match.params.mountId) { + // route parameter has changed + currentMountId = props.match.params.mountId || undefined; + // Hint: This timeout is needed, since it is not recommended to change the state while rendering is in progress ! + window.setTimeout(() => { + if (currentMountId) { + if (props.panelId) { + props.setCurrentPanel(props.panelId); + } else { + props.setCurrentPanel('Equipment'); + } + props.inventoryActions.onFilterChanged('nodeId', currentMountId); + if (!props.inventoryProperties.showFilter) { + props.inventoryActions.onToggleFilter(false); + } + props.inventoryActions.onRefresh(); + } + }); + } + return ( + + ); +}); + +const App = withRouter((props: RouteComponentProps) => ( + + + + + + +)); + +export function register() { + applicationManager.registerApplication({ + name: 'inventory', + icon: appIcon, + rootActionHandler: inventoryAppRootHandler, + rootComponent: App, + menuEntry: 'Inventory', + }); +} + diff --git a/sdnr/wt-odlux/odlux/apps/inventoryApp/src/services/inventoryService.ts b/sdnr/wt-odlux/odlux/apps/inventoryApp/src/services/inventoryService.ts new file mode 100644 index 000000000..4014fcf6d --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/inventoryApp/src/services/inventoryService.ts @@ -0,0 +1,92 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * 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. + * ============LICENSE_END========================================================================== + */ +import { Result } from '../../../../framework/src/models/elasticSearch'; +import { requestRest } from '../../../../framework/src/services/restService'; + +import { InventoryTreeNode, InventoryType } from '../models/inventory'; +import { InventoryDeviceListType } from '../models/inventoryDeviceListType'; + +/** + * Represents a web api accessor service for all maintenence entries related actions. + */ +class InventoryService { + public async getInventoryTree(mountId: string, searchTerm: string = ''): Promise { + //return await getTree(searchTerm); + const path = `/tree/read-inventoryequipment-tree/${mountId}`; + const body = { + 'query': searchTerm, + }; + const inventoryTree = await requestRest(path, { method: 'POST', body: JSON.stringify(body) }); + return inventoryTree && inventoryTree || null; + } + + public async getInventoryEntry(id: string): Promise { + const path = '/rests/operations/data-provider:read-inventory-list'; + const body = { + 'data-provider:input': { + 'filter': [ + { property: 'id', filtervalue: id }, + ], + 'sortorder': [], + 'pagination': { + 'size': 1, + 'page': 1, + }, + }, + }; + const inventoryTreeElement = await requestRest<{ + 'data-provider:output': { + 'pagination': { + 'size': number; + 'page': number; + 'total': number; + }; + 'data': InventoryType[]; + }; + }>(path, { method: 'POST', body: JSON.stringify(body) }); + + return inventoryTreeElement && inventoryTreeElement['data-provider:output'] && inventoryTreeElement['data-provider:output'].pagination && inventoryTreeElement['data-provider:output'].pagination.total >= 1 && + inventoryTreeElement['data-provider:output'].data && inventoryTreeElement['data-provider:output'].data[0] || undefined; + // return await getElement(id); + } + + /** + * Gets all nodes from the inventory device list. + */ + public async getInventoryDeviceList(): Promise<(InventoryDeviceListType)[] | null> { + const path = '/rests/operations/data-provider:read-inventory-device-list'; + const query = { + 'data-provider:input': { + 'filter': [], + 'sortorder': [], + 'pagination': { + 'size': 20, + 'page': 1, + }, + }, + }; + + const result = await requestRest>(path, { method: 'POST', body: JSON.stringify(query) }); + return result && result['data-provider:output'] && result['data-provider:output'].data && result['data-provider:output'].data.map(ne => ({ + nodeId: ne, + })) || null; + } + +} + +export const inventoryService = new InventoryService(); \ No newline at end of file diff --git a/sdnr/wt-odlux/odlux/apps/inventoryApp/src/views/dashboard.tsx b/sdnr/wt-odlux/odlux/apps/inventoryApp/src/views/dashboard.tsx new file mode 100644 index 000000000..acd2c6216 --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/inventoryApp/src/views/dashboard.tsx @@ -0,0 +1,202 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * 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. + * ============LICENSE_END========================================================================== + */ + +import React from 'react'; +import { RouteComponentProps, withRouter } from 'react-router-dom'; + +import Refresh from '@mui/icons-material/Refresh'; +import { AppBar, MenuItem, Tab, Tabs, Typography } from '@mui/material'; + +import { NavigateToApplication } from '../../../../framework/src/actions/navigationActions'; +import { ColumnType, MaterialTable, MaterialTableCtorType } from '../../../../framework/src/components/material-table'; +import { connect, Connect, IDispatcher } from '../../../../framework/src/flux/connect'; +import { IApplicationStoreState } from '../../../../framework/src/store/applicationStore'; + +import { loadAllInventoryDeviceListAsync } from '../actions/inventoryDeviceListActions'; +import { updateInventoryTreeAsyncAction } from '../actions/inventoryTreeActions'; +import { setPanelAction } from '../actions/panelActions'; +import RefreshInventoryDialog, { RefreshInventoryDialogMode } from '../components/refreshInventoryDialog'; +import { createInventoryElementsActions, createInventoryElementsProperties } from '../handlers/inventoryElementsHandler'; +import { InventoryType } from '../models/inventory'; +import { InventoryDeviceListType } from '../models/inventoryDeviceListType'; +import { PanelId } from '../models/panelId'; + +const InventoryTable = MaterialTable as MaterialTableCtorType; + +const mapProps = (state: IApplicationStoreState) => ({ + panelId: state.inventory.currentOpenPanel, + inventoryElementsProperties: createInventoryElementsProperties(state), + inventoryElements: state.inventory.inventoryElements, + inventoryDeviceList: state.inventory.inventoryDeviceList.inventoryDeviceList, +}); + +const mapDispatch = (dispatcher: IDispatcher) => ({ + switchActivePanel: (panelId: PanelId) => { + dispatcher.dispatch(setPanelAction(panelId)); + }, + inventoryElementsActions: createInventoryElementsActions(dispatcher.dispatch), + navigateToApplication: (applicationName: string, path?: string) => dispatcher.dispatch(new NavigateToApplication(applicationName, path)), + updateInventoryTree: (mountId: string, searchTerm?: string) => dispatcher.dispatch(updateInventoryTreeAsyncAction(mountId, searchTerm)), + getAllInventoryDeviceList: async () => { + await dispatcher.dispatch(loadAllInventoryDeviceListAsync); + }, +}); + +let treeViewInitialSorted = false; +let inventoryInitialSorted = false; + +const InventoryDeviceListTable = MaterialTable as MaterialTableCtorType; + +type DashboardComponentProps = RouteComponentProps & Connect; +type DashboardComponentState = { + refreshInventoryEditorMode: RefreshInventoryDialogMode; +}; + +class DashboardSelectorComponent extends React.Component { + constructor(props: DashboardComponentProps) { + super(props); + + this.state = { + refreshInventoryEditorMode: RefreshInventoryDialogMode.None, + }; + } + + private onHandleTabChange = (event: React.SyntheticEvent, newValue: PanelId) => { + this.onTogglePanel(newValue); + }; + + private onTogglePanel = (panelId: PanelId) => { + const nextActivePanel = panelId; + this.props.switchActivePanel(nextActivePanel); + + switch (nextActivePanel) { + case 'Equipment': + + if (!inventoryInitialSorted) { + this.props.inventoryElementsActions.onHandleExplicitRequestSort('nodeId', 'asc'); + inventoryInitialSorted = true; + } else { + this.props.inventoryElementsActions.onRefresh(); + + } + break; + case 'TreeView': + this.props.getAllInventoryDeviceList(); + break; + case null: + // do nothing if all panels are closed + break; + default: + console.warn('Unknown nextActivePanel [' + nextActivePanel + '] in connectView'); + break; + } + + }; + + getContextMenu = (rowData: InventoryType) => { + return [ + { this.props.updateInventoryTree(rowData.nodeId, rowData.uuid); this.props.navigateToApplication('inventory', rowData.nodeId); }}>View in Treeview, + ]; + + }; + + render() { + + const refreshInventoryAction = { + icon: Refresh, tooltip: 'Refresh Inventory', ariaLabel: 'refresh', onClick: () => { + this.setState({ + refreshInventoryEditorMode: RefreshInventoryDialogMode.RefreshInventoryTable, + }); + }, + }; + const { panelId: activePanelId } = this.props; + return ( + <> + + + + + + + + { + + activePanelId === 'Equipment' && + <> + { + + return this.getContextMenu(rowData); + }} > + + + + + } + { + activePanelId === 'TreeView' && + <> + { + this.props.navigateToApplication('inventory', row.nodeId); + this.props.updateInventoryTree(row.nodeId, '*'); + }} + rows={this.props.inventoryDeviceList} asynchronus + columns={[ + { property: 'nodeId', title: 'Node Name', type: ColumnType.text }, + ]} idProperty="nodeId" > + + + } + + ); + } + + private onCloseRefreshInventoryDialog = () => { + this.setState({ + refreshInventoryEditorMode: RefreshInventoryDialogMode.None, + }); + }; + + componentDidMount() { + if (this.props.panelId === null) { //set default tab if none is set + this.onTogglePanel('Equipment'); + } + } +} + +export const Dashboard = withRouter(connect(mapProps, mapDispatch)(DashboardSelectorComponent)); +export default Dashboard; + diff --git a/sdnr/wt-odlux/odlux/apps/inventoryApp/src/views/detail.tsx b/sdnr/wt-odlux/odlux/apps/inventoryApp/src/views/detail.tsx new file mode 100644 index 000000000..8d47ec3d9 --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/inventoryApp/src/views/detail.tsx @@ -0,0 +1,44 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * 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. + * ============LICENSE_END========================================================================== + */ +import React from 'react'; +import { RouteComponentProps, withRouter } from 'react-router-dom'; + +import Button from '@mui/material/Button'; +import { Theme } from '@mui/material/styles'; // infra for styling +import { WithStyles } from '@mui/styles'; +import createStyles from '@mui/styles/createStyles'; +import withStyles from '@mui/styles/withStyles'; + +const styles = (theme: Theme) => createStyles({ + warnButton: { + backgroundColor: theme.palette.primary.dark, + }, +}); + +type DetailProps = RouteComponentProps<{ id: string }> & WithStyles; + +export const Detail = withStyles( styles )( withRouter( (props: DetailProps) => ( +
+

Detail {props.match.params.id}

+

This are the information about {props.staticContext}.

+ + +
+))); + +export default Detail; \ No newline at end of file diff --git a/sdnr/wt-odlux/odlux/apps/inventoryApp/src/views/treeview.tsx b/sdnr/wt-odlux/odlux/apps/inventoryApp/src/views/treeview.tsx new file mode 100644 index 000000000..954c074c1 --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/inventoryApp/src/views/treeview.tsx @@ -0,0 +1,155 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * 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. + * ============LICENSE_END========================================================================== + */ +import React from 'react'; + +import Breadcrumbs from '@mui/material/Breadcrumbs'; +import Link from '@mui/material/Link'; +import { Theme } from '@mui/material/styles'; +import { WithStyles } from '@mui/styles'; +import createStyles from '@mui/styles/createStyles'; +import withStyles from '@mui/styles/withStyles'; +import { RouteComponentProps } from 'react-router-dom'; +import { SearchMode, TreeView, TreeViewCtorType } from '../../../../framework/src/components/material-ui/treeView'; +import { renderObject } from '../../../../framework/src/components/objectDump'; +import { Connect, connect, IDispatcher } from '../../../../framework/src/flux/connect'; +import { IApplicationStoreState } from '../../../../framework/src/store/applicationStore'; + +import { selectInventoryNodeAsyncAction, setSearchTermAction, UpdateExpandedNodesAction, updateInventoryTreeAsyncAction, UpdateSelectedNodeAction } from '../actions/inventoryTreeActions'; +import { TreeDemoItem } from '../models/inventory'; + +const styles = (theme: Theme) => createStyles({ + root: { + flex: '1 0 0%', + display: 'flex', + flexDirection: 'row', + }, + tree: { + wordWrap: 'break-word', + minWidth: '250px', + padding: `0px ${theme.spacing(1)}`, + }, + details: { + flex: '5 0 0%', + padding: `0px ${theme.spacing(1)}`, + }, +}); + +const mapProps = (state: IApplicationStoreState) => ({ + isBusy: state.inventory.inventoryTree.isBusy, + rootNodes: state.inventory.inventoryTree.rootNodes, + searchTerm: state.inventory.inventoryTree.searchTerm, + selectedNode: state.inventory.inventoryTree.selectedNode, + expendedItems: state.inventory.inventoryTree.expandedItems, +}); + +const mapDispatch = (dispatcher: IDispatcher) => ({ + updateExpendedNodes: (expendedNodes: TreeDemoItem[]) => dispatcher.dispatch(new UpdateExpandedNodesAction(expendedNodes)), + updateInventoryTree: (mountId: string, searchTerm?: string) => dispatcher.dispatch(updateInventoryTreeAsyncAction(mountId, searchTerm)), + selectTreeNode: (nodeId?: string) => nodeId ? dispatcher.dispatch(selectInventoryNodeAsyncAction(nodeId)) : dispatcher.dispatch(new UpdateSelectedNodeAction(undefined)), + setSearchTerm: (searchTerm: string) => dispatcher.dispatch(setSearchTermAction(searchTerm)), +}); + +const propsChache = Symbol('PropsCache'); +const InventoryTree = TreeView as any as TreeViewCtorType; + + + +type TreeviewComponentProps = RouteComponentProps<{ mountId: string }> & WithStyles & Connect; + +type TreeviewComponentState = { + [propsChache]: { + rootNodes?: TreeDemoItem[]; + }; + rootNodes: TreeDemoItem[]; +}; + + +class DashboardComponent extends React.Component { + + constructor(props: TreeviewComponentProps) { + super(props); + + this.state = { + [propsChache]: {}, + rootNodes: [], + }; + } + + static getDerivedStateFromProps(props: TreeviewComponentProps, state: TreeviewComponentState) { + if (state[propsChache].rootNodes != props.rootNodes) { + // eslint-disable-next-line no-param-reassign + state = { ...state, rootNodes: props.rootNodes }; + } + return state; + } + + render() { + const { classes, updateInventoryTree, updateExpendedNodes, expendedItems, selectedNode, selectTreeNode, searchTerm, match: { params: { mountId } } } = this.props; + const scrollbar = { overflow: 'auto', paddingRight: '20px' }; + + let filteredDashboardPath = `/inventory/dashboard/${this.props.match.params.mountId}`; + let basePath = `/inventory/${this.props.match.params.mountId}`; + + return ( +
+
+ + ) => { + event.preventDefault(); + this.props.history.push(filteredDashboardPath); + }}>Back + ) => { + event.preventDefault(); + this.props.history.push(basePath); + }}>{this.props.match.params.mountId} + +
+
+
+ updateInventoryTree(mountId, searchTerm)} expandedItems={expendedItems} onFolderClick={(item) => { + const indexOfItemToToggle = expendedItems.indexOf(item); + if (indexOfItemToToggle === -1) { + updateExpendedNodes([...expendedItems, item]); + } else { + updateExpendedNodes([ + ...expendedItems.slice(0, indexOfItemToToggle), + ...expendedItems.slice(indexOfItemToToggle + 1), + ]); + } + }} + onItemClick={(elm) => selectTreeNode(elm.value)} /> +
{ + selectedNode && renderObject(selectedNode, 'tree-view') || null + }
+
+
+ ); + } + + componentWillUnmount() { + this.props.setSearchTerm('*'); + } +} + +export const InventoryTreeView = connect(mapProps, mapDispatch)(withStyles(styles)(DashboardComponent)); +export default InventoryTreeView; \ No newline at end of file diff --git a/sdnr/wt-odlux/odlux/apps/inventoryApp/tsconfig.json b/sdnr/wt-odlux/odlux/apps/inventoryApp/tsconfig.json new file mode 100644 index 000000000..ca65092e0 --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/inventoryApp/tsconfig.json @@ -0,0 +1,37 @@ +{ + "compilerOptions": { + "baseUrl": "./src", + "outDir": "./dist", + "sourceMap": true, + "forceConsistentCasingInFileNames": true, + "allowSyntheticDefaultImports": true, + "allowUnreachableCode": false, + "allowUnusedLabels": false, + "noFallthroughCasesInSwitch": true, + "noImplicitAny": true, + "noImplicitReturns": true, + "noImplicitThis": true, + "strictNullChecks": true, + "pretty": true, + "newLine": "LF", + "module": "es2015", + "target": "es2016", + "moduleResolution": "node", + "experimentalDecorators": true, + "jsx": "preserve", + "lib": [ + "dom", + "es2015", + "es2016" + ], + "types": [ + "prop-types", + "react", + "react-dom" + ] + }, + "exclude": [ + "dist", + "node_modules" + ] +} diff --git a/sdnr/wt-odlux/odlux/apps/inventoryApp/webpack.config.js b/sdnr/wt-odlux/odlux/apps/inventoryApp/webpack.config.js new file mode 100644 index 000000000..6a780560d --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/inventoryApp/webpack.config.js @@ -0,0 +1,178 @@ +/** + * Webpack 4 configuration file + * see https://webpack.js.org/configuration/ + * see https://webpack.js.org/configuration/dev-server/ + */ + +"use strict"; + +const path = require("path"); +const webpack = require("webpack"); +const CopyWebpackPlugin = require("copy-webpack-plugin"); +const TerserPlugin = require('terser-webpack-plugin'); + +// const __dirname = (path => path.replace(/^([a-z]\:)/, c => c.toUpperCase()))(process.__dirname()); + +module.exports = (env) => { + const distPath = path.resolve(__dirname, env === "release" ? "." : "../..", "dist"); + const frameworkPath = path.resolve(__dirname, env === "release" ? "../../framework" : "../..", "dist"); + return [{ + name: "App", + + mode: "none", //disable default behavior + + target: "web", + + context: path.resolve(__dirname, "src"), + + entry: { + inventoryApp: ["./pluginInventory.tsx"] + }, + + devtool: env === "release" ? false : "source-map", + + resolve: { + extensions: [".ts", ".tsx", ".js", ".jsx"] + }, + + output: { + path: distPath, + filename: "[name].js", + library: "[name]", + libraryTarget: "umd2", + chunkFilename: "[name].js" + }, + module: { + rules: [{ + test: /\.tsx?$/, + exclude: /node_modules/, + use: [{ + loader: "babel-loader" + }, { + loader: "ts-loader" + }] + }, { + test: /\.jsx?$/, + exclude: /node_modules/, + use: [{ + loader: "babel-loader" + }] + },{ + //don't minify images + test: /\.(png|gif|jpg|svg)$/, + use: [{ + loader: 'url-loader', + options: { + limit: 10, + name: './images/[name].[ext]' + } + }] + }] + }, + + optimization: { + noEmitOnErrors: true, + namedModules: env !== "release", + minimize: env === "release", + minimizer: env !== "release" ? [] : [new TerserPlugin({ + terserOptions: { + warnings: false, // false, true, "verbose" + compress: { + drop_console: true, + drop_debugger: true, + } + } + })], + }, + + plugins: [ + new webpack.DllReferencePlugin({ + context: path.resolve(__dirname, "../../framework/src"), + manifest: require(path.resolve(frameworkPath, "vendor-manifest.json")), + sourceType: "umd2" + }), + new webpack.DllReferencePlugin({ + context: path.resolve(__dirname, "../../framework/src"), + manifest: require(path.resolve(frameworkPath, "app-manifest.json")), + sourceType: "umd2" + }), + ...(env === "release" ? [ + new webpack.DefinePlugin({ + "process.env": { + NODE_ENV: "'production'", + VERSION: JSON.stringify(require("./package.json").version) + } + }), + ] : [ + new webpack.DefinePlugin({ + "process.env": { + NODE_ENV: "'development'", + VERSION: JSON.stringify(require("./package.json").version) + } + }), + new CopyWebpackPlugin([{ + from: 'index.html', + to: distPath + }]), + ]) + ], + + devServer: { + public: "http://localhost:3100", + contentBase: frameworkPath, + + compress: true, + headers: { + "Access-Control-Allow-Origin": "*" + }, + host: "0.0.0.0", + port: 3100, + disableHostCheck: true, + historyApiFallback: true, + inline: true, + hot: false, + quiet: false, + stats: { + colors: true + }, + proxy: { + "/oauth2/": { + target: "http://sdnc-web:8080", + secure: false + }, + "/database/": { + target: "http://sdnc-web:8080", + secure: false + }, + "/restconf/": { + target: "http://sdnc-web:8080", + secure: false + }, + "/rests/": { + target: "http://sdnc-web:8080", + secure: false + }, + "/help/": { + target: "http://sdnc-web:8080", + secure: false + }, + "/tree/": { + target: "http://sdnc-web:8080", + secure: false + }, + "/websocket": { + target: "http://sdnc-web:8080", + ws: true, + changeOrigin: true, + secure: false + }, + "/yang-schema": { + target: "http://sdnc-web:8080", + ws: true, + changeOrigin: true, + secure: false + } + } + } + }]; +} diff --git a/sdnr/wt-odlux/odlux/apps/maintenanceApp/.babelrc b/sdnr/wt-odlux/odlux/apps/maintenanceApp/.babelrc new file mode 100644 index 000000000..3d8cd1260 --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/maintenanceApp/.babelrc @@ -0,0 +1,17 @@ +{ + "presets": [ + ["@babel/preset-react"], + ["@babel/preset-env", { + "targets": { + "chrome": "66" + }, + "spec": true, + "loose": false, + "modules": false, + "debug": false, + "useBuiltIns": "usage", + "forceAllTransforms": true + }] + ], + "plugins": [] +} diff --git a/sdnr/wt-odlux/odlux/apps/maintenanceApp/package.json b/sdnr/wt-odlux/odlux/apps/maintenanceApp/package.json new file mode 100644 index 000000000..d7c325409 --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/maintenanceApp/package.json @@ -0,0 +1,46 @@ +{ + "name": "@odlux/maintenance-app", + "version": "0.1.0", + "description": "A react based modular UI for the maintenance app.", + "main": "index.js", + "scripts": { + "start": "webpack-dev-server --env debug", + "build": "webpack --env release --config webpack.config.js", + "build:dev": "webpack --env debug --config webpack.config.js" + }, + "repository": { + "type": "git", + "url": "https://git.mfico.de/highstreet-technologies/odlux.git" + }, + "keywords": [ + "reactjs", + "redux", + "ui", + "framework" + ], + "author": "Matthias Fischer", + "license": "Apache-2.0", + "dependencies": { + "@emotion/react": "^11.7.0", + "@emotion/styled": "^11.6.0", + "@mui/icons-material": "^5.2.0", + "@mui/material": "^5.2.2", + "@mui/styles": "^5.2.2", + "@odlux/connect-app": "*", + "@odlux/framework": "*" + }, + "peerDependencies": { + "@types/classnames": "2.2.6", + "@types/flux": "3.1.8", + "@types/jquery": "3.3.10", + "@types/react": "17.0.37", + "@types/react-dom": "17.0.11", + "@types/react-router-dom": "5.1.7", + "jquery": "3.3.1", + "react": "17.0.2", + "react-dom": "17.0.2", + "react-router-dom": "5.2.0", + "@fortawesome/free-solid-svg-icons": "5.6.3", + "@fortawesome/react-fontawesome": "0.1.14" + } +} diff --git a/sdnr/wt-odlux/odlux/apps/maintenanceApp/pom.xml b/sdnr/wt-odlux/odlux/apps/maintenanceApp/pom.xml new file mode 100644 index 000000000..03832f736 --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/maintenanceApp/pom.xml @@ -0,0 +1,109 @@ + + + + + 4.0.0 + + org.onap.ccsdk.features.sdnr.odlux + sdnr-odlux-app-maintenanceApp + 1.7.0-SNAPSHOT + jar + + SDNR ODLUX :: ${project.artifactId} + + + Apache License, Version 2.0 + http://www.apache.org/licenses/LICENSE-2.0 + + + + + true + + + + + + dist + odlux + + + + + maven-clean-plugin + + + + dist + false + + + node + false + + + node_modules + false + + + ../node_modules + false + + + + bin + false + + + + + + de.jacks-it-lab + frontend-maven-plugin + 1.7.2 + + + install node and yarn + + install-node-and-yarn + + + initialize + + v16.17.0 + v1.22.19 + + + + yarn build + + yarn + + + run build + + + + + + + diff --git a/sdnr/wt-odlux/odlux/apps/maintenanceApp/src/actions/maintenenceActions.ts b/sdnr/wt-odlux/odlux/apps/maintenanceApp/src/actions/maintenenceActions.ts new file mode 100644 index 000000000..740abff85 --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/maintenanceApp/src/actions/maintenenceActions.ts @@ -0,0 +1,77 @@ +/* eslint-disable @typescript-eslint/no-unused-expressions */ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * 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. + * ============LICENSE_END========================================================================== + */ +import { AddSnackbarNotification } from '../../../../framework/src/actions/snackbarActions'; +import { Action } from '../../../../framework/src/flux/action'; +import { Dispatch } from '../../../../framework/src/flux/store'; + +import { maintenanceEntriesReloadAction } from '../handlers/maintenanceEntriesHandler'; +import { MaintenanceEntry, spoofSymbol } from '../models/maintenanceEntryType'; +import { maintenenceService } from '../services/maintenenceService'; + +export class BaseAction extends Action { } + +export class LoadAllMainteneceEntriesAction extends BaseAction { } + +export class AllMainteneceEntriesLoadedAction extends BaseAction { + + constructor(public maintenenceEntries: MaintenanceEntry[] | null) { + super(); + + } +} + + +export class UpdateMaintenanceEntry extends BaseAction { + constructor(public maintenenceEntry: MaintenanceEntry) { + super(); + } +} + +/** Represents an async thunk action creator to add an element to the maintenence entries. */ +export const addOrUpdateMaintenenceEntryAsyncActionCreator = (entry: MaintenanceEntry) => (dispatch: Dispatch) => { + maintenenceService.writeMaintenenceEntry(entry).then(result => { + result && window.setTimeout(() => { + // dispatch(loadAllMountedNetworkElementsAsync); + dispatch(new UpdateMaintenanceEntry(entry)); + dispatch(new AddSnackbarNotification({ message: `Successfully ${result && result.created ? 'created' : 'updated'} maintenance settings for [${entry.nodeId}]`, options: { variant: 'success' } })); + }, 900); + dispatch(maintenanceEntriesReloadAction); + }); +}; + +/** Represents an async thunk action creator to delete an element from the maintenence entries. */ +export const removeFromMaintenenceEntrysAsyncActionCreator = (entry: MaintenanceEntry) => (dispatch: Dispatch) => { + maintenenceService.deleteMaintenenceEntry(entry).then(result => { + result && window.setTimeout(() => { + dispatch(new UpdateMaintenanceEntry({ + [spoofSymbol]: true, + mId: entry.mId, + nodeId: entry.nodeId, + description: '', + start: '', + end: '', + active: false, + })); + dispatch(new AddSnackbarNotification({ message: `Successfully removed [${entry.nodeId}]`, options: { variant: 'success' } })); + }, 900); + dispatch(maintenanceEntriesReloadAction); + }); +}; + +// Hint: since there is no notification of changed required network elements, this code is not aware of changes caused outiside of this browser. \ No newline at end of file diff --git a/sdnr/wt-odlux/odlux/apps/maintenanceApp/src/assets/icons/maintenanceAppIcon.svg b/sdnr/wt-odlux/odlux/apps/maintenanceApp/src/assets/icons/maintenanceAppIcon.svg new file mode 100644 index 000000000..8b99a5e7f --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/maintenanceApp/src/assets/icons/maintenanceAppIcon.svg @@ -0,0 +1,50 @@ + + + + + + + + + + + diff --git a/sdnr/wt-odlux/odlux/apps/maintenanceApp/src/components/editMaintenenceEntryDialog.tsx b/sdnr/wt-odlux/odlux/apps/maintenanceApp/src/components/editMaintenenceEntryDialog.tsx new file mode 100644 index 000000000..9ab147ca7 --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/maintenanceApp/src/components/editMaintenenceEntryDialog.tsx @@ -0,0 +1,207 @@ +/* eslint-disable @typescript-eslint/no-unused-expressions */ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * 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. + * ============LICENSE_END========================================================================== + */ +import * as React from 'react'; + +import Button from '@mui/material/Button'; +import Dialog from '@mui/material/Dialog'; +import DialogActions from '@mui/material/DialogActions'; +import DialogContent from '@mui/material/DialogContent'; +import DialogContentText from '@mui/material/DialogContentText'; +import DialogTitle from '@mui/material/DialogTitle'; +import TextField from '@mui/material/TextField'; + +import { connect, Connect, IDispatcher } from '../../../../framework/src/flux/connect'; + +import { FormControl, InputLabel, MenuItem, Select, Typography } from '@mui/material'; +import { + addOrUpdateMaintenenceEntryAsyncActionCreator, + removeFromMaintenenceEntrysAsyncActionCreator, +} from '../actions/maintenenceActions'; +import { MaintenanceEntry } from '../models/maintenanceEntryType'; + +export enum EditMaintenenceEntryDialogMode { + None = 'none', + AddMaintenenceEntry = 'addMaintenenceEntry', + EditMaintenenceEntry = 'editMaintenenceEntry', + RemoveMaintenenceEntry = 'removeMaintenenceEntry', +} + +const mapDispatch = (dispatcher: IDispatcher) => ({ + addOrUpdateMaintenenceEntry: (entry: MaintenanceEntry) => { + dispatcher.dispatch(addOrUpdateMaintenenceEntryAsyncActionCreator(entry)); + }, + removeMaintenenceEntry: (entry: MaintenanceEntry) => { + dispatcher.dispatch(removeFromMaintenenceEntrysAsyncActionCreator(entry)); + }, +}); + +type DialogSettings = { + dialogTitle: string; + dialogDescription: string; + applyButtonText: string; + cancelButtonText: string; + enableMountIdEditor: boolean; + enableTimeEditor: boolean; +}; + +const settings: { [key: string]: DialogSettings } = { + [EditMaintenenceEntryDialogMode.None]: { + dialogTitle: '', + dialogDescription: '', + applyButtonText: '', + cancelButtonText: '', + enableMountIdEditor: false, + enableTimeEditor: false, + }, + [EditMaintenenceEntryDialogMode.AddMaintenenceEntry]: { + dialogTitle: 'Add new maintenence entry', + dialogDescription: '', + applyButtonText: 'Add', + cancelButtonText: 'Cancel', + enableMountIdEditor: true, + enableTimeEditor: true, + }, + [EditMaintenenceEntryDialogMode.EditMaintenenceEntry]: { + dialogTitle: 'Edit maintenence entry', + dialogDescription: '', + applyButtonText: 'Save', + cancelButtonText: 'Cancel', + enableMountIdEditor: false, + enableTimeEditor: true, + }, + [EditMaintenenceEntryDialogMode.RemoveMaintenenceEntry]: { + dialogTitle: 'Remove maintenence entry', + dialogDescription: '', + applyButtonText: 'Remove', + cancelButtonText: 'Cancel', + enableMountIdEditor: false, + enableTimeEditor: false, + }, +}; + +type EditMaintenenceEntryDIalogComponentProps = Connect & { + mode: EditMaintenenceEntryDialogMode; + initialMaintenenceEntry: MaintenanceEntry; + onClose: () => void; +}; + +type EditMaintenenceEntryDIalogComponentState = MaintenanceEntry & { isErrorVisible: boolean }; + +class EditMaintenenceEntryDIalogComponent extends React.Component { + constructor(props: EditMaintenenceEntryDIalogComponentProps) { + super(props); + + this.state = { + ...this.props.initialMaintenenceEntry, + isErrorVisible: false, + }; + } + + render(): JSX.Element { + const setting = settings[this.props.mode]; + return ( + + {setting.dialogTitle} + + + {setting.dialogDescription} + + { this.setState({ nodeId: event.target.value }); }} /> + {this.state.isErrorVisible && Name must not be empty.} + { this.setState({ start: event.target.value }); }} /> + { this.setState({ end: event.target.value }); }} /> + + Active + + + + + + + + + ); + } + + private onApply = (entry: MaintenanceEntry) => { + this.props.onClose && this.props.onClose(); + switch (this.props.mode) { + case EditMaintenenceEntryDialogMode.AddMaintenenceEntry: + entry && this.props.addOrUpdateMaintenenceEntry(entry); + break; + case EditMaintenenceEntryDialogMode.EditMaintenenceEntry: + entry && this.props.addOrUpdateMaintenenceEntry(entry); + break; + case EditMaintenenceEntryDialogMode.RemoveMaintenenceEntry: + entry && this.props.removeMaintenenceEntry(entry); + break; + } + }; + + + private onCancel = () => { + this.props.onClose && this.props.onClose(); + }; + + static getDerivedStateFromProps(props: EditMaintenenceEntryDIalogComponentProps, state: EditMaintenenceEntryDIalogComponentState & { initialMaintenenceEntrie: MaintenanceEntry }): EditMaintenenceEntryDIalogComponentState & { initialMaintenenceEntrie: MaintenanceEntry } { + if (props.initialMaintenenceEntry !== state.initialMaintenenceEntrie) { + // eslint-disable-next-line no-param-reassign + state = { + ...state, + ...props.initialMaintenenceEntry, + initialMaintenenceEntrie: props.initialMaintenenceEntry, + }; + } + return state; + } + +} + +export const EditMaintenenceEntryDIalog = connect(undefined, mapDispatch)(EditMaintenenceEntryDIalogComponent); +export default EditMaintenenceEntryDIalog; \ No newline at end of file diff --git a/sdnr/wt-odlux/odlux/apps/maintenanceApp/src/components/refreshMaintenanceEntries.tsx b/sdnr/wt-odlux/odlux/apps/maintenanceApp/src/components/refreshMaintenanceEntries.tsx new file mode 100644 index 000000000..e8bd635dd --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/maintenanceApp/src/components/refreshMaintenanceEntries.tsx @@ -0,0 +1,113 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * 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. + * ============LICENSE_END========================================================================== + */ +import * as React from 'react'; + +import Button from '@mui/material/Button'; +import Dialog from '@mui/material/Dialog'; +import DialogActions from '@mui/material/DialogActions'; +import DialogContent from '@mui/material/DialogContent'; +import DialogContentText from '@mui/material/DialogContentText'; +import DialogTitle from '@mui/material/DialogTitle'; + +import { connect, Connect, IDispatcher } from '../../../../framework/src/flux/connect'; + +import { maintenanceEntriesReloadAction } from '../handlers/maintenanceEntriesHandler'; +import { MaintenanceEntry } from '../models/maintenanceEntryType'; + +export enum RefreshMaintenanceEntriesDialogMode { + None = 'none', + RefreshMaintenanceEntriesTable = 'RefreshMaintenanceEntriesTable', +} + +const mapDispatch = (dispatcher: IDispatcher) => ({ + refreshMaintenanceEntries: () => dispatcher.dispatch(maintenanceEntriesReloadAction), +}); + +type DialogSettings = { + dialogTitle: string; + dialogDescription: string; + applyButtonText: string; + cancelButtonText: string; + enableMountIdEditor: boolean; + enableUsernameEditor: boolean; + enableExtendedEditor: boolean; +}; + +const settings: { [key: string]: DialogSettings } = { + [RefreshMaintenanceEntriesDialogMode.None]: { + dialogTitle: '', + dialogDescription: '', + applyButtonText: '', + cancelButtonText: '', + enableMountIdEditor: false, + enableUsernameEditor: false, + enableExtendedEditor: false, + }, + [RefreshMaintenanceEntriesDialogMode.RefreshMaintenanceEntriesTable]: { + dialogTitle: 'Do you want to refresh Maintenance Entries?', + dialogDescription: '', + applyButtonText: 'Yes', + cancelButtonText: 'Cancel', + enableMountIdEditor: true, + enableUsernameEditor: true, + enableExtendedEditor: true, + }, +}; + +type RefreshMaintenanceEntriesDialogComponentProps = Connect & { + mode: RefreshMaintenanceEntriesDialogMode; + onClose: () => void; +}; + +type RefreshMaintenanceEntriesDialogComponentState = MaintenanceEntry & { isNameValid: boolean; isHostSet: boolean }; + +class RefreshMaintenanceEntriesDialogComponent extends React.Component { + render(): JSX.Element { + const setting = settings[this.props.mode]; + return ( + + {setting.dialogTitle} + + + {setting.dialogDescription} + + + + + + + + ); + } + + private onRefresh = () => { + this.props.refreshMaintenanceEntries(); + this.props.onClose(); + }; + + private onCancel = () => { + this.props.onClose(); + }; +} + +export const RefreshMaintenanceEntriesDialog = connect(undefined, mapDispatch)(RefreshMaintenanceEntriesDialogComponent); +export default RefreshMaintenanceEntriesDialog; \ No newline at end of file diff --git a/sdnr/wt-odlux/odlux/apps/maintenanceApp/src/handlers/maintenanceAppRootHandler.ts b/sdnr/wt-odlux/odlux/apps/maintenanceApp/src/handlers/maintenanceAppRootHandler.ts new file mode 100644 index 000000000..ced7f2160 --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/maintenanceApp/src/handlers/maintenanceAppRootHandler.ts @@ -0,0 +1,39 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * 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. + * ============LICENSE_END========================================================================== + */ +// main state handler + +import { combineActionHandler } from '../../../../framework/src/flux/middleware'; + +import { IMaintenanceEntriesState, maintenanceEntriesActionHandler } from './maintenanceEntriesHandler'; + +export interface IMaintenanceAppStoreState { + maintenanceEntries : IMaintenanceEntriesState; +} + +declare module '../../../../framework/src/store/applicationStore' { + interface IApplicationStoreState { + maintenance: IMaintenanceAppStoreState; + } +} + +const actionHandlers = { + maintenanceEntries: maintenanceEntriesActionHandler, +}; + +export const maintenanceAppRootHandler = combineActionHandler(actionHandlers); +export default maintenanceAppRootHandler; diff --git a/sdnr/wt-odlux/odlux/apps/maintenanceApp/src/handlers/maintenanceEntriesHandler.ts b/sdnr/wt-odlux/odlux/apps/maintenanceApp/src/handlers/maintenanceEntriesHandler.ts new file mode 100644 index 000000000..c3fe51e80 --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/maintenanceApp/src/handlers/maintenanceEntriesHandler.ts @@ -0,0 +1,35 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * 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. + * ============LICENSE_END========================================================================== + */ +import { createExternal, IExternalTableState } from '../../../../framework/src/components/material-table/utilities'; +import { createSearchDataHandler } from '../../../../framework/src/utilities/elasticSearch'; + +import { MaintenanceEntry } from '../models/maintenanceEntryType'; +export interface IMaintenanceEntriesState extends IExternalTableState { } + +// create elastic search material data fetch handler +const maintenanceEntriesSearchHandler = createSearchDataHandler('maintenance'); + +export const { + actionHandler: maintenanceEntriesActionHandler, + createActions: createmaintenanceEntriesActions, + createProperties: createmaintenanceEntriesProperties, + reloadAction: maintenanceEntriesReloadAction, + + // set value action, to change a value +} = createExternal(maintenanceEntriesSearchHandler, appState => appState.maintenance.maintenanceEntries); + diff --git a/sdnr/wt-odlux/odlux/apps/maintenanceApp/src/index.html b/sdnr/wt-odlux/odlux/apps/maintenanceApp/src/index.html new file mode 100644 index 000000000..c84aecee1 --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/maintenanceApp/src/index.html @@ -0,0 +1,26 @@ + + + + + + + + + Minimal App + + + +
+ + + + + + \ No newline at end of file diff --git a/sdnr/wt-odlux/odlux/apps/maintenanceApp/src/models/maintenanceEntryType.ts b/sdnr/wt-odlux/odlux/apps/maintenanceApp/src/models/maintenanceEntryType.ts new file mode 100644 index 000000000..27cdc8c12 --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/maintenanceApp/src/models/maintenanceEntryType.ts @@ -0,0 +1,33 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * 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. + * ============LICENSE_END========================================================================== + */ +/** Represents the elestic search db type for maintenence enrties */ + + +export const spoofSymbol = Symbol('Spoof'); + +/** Represents the type for an maintenence entry. */ +export type MaintenanceEntry = { + mId: string; + nodeId: string; + description?: string; + end: string; + start: string; + active: boolean; + [spoofSymbol]?: boolean; +}; + diff --git a/sdnr/wt-odlux/odlux/apps/maintenanceApp/src/pluginMaintenance.tsx b/sdnr/wt-odlux/odlux/apps/maintenanceApp/src/pluginMaintenance.tsx new file mode 100644 index 000000000..0f686cb84 --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/maintenanceApp/src/pluginMaintenance.tsx @@ -0,0 +1,44 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * 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. + * ============LICENSE_END========================================================================== + */ +// app configuration and main entry point for the app + +import React, { FC } from 'react'; + +import applicationManager from '../../../framework/src/services/applicationManager'; + +import { maintenanceAppRootHandler } from './handlers/maintenanceAppRootHandler'; + +import { MaintenanceView } from './views/maintenanceView'; + +const appIcon = require('./assets/icons/maintenanceAppIcon.svg'); // select app icon + +const App : FC = () => { + return ; +}; + +export function register() { + applicationManager.registerApplication({ + name: 'maintenance', + icon: appIcon, + rootComponent: App, + rootActionHandler: maintenanceAppRootHandler, + menuEntry: 'Maintenance', + }); +} + + diff --git a/sdnr/wt-odlux/odlux/apps/maintenanceApp/src/services/maintenenceService.ts b/sdnr/wt-odlux/odlux/apps/maintenanceApp/src/services/maintenenceService.ts new file mode 100644 index 000000000..5fdccc349 --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/maintenanceApp/src/services/maintenenceService.ts @@ -0,0 +1,72 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * 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. + * ============LICENSE_END========================================================================== + */ +import { DeleteResponse, PostResponse } from '../../../../framework/src/models/elasticSearch'; +import { requestRest } from '../../../../framework/src/services/restService'; +import { convertPropertyNames, replaceUpperCase } from '../../../../framework/src/utilities/yangHelper'; +import { MaintenanceEntry } from '../models/maintenanceEntryType'; + +import { convertToISODateString } from '../utils/timeUtils'; + + +export const maintenenceEntryDatabasePath = 'mwtn/maintenancemode'; + +/** + * Represents a web api accessor service for all maintenence entries related actions. + */ +class MaintenenceService { + + /** + * Adds or updates one maintenence entry to the backend. + */ + public async writeMaintenenceEntry(maintenenceEntry: MaintenanceEntry): Promise { + const path = '/rests/operations/data-provider:create-maintenance'; + + const query = { + 'id': maintenenceEntry.mId, + 'node-id': maintenenceEntry.nodeId, + 'active': maintenenceEntry.active, + 'description': maintenenceEntry.description, + 'end': convertToISODateString(maintenenceEntry.end), + 'start': convertToISODateString(maintenenceEntry.start), + }; + + const result = await requestRest(path, { method: 'POST', body: JSON.stringify(convertPropertyNames({ 'data-provider:input': query }, replaceUpperCase)) }); + return result || null; + } + + /** + * Deletes one maintenence entry by its mountId from the backend. + */ + public async deleteMaintenenceEntry(maintenenceEntry: MaintenanceEntry): Promise<(DeleteResponse) | null> { + const path = '/rests/operations/data-provider:delete-maintenance'; + + const query = { + 'id': maintenenceEntry.mId, + 'node-id': maintenenceEntry.nodeId, + 'active': maintenenceEntry.active, + 'description': maintenenceEntry.description, + 'end': convertToISODateString(maintenenceEntry.end), + 'start': convertToISODateString(maintenenceEntry.start), + }; + const result = await requestRest(path, { method: 'POST', body: JSON.stringify(convertPropertyNames({ 'data-provider:input': query }, replaceUpperCase)) }); + return result || null; + } +} + +export const maintenenceService = new MaintenenceService(); +export default maintenenceService; \ No newline at end of file diff --git a/sdnr/wt-odlux/odlux/apps/maintenanceApp/src/utils/timeUtils.ts b/sdnr/wt-odlux/odlux/apps/maintenanceApp/src/utils/timeUtils.ts new file mode 100644 index 000000000..0fde5fcad --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/maintenanceApp/src/utils/timeUtils.ts @@ -0,0 +1,45 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * 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. + * ============LICENSE_END========================================================================== + */ +export function convertToGMTString(dateString: string): string { + const date = new Date(dateString); + const pad = (n: number) => (n < 10) ? '0' + n : n; + + return date.getUTCFullYear() + + '-' + pad(date.getUTCMonth() + 1) + + '-' + pad(date.getUTCDate()) + + 'T' + pad(date.getUTCHours()) + + ':' + pad(date.getUTCMinutes()) + + '+00:00'; +} + +export function convertToLocaleString(rawDate: string | number): string { + const date = new Date(rawDate); + const pad = (n: number) => (n < 10) ? '0' + n : n; + + return date.getFullYear() + + '-' + pad(date.getMonth() + 1) + + '-' + pad(date.getDate()) + + 'T' + pad(date.getHours()) + + ':' + pad(date.getMinutes()); +} + +export function convertToISODateString(rawDate: string | number): string { + const date = new Date(rawDate); + const displayDate = date.toISOString(); + return displayDate.replace(/\.[0-9]{2}/, '.'); +} \ No newline at end of file diff --git a/sdnr/wt-odlux/odlux/apps/maintenanceApp/src/views/maintenanceView.tsx b/sdnr/wt-odlux/odlux/apps/maintenanceApp/src/views/maintenanceView.tsx new file mode 100644 index 000000000..d54d63c04 --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/maintenanceApp/src/views/maintenanceView.tsx @@ -0,0 +1,246 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * 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. + * ============LICENSE_END========================================================================== + */ +import React from 'react'; + +import { faBan } from '@fortawesome/free-solid-svg-icons'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import AddIcon from '@mui/icons-material/Add'; +import EditIcon from '@mui/icons-material/Edit'; +import Refresh from '@mui/icons-material/Refresh'; +import RemoveIcon from '@mui/icons-material/RemoveCircleOutline'; +import { Divider, MenuItem, Theme, Typography } from '@mui/material'; +import { WithStyles } from '@mui/styles'; +import createStyles from '@mui/styles/createStyles'; +import withStyles from '@mui/styles/withStyles'; + +import MaterialTable, { ColumnType, MaterialTableCtorType } from '../../../../framework/src/components/material-table'; +import { connect, Connect, IDispatcher } from '../../../../framework/src/flux/connect'; +import { IApplicationStoreState } from '../../../../framework/src/store/applicationStore'; + +import EditMaintenenceEntryDialog, { EditMaintenenceEntryDialogMode } from '../components/editMaintenenceEntryDialog'; +import RefreshMaintenanceEntriesDialog, { RefreshMaintenanceEntriesDialogMode } from '../components/refreshMaintenanceEntries'; +import { createmaintenanceEntriesActions, createmaintenanceEntriesProperties, maintenanceEntriesReloadAction } from '../handlers/maintenanceEntriesHandler'; +import { MaintenanceEntry } from '../models/maintenanceEntryType'; +import { convertToLocaleString } from '../utils/timeUtils'; + +const styles = (theme: Theme) => createStyles({ + button: { + margin: 0, + padding: '6px 6px', + minWidth: 'unset', + }, + spacer: { + marginLeft: theme.spacing(1), + marginRight: theme.spacing(1), + display: 'inline', + }, +}); + +const MaintenanceEntriesTable = MaterialTable as MaterialTableCtorType; + +const mapProps = (state: IApplicationStoreState) => ({ + maintenanceEntriesProperties: createmaintenanceEntriesProperties(state), +}); + +const mapDispatcher = (dispatcher: IDispatcher) => ({ + maintenanceEntriesActions: createmaintenanceEntriesActions(dispatcher.dispatch), + onLoadMaintenanceEntries: async () => { + await dispatcher.dispatch(maintenanceEntriesReloadAction); + }, +}); + +const emptyMaintenenceEntry: MaintenanceEntry = { + mId: '', + nodeId: '', + description: '', + start: convertToLocaleString(new Date().valueOf()), + end: convertToLocaleString(new Date().valueOf()), + active: false, +}; + +type MaintenanceViewComponentProps = Connect & WithStyles & {}; + +type MaintenenceViewComponentState = { + maintenenceEntryToEdit: MaintenanceEntry; + maintenanceEntryEditorMode: EditMaintenenceEntryDialogMode; + refreshMaintenenceEntriesEditorMode: RefreshMaintenanceEntriesDialogMode; +}; + +let initialSorted = false; + +class MaintenenceViewComponent extends React.Component { + + constructor(props: MaintenanceViewComponentProps) { + super(props); + + this.state = { + maintenenceEntryToEdit: emptyMaintenenceEntry, + maintenanceEntryEditorMode: EditMaintenenceEntryDialogMode.None, + refreshMaintenenceEntriesEditorMode: RefreshMaintenanceEntriesDialogMode.None, + }; + + } + + getContextMenu(rowData: MaintenanceEntry): JSX.Element[] { + let buttonArray = [ + this.onOpenPlus1hEditMaintenenceEntryDialog(event, rowData)}>+1h, + this.onOpenPlus8hEditMaintenenceEntryDialog(event, rowData)}>+8h, + , + this.onOpenEditMaintenenceEntryDialog(event, rowData)}>Edit, + this.onOpenRemoveMaintenenceEntryDialog(event, rowData)}>Remove, + ]; + return buttonArray; + } + + render() { + const addMaintenenceEntryAction = { + icon: AddIcon, tooltip: 'Add', ariaLabel:'add-element', onClick: () => { + const startTime = (new Date().valueOf()); + const endTime = startTime; + this.setState({ + maintenenceEntryToEdit: { + ...emptyMaintenenceEntry, + start: convertToLocaleString(startTime), + end: convertToLocaleString(endTime), + }, + maintenanceEntryEditorMode: EditMaintenenceEntryDialogMode.AddMaintenenceEntry, + }); + }, + }; + + const refreshMaintenanceEntriesAction = { + icon: Refresh, tooltip: 'Refresh Maintenance Entries', ariaLabel: 'refresh', onClick: () => { + this.setState({ + refreshMaintenenceEntriesEditorMode: RefreshMaintenanceEntriesDialogMode.RefreshMaintenanceEntriesTable, + }); + }, + }; + + const now = new Date().valueOf(); + return ( + <> + ( + rowData.active && (Date.parse(rowData.start).valueOf() <= now) && (Date.parse(rowData.end).valueOf() >= now) && || null + ), + }, + { property: 'active', title: 'Activation State', type: ColumnType.boolean, labels: { 'true': 'active', 'false': 'not active' } }, + { property: 'start', title: 'Start Date (UTC)', type: ColumnType.text }, + { property: 'end', title: 'End Date (UTC)', type: ColumnType.text }, + ] + } idProperty={'mId'}{...this.props.maintenanceEntriesActions} {...this.props.maintenanceEntriesProperties} asynchronus createContextMenu={rowData => { + return this.getContextMenu(rowData); + }} > + + + + + ); + } + + public componentDidMount() { + + if (!initialSorted) { + initialSorted = true; + this.props.maintenanceEntriesActions.onHandleRequestSort('node-id'); + } else { + this.props.onLoadMaintenanceEntries(); + } + + + } + + private onOpenPlus1hEditMaintenenceEntryDialog = (event: React.MouseEvent, entry: MaintenanceEntry) => { + // event.preventDefault(); + // event.stopPropagation(); + const startTime = (new Date().valueOf()); + const endTime = startTime + (1 * 60 * 60 * 1000); + this.setState({ + maintenenceEntryToEdit: { + ...entry, + start: convertToLocaleString(startTime), + end: convertToLocaleString(endTime), + }, + maintenanceEntryEditorMode: EditMaintenenceEntryDialogMode.EditMaintenenceEntry, + }); + }; + + private onOpenPlus8hEditMaintenenceEntryDialog = (event: React.MouseEvent, entry: MaintenanceEntry) => { + // event.preventDefault(); + // event.stopPropagation(); + const startTime = (new Date().valueOf()); + const endTime = startTime + (8 * 60 * 60 * 1000); + this.setState({ + maintenenceEntryToEdit: { + ...entry, + start: convertToLocaleString(startTime), + end: convertToLocaleString(endTime), + }, + maintenanceEntryEditorMode: EditMaintenenceEntryDialogMode.EditMaintenenceEntry, + }); + }; + + private onOpenEditMaintenenceEntryDialog = (event: React.MouseEvent, entry: MaintenanceEntry) => { + // event.preventDefault(); + // event.stopPropagation(); + const startTime = (new Date().valueOf()); + const endTime = startTime; + this.setState({ + maintenenceEntryToEdit: { + ...entry, + ...(entry.start && endTime ? { start: convertToLocaleString(entry.start), end: convertToLocaleString(entry.end) } : { start: convertToLocaleString(startTime), end: convertToLocaleString(endTime) }), + }, + maintenanceEntryEditorMode: EditMaintenenceEntryDialogMode.EditMaintenenceEntry, + }); + }; + + private onOpenRemoveMaintenenceEntryDialog = (event: React.MouseEvent, entry: MaintenanceEntry) => { + // event.preventDefault(); + // event.stopPropagation(); + const startTime = (new Date().valueOf()); + const endTime = startTime; + this.setState({ + maintenenceEntryToEdit: { + ...entry, + ...(entry.start && endTime ? { start: convertToLocaleString(entry.start), end: convertToLocaleString(entry.end) } : { start: convertToLocaleString(startTime), end: convertToLocaleString(endTime) }), + }, + maintenanceEntryEditorMode: EditMaintenenceEntryDialogMode.RemoveMaintenenceEntry, + }); + }; + + private onCloseEditMaintenenceEntryDialog = () => { + this.setState({ + maintenenceEntryToEdit: emptyMaintenenceEntry, + maintenanceEntryEditorMode: EditMaintenenceEntryDialogMode.None, + }); + }; + + private onCloseRefreshMaintenenceEntryDialog = () => { + this.setState({ + refreshMaintenenceEntriesEditorMode: RefreshMaintenanceEntriesDialogMode.None, + }); + }; +} + +export const MaintenanceView = withStyles(styles)(connect(mapProps, mapDispatcher)(MaintenenceViewComponent)); + diff --git a/sdnr/wt-odlux/odlux/apps/maintenanceApp/tsconfig.json b/sdnr/wt-odlux/odlux/apps/maintenanceApp/tsconfig.json new file mode 100644 index 000000000..ca65092e0 --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/maintenanceApp/tsconfig.json @@ -0,0 +1,37 @@ +{ + "compilerOptions": { + "baseUrl": "./src", + "outDir": "./dist", + "sourceMap": true, + "forceConsistentCasingInFileNames": true, + "allowSyntheticDefaultImports": true, + "allowUnreachableCode": false, + "allowUnusedLabels": false, + "noFallthroughCasesInSwitch": true, + "noImplicitAny": true, + "noImplicitReturns": true, + "noImplicitThis": true, + "strictNullChecks": true, + "pretty": true, + "newLine": "LF", + "module": "es2015", + "target": "es2016", + "moduleResolution": "node", + "experimentalDecorators": true, + "jsx": "preserve", + "lib": [ + "dom", + "es2015", + "es2016" + ], + "types": [ + "prop-types", + "react", + "react-dom" + ] + }, + "exclude": [ + "dist", + "node_modules" + ] +} diff --git a/sdnr/wt-odlux/odlux/apps/maintenanceApp/webpack.config.js b/sdnr/wt-odlux/odlux/apps/maintenanceApp/webpack.config.js new file mode 100644 index 000000000..845fc43a6 --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/maintenanceApp/webpack.config.js @@ -0,0 +1,168 @@ +/** + * Webpack 4 configuration file + * see https://webpack.js.org/configuration/ + * see https://webpack.js.org/configuration/dev-server/ + */ + +"use strict"; + +const path = require("path"); +const webpack = require("webpack"); +const CopyWebpackPlugin = require("copy-webpack-plugin"); +const TerserPlugin = require('terser-webpack-plugin'); + +// const __dirname = (path => path.replace(/^([a-z]\:)/, c => c.toUpperCase()))(process.__dirname()); + +module.exports = (env) => { + const distPath = path.resolve(__dirname, env === "release" ? "." : "../..", "dist"); + const frameworkPath = path.resolve(__dirname, env === "release" ? "../../framework" : "../..", "dist"); + return [{ + name: "App", + + mode: "none", //disable default behavior + + target: "web", + + context: path.resolve(__dirname, "src"), + + entry: { + maintenanceApp: ["./pluginMaintenance.tsx"] + }, + + devtool: env === "release" ? false : "source-map", + + resolve: { + extensions: [".ts", ".tsx", ".js", ".jsx"] + }, + + output: { + path: distPath, + filename: "[name].js", + library: "[name]", + libraryTarget: "umd2", + chunkFilename: "[name].js" + }, + module: { + rules: [{ + test: /\.tsx?$/, + exclude: /node_modules/, + use: [{ + loader: "babel-loader" + }, { + loader: "ts-loader" + }] + }, { + test: /\.jsx?$/, + exclude: /node_modules/, + use: [{ + loader: "babel-loader" + }] + }, { + //don't minify images + test: /\.(png|gif|jpg|svg)$/, + use: [{ + loader: 'url-loader', + options: { + limit: 10, + name: './images/[name].[ext]' + } + }] + }] + }, + + optimization: { + noEmitOnErrors: true, + namedModules: env !== "release", + minimize: env === "release", + minimizer: env !== "release" ? [] : [new TerserPlugin({ + terserOptions: { + warnings: false, // false, true, "verbose" + compress: { + drop_console: true, + drop_debugger: true, + } + } + })], + }, + + plugins: [ + new webpack.DllReferencePlugin({ + context: path.resolve(__dirname, "../../framework/src"), + manifest: require(path.resolve(frameworkPath, "vendor-manifest.json")), + sourceType: "umd2" + }), + new webpack.DllReferencePlugin({ + context: path.resolve(__dirname, "../../framework/src"), + manifest: require(path.resolve(frameworkPath, "app-manifest.json")), + sourceType: "umd2" + }), + ...(env === "release" ? [ + new webpack.DefinePlugin({ + "process.env": { + NODE_ENV: "'production'", + VERSION: JSON.stringify(require("./package.json").version) + } + }), + ] : [ + new webpack.DefinePlugin({ + "process.env": { + NODE_ENV: "'development'", + VERSION: JSON.stringify(require("./package.json").version) + } + }), + new CopyWebpackPlugin([{ + from: 'index.html', + to: distPath + }]), + ]) + ], + + devServer: { + public: "http://localhost:3100", + contentBase: frameworkPath, + + compress: true, + headers: { + "Access-Control-Allow-Origin": "*" + }, + host: "0.0.0.0", + port: 3100, + disableHostCheck: true, + historyApiFallback: true, + inline: true, + hot: false, + quiet: false, + stats: { + colors: true + }, + proxy: { + "/oauth2/": { + target: "http://10.20.6.29:48181", + secure: false + }, + "/database/": { + target: "http://10.20.6.29:48181", + secure: false + }, + "/restconf/": { + target: "http://10.20.6.29:48181", + secure: false + }, + "/rests/": { + target: "http://10.20.6.29:48181", + secure: false + }, + "/help/": { + target: "http://10.20.6.29:48181", + secure: false + }, + "/websocket": { + target: "http://10.20.6.29:48181", + ws: true, + changeOrigin: true, + secure: false + } + } + } + }]; +} diff --git a/sdnr/wt-odlux/odlux/apps/mediatorApp/.babelrc b/sdnr/wt-odlux/odlux/apps/mediatorApp/.babelrc new file mode 100644 index 000000000..3d8cd1260 --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/mediatorApp/.babelrc @@ -0,0 +1,17 @@ +{ + "presets": [ + ["@babel/preset-react"], + ["@babel/preset-env", { + "targets": { + "chrome": "66" + }, + "spec": true, + "loose": false, + "modules": false, + "debug": false, + "useBuiltIns": "usage", + "forceAllTransforms": true + }] + ], + "plugins": [] +} diff --git a/sdnr/wt-odlux/odlux/apps/mediatorApp/package.json b/sdnr/wt-odlux/odlux/apps/mediatorApp/package.json new file mode 100644 index 000000000..867a8790d --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/mediatorApp/package.json @@ -0,0 +1,44 @@ +{ + "name": "@odlux/mediator-app", + "version": "0.1.0", + "description": "A react based modular UI to demo the mediator possible app.", + "main": "index.js", + "scripts": { + "start": "webpack-dev-server --env debug", + "build": "webpack --env release --config webpack.config.js", + "build:dev": "webpack --env debug --config webpack.config.js" + }, + "repository": { + "type": "git", + "url": "https://git.mfico.de/highstreet-technologies/odlux.git" + }, + "keywords": [ + "reactjs", + "redux", + "ui", + "framework" + ], + "author": "Matthias Fischer", + "license": "Apache-2.0", + "dependencies": { + "@emotion/react": "^11.7.0", + "@emotion/styled": "^11.6.0", + "@mui/icons-material": "^5.2.0", + "@mui/material": "^5.2.2", + "@mui/styles": "^5.2.2", + "@odlux/framework": "*" + }, + "peerDependencies": { + "@fortawesome/free-solid-svg-icons": "5.6.3", + "@types/classnames": "2.2.6", + "@types/flux": "3.1.8", + "@types/jquery": "3.3.10", + "@types/react": "17.0.37", + "@types/react-dom": "17.0.11", + "@types/react-router-dom": "5.1.7", + "jquery": "3.3.1", + "react": "17.0.2", + "react-dom": "17.0.2", + "react-router-dom": "5.2.0" + } +} diff --git a/sdnr/wt-odlux/odlux/apps/mediatorApp/pom.xml b/sdnr/wt-odlux/odlux/apps/mediatorApp/pom.xml new file mode 100644 index 000000000..547c91e92 --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/mediatorApp/pom.xml @@ -0,0 +1,105 @@ + + + + + 4.0.0 + + org.onap.ccsdk.features.sdnr.odlux + sdnr-odlux-app-mediatorApp + 1.7.0-SNAPSHOT + jar + + SDNR ODLUX :: ${project.artifactId} + + + Apache License, Version 2.0 + http://www.apache.org/licenses/LICENSE-2.0 + + + + + + + dist + odlux + + + + + maven-clean-plugin + + + + dist + false + + + node + false + + + node_modules + false + + + ../node_modules + false + + + + bin + false + + + + + + de.jacks-it-lab + frontend-maven-plugin + 1.7.2 + + + install node and yarn + + install-node-and-yarn + + + initialize + + v16.17.0 + v1.22.19 + + + + yarn build + + yarn + + + run build + + + + + + + diff --git a/sdnr/wt-odlux/odlux/apps/mediatorApp/src/actions/avaliableMediatorServersActions.ts b/sdnr/wt-odlux/odlux/apps/mediatorApp/src/actions/avaliableMediatorServersActions.ts new file mode 100644 index 000000000..3f56b05e1 --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/mediatorApp/src/actions/avaliableMediatorServersActions.ts @@ -0,0 +1,58 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * 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. + * ============LICENSE_END========================================================================== + */ +import { Action } from '../../../../framework/src/flux/action'; +import { Dispatch } from '../../../../framework/src/flux/store'; +import { AddSnackbarNotification } from '../../../../framework/src/actions/snackbarActions'; + +import { MediatorServer } from '../models/mediatorServer'; +import { avaliableMediatorServersReloadAction } from '../handlers/avaliableMediatorServersHandler'; +import mediatorService from '../services/mediatorService'; + +/** Represents the base action. */ +export class BaseAction extends Action { } + +/** Represents an async thunk action that will add a server to the avaliable mediator servers. */ +export const addAvaliableMediatorServerAsyncActionCreator = (server: MediatorServer) => (dispatch: Dispatch) => { + mediatorService.insertMediatorServer(server).then(_ => { + window.setTimeout(() => { + dispatch(avaliableMediatorServersReloadAction); + dispatch(new AddSnackbarNotification({ message: `Successfully added [${ server.name }]`, options: { variant: 'success' } })); + }, 900); + }); + }; + + /** Represents an async thunk action that will add a server to the avaliable mediator servers. */ +export const updateAvaliableMediatorServerAsyncActionCreator = (server: MediatorServer) => (dispatch: Dispatch) => { + mediatorService.updateMediatorServer(server).then(_ => { + window.setTimeout(() => { + dispatch(avaliableMediatorServersReloadAction); + dispatch(new AddSnackbarNotification({ message: `Successfully updated [${ server.name }]`, options: { variant: 'success' } })); + }, 900); + }); +}; + + /** Represents an async thunk action that will delete a server from the avaliable mediator servers. */ + export const removeAvaliableMediatorServerAsyncActionCreator = (server: MediatorServer) => (dispatch: Dispatch) => { + mediatorService.deleteMediatorServer(server).then(_ => { + window.setTimeout(() => { + dispatch(avaliableMediatorServersReloadAction); + dispatch(new AddSnackbarNotification({ message: `Successfully removed [${ server.name }]`, options: { variant: 'success' } })); + }, 900); + }); + }; + \ No newline at end of file diff --git a/sdnr/wt-odlux/odlux/apps/mediatorApp/src/actions/mediatorConfigActions.ts b/sdnr/wt-odlux/odlux/apps/mediatorApp/src/actions/mediatorConfigActions.ts new file mode 100644 index 000000000..516515ab2 --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/mediatorApp/src/actions/mediatorConfigActions.ts @@ -0,0 +1,154 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * 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. + * ============LICENSE_END========================================================================== + */ + +import { Action } from '../../../../framework/src/flux/action'; +import { Dispatch } from '../../../../framework/src/flux/store'; + +import { AddSnackbarNotification } from '../../../../framework/src/actions/snackbarActions'; +import { IApplicationStoreState } from '../../../../framework/src/store/applicationStore'; + +import mediatorService from '../services/mediatorService'; +import { MediatorConfig, MediatorConfigResponse } from '../models/mediatorServer'; + +/** Represents the base action. */ +export class BaseAction extends Action { } + +export class SetMediatorBusyByName extends BaseAction { + constructor(public name: string, public isBusy: boolean) { + super(); + } +} + +export class AddMediatorConfig extends BaseAction { + constructor(public mediatorConfig: MediatorConfigResponse) { + super(); + } +} + +export class UpdateMediatorConfig extends BaseAction { + constructor(public name: string, public mediatorConfig: MediatorConfigResponse) { + super(); + } +} + +export class RemoveMediatorConfig extends BaseAction { + constructor(public name: string) { + super(); + } +} + + +export const startMediatorByNameAsyncActionCreator = (name: string) => (dispatch: Dispatch, getState: () => IApplicationStoreState) => { + dispatch(new SetMediatorBusyByName(name, true)); + const { mediator: { mediatorServerState: { id } } } = getState(); + if (id) { + mediatorService.startMediatorByName(id, name).then(msg => { + dispatch(new AddSnackbarNotification({ message: msg + ' ' + name, options: { variant: 'info' } })); + // since there is no notification, a timeout will be need here + window.setTimeout(() => { + mediatorService.getMediatorServerConfigByName(id, name).then(config => { + if (config) { + dispatch(new UpdateMediatorConfig(name, config)); + } else { + dispatch(new AddSnackbarNotification({ message: `Error: reading mediator config for ${name}.`, options: { variant: 'error' } })); + } + dispatch(new SetMediatorBusyByName(name, false)); + }); + }, 2100); + }); + } else { + dispatch(new AddSnackbarNotification({ message: `Error: currently no mediator server selected.`, options: { variant: 'error' } })); + dispatch(new SetMediatorBusyByName(name, false)); + } +}; + +export const stopMediatorByNameAsyncActionCreator = (name: string) => (dispatch: Dispatch, getState: () => IApplicationStoreState) => { + dispatch(new SetMediatorBusyByName(name, true)); + const { mediator: { mediatorServerState: { id } } } = getState(); + if (id) { + mediatorService.stopMediatorByName(id, name).then(msg => { + dispatch(new AddSnackbarNotification({ message: msg + ' ' + name, options: { variant: 'info' } })); + // since there is no notification, a timeout will be need here + window.setTimeout(() => { + mediatorService.getMediatorServerConfigByName(id, name).then(config => { + if (config) { + dispatch(new UpdateMediatorConfig(name, config)); + } else { + dispatch(new AddSnackbarNotification({ message: `Error: reading mediator config for ${name}.`, options: { variant: 'error' } })); + } + dispatch(new SetMediatorBusyByName(name, false)); + }); + }, 2100); + }); + } else { + dispatch(new AddSnackbarNotification({ message: `Error: currently no mediator server selected.`, options: { variant: 'error' } })); + dispatch(new SetMediatorBusyByName(name, false)); + } +}; + +export const addMediatorConfigAsyncActionCreator = (config: MediatorConfig) => (dispatch: Dispatch, getState: () => IApplicationStoreState) => { + const { Name: name } = config; + const { mediator: { mediatorServerState: { id } } } = getState(); + if (id) { + mediatorService.createMediatorConfig(id, config).then(msg => { + dispatch(new AddSnackbarNotification({ message: msg + ' ' + name, options: { variant: 'info' } })); + // since there is no notification, a timeout will be need here + window.setTimeout(() => { + mediatorService.getMediatorServerConfigByName(id, name).then(config => { + if (config) { + dispatch(new AddMediatorConfig(config)); + } else { + dispatch(new AddSnackbarNotification({ message: `Error: reading mediator config for ${name}.`, options: { variant: 'error' } })); + } + }); + }, 2100); + }); + } else { + dispatch(new AddSnackbarNotification({ message: `Error: currently no mediator server selected.`, options: { variant: 'error' } })); + } +}; + +export const updateMediatorConfigAsyncActionCreator = (config: MediatorConfig) => (dispatch: Dispatch) => { + // currently not supported be backend +}; + +export const removeMediatorConfigAsyncActionCreator = (config: MediatorConfig) => (dispatch: Dispatch, getState: () => IApplicationStoreState) => { + const { Name: name } = config; + const { mediator: { mediatorServerState: { id } } } = getState(); + if (id) { + mediatorService.deleteMediatorConfigByName(id, name).then(msg => { + dispatch(new AddSnackbarNotification({ message: msg + ' ' + name, options: { variant: 'info' } })); + // since there is no notification, a timeout will be need here + window.setTimeout(() => { + mediatorService.getMediatorServerConfigByName(id, config.Name).then(config => { + if (!config) { + dispatch(new RemoveMediatorConfig(name)); + } else { + dispatch(new AddSnackbarNotification({ message: `Error: deleting mediator config for ${name}.`, options: { variant: 'error' } })); + } + }); + }, 2100); + }); + } else { + dispatch(new AddSnackbarNotification({ message: `Error: currently no mediator server selected.`, options: { variant: 'error' } })); + dispatch(new SetMediatorBusyByName(name, false)); + } +}; + + + diff --git a/sdnr/wt-odlux/odlux/apps/mediatorApp/src/actions/mediatorServerActions.ts b/sdnr/wt-odlux/odlux/apps/mediatorApp/src/actions/mediatorServerActions.ts new file mode 100644 index 000000000..79e46df0e --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/mediatorApp/src/actions/mediatorServerActions.ts @@ -0,0 +1,114 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * 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. + * ============LICENSE_END========================================================================== + */ +import { Action } from '../../../../framework/src/flux/action'; +import { Dispatch } from '../../../../framework/src/flux/store'; + +import { MediatorServerVersionInfo, MediatorConfig, MediatorConfigResponse, MediatorServerDevice } from '../models/mediatorServer'; +import mediatorService from '../services/mediatorService'; +import { AddSnackbarNotification } from '../../../../framework/src/actions/snackbarActions'; +import { NavigateToApplication } from '../../../../framework/src/actions/navigationActions'; +import { IApplicationStoreState } from '../../../../framework/src/store/applicationStore'; + +/** Represents the base action. */ +export class BaseAction extends Action { } + +export class SetMediatorServerBusy extends BaseAction { + constructor(public isBusy: boolean) { + super(); + } +} + +export class SetMediatorServerInfo extends BaseAction { + /** + * Initializes a new instance of this class. + */ + constructor(public id: string | null, public name: string | null, public url: string | null) { + super(); + + } +} + +export class SetMediatorServerVersion extends BaseAction { + /** + * Initializes a new instance of this class. + */ + constructor(public versionInfo: MediatorServerVersionInfo | null) { + super(); + + } +} + +export class SetAllMediatorServerConfigurations extends BaseAction { + /** + * Initializes a new instance of this class. + */ + constructor(public allConfigurations: MediatorConfigResponse[] | null) { + super(); + + } +} + +export class SetMediatorServerSupportedDevices extends BaseAction { + /** + * Initializes a new instance of this class. + */ + constructor(public devices: MediatorServerDevice[] | null) { + super(); + + } +} + +export class SetMediatorServerReachable extends BaseAction { + constructor(public isReachable: boolean) { + super(); + } +} + +export const initializeMediatorServerAsyncActionCreator = (serverId: string) => (dispatch: Dispatch) => { + dispatch(new SetMediatorServerBusy(true)); + mediatorService.getMediatorServerById(serverId).then(mediatorServer => { + if (!mediatorServer) { + dispatch(new SetMediatorServerBusy(false)); + dispatch(new AddSnackbarNotification({ message: `Error loading mediator server [${serverId}]`, options: { variant: 'error' } })); + dispatch(new NavigateToApplication("mediator")); + return; + } + + dispatch(new SetMediatorServerInfo(mediatorServer.id, mediatorServer.name, mediatorServer.url)); + + Promise.all([ + mediatorService.getMediatorServerAllConfigs(mediatorServer.id), + mediatorService.getMediatorServerSupportedDevices(mediatorServer.id), + mediatorService.getMediatorServerVersion(mediatorServer.id) + ]).then(([configurations, supportedDevices, versionInfo]) => { + if (configurations === null && supportedDevices === null && versionInfo === null) { + dispatch(new SetMediatorServerReachable(false)); + } else { + dispatch(new SetMediatorServerReachable(true)); + } + dispatch(new SetAllMediatorServerConfigurations(configurations)); + dispatch(new SetMediatorServerSupportedDevices(supportedDevices)); + dispatch(new SetMediatorServerVersion(versionInfo)); + dispatch(new SetMediatorServerBusy(false)); + }).catch(error => { + dispatch(new SetMediatorServerReachable(false)); + dispatch(new SetMediatorServerBusy(false)); + }); + }); +}; + diff --git a/sdnr/wt-odlux/odlux/apps/mediatorApp/src/assets/icons/mediatorAppIcon.svg b/sdnr/wt-odlux/odlux/apps/mediatorApp/src/assets/icons/mediatorAppIcon.svg new file mode 100644 index 000000000..b819aa610 --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/mediatorApp/src/assets/icons/mediatorAppIcon.svg @@ -0,0 +1,49 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/sdnr/wt-odlux/odlux/apps/mediatorApp/src/components/editMediatorConfigDialog.tsx b/sdnr/wt-odlux/odlux/apps/mediatorApp/src/components/editMediatorConfigDialog.tsx new file mode 100644 index 000000000..34ffc5e20 --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/mediatorApp/src/components/editMediatorConfigDialog.tsx @@ -0,0 +1,399 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * 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. + * ============LICENSE_END========================================================================== + */ +import * as React from 'react'; +import { Theme, Typography, FormControlLabel, Checkbox } from '@mui/material'; + +import { WithStyles } from '@mui/styles'; +import createStyles from '@mui/styles/createStyles'; +import withStyles from '@mui/styles/withStyles'; + +import Button from '@mui/material/Button'; +import TextField from '@mui/material/TextField'; +import Select from '@mui/material/Select'; +import Dialog from '@mui/material/Dialog'; +import DialogActions from '@mui/material/DialogActions'; +import DialogContent from '@mui/material/DialogContent'; +import DialogContentText from '@mui/material/DialogContentText'; +import DialogTitle from '@mui/material/DialogTitle'; + +import Tabs from '@mui/material/Tabs'; +import Tab from '@mui/material/Tab'; + +import Fab from '@mui/material/Fab'; +import AddIcon from '@mui/icons-material/Add'; +import DeleteIcon from '@mui/icons-material/Delete'; +import IconButton from '@mui/material/IconButton'; + +import { addMediatorConfigAsyncActionCreator, updateMediatorConfigAsyncActionCreator, removeMediatorConfigAsyncActionCreator } from '../actions/mediatorConfigActions'; +import { MediatorConfig, ODLConfig } from '../models/mediatorServer'; +import FormControl from '@mui/material/FormControl'; +import InputLabel from '@mui/material/InputLabel'; +import MenuItem from '@mui/material/MenuItem'; + +import { Panel } from '../../../../framework/src/components/material-ui/panel'; + +import { IDispatcher, connect, Connect } from '../../../../framework/src/flux/connect'; +import { IApplicationStoreState } from '../../../../framework/src/store/applicationStore'; + + +const styles = (theme: Theme) => createStyles({ + root: { + display: 'flex', + flexDirection: 'column', + flex: '1', + }, + fab: { + position: 'absolute', + bottom: theme.spacing(1), + right: theme.spacing(1), + }, + title: { + fontSize: 14, + }, + center: { + flex: "1", + display: "flex", + alignItems: "center", + justifyContent: "center", + }, + alignInOneLine: { + display: 'flex', + flexDirection: 'row' + }, + left: { + marginRight: theme.spacing(1), + }, + right: { + marginLeft: 0, + } +}); + +const TabContainer: React.SFC = ({ children }) => { + return ( +
+ {children} +
+ ); +} + +export enum EditMediatorConfigDialogMode { + None = "none", + AddMediatorConfig = "addMediatorConfig", + EditMediatorConfig = "editMediatorConfig", + RemoveMediatorConfig = "removeMediatorConfig", +} + +const mapProps = (state: IApplicationStoreState) => ({ + supportedDevices: state.mediator.mediatorServerState.supportedDevices +}); + +const mapDispatch = (dispatcher: IDispatcher) => ({ + addMediatorConfig: (config: MediatorConfig) => { + dispatcher.dispatch(addMediatorConfigAsyncActionCreator(config)); + }, + updateMediatorConfig: (config: MediatorConfig) => { + dispatcher.dispatch(updateMediatorConfigAsyncActionCreator(config)); + }, + removeMediatorConfig: (config: MediatorConfig) => { + dispatcher.dispatch(removeMediatorConfigAsyncActionCreator(config)); + }, +}); + +type DialogSettings = { + dialogTitle: string; + dialogDescription: string; + applyButtonText: string; + cancelButtonText: string; + readonly: boolean; + readonlyName: boolean; +}; + +const settings: { [key: string]: DialogSettings } = { + [EditMediatorConfigDialogMode.None]: { + dialogTitle: "", + dialogDescription: "", + applyButtonText: "", + cancelButtonText: "", + readonly: true, + readonlyName: true, + }, + [EditMediatorConfigDialogMode.AddMediatorConfig]: { + dialogTitle: "Add Mediator Configuration", + dialogDescription: "", + applyButtonText: "Add", + cancelButtonText: "Cancel", + readonly: false, + readonlyName: false, + }, + [EditMediatorConfigDialogMode.EditMediatorConfig]: { + dialogTitle: "Edit Mediator Configuration", + dialogDescription: "", + applyButtonText: "Update", + cancelButtonText: "Cancel", + readonly: false, + readonlyName: true, + }, + [EditMediatorConfigDialogMode.RemoveMediatorConfig]: { + dialogTitle: "Remove Mediator Configuration", + dialogDescription: "", + applyButtonText: "Remove", + cancelButtonText: "Cancel", + readonly: true, + readonlyName: true, + }, +}; + +type EditMediatorConfigDialogComponentProps = WithStyles & Connect & { + mode: EditMediatorConfigDialogMode; + mediatorConfig: MediatorConfig; + onClose: () => void; +}; + +type EditMediatorConfigDialogComponentState = MediatorConfig & { activeTab: number; activeOdlConfig: string, forceAddOdlConfig: boolean, isOdlConfigHostnameEmpty: boolean }; + +class EditMediatorConfigDialogComponent extends React.Component { + constructor(props: EditMediatorConfigDialogComponentProps) { + super(props); + + this.state = { + ...this.props.mediatorConfig, + activeTab: 0, + activeOdlConfig: "", + forceAddOdlConfig: false, + isOdlConfigHostnameEmpty: false + }; + } + + private odlConfigValueChangeHandlerCreator = (index: number, property: TKey, mapValue: (event: React.ChangeEvent) => any) => (event: React.ChangeEvent) => { + event.stopPropagation(); + event.preventDefault(); + this.setState({ + ODLConfig: [ + ...this.state.ODLConfig.slice(0, index), + { ...this.state.ODLConfig[index], [property]: mapValue(event) }, + ...this.state.ODLConfig.slice(index + 1) + ] + }); + } + + render(): JSX.Element { + const setting = settings[this.props.mode]; + const { classes } = this.props; + return ( + + {setting.dialogTitle} + + + {setting.dialogDescription} + + this.setState({ activeTab: value })} > + + + + {this.state.activeTab === 0 ? + { this.setState({ Name: event.target.value }); }} /> + + Device + + + { this.setState({ DeviceIp: event.target.value }); }} /> + { this.setState({ DevicePort: +event.target.value }); }} /> + { this.setState({ TrapPort: +event.target.value }); }} /> + { this.setState({ NcUsername: event.target.value }); }} /> + { this.setState({ NcPassword: event.target.value }); }} /> + { this.setState({ NcPort: +event.target.value }); }} /> + : null} + {this.state.activeTab === 1 ? + {this.state.ODLConfig && this.state.ODLConfig.length > 0 + ? this.state.ODLConfig.map((cfg, ind) => { + const panelId = `panel-${ind}`; + const deleteButton = ( { + this.setState({ + ODLConfig: [ + ...this.state.ODLConfig.slice(0, ind), + ...this.state.ODLConfig.slice(ind + 1) + ] + }); + }} + size="large">) + return ( + { this.setState({ activeOdlConfig: (this.state.activeOdlConfig === id) ? "" : (id || "") }); console.log("activeOdlConfig " + id); this.hideHostnameErrormessage(id) }} > +
+ + Protocoll + + + e.target.value)} /> + +e.target.value)} /> +
+ { + this.state.isOdlConfigHostnameEmpty && + Please add a hostname. + } +
+ e.target.value)} /> + e.target.value)} /> +
+
+ e.target.checked)} />} label="Trustall" /> +
+
+ ); + }) + : (this.state.forceAddOdlConfig ? +
+ Please add at least one ODL auto connect configuration. +
+ : +
+ Please add an ODL auto connect configuration. +
+ ) + } + this.setState({ + ODLConfig: [...this.state.ODLConfig, { Server: '', Port: 8181, Protocol: 'https', User: 'admin', Password: 'admin', Trustall: false }] + })} > + +
: null} + +
+ + + + +
+ ); + } + + private addConfig = (event: any) => { + event.preventDefault(); + event.stopPropagation(); + + if (this.state.ODLConfig.length === 0) { + this.setState({ activeTab: 1, forceAddOdlConfig: true }); + } + else + if (this.state.ODLConfig.length > 0) { + for (let i = 0; i <= this.state.ODLConfig.length; i++) { + if (this.isHostnameEmpty(i)) { + this.setState({ activeOdlConfig: 'panel-' + i }) + this.setState({ isOdlConfigHostnameEmpty: true }) + return; + } + } + + this.onApply(Object.keys(this.state).reduce((acc, key) => { + // do not copy additional state properties + if (key !== "activeTab" && key !== "activeOdlConfig" && key !== "isOdlConfigHostnameEmpty" + && key !== "forceAddOdlConfig" && key !== "_initialMediatorConfig") acc[key] = (this.state as any)[key]; + return acc; + }, {} as MediatorConfig)); + this.resetPanel(); + } + } + + private resetPanel = () => { + this.setState({ forceAddOdlConfig: false, isOdlConfigHostnameEmpty: false, activeTab: 0 }); + } + + + private hideHostnameErrormessage = (panelId: string | null) => { + + if (panelId) { + let id = Number(panelId.split('-')[1]); + if (!this.isHostnameEmpty(id)) { + this.setState({ isOdlConfigHostnameEmpty: false }) + } + } + } + + private isHostnameEmpty = (id: number) => { + + const element = this.state.ODLConfig[id]; + if (element) { + if (!element.Server) { + return true; + } + else { + return false + } + + } else { + return null; + } + } + + private onApply = (config: MediatorConfig) => { + this.props.onClose && this.props.onClose(); + switch (this.props.mode) { + case EditMediatorConfigDialogMode.AddMediatorConfig: + config && this.props.addMediatorConfig(config); + break; + case EditMediatorConfigDialogMode.EditMediatorConfig: + config && this.props.updateMediatorConfig(config); + break; + case EditMediatorConfigDialogMode.RemoveMediatorConfig: + config && this.props.removeMediatorConfig(config); + break; + } + }; + + private onCancel = () => { + this.props.onClose && this.props.onClose(); + } + + static getDerivedStateFromProps(props: EditMediatorConfigDialogComponentProps, state: EditMediatorConfigDialogComponentState & { _initialMediatorConfig: MediatorConfig }): EditMediatorConfigDialogComponentState & { _initialMediatorConfig: MediatorConfig } { + if (props.mediatorConfig !== state._initialMediatorConfig) { + state = { + ...state, + ...props.mediatorConfig, + _initialMediatorConfig: props.mediatorConfig, + }; + } + return state; + } +} + +export const EditMediatorConfigDialog = withStyles(styles)(connect(mapProps, mapDispatch)(EditMediatorConfigDialogComponent)); +export default EditMediatorConfigDialog; \ No newline at end of file diff --git a/sdnr/wt-odlux/odlux/apps/mediatorApp/src/components/editMediatorServerDialog.tsx b/sdnr/wt-odlux/odlux/apps/mediatorApp/src/components/editMediatorServerDialog.tsx new file mode 100644 index 000000000..c8b158749 --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/mediatorApp/src/components/editMediatorServerDialog.tsx @@ -0,0 +1,221 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * 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. + * ============LICENSE_END========================================================================== + */ +import * as React from 'react'; + +import Button from '@mui/material/Button'; +import TextField from '@mui/material/TextField'; +import Dialog from '@mui/material/Dialog'; +import DialogActions from '@mui/material/DialogActions'; +import DialogContent from '@mui/material/DialogContent'; +import DialogContentText from '@mui/material/DialogContentText'; +import DialogTitle from '@mui/material/DialogTitle'; + +import { IDispatcher, connect, Connect } from '../../../../framework/src/flux/connect'; + +import { addAvaliableMediatorServerAsyncActionCreator, removeAvaliableMediatorServerAsyncActionCreator, updateAvaliableMediatorServerAsyncActionCreator } from '../actions/avaliableMediatorServersActions'; +import { MediatorServer } from '../models/mediatorServer'; +import { Typography } from '@mui/material'; + +export enum EditMediatorServerDialogMode { + None = "none", + AddMediatorServer = "addMediatorServer", + EditMediatorServer = "editMediatorServer", + RemoveMediatorServer = "removeMediatorServer", +} + +const mapDispatch = (dispatcher: IDispatcher) => ({ + addMediatorServer: (element: MediatorServer) => { + dispatcher.dispatch(addAvaliableMediatorServerAsyncActionCreator(element)); + }, + updateMediatorServer: (element: MediatorServer) => { + dispatcher.dispatch(updateAvaliableMediatorServerAsyncActionCreator(element)); + }, + removeMediatorServer: (element: MediatorServer) => { + dispatcher.dispatch(removeAvaliableMediatorServerAsyncActionCreator(element)); + }, +}); + +type DialogSettings = { + dialogTitle: string; + dialogDescription: string; + applyButtonText: string; + cancelButtonText: string; + readonly: boolean; +}; + +const settings: { [key: string]: DialogSettings } = { + [EditMediatorServerDialogMode.None]: { + dialogTitle: "", + dialogDescription: "", + applyButtonText: "", + cancelButtonText: "", + readonly: true, + }, + [EditMediatorServerDialogMode.AddMediatorServer]: { + dialogTitle: "Add Mediator Server", + dialogDescription: "", + applyButtonText: "Add", + cancelButtonText: "Cancel", + readonly: false, + }, + [EditMediatorServerDialogMode.EditMediatorServer]: { + dialogTitle: "Edit Mediator Server", + dialogDescription: "", + applyButtonText: "Update", + cancelButtonText: "Cancel", + readonly: false, + }, + [EditMediatorServerDialogMode.RemoveMediatorServer]: { + dialogTitle: "Remove Mediator Server", + dialogDescription: "", + applyButtonText: "Remove", + cancelButtonText: "Cancel", + readonly: true, + }, +}; + +type EditMediatorServerDialogComponentProps = Connect & { + mode: EditMediatorServerDialogMode; + mediatorServer: MediatorServer; + onClose: () => void; +}; + +const urlRegex = RegExp("^https?://"); + +type EditMediatorServerDialogComponentState = MediatorServer & { errorMessage: string[] }; + +class EditMediatorServerDialogComponent extends React.Component { + constructor(props: EditMediatorServerDialogComponentProps) { + super(props); + + this.state = { + ...this.props.mediatorServer, + errorMessage: [] + }; + } + + areFieldsValid = () => { + return this.state.name.trim().length > 0 && this.state.url.trim().length > 0 && urlRegex.test(this.state.url); + } + + createErrorMessages = () => { + + let messages = []; + if (this.state.name.trim().length === 0 && this.state.url.trim().length === 0) { + messages.push("The server name and the url must not be empty.") + } + else + if (this.state.url.trim().length === 0) { + messages.push("The server url must not be empty.") + + } else if (this.state.name.trim().length === 0) { + messages.push("The server name must not be empty.") + } + + if (!urlRegex.test(this.state.url)) { + if (messages.length > 0) { + return messages.concat(["The server url must start with 'http(s)://'."]) + } else { + return ["The server url must start with 'http(s)://'."] + } + } + + return messages; + } + + + + render(): JSX.Element { + const setting = settings[this.props.mode]; + + return ( + + {setting.dialogTitle} + + + {setting.dialogDescription} + + {/* { this.setState({_id: event.target.value}); } } /> */} + { this.setState({ name: event.target.value }); }} /> + { this.setState({ url: event.target.value }); }} /> + + {this.state.errorMessage.map((error, index) =>
{error}
)}
+ +
+ + + + +
+ ) + } + + private onApply = (element: MediatorServer) => { + this.props.onClose && this.props.onClose(); + switch (this.props.mode) { + case EditMediatorServerDialogMode.AddMediatorServer: + element && this.props.addMediatorServer(element); + break; + case EditMediatorServerDialogMode.EditMediatorServer: + element && this.props.updateMediatorServer(element); + break; + case EditMediatorServerDialogMode.RemoveMediatorServer: + element && this.props.removeMediatorServer(element); + break; + } + }; + + private onCancel = () => { + this.props.onClose && this.props.onClose(); + } + + static getDerivedStateFromProps(props: EditMediatorServerDialogComponentProps, state: EditMediatorServerDialogComponentState & { _initialMediatorServer: MediatorServer }): EditMediatorServerDialogComponentState & { _initialMediatorServer: MediatorServer } { + if (props.mediatorServer !== state._initialMediatorServer) { + state = { + ...state, + ...props.mediatorServer, + _initialMediatorServer: props.mediatorServer, + }; + } + return state; + } +} + +export const EditMediatorServerDialog = connect(undefined, mapDispatch)(EditMediatorServerDialogComponent); +export default EditMediatorServerDialog; \ No newline at end of file diff --git a/sdnr/wt-odlux/odlux/apps/mediatorApp/src/components/refreshMediatorDialog.tsx b/sdnr/wt-odlux/odlux/apps/mediatorApp/src/components/refreshMediatorDialog.tsx new file mode 100644 index 000000000..db1ef8771 --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/mediatorApp/src/components/refreshMediatorDialog.tsx @@ -0,0 +1,117 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * 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. + * ============LICENSE_END========================================================================== + */ +import * as React from 'react'; + +import Button from '@mui/material/Button'; +import Dialog from '@mui/material/Dialog'; +import DialogActions from '@mui/material/DialogActions'; +import DialogContent from '@mui/material/DialogContent'; +import DialogContentText from '@mui/material/DialogContentText'; +import DialogTitle from '@mui/material/DialogTitle'; + +import { avaliableMediatorServersReloadAction } from '../handlers/avaliableMediatorServersHandler'; +import { IDispatcher, connect, Connect } from '../../../../framework/src/flux/connect'; + +import { MediatorServer } from '../models/mediatorServer'; + +export enum RefreshMediatorDialogMode { + None = "none", + RefreshMediatorTable = "RefreshMediatorTable", +} + +const mapDispatch = (dispatcher: IDispatcher) => ({ + refreshMediator: () => dispatcher.dispatch(avaliableMediatorServersReloadAction) +}); + +type DialogSettings = { + dialogTitle: string, + dialogDescription: string, + applyButtonText: string, + cancelButtonText: string, + enableMountIdEditor: boolean, + enableUsernameEditor: boolean, + enableExtendedEditor: boolean, +} + +const settings: { [key: string]: DialogSettings } = { + [RefreshMediatorDialogMode.None]: { + dialogTitle: "", + dialogDescription: "", + applyButtonText: "", + cancelButtonText: "", + enableMountIdEditor: false, + enableUsernameEditor: false, + enableExtendedEditor: false, + }, + [RefreshMediatorDialogMode.RefreshMediatorTable]: { + dialogTitle: "Do you want to refresh the Mediator table?", + dialogDescription: "", + applyButtonText: "Yes", + cancelButtonText: "Cancel", + enableMountIdEditor: true, + enableUsernameEditor: true, + enableExtendedEditor: true, + } +} + +type RefreshMediatorDialogComponentProps = Connect & { + mode: RefreshMediatorDialogMode; + onClose: () => void; +}; + +type RefreshMediatorDialogComponentState = MediatorServer & { isNameValid: boolean, isHostSet: boolean }; + +class RefreshMediatorDialogComponent extends React.Component { + constructor(props: RefreshMediatorDialogComponentProps) { + super(props); + } + + render(): JSX.Element { + const setting = settings[this.props.mode]; + return ( + + {setting.dialogTitle} + + + {setting.dialogDescription} + + + + + + + + ); + } + + private onRefresh = () => { + this.props.refreshMediator(); + this.props.onClose(); + }; + + private onCancel = () => { + this.props.onClose(); + } +} + +export const RefreshMediatorDialog = connect(undefined, mapDispatch)(RefreshMediatorDialogComponent); +export default RefreshMediatorDialog; \ No newline at end of file diff --git a/sdnr/wt-odlux/odlux/apps/mediatorApp/src/components/showMeditaorInfoDialog.tsx b/sdnr/wt-odlux/odlux/apps/mediatorApp/src/components/showMeditaorInfoDialog.tsx new file mode 100644 index 000000000..f8691ab2f --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/mediatorApp/src/components/showMeditaorInfoDialog.tsx @@ -0,0 +1,107 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * 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. + * ============LICENSE_END========================================================================== + */ + +import React from 'react' +import { Dialog, DialogTitle, DialogContent, DialogActions, TextField, DialogContentText, Checkbox, Button, FormControlLabel, FormGroup } from '@mui/material'; +import { IApplicationState } from '../../../../framework/src/handlers/applicationStateHandler'; +import { IApplicationStoreState } from '../../../../framework/src/store/applicationStore'; +import { connect, Connect } from '../../../../framework/src/flux/connect'; +import { MediatorConfigResponse } from '../models/mediatorServer'; +import { Panel } from '../../../../framework/src/components/material-ui/panel'; + +export enum MediatorInfoDialogMode { + None = "none", + ShowDetails = "showDetails" +} + +const mapProps = (state: IApplicationStoreState) => ({ supportedDevices: state.mediator.mediatorServerState.supportedDevices }) + +type ShowMediatorInfoDialogComponentProps = Connect & +{ + config: MediatorConfigResponse, + mode: MediatorInfoDialogMode, + onClose: () => void +} + +type ShowMediatorInfoDialogComponentState = { + status: string, + devicetype: string, + activeOdlConfig: string +} + +/* +Displays all values of a mediator server +*/ +class ShowMediatorInfoDialogComponent extends React.Component { + + constructor(props: ShowMediatorInfoDialogComponentProps) { + super(props); + if (this.props.config) { + let deviceType = props.supportedDevices.find(element => element.id === this.props.config.DeviceType) + + this.state = { + status: props.config.pid > 0 ? "Running" : "Stopped", + devicetype: deviceType != undefined ? deviceType.device : 'none', + activeOdlConfig: '' + } + } + } + + onClose = (event: React.MouseEvent) => { + event.preventDefault(); + event.stopPropagation(); + this.props.onClose(); + } + + render() { + return ( + + {this.props.config.Name} + + + + + + + + } label="Netconf Connection" /> + } label="Network Element Connection" /> + } label="Firewall active" /> + + { + this.props.config.ODLConfig.map((element, index) => + 1 ? index + 1 : '')} key={index} panelId={'panel-' + index} activePanel={this.state.activeOdlConfig} onToggle={(id: string) => { this.setState({ activeOdlConfig: (this.state.activeOdlConfig === id) ? "" : (id || "") }); }}> + + + } label="Trustall" /> + + ) + } + + + + + + + ) + } + +} + +export const ShowMediatorInfoDialog = connect(mapProps)(ShowMediatorInfoDialogComponent) +export default ShowMediatorInfoDialog; \ No newline at end of file diff --git a/sdnr/wt-odlux/odlux/apps/mediatorApp/src/handlers/avaliableMediatorServersHandler.ts b/sdnr/wt-odlux/odlux/apps/mediatorApp/src/handlers/avaliableMediatorServersHandler.ts new file mode 100644 index 000000000..c22252d20 --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/mediatorApp/src/handlers/avaliableMediatorServersHandler.ts @@ -0,0 +1,36 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * 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. + * ============LICENSE_END========================================================================== + */ +import { createExternal,IExternalTableState } from '../../../../framework/src/components/material-table/utilities'; +import { createSearchDataHandler } from '../../../../framework/src/utilities/elasticSearch'; + +import { MediatorServer } from '../models/mediatorServer'; +import { mediatorServerResourcePath } from '../services/mediatorService'; + +export interface IAvaliableMediatorServersState extends IExternalTableState { } + +// create eleactic search material data fetch handler +const avaliableMediatorServersSearchHandler = createSearchDataHandler(mediatorServerResourcePath); + +export const { + actionHandler: avaliableMediatorServersActionHandler, + createActions: createAvaliableMediatorServersActions, + createProperties: createAvaliableMediatorServersProperties, + reloadAction: avaliableMediatorServersReloadAction, + + // set value action, to change a value +} = createExternal(avaliableMediatorServersSearchHandler, appState => appState.mediator.avaliableMediatorServers); \ No newline at end of file diff --git a/sdnr/wt-odlux/odlux/apps/mediatorApp/src/handlers/mediatorAppRootHandler.ts b/sdnr/wt-odlux/odlux/apps/mediatorApp/src/handlers/mediatorAppRootHandler.ts new file mode 100644 index 000000000..2642ec8cd --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/mediatorApp/src/handlers/mediatorAppRootHandler.ts @@ -0,0 +1,43 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * 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. + * ============LICENSE_END========================================================================== + */ +// main state handler + +import { combineActionHandler } from '../../../../framework/src/flux/middleware'; +import { IApplicationStoreState } from '../../../../framework/src/store/applicationStore'; + +import { IAvaliableMediatorServersState, avaliableMediatorServersActionHandler } from './avaliableMediatorServersHandler' ; +import { MediatorServerState, mediatorServerHandler } from './mediatorServerHandler'; + +export interface IMediatorAppStoreState { + avaliableMediatorServers: IAvaliableMediatorServersState, + mediatorServerState: MediatorServerState, +} + +declare module '../../../../framework/src/store/applicationStore' { + interface IApplicationStoreState { + mediator: IMediatorAppStoreState + } +} + +const actionHandlers = { + avaliableMediatorServers: avaliableMediatorServersActionHandler, + mediatorServerState: mediatorServerHandler, +}; + +export const mediatorAppRootHandler = combineActionHandler(actionHandlers); +export default mediatorAppRootHandler; diff --git a/sdnr/wt-odlux/odlux/apps/mediatorApp/src/handlers/mediatorServerHandler.ts b/sdnr/wt-odlux/odlux/apps/mediatorApp/src/handlers/mediatorServerHandler.ts new file mode 100644 index 000000000..246634cbe --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/mediatorApp/src/handlers/mediatorServerHandler.ts @@ -0,0 +1,120 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * 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. + * ============LICENSE_END========================================================================== + */ +import { XmlFileInfo, MediatorConfig, BusySymbol, MediatorConfigResponse, MediatorServerDevice } from "../models/mediatorServer"; +import { IActionHandler } from "../../../../framework/src/flux/action"; +import { SetMediatorServerVersion, SetMediatorServerInfo, SetAllMediatorServerConfigurations, SetMediatorServerBusy, SetMediatorServerSupportedDevices, SetMediatorServerReachable } from "../actions/mediatorServerActions"; +import { SetMediatorBusyByName, UpdateMediatorConfig, AddMediatorConfig, RemoveMediatorConfig } from "../actions/mediatorConfigActions"; + +export type MediatorServerState = { + busy: boolean; + name: string | null; + url: string | null; + id: string | null; + serverVersion: string | null; + mediatorVersion: string | null; + nexmls: XmlFileInfo[]; + configurations: MediatorConfigResponse[]; + supportedDevices: MediatorServerDevice[]; + isReachable: boolean; +} + +const mediatorServerInit: MediatorServerState = { + busy: false, + name: null, + url: null, + id: null, + serverVersion: null, + mediatorVersion: null, + nexmls: [], + configurations: [], + supportedDevices: [], + isReachable: true +} + +export const mediatorServerHandler: IActionHandler = (state = mediatorServerInit, action) => { + if (action instanceof SetMediatorServerBusy) { + state = { + ...state, + busy: action.isBusy + }; + } else if (action instanceof SetMediatorServerInfo) { + state = { + ...state, + name: action.name, + url: action.url, + id: action.id, + }; + } else if (action instanceof SetMediatorServerVersion) { + state = { + ...state, + serverVersion: action.versionInfo && action.versionInfo.server, + mediatorVersion: action.versionInfo && action.versionInfo.mediator, + nexmls: action.versionInfo && [...action.versionInfo.nexmls] || [], + }; + } else if (action instanceof SetAllMediatorServerConfigurations) { + state = { + ...state, + configurations: action.allConfigurations && action.allConfigurations.map(config => ({ ...config, busy: false })) || [], + }; + } else if (action instanceof SetMediatorServerSupportedDevices) { + state = { + ...state, + supportedDevices: action.devices || [], + }; + } else if (action instanceof SetMediatorBusyByName) { + const index = state.configurations.findIndex(config => config.Name === action.name); + if (index > -1) state = { + ...state, + configurations: [ + ...state.configurations.slice(0, index), + { ...state.configurations[index], [BusySymbol]: action.isBusy }, + ...state.configurations.slice(index + 1) + ] + }; + } else if (action instanceof AddMediatorConfig) { + state = { + ...state, + configurations: [ + ...state.configurations, + action.mediatorConfig + ] + }; + } else if (action instanceof UpdateMediatorConfig) { + const index = state.configurations.findIndex(config => config.Name === action.name); + if (index > -1) state = { + ...state, + configurations: [ + ...state.configurations.slice(0, index), + { ...action.mediatorConfig, [BusySymbol]: state.configurations[index][BusySymbol] }, + ...state.configurations.slice(index + 1) + ] + }; + } else if (action instanceof RemoveMediatorConfig) { + const index = state.configurations.findIndex(config => config.Name === action.name); + if (index > -1) state = { + ...state, + configurations: [ + ...state.configurations.slice(0, index), + ...state.configurations.slice(index + 1) + ] + }; + } else if( action instanceof SetMediatorServerReachable){ + state = {...state, isReachable: action.isReachable} + } + return state; +} \ No newline at end of file diff --git a/sdnr/wt-odlux/odlux/apps/mediatorApp/src/index.html b/sdnr/wt-odlux/odlux/apps/mediatorApp/src/index.html new file mode 100644 index 000000000..95bf9ec6b --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/mediatorApp/src/index.html @@ -0,0 +1,29 @@ + + + + + + + + + Mediator App + + + +
+ + + + + + \ No newline at end of file diff --git a/sdnr/wt-odlux/odlux/apps/mediatorApp/src/models/mediatorServer.ts b/sdnr/wt-odlux/odlux/apps/mediatorApp/src/models/mediatorServer.ts new file mode 100644 index 000000000..6ab6db8b3 --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/mediatorApp/src/models/mediatorServer.ts @@ -0,0 +1,77 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * 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. + * ============LICENSE_END========================================================================== + */ +export type MediatorServer = { + id: string; + name: string; + url: string; +} + +export type XmlFileInfo = { + filename: string; + version: string; +} + +export type MediatorServerVersionInfo = { + mediator: string; + server: string; + nexmls: XmlFileInfo[]; +} + +export type ODLConfig = { + User: string; + Password: string; + Port: number; + Protocol: "http" | "https"; + Server: string; + Trustall: boolean; +}; + +export const BusySymbol = Symbol("Busy"); + +export type MediatorConfig = { + Name: string; + DeviceIp: string; + DevicePort: number; + DeviceType: number; + TrapPort: number; + NcUsername: string; + NcPassword: string; + NcPort: number; + NeXMLFile: string; + ODLConfig: ODLConfig[]; +} + +export type MediatorConfigResponse = MediatorConfig & { + IsNCConnected: boolean; + IsNeConnected: boolean; + autorun: boolean; + fwactive: boolean; + islocked: boolean; + ncconnections:{}[]; + pid: number; + // extended properties + [BusySymbol]: boolean; +} + +export type MediatorServerDevice = { + id: number; // DeviceType + device: string; + vendor: string; + version: string; + xml: string; // NeXMLFile +} \ No newline at end of file diff --git a/sdnr/wt-odlux/odlux/apps/mediatorApp/src/plugin.tsx b/sdnr/wt-odlux/odlux/apps/mediatorApp/src/plugin.tsx new file mode 100644 index 000000000..1c30cfc54 --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/mediatorApp/src/plugin.tsx @@ -0,0 +1,83 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * 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. + * ============LICENSE_END========================================================================== + */ +// app configuration and main entry point for the app + +import React from "react"; +import { withRouter, RouteComponentProps, Route, Switch, Redirect } from 'react-router-dom'; + +import applicationManager from '../../../framework/src/services/applicationManager'; + +import { connect, Connect, IDispatcher } from '../../../framework/src/flux/connect'; + +import { mediatorAppRootHandler } from './handlers/mediatorAppRootHandler'; +import { avaliableMediatorServersReloadAction } from "./handlers/avaliableMediatorServersHandler"; + +import { MediatorApplication } from "./views/mediatorApplication"; +import { MediatorServerSelection } from "./views/mediatorServerSelection"; +import { initializeMediatorServerAsyncActionCreator } from "./actions/mediatorServerActions"; + +const appIcon = require('./assets/icons/mediatorAppIcon.svg'); // select app icon + +let currentMediatorServerId: string | undefined = undefined; + +const mapDisp = (dispatcher: IDispatcher) => ({ + loadMediatorServer : (mediatorServerId: string) => dispatcher.dispatch(initializeMediatorServerAsyncActionCreator(mediatorServerId)), +}); + +const MediatorServerRouteAdapter = connect(undefined, mapDisp)((props: RouteComponentProps<{ mediatorServerId: string }> & Connect) => { + if (currentMediatorServerId !== props.match.params.mediatorServerId) { + // route parameter has changed + currentMediatorServerId = props.match.params.mediatorServerId || undefined; + // Hint: This timeout is need, since it is not recommended to change the state while rendering is in progress ! + window.setTimeout(() => { + if (currentMediatorServerId) { + props.loadMediatorServer(currentMediatorServerId); + } + }); + } + return ( + + ) +}); + +type AppProps = RouteComponentProps & Connect; + +const App = (props: AppProps) => ( + + + + + +); + +const FinalApp = withRouter(connect()(App)); + +export function register() { + const applicationApi = applicationManager.registerApplication({ + name: "mediator", + icon: appIcon, + rootComponent: FinalApp, + rootActionHandler: mediatorAppRootHandler, + menuEntry: "Mediator" + }); + + // prefetch all available mediator servers + applicationApi.applicationStoreInitialized.then(applicationStore => { + applicationStore.dispatch(avaliableMediatorServersReloadAction) + }); +}; diff --git a/sdnr/wt-odlux/odlux/apps/mediatorApp/src/services/mediatorService.ts b/sdnr/wt-odlux/odlux/apps/mediatorApp/src/services/mediatorService.ts new file mode 100644 index 000000000..ede681776 --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/mediatorApp/src/services/mediatorService.ts @@ -0,0 +1,204 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * 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. + * ============LICENSE_END========================================================================== + */ +import * as $ from 'jquery'; + +import { requestRest, formEncode } from '../../../../framework/src/services/restService'; +import { MediatorServer, MediatorServerVersionInfo, MediatorConfig, MediatorServerDevice, MediatorConfigResponse } from '../models/mediatorServer'; +import { PostResponse, DeleteResponse, Result } from '../../../../framework/src/models'; + +export const mediatorServerResourcePath = "mediator-server"; + +type MediatorServerResponse = { code: number, data: TData }; +type IndexableMediatorServer = MediatorServer & { [key: string]: any; }; + +/** + * Represents a web api accessor service for all mediator server actions. + */ +class MediatorService { + /** + * Inserts data into the mediator servers table. + */ + public async insertMediatorServer(server: IndexableMediatorServer): Promise { + const path = `/restconf/operations/data-provider:create-mediator-server`; + + const data = { + "url": server.url, + "name": server.name + } + + const result = await requestRest(path, { method: "POST", body: JSON.stringify({ input: data }) }); + return result || null; + } + + /** + * Updates data into the mediator servers table. + */ + public async updateMediatorServer(server: IndexableMediatorServer): Promise { + const path = `/restconf/operations/data-provider:update-mediator-server`; + + const data = { + "id": server.id, + "url": server.url, + "name": server.name + } + + const result = await requestRest(path, { method: "POST", body: JSON.stringify({ input: data }) }); + return result || null; + } + + /** + * Deletes data from the mediator servers table. + */ + public async deleteMediatorServer(server: MediatorServer): Promise { + const path = `/restconf/operations/data-provider:delete-mediator-server`; + + const data = { + "id": server.id, + } + + const result = await requestRest(path, { method: "POST", body: JSON.stringify({ input: data }) }); + return result || null; + } + + public async getMediatorServerById(serverId: string): Promise { + const path = `/restconf/operations/data-provider:read-mediator-server-list`; + + const data = { "filter": [{ "property": "id", "filtervalue": serverId }] } + + + const result = await requestRest>(path, { method: "POST", body: JSON.stringify({ input: data }) }); + + if (result && result["data-provider:output"].data[0]) { + const firstResult = result["data-provider:output"].data[0]; + + return { + id: firstResult.id, + name: firstResult.name, + url: firstResult.url + } + } + else { + return null; + } + } + + // https://cloud-highstreet-technologies.com/wiki/doku.php?id=att:ms:api + + private async accassMediatorServer(mediatorServerId: string, task: string, data?: {}): Promise | null> { + const path = `ms/${mediatorServerId}/api/'?task=${task}`; + const result = (await requestRest(path, { + method: data ? "POST" : "GET", + headers: { + 'Content-Type': 'application/x-www-form-urlencoded' + }, + body: data ? formEncode({ + ...data, + ...{ task: task } + }) : null + }, true)) || null; + + return result ? JSON.parse(result) as { code: number, data: TData } : null; + } + /* + private accassMediatorServer(mediatorServerId: string, task: string, data?: {}): Promise | null> { + const path = `ms/${mediatorServerId}/api/?task=${task}`; + return new Promise<{ code: number, data: TData }>((resolve, reject) => { + $.ajax({ + method: data ? 'POST' : 'GET', + url: path, + data: { ...{ task: task }, ...data }, + //contentType: 'application/json' + }).then((result: any) => { + if (typeof result === "string") { + resolve(JSON.parse(result)); + } else { + resolve(result); + }; + }); + }); + }*/ + + public async getMediatorServerVersion(mediatorServerId: string): Promise { + const result = await this.accassMediatorServer(mediatorServerId, 'version'); + if (result && result.code === 1) return result.data; + return null; + } + + public async getMediatorServerAllConfigs(mediatorServerId: string): Promise { + const result = await this.accassMediatorServer(mediatorServerId, 'getconfig'); + if (result && result.code === 1) return result.data; + return null; + } + + public async getMediatorServerConfigByName(mediatorServerId: string, name: string): Promise { + const result = await this.accassMediatorServer(mediatorServerId, `getconfig&name=${name}`); + if (result && result.code === 1 && result.data && result.data.length === 1) return result.data[0]; + return null; + } + + public async getMediatorServerSupportedDevices(mediatorServerId: string): Promise { + const result = await this.accassMediatorServer(mediatorServerId, 'getdevices'); + if (result && result.code === 1) return result.data; + return null; + } + + public async startMediatorByName(mediatorServerId: string, name: string): Promise { + const result = await this.accassMediatorServer(mediatorServerId, `start&name=${name}`); + if (result && result.code === 1) return result.data; + return null; + } + + public async stopMediatorByName(mediatorServerId: string, name: string): Promise { + const result = await this.accassMediatorServer(mediatorServerId, `stop&name=${name}`); + if (result && result.code === 1) return result.data; + return null; + } + + public async createMediatorConfig(mediatorServerId: string, config: MediatorConfig): Promise { + const result = await this.accassMediatorServer(mediatorServerId, 'create', { config: JSON.stringify(config) }); + if (result && result.code === 1) return result.data; + return null; + } + + public async updateMediatorConfigByName(mediatorServerId: string, config: MediatorConfig): Promise { + const result = await this.accassMediatorServer(mediatorServerId, 'update', { config: JSON.stringify(config) }); + if (result && result.code === 1) return result.data; + return null; + } + + public async deleteMediatorConfigByName(mediatorServerId: string, name: string): Promise { + const result = await this.accassMediatorServer(mediatorServerId, `delete&name=${name}`); + if (result && result.code === 1) return result.data; + return null; + } + + public async getMediatorServerFreeNcPorts(mediatorServerId: string, limit?: number): Promise { + const result = await this.accassMediatorServer(mediatorServerId, 'getncports', { limit }); + if (result && result.code === 1) return result.data; + return null; + } + + public async getMediatorServerFreeSnmpPorts(mediatorServerId: string, limit?: number): Promise { + const result = await this.accassMediatorServer(mediatorServerId, 'getsnmpports', { limit }); + if (result && result.code === 1) return result.data; + return null; + } +} + +export const mediatorService = new MediatorService; +export default mediatorService; \ No newline at end of file diff --git a/sdnr/wt-odlux/odlux/apps/mediatorApp/src/views/mediatorApplication.tsx b/sdnr/wt-odlux/odlux/apps/mediatorApp/src/views/mediatorApplication.tsx new file mode 100644 index 000000000..03ce4532e --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/mediatorApp/src/views/mediatorApplication.tsx @@ -0,0 +1,291 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * 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. + * ============LICENSE_END========================================================================== + */ +import React from 'react'; +import { Theme, Tooltip } from '@mui/material'; + +import { WithStyles } from '@mui/styles'; +import createStyles from '@mui/styles/createStyles'; +import withStyles from '@mui/styles/withStyles'; + +import AddIcon from '@mui/icons-material/Add'; +import IconButton from '@mui/material/IconButton'; +import EditIcon from '@mui/icons-material/Edit'; +import DeleteIcon from '@mui/icons-material/Delete'; +import InfoIcon from '@mui/icons-material/Info'; +import StartIcon from '@mui/icons-material/PlayArrow'; +import StopIcon from '@mui/icons-material/Stop'; + +import CircularProgress from '@mui/material/CircularProgress' + +import { IApplicationStoreState } from '../../../../framework/src/store/applicationStore'; +import { connect, Connect, IDispatcher } from '../../../../framework/src/flux/connect'; +import MaterialTable, { MaterialTableCtorType, ColumnType } from '../../../../framework/src/components/material-table'; + +import { MediatorConfig, BusySymbol, MediatorConfigResponse } from '../models/mediatorServer'; +import EditMediatorConfigDialog, { EditMediatorConfigDialogMode } from '../components/editMediatorConfigDialog'; +import { startMediatorByNameAsyncActionCreator, stopMediatorByNameAsyncActionCreator } from '../actions/mediatorConfigActions'; +import mediatorService from '../services/mediatorService'; +import { ShowMediatorInfoDialog, MediatorInfoDialogMode } from '../components/showMeditaorInfoDialog' + +const styles = (theme: Theme) => createStyles({ + root: { + display: 'flex', + flexDirection: 'column', + flex: '1', + }, + formControl: { + margin: theme.spacing(1), + minWidth: 300, + }, + button: { + margin: 0, + padding: "6px 6px", + minWidth: 'unset' + }, + spacer: { + marginLeft: theme.spacing(1), + marginRight: theme.spacing(1), + display: "inline" + }, + progress: { + flex: '1 1 100%', + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + pointerEvents: 'none' + } +}); + +const mapProps = (state: IApplicationStoreState) => ({ + serverName: state.mediator.mediatorServerState.name, + serverUrl: state.mediator.mediatorServerState.url, + serverId: state.mediator.mediatorServerState.id, + serverVersion: state.mediator.mediatorServerState.serverVersion, + mediatorVersion: state.mediator.mediatorServerState.mediatorVersion, + configurations: state.mediator.mediatorServerState.configurations, + supportedDevices: state.mediator.mediatorServerState.supportedDevices, + busy: state.mediator.mediatorServerState.busy, + isReachable: state.mediator.mediatorServerState.isReachable +}); + +const mapDispatch = (dispatcher: IDispatcher) => ({ + startMediator: (name: string) => dispatcher.dispatch(startMediatorByNameAsyncActionCreator(name)), + stopMediator: (name: string) => dispatcher.dispatch(stopMediatorByNameAsyncActionCreator(name)), +}); + +const emptyMediatorConfig: MediatorConfig = { + Name: "", + DeviceIp: "127.0.0.1", + DevicePort: 161, + NcUsername: "admin", + NcPassword: "admin", + DeviceType: -1, + NcPort: 4020, + TrapPort: 10020, + NeXMLFile: "", + ODLConfig: [] +}; + +const MediatorServerConfigurationsTable = MaterialTable as MaterialTableCtorType; +const MediatorServerUnreachableTable = MaterialTable as MaterialTableCtorType<{ Name: string, status: string, ipAdress: string, device: string, actions: string }> + +type MediatorApplicationComponentProps = Connect & WithStyles; + +type MediatorServerSelectionComponentState = { + busy: boolean, + mediatorConfigToEdit: MediatorConfig, + mediatorConfigEditorMode: EditMediatorConfigDialogMode, + mediatorShowInfoMode: MediatorInfoDialogMode, + mediatorConfigToDisplay: MediatorConfigResponse | null +} + +class MediatorApplicationComponent extends React.Component { + + constructor(props: MediatorApplicationComponentProps) { + super(props); + + this.state = { + busy: false, + mediatorConfigToEdit: emptyMediatorConfig, + mediatorConfigEditorMode: EditMediatorConfigDialogMode.None, + mediatorShowInfoMode: MediatorInfoDialogMode.None, + mediatorConfigToDisplay: null + } + } + + render() { + const { classes } = this.props; + + const renderActions = (rowData: MediatorConfigResponse) => ( + <> +
+ + + { event.preventDefault(); event.stopPropagation(); this.props.startMediator(rowData.Name); }} /> + + + + + { event.preventDefault(); event.stopPropagation(); this.props.stopMediator(rowData.Name); }} /> + + +
+
+ { this.onOpenInfoDialog(event, rowData) }} + size="large"> +
+
+ {process.env.NODE_ENV === "development" ? this.onOpenEditConfigurationDialog(event, rowData)} + size="large"> : null} + this.onOpenRemoveConfigutationDialog(event, rowData)} + size="large"> +
+ + ); + + const addMediatorConfigAction = { icon: AddIcon, tooltip: 'Add', ariaLabel: 'add-element', onClick: this.onOpenAddConfigurationDialog }; + + return ( +
+ {this.props.busy || this.state.busy + ?
+ : + + this.props.isReachable ? + + rowData.pid ? (Running) : (Stopped) }, + { property: "DeviceIp", title: "IP Adress", type: ColumnType.text }, + { + property: "Device", title: "Device", type: ColumnType.custom, customControl: ({ rowData }) => { + const dev = this.props.supportedDevices && this.props.supportedDevices.find(dev => dev.id === rowData.DeviceType); + return ( + {dev && `${dev.vendor} - ${dev.device} (${dev.version || '0.0.0'})`} + ) + } + }, + { property: "actions", title: "Actions", type: ColumnType.custom, customControl: ({ rowData }) => renderActions(rowData) }, + ]} /> + : + + } + + + + { + + this.state.mediatorShowInfoMode != MediatorInfoDialogMode.None && + + } +
+ ); + } + + private onOpenInfoDialog = (event: React.MouseEvent, configEntry: MediatorConfigResponse) => { + event.stopPropagation(); + event.preventDefault(); + this.setState({ mediatorShowInfoMode: MediatorInfoDialogMode.ShowDetails, mediatorConfigToDisplay: configEntry }) + } + + private onCloseInfoDialog = () => { + this.setState({ mediatorShowInfoMode: MediatorInfoDialogMode.None, mediatorConfigToDisplay: null }) + } + + private onOpenAddConfigurationDialog = () => { + // Tries to determine a free port for netconf listener and snpm listener + // it it could not determine free ports the dialog will open any way + // those ports should not be configured from the fontend, furthermore + // the backend should auto configure them and tell the user the result + // after the creation process. + this.setState({ + busy: true, + }); + this.props.serverId && Promise.all([ + mediatorService.getMediatorServerFreeNcPorts(this.props.serverId, 1), + mediatorService.getMediatorServerFreeSnmpPorts(this.props.serverId, 1), + ]).then(([freeNcPorts, freeSnmpPorts]) => { + if (freeNcPorts && freeSnmpPorts && freeNcPorts.length > 0 && freeSnmpPorts.length > 0) { + this.setState({ + busy: false, + mediatorConfigEditorMode: EditMediatorConfigDialogMode.AddMediatorConfig, + mediatorConfigToEdit: { + ...emptyMediatorConfig, + NcPort: freeNcPorts[0], + TrapPort: freeSnmpPorts[0], + }, + }); + } else { + this.setState({ + busy: false, + mediatorConfigEditorMode: EditMediatorConfigDialogMode.AddMediatorConfig, + mediatorConfigToEdit: { ...emptyMediatorConfig }, + }); + } + }) + + } + + private onOpenEditConfigurationDialog = (event: React.MouseEvent, configEntry: MediatorConfig) => { + event.preventDefault(); + event.stopPropagation(); + this.setState({ + mediatorConfigEditorMode: EditMediatorConfigDialogMode.EditMediatorConfig, + mediatorConfigToEdit: configEntry, + }); + } + + private onOpenRemoveConfigutationDialog = (event: React.MouseEvent, configEntry: MediatorConfig) => { + event.preventDefault(); + event.stopPropagation(); + this.setState({ + mediatorConfigEditorMode: EditMediatorConfigDialogMode.RemoveMediatorConfig, + mediatorConfigToEdit: configEntry, + }); + } + + private onCloseEditMediatorConfigDialog = () => { + this.setState({ + mediatorConfigEditorMode: EditMediatorConfigDialogMode.None, + mediatorConfigToEdit: emptyMediatorConfig, + }); + } +} + +export const MediatorApplication = withStyles(styles)(connect(mapProps, mapDispatch)(MediatorApplicationComponent)); diff --git a/sdnr/wt-odlux/odlux/apps/mediatorApp/src/views/mediatorServerSelection.tsx b/sdnr/wt-odlux/odlux/apps/mediatorApp/src/views/mediatorServerSelection.tsx new file mode 100644 index 000000000..fb12f2608 --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/mediatorApp/src/views/mediatorServerSelection.tsx @@ -0,0 +1,193 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * 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. + * ============LICENSE_END========================================================================== + */ +import React from 'react'; +import { Theme, Tooltip } from '@mui/material'; + +import { WithStyles } from '@mui/styles'; +import withStyles from '@mui/styles/withStyles'; +import createStyles from '@mui/styles/createStyles'; + +import AddIcon from '@mui/icons-material/Add'; +import IconButton from '@mui/material/IconButton'; +import EditIcon from '@mui/icons-material/Edit'; +import DeleteIcon from '@mui/icons-material/Delete'; +import Refresh from '@mui/icons-material/Refresh'; + +import { IApplicationStoreState } from '../../../../framework/src/store/applicationStore'; +import { connect, IDispatcher, Connect } from '../../../../framework/src/flux/connect'; +import MaterialTable, { MaterialTableCtorType, ColumnType } from '../../../../framework/src/components/material-table'; + +import { createAvaliableMediatorServersProperties, createAvaliableMediatorServersActions } from '../handlers/avaliableMediatorServersHandler'; + +import { MediatorServer } from '../models/mediatorServer'; +import EditMediatorServerDialog, { EditMediatorServerDialogMode } from '../components/editMediatorServerDialog'; +import RefreshMediatorDialog, { RefreshMediatorDialogMode } from '../components/refreshMediatorDialog'; +import { NavigateToApplication } from '../../../../framework/src/actions/navigationActions'; + +const MediatorServersTable = MaterialTable as MaterialTableCtorType; + +const styles = (theme: Theme) => createStyles({ + button: { + margin: 0, + padding: "6px 6px", + minWidth: 'unset', + }, + spacer: { + marginLeft: theme.spacing(1), + marginRight: theme.spacing(1), + display: "inline", + }, +}); + +const mapProps = (state: IApplicationStoreState) => ({ + mediatorServersProperties: createAvaliableMediatorServersProperties(state), +}); + +const mapDispatch = (dispatcher: IDispatcher) => ({ + mediatorServersActions: createAvaliableMediatorServersActions(dispatcher.dispatch), + selectMediatorServer: (mediatorServerId: string) => mediatorServerId && dispatcher.dispatch(new NavigateToApplication("mediator", mediatorServerId)), +}); + +const emptyMediatorServer: MediatorServer = { + id: "", + name: "", + url: "" +}; + +type MediatorServerSelectionComponentProps = Connect & WithStyles; + +type MediatorServerSelectionComponentState = { + mediatorServerToEdit: MediatorServer, + mediatorServerEditorMode: EditMediatorServerDialogMode, + refreshMediatorEditorMode: RefreshMediatorDialogMode +} + +let initialSorted = false; + +class MediatorServerSelectionComponent extends React.Component { + + constructor(props: MediatorServerSelectionComponentProps) { + super(props); + + this.state = { + mediatorServerEditorMode: EditMediatorServerDialogMode.None, + mediatorServerToEdit: emptyMediatorServer, + refreshMediatorEditorMode: RefreshMediatorDialogMode.None + } + } + + render() { + const { classes } = this.props; + const refreshMediatorAction = { + icon: Refresh, tooltip: 'Refresh Mediator Server Table', ariaLabel:'refresh', onClick: () => { + this.setState({ + refreshMediatorEditorMode: RefreshMediatorDialogMode.RefreshMediatorTable + }); + } + }; + + const addMediatorServerActionButton = { + icon: AddIcon, tooltip: 'Add', ariaLabel:'add-element', onClick: () => { + this.setState({ + mediatorServerEditorMode: EditMediatorServerDialogMode.AddMediatorServer, + mediatorServerToEdit: emptyMediatorServer, + }); + } + }; + return <> + ( +
+ { this.onEditMediatorServer(event, rowData); }} + size="large"> + { this.onRemoveMediatorServer(event, rowData); }} + size="large"> +
+ ) + } + ]} onHandleClick={this.onSelectMediatorServer} /> + + + ; + } + + public componentDidMount() { + + if (!initialSorted) { + initialSorted = true; + this.props.mediatorServersActions.onHandleRequestSort("name"); + } else { + this.props.mediatorServersActions.onRefresh(); + } + } + + private onSelectMediatorServer = (event: React.MouseEvent, server: MediatorServer) => { + event.preventDefault(); + event.stopPropagation(); + this.props.selectMediatorServer(server && server.id); + + } + + private onEditMediatorServer = (event: React.MouseEvent, server: MediatorServer) => { + event.preventDefault(); + event.stopPropagation(); + this.setState({ + mediatorServerEditorMode: EditMediatorServerDialogMode.EditMediatorServer, + mediatorServerToEdit: server, + }); + } + + private onRemoveMediatorServer = (event: React.MouseEvent, server: MediatorServer) => { + event.preventDefault(); + event.stopPropagation(); + this.setState({ + mediatorServerEditorMode: EditMediatorServerDialogMode.RemoveMediatorServer, + mediatorServerToEdit: server, + }); + } + + private onCloseEditMediatorServerDialog = () => { + this.setState({ + mediatorServerEditorMode: EditMediatorServerDialogMode.None, + mediatorServerToEdit: emptyMediatorServer, + }); + } + private onCloseRefreshMediatorDialog = () => { + this.setState({ + refreshMediatorEditorMode: RefreshMediatorDialogMode.None + }); + } +} + + +export const MediatorServerSelection = withStyles(styles)(connect(mapProps, mapDispatch)(MediatorServerSelectionComponent)); +export default MediatorServerSelection; \ No newline at end of file diff --git a/sdnr/wt-odlux/odlux/apps/mediatorApp/tsconfig.json b/sdnr/wt-odlux/odlux/apps/mediatorApp/tsconfig.json new file mode 100644 index 000000000..c95005660 --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/mediatorApp/tsconfig.json @@ -0,0 +1,38 @@ +{ + "compilerOptions": { + "baseUrl": "./src", + "outDir": "./dist", + "sourceMap": true, + "forceConsistentCasingInFileNames": true, + "allowSyntheticDefaultImports": true, + "allowUnreachableCode": false, + "allowUnusedLabels": false, + "noFallthroughCasesInSwitch": true, + "noImplicitAny": true, + "noImplicitReturns": true, + "noImplicitThis": true, + "strictNullChecks": true, + "pretty": true, + "newLine": "LF", + "module": "es2015", + "target": "es2016", + "moduleResolution": "node", + "experimentalDecorators": true, + "jsx": "preserve", + "lib": [ + "dom", + "es2015", + "es2016" + ], + "types": [ + "node", + "prop-types", + "react", + "react-dom" + ] + }, + "exclude": [ + "dist", + "node_modules" + ] +} diff --git a/sdnr/wt-odlux/odlux/apps/mediatorApp/tslint.json b/sdnr/wt-odlux/odlux/apps/mediatorApp/tslint.json new file mode 100644 index 000000000..b5143e92c --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/mediatorApp/tslint.json @@ -0,0 +1,4 @@ +{ + "rules":{ + } +} \ No newline at end of file diff --git a/sdnr/wt-odlux/odlux/apps/mediatorApp/webpack.config.js b/sdnr/wt-odlux/odlux/apps/mediatorApp/webpack.config.js new file mode 100644 index 000000000..813c28f37 --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/mediatorApp/webpack.config.js @@ -0,0 +1,158 @@ +/** + * Webpack 4 configuration file + * see https://webpack.js.org/configuration/ + * see https://webpack.js.org/configuration/dev-server/ + */ + +"use strict"; + +const path = require("path"); +const webpack = require("webpack"); +const CopyWebpackPlugin = require("copy-webpack-plugin"); +const TerserPlugin = require('terser-webpack-plugin'); + +// const __dirname = (path => path.replace(/^([a-z]\:)/, c => c.toUpperCase()))(process.__dirname()); + +module.exports = (env) => { + const distPath = path.resolve(__dirname, env === "release" ? "." : "../..", "dist"); + const frameworkPath = path.resolve(__dirname, env === "release" ? "../../framework" : "../..", "dist"); + return [{ + name: "App", + + mode: "none", //disable default behavior + + target: "web", + + context: path.resolve(__dirname, "src"), + + entry: { + mediatorApp: ["./plugin.tsx"] + }, + + devtool: env === "release" ? false : "source-map", + + resolve: { + extensions: [".ts", ".tsx", ".js", ".jsx"] + }, + + output: { + path: distPath, + filename: "[name].js", + library: "[name]", + libraryTarget: "umd2", + chunkFilename: "[name].js" + }, + module: { + rules: [{ + test: /\.tsx?$/, + exclude: /node_modules/, + use: [{ + loader: "babel-loader" + }, { + loader: "ts-loader" + }] + }, { + test: /\.jsx?$/, + exclude: /node_modules/, + use: [{ + loader: "babel-loader" + }] + },{ + //don't minify images + test: /\.(png|gif|jpg|svg)$/, + use: [{ + loader: 'url-loader', + options: { + limit: 10, + name: './images/[name].[ext]' + } + }] + }] + }, + + optimization: { + noEmitOnErrors: true, + namedModules: env !== "release", + minimize: env === "release", + minimizer: env !== "release" ? [] : [new TerserPlugin({ + terserOptions: { + warnings: false, // false, true, "verbose" + compress: { + drop_console: true, + drop_debugger: true, + } + } + })], + }, + + plugins: [ + new webpack.DllReferencePlugin({ + context: path.resolve(__dirname, "../../framework/src"), + manifest: require(path.resolve(frameworkPath, "vendor-manifest.json")), + sourceType: "umd2" + }), + new webpack.DllReferencePlugin({ + context: path.resolve(__dirname, "../../framework/src"), + manifest: require(path.resolve(frameworkPath, "app-manifest.json")), + sourceType: "umd2" + }), + ...(env === "release" ? [ + new webpack.DefinePlugin({ + "process.env": { + NODE_ENV: "'production'", + VERSION: JSON.stringify(require("./package.json").version) + } + }), + ] : [ + new webpack.DefinePlugin({ + "process.env": { + NODE_ENV: "'development'", + VERSION: JSON.stringify(require("./package.json").version) + } + }), + new CopyWebpackPlugin([{ + from: 'index.html', + to: distPath + }]), + ]) + ], + + devServer: { + public: "http://localhost:3100", + contentBase: frameworkPath, + + compress: true, + headers: { + "Access-Control-Allow-Origin": "*" + }, + host: "0.0.0.0", + port: 3100, + disableHostCheck: true, + historyApiFallback: true, + inline: true, + hot: false, + quiet: false, + stats: { + colors: true + }, + proxy: { + "/oauth2/": { + target: "http://localhost:3000", + secure: false + }, + "/database/": { + target: "http://localhost:3000", + secure: false + }, + "/restconf/": { + target: "http://localhost:3000", + secure: false + }, + "/help/": { + target: "http://localhost:3000", + secure: false + } + } + } + }]; +} diff --git a/sdnr/wt-odlux/odlux/apps/minimumApp/.babelrc b/sdnr/wt-odlux/odlux/apps/minimumApp/.babelrc new file mode 100644 index 000000000..3d8cd1260 --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/minimumApp/.babelrc @@ -0,0 +1,17 @@ +{ + "presets": [ + ["@babel/preset-react"], + ["@babel/preset-env", { + "targets": { + "chrome": "66" + }, + "spec": true, + "loose": false, + "modules": false, + "debug": false, + "useBuiltIns": "usage", + "forceAllTransforms": true + }] + ], + "plugins": [] +} diff --git a/sdnr/wt-odlux/odlux/apps/minimumApp/package.json b/sdnr/wt-odlux/odlux/apps/minimumApp/package.json new file mode 100644 index 000000000..2c88f301b --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/minimumApp/package.json @@ -0,0 +1,43 @@ +{ + "name": "@odlux/minimum-app", + "version": "0.1.0", + "description": "A react based modular UI to demo the minimum possible app.", + "main": "index.js", + "scripts": { + "start": "webpack-dev-server --env debug", + "build": "webpack --env release --config webpack.config.js", + "build:dev": "webpack --env debug --config webpack.config.js" + }, + "repository": { + "type": "git", + "url": "https://git.mfico.de/highstreet-technologies/odlux.git" + }, + "keywords": [ + "reactjs", + "redux", + "ui", + "framework" + ], + "author": "Matthias Fischer", + "license": "Apache-2.0", + "dependencies": { + "@emotion/react": "^11.7.0", + "@emotion/styled": "^11.6.0", + "@mui/icons-material": "^5.2.0", + "@mui/material": "^5.2.2", + "@mui/styles": "^5.2.2", + "@odlux/framework": "*" + }, + "peerDependencies": { + "@types/classnames": "2.2.6", + "@types/flux": "3.1.8", + "@types/jquery": "3.3.10", + "@types/react": "17.0.37", + "@types/react-dom": "17.0.11", + "@types/react-router-dom": "5.1.7", + "jquery": "3.3.1", + "react": "17.0.2", + "react-dom": "17.0.2", + "react-router-dom": "5.2.0" + } +} diff --git a/sdnr/wt-odlux/odlux/apps/minimumApp/pom.xml b/sdnr/wt-odlux/odlux/apps/minimumApp/pom.xml new file mode 100644 index 000000000..f23c795eb --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/minimumApp/pom.xml @@ -0,0 +1,109 @@ + + + + + 4.0.0 + + org.onap.ccsdk.features.sdnr.odlux + sdnr-odlux-app-minimumApp + 1.7.0-SNAPSHOT + jar + + SDNR ODLUX :: ${project.artifactId} + + + Apache License, Version 2.0 + http://www.apache.org/licenses/LICENSE-2.0 + + + + + true + + + + + + dist + odlux + + + + + maven-clean-plugin + + + + dist + false + + + node + false + + + node_modules + false + + + ../node_modules + false + + + + bin + false + + + + + + de.jacks-it-lab + frontend-maven-plugin + 1.7.2 + + + install node and yarn + + install-node-and-yarn + + + initialize + + v16.17.0 + v1.22.19 + + + + yarn build + + yarn + + + run build + + + + + + + diff --git a/sdnr/wt-odlux/odlux/apps/minimumApp/src/assets/icons/minimumAppIcon.svg b/sdnr/wt-odlux/odlux/apps/minimumApp/src/assets/icons/minimumAppIcon.svg new file mode 100644 index 000000000..298eaa162 --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/minimumApp/src/assets/icons/minimumAppIcon.svg @@ -0,0 +1,27 @@ + + + + + + + + + + + + diff --git a/sdnr/wt-odlux/odlux/apps/minimumApp/src/handlers/minimumAppRootHandler.ts b/sdnr/wt-odlux/odlux/apps/minimumApp/src/handlers/minimumAppRootHandler.ts new file mode 100644 index 000000000..4a4dc613a --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/minimumApp/src/handlers/minimumAppRootHandler.ts @@ -0,0 +1,38 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * 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. + * ============LICENSE_END========================================================================== + */ +// main state handler + +import { combineActionHandler } from '../../../../framework/src/flux/middleware'; + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +import { IApplicationStoreState } from '../../../../framework/src/store/applicationStore'; + +export interface IMinimumAppStoreState { +} + +declare module '../../../../framework/src/store/applicationStore' { + interface IApplicationStoreState { + minimum: IMinimumAppStoreState; + } +} + +const actionHandlers = { +}; + +export const minimumAppRootHandler = combineActionHandler(actionHandlers); +export default minimumAppRootHandler; diff --git a/sdnr/wt-odlux/odlux/apps/minimumApp/src/index.html b/sdnr/wt-odlux/odlux/apps/minimumApp/src/index.html new file mode 100644 index 000000000..58865edc1 --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/minimumApp/src/index.html @@ -0,0 +1,24 @@ + + + + + + + + + Minimal App + + + +
+ + + + + + \ No newline at end of file diff --git a/sdnr/wt-odlux/odlux/apps/minimumApp/src/plugin.tsx b/sdnr/wt-odlux/odlux/apps/minimumApp/src/plugin.tsx new file mode 100644 index 000000000..5c8500a98 --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/minimumApp/src/plugin.tsx @@ -0,0 +1,48 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * 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. + * ============LICENSE_END========================================================================== + */ +// app configuration and main entry point for the app + +import React, { FC } from 'react'; +import { withRouter, RouteComponentProps } from 'react-router-dom'; + +import applicationManager from '../../../framework/src/services/applicationManager'; +import { connect, Connect } from '../../../framework/src/flux/connect'; + +import { minimumAppRootHandler } from './handlers/minimumAppRootHandler'; + +// const appIcon = require('./assets/icons/minimunAppIcon.svg'); // select app icon + +type AppProps = RouteComponentProps & Connect; + +const App: FC = (_props) => ( +
Start your app here!!
+); + +const FinalApp = withRouter(connect()(App)); + +export function register() { + applicationManager.registerApplication({ + name: 'minimum', + // icon: appIcon, + rootComponent: FinalApp, + rootActionHandler: minimumAppRootHandler, + menuEntry: 'Minimum', + }); +} + + diff --git a/sdnr/wt-odlux/odlux/apps/minimumApp/tsconfig.json b/sdnr/wt-odlux/odlux/apps/minimumApp/tsconfig.json new file mode 100644 index 000000000..ca65092e0 --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/minimumApp/tsconfig.json @@ -0,0 +1,37 @@ +{ + "compilerOptions": { + "baseUrl": "./src", + "outDir": "./dist", + "sourceMap": true, + "forceConsistentCasingInFileNames": true, + "allowSyntheticDefaultImports": true, + "allowUnreachableCode": false, + "allowUnusedLabels": false, + "noFallthroughCasesInSwitch": true, + "noImplicitAny": true, + "noImplicitReturns": true, + "noImplicitThis": true, + "strictNullChecks": true, + "pretty": true, + "newLine": "LF", + "module": "es2015", + "target": "es2016", + "moduleResolution": "node", + "experimentalDecorators": true, + "jsx": "preserve", + "lib": [ + "dom", + "es2015", + "es2016" + ], + "types": [ + "prop-types", + "react", + "react-dom" + ] + }, + "exclude": [ + "dist", + "node_modules" + ] +} diff --git a/sdnr/wt-odlux/odlux/apps/minimumApp/webpack.config.js b/sdnr/wt-odlux/odlux/apps/minimumApp/webpack.config.js new file mode 100644 index 000000000..50b24e718 --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/minimumApp/webpack.config.js @@ -0,0 +1,136 @@ +/** + * Webpack 4 configuration file + * see https://webpack.js.org/configuration/ + * see https://webpack.js.org/configuration/dev-server/ + */ + +"use strict"; + +const path = require("path"); +const webpack = require("webpack"); +const CopyWebpackPlugin = require("copy-webpack-plugin"); +const TerserPlugin = require('terser-webpack-plugin'); + +// const __dirname = (path => path.replace(/^([a-z]\:)/, c => c.toUpperCase()))(process.__dirname()); + +module.exports = (env) => { + const distPath = path.resolve(__dirname, env === "release" ? "." : "../..", "dist"); + const frameworkPath = path.resolve(__dirname, env === "release" ? "../../framework" : "../..", "dist"); + return [{ + name: "App", + + mode: "none", //disable default behavior + + target: "web", + + context: path.resolve(__dirname, "src"), + + entry: { + minimumApp: ["./plugin.tsx"] + }, + + devtool: env === "release" ? false : "source-map", + + resolve: { + extensions: [".ts", ".tsx", ".js", ".jsx"] + }, + + output: { + path: distPath, + filename: "[name].js", + library: "[name]", + libraryTarget: "umd2", + chunkFilename: "[name].js" + }, + module: { + rules: [{ + test: /\.tsx?$/, + exclude: /node_modules/, + use: [{ + loader: "babel-loader" + }, { + loader: "ts-loader" + }] + }, { + test: /\.jsx?$/, + exclude: /node_modules/, + use: [{ + loader: "babel-loader" + }] + }] + }, + + optimization: { + noEmitOnErrors: true, + namedModules: env !== "release", + minimize: env === "release", + minimizer: env !== "release" ? [] : [new TerserPlugin({ + terserOptions: { + warnings: false, // false, true, "verbose" + compress: { + drop_console: true, + drop_debugger: true, + } + } + })], + }, + + plugins: [ + new webpack.DllReferencePlugin({ + context: path.resolve(__dirname, "../../framework/src"), + manifest: require(path.resolve(frameworkPath, "vendor-manifest.json")), + sourceType: "umd2" + }), + new webpack.DllReferencePlugin({ + context: path.resolve(__dirname, "../../framework/src"), + manifest: require(path.resolve(frameworkPath, "app-manifest.json")), + sourceType: "umd2" + }), + ...(env === "release" ? [ + new webpack.DefinePlugin({ + "process.env": { + NODE_ENV: "'production'", + VERSION: JSON.stringify(require("./package.json").version) + } + }), + ] : [ + new webpack.DefinePlugin({ + "process.env": { + NODE_ENV: "'development'", + VERSION: JSON.stringify(require("./package.json").version) + } + }), + new CopyWebpackPlugin([{ + from: 'index.html', + to: distPath + }]), + ]) + ], + + devServer: { + public: "http://localhost:3100", + contentBase: frameworkPath, + + compress: true, + headers: { + "Access-Control-Allow-Origin": "*" + }, + host: "0.0.0.0", + port: 3100, + disableHostCheck: true, + historyApiFallback: true, + inline: true, + hot: false, + quiet: false, + stats: { + colors: true + }, + proxy: { + "/api": { + target: "http://localhost:3001", + secure: false + } + } + } + }]; +} diff --git a/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/.babelrc b/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/.babelrc new file mode 100644 index 000000000..3d8cd1260 --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/.babelrc @@ -0,0 +1,17 @@ +{ + "presets": [ + ["@babel/preset-react"], + ["@babel/preset-env", { + "targets": { + "chrome": "66" + }, + "spec": true, + "loose": false, + "modules": false, + "debug": false, + "useBuiltIns": "usage", + "forceAllTransforms": true + }] + ], + "plugins": [] +} diff --git a/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/package.json b/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/package.json new file mode 100644 index 000000000..9a08612b8 --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/package.json @@ -0,0 +1,46 @@ +{ + "name": "@odlux/performancehistory-app", + "version": "0.1.1", + "description": "A react based modular UI to display performance history data from a database.", + "main": "index.js", + "scripts": { + "start": "webpack-dev-server --env debug", + "build": "webpack --env release --config webpack.config.js", + "build:dev": "webpack --env debug --config webpack.config.js" + }, + "repository": { + "type": "git", + "url": "https://git.mfico.de/highstreet-technologies/odlux.git" + }, + "keywords": [ + "reactjs", + "redux", + "ui", + "framework" + ], + "author": "Sai Neetha Phulmali", + "license": "Apache-2.0", + "dependencies": { + "@emotion/react": "^11.7.0", + "@emotion/styled": "^11.6.0", + "@mui/icons-material": "^5.2.0", + "@mui/material": "^5.2.2", + "@mui/styles": "^5.2.2", + "@odlux/connect-app": "*", + "@odlux/framework": "*", + "chart.js": "2.8.0", + "react-chartjs-2": "2.7.6" + }, + "peerDependencies": { + "@types/classnames": "2.2.6", + "@types/flux": "3.1.8", + "@types/jquery": "3.3.10", + "@types/react": "17.0.37", + "@types/react-dom": "17.0.11", + "@types/react-router-dom": "5.1.7", + "jquery": "3.3.1", + "react": "17.0.2", + "react-dom": "17.0.2", + "react-router-dom": "5.2.0" + } +} diff --git a/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/pom.xml b/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/pom.xml new file mode 100644 index 000000000..eb0160f67 --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/pom.xml @@ -0,0 +1,105 @@ + + + + + 4.0.0 + + org.onap.ccsdk.features.sdnr.odlux + sdnr-odlux-app-performanceHistoryApp + 1.7.0-SNAPSHOT + jar + + SDNR ODLUX :: ${project.artifactId} + + + Apache License, Version 2.0 + http://www.apache.org/licenses/LICENSE-2.0 + + + + + + + dist + odlux + + + + + maven-clean-plugin + + + + dist + false + + + node + false + + + node_modules + false + + + ../node_modules + false + + + + bin + false + + + + + + de.jacks-it-lab + frontend-maven-plugin + 1.7.2 + + + install node and yarn + + install-node-and-yarn + + + initialize + + v16.17.0 + v1.22.19 + + + + yarn build + + yarn + + + run build + + + + + + + diff --git a/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/actions/deviceListActions.ts b/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/actions/deviceListActions.ts new file mode 100644 index 000000000..fbbf2c5f2 --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/actions/deviceListActions.ts @@ -0,0 +1,79 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * 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. + * ============LICENSE_END========================================================================== + */ +import { Action } from '../../../../framework/src/flux/action'; +import { Dispatch } from '../../../../framework/src/flux/store'; +// eslint-disable-next-line @typescript-eslint/no-unused-vars +import { IApplicationStoreState } from '../../../../framework/src/store/applicationStore'; + +import { DeviceListType } from '../models/deviceListType'; +import { PerformanceHistoryService } from '../services/performanceHistoryService'; + +/** + * Represents the base action. + */ +export class BaseAction extends Action { } + +/** + * Represents an action causing the store to load all devices. + */ +export class LoadAllDeviceListAction extends BaseAction { } + +/** + * Represents an action causing the store to update all devices. + */ +export class AllDeviceListLoadedAction extends BaseAction { + /** + * Initialize this instance. + * + * @param deviceList All the distinct devices from the performance history database. + */ + constructor(public deviceList: DeviceListType[] | null, public error?: string) { + super(); + } +} + +/** + * Represents an asynchronous thunk action to load all devices. + */ +export const loadAllDeviceListAsync = async (dispatch: Dispatch) => { + dispatch(new LoadAllDeviceListAction()); + const deviceListFromPerfHistory: DeviceListType[] = (await PerformanceHistoryService.getDeviceListfromPerf15minHistory().then(ne => (ne))) || []; + const deviceListFromPerf24History: DeviceListType[] = (await PerformanceHistoryService.getDeviceListfromPerf24hHistory().then(ne => (ne))) || []; + deviceListFromPerf24History.forEach(deviceList24h => { + if (deviceListFromPerfHistory.findIndex(deviceList15min => deviceList15min.nodeId === deviceList24h.nodeId) < 0) { + deviceListFromPerfHistory.push(deviceList24h); + } + }); + return deviceListFromPerfHistory && dispatch(new AllDeviceListLoadedAction(deviceListFromPerfHistory)); +}; + +/** + * Represents an action causing the store to update mountId. + */ +export class UpdateMountId extends BaseAction { + constructor(public nodeId?: string) { + super(); + } +} + +/** + * Represents an asynchronous thunk action to load updated mountId. + */ +export const updateMountIdActionCreator = (nodeId: string) => async (dispatch: Dispatch) => { + return dispatch(new UpdateMountId(nodeId)); +}; diff --git a/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/actions/ltpAction.ts b/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/actions/ltpAction.ts new file mode 100644 index 000000000..1c333ab50 --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/actions/ltpAction.ts @@ -0,0 +1,94 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * 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. + * ============LICENSE_END========================================================================== + */ +import { Action } from '../../../../framework/src/flux/action'; +import { Dispatch } from '../../../../framework/src/flux/store'; + +import { LtpIds } from '../models/availableLtps'; +import { PerformanceHistoryService } from '../services/performanceHistoryService'; + +/** + * Represents the base action. + */ +export class BaseAction extends Action { } + +/** + * Represents an action causing the store to load available ltps. + */ +export class LoadAllAvailableLtpsAction extends BaseAction { } + +/** + * Represents an action causing the store to update available ltps. + */ +export class AllAvailableLtpsLoadedAction extends BaseAction { + /** + * Initialize this instance. + * @param availableLtps The available ltps which are returned from the database. + */ + constructor(public availableLtps: LtpIds[] | null, public error?: string) { + super(); + } +} + +export class SetInitialLoadedAction extends BaseAction { + constructor(public initialLoaded: boolean) { + super(); + } +} + +export class NoLtpsFoundAction extends BaseAction { } + +export class ResetLtpsAction extends BaseAction { } + +const getDistinctLtps = (distinctLtps: LtpIds[], selectedLtp: string, selectFirstLtp?: Function, resetLtp?: Function) => { + let ltpNotSelected: boolean = true; + // eslint-disable-next-line @typescript-eslint/no-unused-expressions + selectFirstLtp && selectFirstLtp(distinctLtps[0].key); + distinctLtps.forEach((value: LtpIds) => { + if (value.key === selectedLtp) { + ltpNotSelected = false; + } + }); + // eslint-disable-next-line @typescript-eslint/no-unused-expressions + resetLtp && resetLtp(ltpNotSelected); + return distinctLtps; +}; + +/** + * Represents an asynchronous thunk action to load available distinctLtps by networkElement from the database and set the returned first Ltp as default. + * @param networkElement The network element sent to database to get its available distinct Ltps. + * @param selectedTimePeriod The time period selected sent to database to get the distinct Ltps of the selected network element. + * @param selectedLtp The Ltp which is selected in the dropdown. + * @param selectFirstLtp The function to get the first ltp returned from the database to be selected as default on selection upon network element. + * @param resetLtp The function to verify if the selected ltp is also available in the selected time period database else reset the Ltp dropdown to select. + */ +export const loadDistinctLtpsbyNetworkElementAsync = (networkElement: string, selectedTimePeriod: string, selectedLtp: string, selectFirstLtp?: Function, resetLtp?: Function) => (dispatch: Dispatch) => { + dispatch(new LoadAllAvailableLtpsAction()); + PerformanceHistoryService.getDistinctLtpsFromDatabase(networkElement, selectedTimePeriod).then(distinctLtps => { + if (distinctLtps) { + const ltps = getDistinctLtps(distinctLtps, selectedLtp, selectFirstLtp, resetLtp); + dispatch(new AllAvailableLtpsLoadedAction(ltps)); + } else { + if (resetLtp) + resetLtp(); + dispatch(new NoLtpsFoundAction()); + } + }).catch(error => { + dispatch(new AllAvailableLtpsLoadedAction(null, error)); + }); +}; + diff --git a/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/actions/panelChangeActions.ts b/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/actions/panelChangeActions.ts new file mode 100644 index 000000000..8b77cb3b0 --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/actions/panelChangeActions.ts @@ -0,0 +1,32 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * 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. + * ============LICENSE_END========================================================================== + */ +import { Action } from '../../../../framework/src/flux/action'; +import { PanelId } from '../models/panelId'; + +/** + * Represents an action causing the store to update the panel. + */ +export class SetPanelAction extends Action { + /** + * Initialize this instance. + * @param panelId Action to set the current panel by its Id. + */ + constructor(public panelId: PanelId) { + super(); + } +} diff --git a/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/actions/reloadAction.ts b/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/actions/reloadAction.ts new file mode 100644 index 000000000..6bd54ce96 --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/actions/reloadAction.ts @@ -0,0 +1,25 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2020 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * 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. + * ============LICENSE_END========================================================================== + */ + +import { Action } from '../../../../framework/src/flux/action'; + +export class ReloadAction extends Action { + constructor(public show: boolean) { + super(); + } +} \ No newline at end of file diff --git a/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/actions/timeChangeAction.ts b/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/actions/timeChangeAction.ts new file mode 100644 index 000000000..13214b24b --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/actions/timeChangeAction.ts @@ -0,0 +1,30 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * 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. + * ============LICENSE_END========================================================================== + */ +import { Action } from '../../../../framework/src/flux/action'; + +import { PmDataInterval } from '../models/performanceDataType'; + +export class TimeChangeAction extends Action { + /** + * Initialize this instance. + * @param time Action to set the time interval in dropdown. + */ + constructor(public time: PmDataInterval) { + super(); + } +} \ No newline at end of file diff --git a/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/actions/toggleActions.ts b/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/actions/toggleActions.ts new file mode 100644 index 000000000..7921ea541 --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/actions/toggleActions.ts @@ -0,0 +1,36 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2020 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * 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. + * ============LICENSE_END========================================================================== + */ + +import { Action } from '../../../../framework/src/flux/action'; + +import { currentViewType } from '../models/toggleDataType'; + + +export class SetSubViewAction extends Action { + constructor(public currentView: currentViewType, public selectedTab: 'chart' | 'table') { + super(); + } +} + +export class ResetAllSubViewsAction extends Action { } + +export class SetFilterVisibility extends Action { + constructor(public currentView: currentViewType, public isVisible: boolean) { + super(); + } +} \ No newline at end of file diff --git a/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/assets/icons/performanceHistoryAppIcon.svg b/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/assets/icons/performanceHistoryAppIcon.svg new file mode 100644 index 000000000..982f1eec3 --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/assets/icons/performanceHistoryAppIcon.svg @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/components/adaptiveModulation.tsx b/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/components/adaptiveModulation.tsx new file mode 100644 index 000000000..5dac0bc14 --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/components/adaptiveModulation.tsx @@ -0,0 +1,489 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * 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. + * ============LICENSE_END========================================================================== + */ +import React from 'react'; +import { RouteComponentProps, withRouter } from 'react-router-dom'; + +import { ColumnModel, ColumnType, MaterialTable, MaterialTableCtorType } from '../../../../framework/src/components/material-table'; +import { connect, Connect, IDispatcher } from '../../../../framework/src/flux/connect'; +import { IApplicationStoreState } from '../../../../framework/src/store/applicationStore'; + +import { SetFilterVisibility, SetSubViewAction } from '../actions/toggleActions'; +import { createAdaptiveModulationActions, createAdaptiveModulationProperties } from '../handlers/adaptiveModulationHandler'; +import { AdaptiveModulationDatabaseDataType, AdaptiveModulationDataType } from '../models/adaptiveModulationDataType'; +import { IDataSet, IDataSetsObject } from '../models/chartTypes'; +import { lineChart, sortDataByTimeStamp } from '../utils/chartUtils'; +import { addColumnLabels } from '../utils/tableUtils'; +import ToggleContainer from './toggleContainer'; + +const mapProps = (state: IApplicationStoreState) => ({ + adaptiveModulationProperties: createAdaptiveModulationProperties(state), + currentView: state.performanceHistory.subViews.adaptiveModulation.subView, + isFilterVisible: state.performanceHistory.subViews.adaptiveModulation.isFilterVisible, + existingFilter: state.performanceHistory.adaptiveModulation.filter, +}); + +const mapDisp = (dispatcher: IDispatcher) => ({ + adaptiveModulationActions: createAdaptiveModulationActions(dispatcher.dispatch), + setSubView: (value: 'chart' | 'table') => dispatcher.dispatch(new SetSubViewAction('adaptiveModulation', value)), + toggleFilterButton: (value: boolean) => { dispatcher.dispatch(new SetFilterVisibility('adaptiveModulation', value)); }, +}); + +type AdaptiveModulationComponentProps = RouteComponentProps & Connect & { + selectedTimePeriod: string; +}; + +const AdaptiveModulationTable = MaterialTable as MaterialTableCtorType; + +/** + * The Component which gets the adaptiveModulation data from the database based on the selected time period. + */ +class AdaptiveModulationComponent extends React.Component { + onToggleFilterButton = () => { + this.props.toggleFilterButton(!this.props.isFilterVisible); + }; + + onChange = (value: 'chart' | 'table') => { + this.props.setSubView(value); + }; + + onFilterChanged = (property: string, filterTerm: string) => { + this.props.adaptiveModulationActions.onFilterChanged(property, filterTerm); + if (!this.props.adaptiveModulationProperties.showFilter) + this.props.adaptiveModulationActions.onToggleFilter(false); + }; + + render(): JSX.Element { + const properties = this.props.adaptiveModulationProperties; + const actions = this.props.adaptiveModulationActions; + + const chartPagedData = this.getChartDataValues(properties.rows); + const adaptiveModulationColumns: ColumnModel[] = [ + { property: 'radioSignalId', title: 'Radio signal', type: ColumnType.text }, + { property: 'scannerId', title: 'Scanner ID', type: ColumnType.text }, + { property: 'timeStamp', title: 'End Time', type: ColumnType.text }, + { + property: 'suspectIntervalFlag', title: 'Suspect Interval', type: ColumnType.boolean, + }]; + + chartPagedData.datasets.forEach(ds => { + adaptiveModulationColumns.push(addColumnLabels(ds.name, ds.columnLabel)); + }); + + return ( + <> + + {lineChart(chartPagedData)} + + + + ); + } + + /** + * This function gets the performance values for Adaptive modulation according on the chartjs dataset structure + * which is to be sent to the chart. + */ + + private getChartDataValues = (rows: AdaptiveModulationDataType[]): IDataSetsObject => { + const data_rows = [...rows]; + sortDataByTimeStamp(data_rows); + + const datasets: IDataSet[] = [{ + name: 'time2StatesS', + label: 'QAM2S', + borderColor: '#62a309fc', + bezierCurve: false, + lineTension: 0, + fill: false, + data: [], + columnLabel: 'QAM2S', + }, { + name: 'time2States', + label: 'QAM2', + borderColor: '#62a309fc', + bezierCurve: false, + lineTension: 0, + fill: false, + data: [], + columnLabel: 'QAM2', + }, { + name: 'time2StatesL', + label: 'QAM2L', + borderColor: '#62a309fc', + bezierCurve: false, + lineTension: 0, + fill: false, + data: [], + columnLabel: 'QAM2L', + }, { + name: 'time4StatesS', + label: 'QAM4S', + borderColor: '#b308edde', + bezierCurve: false, + lineTension: 0, + fill: false, + data: [], + columnLabel: 'QAM4S', + }, { + name: 'time4States', + label: 'QAM4', + borderColor: '#b308edde', + bezierCurve: false, + lineTension: 0, + fill: false, + data: [], + columnLabel: 'QAM4', + }, { + name: 'time4StatesL', + label: 'QAM4L', + borderColor: '#b308edde', + bezierCurve: false, + lineTension: 0, + fill: false, + data: [], + columnLabel: 'QAM4L', + }, { + name: 'time16StatesS', + label: 'QAM16S', + borderColor: '#9b15e2', + bezierCurve: false, + lineTension: 0, + fill: false, + data: [], + columnLabel: 'QAM16S', + }, { + name: 'time16States', + label: 'QAM16', + borderColor: '#9b15e2', + bezierCurve: false, + lineTension: 0, + fill: false, + data: [], + columnLabel: 'QAM16', + }, { + name: 'time16StatesL', + label: 'QAM16L', + borderColor: '#9b15e2', + bezierCurve: false, + lineTension: 0, + fill: false, + data: [], + columnLabel: 'QAM16L', + }, { + name: 'time32StatesS', + label: 'QAM32S', + borderColor: '#2704f5f0', + bezierCurve: false, + lineTension: 0, + fill: false, + data: [], + columnLabel: 'QAM32S', + }, { + name: 'time32States', + label: 'QAM32', + borderColor: '#2704f5f0', + bezierCurve: false, + lineTension: 0, + fill: false, + data: [], + columnLabel: 'QAM32', + }, { + name: 'time32StatesL', + label: 'QAM32L', + borderColor: '#2704f5f0', + bezierCurve: false, + lineTension: 0, + fill: false, + data: [], + columnLabel: 'QAM32L', + }, { + name: 'time64StatesS', + label: 'QAM64S', + borderColor: '#347692', + bezierCurve: false, + lineTension: 0, + fill: false, + data: [], + columnLabel: 'QAM64S', + }, { + name: 'time64States', + label: 'QAM64', + borderColor: '#347692', + bezierCurve: false, + lineTension: 0, + fill: false, + data: [], + columnLabel: 'QAM64', + }, { + name: 'time64StatesL', + label: 'QAM64L', + borderColor: '#347692', + bezierCurve: false, + lineTension: 0, + fill: false, + data: [], + columnLabel: 'QAM64L', + }, { + name: 'time128StatesS', + label: 'QAM128S', + borderColor: '#885e22', + bezierCurve: false, + lineTension: 0, + fill: false, + data: [], + columnLabel: 'QAM128S', + }, { + name: 'time128States', + label: 'QAM128', + borderColor: '#885e22', + bezierCurve: false, + lineTension: 0, + fill: false, + data: [], + columnLabel: 'QAM128', + }, { + name: 'time128StatesL', + label: 'QAM128L', + borderColor: '#885e22', + bezierCurve: false, + lineTension: 0, + fill: false, + data: [], + columnLabel: 'QAM128L', + }, { + name: 'time256StatesS', + label: 'QAM256S', + borderColor: '#de07807a', + bezierCurve: false, + lineTension: 0, + fill: false, + data: [], + columnLabel: 'QAM256S', + }, { + name: 'time256States', + label: 'QAM256', + borderColor: '#de07807a', + bezierCurve: false, + lineTension: 0, + fill: false, + data: [], + columnLabel: 'QAM256', + }, { + name: 'time256StatesL', + label: 'QAM256L', + borderColor: '#de07807a', + bezierCurve: false, + lineTension: 0, + fill: false, + data: [], + columnLabel: 'QAM256L', + }, { + name: 'time512StatesS', + label: 'QAM512S', + borderColor: '#8fdaacde', + bezierCurve: false, + lineTension: 0, + fill: false, + data: [], + columnLabel: 'QAM512S', + }, { + name: 'time512States', + label: 'QAM512', + borderColor: '#8fdaacde', + bezierCurve: false, + lineTension: 0, + fill: false, + data: [], + columnLabel: 'QAM512', + }, { + + name: 'time512StatesL', + label: 'QAM512L', + borderColor: '#8fdaacde', + bezierCurve: false, + lineTension: 0, + fill: false, + data: [], + columnLabel: 'QAM512L', + }, { + + name: 'time1024StatesS', + label: 'QAM1024S', + borderColor: '#435b22', + bezierCurve: false, + lineTension: 0, + fill: false, + data: [], + columnLabel: 'QAM1024S', + }, { + + name: 'time1024States', + label: 'QAM1024', + borderColor: '#435b22', + bezierCurve: false, + lineTension: 0, + fill: false, + data: [], + columnLabel: 'QAM1024', + }, { + + name: 'time1024StatesL', + label: 'QAM1024L', + borderColor: '#435b22', + bezierCurve: false, + lineTension: 0, + fill: false, + data: [], + columnLabel: 'QAM1024L', + }, { + name: 'time2048StatesS', + label: 'QAM2048S', + borderColor: '#e87a5b', + bezierCurve: false, + lineTension: 0, + fill: false, + data: [], + columnLabel: 'QAM2048S', + }, { + name: 'time2048States', + label: 'QAM2048', + borderColor: '#e87a5b', + bezierCurve: false, + lineTension: 0, + fill: false, + data: [], + columnLabel: 'QAM2048', + }, { + name: 'time2048StatesL', + label: 'QAM2048L', + borderColor: '#e87a5b', + bezierCurve: false, + lineTension: 0, + fill: false, + data: [], + columnLabel: 'QAM2048L', + }, { + name: 'time4096StatesS', + label: 'QAM4096S', + borderColor: '#5be878', + bezierCurve: false, + lineTension: 0, + fill: false, + data: [], + columnLabel: 'QAM4096S', + }, { + name: 'time4096States', + label: 'QAM4096', + borderColor: '#5be878', + bezierCurve: false, + lineTension: 0, + fill: false, + data: [], + columnLabel: 'QAM4096', + }, { + name: 'time4096StatesL', + label: 'QAM4096L', + borderColor: '#5be878', + bezierCurve: false, + lineTension: 0, + fill: false, + data: [], + columnLabel: 'QAM4096L', + }, { + name: 'time8192StatesS', + label: 'QAM8192s', + borderColor: '#cb5be8', + bezierCurve: false, + lineTension: 0, + fill: false, + data: [], + columnLabel: 'QAM8192S', + }, { + name: 'time8192States', + label: 'QAM8192', + borderColor: '#cb5be8', + bezierCurve: false, + lineTension: 0, + fill: false, + data: [], + columnLabel: 'QAM8192', + }, { + name: 'time8192StatesL', + label: 'QAM8192L', + borderColor: '#cb5be8', + bezierCurve: false, + lineTension: 0, + fill: false, + data: [], + columnLabel: 'QAM8192L', + }, + ]; + + data_rows.forEach(row => { + row.time2StatesS = row.performanceData.time2StatesS; + row.time2States = row.performanceData.time2States; + row.time2StatesL = row.performanceData.time2StatesL; + row.time4StatesS = row.performanceData.time4StatesS; + row.time4States = row.performanceData.time4States; + row.time4StatesL = row.performanceData.time4StatesL; + row.time16StatesS = row.performanceData.time16StatesS; + row.time16States = row.performanceData.time16States; + row.time16StatesL = row.performanceData.time16StatesL; + row.time32StatesS = row.performanceData.time32StatesS; + row.time32States = row.performanceData.time32States; + row.time32StatesL = row.performanceData.time32StatesL; + row.time64StatesS = row.performanceData.time64StatesS; + row.time64States = row.performanceData.time64States; + row.time64StatesL = row.performanceData.time64StatesL; + row.time128StatesS = row.performanceData.time128StatesS; + row.time128States = row.performanceData.time128States; + row.time128StatesL = row.performanceData.time128StatesL; + row.time256StatesS = row.performanceData.time256StatesS; + row.time256States = row.performanceData.time256States; + row.time256StatesL = row.performanceData.time256StatesL; + row.time512StatesS = row.performanceData.time512StatesS; + row.time512States = row.performanceData.time512States; + row.time512StatesL = row.performanceData.time512StatesL; + row.time1024StatesS = row.performanceData.time1024StatesS; + row.time1024States = row.performanceData.time1024States; + row.time1024StatesL = row.performanceData.time1024StatesL; + row.time2048StatesS = row.performanceData.time2048StatesS; + row.time2048States = row.performanceData.time2048States; + row.time2048StatesL = row.performanceData.time2048StatesL; + row.time4096StatesS = row.performanceData.time4096StatesS; + row.time4096States = row.performanceData.time4096States; + row.time4096StatesL = row.performanceData.time4096StatesL; + row.time8192StatesS = row.performanceData.time8192StatesS; + row.time8192States = row.performanceData.time8192States; + row.time8192StatesL = row.performanceData.time8192StatesL; + datasets.forEach(ds => { + ds.data.push({ + x: row['timeStamp' as keyof AdaptiveModulationDataType] as string, + y: row.performanceData[ds.name as keyof AdaptiveModulationDatabaseDataType] as string, + }); + }); + }); + + return { + datasets: datasets, + }; + }; +} +const AdaptiveModulation = withRouter(connect(mapProps, mapDisp)(AdaptiveModulationComponent)); +export default AdaptiveModulation; diff --git a/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/components/chartFilter.tsx b/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/components/chartFilter.tsx new file mode 100644 index 000000000..021f74a4f --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/components/chartFilter.tsx @@ -0,0 +1,75 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2020 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * 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. + * ============LICENSE_END========================================================================== + */ + + +import * as React from 'react'; + +import { TextField, Select, MenuItem, FormControl, InputLabel } from '@mui/material'; + +import makeStyles from '@mui/styles/makeStyles'; + +const styles = makeStyles({ + filterInput: { + marginRight: '15px', + }, + filterContainer: { + marginLeft: '90px', + }, +}); + +type filterProps = { isVisible: boolean; onFilterChanged: (property: string, filterTerm: string) => void; filters: any }; + +const ChartFilter: React.FunctionComponent = (props) => { + + + const classes = styles(); + + // make sure suspectIntervalFlag is a string to show the correct value in the select element + + const suspectIntervalFlag = props.filters.suspectIntervalFlag === undefined ? undefined : props.filters.suspectIntervalFlag.toString(); + return ( + <> + { + props.isVisible && +
+ props.onFilterChanged('radioSignalId', event.target.value)} InputLabelProps={{ + shrink: true, + }} /> + props.onFilterChanged('scannerId', event.target.value)} InputLabelProps={{ + shrink: true, + }} /> + props.onFilterChanged('timeStamp', event.target.value)} InputLabelProps={{ + shrink: true, + }} /> + + Suspect Interval + + + + + } + + ); +}; + +export default ChartFilter; \ No newline at end of file diff --git a/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/components/crossPolarDiscrimination.tsx b/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/components/crossPolarDiscrimination.tsx new file mode 100644 index 000000000..5f925a9ad --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/components/crossPolarDiscrimination.tsx @@ -0,0 +1,155 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * 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. + * ============LICENSE_END========================================================================== + */ +import React from 'react'; +import { RouteComponentProps, withRouter } from 'react-router-dom'; + +import { ColumnModel, ColumnType, MaterialTable, MaterialTableCtorType } from '../../../../framework/src/components/material-table'; +import { connect, Connect, IDispatcher } from '../../../../framework/src/flux/connect'; +import { IApplicationStoreState } from '../../../../framework/src/store/applicationStore'; + +import { SetFilterVisibility, SetSubViewAction } from '../actions/toggleActions'; +import { createCrossPolarDiscriminationActions, createCrossPolarDiscriminationProperties } from '../handlers/crossPolarDiscriminationHandler'; +import { IDataSet, IDataSetsObject } from '../models/chartTypes'; +import { CrossPolarDiscriminationDatabaseDataType, CrossPolarDiscriminationDataType } from '../models/crossPolarDiscriminationDataType'; +import { lineChart, sortDataByTimeStamp } from '../utils/chartUtils'; +import { addColumnLabels } from '../utils/tableUtils'; +import ToggleContainer from './toggleContainer'; + + +const mapProps = (state: IApplicationStoreState) => ({ + crossPolarDiscriminationProperties: createCrossPolarDiscriminationProperties(state), + currentView: state.performanceHistory.subViews.CPD.subView, + isFilterVisible: state.performanceHistory.subViews.CPD.isFilterVisible, + existingFilter: state.performanceHistory.crossPolarDiscrimination.filter, + +}); + +const mapDisp = (dispatcher: IDispatcher) => ({ + crossPolarDiscriminationActions: createCrossPolarDiscriminationActions(dispatcher.dispatch), + setSubView: (value: 'chart' | 'table') => dispatcher.dispatch(new SetSubViewAction('CPD', value)), + toggleFilterButton: (value: boolean) => { dispatcher.dispatch(new SetFilterVisibility('CPD', value));}, +}); + +type CrossPolarDiscriminationComponentProps = RouteComponentProps & Connect & { + selectedTimePeriod: string; +}; + +const CrossPolarDiscriminationTable = MaterialTable as MaterialTableCtorType; + +/** + * The Component which gets the crossPolarDiscrimination data from the database based on the selected time period. + */ +class CrossPolarDiscriminationComponent extends React.Component { + onToggleFilterButton = () => { + this.props.toggleFilterButton(!this.props.isFilterVisible); + }; + + onChange = (value: 'chart' | 'table') => { + this.props.setSubView(value); + }; + + onFilterChanged = (property: string, filterTerm: string) => { + this.props.crossPolarDiscriminationActions.onFilterChanged(property, filterTerm); + if (!this.props.crossPolarDiscriminationProperties.showFilter) + this.props.crossPolarDiscriminationActions.onToggleFilter(false); + }; + + render(): JSX.Element { + const properties = this.props.crossPolarDiscriminationProperties; + const actions = this.props.crossPolarDiscriminationActions; + + const chartPagedData = this.getChartDataValues(properties.rows); + + const cpdColumns: ColumnModel[] = [ + { property: 'radioSignalId', title: 'Radio signal', type: ColumnType.text }, + { property: 'scannerId', title: 'Scanner ID', type: ColumnType.text }, + { property: 'timeStamp', title: 'End Time', type: ColumnType.text }, + { + property: 'suspectIntervalFlag', title: 'Suspect Interval', type: ColumnType.boolean, + }, + ]; + + chartPagedData.datasets.forEach(ds => { + cpdColumns.push(addColumnLabels(ds.name, ds.columnLabel)); + }); + return ( + <> + + {lineChart(chartPagedData)} + + + + ); + } + + /** + * This function gets the performance values for CPD according on the chartjs dataset structure + * which is to be sent to the chart. + */ + private getChartDataValues = (rows: CrossPolarDiscriminationDataType[]): IDataSetsObject => { + const data_rows = [...rows]; + sortDataByTimeStamp(data_rows); + + const datasets: IDataSet[] = [{ + name: 'xpdMin', + label: 'xpd-min', + borderColor: '#0e17f3de', + bezierCurve: false, + lineTension: 0, + fill: false, + data: [], + columnLabel: 'CPD (min)[db]', + }, { + name: 'xpdAvg', + label: 'xpd-avg', + borderColor: '#08edb6de', + bezierCurve: false, + lineTension: 0, + fill: false, + data: [], + columnLabel: 'CPD (avg)[db]', + }, { + name: 'xpdMax', + label: 'xpd-max', + borderColor: '#b308edde', + bezierCurve: false, + lineTension: 0, + fill: false, + data: [], + columnLabel: 'CPD (max)[db]', + }]; + + data_rows.forEach(row => { + row.xpdMin = row.performanceData.xpdMin; + row.xpdAvg = row.performanceData.xpdAvg; + row.xpdMax = row.performanceData.xpdMax; + datasets.forEach(ds => { + ds.data.push({ + x: row['timeStamp' as keyof CrossPolarDiscriminationDataType] as string, + y: row.performanceData[ds.name as keyof CrossPolarDiscriminationDatabaseDataType] as string, + }); + }); + }); + return { + datasets: datasets, + }; + }; +} +const CrossPolarDiscrimination = withRouter(connect(mapProps, mapDisp)(CrossPolarDiscriminationComponent)); +export default CrossPolarDiscrimination; diff --git a/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/components/ltpSelection.tsx b/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/components/ltpSelection.tsx new file mode 100644 index 000000000..bd6333be7 --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/components/ltpSelection.tsx @@ -0,0 +1,105 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * 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. + * ============LICENSE_END========================================================================== + */ +import React from 'react'; + +import { FormControl, MenuItem, Select, SelectChangeEvent, Typography } from '@mui/material'; +import { Theme } from '@mui/material/styles'; +import makeStyles from '@mui/styles/makeStyles'; +import { Loader } from '../../../../framework/src/components/material-ui'; +import { LtpIds } from '../models/availableLtps'; + +const useStyles = makeStyles((theme: Theme) => ({ + display: { + display: 'inline-block', + }, + selectDropdown: { + borderRadius: 1, + position: 'relative', + backgroundColor: theme.palette.background.paper, + border: '1px solid #ced4da', + fontSize: 16, + width: 'auto', + padding: '5px 5px 5px 5px', + transition: theme.transitions.create(['border-color', 'box-shadow']), + }, + center: { + 'flex': '1', + 'height': '100%', + 'display': 'flex', + 'alignItems': 'center', + 'justifyContent': 'center', + flexDirection: 'column', + }, +})); + +type LtpSelectionProps = { + selectedNE: string; error?: string; finishedLoading: boolean; selectedLtp: string; + availableLtps: LtpIds[]; + onChangeLtp(event: SelectChangeEvent): void; + selectedTimePeriod: string; + onChangeTimePeriod(event: SelectChangeEvent): void; +}; + +export const LtpSelection = (props: LtpSelectionProps) => { + const classes = useStyles(); + return ( + <> +

Selected Network Element: {props.selectedNE}

+ + + Select LTP + + + Time-Period + + + { + !props.finishedLoading && !props.error && +
+ +

Collecting Data ...

+
+ } + { + props.finishedLoading && props.error && +
+

Data couldn't be loaded

+ {props.error} +
+ } + { + props.selectedLtp === '-1' && props.finishedLoading && !props.error && (props.availableLtps.length > 0 ? +
+

Please select a LTP

+
+ : +
+

No performance data found

+
) + } + ); +}; + +export default LtpSelection; \ No newline at end of file diff --git a/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/components/performanceData.tsx b/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/components/performanceData.tsx new file mode 100644 index 000000000..fb608aac6 --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/components/performanceData.tsx @@ -0,0 +1,150 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * 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. + * ============LICENSE_END========================================================================== + */ +import React from 'react'; +import { RouteComponentProps, withRouter } from 'react-router-dom'; + +import { ColumnModel, ColumnType, MaterialTable, MaterialTableCtorType } from '../../../../framework/src/components/material-table'; +import { connect, Connect, IDispatcher } from '../../../../framework/src/flux/connect'; +import { IApplicationStoreState } from '../../../../framework/src/store/applicationStore'; + +import { SetFilterVisibility, SetSubViewAction } from '../actions/toggleActions'; +import { createPerformanceDataActions, createPerformanceDataProperties } from '../handlers/performanceDataHandler'; +import { IDataSet, IDataSetsObject } from '../models/chartTypes'; +import { PerformanceDatabaseDataType, PerformanceDataType } from '../models/performanceDataType'; +import { lineChart, sortDataByTimeStamp } from '../utils/chartUtils'; +import { addColumnLabels } from '../utils/tableUtils'; +import ToggleContainer from './toggleContainer'; + +const mapProps = (state: IApplicationStoreState) => ({ + performanceDataProperties: createPerformanceDataProperties(state), + currentView: state.performanceHistory.subViews.performanceData.subView, + isFilterVisible: state.performanceHistory.subViews.performanceData.isFilterVisible, + existingFilter: state.performanceHistory.performanceData.filter, +}); + +const mapDisp = (dispatcher: IDispatcher) => ({ + performanceDataActions: createPerformanceDataActions(dispatcher.dispatch), + setSubView: (value: 'chart' | 'table') => dispatcher.dispatch(new SetSubViewAction('performanceData', value)), + toggleFilterButton: (value: boolean) => { dispatcher.dispatch(new SetFilterVisibility('performanceData', value)); }, +}); + +type PerformanceDataComponentProps = RouteComponentProps & Connect & { + selectedTimePeriod: string; +}; + +const PerformanceDataTable = MaterialTable as MaterialTableCtorType; + +/** + * The Component which gets the performance data from the database based on the selected time period. + */ +class PerformanceDataComponent extends React.Component { + onToggleFilterButton = () => { + this.props.toggleFilterButton(!this.props.isFilterVisible); + }; + + onFilterChanged = (property: string, filterTerm: string) => { + this.props.performanceDataActions.onFilterChanged(property, filterTerm); + if (!this.props.performanceDataProperties.showFilter) + this.props.performanceDataActions.onToggleFilter(false); + }; + + render(): JSX.Element { + const properties = this.props.performanceDataProperties; + const actions = this.props.performanceDataActions; + + const chartPagedData = this.getChartDataValues(properties.rows); + const performanceColumns: ColumnModel[] = [ + { property: 'radioSignalId', title: 'Radio signal', type: ColumnType.text }, + { property: 'scannerId', title: 'Scanner ID', type: ColumnType.text }, + { property: 'timeStamp', title: 'End Time', type: ColumnType.text }, + { + property: 'suspectIntervalFlag', title: 'Suspect Interval', type: ColumnType.boolean, + }, + ]; + + chartPagedData.datasets.forEach(ds => { + performanceColumns.push(addColumnLabels(ds.name, ds.columnLabel)); + }); + return ( + <> + this.props.toggleFilterButton(!this.props.isFilterVisible)} + existingFilter={this.props.existingFilter} onFilterChanged={this.onFilterChanged} selectedValue={this.props.currentView} showFilter={this.props.isFilterVisible} onChange={this.props.setSubView}> + {lineChart(chartPagedData)} + + + + ); + } + + /** + * This function gets the performance values for PerformanceData according on the chartjs dataset structure + * which is to be sent to the chart. + */ + private getChartDataValues = (rows: PerformanceDataType[]): IDataSetsObject => { + const data_rows = [...rows]; + sortDataByTimeStamp(data_rows); + + const datasets: IDataSet[] = [{ + name: 'es', + label: 'es', + borderColor: '#0e17f3de', + bezierCurve: false, + lineTension: 0, + fill: false, + data: [], + columnLabel: 'ES', + }, { + name: 'ses', + label: 'ses', + borderColor: '#08edb6de', + bezierCurve: false, + lineTension: 0, + fill: false, + data: [], + columnLabel: 'SES', + }, { + name: 'unavailability', + label: 'unavailability', + borderColor: '#b308edde', + bezierCurve: false, + lineTension: 0, + fill: false, + data: [], + columnLabel: 'Unavailability', + }]; + + data_rows.forEach(row => { + row.es = row.performanceData.es; + row.ses = row.performanceData.ses; + row.unavailability = row.performanceData.unavailability; + datasets.forEach(ds => { + ds.data.push({ + x: row['timeStamp' as keyof PerformanceDataType] as string, + y: row.performanceData[ds.name as keyof PerformanceDatabaseDataType] as string, + }); + }); + }); + + return { + datasets: datasets, + }; + }; +} + +const PerformanceData = withRouter(connect(mapProps, mapDisp)(PerformanceDataComponent)); +export default PerformanceData; diff --git a/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/components/receiveLevel.tsx b/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/components/receiveLevel.tsx new file mode 100644 index 000000000..125cc6ee6 --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/components/receiveLevel.tsx @@ -0,0 +1,154 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * 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. + * ============LICENSE_END========================================================================== + */ +import React from 'react'; +import { RouteComponentProps, withRouter } from 'react-router-dom'; + +import { ColumnModel, ColumnType, MaterialTable, MaterialTableCtorType } from '../../../../framework/src/components/material-table'; +import { connect, Connect, IDispatcher } from '../../../../framework/src/flux/connect'; +import { IApplicationStoreState } from '../../../../framework/src/store/applicationStore'; + +import { SetFilterVisibility, SetSubViewAction } from '../actions/toggleActions'; +import { createReceiveLevelActions, createReceiveLevelProperties } from '../handlers/receiveLevelHandler'; +import { IDataSet, IDataSetsObject } from '../models/chartTypes'; +import { ReceiveLevelDatabaseDataType, ReceiveLevelDataType } from '../models/receiveLevelDataType'; +import { lineChart, sortDataByTimeStamp } from '../utils/chartUtils'; +import { addColumnLabels } from '../utils/tableUtils'; +import ToggleContainer from './toggleContainer'; + +const mapProps = (state: IApplicationStoreState) => ({ + receiveLevelProperties: createReceiveLevelProperties(state), + currentView: state.performanceHistory.subViews.receiveLevel.subView, + isFilterVisible: state.performanceHistory.subViews.receiveLevel.isFilterVisible, + existingFilter: state.performanceHistory.receiveLevel.filter, +}); + +const mapDisp = (dispatcher: IDispatcher) => ({ + receiveLevelActions: createReceiveLevelActions(dispatcher.dispatch), + setSubView: (value: 'chart' | 'table') => dispatcher.dispatch(new SetSubViewAction('receiveLevel', value)), + toggleFilterButton: (value: boolean) => { dispatcher.dispatch(new SetFilterVisibility('receiveLevel', value)); }, +}); + +type ReceiveLevelComponentProps = RouteComponentProps & Connect & { + selectedTimePeriod: string; +}; + +const ReceiveLevelTable = MaterialTable as MaterialTableCtorType; + +/** + * The Component which gets the receiveLevel data from the database based on the selected time period. + */ +class ReceiveLevelComponent extends React.Component { + onToggleFilterButton = () => { + this.props.toggleFilterButton(!this.props.isFilterVisible); + }; + + + onChange = (value: 'chart' | 'table') => { + this.props.setSubView(value); + }; + + onFilterChanged = (property: string, filterTerm: string) => { + this.props.receiveLevelActions.onFilterChanged(property, filterTerm); + if (!this.props.receiveLevelProperties.showFilter) + this.props.receiveLevelActions.onToggleFilter(false); + }; + + render(): JSX.Element { + const properties = this.props.receiveLevelProperties; + const actions = this.props.receiveLevelActions; + + const chartPagedData = this.getChartDataValues(properties.rows); + const receiveLevelColumns: ColumnModel[] = [ + { property: 'radioSignalId', title: 'Radio signal', type: ColumnType.text }, + { property: 'scannerId', title: 'Scanner ID', type: ColumnType.text }, + { property: 'timeStamp', title: 'End Time', type: ColumnType.text }, + { + property: 'suspectIntervalFlag', title: 'Suspect Interval', type: ColumnType.boolean, + }, + ]; + + chartPagedData.datasets.forEach(ds => { + receiveLevelColumns.push(addColumnLabels(ds.name, ds.columnLabel)); + }); + + return ( + <> + + {lineChart(chartPagedData)} + + + + ); + } + + /** + * This function gets the performance values for ReceiveLevel according on the chartjs dataset structure + * which is to be sent to the chart. + */ + private getChartDataValues = (rows: ReceiveLevelDataType[]): IDataSetsObject => { + const data_rows = [...rows]; + sortDataByTimeStamp(data_rows); + + const datasets: IDataSet[] = [{ + name: 'rxLevelMin', + label: 'rx-level-min', + borderColor: '#0e17f3de', + bezierCurve: false, + lineTension: 0, + fill: false, + data: [], + columnLabel: 'Rx min', + }, { + name: 'rxLevelAvg', + label: 'rx-level-avg', + borderColor: '#08edb6de', + bezierCurve: false, + lineTension: 0, + fill: false, + data: [], + columnLabel: 'Rx avg', + }, { + name: 'rxLevelMax', + label: 'rx-level-max', + borderColor: '#b308edde', + bezierCurve: false, + lineTension: 0, + fill: false, + data: [], + columnLabel: 'Rx max', + }]; + + data_rows.forEach(row => { + row.rxLevelMin = row.performanceData.rxLevelMin; + row.rxLevelAvg = row.performanceData.rxLevelAvg; + row.rxLevelMax = row.performanceData.rxLevelMax; + datasets.forEach(ds => { + ds.data.push({ + x: row['timeStamp' as keyof ReceiveLevelDataType] as string, + y: row.performanceData[ds.name as keyof ReceiveLevelDatabaseDataType] as string, + }); + }); + }); + return { + datasets: datasets, + }; + }; +} + +const ReceiveLevel = withRouter(connect(mapProps, mapDisp)(ReceiveLevelComponent)); +export default ReceiveLevel; diff --git a/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/components/signalToInterference.tsx b/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/components/signalToInterference.tsx new file mode 100644 index 000000000..85241fbb1 --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/components/signalToInterference.tsx @@ -0,0 +1,156 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * 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. + * ============LICENSE_END========================================================================== + */ +import React from 'react'; +import { RouteComponentProps, withRouter } from 'react-router-dom'; + +import { ColumnModel, ColumnType, MaterialTable, MaterialTableCtorType } from '../../../../framework/src/components/material-table'; +import { connect, Connect, IDispatcher } from '../../../../framework/src/flux/connect'; +import { IApplicationStoreState } from '../../../../framework/src/store/applicationStore'; + +import { SetFilterVisibility, SetSubViewAction } from '../actions/toggleActions'; +import { createSignalToInterferenceActions, createSignalToInterferenceProperties } from '../handlers/signalToInterferenceHandler'; +import { IDataSet, IDataSetsObject } from '../models/chartTypes'; +import { SignalToInterferenceDatabaseDataType, SignalToInterferenceDataType } from '../models/signalToInteferenceDataType'; +import { lineChart, sortDataByTimeStamp } from '../utils/chartUtils'; +import { addColumnLabels } from '../utils/tableUtils'; +import ToggleContainer from './toggleContainer'; + +const mapProps = (state: IApplicationStoreState) => ({ + signalToInterferenceProperties: createSignalToInterferenceProperties(state), + currentView: state.performanceHistory.subViews.SINR.subView, + isFilterVisible: state.performanceHistory.subViews.SINR.isFilterVisible, + existingFilter: state.performanceHistory.signalToInterference.filter, +}); + +const mapDisp = (dispatcher: IDispatcher) => ({ + signalToInterferenceActions: createSignalToInterferenceActions(dispatcher.dispatch), + setSubView: (value: 'chart' | 'table') => dispatcher.dispatch(new SetSubViewAction('SINR', value)), + toggleFilterButton: (value: boolean) => { dispatcher.dispatch(new SetFilterVisibility('SINR', value)); }, +}); + +type SignalToInterferenceComponentProps = RouteComponentProps & Connect & { + selectedTimePeriod: string; +}; + +const SignalToInterferenceTable = MaterialTable as MaterialTableCtorType; + +/** + * The Component which gets the signal to interference data from the database based on the selected time period. + */ +class SignalToInterferenceComponent extends React.Component { + onToggleFilterButton = () => { + this.props.toggleFilterButton(!this.props.isFilterVisible); + }; + + onChange = (value: 'chart' | 'table') => { + this.props.setSubView(value); + }; + + onFilterChanged = (property: string, filterTerm: string) => { + this.props.signalToInterferenceActions.onFilterChanged(property, filterTerm); + if (!this.props.signalToInterferenceProperties.showFilter) + this.props.signalToInterferenceActions.onToggleFilter(false); + }; + + render(): JSX.Element { + const properties = this.props.signalToInterferenceProperties; + const actions = this.props.signalToInterferenceActions; + + const chartPagedData = this.getChartDataValues(properties.rows); + + const sinrColumns: ColumnModel[] = [ + { property: 'radioSignalId', title: 'Radio signal', type: ColumnType.text }, + { property: 'scannerId', title: 'Scanner ID', type: ColumnType.text }, + { property: 'timeStamp', title: 'End Time', type: ColumnType.text }, + { + property: 'suspectIntervalFlag', title: 'Suspect Interval', type: ColumnType.boolean, + }, + ]; + + chartPagedData.datasets.forEach(ds => { + sinrColumns.push(addColumnLabels(ds.name, ds.columnLabel)); + }); + return ( + <> + + {lineChart(chartPagedData)} + + + + ); + } + + /** + * This function gets the performance values for SINR according on the chartjs dataset structure + * which is to be sent to the chart. + */ + + private getChartDataValues = (rows: SignalToInterferenceDataType[]): IDataSetsObject => { + const data_rows = [...rows]; + sortDataByTimeStamp(data_rows); + + const datasets: IDataSet[] = [{ + name: 'snirMin', + label: 'snir-min', + borderColor: '#0e17f3de', + bezierCurve: false, + lineTension: 0, + fill: false, + data: [], + columnLabel: 'SINR (min)[db]', + }, { + name: 'snirAvg', + label: 'snir-avg', + borderColor: '#08edb6de', + bezierCurve: false, + lineTension: 0, + fill: false, + data: [], + columnLabel: 'SINR (avg)[db]', + }, { + name: 'snirMax', + label: 'snir-max', + borderColor: '#b308edde', + bezierCurve: false, + lineTension: 0, + fill: false, + data: [], + columnLabel: 'SINR (max)[db]', + }]; + + data_rows.forEach(row => { + row.snirMin = row.performanceData.snirMin; + row.snirAvg = row.performanceData.snirAvg; + row.snirMax = row.performanceData.snirMax; + datasets.forEach(ds => { + ds.data.push({ + x: row['timeStamp' as keyof SignalToInterferenceDataType] as string, + y: row.performanceData[ds.name as keyof SignalToInterferenceDatabaseDataType] as string, + }); + }); + }); + return { + datasets: datasets, + }; + }; +} + +const SignalToInterference = withRouter(connect(mapProps, mapDisp)(SignalToInterferenceComponent)); +export default SignalToInterference; diff --git a/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/components/temperature.tsx b/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/components/temperature.tsx new file mode 100644 index 000000000..d4b823387 --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/components/temperature.tsx @@ -0,0 +1,156 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * 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. + * ============LICENSE_END========================================================================== + */ +import React from 'react'; +import { RouteComponentProps, withRouter } from 'react-router-dom'; + +import { ColumnModel, ColumnType, MaterialTable, MaterialTableCtorType } from '../../../../framework/src/components/material-table'; +import { connect, Connect, IDispatcher } from '../../../../framework/src/flux/connect'; +import { IApplicationStoreState } from '../../../../framework/src/store/applicationStore'; + +import { SetFilterVisibility, SetSubViewAction } from '../actions/toggleActions'; +import { createTemperatureActions, createTemperatureProperties } from '../handlers/temperatureHandler'; +import { IDataSet, IDataSetsObject } from '../models/chartTypes'; +import { TemperatureDatabaseDataType, TemperatureDataType } from '../models/temperatureDataType'; +import { lineChart, sortDataByTimeStamp } from '../utils/chartUtils'; +import { addColumnLabels } from '../utils/tableUtils'; +import ToggleContainer from './toggleContainer'; + +const mapProps = (state: IApplicationStoreState) => ({ + temperatureProperties: createTemperatureProperties(state), + currentView: state.performanceHistory.subViews.temperatur.subView, + isFilterVisible: state.performanceHistory.subViews.temperatur.isFilterVisible, + existingFilter: state.performanceHistory.temperature.filter, +}); + +const mapDisp = (dispatcher: IDispatcher) => ({ + temperatureActions: createTemperatureActions(dispatcher.dispatch), + setSubView: (value: 'chart' | 'table') => dispatcher.dispatch(new SetSubViewAction('Temp', value)), + toggleFilterButton: (value: boolean) => { dispatcher.dispatch(new SetFilterVisibility('Temp', value)); }, + +}); + +type TemperatureComponentProps = RouteComponentProps & Connect & { + selectedTimePeriod: string; +}; + +const TemperatureTable = MaterialTable as MaterialTableCtorType; + +/** + * The Component which gets the temperature data from the database based on the selected time period. + */ +class TemperatureComponent extends React.Component { + onToggleFilterButton = () => { + this.props.toggleFilterButton(!this.props.isFilterVisible); + }; + + + onChange = (value: 'chart' | 'table') => { + this.props.setSubView(value); + }; + + onFilterChanged = (property: string, filterTerm: string) => { + this.props.temperatureActions.onFilterChanged(property, filterTerm); + if (!this.props.temperatureProperties.showFilter) + this.props.temperatureActions.onToggleFilter(false); + }; + + render(): JSX.Element { + const properties = this.props.temperatureProperties; + const actions = this.props.temperatureActions; + + const chartPagedData = this.getChartDataValues(properties.rows); + const temperatureColumns: ColumnModel[] = [ + { property: 'radioSignalId', title: 'Radio signal', type: ColumnType.text }, + { property: 'scannerId', title: 'Scanner ID', type: ColumnType.text }, + { property: 'timeStamp', title: 'End Time', type: ColumnType.text }, + { + property: 'suspectIntervalFlag', title: 'Suspect Interval', type: ColumnType.boolean, + }, + ]; + + chartPagedData.datasets.forEach(ds => { + temperatureColumns.push(addColumnLabels(ds.name, ds.columnLabel)); + }); + return ( + <> + + + {lineChart(chartPagedData)} + + + + ); + } + + /** + * This function gets the performance values for Temperature according on the chartjs dataset structure + * which is to be sent to the chart. + */ + + private getChartDataValues = (rows: TemperatureDataType[]): IDataSetsObject => { + const data_rows = [...rows]; + sortDataByTimeStamp(data_rows); + + const datasets: IDataSet[] = [{ + name: 'rfTempMin', + label: 'rf-temp-min', + borderColor: '#0e17f3de', + bezierCurve: false, + lineTension: 0, + fill: false, + data: [], + columnLabel: 'Rf Temp Min[deg C]', + }, { + name: 'rfTempAvg', + label: 'rf-temp-avg', + borderColor: '#08edb6de', + bezierCurve: false, + lineTension: 0, + fill: false, + data: [], + columnLabel: 'Rf Temp Avg[deg C]', + }, { + name: 'rfTempMax', + label: 'rf-temp-max', + borderColor: '#b308edde', + bezierCurve: false, + lineTension: 0, + fill: false, + data: [], + columnLabel: 'Rf Temp Max[deg C]', + }]; + + data_rows.forEach(row => { + row.rfTempMin = row.performanceData.rfTempMin; + row.rfTempAvg = row.performanceData.rfTempAvg; + row.rfTempMax = row.performanceData.rfTempMax; + datasets.forEach(ds => { + ds.data.push({ + x: row['timeStamp' as keyof TemperatureDataType] as string, + y: row.performanceData[ds.name as keyof TemperatureDatabaseDataType] as string, + }); + }); + }); + return { + datasets: datasets, + }; + }; +} + +const Temperature = withRouter(connect(mapProps, mapDisp)(TemperatureComponent)); +export default Temperature; diff --git a/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/components/toggleContainer.tsx b/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/components/toggleContainer.tsx new file mode 100644 index 000000000..e883aef7f --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/components/toggleContainer.tsx @@ -0,0 +1,102 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2020 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * 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. + * ============LICENSE_END========================================================================== + */ + +import * as React from 'react'; + +import BarChartIcon from '@mui/icons-material/BarChart'; +import FilterListIcon from '@mui/icons-material/FilterList'; +import TableChartIcon from '@mui/icons-material/TableChart'; +import ToggleButton from '@mui/material/ToggleButton'; +import ToggleButtonGroup from '@mui/material/ToggleButtonGroup'; +import Tooltip from '@mui/material/Tooltip'; +import makeStyles from '@mui/styles/makeStyles'; + +import ChartFilter from './chartFilter'; + +const styles = makeStyles({ + toggleButtonContainer: { + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + padding: '10px', + }, + subViewGroup: { + padding: '10px', + }, + filterGroup: { + marginLeft: '10px', + }, +}); + +type toggleProps = { selectedValue: string; onChange(value: string): void; showFilter: boolean; onToggleFilterButton(): void; onFilterChanged: (property: string, filterTerm: string) => void; existingFilter: any }; + +const ToggleContainer: React.FunctionComponent = (props) => { + + const classes = styles(); + + const handleChange = (event: React.MouseEvent, newView: string) => { + if (newView !== null) { + props.onChange(newView); + } + }; + + const handleFilterChange = (_event: React.MouseEvent) => { + props.onToggleFilterButton(); + }; + + const children = React.Children.toArray(props.children); + + //hide filter if visible + table + //put current name into state, let container handle stuff itelf, register for togglestate, get right via set name + + return ( + <> +
+ + + + + + + + + + + + + + + + + + + + + + +
+ { + props.selectedValue === 'chart' && + + + } + {props.selectedValue === 'chart' ? children[0] : props.selectedValue === 'table' && children[1]} + ); +}; + +export default ToggleContainer; \ No newline at end of file diff --git a/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/components/transmissionPower.tsx b/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/components/transmissionPower.tsx new file mode 100644 index 000000000..db9a7c077 --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/components/transmissionPower.tsx @@ -0,0 +1,158 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * 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. + * ============LICENSE_END========================================================================== + */ +import React from 'react'; +import { RouteComponentProps, withRouter } from 'react-router-dom'; + +import { ColumnModel, ColumnType, MaterialTable, MaterialTableCtorType } from '../../../../framework/src/components/material-table'; +import { connect, Connect, IDispatcher } from '../../../../framework/src/flux/connect'; +import { IApplicationStoreState } from '../../../../framework/src/store/applicationStore'; + +import { SetFilterVisibility, SetSubViewAction } from '../actions/toggleActions'; +import { createTransmissionPowerActions, createTransmissionPowerProperties } from '../handlers/transmissionPowerHandler'; +import { IDataSet, IDataSetsObject } from '../models/chartTypes'; +import { TransmissionPowerDatabaseDataType, TransmissionPowerDataType } from '../models/transmissionPowerDataType'; +import { lineChart, sortDataByTimeStamp } from '../utils/chartUtils'; +import { addColumnLabels } from '../utils/tableUtils'; +import ToggleContainer from './toggleContainer'; + +const mapProps = (state: IApplicationStoreState) => ({ + transmissionPowerProperties: createTransmissionPowerProperties(state), + currentView: state.performanceHistory.subViews.transmissionPower.subView, + isFilterVisible: state.performanceHistory.subViews.transmissionPower.isFilterVisible, + existingFilter: state.performanceHistory.transmissionPower.filter, +}); + +const mapDisp = (dispatcher: IDispatcher) => ({ + transmissionPowerActions: createTransmissionPowerActions(dispatcher.dispatch), + setSubView: (value: 'chart' | 'table') => dispatcher.dispatch(new SetSubViewAction('transmissionPower', value)), + toggleFilterButton: (value: boolean) => { dispatcher.dispatch(new SetFilterVisibility('transmissionPower', value)); }, + + +}); + +type TransmissionPowerComponentProps = RouteComponentProps & Connect & { + selectedTimePeriod: string; +}; + +const TransmissionPowerTable = MaterialTable as MaterialTableCtorType; + +/** + * The Component which gets the transmission power data from the database based on the selected time period. + */ +class TransmissionPowerComponent extends React.Component { + onToggleFilterButton = () => { + this.props.toggleFilterButton(!this.props.isFilterVisible); + }; + + onChange = (value: 'chart' | 'table') => { + this.props.setSubView(value); + }; + + onFilterChanged = (property: string, filterTerm: string) => { + this.props.transmissionPowerActions.onFilterChanged(property, filterTerm); + if (!this.props.transmissionPowerProperties.showFilter) + this.props.transmissionPowerActions.onToggleFilter(false); + }; + + render(): JSX.Element { + const properties = this.props.transmissionPowerProperties; + const actions = this.props.transmissionPowerActions; + + const chartPagedData = this.getChartDataValues(properties.rows); + + const transmissionColumns: ColumnModel[] = [ + { property: 'radioSignalId', title: 'Radio signal', type: ColumnType.text }, + { property: 'scannerId', title: 'Scanner ID', type: ColumnType.text }, + { property: 'timeStamp', title: 'End Time', type: ColumnType.text }, + { + property: 'suspectIntervalFlag', title: 'Suspect Interval', type: ColumnType.boolean, + }, + ]; + + chartPagedData.datasets.forEach(ds => { + transmissionColumns.push(addColumnLabels(ds.name, ds.columnLabel)); + }); + + return ( + <> + + {lineChart(chartPagedData)} + + + + ); + } + + /** + * This function gets the performance values for TransmissionPower according on the chartjs dataset structure + * which is to be sent to the chart. + */ + + private getChartDataValues = (rows: TransmissionPowerDataType[]): IDataSetsObject => { + const data_rows = [...rows]; + sortDataByTimeStamp(data_rows); + + const datasets: IDataSet[] = [{ + name: 'txLevelMin', + label: 'tx-level-min', + borderColor: '#0e17f3de', + bezierCurve: false, + lineTension: 0, + fill: false, + data: [], + columnLabel: 'Tx min', + }, { + name: 'txLevelAvg', + label: 'tx-level-avg', + borderColor: '#08edb6de', + bezierCurve: false, + lineTension: 0, + fill: false, + data: [], + columnLabel: 'Tx avg', + }, { + name: 'txLevelMax', + label: 'tx-level-max', + borderColor: '#b308edde', + bezierCurve: false, + lineTension: 0, + fill: false, + data: [], + columnLabel: 'Tx max', + }]; + + data_rows.forEach(row => { + row.txLevelMin = row.performanceData.txLevelMin; + row.txLevelAvg = row.performanceData.txLevelAvg; + row.txLevelMax = row.performanceData.txLevelMax; + datasets.forEach(ds => { + ds.data.push({ + x: row['timeStamp' as keyof TransmissionPowerDataType] as string, + y: row.performanceData[ds.name as keyof TransmissionPowerDatabaseDataType] as string, + }); + }); + }); + return { + datasets: datasets, + }; + }; +} + +const TransmissionPower = withRouter(connect(mapProps, mapDisp)(TransmissionPowerComponent)); +export default TransmissionPower; diff --git a/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/handlers/adaptiveModulationHandler.ts b/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/handlers/adaptiveModulationHandler.ts new file mode 100644 index 000000000..9baf54529 --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/handlers/adaptiveModulationHandler.ts @@ -0,0 +1,37 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * 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. + * ============LICENSE_END========================================================================== + */ +import { createExternal, IExternalTableState } from '../../../../framework/src/components/material-table/utilities'; +import { createSearchDataHandler } from '../../../../framework/src/utilities/elasticSearch'; + +import { AdaptiveModulationDataType } from '../models/adaptiveModulationDataType'; +import { getFilter } from '../utils/tableUtils'; + +export interface IAdaptiveModulationState extends IExternalTableState { } + +/** + * Creates elastic search material data fetch handler for Adaptive modulation from historicalperformance database. + */ +const adaptiveModulationSearchHandler = createSearchDataHandler(getFilter, false, null); +export const { + actionHandler: adaptiveModulationActionHandler, + createActions: createAdaptiveModulationActions, + createProperties: createAdaptiveModulationProperties, + createPreActions: createAdaptiveModulationPreActions, + reloadAction: adaptiveModulationReloadAction, +} = createExternal(adaptiveModulationSearchHandler, appState => appState.performanceHistory.adaptiveModulation); + diff --git a/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/handlers/availableLtpsActionHandler.ts b/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/handlers/availableLtpsActionHandler.ts new file mode 100644 index 000000000..f943ef44c --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/handlers/availableLtpsActionHandler.ts @@ -0,0 +1,99 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * 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. + * ============LICENSE_END========================================================================== + */ +import { IActionHandler } from '../../../../framework/src/flux/action'; + +import { + AllAvailableLtpsLoadedAction, + LoadAllAvailableLtpsAction, + SetInitialLoadedAction, + NoLtpsFoundAction, + ResetLtpsAction, +} from '../actions/ltpAction'; + +import { LtpIds } from '../models/availableLtps'; + +export interface IAvailableLtpsState { + distinctLtps: LtpIds[]; + busy: boolean; + loadedOnce: boolean; + error: string | undefined; +} + +const ltpListStateInit: IAvailableLtpsState = { + distinctLtps: [], + busy: false, + loadedOnce: false, + error: undefined, +}; + +export const availableLtpsActionHandler: IActionHandler = (state = ltpListStateInit, action) => { + if (action instanceof LoadAllAvailableLtpsAction) { + + state = { + ...state, + busy: true, + }; + + } else if (action instanceof AllAvailableLtpsLoadedAction) { + if (!action.error && action.availableLtps) { + state = { + ...state, + distinctLtps: action.availableLtps, + busy: false, + error: undefined, + loadedOnce: true, + }; + } else if (action.error) { + state = { + ...state, + busy: false, + loadedOnce: true, + error: action.error, + }; + } + } else if (action instanceof SetInitialLoadedAction) { + + state = { + ...state, + loadedOnce: action.initialLoaded, + }; + } else if (action instanceof NoLtpsFoundAction) { + state = { + ...state, + busy: false, + error: undefined, + loadedOnce: true, + distinctLtps: [], + }; + } else if (action instanceof ResetLtpsAction) { + state = { + ...state, + busy: false, + error: undefined, + loadedOnce: false, + distinctLtps: [], + }; + } else { + state = { + ...state, + busy: false, + }; + } + + return state; +}; \ No newline at end of file diff --git a/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/handlers/crossPolarDiscriminationHandler.ts b/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/handlers/crossPolarDiscriminationHandler.ts new file mode 100644 index 000000000..96cabfa83 --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/handlers/crossPolarDiscriminationHandler.ts @@ -0,0 +1,38 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * 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. + * ============LICENSE_END========================================================================== + */ +import { createExternal, IExternalTableState } from '../../../../framework/src/components/material-table/utilities'; +import { createSearchDataHandler } from '../../../../framework/src/utilities/elasticSearch'; + +import { CrossPolarDiscriminationDataType } from '../models/crossPolarDiscriminationDataType'; +import { getFilter } from '../utils/tableUtils'; + +export interface ICrossPolarDiscriminationState extends IExternalTableState { } + +/** + * Creates elastic search material data fetch handler for CPD from historicalperformance database. + */ +const crossPolarDiscriminationSearchHandler = createSearchDataHandler(getFilter, false, null); + +export const { + actionHandler: crossPolarDiscriminationActionHandler, + createActions: createCrossPolarDiscriminationActions, + createProperties: createCrossPolarDiscriminationProperties, + createPreActions: createCrossPolarDiscriminationPreActions, + reloadAction: crossPolarDiscriminationReloadAction, +} = createExternal(crossPolarDiscriminationSearchHandler, appState => appState.performanceHistory.crossPolarDiscrimination); + diff --git a/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/handlers/deviceListActionHandler.ts b/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/handlers/deviceListActionHandler.ts new file mode 100644 index 000000000..11e380ad5 --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/handlers/deviceListActionHandler.ts @@ -0,0 +1,56 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * 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. + * ============LICENSE_END========================================================================== + */ +import { IActionHandler } from '../../../../framework/src/flux/action'; + +import { AllDeviceListLoadedAction, LoadAllDeviceListAction } from '../actions/deviceListActions'; +import { DeviceListType } from '../models/deviceListType'; + +export interface IDeviceListState { + deviceList: DeviceListType[]; + busy: boolean; +} + +const deviceListStateInit: IDeviceListState = { + deviceList: [], + busy: false, +}; + +export const deviceListActionHandler: IActionHandler = (state = deviceListStateInit, action) => { + if (action instanceof LoadAllDeviceListAction) { + + state = { + ...state, + busy: true, + }; + + } else if (action instanceof AllDeviceListLoadedAction) { + if (!action.error && action.deviceList) { + state = { + ...state, + deviceList: action.deviceList, + busy: false, + }; + } else { + state = { + ...state, + busy: false, + }; + } + } + return state; +}; \ No newline at end of file diff --git a/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/handlers/performanceDataHandler.ts b/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/handlers/performanceDataHandler.ts new file mode 100644 index 000000000..664c7cd6c --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/handlers/performanceDataHandler.ts @@ -0,0 +1,39 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * 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. + * ============LICENSE_END========================================================================== + */ + +import { createExternal, IExternalTableState } from '../../../../framework/src/components/material-table/utilities'; +import { createSearchDataHandler } from '../../../../framework/src/utilities/elasticSearch'; + +import { PerformanceDataType } from '../models/performanceDataType'; +import { getFilter } from '../utils/tableUtils'; + +export interface IPerformanceDataState extends IExternalTableState { } + +/** +* Creates elastic search material data fetch handler for performance data from historicalperformance15min database. +*/ +const performanceDataSearchHandler = createSearchDataHandler(getFilter, false, null); + +export const { + actionHandler: performanceDataActionHandler, + createActions: createPerformanceDataActions, + createProperties: createPerformanceDataProperties, + createPreActions: createPerformanceDataPreActions, + reloadAction: performanceDataReloadAction, +} = createExternal(performanceDataSearchHandler, appState => appState.performanceHistory.performanceData); + diff --git a/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/handlers/performanceHistoryRootHandler.ts b/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/handlers/performanceHistoryRootHandler.ts new file mode 100644 index 000000000..c0cee46ba --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/handlers/performanceHistoryRootHandler.ts @@ -0,0 +1,181 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * 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. + * ============LICENSE_END========================================================================== + */ +// main state handler + +import { IActionHandler } from '../../../../framework/src/flux/action'; +import { combineActionHandler } from '../../../../framework/src/flux/middleware'; +// ** do not remove ** +// eslint-disable-next-line @typescript-eslint/no-unused-vars +import { IApplicationStoreState } from '../../../../framework/src/store/applicationStore'; + +import { IConnectAppStoreState } from '../../../connectApp/src/handlers/connectAppRootHandler'; +import { UpdateMountId } from '../actions/deviceListActions'; +import { SetPanelAction } from '../actions/panelChangeActions'; +import { ReloadAction } from '../actions/reloadAction'; +import { TimeChangeAction } from '../actions/timeChangeAction'; +import { ResetAllSubViewsAction, SetFilterVisibility, SetSubViewAction } from '../actions/toggleActions'; +import { PmDataInterval } from '../models/performanceDataType'; +import { currentViewType, SubTabType } from '../models/toggleDataType'; +import { adaptiveModulationActionHandler, IAdaptiveModulationState } from './adaptiveModulationHandler'; +import { availableLtpsActionHandler, IAvailableLtpsState } from './availableLtpsActionHandler'; +import { crossPolarDiscriminationActionHandler, ICrossPolarDiscriminationState } from './crossPolarDiscriminationHandler'; +import { deviceListActionHandler, IDeviceListState } from './deviceListActionHandler'; +import { IPerformanceDataState, performanceDataActionHandler } from './performanceDataHandler'; +import { IReceiveLevelState, receiveLevelActionHandler } from './receiveLevelHandler'; +import { ISignalToInterferenceState, signalToInterferenceActionHandler } from './signalToInterferenceHandler'; +import { ITemperatureState, temperatureActionHandler } from './temperatureHandler'; +import { ITransmissionPowerState, transmissionPowerActionHandler } from './transmissionPowerHandler'; + +export interface IPerformanceHistoryStoreState { + nodeId: string; + networkElements: IDeviceListState; + ltps: IAvailableLtpsState; + performanceData: IPerformanceDataState; + receiveLevel: IReceiveLevelState; + transmissionPower: ITransmissionPowerState; + adaptiveModulation: IAdaptiveModulationState; + temperature: ITemperatureState; + signalToInterference: ISignalToInterferenceState; + crossPolarDiscrimination: ICrossPolarDiscriminationState; + currentOpenPanel: string | null; + pmDataIntervalType: PmDataInterval; + subViews: toggleViewDataType; + isReloadSchedueled: boolean; +} + +const mountIdHandler: IActionHandler = (state = '', action) => { + if (action instanceof UpdateMountId) { + state = ''; + if (action.nodeId) { + state = action.nodeId; + } + } + return state; +}; + +const reloadHandler: IActionHandler = (state = false, action) => { + + if (action instanceof ReloadAction) { + state = action.show; + } + return state; +}; + + +const currentOpenPanelHandler: IActionHandler = (state = null, action) => { + if (action instanceof SetPanelAction) { + state = action.panelId; + } + return state; +}; + +const currentPMDataIntervalHandler: IActionHandler = (state = PmDataInterval.pmInterval15Min, action) => { + if (action instanceof TimeChangeAction) { + state = action.time; + } + return state; +}; + +type filterableSubview = { subView: SubTabType; isFilterVisible: boolean }; +type toggleViewDataType = { + currentSubView: currentViewType; + performanceData: filterableSubview; + receiveLevel: filterableSubview; + transmissionPower: filterableSubview; + adaptiveModulation: filterableSubview; + temperatur: filterableSubview; + SINR: filterableSubview; + CPD: filterableSubview; +}; + + +const toogleViewDataHandler: IActionHandler = ( + state = { + currentSubView: 'performanceData', + performanceData: { subView: 'chart', isFilterVisible: true }, + receiveLevel: { subView: 'chart', isFilterVisible: true }, + adaptiveModulation: { subView: 'chart', isFilterVisible: true }, + transmissionPower: { subView: 'chart', isFilterVisible: true }, + temperatur: { subView: 'chart', isFilterVisible: true }, + SINR: { subView: 'chart', isFilterVisible: true }, + CPD: { subView: 'chart', isFilterVisible: true }, + }, action) => { + + if (action instanceof SetSubViewAction) { + switch (action.currentView) { + case 'performanceData': state = { ...state, performanceData: { ...state.performanceData, subView: action.selectedTab } }; break; + case 'adaptiveModulation': state = { ...state, adaptiveModulation: { ...state.adaptiveModulation, subView: action.selectedTab } }; break; + case 'receiveLevel': state = { ...state, receiveLevel: { ...state.receiveLevel, subView: action.selectedTab } }; break; + case 'transmissionPower': state = { ...state, transmissionPower: { ...state.transmissionPower, subView: action.selectedTab } }; break; + case 'Temp': state = { ...state, temperatur: { ...state.temperatur, subView: action.selectedTab } }; break; + case 'SINR': state = { ...state, SINR: { ...state.SINR, subView: action.selectedTab } }; break; + case 'CPD': state = { ...state, CPD: { ...state.CPD, subView: action.selectedTab } }; break; + } + } else if (action instanceof SetFilterVisibility) { + switch (action.currentView) { + case 'performanceData': state = { + ...state, performanceData: { ...state.performanceData, isFilterVisible: action.isVisible }, + }; break; + case 'adaptiveModulation': state = { ...state, adaptiveModulation: { ...state.performanceData, isFilterVisible: action.isVisible } }; break; + case 'receiveLevel': state = { ...state, receiveLevel: { ...state.receiveLevel, isFilterVisible: action.isVisible } }; break; + case 'transmissionPower': state = { ...state, transmissionPower: { ...state.transmissionPower, isFilterVisible: action.isVisible } }; break; + case 'Temp': state = { ...state, temperatur: { ...state.temperatur, isFilterVisible: action.isVisible } }; break; + case 'SINR': state = { ...state, SINR: { ...state.SINR, isFilterVisible: action.isVisible } }; break; + case 'CPD': state = { ...state, CPD: { ...state.CPD, isFilterVisible: action.isVisible } }; break; + } + } else if (action instanceof ResetAllSubViewsAction) { + state = { + ...state, performanceData: { ...state.performanceData, subView: 'chart' }, + adaptiveModulation: { ...state.adaptiveModulation, subView: 'chart' }, + receiveLevel: { ...state.receiveLevel, subView: 'chart' }, + transmissionPower: { ...state.transmissionPower, subView: 'chart' }, + temperatur: { ...state.temperatur, subView: 'chart' }, + SINR: { ...state.SINR, subView: 'chart' }, + CPD: { ...state.CPD, subView: 'chart' }, + }; + } + return state; +}; + +declare module '../../../../framework/src/store/applicationStore' { + interface IApplicationStoreState { + performanceHistory: IPerformanceHistoryStoreState; + connect: IConnectAppStoreState; + } +} + +const actionHandlers = { + nodeId: mountIdHandler, + networkElements: deviceListActionHandler, + ltps: availableLtpsActionHandler, + performanceData: performanceDataActionHandler, + receiveLevel: receiveLevelActionHandler, + transmissionPower: transmissionPowerActionHandler, + adaptiveModulation: adaptiveModulationActionHandler, + temperature: temperatureActionHandler, + signalToInterference: signalToInterferenceActionHandler, + crossPolarDiscrimination: crossPolarDiscriminationActionHandler, + currentOpenPanel: currentOpenPanelHandler, + pmDataIntervalType: currentPMDataIntervalHandler, + subViews: toogleViewDataHandler, + isReloadSchedueled: reloadHandler, +}; + +const performanceHistoryRootHandler = combineActionHandler(actionHandlers); +export default performanceHistoryRootHandler; + diff --git a/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/handlers/receiveLevelHandler.ts b/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/handlers/receiveLevelHandler.ts new file mode 100644 index 000000000..78626ebec --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/handlers/receiveLevelHandler.ts @@ -0,0 +1,38 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * 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. + * ============LICENSE_END========================================================================== + */ +import { createExternal, IExternalTableState } from '../../../../framework/src/components/material-table/utilities'; +import { createSearchDataHandler } from '../../../../framework/src/utilities/elasticSearch'; + +import { ReceiveLevelDataType } from '../models/receiveLevelDataType'; +import { getFilter } from '../utils/tableUtils'; + +export interface IReceiveLevelState extends IExternalTableState { } + +/** + * Creates elastic search material data fetch handler for receiveLevel from historicalperformance database. + */ +const receiveLevelSearchHandler = createSearchDataHandler(getFilter, false, null); + +export const { + actionHandler: receiveLevelActionHandler, + createActions: createReceiveLevelActions, + createProperties: createReceiveLevelProperties, + createPreActions: createReceiveLevelPreActions, + reloadAction: receiveLevelReloadAction, +} = createExternal(receiveLevelSearchHandler, appState => appState.performanceHistory.receiveLevel); + diff --git a/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/handlers/signalToInterferenceHandler.ts b/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/handlers/signalToInterferenceHandler.ts new file mode 100644 index 000000000..ab6efabae --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/handlers/signalToInterferenceHandler.ts @@ -0,0 +1,38 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * 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. + * ============LICENSE_END========================================================================== + */ +import { createExternal, IExternalTableState } from '../../../../framework/src/components/material-table/utilities'; +import { createSearchDataHandler } from '../../../../framework/src/utilities/elasticSearch'; + +import { SignalToInterferenceDataType } from '../models/signalToInteferenceDataType'; +import { getFilter } from '../utils/tableUtils'; + +export interface ISignalToInterferenceState extends IExternalTableState { } + +/** + * Creates elastic search material data fetch handler for SINR from historicalperformance database. + */ +const signalToInterferenceSearchHandler = createSearchDataHandler(getFilter, false, null); + +export const { + actionHandler: signalToInterferenceActionHandler, + createActions: createSignalToInterferenceActions, + createProperties: createSignalToInterferenceProperties, + createPreActions: createSignalToInterferencePreActions, + reloadAction: signalToInterferenceReloadAction, +} = createExternal(signalToInterferenceSearchHandler, appState => appState.performanceHistory.signalToInterference); + diff --git a/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/handlers/temperatureHandler.ts b/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/handlers/temperatureHandler.ts new file mode 100644 index 000000000..02bf69be7 --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/handlers/temperatureHandler.ts @@ -0,0 +1,38 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * 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. + * ============LICENSE_END========================================================================== + */ +import { createExternal, IExternalTableState } from '../../../../framework/src/components/material-table/utilities'; +import { createSearchDataHandler } from '../../../../framework/src/utilities/elasticSearch'; + +import { TemperatureDataType } from '../models/temperatureDataType'; +import { getFilter } from '../utils/tableUtils'; + +export interface ITemperatureState extends IExternalTableState { } + +/** + * Creates elastic search material data fetch handler for Temperature from historicalperformance database. + */ +const temperatureSearchHandler = createSearchDataHandler(getFilter, false, null); + +export const { + actionHandler: temperatureActionHandler, + createActions: createTemperatureActions, + createProperties: createTemperatureProperties, + createPreActions: createTemperaturePreActions, + reloadAction: temperatureReloadAction, +} = createExternal(temperatureSearchHandler, appState => appState.performanceHistory.temperature); + diff --git a/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/handlers/transmissionPowerHandler.ts b/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/handlers/transmissionPowerHandler.ts new file mode 100644 index 000000000..9cf70dc87 --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/handlers/transmissionPowerHandler.ts @@ -0,0 +1,38 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * 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. + * ============LICENSE_END========================================================================== + */ +import { createExternal, IExternalTableState } from '../../../../framework/src/components/material-table/utilities'; +import { createSearchDataHandler } from '../../../../framework/src/utilities/elasticSearch'; + +import { TransmissionPowerDataType } from '../models/transmissionPowerDataType'; +import { getFilter } from '../utils/tableUtils'; + +export interface ITransmissionPowerState extends IExternalTableState { } + +/** + * Creates elastic search material data fetch handler for Transmission power from historicalperformance database. + */ +const transmissionPowerSearchHandler = createSearchDataHandler(getFilter, false, null); + +export const { + actionHandler: transmissionPowerActionHandler, + createActions: createTransmissionPowerActions, + createProperties: createTransmissionPowerProperties, + createPreActions: createTransmissionPowerPreActions, + reloadAction: transmissionPowerReloadAction, +} = createExternal(transmissionPowerSearchHandler, appState => appState.performanceHistory.transmissionPower); + diff --git a/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/index.html b/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/index.html new file mode 100644 index 000000000..8cb775be2 --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/index.html @@ -0,0 +1,26 @@ + + + + + + + + + PM History Application + + + +
+ + + + + + \ No newline at end of file diff --git a/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/models/adaptiveModulationDataType.ts b/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/models/adaptiveModulationDataType.ts new file mode 100644 index 000000000..adb0bcd6f --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/models/adaptiveModulationDataType.ts @@ -0,0 +1,109 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * 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. + * ============LICENSE_END========================================================================== + */ +export { HitEntry, Result } from '../../../../framework/src/models'; + +/** + * Represents Adaptive Modulation data fields of the performance history table. + */ +export type AdaptiveModulationDatabaseDataType = { + _id: string ; + time2StatesS: number; + time2States: number; + time2StatesL: number; + time4StatesS: number; + time4States: number; + time4StatesL: number; + time16StatesS: number; + time16States: number; + time16StatesL: number; + time32StatesS: number; + time32States: number; + time32StatesL: number; + time64StatesS: number; + time64States: number; + time64StatesL: number; + time128StatesS: number; + time128States: number; + time128StatesL: number; + time256StatesS: number; + time256States: number; + time256StatesL: number; + time512StatesS: number; + time512States: number; + time512StatesL: number; + time1024StatesS: number; + time1024States: number; + time1024StatesL: number; + time2048StatesS: number; + time2048States: number; + time2048StatesL: number; + time4096StatesS: number; + time4096States: number; + time4096StatesL: number; + time8192StatesS: number; + time8192States: number; + time8192StatesL: number; +}; + + +/** + * Internally used type to provide table and chart data + */ +export type AdaptiveModulationDataType = { + performanceData: AdaptiveModulationDatabaseDataType; + radioSignalId: string; + scannerId: string; + timeStamp: string; + suspectIntervalFlag: boolean; + time2StatesS: number; + time2States: number; + time2StatesL: number; + time4StatesS: number; + time4States: number; + time4StatesL: number; + time16StatesS: number; + time16States: number; + time16StatesL: number; + time32StatesS: number; + time32States: number; + time32StatesL: number; + time64StatesS: number; + time64States: number; + time64StatesL: number; + time128StatesS: number; + time128States: number; + time128StatesL: number; + time256StatesS: number; + time256States: number; + time256StatesL: number; + time512StatesS: number; + time512States: number; + time512StatesL: number; + time1024StatesS: number; + time1024States: number; + time1024StatesL: number; + time2048StatesS: number; + time2048States: number; + time2048StatesL: number; + time4096StatesS: number; + time4096States: number; + time4096StatesL: number; + time8192StatesS: number; + time8192States: number; + time8192StatesL: number; +} & { _id: string }; \ No newline at end of file diff --git a/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/models/availableLtps.ts b/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/models/availableLtps.ts new file mode 100644 index 000000000..60061571c --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/models/availableLtps.ts @@ -0,0 +1,21 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * 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. + * ============LICENSE_END========================================================================== + */ +export type LtpIds = { + key: string; +}; + diff --git a/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/models/chartTypes.ts b/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/models/chartTypes.ts new file mode 100644 index 000000000..969c0b399 --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/models/chartTypes.ts @@ -0,0 +1,49 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * 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. + * ============LICENSE_END========================================================================== + */ +export interface IData { + x: string; + y: string; +} + +/** + * Structure of chartjs dataset with the chart properties. + */ +export interface IDataSet { + name: string; + label: string; + lineTension: 0; + bezierCurve: boolean; + fill: boolean; + borderColor: string; + data: IData[]; + columnLabel: string; +} + +/** + * Structure of chartjs dataset which is sent to the chart. + */ +export interface IDataSetsObject { + datasets: IDataSet[]; +} + +/** + * Interface used by chart for sorting on time-stamp + */ +export interface ITimeStamp { + timeStamp: string; +} diff --git a/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/models/crossPolarDiscriminationDataType.ts b/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/models/crossPolarDiscriminationDataType.ts new file mode 100644 index 000000000..749624b9a --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/models/crossPolarDiscriminationDataType.ts @@ -0,0 +1,44 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * 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. + * ============LICENSE_END========================================================================== + */ +export { HitEntry, Result } from '../../../../framework/src/models'; + + +/** + * Represents Receive level data fields of the performance history table. + */ +export type CrossPolarDiscriminationDatabaseDataType = { + _id: string; + xpdMin: number; + xpdAvg: number; + xpdMax: number; +}; + +/** + * Internally used type to provide table and chart data + */ +export type CrossPolarDiscriminationDataType = { + performanceData: CrossPolarDiscriminationDatabaseDataType; + radioSignalId: string; + scannerId: string; + timeStamp: string; + suspectIntervalFlag: boolean; + xpdMin: number; + xpdAvg: number; + xpdMax: number; +} & { _id: string }; + diff --git a/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/models/deviceListType.ts b/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/models/deviceListType.ts new file mode 100644 index 000000000..fbe314162 --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/models/deviceListType.ts @@ -0,0 +1,25 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * 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. + * ============LICENSE_END========================================================================== + */ + +/** + * Represents all the distinct devices from the performance history data. + */ + +export type DeviceListType = { + nodeId: string; +}; diff --git a/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/models/panelId.ts b/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/models/panelId.ts new file mode 100644 index 000000000..08bf7f815 --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/models/panelId.ts @@ -0,0 +1,22 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * 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. + * ============LICENSE_END========================================================================== + */ + +/** + * Represents PanelIds for the available Expansional panels. + */ +export type PanelId = null | 'PerformanceData' | 'ReceiveLevel' | 'TransmissionPower' | 'AdaptiveModulation' | 'Temperature' | 'SINR' | 'CPD'; \ No newline at end of file diff --git a/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/models/performanceDataType.ts b/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/models/performanceDataType.ts new file mode 100644 index 000000000..f71e09de9 --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/models/performanceDataType.ts @@ -0,0 +1,53 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * 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. + * ============LICENSE_END========================================================================== + */ + +//export { HitEntry, Result } from '../../../../framework/src/models'; + +/** + * Represents performance data fields of the performance history table as used in the database + */ +export type PerformanceDatabaseDataType = { + _id: string; + es: number; + ses: number; + unavailability: number; +}; + +/** + * Internally used type to provide table and chart data + */ +export type PerformanceDataType = { + + performanceData: PerformanceDatabaseDataType; + radioSignalId: string; + scannerId: string; + timeStamp: string; + suspectIntervalFlag: boolean; + es: number; + ses: number; + unavailability: number; +} & { _id: string }; + + +/** + * Represents performance data time interval. + */ +export const enum PmDataInterval { + pmInterval15Min, + pmInterval24Hours, +} \ No newline at end of file diff --git a/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/models/receiveLevelDataType.ts b/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/models/receiveLevelDataType.ts new file mode 100644 index 000000000..2748a3d5d --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/models/receiveLevelDataType.ts @@ -0,0 +1,43 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * 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. + * ============LICENSE_END========================================================================== + */ +export { HitEntry, Result } from '../../../../framework/src/models'; + +/** + * Represents Receive level data fields of the performance history table. + */ +export type ReceiveLevelDatabaseDataType = { + _id: string; + rxLevelMin: number; + rxLevelAvg: number; + rxLevelMax: number; +}; + +/** + * Internally used type to provide table and chart data + */ +export type ReceiveLevelDataType = { + performanceData: ReceiveLevelDatabaseDataType; + radioSignalId: string; + scannerId: string; + timeStamp: string; + suspectIntervalFlag: boolean; + rxLevelMin: number; + rxLevelAvg: number; + rxLevelMax: number; +} & { _id: string }; + diff --git a/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/models/signalToInteferenceDataType.ts b/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/models/signalToInteferenceDataType.ts new file mode 100644 index 000000000..5c675feb8 --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/models/signalToInteferenceDataType.ts @@ -0,0 +1,44 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * 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. + * ============LICENSE_END========================================================================== + */ +export { HitEntry, Result } from '../../../../framework/src/models'; + + +/** + * Represents Receive level data fields of the performance history table. + */ +export type SignalToInterferenceDatabaseDataType = { + _id: string; + snirMin: number; + snirAvg: number; + snirMax: number; +}; + +/** + * Internally used type to provide table and chart data + */ +export type SignalToInterferenceDataType = { + performanceData: SignalToInterferenceDatabaseDataType; + radioSignalId: string; + scannerId: string; + timeStamp: string; + suspectIntervalFlag: boolean; + snirMin: number; + snirAvg: number; + snirMax: number; +} & { _id: string }; + diff --git a/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/models/temperatureDataType.ts b/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/models/temperatureDataType.ts new file mode 100644 index 000000000..5798d5c5d --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/models/temperatureDataType.ts @@ -0,0 +1,45 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * 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. + * ============LICENSE_END========================================================================== + */ +export { HitEntry, Result } from '../../../../framework/src/models'; + + +/** + * Represents Receive level data fields of the performance history table. + */ +export type TemperatureDatabaseDataType = { + _id: string; + rfTempMin: number; + rfTempAvg: number; + rfTempMax: number; +}; + +/** + * Internally used type to provide table and chart data + */ +export type TemperatureDataType = { + performanceData: TemperatureDatabaseDataType; + radioSignalId: string; + scannerId: string; + timeStamp: string; + suspectIntervalFlag: boolean; + rfTempMin: number; + rfTempAvg: number; + rfTempMax: number; +} & { _id: string }; + + diff --git a/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/models/toggleDataType.ts b/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/models/toggleDataType.ts new file mode 100644 index 000000000..0e71c9474 --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/models/toggleDataType.ts @@ -0,0 +1,25 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2020 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * 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. + * ============LICENSE_END========================================================================== + */ + +/** + * Specifies possible sub views + */ +export type SubTabType = 'chart' | 'table'; + +export type currentViewType = 'performanceData' | 'receiveLevel' | 'transmissionPower' | 'adaptiveModulation' | 'Temp' | 'SINR' | 'CPD'; + diff --git a/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/models/topologyNetconf.ts b/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/models/topologyNetconf.ts new file mode 100644 index 000000000..f52af9784 --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/models/topologyNetconf.ts @@ -0,0 +1,26 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * 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. + * ============LICENSE_END========================================================================== + */ + +export interface TopologyNode { + 'node-id': string; +} + +export interface Topology { + 'topology-id': string; + node: TopologyNode[]; +} diff --git a/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/models/transmissionPowerDataType.ts b/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/models/transmissionPowerDataType.ts new file mode 100644 index 000000000..62c00bfe4 --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/models/transmissionPowerDataType.ts @@ -0,0 +1,44 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * 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. + * ============LICENSE_END========================================================================== + */ +export { HitEntry, Result } from '../../../../framework/src/models'; + + +/** + * Represents Receive level data fields of the performance history table. + */ +export type TransmissionPowerDatabaseDataType = { + _id: string; + txLevelMin: number; + txLevelAvg: number; + txLevelMax: number; +}; + +/** + * Internally used type to provide table and chart data + */ +export type TransmissionPowerDataType = { + performanceData: TransmissionPowerDatabaseDataType; + radioSignalId: string; + scannerId: string; + timeStamp: string; + suspectIntervalFlag: boolean; + txLevelMin: number; + txLevelAvg: number; + txLevelMax: number; +} & { _id: string }; + diff --git a/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/pluginPerformance.tsx b/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/pluginPerformance.tsx new file mode 100644 index 000000000..ef939fdba --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/pluginPerformance.tsx @@ -0,0 +1,147 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * 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. + * ============LICENSE_END========================================================================== + */ + +import React from 'react'; +import { Redirect, Route, RouteComponentProps, Switch, withRouter } from 'react-router-dom'; + +import { connect, Connect, IDispatcher } from '../../../framework/src/flux/connect'; +import applicationManager from '../../../framework/src/services/applicationManager'; +// eslint-disable-next-line @typescript-eslint/no-unused-vars +import { IApplicationStoreState } from '../../../framework/src/store/applicationStore'; +import { ApplicationStore } from '../../../framework/src/store/applicationStore'; + +import { updateMountIdActionCreator } from './actions/deviceListActions'; +import { ResetLtpsAction } from './actions/ltpAction'; +import { ReloadAction } from './actions/reloadAction'; +import { ResetAllSubViewsAction } from './actions/toggleActions'; +import performanceHistoryRootHandler from './handlers/performanceHistoryRootHandler'; +import { PmDataInterval } from './models/performanceDataType'; +import PerformanceHistoryApplication from './views/performanceHistoryApplication'; + +const appIcon = require('./assets/icons/performanceHistoryAppIcon.svg'); // select app icon + +let api: { + readonly applicationStore: ApplicationStore | null; + readonly applicationStoreInitialized: Promise; +}; + +const mapProps = () => ({ +}); + +const mapDisp = (dispatcher: IDispatcher) => ({ + updateMountId: (mountId: string) => dispatcher.dispatch(updateMountIdActionCreator(mountId)), + resetLtps: () => dispatcher.dispatch(new ResetLtpsAction()), + resetSubViews: () => dispatcher.dispatch(new ResetAllSubViewsAction()), + setScheduleReload: (show: boolean) => dispatcher.dispatch(new ReloadAction(show)), +}); + +let currentMountId: string | null = null; +let lastUrl: string = '/performanceHistory'; +const PerformanceHistoryApplicationRouteAdapter = connect(mapProps, mapDisp)((props: RouteComponentProps<{ mountId?: string }> & Connect) => { + let mountId: string = ''; + + const getMountId = (last_url: string) => { + let index = last_url.lastIndexOf('performanceHistory/'); + if (index >= 0) { + mountId = last_url.substring(index + 19); + } else { + mountId = ''; + } + + return mountId; + }; + + const scheduleReload = (current_mount_id: string) => { + props.updateMountId(current_mount_id); + props.resetLtps(); + props.resetSubViews(); + props.setScheduleReload(true); + }; + + // called when component finished mounting + React.useEffect(() => { + + lastUrl = props.location.pathname; + mountId = getMountId(lastUrl); + + if (currentMountId !== mountId) { // new element is loaded + currentMountId = mountId; + scheduleReload(currentMountId); + } else + if (currentMountId !== '') { // same element is loaded again + scheduleReload(currentMountId); + } + }, []); + + // called when component gets updated + React.useEffect(() => { + + lastUrl = props.location.pathname; + mountId = getMountId(lastUrl); + + if (currentMountId !== mountId) { + currentMountId = mountId; + scheduleReload(currentMountId); + } + + }); + + return ( + + ); +}); + +const PerformanceHistoryRouterApp = withRouter((props: RouteComponentProps) => { + props.history.action = 'POP'; + return ( + + + + + + ); +}); + +export function register() { + api = applicationManager.registerApplication({ + name: 'performanceHistory', + icon: appIcon, + rootComponent: PerformanceHistoryRouterApp, + rootActionHandler: performanceHistoryRootHandler, + menuEntry: 'Performance', + }); +} + +export function setPmDataInterval(pmDataInterval: PmDataInterval): boolean { + let reload: boolean = true; + if (api && api.applicationStore) { + if (api.applicationStore.state.performanceHistory.pmDataIntervalType !== pmDataInterval) { + reload = true; + } + api.applicationStore.state.performanceHistory.pmDataIntervalType = pmDataInterval; + } + return reload; +} + + +export function getPmDataInterval(): PmDataInterval { + let result = api && api.applicationStore + ? api.applicationStore.state.performanceHistory.pmDataIntervalType + : PmDataInterval.pmInterval15Min; + return result ? result : PmDataInterval.pmInterval15Min; +} diff --git a/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/services/performanceHistoryService.ts b/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/services/performanceHistoryService.ts new file mode 100644 index 000000000..ef013f1cb --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/services/performanceHistoryService.ts @@ -0,0 +1,107 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * 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. + * ============LICENSE_END========================================================================== + */ +import { Result } from '../../../../framework/src/models/elasticSearch'; +import { requestRest } from '../../../../framework/src/services/restService'; + +import { convertPropertyNames, replaceUpperCase } from '../../../../framework/src/utilities/yangHelper'; +import { LtpIds } from '../models/availableLtps'; +import { DeviceListType } from '../models/deviceListType'; + +/** + * Represents a web api accessor service for Network elements actions. + */ +class PerformanceService { + + /** + * Get distinct ltps based on the selected network element and time period from the historicalperformance15min database table. + */ + public async getDistinctLtpsFromDatabase(networkElement: string, selectedTimePeriod: string): Promise { + let path; + const query = { + 'filter': [{ + 'property': 'node-name', + 'filtervalue': networkElement, + }], + 'sortorder': [], + 'pagination': { + 'size': 20, + 'page': 1, + }, + }; + + + if (selectedTimePeriod === '15min') { + path = '/rests/operations/data-provider:read-pmdata-15m-ltp-list'; + } else { + path = '/rests/operations/data-provider:read-pmdata-24h-ltp-list'; + } + + const result = await requestRest>(path, { method: 'POST', body: JSON.stringify(convertPropertyNames({ input: query }, replaceUpperCase)) }); + return result && result['data-provider:output'] && result['data-provider:output'].data && result['data-provider:output'].data.map(ne => ({ key: ne })) || null; + } + + + + /** + * Gets all devices from the performanceHistory 15min backend. + */ + public async getDeviceListfromPerf15minHistory(): Promise<(DeviceListType)[] | null> { + const path = '/rests/operations/data-provider:read-pmdata-15m-device-list'; + const query = { + 'data-provider:input': { + 'filter': [], + 'sortorder': [], + 'pagination': { + 'size': 20, + 'page': 1, + }, + }, + }; + + const result = await requestRest>(path, { method: 'POST', body: JSON.stringify(query) }); + return result && result['data-provider:output'] && result['data-provider:output'].data && result['data-provider:output'].data.map(ne => ({ + nodeId: ne, + })) || null; + } + + /** + * Gets all devices from the performanceHistory 24h backend. + */ + public async getDeviceListfromPerf24hHistory(): Promise<(DeviceListType)[] | null> { + const path = '/rests/operations/data-provider:read-pmdata-24h-device-list'; + const query = { + 'data-provider:input': { + 'filter': [], + 'sortorder': [], + 'pagination': { + 'size': 20, + 'page': 1, + }, + }, + }; + + const result = await requestRest>(path, { method: 'POST', body: JSON.stringify(query) }); + return result && result['data-provider:output'] && result['data-provider:output'].data && result['data-provider:output'].data.map(ne => ({ + nodeId: ne, + })) || null; + } +} + + +export const PerformanceHistoryService = new PerformanceService(); +export default PerformanceHistoryService; diff --git a/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/utils/chartUtils.tsx b/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/utils/chartUtils.tsx new file mode 100644 index 000000000..38abb3e79 --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/utils/chartUtils.tsx @@ -0,0 +1,77 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * 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. + * ============LICENSE_END========================================================================== + */ +import React from 'react'; +import moment from 'moment'; +import { Line } from 'react-chartjs-2'; + +import { IDataSetsObject } from '../models/chartTypes'; +import { ITimeStamp } from '../models/chartTypes'; + +const style: React.CSSProperties = { + height: '80%', +}; + +export const lineChart = (chartPagedData: IDataSetsObject) => { + return ( +
+ +
+ ); +}; + +export const sortDataByTimeStamp = (_rows: T[]): T[] => { + return (_rows.sort((a, b) => { + const result = Date.parse(a.timeStamp) - Date.parse(b.timeStamp); + return isNaN(result) ? 0 : result; + })); +}; \ No newline at end of file diff --git a/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/utils/tableUtils.ts b/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/utils/tableUtils.ts new file mode 100644 index 000000000..37fe962dc --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/utils/tableUtils.ts @@ -0,0 +1,36 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * 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. + * ============LICENSE_END========================================================================== + */ +import { ColumnModel, ColumnType } from '../../../../framework/src/components/material-table'; + +import { PmDataInterval } from '../models/performanceDataType'; +import { getPmDataInterval } from '../pluginPerformance'; + +export const addColumnLabels = (name: string, title: string, disableFilter = true, disableSorting = true): ColumnModel => { + return { property: name as keyof T, title: title, type: ColumnType.text, disableFilter: disableFilter, disableSorting: disableSorting }; +}; + +export function getFilter(): string { + switch (getPmDataInterval()) { + case PmDataInterval.pmInterval15Min: + return 'pmdata-15m'; + case PmDataInterval.pmInterval24Hours: + return 'pmdata-24h'; + default: + throw new Error('Unknown time intervall'); + } +} \ No newline at end of file diff --git a/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/views/performanceHistoryApplication.tsx b/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/views/performanceHistoryApplication.tsx new file mode 100644 index 000000000..a4b968622 --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/views/performanceHistoryApplication.tsx @@ -0,0 +1,456 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * 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. + * ============LICENSE_END========================================================================== + */ +import React from 'react'; +import { connect, Connect, IDispatcher } from '../../../../framework/src/flux/connect'; + +import { Theme } from '@mui/material/styles'; +import { WithStyles } from '@mui/styles'; +import createStyles from '@mui/styles/createStyles'; +import withStyles from '@mui/styles/withStyles'; + + +import { NavigateToApplication } from '../../../../framework/src/actions/navigationActions'; +import { Dispatch } from '../../../../framework/src/flux/store'; +import { IApplicationStoreState } from '../../../../framework/src/store/applicationStore'; + +import { loadAllDeviceListAsync } from '../actions/deviceListActions'; +import { loadDistinctLtpsbyNetworkElementAsync, ResetLtpsAction } from '../actions/ltpAction'; +import { SetPanelAction } from '../actions/panelChangeActions'; +import { TimeChangeAction } from '../actions/timeChangeAction'; +import AdaptiveModulation from '../components/adaptiveModulation'; +import CrossPolarDiscrimination from '../components/crossPolarDiscrimination'; +import PerformanceData from '../components/performanceData'; +import ReceiveLevel from '../components/receiveLevel'; +import SignalToInterference from '../components/signalToInterference'; +import Temperature from '../components/temperature'; +import TransmissionPower from '../components/transmissionPower'; +import { adaptiveModulationReloadAction, createAdaptiveModulationActions, createAdaptiveModulationPreActions } from '../handlers/adaptiveModulationHandler'; +import { createCrossPolarDiscriminationActions, createCrossPolarDiscriminationPreActions, crossPolarDiscriminationReloadAction } from '../handlers/crossPolarDiscriminationHandler'; +import { createPerformanceDataActions, createPerformanceDataPreActions, performanceDataReloadAction } from '../handlers/performanceDataHandler'; +import { createReceiveLevelActions, createReceiveLevelPreActions, receiveLevelReloadAction } from '../handlers/receiveLevelHandler'; +import { createSignalToInterferenceActions, createSignalToInterferencePreActions, signalToInterferenceReloadAction } from '../handlers/signalToInterferenceHandler'; +import { createTemperatureActions, createTemperaturePreActions, temperatureReloadAction } from '../handlers/temperatureHandler'; +import { createTransmissionPowerActions, createTransmissionPowerPreActions, transmissionPowerReloadAction } from '../handlers/transmissionPowerHandler'; +import { PanelId } from '../models/panelId'; +import { PmDataInterval } from '../models/performanceDataType'; + +import { AppBar, SelectChangeEvent, Tab, Tabs } from '@mui/material'; +import { MaterialTable, MaterialTableCtorType } from '../../../../framework/src/components/material-table'; +import { ReloadAction } from '../actions/reloadAction'; +import { ResetAllSubViewsAction } from '../actions/toggleActions'; +import LtpSelection from '../components/ltpSelection'; + +const PerformanceHistoryComponentStyles = (theme: Theme) => createStyles({ + root: { + display: 'flex', + flexWrap: 'wrap', + }, + margin: { + margin: theme.spacing(1), + }, +}); + +const mapProps = (state: IApplicationStoreState) => ({ + ...state.performanceHistory, + activePanel: state.performanceHistory.currentOpenPanel, + availableLtps: state.performanceHistory.ltps.distinctLtps, + networkElements: state.performanceHistory.networkElements.deviceList, + initialLoaded: state.performanceHistory.ltps.loadedOnce, + error: state.performanceHistory.ltps.error, + shouldReload: state.performanceHistory.isReloadSchedueled, +}); + +const mapDispatcher = (dispatcher: IDispatcher) => ({ + enableFilterPerformanceData: createPerformanceDataActions(dispatcher.dispatch), + enableFilterReceiveLevel: createReceiveLevelActions(dispatcher.dispatch), + enableFilterTransmissionPower: createTransmissionPowerActions(dispatcher.dispatch), + enableFilterAdaptiveModulation: createAdaptiveModulationActions(dispatcher.dispatch), + enableFilterTemperature: createTemperatureActions(dispatcher.dispatch), + enableFilterSinr: createSignalToInterferenceActions(dispatcher.dispatch), + enableFilterCpd: createCrossPolarDiscriminationActions(dispatcher.dispatch), + reloadPerformanceData: () => dispatcher.dispatch(performanceDataReloadAction), + reloadReceiveLevel: () => dispatcher.dispatch(receiveLevelReloadAction), + reloadTransmissionPower: () => dispatcher.dispatch(transmissionPowerReloadAction), + reloadAdaptiveModulation: () => dispatcher.dispatch(adaptiveModulationReloadAction), + reloadTemperature: () => dispatcher.dispatch(temperatureReloadAction), + reloadSignalToInterference: () => dispatcher.dispatch(signalToInterferenceReloadAction), + reloadCrossPolarDiscrimination: () => dispatcher.dispatch(crossPolarDiscriminationReloadAction), + performanceDataPreActions: createPerformanceDataPreActions(dispatcher.dispatch), + receiveLevelPreActions: createReceiveLevelPreActions(dispatcher.dispatch), + transmissionPowerPreActions: createTransmissionPowerPreActions(dispatcher.dispatch), + adaptiveModulationPreActions: createAdaptiveModulationPreActions(dispatcher.dispatch), + temperaturePreActions: createTemperaturePreActions(dispatcher.dispatch), + signalToInterferencePreActions: createSignalToInterferencePreActions(dispatcher.dispatch), + crossPolarDiscriminationPreActions: createCrossPolarDiscriminationPreActions(dispatcher.dispatch), + getAllDevicesPMdata: async () => { + await dispatcher.dispatch(loadAllDeviceListAsync); + }, + getDistinctLtpsIds: ( + selectedNetworkElement: string, + selectedTimePeriod: string, + selectedLtp: string, + selectFirstLtp?: Function, + resetLTP?: Function, + ) => dispatcher.dispatch(loadDistinctLtpsbyNetworkElementAsync(selectedNetworkElement, selectedTimePeriod, selectedLtp, selectFirstLtp, resetLTP)), + setCurrentPanel: (panelId: PanelId) => dispatcher.dispatch(new SetPanelAction(panelId)), + timeIntervalChange: (time: PmDataInterval) => dispatcher.dispatch(new TimeChangeAction(time)), + changeNode: (nodeId: string) => dispatcher.dispatch((dispatch: Dispatch) => { + dispatch(new NavigateToApplication('performanceHistory', nodeId)); + }), + resetLtps: () => dispatcher.dispatch((dispatch: Dispatch) => { dispatch(new ResetLtpsAction()); }), + resetSubViews: () => dispatcher.dispatch(new ResetAllSubViewsAction()), + setShouldReload: (show: boolean) => dispatcher.dispatch(new ReloadAction(show)), +}); + +export type NetworkElementType = { + nodeId: string; +}; + +const NetworkElementTable = MaterialTable as MaterialTableCtorType; + +type PerformanceHistoryComponentProps = Connect & WithStyles; + +type PerformanceHistoryComponentState = { + selectedNetworkElement: string; + selectedTimePeriod: string; + selectedLtp: string; + showNetworkElementsTable: boolean; + showLtps: boolean; + showPanels: boolean; + preFilter: + { + 'node-name': string; + 'uuid-interface': string; + } | {}; +}; + +/** +* Represents the component for Performance history application. +*/ +class PerformanceHistoryComponent extends React.Component { + /** + * Initialises this instance + */ + constructor(props: PerformanceHistoryComponentProps) { + super(props); + this.state = { + selectedNetworkElement: props.nodeId !== '' ? props.nodeId : '-1', + selectedTimePeriod: '15min', + selectedLtp: '-1', + showNetworkElementsTable: true, + showLtps: false, + showPanels: false, + preFilter: {}, + }; + } + + onChangeTabs = (event: React.SyntheticEvent, newValue: PanelId) => { + const nextActivePanel = newValue; + this.changeTabs(nextActivePanel); + }; + + changeTabs = (nextActivePanel: PanelId) => { + this.props.setCurrentPanel(nextActivePanel); + const preFilter = this.state.preFilter; + switch (nextActivePanel) { + case 'PerformanceData': + if (this.props.performanceData.preFilter !== {} && this.props.performanceData.preFilter === preFilter) { + this.props.reloadPerformanceData(); + } else { + this.props.performanceDataPreActions.onPreFilterChanged(preFilter); + } + break; + case 'ReceiveLevel': + if (this.props.receiveLevel.preFilter !== {} && this.props.receiveLevel.preFilter === preFilter) { + this.props.reloadReceiveLevel(); + } else { + this.props.receiveLevelPreActions.onPreFilterChanged(preFilter); + } + break; + case 'TransmissionPower': + if (this.props.transmissionPower.preFilter !== {} && this.props.transmissionPower.preFilter === preFilter) { + this.props.reloadTransmissionPower(); + } else { + this.props.transmissionPowerPreActions.onPreFilterChanged(preFilter); + } + break; + case 'AdaptiveModulation': + if (this.props.adaptiveModulation.preFilter !== {} && this.props.adaptiveModulation.preFilter === preFilter) { + this.props.reloadAdaptiveModulation(); + } else { + this.props.adaptiveModulationPreActions.onPreFilterChanged(preFilter); + } + break; + case 'Temperature': + if (this.props.temperature.preFilter !== {} && this.props.temperature.preFilter === preFilter) { + this.props.reloadTemperature(); + } else { + this.props.temperaturePreActions.onPreFilterChanged(preFilter); + } + break; + case 'SINR': + if (this.props.signalToInterference.preFilter !== {} && this.props.signalToInterference.preFilter === preFilter) { + this.props.reloadSignalToInterference(); + } else { + this.props.signalToInterferencePreActions.onPreFilterChanged(preFilter); + } + break; + case 'CPD': + if (this.props.crossPolarDiscrimination.preFilter !== {} && this.props.crossPolarDiscrimination.preFilter === preFilter) { + this.props.reloadCrossPolarDiscrimination(); + } else { + this.props.crossPolarDiscriminationPreActions.onPreFilterChanged(preFilter); + } + break; + default: + // do nothing if all panels are closed + break; + } + }; + + render(): JSX.Element { + const { activePanel, nodeId } = this.props; + if (nodeId === '') { + return ( + <> + { this.handleNetworkElementSelect(rowData.nodeId); }} columns={ + [{ property: 'nodeId', title: 'Node Name' }] + } /> + + ); + } else { + this.handleURLChange(nodeId); + return ( + <> + {this.state.showLtps && + + + } + {this.state.showPanels && + <> + + + + + + + + + + + + + { + activePanel === 'PerformanceData' && + + } + + { + activePanel === 'ReceiveLevel' && + + } + + { + activePanel === 'TransmissionPower' && + + } + + { + activePanel === 'AdaptiveModulation' && + + } + { + activePanel === 'Temperature' && + + } + + { + activePanel === 'SINR' && + + } + + { + activePanel === 'CPD' && + + } + + } + + ); + } + } + + + public componentDidMount() { + this.props.setCurrentPanel(null); + this.props.getAllDevicesPMdata(); + } + + /** + * Function which selects the first ltp returned from the database on selection of network element. + */ + private selectFirstLtp = (firstLtp: string) => { + this.setState({ + showPanels: true, + selectedLtp: firstLtp, + }); + this.preFilterChangeAndReload(this.state.selectedNetworkElement, this.state.selectedTimePeriod, firstLtp); + this.changeTabs('PerformanceData'); + }; + + /** + * A function which reloads the visible table, if available, based on prefilters defined by network element and ltp on selected time period. + */ + private preFilterChangeAndReload = (networkElement: string, timePeriod: string, ltp: string) => { + const newPreFilter = { + 'node-name': networkElement, + 'uuid-interface': ltp, + }; + + const activePanel = this.props.activePanel; + + if (this.props.activePanel !== null) { + // set prefilter and reload data if panel is open + + switch (activePanel) { + case 'PerformanceData': + this.props.performanceDataPreActions.onPreFilterChanged(newPreFilter); + break; + case 'ReceiveLevel': + this.props.receiveLevelPreActions.onPreFilterChanged(newPreFilter); + break; + case 'TransmissionPower': + this.props.transmissionPowerPreActions.onPreFilterChanged(newPreFilter); + break; + case 'AdaptiveModulation': + this.props.adaptiveModulationPreActions.onPreFilterChanged(newPreFilter); + break; + case 'Temperature': + this.props.temperaturePreActions.onPreFilterChanged(newPreFilter); + break; + case 'SINR': + this.props.signalToInterferencePreActions.onPreFilterChanged(newPreFilter); + break; + case 'CPD': + this.props.crossPolarDiscriminationPreActions.onPreFilterChanged(newPreFilter); + break; + default: + // do nothing if all panels are closed + break; + } + } + + // set prefilter + this.setState({ preFilter: newPreFilter }); + + }; + + /** + * Function which handles network element changes. + */ + private handleNetworkElementSelect = (selectedNetworkElement: string) => { + + this.setState({ + showLtps: true, + selectedNetworkElement: selectedNetworkElement, + showNetworkElementsTable: false, + showPanels: false, + selectedLtp: '-1', + }); + + this.props.resetSubViews(); + this.props.resetLtps(); + this.setState({ preFilter: {} }); + this.props.changeNode(selectedNetworkElement); + this.props.getDistinctLtpsIds(selectedNetworkElement, this.state.selectedTimePeriod, '-1', this.selectFirstLtp); + }; + + private handleURLChange = (selectedNetworkElement: string) => { + + if (this.props.shouldReload) { + + this.setState({ + showLtps: true, + selectedNetworkElement: selectedNetworkElement, + showPanels: false, + selectedLtp: '-1', + }); + this.props.getDistinctLtpsIds(selectedNetworkElement, this.state.selectedTimePeriod, '-1', this.selectFirstLtp); + this.props.setShouldReload(false); + } + }; + + /** + * Function which resets the ltps to "--select--" in the state if the passed parameter @ltpNotSelected is true. + * @param ltpNotSelected: true, if existing selected is not available in the given time period, else false + */ + private resetLtpDropdown = (ltpNotSelected: boolean) => { + if (ltpNotSelected) { + this.setState({ + selectedLtp: '-1', + showPanels: false, + }); + } + }; + + /** + * Function which handles the time period changes. + */ + private handleTimePeriodChange = (event: SelectChangeEvent) => { + + const selectedTimeInterval = event.target.value === '15min' + ? PmDataInterval.pmInterval15Min + : PmDataInterval.pmInterval24Hours; + + this.setState({ + selectedTimePeriod: event.target.value as string, + }); + + this.props.timeIntervalChange(selectedTimeInterval); + this.props.getDistinctLtpsIds(this.state.selectedNetworkElement, event.target.value as string, this.state.selectedLtp, undefined, this.resetLtpDropdown); + this.preFilterChangeAndReload(this.state.selectedNetworkElement, event.target.value as string, this.state.selectedLtp); + }; + + /** + * Function which handles the ltp changes. + */ + private handleLtpChange = (event:SelectChangeEvent ) => { + + if (event.target.value === '-1') { + this.setState({ + showPanels: false, + selectedLtp: event.target.value, + }); + + } else if (event.target.value !== this.state.selectedLtp) { + this.setState({ + showPanels: true, + selectedLtp: event.target.value as string, + }); + this.preFilterChangeAndReload(this.state.selectedNetworkElement, this.state.selectedTimePeriod, event.target.value as string); + + } + }; +} + +const PerformanceHistoryApplication = withStyles(PerformanceHistoryComponentStyles)(connect(mapProps, mapDispatcher)(PerformanceHistoryComponent)); +export default PerformanceHistoryApplication; diff --git a/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/tsconfig.json b/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/tsconfig.json new file mode 100644 index 000000000..ca65092e0 --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/tsconfig.json @@ -0,0 +1,37 @@ +{ + "compilerOptions": { + "baseUrl": "./src", + "outDir": "./dist", + "sourceMap": true, + "forceConsistentCasingInFileNames": true, + "allowSyntheticDefaultImports": true, + "allowUnreachableCode": false, + "allowUnusedLabels": false, + "noFallthroughCasesInSwitch": true, + "noImplicitAny": true, + "noImplicitReturns": true, + "noImplicitThis": true, + "strictNullChecks": true, + "pretty": true, + "newLine": "LF", + "module": "es2015", + "target": "es2016", + "moduleResolution": "node", + "experimentalDecorators": true, + "jsx": "preserve", + "lib": [ + "dom", + "es2015", + "es2016" + ], + "types": [ + "prop-types", + "react", + "react-dom" + ] + }, + "exclude": [ + "dist", + "node_modules" + ] +} diff --git a/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/webpack.config.js b/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/webpack.config.js new file mode 100644 index 000000000..2f25d0df1 --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/webpack.config.js @@ -0,0 +1,167 @@ +/** + * Webpack 4 configuration file + * see https://webpack.js.org/configuration/ + * see https://webpack.js.org/configuration/dev-server/ + */ + +"use strict"; + +const path = require("path"); +const webpack = require("webpack"); +const CopyWebpackPlugin = require("copy-webpack-plugin"); +const TerserPlugin = require('terser-webpack-plugin'); + +// const __dirname = (path => path.replace(/^([a-z]\:)/, c => c.toUpperCase()))(process.__dirname()); + +module.exports = (env) => { + const distPath = path.resolve(__dirname, env === "release" ? "." : "../..", "dist"); + const frameworkPath = path.resolve(__dirname, env === "release" ? "../../framework" : "../..", "dist"); + return [{ + name: "App", + + mode: "none", //disable default behavior + + target: "web", + + context: path.resolve(__dirname, "src"), + + entry: { + performanceHistoryApp: ["./pluginPerformance.tsx"] + }, + + devtool: env === "release" ? false : "source-map", + + resolve: { + extensions: [".ts", ".tsx", ".js", ".jsx"] + }, + + output: { + path: distPath, + filename: "[name].js", + library: "[name]", + libraryTarget: "umd2", + chunkFilename: "[name].js" + }, + module: { + rules: [{ + test: /\.tsx?$/, + exclude: /node_modules/, + use: [{ + loader: "babel-loader" + }, { + loader: "ts-loader" + }] + }, { + test: /\.jsx?$/, + exclude: /node_modules/, + use: [{ + loader: "babel-loader" + }] + },{ + //don't minify images + test: /\.(png|gif|jpg|svg)$/, + use: [{ + loader: 'url-loader', + options: { + limit: 10, + name: './images/[name].[ext]' + } + }] + }] + }, + + optimization: { + noEmitOnErrors: true, + namedModules: env !== "release", + minimize: env === "release", + minimizer: env !== "release" ? [] : [new TerserPlugin({ + terserOptions: { + warnings: false, // false, true, "verbose" + compress: { + drop_console: true, + drop_debugger: true, + } + } + })], + }, + plugins: [ + new webpack.DllReferencePlugin({ + context: path.resolve(__dirname, "../../framework/src"), + manifest: require(path.resolve(frameworkPath, "vendor-manifest.json")), + sourceType: "umd2" + }), + new webpack.DllReferencePlugin({ + context: path.resolve(__dirname, "../../framework/src"), + manifest: require(path.resolve(frameworkPath, "app-manifest.json")), + sourceType: "umd2" + }), + ...(env === "release" ? [ + new webpack.DefinePlugin({ + "process.env": { + NODE_ENV: "'production'", + VERSION: JSON.stringify(require("./package.json").version) + } + }), + ] : [ + new webpack.DefinePlugin({ + "process.env": { + NODE_ENV: "'development'", + VERSION: JSON.stringify(require("./package.json").version) + } + }), + new CopyWebpackPlugin([{ + from: 'index.html', + to: distPath + }]), + ]) + ], + + devServer: { + public: "http://localhost:3100", + contentBase: frameworkPath, + + compress: true, + headers: { + "Access-Control-Allow-Origin": "*" + }, + host: "0.0.0.0", + port: 3100, + disableHostCheck: true, + historyApiFallback: true, + inline: true, + hot: false, + quiet: false, + stats: { + colors: true + }, + proxy: { + "/oauth2/": { + target: "http://10.20.6.29:48181", + secure: false + }, + "/database/": { + target: "http://10.20.6.29:48181", + secure: false + }, + "/restconf/": { + target: "http://10.20.6.29:48181", + secure: false + }, + "/rests/": { + target: "http://10.20.6.29:48181", + secure: false + }, + "/help/": { + target: "http://10.20.6.29:48181", + secure: false + }, + "/websocket": { + target: "http://10.20.6.29:48181", + ws: true, + changeOrigin: true, + secure: false + } + } + } + }]; +} -- cgit 1.2.3-korg