From 9ca823e472ad875bddad78f8104976a0fd73efc9 Mon Sep 17 00:00:00 2001 From: Ravi Pendurty Date: Wed, 11 Jun 2025 20:21:47 +0530 Subject: [PATCH] Provide ccsdk features wt provide ccsdk features odlux Issue-ID: OAM-468 Change-Id: I9064d8d38378106179767f4784f7df25e6edb2e0 Signed-off-by: Ravi Pendurty --- features/sdnr/odlux/helpserver/pom.xml | 40 + features/sdnr/odlux/helpserver/provider/README.md | 34 + .../provider/bitnami/nginx/help/meta.json | 1 + .../provider/bitnami/nginx/help/test.css | 1 + .../provider/bitnami/nginx/help/test.eps | 1 + .../provider/bitnami/nginx/help/test.pdf | 1 + .../provider/bitnami/nginx/help/test/test.txt | 1 + .../sdnr/odlux/helpserver/provider/help/meta.json | 1 + .../odlux/helpserver/provider/help/test/test.txt | 1 + features/sdnr/odlux/helpserver/provider/pom.xml | 88 + .../features/sdnr/wt/helpserver/HelpServlet.java | 179 + .../helpserver/data/HelpInfrastructureObject.java | 174 + .../main/resources/help/mediatorserver/README.md | 144 + .../help/mediatorserver/installation/README.md | 246 + .../help/mediatorserver/mediator/README.md | 66 + .../provider/src/main/resources/help/meta.json | 261 + .../src/main/resources/help/sdnr/ONAP-SDN-R.png | Bin 0 -> 195578 bytes .../src/main/resources/help/sdnr/README.md | 8 + .../src/main/resources/help/sdnr/abbreviations.md | 212 + .../src/main/resources/help/sdnr/connect/README.md | 19 + .../provider/src/main/resources/help/sdnr/faq.md | 72 + .../src/main/resources/help/sdnr/general.md | 25 + .../resources/help/sdnr/linkCalculator/README.md | 51 + .../main/resources/help/sdnr/networkMap/README.md | 46 + .../main/resources/help/sdnr/pnfConfig/README.md | 22 + .../main/resources/help/sdnr/pnfEventLog/README.md | 6 + .../main/resources/help/sdnr/pnfFault/README.md | 31 + .../resources/help/sdnr/pnfInventory/README.md | 30 + .../resources/help/sdnr/pnfMaintenance/README.md | 7 + .../main/resources/help/sdnr/pnfMediator/README.md | 7 + .../resources/help/sdnr/pnfPerformance/README.md | 14 + .../sdnr/wt/helpserver/test/TestMyServlet.java | 177 + .../src/test/resources/simplelogger.properties | 58 + .../sdnr/odlux/helpserver/provider/test/test.txt | 1 + features/sdnr/odlux/odlux/.eslintignore | 13 + features/sdnr/odlux/odlux/.eslintrc.json | 54 + features/sdnr/odlux/odlux/.gitignore | 7 + features/sdnr/odlux/odlux/LICENSE | 15 + features/sdnr/odlux/odlux/README.md | 111 + features/sdnr/odlux/odlux/apps/apiDemo/.babelrc | 17 + .../sdnr/odlux/odlux/apps/apiDemo/package.json | 46 + features/sdnr/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/odlux/odlux/apps/apiDemo/src/index.html | 24 + .../odlux/odlux/apps/apiDemo/src/models/module.ts | 28 + .../sdnr/odlux/odlux/apps/apiDemo/src/plugin.tsx | 53 + .../sdnr/odlux/odlux/apps/apiDemo/tsconfig.json | 37 + .../odlux/odlux/apps/apiDemo/webpack.config.js | 139 + .../sdnr/odlux/odlux/apps/app-installer/pom.xml | 181 + .../src/assembly/assemble_mvnrepo_zip.xml | 47 + .../odlux/odlux/apps/configurationApp/.babelrc | 17 + .../odlux/odlux/apps/configurationApp/package.json | 47 + .../odlux/apps/configurationApp/policies.json | 12 + .../sdnr/odlux/odlux/apps/configurationApp/pom.xml | 109 + .../configurationApp/src/actions/deviceActions.ts | 685 + .../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 | 88 + .../odlux/apps/configurationApp/src/index.html | 30 + .../src/models/networkElementConnection.ts | 37 + .../apps/configurationApp/src/models/uiModels.ts | 242 + .../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 | 351 + .../src/views/configurationApplication.tsx | 951 + .../src/views/networkElementSelector.tsx | 72 + .../apps/configurationApp/src/yang/whenParser.ts | 289 + .../apps/configurationApp/src/yang/yangParser.ts | 1730 ++ .../odlux/apps/configurationApp/tsconfig.json | 38 + .../odlux/apps/configurationApp/webpack.config.js | 150 + features/sdnr/odlux/odlux/apps/connectApp/.babelrc | 17 + .../sdnr/odlux/odlux/apps/connectApp/package.json | 44 + .../sdnr/odlux/odlux/apps/connectApp/policies.json | 12 + features/sdnr/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 | 315 + .../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 + .../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 | 70 + .../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 | 310 + .../apps/connectApp/src/views/connectView.tsx | 102 + .../sdnr/odlux/odlux/apps/connectApp/tsconfig.json | 6 + .../odlux/odlux/apps/connectApp/webpack.config.js | 202 + features/sdnr/odlux/odlux/apps/demoApp/.babelrc | 17 + .../sdnr/odlux/odlux/apps/demoApp/package.json | 46 + features/sdnr/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/odlux/odlux/apps/demoApp/src/index.html | 26 + .../odlux/odlux/apps/demoApp/src/models/author.ts | 37 + .../sdnr/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/odlux/odlux/apps/demoApp/tsconfig.json | 37 + .../odlux/odlux/apps/demoApp/webpack.config.js | 134 + .../sdnr/odlux/odlux/apps/eventLogApp/.babelrc | 17 + .../sdnr/odlux/odlux/apps/eventLogApp/package.json | 43 + features/sdnr/odlux/odlux/apps/eventLogApp/pom.xml | 109 + .../src/assets/icons/eventLogAppIcon.svg | 21 + .../src/components/refreshEventLogDialog.tsx | 105 + .../src/handlers/eventLogAppRootHandler.ts | 44 + .../eventLogApp/src/handlers/eventLogHandler.tsx | 36 + .../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 | 87 + .../odlux/odlux/apps/eventLogApp/tsconfig.json | 37 + .../odlux/odlux/apps/eventLogApp/webpack.config.js | 199 + features/sdnr/odlux/odlux/apps/faultApp/.babelrc | 17 + .../sdnr/odlux/odlux/apps/faultApp/package.json | 46 + features/sdnr/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 | 116 + .../apps/faultApp/src/components/dashboardHome.tsx | 450 + .../apps/faultApp/src/components/faultStatus.tsx | 93 + .../src/components/refreshAlarmLogDialog.tsx | 108 + .../src/components/refreshCurrentAlarmsDialog.tsx | 109 + .../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/odlux/odlux/apps/faultApp/src/index.html | 26 + .../odlux/odlux/apps/faultApp/src/models/fault.ts | 97 + .../odlux/apps/faultApp/src/models/panelId.ts | 18 + .../odlux/odlux/apps/faultApp/src/pluginFault.tsx | 171 + .../faultApp/src/services/faultStatusService.ts | 69 + .../apps/faultApp/src/views/faultApplication.tsx | 208 + .../sdnr/odlux/odlux/apps/faultApp/tsconfig.json | 37 + .../odlux/odlux/apps/faultApp/webpack.config.js | 166 + features/sdnr/odlux/odlux/apps/helpApp/.babelrc | 17 + .../sdnr/odlux/odlux/apps/helpApp/package.json | 48 + features/sdnr/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/odlux/odlux/apps/helpApp/src/index.html | 29 + .../odlux/odlux/apps/helpApp/src/models/tocNode.ts | 42 + .../sdnr/odlux/odlux/apps/helpApp/src/plugin.tsx | 91 + .../odlux/apps/helpApp/src/services/helpService.ts | 65 + .../odlux/odlux/apps/helpApp/src/utilities/path.ts | 79 + .../apps/helpApp/src/views/helpApplication.tsx | 84 + .../odlux/apps/helpApp/src/views/helpTocApp.tsx | 55 + .../sdnr/odlux/odlux/apps/helpApp/tsconfig.json | 37 + .../odlux/odlux/apps/helpApp/webpack.config.js | 189 + .../sdnr/odlux/odlux/apps/inventoryApp/.babelrc | 17 + .../odlux/odlux/apps/inventoryApp/package.json | 43 + .../sdnr/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 | 109 + .../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/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 | 87 + .../inventoryApp/src/services/inventoryService.ts | 91 + .../apps/inventoryApp/src/views/dashboard.tsx | 171 + .../odlux/apps/inventoryApp/src/views/detail.tsx | 44 + .../odlux/apps/inventoryApp/src/views/treeview.tsx | 133 + .../odlux/odlux/apps/inventoryApp/tsconfig.json | 37 + .../odlux/apps/inventoryApp/webpack.config.js | 178 + .../sdnr/odlux/odlux/apps/maintenanceApp/.babelrc | 17 + .../odlux/odlux/apps/maintenanceApp/package.json | 46 + .../sdnr/odlux/odlux/apps/maintenanceApp/pom.xml | 109 + .../src/actions/maintenenceActions.ts | 89 + .../src/assets/icons/maintenanceAppIcon.svg | 50 + .../src/components/editMaintenenceEntryDialog.tsx | 189 + .../src/components/refreshMaintenanceEntries.tsx | 103 + .../src/handlers/maintenanceAppRootHandler.ts | 39 + .../src/handlers/maintenanceEntriesHandler.ts | 35 + .../odlux/odlux/apps/maintenanceApp/src/index.html | 26 + .../src/models/maintenanceEntryType.ts | 33 + .../apps/maintenanceApp/src/pluginMaintenance.tsx | 44 + .../src/services/maintenenceService.ts | 83 + .../apps/maintenanceApp/src/utils/timeUtils.ts | 45 + .../maintenanceApp/src/views/maintenanceView.tsx | 177 + .../odlux/odlux/apps/maintenanceApp/tsconfig.json | 37 + .../odlux/apps/maintenanceApp/webpack.config.js | 168 + .../sdnr/odlux/odlux/apps/mediatorApp/.babelrc | 17 + .../sdnr/odlux/odlux/apps/mediatorApp/package.json | 44 + features/sdnr/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 + .../odlux/odlux/apps/mediatorApp/src/index.html | 29 + .../apps/mediatorApp/src/models/mediatorServer.ts | 77 + .../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 + .../odlux/odlux/apps/mediatorApp/tsconfig.json | 38 + .../sdnr/odlux/odlux/apps/mediatorApp/tslint.json | 4 + .../odlux/odlux/apps/mediatorApp/webpack.config.js | 158 + .../sdnr/odlux/odlux/apps/microwaveApp/.babelrc | 17 + .../odlux/odlux/apps/microwaveApp/package.json | 55 + .../sdnr/odlux/odlux/apps/microwaveApp/pom.xml | 109 + .../odlux/odlux/apps/microwaveApp/src/index.html | 29 + .../actions/lineOfSightCommonActions.ts | 49 + .../lineOfSight/actions/lineOfSightMapActions.ts | 68 + .../src/lineOfSight/assets/lineOfSightAppIcon.svg | 19 + .../components/lineOfSightConnectionErrorPoup.tsx | 39 + .../components/lineOfSightHeightChart.tsx | 122 + .../src/lineOfSight/components/lineOfSightMap.tsx | 290 + .../components/lineOfSightMapContextMenu.tsx | 82 + .../lineOfSight/components/lineOfSightMapInfo.tsx | 144 + .../apps/microwaveApp/src/lineOfSight/config.ts | 46 + .../lineOfSight/handlers/lineOfSightMapHandler.ts | 76 + .../apps/microwaveApp/src/lineOfSight/hooks/d3.ts | 37 + .../model/lineOfSightGPSProfileResult.ts | 19 + .../src/lineOfSight/model/lineOfSightHeight.tsx | 22 + .../src/lineOfSight/model/lineOfSightLatLon.ts | 46 + .../service/lineOfSightHeightService.ts | 68 + .../microwaveApp/src/lineOfSight/styles/index.css | 13 + .../src/lineOfSight/styles/mapbox-gl.css | 1 + .../src/lineOfSight/utils/lineOfSightMap.ts | 120 + .../src/lineOfSight/utils/lineOfSightMath.ts | 29 + .../src/lineOfSight/views/lineOfSightMain.tsx | 30 + .../actions/adaptiveModulationAction.ts | 128 + .../src/linkCalculator/actions/antennaActions.ts | 38 + .../actions/atmosphericLossAction.ts | 45 + .../src/linkCalculator/actions/bandPlanAction.ts | 128 + .../src/linkCalculator/actions/commonActions.ts | 34 + .../src/linkCalculator/actions/errorAction.ts | 74 + .../linkCalculator/actions/handleButtonAction.ts | 75 + .../src/linkCalculator/actions/linkAction.ts | 36 + .../src/linkCalculator/actions/queryActions.ts | 39 + .../src/linkCalculator/actions/radioActions.ts | 177 + .../src/linkCalculator/actions/saveLinkAction.ts | 95 + .../src/linkCalculator/actions/siteAction.ts | 41 + .../src/linkCalculator/actions/viewAction.ts | 40 + .../src/linkCalculator/actions/waveguideActions.ts | 65 + .../assets/icons/microwaveAppIcon.svg | 19 + .../components/adaptiveModulationDialog.tsx | 157 + .../src/linkCalculator/components/antenna.tsx | 255 + .../src/linkCalculator/components/attenuations.tsx | 298 + .../components/channelListDialog.tsx | 316 + .../linkCalculator/components/connectionInfo.tsx | 66 + .../linkCalculator/components/frequencyChannel.tsx | 386 + .../src/linkCalculator/components/linkTable.tsx | 123 + .../src/linkCalculator/components/location.tsx | 262 + .../components/manualLocationEntr.tsx | 164 + .../components/missingInformation.tsx | 45 + .../src/linkCalculator/components/outlinedDiv.tsx | 48 + .../src/linkCalculator/components/radio.tsx | 597 + .../components/textFieldwithAdornment.tsx | 47 + .../src/linkCalculator/components/waveguide.tsx | 328 + .../src/linkCalculator/handlers/antennaHandler.ts | 104 + .../handlers/atmosphericLossHandler.ts | 91 + .../src/linkCalculator/handlers/bandPlanHandler.ts | 113 + .../src/linkCalculator/handlers/errorHandler.ts | 57 + .../src/linkCalculator/handlers/linkHandler.ts | 57 + .../linkCalculator/handlers/linkTableHandler.ts | 36 + .../src/linkCalculator/handlers/queryHandler.ts | 83 + .../src/linkCalculator/handlers/radioHandler.ts | 327 + .../src/linkCalculator/handlers/rootHandler.ts | 87 + .../src/linkCalculator/handlers/siteHandler.ts | 87 + .../src/linkCalculator/handlers/viewHandler.ts | 56 + .../linkCalculator/handlers/waveguideHandler.ts | 154 + .../model/adaptiveModulationInput.ts | 49 + .../model/adaptiveModulationTable.ts | 37 + .../src/linkCalculator/model/antenna.ts | 45 + .../src/linkCalculator/model/bandPlan.ts | 84 + .../src/linkCalculator/model/calculationResult.ts | 33 + .../microwaveApp/src/linkCalculator/model/link.ts | 89 + .../src/linkCalculator/model/linkTable.ts | 52 + .../src/linkCalculator/model/modulation.ts | 40 + .../microwaveApp/src/linkCalculator/model/radio.ts | 62 + .../microwaveApp/src/linkCalculator/model/tabId.ts | 21 + .../src/linkCalculator/model/topologyTypes.ts | 80 + .../src/linkCalculator/model/updateLink.ts | 50 + .../src/linkCalculator/model/waveguide.ts | 52 + .../src/linkCalculator/service/dataService.ts | 158 + .../linkCalculator/service/processingService.ts | 94 + .../src/linkCalculator/utils/checkLink.ts | 77 + .../src/linkCalculator/utils/geoConverter.ts | 34 + .../microwaveApp/src/linkCalculator/utils/math.ts | 30 + .../src/linkCalculator/views/linkCalculation.tsx | 377 + .../src/linkCalculator/views/mainView.tsx | 88 + .../apps/microwaveApp/src/pluginMicrowave.tsx | 213 + .../odlux/odlux/apps/microwaveApp/tsconfig.json | 6 + .../odlux/apps/microwaveApp/webpack.config.js | 147 + features/sdnr/odlux/odlux/apps/minimumApp/.babelrc | 17 + .../sdnr/odlux/odlux/apps/minimumApp/package.json | 43 + features/sdnr/odlux/odlux/apps/minimumApp/pom.xml | 109 + .../minimumApp/src/assets/icons/minimumAppIcon.svg | 27 + .../src/handlers/minimumAppRootHandler.ts | 38 + .../odlux/odlux/apps/minimumApp/src/index.html | 24 + .../odlux/odlux/apps/minimumApp/src/plugin.tsx | 48 + .../sdnr/odlux/odlux/apps/minimumApp/tsconfig.json | 37 + .../odlux/odlux/apps/minimumApp/webpack.config.js | 136 + .../sdnr/odlux/odlux/apps/networkMapApp/.babelrc | 17 + .../odlux/odlux/apps/networkMapApp/icons/README.md | 29 + .../odlux/apps/networkMapApp/icons/apartment.png | Bin 0 -> 1717 bytes .../apps/networkMapApp/icons/apartment.png.d.ts | 2 + .../odlux/apps/networkMapApp/icons/customize.png | Bin 0 -> 399 bytes .../apps/networkMapApp/icons/customize.png.d.ts | 2 + .../odlux/apps/networkMapApp/icons/datacenter.png | Bin 0 -> 2603 bytes .../apps/networkMapApp/icons/datacenter.png.d.ts | 2 + .../apps/networkMapApp/icons/datacenterred.png | Bin 0 -> 1814 bytes .../networkMapApp/icons/datacenterred.png.d.ts | 2 + .../odlux/apps/networkMapApp/icons/factory.png | Bin 0 -> 1285 bytes .../apps/networkMapApp/icons/factory.png.d.ts | 2 + .../odlux/apps/networkMapApp/icons/factoryred.png | Bin 0 -> 1336 bytes .../apps/networkMapApp/icons/factoryred.png.d.ts | 2 + .../odlux/odlux/apps/networkMapApp/icons/lamp.png | Bin 0 -> 2233 bytes .../odlux/apps/networkMapApp/icons/lamp.png.d.ts | 2 + .../odlux/apps/networkMapApp/icons/lampred.png | Bin 0 -> 1853 bytes .../apps/networkMapApp/icons/lampred.png.d.ts | 2 + .../odlux/odlux/apps/networkMapApp/package.json | 49 + .../sdnr/odlux/odlux/apps/networkMapApp/pom.xml | 106 + .../src/actions/connectivityAction.ts | 64 + .../networkMapApp/src/actions/detailsAction.ts | 146 + .../networkMapApp/src/actions/filterActions.ts | 25 + .../apps/networkMapApp/src/actions/mapActions.ts | 196 + .../apps/networkMapApp/src/actions/searchAction.ts | 25 + .../networkMapApp/src/actions/settingsAction.ts | 86 + .../src/actions/sitedocManagementAction.ts | 74 + .../odlux/odlux/apps/networkMapApp/src/app.tsx | 166 + .../src/assets/icons/networkMapAppIcon.svg | 26 + .../src/components/customize/networkMapSetup.tsx | 311 + .../src/components/customize/themeElement.tsx | 48 + .../networkMapApp/src/components/denseTable.tsx | 159 + .../src/components/details/details.tsx | 125 + .../src/components/details/linkDetails.tsx | 152 + .../src/components/details/serviceDetails.tsx | 48 + .../src/components/details/siteDetails.tsx | 258 + .../src/components/map/connectionInfo.tsx | 88 + .../networkMapApp/src/components/map/filterBar.tsx | 88 + .../src/components/map/iconSwitch.tsx | 101 + .../src/components/map/layerSelection.tsx | 123 + .../apps/networkMapApp/src/components/map/map.tsx | 791 + .../src/components/map/mapControl.tsx | 116 + .../networkMapApp/src/components/map/mapPopup.tsx | 81 + .../networkMapApp/src/components/map/searchBar.tsx | 192 + .../src/components/map/searchResultDisplay.tsx | 74 + .../src/components/map/statistics.tsx | 69 + .../src/components/stadok/stadokDetailsPopup.tsx | 314 + .../odlux/odlux/apps/networkMapApp/src/config.ts | 89 + .../src/handlers/connectivityHandler.ts | 45 + .../networkMapApp/src/handlers/detailsHandler.ts | 74 + .../networkMapApp/src/handlers/filterHandler.ts | 41 + .../apps/networkMapApp/src/handlers/mapHandler.ts | 326 + .../apps/networkMapApp/src/handlers/rootHandler.ts | 56 + .../networkMapApp/src/handlers/searchHandler.ts | 37 + .../networkMapApp/src/handlers/settingsHandler.ts | 73 + .../src/handlers/sitedocManagementHandler.ts | 60 + .../odlux/odlux/apps/networkMapApp/src/index.html | 30 + .../apps/networkMapApp/src/model/boundingBox.ts | 67 + .../apps/networkMapApp/src/model/coordinates.ts | 28 + .../odlux/apps/networkMapApp/src/model/count.ts | 23 + .../apps/networkMapApp/src/model/historyEntry.ts | 24 + .../src/model/networkElementConnection.ts | 30 + .../apps/networkMapApp/src/model/searchResult.ts | 25 + .../odlux/apps/networkMapApp/src/model/settings.ts | 53 + .../apps/networkMapApp/src/model/siteDocTypes.ts | 52 + .../apps/networkMapApp/src/model/stadokOrder.ts | 55 + .../apps/networkMapApp/src/model/stadokSite.ts | 52 + .../apps/networkMapApp/src/model/topologyTypes.ts | 144 + .../apps/networkMapApp/src/pluginTransport.tsx | 76 + .../apps/networkMapApp/src/services/dataService.ts | 118 + .../networkMapApp/src/services/mapImagesService.ts | 56 + .../networkMapApp/src/services/settingsService.ts | 40 + .../src/services/sitedocDataService.ts | 70 + .../odlux/apps/networkMapApp/src/styles/index.css | 13 + .../apps/networkMapApp/src/styles/mapbox-gl.css | 1 + .../apps/networkMapApp/src/utils/detailsUtils.ts | 48 + .../apps/networkMapApp/src/utils/mapLayers.ts | 275 + .../odlux/apps/networkMapApp/src/utils/mapUtils.ts | 148 + .../odlux/odlux/apps/networkMapApp/tsconfig.json | 6 + .../odlux/apps/networkMapApp/webpack.config.js | 146 + .../odlux/apps/performanceHistoryApp/.babelrc | 17 + .../odlux/apps/performanceHistoryApp/package.json | 46 + .../odlux/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 + .../sdnr/odlux/odlux/apps/siteManagerApp/.babelrc | 17 + .../odlux/odlux/apps/siteManagerApp/package.json | 48 + .../sdnr/odlux/odlux/apps/siteManagerApp/pom.xml | 106 + .../siteManagerApp/src/actions/detailsAction.ts | 126 + .../siteManagerApp/src/actions/panelActions.ts | 32 + .../src/actions/siteManagerSiteSearchAction.ts | 56 + .../src/actions/siteManagerTreeActions.ts | 214 + .../src/actions/sitedocManagementAction.ts | 93 + .../src/assets/icons/siteManagerAppIcon.svg | 40 + .../src/components/createNewOrder.tsx | 252 + .../siteManagerApp/src/components/denseTable.tsx | 125 + .../siteManagerApp/src/components/deviceTable.tsx | 65 + .../siteManagerApp/src/components/linkTable.tsx | 82 + .../src/components/messageDialog.tsx | 53 + .../siteManagerApp/src/components/orderTask.tsx | 60 + .../src/components/picturesTable.tsx | 114 + .../src/components/refreshSiteTableDialog.tsx | 104 + .../src/components/siteAdditionalInformation.tsx | 136 + .../src/components/siteConfiguration.tsx | 357 + .../siteManagerApp/src/components/siteDetails.tsx | 216 + .../src/components/siteManagerSiteSearch.tsx | 130 + .../src/components/siteManagerTreeview.tsx | 1212 ++ .../src/components/siteOrdersTable.tsx | 155 + .../siteManagerApp/src/components/siteTable.tsx | 122 + .../siteManagerApp/src/components/treeItem.tsx | 107 + .../src/components/tssReportsTable.tsx | 101 + .../odlux/odlux/apps/siteManagerApp/src/config.ts | 50 + .../siteManagerApp/src/handlers/detailsReducer.ts | 70 + .../src/handlers/deviceTableHandler.ts | 37 + .../src/handlers/linkTableHandler.ts | 37 + .../src/handlers/siteManagerAppRootHandler.ts | 72 + .../src/handlers/siteManagerSiteSearchHandler.ts | 74 + .../src/handlers/siteManagerTreeHandler.ts | 219 + .../src/handlers/siteTableHandler.ts | 36 + .../src/handlers/sitedocManagementHandler.ts | 62 + .../odlux/odlux/apps/siteManagerApp/src/index.html | 30 + .../odlux/apps/siteManagerApp/src/models/count.ts | 19 + .../apps/siteManagerApp/src/models/historyEntry.ts | 22 + .../odlux/apps/siteManagerApp/src/models/link.ts | 49 + .../src/models/networkElementConnection.ts | 30 + .../apps/siteManagerApp/src/models/panelId.ts | 19 + .../odlux/apps/siteManagerApp/src/models/site.ts | 49 + .../apps/siteManagerApp/src/models/siteDetails.ts | 35 + .../apps/siteManagerApp/src/models/siteDocTypes.ts | 51 + .../apps/siteManagerApp/src/models/siteManager.ts | 182 + .../apps/siteManagerApp/src/models/siteSearch.ts | 41 + .../apps/siteManagerApp/src/models/stadokSite.ts | 57 + .../siteManagerApp/src/models/topologyTypes.ts | 15 + .../apps/siteManagerApp/src/pluginSiteManager.tsx | 89 + .../siteManagerApp/src/services/dataService.ts | 52 + .../apps/siteManagerApp/src/services/mapService.ts | 62 + .../src/services/siteManagerService.ts | 274 + .../src/services/sitedocDataService.ts | 82 + .../siteManagerApp/src/views/OrderCreation.tsx | 86 + .../apps/siteManagerApp/src/views/siteManager.tsx | 86 + .../odlux/odlux/apps/siteManagerApp/tsconfig.json | 37 + .../odlux/apps/siteManagerApp/webpack.config.js | 142 + .../odlux/apps/unmFaultManagementApp/.babelrc | 17 + .../odlux/apps/unmFaultManagementApp/package.json | 46 + .../odlux/odlux/apps/unmFaultManagementApp/pom.xml | 105 + .../src/actions/panelChangeActions.ts | 37 + .../unmFaultManagementAlarmStatusActions.ts | 47 + .../src/assets/icons/unmFaultManagementAppIcon.svg | 19 + .../src/components/refreshUnmFaultLogDialog.tsx | 112 + .../unmFaultManagementAlarmDetailsDialog.tsx | 172 + .../src/components/unmFaultManagementDashboard.tsx | 111 + .../src/components/unmFaultManagementFaultLog.tsx | 151 + .../src/handlers/unmFaultLogEntriesHandler.ts | 36 + .../unmFaultManagementAlarmStatusHandler.ts | 50 + .../handlers/unmFaultManagementAppRootHandler.ts | 58 + .../apps/unmFaultManagementApp/src/index.html | 25 + .../unmFaultManagementApp/src/models/panelId.ts | 18 + .../unmFaultManagementApp/src/models/unmFault.ts | 52 + .../src/pluginUnmFaultManagement.tsx | 81 + .../src/services/unmFaultStatusService.ts | 62 + .../src/views/unmFaultManagementApplication.tsx | 93 + .../odlux/apps/unmFaultManagementApp/tsconfig.json | 37 + .../apps/unmFaultManagementApp/webpack.config.js | 166 + features/sdnr/odlux/odlux/framework/.babelrc | 17 + features/sdnr/odlux/odlux/framework/LICENSE | 13 + features/sdnr/odlux/odlux/framework/package.json | 59 + features/sdnr/odlux/odlux/framework/pom.xml | 262 + .../odlux/framework/src/actions/authentication.ts | 107 + .../odlux/framework/src/actions/errorActions.ts | 42 + .../odlux/framework/src/actions/loginProvider.ts | 36 + .../odlux/framework/src/actions/menuAction.ts | 31 + .../framework/src/actions/navigationActions.ts | 64 + .../odlux/framework/src/actions/settingsAction.ts | 130 + .../odlux/framework/src/actions/snackbarActions.ts | 37 + .../odlux/framework/src/actions/titleActions.ts | 27 + .../odlux/framework/src/actions/websocketAction.ts | 26 + features/sdnr/odlux/odlux/framework/src/app.css | 17 + features/sdnr/odlux/odlux/framework/src/app.tsx | 120 + .../odlux/framework/src/assets/icons/About.svg | 18 + .../odlux/framework/src/assets/icons/Home.svg | 28 + .../odlux/framework/src/assets/icons/Menu.svg | 18 + .../odlux/framework/src/assets/icons/Tools.svg | 35 + .../odlux/framework/src/assets/icons/User.svg | 21 + .../framework/src/assets/icons/ht.Connect.png | Bin 0 -> 14989 bytes .../framework/src/assets/icons/ht.Connect.svg | 81 + .../framework/src/assets/images/defaultLogo.svg | 179 + .../src/assets/images/defaultLogo.svg.d.ts | 19 + .../odlux/framework/src/assets/images/home.svg | 20 + .../framework/src/assets/images/home.svg.d.ts | 20 + .../framework/src/assets/images/odluxLogo.gif | Bin 0 -> 5709 bytes .../framework/src/assets/images/odluxLogo.gif.d.ts | 20 + .../odlux/framework/src/assets/images/onapLogo.gif | Bin 0 -> 9249 bytes .../framework/src/assets/images/onapLogo.gif.d.ts | 20 + .../odlux/odlux/framework/src/assets/version.json | 4 + .../sdnr/odlux/odlux/framework/src/common/event.ts | 81 + .../framework/src/components/errorDisplay.tsx | 131 + .../framework/src/components/icons/menuIcon.tsx | 29 + .../odlux/odlux/framework/src/components/logo.tsx | 103 + .../src/components/material-table/columnModel.ts | 56 + .../src/components/material-table/index.tsx | 707 + .../components/material-table/showColumnDialog.tsx | 188 + .../src/components/material-table/tableFilter.tsx | 113 + .../src/components/material-table/tableHead.tsx | 127 + .../src/components/material-table/tableToolbar.tsx | 191 + .../src/components/material-table/utilities.ts | 357 + .../framework/src/components/material-ui/index.ts | 22 + .../src/components/material-ui/listItemLink.tsx | 83 + .../src/components/material-ui/loader.tsx | 49 + .../framework/src/components/material-ui/panel.tsx | 76 + .../src/components/material-ui/snackDisplay.tsx | 74 + .../src/components/material-ui/toggleButton.tsx | 181 + .../components/material-ui/toggleButtonGroup.tsx | 40 + .../src/components/material-ui/treeView.tsx | 380 + .../framework/src/components/navigationMenu.tsx | 218 + .../framework/src/components/objectDump/index.tsx | 205 + .../framework/src/components/routing/appFrame.tsx | 55 + .../framework/src/components/settings/general.tsx | 110 + .../odlux/framework/src/components/titleBar.tsx | 233 + .../odlux/odlux/framework/src/design/default.ts | 81 + .../sdnr/odlux/odlux/framework/src/favicon.ico | Bin 0 -> 1150 bytes .../sdnr/odlux/odlux/framework/src/flux/action.ts | 26 + .../odlux/odlux/framework/src/flux/connect.tsx | 213 + .../odlux/odlux/framework/src/flux/middleware.ts | 107 + .../sdnr/odlux/odlux/framework/src/flux/store.ts | 106 + .../src/handlers/applicationRegistryHandler.ts | 31 + .../src/handlers/applicationStateHandler.ts | 176 + .../src/handlers/authenticationHandler.ts | 59 + .../src/handlers/navigationStateHandler.ts | 45 + .../sdnr/odlux/odlux/framework/src/index.dev.html | 35 + features/sdnr/odlux/odlux/framework/src/index.html | 27 + .../odlux/odlux/framework/src/middleware/api.ts | 72 + .../odlux/odlux/framework/src/middleware/logger.ts | 35 + .../odlux/framework/src/middleware/navigation.ts | 96 + .../odlux/framework/src/middleware/policies.ts | 41 + .../odlux/odlux/framework/src/middleware/thunk.ts | 35 + .../framework/src/models/applicationConfig.ts | 5 + .../odlux/framework/src/models/applicationInfo.ts | 55 + .../odlux/framework/src/models/authentication.ts | 94 + .../odlux/framework/src/models/elasticSearch.ts | 72 + .../odlux/odlux/framework/src/models/errorInfo.ts | 29 + .../framework/src/models/externalLoginProvider.ts | 23 + .../odlux/framework/src/models/iconDefinition.ts | 21 + .../sdnr/odlux/odlux/framework/src/models/index.ts | 18 + .../odlux/framework/src/models/restService.ts | 48 + .../odlux/odlux/framework/src/models/settings.ts | 48 + .../odlux/framework/src/models/snackbarItem.ts | 20 + features/sdnr/odlux/odlux/framework/src/run.ts | 19 + .../odlux/framework/src/services/applicationApi.ts | 74 + .../framework/src/services/applicationManager.ts | 53 + .../src/services/authenticationService.ts | 96 + .../framework/src/services/broadcastService.ts | 115 + .../odlux/odlux/framework/src/services/index.ts | 22 + .../framework/src/services/notificationService.ts | 220 + .../framework/src/services/restAccessorService.ts | 93 + .../odlux/framework/src/services/restService.ts | 168 + .../framework/src/services/snackbarService.ts | 22 + .../odlux/framework/src/services/storeService.ts | 11 + .../framework/src/services/userSessionService.ts | 80 + .../framework/src/services/userdataService.ts | 28 + .../odlux/framework/src/store/applicationStore.ts | 74 + .../sdnr/odlux/odlux/framework/src/styles/att.ts | 46 + .../odlux/framework/src/utilities/elasticSearch.ts | 114 + .../odlux/framework/src/utilities/logLevel.ts | 8 + .../framework/src/utilities/withComponents.ts | 37 + .../odlux/framework/src/utilities/yangHelper.ts | 44 + .../sdnr/odlux/odlux/framework/src/views/about.tsx | 198 + .../sdnr/odlux/odlux/framework/src/views/frame.tsx | 134 + .../sdnr/odlux/odlux/framework/src/views/home.tsx | 59 + .../sdnr/odlux/odlux/framework/src/views/login.tsx | 250 + .../odlux/odlux/framework/src/views/settings.tsx | 115 + .../sdnr/odlux/odlux/framework/src/views/test.tsx | 877 + .../framework/src2/main/resources/version.json | 21 + features/sdnr/odlux/odlux/framework/tsconfig.json | 39 + .../sdnr/odlux/odlux/framework/webpack.config.js | 265 + .../sdnr/odlux/odlux/framework/webpack.runner.js | 85 + .../sdnr/odlux/odlux/framework/webpack.vendor.js | 127 + features/sdnr/odlux/odlux/installer/pom.xml | 230 + .../src/assembly/assemble_mvnrepo_zip.xml | 53 + features/sdnr/odlux/odlux/jest.json | 18 + features/sdnr/odlux/odlux/lerna.json | 14 + .../sdnr/odlux/odlux/lib/broadcast/mapChannel.ts | 29 + features/sdnr/odlux/odlux/odlux.properties | 15 + features/sdnr/odlux/odlux/package.json | 101 + features/sdnr/odlux/odlux/pom.xml | 80 + features/sdnr/odlux/odlux/proxy.conf.js | 106 + features/sdnr/odlux/odlux/test.txt | 3742 ++++ features/sdnr/odlux/odlux/tsconfig.json | 39 + features/sdnr/odlux/odlux/yarn.lock | 13765 +++++++++++++++ features/sdnr/odlux/pom.xml | 37 + features/sdnr/odlux/readthedocs/README.md | 42 + features/sdnr/odlux/readthedocs/convert.sh | 60 + features/sdnr/odlux/readthedocs/pom.xml | 109 + .../src/assembly/assemble_mvnrepo_zip.xml | 47 + features/sdnr/odlux/readthedocs/src/docs/conf.py | 496 + .../src/docs/guides/onap-user/applications.rst | 18 + .../readthedocs/src/docs/guides/onap-user/home.rst | 32 + .../src/docs/guides/onap-user/installation.rst | 14 + .../onap-user/sdnr_Docker_Image_configuration.rst | 52 + .../sdnr_WT_Service_Configuration_parameters.rst | 118 + features/sdnr/odlux/readthedocs/src/docs/index.rst | 12 + .../odlux/readthedocs/src/docs/requirements.txt | 6 + features/sdnr/wt/README.md | 60 + .../sdnr/wt/common-yang/iana-crypt-hash/pom.xml | 47 + .../src/main/yang/iana-crypt-hash@2014-08-06.yang | 120 + features/sdnr/wt/common-yang/ietf-alarms/pom.xml | 47 + .../src/main/yang/ietf-alarms@2019-09-11.yang | 1526 ++ .../sdnr/wt/common-yang/openroadm-pm-types/pom.xml | 48 + .../pm/types/rev191129/PmDataTypeBuilder.java | 60 + .../yang/org-openroadm-pm-types@2019-11-29.yang | 680 + features/sdnr/wt/common-yang/pom.xml | 51 + .../wt/common-yang/rfc7317-ietf-system/pom.xml | 59 + .../src/main/yang/ietf-system@2014-08-06.yang | 800 + features/sdnr/wt/common-yang/rfc8341/pom.xml | 48 + .../rfc8341/src/main/yang/ietf-netconf-acm.yang | 464 + features/sdnr/wt/common-yang/test-yang/pom.xml | 60 + .../ietf/inet/types/rev130715/HostBuilder.java | 24 + .../inet/types/rev130715/IpAddressBuilder.java | 24 + .../types/rev130715/IpAddressNoZoneBuilder.java | 24 + .../ietf/inet/types/rev130715/IpPrefixBuilder.java | 24 + .../test-yang/src/main/yang/test-yang-utils.yang | 79 + features/sdnr/wt/common-yang/utils/pom.xml | 120 + .../sdnr/wt/yang/mapper/YangToolsMapper.java | 96 + .../sdnr/wt/yang/mapper/YangToolsMapperHelper.java | 287 + .../wt/yang/mapper/builder/DateAndTimeBuilder.java | 36 + .../YangToolsBuilderAnnotationIntrospector.java | 135 + .../YangToolsDeserializerModifier.java | 137 + .../mapper/mapperextensions/YangToolsModule.java | 60 + .../mapperextensions/YangtoolsMapDesirializer.java | 36 + .../mapper/serialize/BaseIdentityDeserializer.java | 89 + .../mapper/serialize/BaseIdentitySerializer.java | 20 + .../yang/mapper/serialize/ClassDeserializer.java | 45 + .../mapper/serialize/ContainerNodeSerializer.java | 99 + .../mapper/serialize/DateAndTimeSerializer.java | 51 + .../wt/yang/mapper/serialize/EnumSerializer.java | 44 + .../mapper/serialize/IdentifierDeserializer.java | 71 + .../wt/yang/mapper/serialize/MapSerializer.java | 37 + .../wt/yang/mapper/serialize/SetDeserializer.java | 34 + .../mapper/serialize/TypeObjectDeserializer.java | 86 + .../mapper/serialize/TypeObjectSerializer.java | 73 + .../mapper/serialize/XMLNamespaceSerializer.java | 42 + .../features/sdnr/wt/yang/mapper/TestHashMap.java | 84 + .../sdnr/wt/yang/mapper/TestYangToolsMapper.java | 218 + .../utils/src/test/yang/ietf-inet-types.yang | 429 + .../utils/src/test/yang/test-yang-utils.yang | 84 + features/sdnr/wt/common/pom.xml | 95 + .../ccsdk/features/sdnr/wt/common/HtAssert.java | 51 + .../ccsdk/features/sdnr/wt/common/Resources.java | 105 + .../ccsdk/features/sdnr/wt/common/YangHelper.java | 42 + .../wt/common/configuration/Configuration.java | 32 + .../ConfigurationFileRepresentation.java | 244 + .../wt/common/configuration/ISubConfigHandler.java | 26 + .../exception/ConversionException.java | 31 + .../filechange/ConfigFileObserver.java | 59 + .../filechange/IConfigChangedListener.java | 26 + .../wt/common/configuration/subtypes/Section.java | 281 + .../configuration/subtypes/SectionValue.java | 98 + .../sdnr/wt/common/database/Portstatus.java | 77 + .../wt/common/database/data/DatabaseVersion.java | 148 + .../features/sdnr/wt/common/file/FileWatchdog.java | 116 + .../features/sdnr/wt/common/file/PomFile.java | 117 + .../sdnr/wt/common/file/PomPropertiesFile.java | 91 + .../sdnr/wt/common/http/BaseHTTPClient.java | 394 + .../sdnr/wt/common/http/BaseHTTPResponse.java | 51 + .../features/sdnr/wt/common/test/JSONAssert.java | 210 + ...ServletOutputStreamToByteArrayOutputStream.java | 53 + .../test/ServletOutputStreamToStringWriter.java | 59 + .../common/threading/GenericRunnableFactory.java | 27 + .../threading/GenericRunnableFactoryCallback.java | 27 + .../wt/common/threading/KeyBasedThreadpool.java | 159 + .../features/sdnr/wt/common/util/StackTrace.java | 46 + .../sdnr/wt/common/test/TestBaseHttpClient.java | 182 + .../features/sdnr/wt/common/test/TestConfig.java | 234 + .../sdnr/wt/common/test/TestDatabaseVersion.java | 62 + .../sdnr/wt/common/test/TestJsonAssert.java | 108 + .../wt/common/test/TestKeybasedThreadpool.java | 95 + .../features/sdnr/wt/common/test/TestPomfile.java | 65 + .../sdnr/wt/common/test/TestPortstatus.java | 43 + .../sdnr/wt/common/test/TestResources.java | 38 + .../wt/common/src/test/resources/log4j.properties | 33 + .../sdnr/wt/common/src/test/resources/log4j2.xml | 38 + .../src/test/resources/simplelogger.properties | 27 + .../sdnr/wt/common/src/test/resources/testpom.xml | 157 + features/sdnr/wt/data-provider/README.md | 28 + features/sdnr/wt/data-provider/dblib/pom.xml | 117 + .../dataprovider/database/sqldb/SqlDBClient.java | 372 + .../dataprovider/database/sqldb/SqlDBConfig.java | 118 + .../database/sqldb/data/HtUserdataManagerBase.java | 217 + .../database/sqldb/data/HtUserdataManagerImpl.java | 52 + .../database/sqldb/data/PropertyList.java | 36 + .../database/sqldb/data/SqlDBDataProvider.java | 534 + .../sqldb/data/SqlDbInventoryTreeProvider.java | 89 + .../database/sqldb/data/SqlPropertyInfo.java | 56 + .../dataprovider/database/sqldb/data/SqlTable.java | 14 + .../dataprovider/database/sqldb/data/SqlView.java | 20 + .../dataprovider/database/sqldb/data/Userdata.java | 135 + .../database/sqldb/data/UserdataBuilder.java | 206 + .../sqldb/data/entity/DatabaseIdGenerator.java | 116 + .../sqldb/data/entity/FaultEntityManager.java | 65 + .../sqldb/data/entity/HtDatabaseEventsService.java | 297 + .../data/entity/HtDatabaseMaintenanceService.java | 121 + .../sqldb/data/rpctypehelper/QueryResult.java | 70 + .../database/sqldb/database/SqlDBMapper.java | 520 + .../database/sqldb/database/SqlDBReader.java | 209 + .../database/sqldb/database/SqlDBReaderWriter.java | 223 + .../sqldb/database/SqlDBReaderWriterFault.java | 43 + .../sqldb/database/SqlDBReaderWriterInventory.java | 87 + .../sqldb/database/SqlDBReaderWriterPm.java | 136 + .../sqldb/database/SqlDBReaderWriterUserdata.java | 33 + .../database/sqldb/database/SqlDBStatusReader.java | 145 + .../database/sqldb/query/CountQuery.java | 82 + .../database/sqldb/query/DeleteQuery.java | 44 + .../database/sqldb/query/InsertQuery.java | 133 + .../database/sqldb/query/SelectQuery.java | 237 + .../database/sqldb/query/SqlQuery.java | 397 + .../database/sqldb/query/UpdateQuery.java | 111 + .../database/sqldb/query/UpsertQuery.java | 60 + .../sqldb/query/filters/DBFilterKeyValuePair.java | 31 + .../sqldb/query/filters/DBKeyValuePair.java | 69 + .../sqldb/query/filters/RangeSqlDBFilter.java | 59 + .../sqldb/query/filters/RegexSqlDBFilter.java | 42 + .../database/sqldb/query/filters/SqlDBFilter.java | 32 + .../sqldb/query/filters/SqlDBSearchFilter.java | 44 + .../dataprovider/dblib/test/TestCRUDMariaDB.java | 239 + .../wt/dataprovider/dblib/test/TestConfig.java | 134 + .../dblib/test/TestMariaDataProvider.java | 761 + .../wt/dataprovider/dblib/test/TestObjectIds.java | 83 + .../dataprovider/dblib/test/TestQuerySyntax.java | 256 + .../dblib/test/util/MariaDBTestBase.java | 216 + .../dblib/src/test/resources/UserdataBuilder.java | 206 + .../dblib/src/test/resources/inventory.json | 381 + .../dblib/src/test/resources/inventory2.json | 364 + .../dblib/src/test/resources/pmdata15m.json | 532 + .../dblib/src/test/resources/pmdata24h.json | 55 + features/sdnr/wt/data-provider/feature/pom.xml | 54 + features/sdnr/wt/data-provider/installer/pom.xml | 139 + .../src/assembly/assemble_mvnrepo_zip.xml | 47 + features/sdnr/wt/data-provider/model/pom.xml | 98 + .../dataprovider/model/ArchiveCleanProvider.java | 41 + .../model/BaseInventoryTreeProvider.java | 206 + .../model/DataInconsistencyException.java | 40 + .../sdnr/wt/dataprovider/model/DataProvider.java | 119 + .../dataprovider/model/DatabaseDataProvider.java | 121 + .../dataprovider/model/HtDatabaseMaintenance.java | 56 + .../model/HtDatabaseMediatorserver.java | 30 + .../wt/dataprovider/model/HtUserdataManager.java | 38 + .../wt/dataprovider/model/IEntityDataProvider.java | 37 + .../sdnr/wt/dataprovider/model/IEsConfig.java | 49 + .../dataprovider/model/InventoryTreeProvider.java | 30 + .../wt/dataprovider/model/NetconfTimeStamp.java | 89 + .../sdnr/wt/dataprovider/model/SdnrDbType.java | 26 + .../dataprovider/model/StatusChangedHandler.java | 30 + .../model/types/DataTreeChildObject.java | 155 + .../dataprovider/model/types/DataTreeObject.java | 93 + .../model/types/NetconfTimeStampImpl.java | 223 + .../dataprovider/model/types/ScalarTypeObject.java | 49 + .../wt/dataprovider/model/types/YangHelper.java | 59 + .../src/main/yang/data-provider-g826-pm-types.yang | 97 + .../yang/data-provider-openroadm-pm-types.yang | 609 + .../model/src/main/yang/data-provider-units.yang | 86 + .../src/main/yang/data-provider@2020-11-10.yang | 2091 +++ features/sdnr/wt/data-provider/pom.xml | 55 + features/sdnr/wt/data-provider/provider/copyright | 17 + features/sdnr/wt/data-provider/provider/java.sh | 14 + features/sdnr/wt/data-provider/provider/pom.xml | 152 + .../data-provider/provider/simplelogger.properties | 34 + .../database/nodb/NoDbDataProvider.java | 131 + .../database/nodb/NoDbDatabaseDataProvider.java | 552 + .../database/nodb/NoDbHtDatabaseMaintenance.java | 57 + .../database/nodb/NoDbHtUserdataManager.java | 49 + .../database/nodb/NoDbInventoryTreeProvider.java | 33 + .../wt/dataprovider/http/DataTreeHttpServlet.java | 240 + .../wt/dataprovider/http/UserdataHttpServlet.java | 183 + .../dataprovider/http/about/AboutHttpServlet.java | 402 + .../wt/dataprovider/http/about/MarkdownTable.java | 100 + .../wt/dataprovider/http/about/ODLVersionLUT.java | 130 + .../wt/dataprovider/http/about/SystemInfo.java | 344 + .../http/yangschema/GetYangSchemaRequest.java | 61 + .../http/yangschema/YangFileProvider.java | 242 + .../dataprovider/http/yangschema/YangFilename.java | 74 + .../http/yangschema/YangSchemaHttpServlet.java | 127 + .../wt/dataprovider/impl/DataProviderConfig.java | 75 + .../wt/dataprovider/impl/DataProviderImpl.java | 167 + .../wt/dataprovider/impl/DataProviderService.java | 135 + .../dataprovider/impl/DataProviderServiceImpl.java | 590 + .../yangtools/DataProviderYangToolsMapper.java | 50 + .../provider/src/main/resources/about/README.json | 16 + .../provider/src/main/resources/about/README.md | 21 + .../provider/src/main/resources/about/test.bmp | Bin 0 -> 14454 bytes .../org/opendaylight/blueprint/impl-blueprint.xml | 65 + .../sdnr/wt/dataprovider/test/TestAbout.java | 138 + .../wt/dataprovider/test/TestDataMappings.java | 101 + .../sdnr/wt/dataprovider/test/TestMapper.java | 94 + .../dataprovider/test/TestNetconfNodeBuilder.java | 62 + .../wt/dataprovider/test/TestNetconfTimestamp.java | 95 + .../wt/dataprovider/test/TestNoDbDataProvider.java | 215 + .../sdnr/wt/dataprovider/test/TestNuMappings.java | 52 + .../sdnr/wt/dataprovider/test/TestYangCloning.java | 58 + .../test/TestYangGenSalMappingOpenRoadm.java | 167 + .../wt/dataprovider/test/TestYangProvider.java | 187 + .../wt/dataprovider/test/issues/TestIssue227.java | 130 + .../dataprovider/test/util/DataBrokerHelper.java | 182 + .../wt/dataprovider/test/util/MariaDBTestBase.java | 171 + .../src/test/resources/TestTree/test1.json | 513 + .../src/test/resources/TestTree/test2.json | 490 + .../src/test/resources/TestTree/test3.json | 180 + .../TestYangGenSalMappingOpenRoadm/pmdata1.json | 18 + .../TestYangGenSalMappingOpenRoadm/pmdata2.json | 14 + .../TestYangGenSalMappingOpenRoadm/pmdata3.json | 14 + .../provider/src/test/resources/log4j.properties | 9 + .../provider/src/test/resources/test.properties | 76 + .../provider/src/test/resources/tlskeys/keys1.json | 40 + .../provider/src/test/resources/userdata/full.json | 20 + .../src/test/resources/userdata/merged.json | 13 + .../src/test/resources/userdata/networkmap.json | 11 + features/sdnr/wt/data-provider/setup/pom.xml | 147 + .../setup/DataMigrationProviderImpl.java | 91 + .../setup/DataMigrationProviderService.java | 77 + .../sdnr/wt/dataprovider/setup/Program.java | 474 + .../wt/dataprovider/setup/ReleaseInformation.java | 219 + .../wt/dataprovider/setup/data/ComponentName.java | 60 + .../wt/dataprovider/setup/data/ConfigData.java | 36 + .../wt/dataprovider/setup/data/ConfigName.java | 53 + .../setup/data/DataMigrationReport.java | 60 + .../wt/dataprovider/setup/data/DatabaseInfo.java | 70 + .../wt/dataprovider/setup/data/DatabaseInfo7.java | 59 + .../dataprovider/setup/data/MariaDBTableInfo.java | 42 + .../setup/data/MavenDatabasePluginInitFile.java | 66 + .../sdnr/wt/dataprovider/setup/data/Release.java | 129 + .../wt/dataprovider/setup/data/ReleaseGroup.java | 67 + .../database/MariaDbDataMigrationProvider.java | 334 + .../setup/releases/ElAltoReleaseInformation.java | 69 + .../releases/FrankfurtReleaseInformation.java | 79 + .../releases/FrankfurtReleaseInformationR2.java | 81 + .../setup/releases/GuilinReleaseInformation.java | 48 + .../setup/releases/HonoluluReleaseInformation.java | 59 + .../setup/releases/IstanbulReleaseInformation.java | 192 + .../setup/releases/JakartaReleaseInformation.java | 93 + .../wt/dataprovider/setup/MariaDBTestBase.java | 190 + .../setup/TestBaseReleaseInformation.java | 33 + .../wt/dataprovider/setup/TestMariaDBJakarta.java | 91 + .../wt/dataprovider/setup/TestMariaDBMapper.java | 215 + .../setup/src/test/resources/test.bak.json | 1 + .../setup/src/test/resources/test2.bak.json | 1 + .../sdnr/wt/devicemanager-core/feature/pom.xml | 51 + .../sdnr/wt/devicemanager-core/installer/pom.xml | 141 + .../src/assembly/assemble_mvnrepo_zip.xml | 47 + features/sdnr/wt/devicemanager-core/model/pom.xml | 111 + .../ne/factory/DevicemanagerNature.java | 57 + .../ne/factory/FactoryRegistration.java | 23 + .../ne/factory/NetworkElementFactory.java | 46 + .../ne/factory/NetworkElementFactory2.java | 29 + .../ne/service/DeviceMonitoredNe.java | 49 + .../ne/service/InventoryProvider.java | 34 + .../devicemanager/ne/service/NetworkElement.java | 47 + .../ne/service/NetworkElementService.java | 31 + .../ne/service/PerformanceDataProvider.java | 41 + .../sdnr/wt/devicemanager/service/AaiService.java | 37 + .../service/DeviceManagerService.java | 25 + .../service/DeviceManagerServiceProvider.java | 70 + .../wt/devicemanager/service/EquipmentService.java | 39 + .../service/EventHandlingService.java | 62 + .../wt/devicemanager/service/FaultService.java | 70 + .../devicemanager/service/MaintenanceService.java | 46 + .../service/NetconfNetworkElementService.java | 37 + .../service/NotificationProxyParser.java | 49 + .../devicemanager/service/NotificationService.java | 86 + .../devicemanager/service/PerformanceManager.java | 40 + .../service/VESCollectorCfgService.java | 41 + .../service/VESCollectorConfigChangeListener.java | 27 + .../devicemanager/service/VESCollectorService.java | 111 + .../sdnr/wt/devicemanager/types/EquipmentData.java | 72 + .../types/EventlogNotificationBuilder.java | 46 + .../sdnr/wt/devicemanager/types/FaultData.java | 101 + .../types/FaultNotificationBuilder2.java | 49 + .../types/InternalConnectionStatus.java | 40 + .../types/InventoryInformationDcae.java | 131 + .../wt/devicemanager/types/PerformanceDataLtp.java | 52 + .../types/VESCommonEventHeaderPOJO.java | 54 + .../wt/devicemanager/types/VESFaultFieldsPOJO.java | 43 + .../sdnr/wt/devicemanager/types/VESMessage.java | 35 + .../types/VESNotificationFieldsPOJO.java | 55 + .../types/VESPNFRegistrationFieldsPOJO.java | 50 + .../types/VESStndDefinedFieldsPOJO.java | 34 + .../util/InconsistentPMDataException.java | 43 + .../sdnr/wt/devicemanager/util/PmUtil.java | 42 + .../util/UnkownDevicemanagerServiceException.java | 29 + .../model/src/main/yang/devicemanager.yang | 335 + features/sdnr/wt/devicemanager-core/pom.xml | 53 + .../sdnr/wt/devicemanager-core/provider/copyright | 17 + .../sdnr/wt/devicemanager-core/provider/pom.xml | 196 + .../aaiconnector/impl/AaiProviderClient.java | 167 + .../aaiconnector/impl/AaiWebApiClient.java | 158 + .../aaiconnector/impl/URLParamEncoder.java | 46 + .../impl/config/AaiClientPropertiesFile.java | 98 + .../aaiconnector/impl/config/AaiConfig.java | 281 + .../dcaeconnector/impl/DcaeForwarderImpl.java | 82 + .../dcaeconnector/impl/DcaeForwarderInternal.java | 41 + .../dcaeconnector/impl/DcaeMessages.java | 354 + .../dcaeconnector/impl/DcaeProviderClient.java | 85 + .../dcaeconnector/impl/DcaeProviderTask.java | 50 + .../dcaeconnector/impl/DcaeProviderWorker.java | 84 + .../dcaeconnector/impl/DcaeSender.java | 31 + .../dcaeconnector/impl/DcaeSenderImpl.java | 236 + .../dcaeconnector/impl/config/DcaeConfig.java | 83 + .../devicemanager/devicemonitor/impl/Checker.java | 50 + .../devicemonitor/impl/DeviceMonitor.java | 59 + .../devicemonitor/impl/DeviceMonitorEmptyImpl.java | 42 + .../devicemonitor/impl/DeviceMonitorImpl.java | 289 + .../devicemonitor/impl/DeviceMonitorProblems.java | 68 + .../devicemonitor/impl/DeviceMonitorTask.java | 322 + .../devicemonitor/impl/config/DmConfig.java | 66 + .../DeviceManagerDatabaseNotificationService.java | 217 + .../eventdatahandler/ODLEventListenerHandler.java | 400 + .../RpcPushNotificationsHandler.java | 124 + .../ConnectionStatusHousekeepingService.java | 268 + .../housekeeping/HouseKeepingConfig.java | 63 + .../ResyncNetworkElementHouskeepingService.java | 170 + .../ResyncNetworkElementsListener.java | 36 + .../impl/DeviceManagerApiServiceImpl.java | 362 + .../wt/devicemanager/impl/DeviceManagerImpl.java | 421 + .../impl/DeviceManagerNetconfConnectHandler.java | 201 + .../DeviceManagerNetconfNotConnectHandler.java | 145 + .../wt/devicemanager/impl/NetconfNodeService.java | 38 + .../sdnr/wt/devicemanager/impl/ProviderClient.java | 43 + .../wt/devicemanager/impl/PushNotifications.java | 35 + .../impl/util/InternalDateAndTime.java | 104 + .../devicemanager/impl/util/InternalSeverity.java | 226 + .../util/NetworkElementConnectionEntitiyUtil.java | 120 + .../impl/util/NotificationProxyParserImpl.java | 343 + .../impl/util/OdlClusterSingleton.java | 72 + .../devicemanager/impl/xml/FaultEntityManager.java | 91 + .../wt/devicemanager/impl/xml/GetEventType.java | 29 + .../impl/xml/MwtNotificationBase.java | 88 + .../impl/xml/ProblemNotificationXml.java | 161 + .../impl/xml/WebSocketServiceClientImpl.java | 60 + .../impl/xml/WebSocketServiceClientInternal.java | 40 + .../maintenance/MaintenanceRPCServiceAPI.java | 42 + .../maintenance/impl/MaintenanceCalculator.java | 148 + .../maintenance/impl/MaintenanceServiceImpl.java | 172 + .../impl/PerformanceManagerImpl.java | 84 + .../impl/PerformanceManagerTask.java | 246 + .../performancemanager/impl/config/PmConfig.java | 54 + .../DevicemanagerNotificationDelayService.java | 34 + .../toggleAlarmFilter/NotificationDelayFilter.java | 181 + .../NotificationDelayService.java | 71 + .../NotificationDelayedListener.java | 25 + .../NotificationWithServerTimeStamp.java | 78 + .../toggleAlarmFilter/ToggleAlarmFilterable.java | 33 + .../toggleAlarmFilter/conf/ToggleAlarmConfig.java | 60 + .../impl/VESCollectorServiceImpl.java | 216 + .../impl/config/VESCollectorCfgImpl.java | 162 + .../org/opendaylight/blueprint/impl-blueprint.xml | 75 + .../bbf-tr-196-2-0-3-full@2018-04-08.yang | 17194 +++++++++++++++++++ .../core-model@2017-03-20.yang | 1951 +++ .../g.874.1-model@2017-03-20.yang | 646 + .../iana-crypt-hash@2014-08-06.yang | 120 + .../ietf-inet-types@2010-09-24.yang | 418 + .../ietf-inet-types@2013-07-15.yang | 457 + .../ietf-netconf-acm@2012-02-22.yang | 449 + .../ietf-netconf-monitoring@2010-10-04.yang | 558 + .../ietf-netconf-partial-lock@2009-10-19.yang | 83 + .../ietf-netconf-with-defaults@2011-06-01.yang | 140 + .../ietf-netconf@2011-06-01.yang | 934 + .../ietf-ptp-dataset@2017-02-08.yang | 605 + .../ietf-restconf@2013-10-19.yang | 689 + .../ietf-system@2014-08-06.yang | 842 + .../ietf-yang-library@2016-04-09.yang | 263 + .../ietf-yang-types@2013-07-15.yang | 467 + .../microwave-model@2017-03-24.yang | 2262 +++ .../microwave-model@2018-09-07.yang | 2567 +++ .../microwave-model@2018-10-10.yang | 3581 ++++ .../nc-notifications@2008-07-14.yang | 95 + .../notifications@2008-07-14.yang | 82 + .../notifications@2018-05-30.yang | 97 + ...core-model-conditional-packages@2017-04-02.yang | 350 + ...f-ethernet-conditional-packages@2017-04-02.yang | 88 + ...nf-otn-odu-conditional-packages@2017-10-20.yang | 361 + .../onf-ptp-dataset@2017-05-08.yang | 125 + .../photonic-media@2018-09-24.yang | 207 + .../tapi-common@2018-08-31.yang | 666 + .../tapi-connectivity@2018-08-31.yang | 718 + .../preload.cache.schema/tapi-dsr@2018-08-31.yang | 204 + .../preload.cache.schema/tapi-eth@2018-08-31.yang | 1770 ++ .../tapi-notification@2018-08-31.yang | 581 + .../preload.cache.schema/tapi-oam@2018-08-31.yang | 830 + .../preload.cache.schema/tapi-odu@2018-08-31.yang | 687 + .../tapi-path-computation@2018-08-31.yang | 436 + .../tapi-photonic-media@2018-08-31.yang | 746 + .../tapi-topology@2018-08-31.yang | 701 + .../tapi-virtual-network@2018-08-31.yang | 252 + .../provider/src/main/resources/version.properties | 24 + .../sdnr/wt/devicemanager/test/TestAai.java | 281 + .../sdnr/wt/devicemanager/test/TestDcae.java | 176 + .../test/TestDevMgrPropertiesFile.java | 265 + .../wt/devicemanager/test/TestDeviceMonitor.java | 75 + .../wt/devicemanager/test/TestDevicemanager.java | 210 + .../test/TestMaintenanceTimeFilter.java | 90 + .../sdnr/wt/devicemanager/test/TestNameSpace.java | 52 + .../devicemanager/test/TestToggleAlarmFilter.java | 40 + .../devicemanager/test/TestVESCollectorClient.java | 116 + .../devicemanager/test/TestsNectconfDateTime.java | 107 + .../test/mock/RpcProviderServiceMock.java | 94 + .../test/util/NetconfTimeStampOld.java | 280 + .../src/test/resources/aaiclient.properties | 165 + .../provider/src/test/resources/captured-akka.conf | 72 + .../src/test/resources/mediator-server.json | 18 + .../src/test/resources/simplelogger.properties | 61 + .../provider/src/test/resources/test.properties | 76 + .../o-ran/ru-fh/feature/pom.xml | 50 + .../o-ran/ru-fh/installer/pom.xml | 116 + .../src/assembly/assemble_mvnrepo_zip.xml | 47 + .../o-ran/ru-fh/model/pom.xml | 72 + .../model/src/main/yang/devicemanager-oran.yang | 37 + .../wt/devicemanager-o-ran-sc/o-ran/ru-fh/pom.xml | 53 + .../o-ran/ru-fh/provider/copyright | 17 + .../o-ran/ru-fh/provider/pom.xml | 168 + .../wt/devicemanager/oran/config/ORanDMConfig.java | 83 + .../dataprovider/ORanDOMToInternalDataModel.java | 290 + .../oran/impl/dom/DeviceManagerORanImpl.java | 91 + .../oran/impl/dom/ORanDOMNetworkElement.java | 332 + .../oran/impl/dom/ORanNetworkElementFactory.java | 65 + .../ORanDOMChangeNotificationListener.java | 149 + .../ORanDOMFaultNotificationListener.java | 179 + .../ORanDOMNotifToVESEventAssembly.java | 103 + .../notification/ORanDOMNotificationToXPath.java | 235 + .../ORanDOMSupervisionNotificationListener.java | 105 + .../notification/ORanNotificationObserver.java | 31 + .../notification/ORanNotificationObserverImpl.java | 43 + .../ORanNotificationReceivedService.java | 33 + .../oran/rpc/ORanSupervisionRPCImpl.java | 88 + .../devicemanager/oran/util/ORanDMDOMUtility.java | 104 + .../oran/util/ORanDeviceManagerQNames.java | 101 + .../vesmapper/ORanDOMFaultToVESFaultMapper.java | 158 + .../ORanDOMSupervisionNotifToVESMapper.java | 207 + ...ORanRegistrationToVESpnfRegistrationMapper.java | 117 + .../wt/devicemanager/oran/yangspecs/ORANFM.java | 99 + .../devicemanager/oran/yangspecs/OnapSystem.java | 126 + .../devicemanager/oran/yangspecs/YangModule.java | 51 + .../org/opendaylight/blueprint/impl-blueprint.xml | 38 + .../provider/src/main/resources/version.properties | 24 + .../provider/src/main/yang/iana-hardware.yang | 180 + .../src/main/yang/ietf-alarms@2019-09-11.yang | 1530 ++ .../provider/src/main/yang/ietf-hardware.yang | 1141 ++ .../provider/src/main/yang/ietf-interfaces.yang | 1073 ++ .../src/main/yang/o-ran-ald-port@2019-07-03.yang | 238 + .../ru-fh/provider/src/main/yang/o-ran-fm.yang | 168 + .../provider/src/main/yang/o-ran-hardware.yang | 271 + .../src/main/yang/o-ran-hardware@2019-07-03.yang | 271 + .../src/main/yang/o-ran-sc-common-alarms-v1.yang | 56 + .../src/main/yang/o-ran-sc-cu-cp-alarms-v1.yang | 57 + .../src/main/yang/o-ran-sc-cu-up-alarms-v1.yang | 57 + .../src/main/yang/o-ran-sc-du-alarms-v1.yang | 56 + .../src/main/yang/o-ran-sc-ric-alarms-v1.yang | 56 + .../src/main/yang/o-ran-sc-ru-alarms-v1.yang | 56 + .../oran/impl/dom/TestDeviceManagerORanImpl.java | 51 + .../dom/TestORanDOMFaultNotificationListener.java | 175 + .../oran/impl/dom/TestORanDOMNetworkElement.java | 138 + .../oran/impl/dom/TestORanDOMNotification.java | 201 + .../impl/dom/TestORanDOMToInternalDataModel.java | 262 + .../impl/dom/TestORanNetworkElementFactory.java | 127 + .../TestORanRegistrationToVESpnfRegistration.java | 122 + .../oran/impl/dom/util/TestYangParserUtil.java | 156 + .../resources/Device-ietf-hardware-Output.json | 442 + .../test/resources/iana-crypt-hash@2014-08-06.yang | 120 + .../provider/src/test/resources/iana-hardware.yang | 180 + .../provider/src/test/resources/ietf-hardware.xml | 418 + .../provider/src/test/resources/ietf-hardware.yang | 1141 ++ .../src/test/resources/ietf-inet-types.yang | 429 + .../src/test/resources/ietf-netconf-acm.yang | 464 + .../src/test/resources/ietf-system@2014-08-06.yang | 800 + .../src/test/resources/ietf-yang-types.yang | 435 + .../src/test/resources/o-ran-fm@2022-08-15.yang | 153 + .../test/resources/o-ran-hardware@2019-07-03.yang | 271 + .../provider/src/test/resources/onap-system.xml | 15 + .../provider/src/test/resources/onap-system.yang | 99 + .../src/test/resources/oran-fm-active-alarm.xml | 25 + .../src/test/resources/simplelogger.properties | 61 + features/sdnr/wt/devicemanager-o-ran-sc/pom.xml | 35 + .../feature-devicemanager-base/pom.xml | 87 + .../feature-devicemanager/pom.xml | 53 + .../wt/featureaggregator/feature-oauth/pom.xml | 60 + features/sdnr/wt/featureaggregator/feature/pom.xml | 97 + .../sdnr/wt/featureaggregator/installer/pom.xml | 192 + .../src/assembly/assemble_mvnrepo_zip.xml | 48 + features/sdnr/wt/featureaggregator/pom.xml | 50 + .../sdnr/wt/mountpoint-registrar/feature/pom.xml | 54 + .../sdnr/wt/mountpoint-registrar/installer/pom.xml | 141 + .../src/assembly/assemble_mvnrepo_zip.xml | 47 + .../sdnr/wt/mountpoint-registrar/model/pom.xml | 72 + .../model/src/main/yang/mountpoint-registrar.yang | 64 + features/sdnr/wt/mountpoint-registrar/pom.xml | 53 + .../wt/mountpoint-registrar/provider/copyright | 17 + .../sdnr/wt/mountpoint-registrar/provider/pom.xml | 132 + .../wt/mountpointregistrar/config/FaultConfig.java | 38 + .../mountpointregistrar/config/GeneralConfig.java | 74 + .../mountpointregistrar/config/MessageConfig.java | 95 + .../config/PNFRegistrationConfig.java | 38 + .../config/ProvisioningConfig.java | 35 + .../config/StndDefinedFaultConfig.java | 37 + .../config/StrimziKafkaConfig.java | 99 + .../impl/InvalidMessageException.java | 38 + .../wt/mountpointregistrar/impl/MessageClient.java | 129 + .../impl/MountpointRegistrarImpl.java | 143 + .../impl/StrimziKafkaVESMsgConsumer.java | 35 + .../impl/StrimziKafkaVESMsgConsumerImpl.java | 176 + .../impl/StrimziKafkaVESMsgConsumerMain.java | 230 + .../impl/StrimziKafkaVESMsgValidator.java | 25 + .../kafka/VESMsgKafkaConsumer.java | 86 + .../cm/CMBasicHeaderFieldsNotification.java | 100 + .../vesdomain/cm/CMNotification.java | 113 + .../vesdomain/cm/CMNotificationClient.java | 84 + .../vesdomain/cm/StrimziKafkaCMVESMsgConsumer.java | 138 + .../vesdomain/fault/FaultNotificationClient.java | 77 + .../fault/StrimziKafkaFaultVESMsgConsumer.java | 114 + .../vesdomain/pnfreg/PNFMountPointClient.java | 145 + .../pnfreg/StrimziKafkaPNFRegVESMsgConsumer.java | 157 + ...StrimziKafkaStndDefinedFaultVESMsgConsumer.java | 141 + .../org/opendaylight/blueprint/impl-blueprint.xml | 32 + .../provider/src/main/resources/version.properties | 24 + .../wt/mountpointregistrar/test/TestMapping.java | 65 + .../test/TestMountpointRegistrarImpl.java | 127 + .../TestCMBasicHeaderFieldsNotification.java | 45 + .../test/client/TestCMNotificationBuilder.java | 79 + .../test/client/TestCMNotificationClient.java | 70 + .../test/client/TestFaultNotificationClient.java | 60 + .../test/client/TestPNFMountPointClient.java | 63 + .../test/config/GeneralConfigForTest.java | 65 + .../test/config/PNFRegistrationConfigTest.java | 75 + .../test/config/TestFaultConfig.java | 76 + .../test/config/TestGeneralConfig.java | 53 + .../test/config/TestProvisioningConfig.java | 69 + .../test/config/TestStrimziKafkaConfig.java | 75 + .../consumer/TestStrimziKafkaCMVESMsgConsumer.java | 242 + .../TestStrimziKafkaFaultVESMsgConsumer.java | 150 + .../TestStrimziKafkaPNFRegVESMsgConsumer.java | 271 + .../TestStrimziKafkaStndDefinedVESMsgConsumer.java | 250 + .../TestStrimziKafkaVESMsgConsumerMain.java | 175 + .../ClusterSingletonServiceProviderMock.java | 32 + .../src/test/resources/msgs/cm_invalid.json | 49 + .../src/test/resources/msgs/cm_invalid_type.json | 49 + .../msgs/cm_moi_attribute_value_changes.json | 45 + .../src/test/resources/msgs/cm_moi_creation.json | 42 + .../src/test/resources/msgs/cm_moi_deletion.json | 42 + .../provider/src/test/resources/msgs/cm_valid.json | 49 + .../cm_valid_two_element_moi_changes_array.json | 61 + .../src/test/resources/msgs/not_a_json.json | 10 + .../src/test/resources/simplelogger.properties | 59 + .../wt/mountpoint-state-provider/feature/pom.xml | 50 + .../wt/mountpoint-state-provider/installer/pom.xml | 141 + .../src/assembly/assemble_mvnrepo_zip.xml | 47 + features/sdnr/wt/mountpoint-state-provider/pom.xml | 52 + .../mountpoint-state-provider/provider/copyright | 17 + .../wt/mountpoint-state-provider/provider/pom.xml | 132 + .../wt/mountpointstateprovider/impl/Constants.java | 31 + .../impl/MountpointStateProviderImpl.java | 176 + .../impl/MountpointStateVESMessageFormatter.java | 106 + .../org/opendaylight/blueprint/impl-blueprint.xml | 42 + .../provider/src/main/resources/version.properties | 24 + .../test/TestMountpointStateProviderImpl.java | 69 + .../src/test/resources/simplelogger.properties | 59 + .../src/test/resources/testpublisher.properties | 32 + .../wt/netconfnode-state-service/feature/pom.xml | 59 + .../wt/netconfnode-state-service/installer/pom.xml | 116 + .../src/assembly/assemble_mvnrepo_zip.xml | 47 + .../wt/netconfnode-state-service/model/pom.xml | 96 + .../wt/netconfnodestateservice/Capabilities.java | 272 + .../wt/netconfnodestateservice/DomContext.java | 39 + .../netconfnodestateservice/NetconfAccessor.java | 77 + .../NetconfBindingAccessor.java | 96 + .../NetconfDomAccessor.java | 177 + .../NetconfNodeConnectListener.java | 54 + .../NetconfNodeStateListener.java | 54 + .../NetconfNodeStateService.java | 56 + .../netconfnodestateservice/TransactionUtils.java | 62 + .../VesNotificationListener.java | 46 + .../model/src/main/yang/config.yang | 29 + .../model/src/main/yang/netconfnode-state.yang | 94 + .../test/TestCapabilities.java | 93 + features/sdnr/wt/netconfnode-state-service/pom.xml | 53 + .../netconfnode-state-service/provider/copyright | 17 + .../wt/netconfnode-state-service/provider/pom.xml | 132 + .../impl/NetconfNodeStateServiceImpl.java | 712 + .../impl/access/NetconfAccessorImpl.java | 131 + .../impl/access/NetconfAccessorManager.java | 80 + .../impl/access/NetconfCommunicatorManager.java | 123 + .../access/binding/GenericTransactionUtils.java | 117 + .../access/binding/NetconfBindingAccessorImpl.java | 161 + .../impl/access/dom/CanNotConvertException.java | 44 + .../impl/access/dom/DomContextImpl.java | 54 + .../impl/access/dom/DomParser.java | 61 + .../impl/access/dom/NetconfDomAccessorImpl.java | 318 + .../access/dom/NotificationServiceNotProvided.java | 44 + .../impl/conf/NetconfStateConfig.java | 84 + .../impl/conf/odlAkka/AkkaConfig.java | 87 + .../impl/conf/odlAkka/ClusterConfig.java | 145 + .../impl/conf/odlAkka/ClusterNodeInfo.java | 82 + .../impl/conf/odlGeo/ClusterRoleInfo.java | 96 + .../conf/odlGeo/ClusterRoleInfoCollection.java | 45 + .../impl/conf/odlGeo/GeoConfig.java | 162 + .../impl/mdsal/MdsalApi.java | 48 + .../rpc/NetconfnodeStateServiceRpcApiImpl.java | 169 + .../impl/rpc/RpcApigetStateCallback.java | 27 + .../src/main/resources/NetconfAccessorImpl.java | 131 + .../main/resources/NetconfBindingAccessorImpl.java | 163 + .../provider/src/main/resources/example.json | 16 + .../org/opendaylight/blueprint/impl-blueprint.xml | 71 + .../provider/src/main/resources/sample.json | 20 + .../provider/src/main/resources/version.properties | 24 + .../test/TestAkkaConfig.java | 243 + .../test/TestCapabilites.java | 91 + .../netconfnodestateservice/test/TestConfig.java | 66 + .../test/TestGenericTransactionUtils.java | 58 + .../test/TestGeoConfig.java | 63 + .../test/TestNetconfAccessorImpl.java | 195 + .../test/TestNetconfNodeStateService.java | 376 + .../test/example/ExampleConfig.java | 117 + .../test/example/TestNetconfHelper.java | 86 + .../provider/src/test/resources/captured-akka.conf | 72 + .../src/test/resources/config@2020-12-08.yang | 29 + .../src/test/resources/simplelogger.properties | 59 + .../provider/src/test/resources/test.properties | 76 + features/sdnr/wt/oauth-provider/oauth-cli/pom.xml | 95 + .../features/sdnr/wt/oauthprovider/Program.java | 190 + features/sdnr/wt/oauth-provider/oauth-core/pom.xml | 190 + .../sdnr/wt/oauthprovider/OAuth2Realm.java | 143 + .../sdnr/wt/oauthprovider/data/Config.java | 347 + .../wt/oauthprovider/data/CustomObjectMapper.java | 39 + .../data/InvalidConfigurationException.java | 32 + .../sdnr/wt/oauthprovider/data/KeycloakRole.java | 80 + .../data/KeycloakUserTokenPayload.java | 231 + .../data/NoDefinitionFoundException.java | 33 + .../wt/oauthprovider/data/OAuthProviderConfig.java | 208 + .../wt/oauthprovider/data/OAuthResponseData.java | 88 + .../sdnr/wt/oauthprovider/data/OAuthToken.java | 57 + .../sdnr/wt/oauthprovider/data/OdlPolicy.java | 130 + .../oauthprovider/data/OdlShiroConfiguration.java | 67 + .../sdnr/wt/oauthprovider/data/OdlXmlMapper.java | 44 + .../data/OpenIdConfigResponseData.java | 65 + .../data/UnableToConfigureOAuthService.java | 12 + .../wt/oauthprovider/data/UserTokenPayload.java | 103 + .../filters/AnyRoleHttpAuthenticationFilter.java | 75 + .../BearerAndBasicHttpAuthenticationFilter.java | 161 + .../CustomizedMDSALDynamicAuthorizationFilter.java | 200 + .../wt/oauthprovider/http/AuthHttpServlet.java | 535 + .../http/HeadersOnlyHttpServletRequest.java | 469 + .../http/client/MappedBaseHttpResponse.java | 63 + .../http/client/MappingBaseHttpClient.java | 63 + .../wt/oauthprovider/providers/AuthService.java | 363 + .../providers/GitlabProviderService.java | 180 + .../providers/KeycloakProviderService.java | 115 + .../providers/MdSalAuthorizationStore.java | 117 + .../providers/NextcloudProviderService.java | 91 + .../providers/OAuthProviderFactory.java | 47 + .../sdnr/wt/oauthprovider/providers/PemUtils.java | 106 + .../wt/oauthprovider/providers/RSAKeyReader.java | 47 + .../wt/oauthprovider/providers/TokenCreator.java | 203 + .../wt/oauthprovider/test/TestAuthHttpServlet.java | 401 + .../sdnr/wt/oauthprovider/test/TestConfig.java | 81 + .../wt/oauthprovider/test/TestDeserializer.java | 102 + .../oauthprovider/test/TestGitlabAuthService.java | 199 + .../test/TestKeycloakAuthService.java | 197 + .../sdnr/wt/oauthprovider/test/TestPolicy.java | 57 + .../sdnr/wt/oauthprovider/test/TestProperty.java | 43 + .../wt/oauthprovider/test/TestRSAAlgorithms.java | 109 + .../sdnr/wt/oauthprovider/test/TestRealm.java | 198 + .../oauthprovider/test/helper/OdlJsonMapper.java | 64 + .../wt/oauthprovider/test/helper/OdlXmlMapper.java | 46 + .../src/test/resources/aaa-app-config.test.xml | 77 + .../oauth-core/src/test/resources/jwtRS256.key | 27 + .../oauth-core/src/test/resources/jwtRS256.key.pub | 9 + .../oauth-core/src/test/resources/jwtRS512.key | 51 + .../oauth-core/src/test/resources/jwtRS512.key.pub | 14 + .../src/test/resources/mdsalDynAuthData.json | 694 + .../resources/oauth/gitlab-groups-response.json | 112 + .../resources/oauth/gitlab-token-response.json | 7 + .../test/resources/oauth/gitlab-user-response.json | 32 + .../resources/oauth/keycloak-token-response.json | 11 + .../src/test/resources/oom.test.config.json | 21 + .../oauth-core/src/test/resources/test.config.json | 20 + .../test/resources/test.configRS256-invalid.json | 24 + .../src/test/resources/test.configRS256.json | 22 + .../src/test/resources/test.configRS512.json | 22 + .../sdnr/wt/oauth-provider/oauth-realm/pom.xml | 154 + features/sdnr/wt/oauth-provider/oauth-web/pom.xml | 155 + .../org/opendaylight/blueprint/impl-blueprint.xml | 41 + features/sdnr/wt/oauth-provider/pom.xml | 53 + features/sdnr/wt/pom.xml | 65 + features/sdnr/wt/websocketmanager/feature/pom.xml | 55 + .../sdnr/wt/websocketmanager/installer/pom.xml | 116 + .../src/assembly/assemble_mvnrepo_zip.xml | 47 + features/sdnr/wt/websocketmanager/model/pom.xml | 69 + .../model/WebsocketManagerService.java | 90 + .../model/data/DOMNotificationOutput.java | 82 + .../model/data/INotificationOutput.java | 31 + .../model/data/NotificationOutput.java | 82 + .../model/data/ReducedSchemaInfo.java | 92 + .../wt/websocketmanager/model/data/SchemaInfo.java | 149 + .../sdnr/wt/websocketmanager/model/data/Scope.java | 132 + .../model/data/ScopeRegistration.java | 131 + .../model/data/ScopeRegistrationResponse.java | 79 + .../model/src/main/yang/websocketmanager.yang | 51 + features/sdnr/wt/websocketmanager/pom.xml | 53 + features/sdnr/wt/websocketmanager/provider/pom.xml | 148 + .../sdnr/wt/websocketmanager/WebSocketManager.java | 130 + .../websocketmanager/WebSocketManagerCreator.java | 36 + .../websocketmanager/WebSocketManagerProvider.java | 190 + .../websocketmanager/WebSocketManagerSocket.java | 315 + .../config/WebSocketManagerConfig.java | 56 + .../data/TimeRateLimitingQueue.java | 34 + .../sdnr/wt/websocketmanager/utils/AkkaConfig.java | 210 + .../websocketmanager/utils/RateFilterManager.java | 323 + .../sdnr/wt/websocketmanager/utils/UserScopes.java | 56 + .../websocket/SyncWebSocketClient.java | 120 + .../org/opendaylight/blueprint/impl-blueprint.xml | 51 + .../wt/websocketmanager2/test/AkkaConfigTest.java | 62 + .../wt/websocketmanager2/test/RateFilterTest.java | 192 + .../wt/websocketmanager2/test/TestDeserialize.java | 86 + .../wt/websocketmanager2/test/TestSerializer.java | 60 + .../wt/websocketmanager2/test/UserScopeTest.java | 87 + .../test/WebsockerProviderTest.java | 62 + .../test/WebsocketClientTest.java | 49 + .../test/WebsocketMessageTest.java | 84 + .../test/WebsocketServerConnectTest.java | 46 + .../src/test/resources/akka-cluster-local.cfg | 49 + .../provider/src/test/resources/akka-cluster.cfg | 49 + .../src/test/resources/akka-singlenode.cfg | 48 + .../src/test/resources/simplelogger.properties | 58 + 1432 files changed, 212267 insertions(+) create mode 100755 features/sdnr/odlux/helpserver/pom.xml create mode 100644 features/sdnr/odlux/helpserver/provider/README.md create mode 100644 features/sdnr/odlux/helpserver/provider/bitnami/nginx/help/meta.json create mode 100644 features/sdnr/odlux/helpserver/provider/bitnami/nginx/help/test.css create mode 100644 features/sdnr/odlux/helpserver/provider/bitnami/nginx/help/test.eps create mode 100644 features/sdnr/odlux/helpserver/provider/bitnami/nginx/help/test.pdf create mode 100644 features/sdnr/odlux/helpserver/provider/bitnami/nginx/help/test/test.txt create mode 100644 features/sdnr/odlux/helpserver/provider/help/meta.json create mode 100644 features/sdnr/odlux/helpserver/provider/help/test/test.txt create mode 100644 features/sdnr/odlux/helpserver/provider/pom.xml create mode 100644 features/sdnr/odlux/helpserver/provider/src/main/java/org/onap/ccsdk/features/sdnr/wt/helpserver/HelpServlet.java create mode 100644 features/sdnr/odlux/helpserver/provider/src/main/java/org/onap/ccsdk/features/sdnr/wt/helpserver/data/HelpInfrastructureObject.java create mode 100644 features/sdnr/odlux/helpserver/provider/src/main/resources/help/mediatorserver/README.md create mode 100644 features/sdnr/odlux/helpserver/provider/src/main/resources/help/mediatorserver/installation/README.md create mode 100644 features/sdnr/odlux/helpserver/provider/src/main/resources/help/mediatorserver/mediator/README.md create mode 100644 features/sdnr/odlux/helpserver/provider/src/main/resources/help/meta.json create mode 100644 features/sdnr/odlux/helpserver/provider/src/main/resources/help/sdnr/ONAP-SDN-R.png create mode 100644 features/sdnr/odlux/helpserver/provider/src/main/resources/help/sdnr/README.md create mode 100644 features/sdnr/odlux/helpserver/provider/src/main/resources/help/sdnr/abbreviations.md create mode 100644 features/sdnr/odlux/helpserver/provider/src/main/resources/help/sdnr/connect/README.md create mode 100644 features/sdnr/odlux/helpserver/provider/src/main/resources/help/sdnr/faq.md create mode 100644 features/sdnr/odlux/helpserver/provider/src/main/resources/help/sdnr/general.md create mode 100644 features/sdnr/odlux/helpserver/provider/src/main/resources/help/sdnr/linkCalculator/README.md create mode 100644 features/sdnr/odlux/helpserver/provider/src/main/resources/help/sdnr/networkMap/README.md create mode 100644 features/sdnr/odlux/helpserver/provider/src/main/resources/help/sdnr/pnfConfig/README.md create mode 100644 features/sdnr/odlux/helpserver/provider/src/main/resources/help/sdnr/pnfEventLog/README.md create mode 100644 features/sdnr/odlux/helpserver/provider/src/main/resources/help/sdnr/pnfFault/README.md create mode 100644 features/sdnr/odlux/helpserver/provider/src/main/resources/help/sdnr/pnfInventory/README.md create mode 100644 features/sdnr/odlux/helpserver/provider/src/main/resources/help/sdnr/pnfMaintenance/README.md create mode 100644 features/sdnr/odlux/helpserver/provider/src/main/resources/help/sdnr/pnfMediator/README.md create mode 100644 features/sdnr/odlux/helpserver/provider/src/main/resources/help/sdnr/pnfPerformance/README.md create mode 100644 features/sdnr/odlux/helpserver/provider/src/test/java/org/onap/ccsdk/features/sdnr/wt/helpserver/test/TestMyServlet.java create mode 100644 features/sdnr/odlux/helpserver/provider/src/test/resources/simplelogger.properties create mode 100644 features/sdnr/odlux/helpserver/provider/test/test.txt create mode 100644 features/sdnr/odlux/odlux/.eslintignore create mode 100644 features/sdnr/odlux/odlux/.eslintrc.json create mode 100644 features/sdnr/odlux/odlux/.gitignore create mode 100644 features/sdnr/odlux/odlux/LICENSE create mode 100644 features/sdnr/odlux/odlux/README.md create mode 100644 features/sdnr/odlux/odlux/apps/apiDemo/.babelrc create mode 100644 features/sdnr/odlux/odlux/apps/apiDemo/package.json create mode 100644 features/sdnr/odlux/odlux/apps/apiDemo/pom.xml create mode 100644 features/sdnr/odlux/odlux/apps/apiDemo/src/actions/modulesSuccess.ts create mode 100644 features/sdnr/odlux/odlux/apps/apiDemo/src/handlers/apiDemoRootHandler.ts create mode 100644 features/sdnr/odlux/odlux/apps/apiDemo/src/handlers/modulesHandler.ts create mode 100644 features/sdnr/odlux/odlux/apps/apiDemo/src/index.html create mode 100644 features/sdnr/odlux/odlux/apps/apiDemo/src/models/module.ts create mode 100644 features/sdnr/odlux/odlux/apps/apiDemo/src/plugin.tsx create mode 100644 features/sdnr/odlux/odlux/apps/apiDemo/tsconfig.json create mode 100644 features/sdnr/odlux/odlux/apps/apiDemo/webpack.config.js create mode 100755 features/sdnr/odlux/odlux/apps/app-installer/pom.xml create mode 100644 features/sdnr/odlux/odlux/apps/app-installer/src/assembly/assemble_mvnrepo_zip.xml create mode 100644 features/sdnr/odlux/odlux/apps/configurationApp/.babelrc create mode 100644 features/sdnr/odlux/odlux/apps/configurationApp/package.json create mode 100644 features/sdnr/odlux/odlux/apps/configurationApp/policies.json create mode 100644 features/sdnr/odlux/odlux/apps/configurationApp/pom.xml create mode 100644 features/sdnr/odlux/odlux/apps/configurationApp/src/actions/deviceActions.ts create mode 100644 features/sdnr/odlux/odlux/apps/configurationApp/src/assets/icons/configurationAppIcon.svg create mode 100644 features/sdnr/odlux/odlux/apps/configurationApp/src/components/baseProps.ts create mode 100644 features/sdnr/odlux/odlux/apps/configurationApp/src/components/ifWhenTextInput.tsx create mode 100644 features/sdnr/odlux/odlux/apps/configurationApp/src/components/uiElementBoolean.tsx create mode 100644 features/sdnr/odlux/odlux/apps/configurationApp/src/components/uiElementLeafList.tsx create mode 100644 features/sdnr/odlux/odlux/apps/configurationApp/src/components/uiElementNumber.tsx create mode 100644 features/sdnr/odlux/odlux/apps/configurationApp/src/components/uiElementReference.tsx create mode 100644 features/sdnr/odlux/odlux/apps/configurationApp/src/components/uiElementSelection.tsx create mode 100644 features/sdnr/odlux/odlux/apps/configurationApp/src/components/uiElementString.tsx create mode 100644 features/sdnr/odlux/odlux/apps/configurationApp/src/components/uiElementUnion.tsx create mode 100644 features/sdnr/odlux/odlux/apps/configurationApp/src/handlers/configurationAppRootHandler.ts create mode 100644 features/sdnr/odlux/odlux/apps/configurationApp/src/handlers/connectedNetworkElementsHandler.ts create mode 100644 features/sdnr/odlux/odlux/apps/configurationApp/src/handlers/deviceDescriptionHandler.ts create mode 100644 features/sdnr/odlux/odlux/apps/configurationApp/src/handlers/valueSelectorHandler.ts create mode 100644 features/sdnr/odlux/odlux/apps/configurationApp/src/handlers/viewDescriptionHandler.ts create mode 100644 features/sdnr/odlux/odlux/apps/configurationApp/src/index.html create mode 100644 features/sdnr/odlux/odlux/apps/configurationApp/src/models/networkElementConnection.ts create mode 100644 features/sdnr/odlux/odlux/apps/configurationApp/src/models/uiModels.ts create mode 100644 features/sdnr/odlux/odlux/apps/configurationApp/src/models/yang.ts create mode 100644 features/sdnr/odlux/odlux/apps/configurationApp/src/pluginConfiguration.tsx create mode 100644 features/sdnr/odlux/odlux/apps/configurationApp/src/services/restServices.ts create mode 100644 features/sdnr/odlux/odlux/apps/configurationApp/src/services/yangService.ts create mode 100644 features/sdnr/odlux/odlux/apps/configurationApp/src/utilities/verifyer.ts create mode 100644 features/sdnr/odlux/odlux/apps/configurationApp/src/utilities/viewEngineHelper.ts create mode 100644 features/sdnr/odlux/odlux/apps/configurationApp/src/views/configurationApplication.tsx create mode 100644 features/sdnr/odlux/odlux/apps/configurationApp/src/views/networkElementSelector.tsx create mode 100644 features/sdnr/odlux/odlux/apps/configurationApp/src/yang/whenParser.ts create mode 100644 features/sdnr/odlux/odlux/apps/configurationApp/src/yang/yangParser.ts create mode 100644 features/sdnr/odlux/odlux/apps/configurationApp/tsconfig.json create mode 100644 features/sdnr/odlux/odlux/apps/configurationApp/webpack.config.js create mode 100644 features/sdnr/odlux/odlux/apps/connectApp/.babelrc create mode 100644 features/sdnr/odlux/odlux/apps/connectApp/package.json create mode 100644 features/sdnr/odlux/odlux/apps/connectApp/policies.json create mode 100644 features/sdnr/odlux/odlux/apps/connectApp/pom.xml create mode 100644 features/sdnr/odlux/odlux/apps/connectApp/src/actions/commonNetworkElementsActions.ts create mode 100644 features/sdnr/odlux/odlux/apps/connectApp/src/actions/infoNetworkElementActions.ts create mode 100644 features/sdnr/odlux/odlux/apps/connectApp/src/actions/mountedNetworkElementsActions.ts create mode 100644 features/sdnr/odlux/odlux/apps/connectApp/src/actions/networkElementsActions.ts create mode 100644 features/sdnr/odlux/odlux/apps/connectApp/src/actions/tlsKeyActions.ts create mode 100644 features/sdnr/odlux/odlux/apps/connectApp/src/assets/icons/connectAppIcon.svg create mode 100644 features/sdnr/odlux/odlux/apps/connectApp/src/components/connectionStatusLog.tsx create mode 100644 features/sdnr/odlux/odlux/apps/connectApp/src/components/editNetworkElementDialog.tsx create mode 100644 features/sdnr/odlux/odlux/apps/connectApp/src/components/infoNetworkElementDialog.tsx create mode 100644 features/sdnr/odlux/odlux/apps/connectApp/src/components/networkElements.tsx create mode 100644 features/sdnr/odlux/odlux/apps/connectApp/src/components/refreshConnectionStatusLogDialog.tsx create mode 100644 features/sdnr/odlux/odlux/apps/connectApp/src/components/refreshNetworkElementsDialog.tsx create mode 100644 features/sdnr/odlux/odlux/apps/connectApp/src/handlers/connectAppRootHandler.ts create mode 100644 features/sdnr/odlux/odlux/apps/connectApp/src/handlers/connectionStatusLogHandler.ts create mode 100644 features/sdnr/odlux/odlux/apps/connectApp/src/handlers/infoNetworkElementHandler.ts create mode 100644 features/sdnr/odlux/odlux/apps/connectApp/src/handlers/networkElementsHandler.ts create mode 100644 features/sdnr/odlux/odlux/apps/connectApp/src/handlers/tlsKeyHandler.ts create mode 100644 features/sdnr/odlux/odlux/apps/connectApp/src/index.html create mode 100644 features/sdnr/odlux/odlux/apps/connectApp/src/models/connectionStatusLog.ts create mode 100644 features/sdnr/odlux/odlux/apps/connectApp/src/models/guiCutTrough.ts create mode 100644 features/sdnr/odlux/odlux/apps/connectApp/src/models/networkElementBase.ts create mode 100644 features/sdnr/odlux/odlux/apps/connectApp/src/models/networkElementConnection.ts create mode 100644 features/sdnr/odlux/odlux/apps/connectApp/src/models/networkElementConnectionLog.ts create mode 100644 features/sdnr/odlux/odlux/apps/connectApp/src/models/panelId.ts create mode 100644 features/sdnr/odlux/odlux/apps/connectApp/src/models/topologyNetconf.ts create mode 100644 features/sdnr/odlux/odlux/apps/connectApp/src/models/yangCapabilitiesType.ts create mode 100644 features/sdnr/odlux/odlux/apps/connectApp/src/pluginConnect.tsx create mode 100644 features/sdnr/odlux/odlux/apps/connectApp/src/services/connectService.ts create mode 100644 features/sdnr/odlux/odlux/apps/connectApp/src/views/connectView.tsx create mode 100644 features/sdnr/odlux/odlux/apps/connectApp/tsconfig.json create mode 100644 features/sdnr/odlux/odlux/apps/connectApp/webpack.config.js create mode 100644 features/sdnr/odlux/odlux/apps/demoApp/.babelrc create mode 100644 features/sdnr/odlux/odlux/apps/demoApp/package.json create mode 100644 features/sdnr/odlux/odlux/apps/demoApp/pom.xml create mode 100644 features/sdnr/odlux/odlux/apps/demoApp/src/actions/authorActions.ts create mode 100644 features/sdnr/odlux/odlux/apps/demoApp/src/components/counter.tsx create mode 100644 features/sdnr/odlux/odlux/apps/demoApp/src/handlers/demoAppRootHandler.ts create mode 100644 features/sdnr/odlux/odlux/apps/demoApp/src/handlers/editAuthorHandler.ts create mode 100644 features/sdnr/odlux/odlux/apps/demoApp/src/handlers/listAuthorsHandler.ts create mode 100644 features/sdnr/odlux/odlux/apps/demoApp/src/index.html create mode 100644 features/sdnr/odlux/odlux/apps/demoApp/src/models/author.ts create mode 100644 features/sdnr/odlux/odlux/apps/demoApp/src/plugin.tsx create mode 100644 features/sdnr/odlux/odlux/apps/demoApp/src/services/authorService.ts create mode 100644 features/sdnr/odlux/odlux/apps/demoApp/src/views/authorsList.tsx create mode 100644 features/sdnr/odlux/odlux/apps/demoApp/src/views/editAuthor.tsx create mode 100644 features/sdnr/odlux/odlux/apps/demoApp/tsconfig.json create mode 100644 features/sdnr/odlux/odlux/apps/demoApp/webpack.config.js create mode 100644 features/sdnr/odlux/odlux/apps/eventLogApp/.babelrc create mode 100644 features/sdnr/odlux/odlux/apps/eventLogApp/package.json create mode 100644 features/sdnr/odlux/odlux/apps/eventLogApp/pom.xml create mode 100644 features/sdnr/odlux/odlux/apps/eventLogApp/src/assets/icons/eventLogAppIcon.svg create mode 100644 features/sdnr/odlux/odlux/apps/eventLogApp/src/components/refreshEventLogDialog.tsx create mode 100644 features/sdnr/odlux/odlux/apps/eventLogApp/src/handlers/eventLogAppRootHandler.ts create mode 100644 features/sdnr/odlux/odlux/apps/eventLogApp/src/handlers/eventLogHandler.tsx create mode 100644 features/sdnr/odlux/odlux/apps/eventLogApp/src/index.html create mode 100644 features/sdnr/odlux/odlux/apps/eventLogApp/src/models/eventLogType.ts create mode 100644 features/sdnr/odlux/odlux/apps/eventLogApp/src/pluginEventLog.tsx create mode 100644 features/sdnr/odlux/odlux/apps/eventLogApp/src/views/eventLog.tsx create mode 100644 features/sdnr/odlux/odlux/apps/eventLogApp/tsconfig.json create mode 100644 features/sdnr/odlux/odlux/apps/eventLogApp/webpack.config.js create mode 100644 features/sdnr/odlux/odlux/apps/faultApp/.babelrc create mode 100644 features/sdnr/odlux/odlux/apps/faultApp/package.json create mode 100644 features/sdnr/odlux/odlux/apps/faultApp/pom.xml create mode 100644 features/sdnr/odlux/odlux/apps/faultApp/src/actions/clearStuckAlarmsAction.ts create mode 100644 features/sdnr/odlux/odlux/apps/faultApp/src/actions/notificationActions.ts create mode 100644 features/sdnr/odlux/odlux/apps/faultApp/src/actions/panelChangeActions.ts create mode 100644 features/sdnr/odlux/odlux/apps/faultApp/src/actions/statusActions.ts create mode 100644 features/sdnr/odlux/odlux/apps/faultApp/src/assets/icons/faultAppIcon.svg create mode 100644 features/sdnr/odlux/odlux/apps/faultApp/src/components/clearStuckAlarmsDialog.tsx create mode 100644 features/sdnr/odlux/odlux/apps/faultApp/src/components/dashboardHome.tsx create mode 100644 features/sdnr/odlux/odlux/apps/faultApp/src/components/faultStatus.tsx create mode 100644 features/sdnr/odlux/odlux/apps/faultApp/src/components/refreshAlarmLogDialog.tsx create mode 100644 features/sdnr/odlux/odlux/apps/faultApp/src/components/refreshCurrentAlarmsDialog.tsx create mode 100644 features/sdnr/odlux/odlux/apps/faultApp/src/handlers/alarmLogEntriesHandler.ts create mode 100644 features/sdnr/odlux/odlux/apps/faultApp/src/handlers/clearStuckAlarmsHandler.ts create mode 100644 features/sdnr/odlux/odlux/apps/faultApp/src/handlers/currentAlarmsHandler.ts create mode 100644 features/sdnr/odlux/odlux/apps/faultApp/src/handlers/faultAppRootHandler.ts create mode 100644 features/sdnr/odlux/odlux/apps/faultApp/src/handlers/faultStatusHandler.ts create mode 100644 features/sdnr/odlux/odlux/apps/faultApp/src/handlers/notificationsHandler.ts create mode 100644 features/sdnr/odlux/odlux/apps/faultApp/src/index.html create mode 100644 features/sdnr/odlux/odlux/apps/faultApp/src/models/fault.ts create mode 100644 features/sdnr/odlux/odlux/apps/faultApp/src/models/panelId.ts create mode 100644 features/sdnr/odlux/odlux/apps/faultApp/src/pluginFault.tsx create mode 100644 features/sdnr/odlux/odlux/apps/faultApp/src/services/faultStatusService.ts create mode 100644 features/sdnr/odlux/odlux/apps/faultApp/src/views/faultApplication.tsx create mode 100644 features/sdnr/odlux/odlux/apps/faultApp/tsconfig.json create mode 100644 features/sdnr/odlux/odlux/apps/faultApp/webpack.config.js create mode 100644 features/sdnr/odlux/odlux/apps/helpApp/.babelrc create mode 100644 features/sdnr/odlux/odlux/apps/helpApp/package.json create mode 100644 features/sdnr/odlux/odlux/apps/helpApp/pom.xml create mode 100644 features/sdnr/odlux/odlux/apps/helpApp/src/actions/helpActions.ts create mode 100644 features/sdnr/odlux/odlux/apps/helpApp/src/assets/icons/helpAppIcon.svg create mode 100644 features/sdnr/odlux/odlux/apps/helpApp/src/components/helpStatus.tsx create mode 100644 features/sdnr/odlux/odlux/apps/helpApp/src/components/markdown.tsx create mode 100644 features/sdnr/odlux/odlux/apps/helpApp/src/components/tocEntry.tsx create mode 100644 features/sdnr/odlux/odlux/apps/helpApp/src/handlers/helpAppRootHandler.ts create mode 100644 features/sdnr/odlux/odlux/apps/helpApp/src/index.html create mode 100644 features/sdnr/odlux/odlux/apps/helpApp/src/models/tocNode.ts create mode 100644 features/sdnr/odlux/odlux/apps/helpApp/src/plugin.tsx create mode 100644 features/sdnr/odlux/odlux/apps/helpApp/src/services/helpService.ts create mode 100644 features/sdnr/odlux/odlux/apps/helpApp/src/utilities/path.ts create mode 100644 features/sdnr/odlux/odlux/apps/helpApp/src/views/helpApplication.tsx create mode 100644 features/sdnr/odlux/odlux/apps/helpApp/src/views/helpTocApp.tsx create mode 100644 features/sdnr/odlux/odlux/apps/helpApp/tsconfig.json create mode 100644 features/sdnr/odlux/odlux/apps/helpApp/webpack.config.js create mode 100644 features/sdnr/odlux/odlux/apps/inventoryApp/.babelrc create mode 100644 features/sdnr/odlux/odlux/apps/inventoryApp/package.json create mode 100644 features/sdnr/odlux/odlux/apps/inventoryApp/pom.xml create mode 100644 features/sdnr/odlux/odlux/apps/inventoryApp/src/actions/inventoryDeviceListActions.ts create mode 100644 features/sdnr/odlux/odlux/apps/inventoryApp/src/actions/inventoryTreeActions.ts create mode 100644 features/sdnr/odlux/odlux/apps/inventoryApp/src/actions/panelActions.ts create mode 100644 features/sdnr/odlux/odlux/apps/inventoryApp/src/assets/icons/inventoryAppIcon.svg create mode 100644 features/sdnr/odlux/odlux/apps/inventoryApp/src/components/refreshInventoryDialog.tsx create mode 100644 features/sdnr/odlux/odlux/apps/inventoryApp/src/fakeData/index.ts create mode 100644 features/sdnr/odlux/odlux/apps/inventoryApp/src/handlers/inventoryAppRootHandler.ts create mode 100644 features/sdnr/odlux/odlux/apps/inventoryApp/src/handlers/inventoryDeviceListActionHandler.ts create mode 100644 features/sdnr/odlux/odlux/apps/inventoryApp/src/handlers/inventoryElementsHandler.ts create mode 100644 features/sdnr/odlux/odlux/apps/inventoryApp/src/handlers/inventoryTreeHandler.ts create mode 100644 features/sdnr/odlux/odlux/apps/inventoryApp/src/handlers/panelHandler.ts create mode 100644 features/sdnr/odlux/odlux/apps/inventoryApp/src/index.html create mode 100644 features/sdnr/odlux/odlux/apps/inventoryApp/src/models/inventory.ts create mode 100644 features/sdnr/odlux/odlux/apps/inventoryApp/src/models/inventoryDeviceListType.ts create mode 100644 features/sdnr/odlux/odlux/apps/inventoryApp/src/models/networkElementConnection.ts create mode 100644 features/sdnr/odlux/odlux/apps/inventoryApp/src/models/panelId.ts create mode 100644 features/sdnr/odlux/odlux/apps/inventoryApp/src/pluginInventory.tsx create mode 100644 features/sdnr/odlux/odlux/apps/inventoryApp/src/services/inventoryService.ts create mode 100644 features/sdnr/odlux/odlux/apps/inventoryApp/src/views/dashboard.tsx create mode 100644 features/sdnr/odlux/odlux/apps/inventoryApp/src/views/detail.tsx create mode 100644 features/sdnr/odlux/odlux/apps/inventoryApp/src/views/treeview.tsx create mode 100644 features/sdnr/odlux/odlux/apps/inventoryApp/tsconfig.json create mode 100644 features/sdnr/odlux/odlux/apps/inventoryApp/webpack.config.js create mode 100644 features/sdnr/odlux/odlux/apps/maintenanceApp/.babelrc create mode 100644 features/sdnr/odlux/odlux/apps/maintenanceApp/package.json create mode 100644 features/sdnr/odlux/odlux/apps/maintenanceApp/pom.xml create mode 100644 features/sdnr/odlux/odlux/apps/maintenanceApp/src/actions/maintenenceActions.ts create mode 100644 features/sdnr/odlux/odlux/apps/maintenanceApp/src/assets/icons/maintenanceAppIcon.svg create mode 100644 features/sdnr/odlux/odlux/apps/maintenanceApp/src/components/editMaintenenceEntryDialog.tsx create mode 100644 features/sdnr/odlux/odlux/apps/maintenanceApp/src/components/refreshMaintenanceEntries.tsx create mode 100644 features/sdnr/odlux/odlux/apps/maintenanceApp/src/handlers/maintenanceAppRootHandler.ts create mode 100644 features/sdnr/odlux/odlux/apps/maintenanceApp/src/handlers/maintenanceEntriesHandler.ts create mode 100644 features/sdnr/odlux/odlux/apps/maintenanceApp/src/index.html create mode 100644 features/sdnr/odlux/odlux/apps/maintenanceApp/src/models/maintenanceEntryType.ts create mode 100644 features/sdnr/odlux/odlux/apps/maintenanceApp/src/pluginMaintenance.tsx create mode 100644 features/sdnr/odlux/odlux/apps/maintenanceApp/src/services/maintenenceService.ts create mode 100644 features/sdnr/odlux/odlux/apps/maintenanceApp/src/utils/timeUtils.ts create mode 100644 features/sdnr/odlux/odlux/apps/maintenanceApp/src/views/maintenanceView.tsx create mode 100644 features/sdnr/odlux/odlux/apps/maintenanceApp/tsconfig.json create mode 100644 features/sdnr/odlux/odlux/apps/maintenanceApp/webpack.config.js create mode 100644 features/sdnr/odlux/odlux/apps/mediatorApp/.babelrc create mode 100644 features/sdnr/odlux/odlux/apps/mediatorApp/package.json create mode 100644 features/sdnr/odlux/odlux/apps/mediatorApp/pom.xml create mode 100644 features/sdnr/odlux/odlux/apps/mediatorApp/src/actions/avaliableMediatorServersActions.ts create mode 100644 features/sdnr/odlux/odlux/apps/mediatorApp/src/actions/mediatorConfigActions.ts create mode 100644 features/sdnr/odlux/odlux/apps/mediatorApp/src/actions/mediatorServerActions.ts create mode 100644 features/sdnr/odlux/odlux/apps/mediatorApp/src/assets/icons/mediatorAppIcon.svg create mode 100644 features/sdnr/odlux/odlux/apps/mediatorApp/src/components/editMediatorConfigDialog.tsx create mode 100644 features/sdnr/odlux/odlux/apps/mediatorApp/src/components/editMediatorServerDialog.tsx create mode 100644 features/sdnr/odlux/odlux/apps/mediatorApp/src/components/refreshMediatorDialog.tsx create mode 100644 features/sdnr/odlux/odlux/apps/mediatorApp/src/components/showMeditaorInfoDialog.tsx create mode 100644 features/sdnr/odlux/odlux/apps/mediatorApp/src/handlers/avaliableMediatorServersHandler.ts create mode 100644 features/sdnr/odlux/odlux/apps/mediatorApp/src/handlers/mediatorAppRootHandler.ts create mode 100644 features/sdnr/odlux/odlux/apps/mediatorApp/src/handlers/mediatorServerHandler.ts create mode 100644 features/sdnr/odlux/odlux/apps/mediatorApp/src/index.html create mode 100644 features/sdnr/odlux/odlux/apps/mediatorApp/src/models/mediatorServer.ts create mode 100644 features/sdnr/odlux/odlux/apps/mediatorApp/src/plugin.tsx create mode 100644 features/sdnr/odlux/odlux/apps/mediatorApp/src/services/mediatorService.ts create mode 100644 features/sdnr/odlux/odlux/apps/mediatorApp/src/views/mediatorApplication.tsx create mode 100644 features/sdnr/odlux/odlux/apps/mediatorApp/src/views/mediatorServerSelection.tsx create mode 100644 features/sdnr/odlux/odlux/apps/mediatorApp/tsconfig.json create mode 100644 features/sdnr/odlux/odlux/apps/mediatorApp/tslint.json create mode 100644 features/sdnr/odlux/odlux/apps/mediatorApp/webpack.config.js create mode 100644 features/sdnr/odlux/odlux/apps/microwaveApp/.babelrc create mode 100644 features/sdnr/odlux/odlux/apps/microwaveApp/package.json create mode 100644 features/sdnr/odlux/odlux/apps/microwaveApp/pom.xml create mode 100644 features/sdnr/odlux/odlux/apps/microwaveApp/src/index.html create mode 100644 features/sdnr/odlux/odlux/apps/microwaveApp/src/lineOfSight/actions/lineOfSightCommonActions.ts create mode 100644 features/sdnr/odlux/odlux/apps/microwaveApp/src/lineOfSight/actions/lineOfSightMapActions.ts create mode 100644 features/sdnr/odlux/odlux/apps/microwaveApp/src/lineOfSight/assets/lineOfSightAppIcon.svg create mode 100644 features/sdnr/odlux/odlux/apps/microwaveApp/src/lineOfSight/components/lineOfSightConnectionErrorPoup.tsx create mode 100644 features/sdnr/odlux/odlux/apps/microwaveApp/src/lineOfSight/components/lineOfSightHeightChart.tsx create mode 100644 features/sdnr/odlux/odlux/apps/microwaveApp/src/lineOfSight/components/lineOfSightMap.tsx create mode 100644 features/sdnr/odlux/odlux/apps/microwaveApp/src/lineOfSight/components/lineOfSightMapContextMenu.tsx create mode 100644 features/sdnr/odlux/odlux/apps/microwaveApp/src/lineOfSight/components/lineOfSightMapInfo.tsx create mode 100644 features/sdnr/odlux/odlux/apps/microwaveApp/src/lineOfSight/config.ts create mode 100644 features/sdnr/odlux/odlux/apps/microwaveApp/src/lineOfSight/handlers/lineOfSightMapHandler.ts create mode 100644 features/sdnr/odlux/odlux/apps/microwaveApp/src/lineOfSight/hooks/d3.ts create mode 100644 features/sdnr/odlux/odlux/apps/microwaveApp/src/lineOfSight/model/lineOfSightGPSProfileResult.ts create mode 100644 features/sdnr/odlux/odlux/apps/microwaveApp/src/lineOfSight/model/lineOfSightHeight.tsx create mode 100644 features/sdnr/odlux/odlux/apps/microwaveApp/src/lineOfSight/model/lineOfSightLatLon.ts create mode 100644 features/sdnr/odlux/odlux/apps/microwaveApp/src/lineOfSight/service/lineOfSightHeightService.ts create mode 100644 features/sdnr/odlux/odlux/apps/microwaveApp/src/lineOfSight/styles/index.css create mode 100644 features/sdnr/odlux/odlux/apps/microwaveApp/src/lineOfSight/styles/mapbox-gl.css create mode 100644 features/sdnr/odlux/odlux/apps/microwaveApp/src/lineOfSight/utils/lineOfSightMap.ts create mode 100644 features/sdnr/odlux/odlux/apps/microwaveApp/src/lineOfSight/utils/lineOfSightMath.ts create mode 100644 features/sdnr/odlux/odlux/apps/microwaveApp/src/lineOfSight/views/lineOfSightMain.tsx create mode 100644 features/sdnr/odlux/odlux/apps/microwaveApp/src/linkCalculator/actions/adaptiveModulationAction.ts create mode 100644 features/sdnr/odlux/odlux/apps/microwaveApp/src/linkCalculator/actions/antennaActions.ts create mode 100644 features/sdnr/odlux/odlux/apps/microwaveApp/src/linkCalculator/actions/atmosphericLossAction.ts create mode 100644 features/sdnr/odlux/odlux/apps/microwaveApp/src/linkCalculator/actions/bandPlanAction.ts create mode 100644 features/sdnr/odlux/odlux/apps/microwaveApp/src/linkCalculator/actions/commonActions.ts create mode 100644 features/sdnr/odlux/odlux/apps/microwaveApp/src/linkCalculator/actions/errorAction.ts create mode 100644 features/sdnr/odlux/odlux/apps/microwaveApp/src/linkCalculator/actions/handleButtonAction.ts create mode 100644 features/sdnr/odlux/odlux/apps/microwaveApp/src/linkCalculator/actions/linkAction.ts create mode 100644 features/sdnr/odlux/odlux/apps/microwaveApp/src/linkCalculator/actions/queryActions.ts create mode 100644 features/sdnr/odlux/odlux/apps/microwaveApp/src/linkCalculator/actions/radioActions.ts create mode 100644 features/sdnr/odlux/odlux/apps/microwaveApp/src/linkCalculator/actions/saveLinkAction.ts create mode 100644 features/sdnr/odlux/odlux/apps/microwaveApp/src/linkCalculator/actions/siteAction.ts create mode 100644 features/sdnr/odlux/odlux/apps/microwaveApp/src/linkCalculator/actions/viewAction.ts create mode 100644 features/sdnr/odlux/odlux/apps/microwaveApp/src/linkCalculator/actions/waveguideActions.ts create mode 100644 features/sdnr/odlux/odlux/apps/microwaveApp/src/linkCalculator/assets/icons/microwaveAppIcon.svg create mode 100644 features/sdnr/odlux/odlux/apps/microwaveApp/src/linkCalculator/components/adaptiveModulationDialog.tsx create mode 100644 features/sdnr/odlux/odlux/apps/microwaveApp/src/linkCalculator/components/antenna.tsx create mode 100644 features/sdnr/odlux/odlux/apps/microwaveApp/src/linkCalculator/components/attenuations.tsx create mode 100644 features/sdnr/odlux/odlux/apps/microwaveApp/src/linkCalculator/components/channelListDialog.tsx create mode 100644 features/sdnr/odlux/odlux/apps/microwaveApp/src/linkCalculator/components/connectionInfo.tsx create mode 100644 features/sdnr/odlux/odlux/apps/microwaveApp/src/linkCalculator/components/frequencyChannel.tsx create mode 100644 features/sdnr/odlux/odlux/apps/microwaveApp/src/linkCalculator/components/linkTable.tsx create mode 100644 features/sdnr/odlux/odlux/apps/microwaveApp/src/linkCalculator/components/location.tsx create mode 100644 features/sdnr/odlux/odlux/apps/microwaveApp/src/linkCalculator/components/manualLocationEntr.tsx create mode 100644 features/sdnr/odlux/odlux/apps/microwaveApp/src/linkCalculator/components/missingInformation.tsx create mode 100644 features/sdnr/odlux/odlux/apps/microwaveApp/src/linkCalculator/components/outlinedDiv.tsx create mode 100644 features/sdnr/odlux/odlux/apps/microwaveApp/src/linkCalculator/components/radio.tsx create mode 100644 features/sdnr/odlux/odlux/apps/microwaveApp/src/linkCalculator/components/textFieldwithAdornment.tsx create mode 100644 features/sdnr/odlux/odlux/apps/microwaveApp/src/linkCalculator/components/waveguide.tsx create mode 100644 features/sdnr/odlux/odlux/apps/microwaveApp/src/linkCalculator/handlers/antennaHandler.ts create mode 100644 features/sdnr/odlux/odlux/apps/microwaveApp/src/linkCalculator/handlers/atmosphericLossHandler.ts create mode 100644 features/sdnr/odlux/odlux/apps/microwaveApp/src/linkCalculator/handlers/bandPlanHandler.ts create mode 100644 features/sdnr/odlux/odlux/apps/microwaveApp/src/linkCalculator/handlers/errorHandler.ts create mode 100644 features/sdnr/odlux/odlux/apps/microwaveApp/src/linkCalculator/handlers/linkHandler.ts create mode 100644 features/sdnr/odlux/odlux/apps/microwaveApp/src/linkCalculator/handlers/linkTableHandler.ts create mode 100644 features/sdnr/odlux/odlux/apps/microwaveApp/src/linkCalculator/handlers/queryHandler.ts create mode 100644 features/sdnr/odlux/odlux/apps/microwaveApp/src/linkCalculator/handlers/radioHandler.ts create mode 100644 features/sdnr/odlux/odlux/apps/microwaveApp/src/linkCalculator/handlers/rootHandler.ts create mode 100644 features/sdnr/odlux/odlux/apps/microwaveApp/src/linkCalculator/handlers/siteHandler.ts create mode 100644 features/sdnr/odlux/odlux/apps/microwaveApp/src/linkCalculator/handlers/viewHandler.ts create mode 100644 features/sdnr/odlux/odlux/apps/microwaveApp/src/linkCalculator/handlers/waveguideHandler.ts create mode 100644 features/sdnr/odlux/odlux/apps/microwaveApp/src/linkCalculator/model/adaptiveModulationInput.ts create mode 100644 features/sdnr/odlux/odlux/apps/microwaveApp/src/linkCalculator/model/adaptiveModulationTable.ts create mode 100644 features/sdnr/odlux/odlux/apps/microwaveApp/src/linkCalculator/model/antenna.ts create mode 100644 features/sdnr/odlux/odlux/apps/microwaveApp/src/linkCalculator/model/bandPlan.ts create mode 100644 features/sdnr/odlux/odlux/apps/microwaveApp/src/linkCalculator/model/calculationResult.ts create mode 100644 features/sdnr/odlux/odlux/apps/microwaveApp/src/linkCalculator/model/link.ts create mode 100644 features/sdnr/odlux/odlux/apps/microwaveApp/src/linkCalculator/model/linkTable.ts create mode 100644 features/sdnr/odlux/odlux/apps/microwaveApp/src/linkCalculator/model/modulation.ts create mode 100644 features/sdnr/odlux/odlux/apps/microwaveApp/src/linkCalculator/model/radio.ts create mode 100644 features/sdnr/odlux/odlux/apps/microwaveApp/src/linkCalculator/model/tabId.ts create mode 100644 features/sdnr/odlux/odlux/apps/microwaveApp/src/linkCalculator/model/topologyTypes.ts create mode 100644 features/sdnr/odlux/odlux/apps/microwaveApp/src/linkCalculator/model/updateLink.ts create mode 100644 features/sdnr/odlux/odlux/apps/microwaveApp/src/linkCalculator/model/waveguide.ts create mode 100644 features/sdnr/odlux/odlux/apps/microwaveApp/src/linkCalculator/service/dataService.ts create mode 100644 features/sdnr/odlux/odlux/apps/microwaveApp/src/linkCalculator/service/processingService.ts create mode 100644 features/sdnr/odlux/odlux/apps/microwaveApp/src/linkCalculator/utils/checkLink.ts create mode 100644 features/sdnr/odlux/odlux/apps/microwaveApp/src/linkCalculator/utils/geoConverter.ts create mode 100644 features/sdnr/odlux/odlux/apps/microwaveApp/src/linkCalculator/utils/math.ts create mode 100644 features/sdnr/odlux/odlux/apps/microwaveApp/src/linkCalculator/views/linkCalculation.tsx create mode 100644 features/sdnr/odlux/odlux/apps/microwaveApp/src/linkCalculator/views/mainView.tsx create mode 100644 features/sdnr/odlux/odlux/apps/microwaveApp/src/pluginMicrowave.tsx create mode 100644 features/sdnr/odlux/odlux/apps/microwaveApp/tsconfig.json create mode 100644 features/sdnr/odlux/odlux/apps/microwaveApp/webpack.config.js create mode 100644 features/sdnr/odlux/odlux/apps/minimumApp/.babelrc create mode 100644 features/sdnr/odlux/odlux/apps/minimumApp/package.json create mode 100644 features/sdnr/odlux/odlux/apps/minimumApp/pom.xml create mode 100644 features/sdnr/odlux/odlux/apps/minimumApp/src/assets/icons/minimumAppIcon.svg create mode 100644 features/sdnr/odlux/odlux/apps/minimumApp/src/handlers/minimumAppRootHandler.ts create mode 100644 features/sdnr/odlux/odlux/apps/minimumApp/src/index.html create mode 100644 features/sdnr/odlux/odlux/apps/minimumApp/src/plugin.tsx create mode 100644 features/sdnr/odlux/odlux/apps/minimumApp/tsconfig.json create mode 100644 features/sdnr/odlux/odlux/apps/minimumApp/webpack.config.js create mode 100644 features/sdnr/odlux/odlux/apps/networkMapApp/.babelrc create mode 100644 features/sdnr/odlux/odlux/apps/networkMapApp/icons/README.md create mode 100644 features/sdnr/odlux/odlux/apps/networkMapApp/icons/apartment.png create mode 100644 features/sdnr/odlux/odlux/apps/networkMapApp/icons/apartment.png.d.ts create mode 100644 features/sdnr/odlux/odlux/apps/networkMapApp/icons/customize.png create mode 100644 features/sdnr/odlux/odlux/apps/networkMapApp/icons/customize.png.d.ts create mode 100644 features/sdnr/odlux/odlux/apps/networkMapApp/icons/datacenter.png create mode 100644 features/sdnr/odlux/odlux/apps/networkMapApp/icons/datacenter.png.d.ts create mode 100644 features/sdnr/odlux/odlux/apps/networkMapApp/icons/datacenterred.png create mode 100644 features/sdnr/odlux/odlux/apps/networkMapApp/icons/datacenterred.png.d.ts create mode 100644 features/sdnr/odlux/odlux/apps/networkMapApp/icons/factory.png create mode 100644 features/sdnr/odlux/odlux/apps/networkMapApp/icons/factory.png.d.ts create mode 100644 features/sdnr/odlux/odlux/apps/networkMapApp/icons/factoryred.png create mode 100644 features/sdnr/odlux/odlux/apps/networkMapApp/icons/factoryred.png.d.ts create mode 100644 features/sdnr/odlux/odlux/apps/networkMapApp/icons/lamp.png create mode 100644 features/sdnr/odlux/odlux/apps/networkMapApp/icons/lamp.png.d.ts create mode 100644 features/sdnr/odlux/odlux/apps/networkMapApp/icons/lampred.png create mode 100644 features/sdnr/odlux/odlux/apps/networkMapApp/icons/lampred.png.d.ts create mode 100644 features/sdnr/odlux/odlux/apps/networkMapApp/package.json create mode 100644 features/sdnr/odlux/odlux/apps/networkMapApp/pom.xml create mode 100644 features/sdnr/odlux/odlux/apps/networkMapApp/src/actions/connectivityAction.ts create mode 100644 features/sdnr/odlux/odlux/apps/networkMapApp/src/actions/detailsAction.ts create mode 100644 features/sdnr/odlux/odlux/apps/networkMapApp/src/actions/filterActions.ts create mode 100644 features/sdnr/odlux/odlux/apps/networkMapApp/src/actions/mapActions.ts create mode 100644 features/sdnr/odlux/odlux/apps/networkMapApp/src/actions/searchAction.ts create mode 100644 features/sdnr/odlux/odlux/apps/networkMapApp/src/actions/settingsAction.ts create mode 100644 features/sdnr/odlux/odlux/apps/networkMapApp/src/actions/sitedocManagementAction.ts create mode 100644 features/sdnr/odlux/odlux/apps/networkMapApp/src/app.tsx create mode 100644 features/sdnr/odlux/odlux/apps/networkMapApp/src/assets/icons/networkMapAppIcon.svg create mode 100644 features/sdnr/odlux/odlux/apps/networkMapApp/src/components/customize/networkMapSetup.tsx create mode 100644 features/sdnr/odlux/odlux/apps/networkMapApp/src/components/customize/themeElement.tsx create mode 100644 features/sdnr/odlux/odlux/apps/networkMapApp/src/components/denseTable.tsx create mode 100644 features/sdnr/odlux/odlux/apps/networkMapApp/src/components/details/details.tsx create mode 100644 features/sdnr/odlux/odlux/apps/networkMapApp/src/components/details/linkDetails.tsx create mode 100644 features/sdnr/odlux/odlux/apps/networkMapApp/src/components/details/serviceDetails.tsx create mode 100644 features/sdnr/odlux/odlux/apps/networkMapApp/src/components/details/siteDetails.tsx create mode 100644 features/sdnr/odlux/odlux/apps/networkMapApp/src/components/map/connectionInfo.tsx create mode 100644 features/sdnr/odlux/odlux/apps/networkMapApp/src/components/map/filterBar.tsx create mode 100644 features/sdnr/odlux/odlux/apps/networkMapApp/src/components/map/iconSwitch.tsx create mode 100644 features/sdnr/odlux/odlux/apps/networkMapApp/src/components/map/layerSelection.tsx create mode 100644 features/sdnr/odlux/odlux/apps/networkMapApp/src/components/map/map.tsx create mode 100644 features/sdnr/odlux/odlux/apps/networkMapApp/src/components/map/mapControl.tsx create mode 100644 features/sdnr/odlux/odlux/apps/networkMapApp/src/components/map/mapPopup.tsx create mode 100644 features/sdnr/odlux/odlux/apps/networkMapApp/src/components/map/searchBar.tsx create mode 100644 features/sdnr/odlux/odlux/apps/networkMapApp/src/components/map/searchResultDisplay.tsx create mode 100644 features/sdnr/odlux/odlux/apps/networkMapApp/src/components/map/statistics.tsx create mode 100644 features/sdnr/odlux/odlux/apps/networkMapApp/src/components/stadok/stadokDetailsPopup.tsx create mode 100644 features/sdnr/odlux/odlux/apps/networkMapApp/src/config.ts create mode 100644 features/sdnr/odlux/odlux/apps/networkMapApp/src/handlers/connectivityHandler.ts create mode 100644 features/sdnr/odlux/odlux/apps/networkMapApp/src/handlers/detailsHandler.ts create mode 100644 features/sdnr/odlux/odlux/apps/networkMapApp/src/handlers/filterHandler.ts create mode 100644 features/sdnr/odlux/odlux/apps/networkMapApp/src/handlers/mapHandler.ts create mode 100644 features/sdnr/odlux/odlux/apps/networkMapApp/src/handlers/rootHandler.ts create mode 100644 features/sdnr/odlux/odlux/apps/networkMapApp/src/handlers/searchHandler.ts create mode 100644 features/sdnr/odlux/odlux/apps/networkMapApp/src/handlers/settingsHandler.ts create mode 100644 features/sdnr/odlux/odlux/apps/networkMapApp/src/handlers/sitedocManagementHandler.ts create mode 100644 features/sdnr/odlux/odlux/apps/networkMapApp/src/index.html create mode 100644 features/sdnr/odlux/odlux/apps/networkMapApp/src/model/boundingBox.ts create mode 100644 features/sdnr/odlux/odlux/apps/networkMapApp/src/model/coordinates.ts create mode 100644 features/sdnr/odlux/odlux/apps/networkMapApp/src/model/count.ts create mode 100644 features/sdnr/odlux/odlux/apps/networkMapApp/src/model/historyEntry.ts create mode 100644 features/sdnr/odlux/odlux/apps/networkMapApp/src/model/networkElementConnection.ts create mode 100644 features/sdnr/odlux/odlux/apps/networkMapApp/src/model/searchResult.ts create mode 100644 features/sdnr/odlux/odlux/apps/networkMapApp/src/model/settings.ts create mode 100644 features/sdnr/odlux/odlux/apps/networkMapApp/src/model/siteDocTypes.ts create mode 100644 features/sdnr/odlux/odlux/apps/networkMapApp/src/model/stadokOrder.ts create mode 100644 features/sdnr/odlux/odlux/apps/networkMapApp/src/model/stadokSite.ts create mode 100644 features/sdnr/odlux/odlux/apps/networkMapApp/src/model/topologyTypes.ts create mode 100644 features/sdnr/odlux/odlux/apps/networkMapApp/src/pluginTransport.tsx create mode 100644 features/sdnr/odlux/odlux/apps/networkMapApp/src/services/dataService.ts create mode 100644 features/sdnr/odlux/odlux/apps/networkMapApp/src/services/mapImagesService.ts create mode 100644 features/sdnr/odlux/odlux/apps/networkMapApp/src/services/settingsService.ts create mode 100644 features/sdnr/odlux/odlux/apps/networkMapApp/src/services/sitedocDataService.ts create mode 100644 features/sdnr/odlux/odlux/apps/networkMapApp/src/styles/index.css create mode 100644 features/sdnr/odlux/odlux/apps/networkMapApp/src/styles/mapbox-gl.css create mode 100644 features/sdnr/odlux/odlux/apps/networkMapApp/src/utils/detailsUtils.ts create mode 100644 features/sdnr/odlux/odlux/apps/networkMapApp/src/utils/mapLayers.ts create mode 100644 features/sdnr/odlux/odlux/apps/networkMapApp/src/utils/mapUtils.ts create mode 100644 features/sdnr/odlux/odlux/apps/networkMapApp/tsconfig.json create mode 100644 features/sdnr/odlux/odlux/apps/networkMapApp/webpack.config.js create mode 100644 features/sdnr/odlux/odlux/apps/performanceHistoryApp/.babelrc create mode 100644 features/sdnr/odlux/odlux/apps/performanceHistoryApp/package.json create mode 100644 features/sdnr/odlux/odlux/apps/performanceHistoryApp/pom.xml create mode 100644 features/sdnr/odlux/odlux/apps/performanceHistoryApp/src/actions/deviceListActions.ts create mode 100644 features/sdnr/odlux/odlux/apps/performanceHistoryApp/src/actions/ltpAction.ts create mode 100644 features/sdnr/odlux/odlux/apps/performanceHistoryApp/src/actions/panelChangeActions.ts create mode 100644 features/sdnr/odlux/odlux/apps/performanceHistoryApp/src/actions/reloadAction.ts create mode 100644 features/sdnr/odlux/odlux/apps/performanceHistoryApp/src/actions/timeChangeAction.ts create mode 100644 features/sdnr/odlux/odlux/apps/performanceHistoryApp/src/actions/toggleActions.ts create mode 100644 features/sdnr/odlux/odlux/apps/performanceHistoryApp/src/assets/icons/performanceHistoryAppIcon.svg create mode 100644 features/sdnr/odlux/odlux/apps/performanceHistoryApp/src/components/adaptiveModulation.tsx create mode 100644 features/sdnr/odlux/odlux/apps/performanceHistoryApp/src/components/chartFilter.tsx create mode 100644 features/sdnr/odlux/odlux/apps/performanceHistoryApp/src/components/crossPolarDiscrimination.tsx create mode 100644 features/sdnr/odlux/odlux/apps/performanceHistoryApp/src/components/ltpSelection.tsx create mode 100644 features/sdnr/odlux/odlux/apps/performanceHistoryApp/src/components/performanceData.tsx create mode 100644 features/sdnr/odlux/odlux/apps/performanceHistoryApp/src/components/receiveLevel.tsx create mode 100644 features/sdnr/odlux/odlux/apps/performanceHistoryApp/src/components/signalToInterference.tsx create mode 100644 features/sdnr/odlux/odlux/apps/performanceHistoryApp/src/components/temperature.tsx create mode 100644 features/sdnr/odlux/odlux/apps/performanceHistoryApp/src/components/toggleContainer.tsx create mode 100644 features/sdnr/odlux/odlux/apps/performanceHistoryApp/src/components/transmissionPower.tsx create mode 100644 features/sdnr/odlux/odlux/apps/performanceHistoryApp/src/handlers/adaptiveModulationHandler.ts create mode 100644 features/sdnr/odlux/odlux/apps/performanceHistoryApp/src/handlers/availableLtpsActionHandler.ts create mode 100644 features/sdnr/odlux/odlux/apps/performanceHistoryApp/src/handlers/crossPolarDiscriminationHandler.ts create mode 100644 features/sdnr/odlux/odlux/apps/performanceHistoryApp/src/handlers/deviceListActionHandler.ts create mode 100644 features/sdnr/odlux/odlux/apps/performanceHistoryApp/src/handlers/performanceDataHandler.ts create mode 100644 features/sdnr/odlux/odlux/apps/performanceHistoryApp/src/handlers/performanceHistoryRootHandler.ts create mode 100644 features/sdnr/odlux/odlux/apps/performanceHistoryApp/src/handlers/receiveLevelHandler.ts create mode 100644 features/sdnr/odlux/odlux/apps/performanceHistoryApp/src/handlers/signalToInterferenceHandler.ts create mode 100644 features/sdnr/odlux/odlux/apps/performanceHistoryApp/src/handlers/temperatureHandler.ts create mode 100644 features/sdnr/odlux/odlux/apps/performanceHistoryApp/src/handlers/transmissionPowerHandler.ts create mode 100644 features/sdnr/odlux/odlux/apps/performanceHistoryApp/src/index.html create mode 100644 features/sdnr/odlux/odlux/apps/performanceHistoryApp/src/models/adaptiveModulationDataType.ts create mode 100644 features/sdnr/odlux/odlux/apps/performanceHistoryApp/src/models/availableLtps.ts create mode 100644 features/sdnr/odlux/odlux/apps/performanceHistoryApp/src/models/chartTypes.ts create mode 100644 features/sdnr/odlux/odlux/apps/performanceHistoryApp/src/models/crossPolarDiscriminationDataType.ts create mode 100644 features/sdnr/odlux/odlux/apps/performanceHistoryApp/src/models/deviceListType.ts create mode 100644 features/sdnr/odlux/odlux/apps/performanceHistoryApp/src/models/panelId.ts create mode 100644 features/sdnr/odlux/odlux/apps/performanceHistoryApp/src/models/performanceDataType.ts create mode 100644 features/sdnr/odlux/odlux/apps/performanceHistoryApp/src/models/receiveLevelDataType.ts create mode 100644 features/sdnr/odlux/odlux/apps/performanceHistoryApp/src/models/signalToInteferenceDataType.ts create mode 100644 features/sdnr/odlux/odlux/apps/performanceHistoryApp/src/models/temperatureDataType.ts create mode 100644 features/sdnr/odlux/odlux/apps/performanceHistoryApp/src/models/toggleDataType.ts create mode 100644 features/sdnr/odlux/odlux/apps/performanceHistoryApp/src/models/topologyNetconf.ts create mode 100644 features/sdnr/odlux/odlux/apps/performanceHistoryApp/src/models/transmissionPowerDataType.ts create mode 100644 features/sdnr/odlux/odlux/apps/performanceHistoryApp/src/pluginPerformance.tsx create mode 100644 features/sdnr/odlux/odlux/apps/performanceHistoryApp/src/services/performanceHistoryService.ts create mode 100644 features/sdnr/odlux/odlux/apps/performanceHistoryApp/src/utils/chartUtils.tsx create mode 100644 features/sdnr/odlux/odlux/apps/performanceHistoryApp/src/utils/tableUtils.ts create mode 100644 features/sdnr/odlux/odlux/apps/performanceHistoryApp/src/views/performanceHistoryApplication.tsx create mode 100644 features/sdnr/odlux/odlux/apps/performanceHistoryApp/tsconfig.json create mode 100644 features/sdnr/odlux/odlux/apps/performanceHistoryApp/webpack.config.js create mode 100644 features/sdnr/odlux/odlux/apps/siteManagerApp/.babelrc create mode 100644 features/sdnr/odlux/odlux/apps/siteManagerApp/package.json create mode 100644 features/sdnr/odlux/odlux/apps/siteManagerApp/pom.xml create mode 100644 features/sdnr/odlux/odlux/apps/siteManagerApp/src/actions/detailsAction.ts create mode 100644 features/sdnr/odlux/odlux/apps/siteManagerApp/src/actions/panelActions.ts create mode 100644 features/sdnr/odlux/odlux/apps/siteManagerApp/src/actions/siteManagerSiteSearchAction.ts create mode 100644 features/sdnr/odlux/odlux/apps/siteManagerApp/src/actions/siteManagerTreeActions.ts create mode 100644 features/sdnr/odlux/odlux/apps/siteManagerApp/src/actions/sitedocManagementAction.ts create mode 100644 features/sdnr/odlux/odlux/apps/siteManagerApp/src/assets/icons/siteManagerAppIcon.svg create mode 100644 features/sdnr/odlux/odlux/apps/siteManagerApp/src/components/createNewOrder.tsx create mode 100644 features/sdnr/odlux/odlux/apps/siteManagerApp/src/components/denseTable.tsx create mode 100644 features/sdnr/odlux/odlux/apps/siteManagerApp/src/components/deviceTable.tsx create mode 100644 features/sdnr/odlux/odlux/apps/siteManagerApp/src/components/linkTable.tsx create mode 100644 features/sdnr/odlux/odlux/apps/siteManagerApp/src/components/messageDialog.tsx create mode 100644 features/sdnr/odlux/odlux/apps/siteManagerApp/src/components/orderTask.tsx create mode 100644 features/sdnr/odlux/odlux/apps/siteManagerApp/src/components/picturesTable.tsx create mode 100644 features/sdnr/odlux/odlux/apps/siteManagerApp/src/components/refreshSiteTableDialog.tsx create mode 100644 features/sdnr/odlux/odlux/apps/siteManagerApp/src/components/siteAdditionalInformation.tsx create mode 100644 features/sdnr/odlux/odlux/apps/siteManagerApp/src/components/siteConfiguration.tsx create mode 100644 features/sdnr/odlux/odlux/apps/siteManagerApp/src/components/siteDetails.tsx create mode 100644 features/sdnr/odlux/odlux/apps/siteManagerApp/src/components/siteManagerSiteSearch.tsx create mode 100644 features/sdnr/odlux/odlux/apps/siteManagerApp/src/components/siteManagerTreeview.tsx create mode 100644 features/sdnr/odlux/odlux/apps/siteManagerApp/src/components/siteOrdersTable.tsx create mode 100644 features/sdnr/odlux/odlux/apps/siteManagerApp/src/components/siteTable.tsx create mode 100644 features/sdnr/odlux/odlux/apps/siteManagerApp/src/components/treeItem.tsx create mode 100644 features/sdnr/odlux/odlux/apps/siteManagerApp/src/components/tssReportsTable.tsx create mode 100644 features/sdnr/odlux/odlux/apps/siteManagerApp/src/config.ts create mode 100644 features/sdnr/odlux/odlux/apps/siteManagerApp/src/handlers/detailsReducer.ts create mode 100644 features/sdnr/odlux/odlux/apps/siteManagerApp/src/handlers/deviceTableHandler.ts create mode 100644 features/sdnr/odlux/odlux/apps/siteManagerApp/src/handlers/linkTableHandler.ts create mode 100644 features/sdnr/odlux/odlux/apps/siteManagerApp/src/handlers/siteManagerAppRootHandler.ts create mode 100644 features/sdnr/odlux/odlux/apps/siteManagerApp/src/handlers/siteManagerSiteSearchHandler.ts create mode 100644 features/sdnr/odlux/odlux/apps/siteManagerApp/src/handlers/siteManagerTreeHandler.ts create mode 100644 features/sdnr/odlux/odlux/apps/siteManagerApp/src/handlers/siteTableHandler.ts create mode 100644 features/sdnr/odlux/odlux/apps/siteManagerApp/src/handlers/sitedocManagementHandler.ts create mode 100644 features/sdnr/odlux/odlux/apps/siteManagerApp/src/index.html create mode 100644 features/sdnr/odlux/odlux/apps/siteManagerApp/src/models/count.ts create mode 100644 features/sdnr/odlux/odlux/apps/siteManagerApp/src/models/historyEntry.ts create mode 100644 features/sdnr/odlux/odlux/apps/siteManagerApp/src/models/link.ts create mode 100644 features/sdnr/odlux/odlux/apps/siteManagerApp/src/models/networkElementConnection.ts create mode 100644 features/sdnr/odlux/odlux/apps/siteManagerApp/src/models/panelId.ts create mode 100644 features/sdnr/odlux/odlux/apps/siteManagerApp/src/models/site.ts create mode 100644 features/sdnr/odlux/odlux/apps/siteManagerApp/src/models/siteDetails.ts create mode 100644 features/sdnr/odlux/odlux/apps/siteManagerApp/src/models/siteDocTypes.ts create mode 100644 features/sdnr/odlux/odlux/apps/siteManagerApp/src/models/siteManager.ts create mode 100644 features/sdnr/odlux/odlux/apps/siteManagerApp/src/models/siteSearch.ts create mode 100644 features/sdnr/odlux/odlux/apps/siteManagerApp/src/models/stadokSite.ts create mode 100644 features/sdnr/odlux/odlux/apps/siteManagerApp/src/models/topologyTypes.ts create mode 100644 features/sdnr/odlux/odlux/apps/siteManagerApp/src/pluginSiteManager.tsx create mode 100644 features/sdnr/odlux/odlux/apps/siteManagerApp/src/services/dataService.ts create mode 100644 features/sdnr/odlux/odlux/apps/siteManagerApp/src/services/mapService.ts create mode 100644 features/sdnr/odlux/odlux/apps/siteManagerApp/src/services/siteManagerService.ts create mode 100644 features/sdnr/odlux/odlux/apps/siteManagerApp/src/services/sitedocDataService.ts create mode 100644 features/sdnr/odlux/odlux/apps/siteManagerApp/src/views/OrderCreation.tsx create mode 100644 features/sdnr/odlux/odlux/apps/siteManagerApp/src/views/siteManager.tsx create mode 100644 features/sdnr/odlux/odlux/apps/siteManagerApp/tsconfig.json create mode 100644 features/sdnr/odlux/odlux/apps/siteManagerApp/webpack.config.js create mode 100644 features/sdnr/odlux/odlux/apps/unmFaultManagementApp/.babelrc create mode 100644 features/sdnr/odlux/odlux/apps/unmFaultManagementApp/package.json create mode 100644 features/sdnr/odlux/odlux/apps/unmFaultManagementApp/pom.xml create mode 100644 features/sdnr/odlux/odlux/apps/unmFaultManagementApp/src/actions/panelChangeActions.ts create mode 100644 features/sdnr/odlux/odlux/apps/unmFaultManagementApp/src/actions/unmFaultManagementAlarmStatusActions.ts create mode 100644 features/sdnr/odlux/odlux/apps/unmFaultManagementApp/src/assets/icons/unmFaultManagementAppIcon.svg create mode 100644 features/sdnr/odlux/odlux/apps/unmFaultManagementApp/src/components/refreshUnmFaultLogDialog.tsx create mode 100644 features/sdnr/odlux/odlux/apps/unmFaultManagementApp/src/components/unmFaultManagementAlarmDetailsDialog.tsx create mode 100644 features/sdnr/odlux/odlux/apps/unmFaultManagementApp/src/components/unmFaultManagementDashboard.tsx create mode 100644 features/sdnr/odlux/odlux/apps/unmFaultManagementApp/src/components/unmFaultManagementFaultLog.tsx create mode 100644 features/sdnr/odlux/odlux/apps/unmFaultManagementApp/src/handlers/unmFaultLogEntriesHandler.ts create mode 100644 features/sdnr/odlux/odlux/apps/unmFaultManagementApp/src/handlers/unmFaultManagementAlarmStatusHandler.ts create mode 100644 features/sdnr/odlux/odlux/apps/unmFaultManagementApp/src/handlers/unmFaultManagementAppRootHandler.ts create mode 100644 features/sdnr/odlux/odlux/apps/unmFaultManagementApp/src/index.html create mode 100644 features/sdnr/odlux/odlux/apps/unmFaultManagementApp/src/models/panelId.ts create mode 100644 features/sdnr/odlux/odlux/apps/unmFaultManagementApp/src/models/unmFault.ts create mode 100644 features/sdnr/odlux/odlux/apps/unmFaultManagementApp/src/pluginUnmFaultManagement.tsx create mode 100644 features/sdnr/odlux/odlux/apps/unmFaultManagementApp/src/services/unmFaultStatusService.ts create mode 100644 features/sdnr/odlux/odlux/apps/unmFaultManagementApp/src/views/unmFaultManagementApplication.tsx create mode 100644 features/sdnr/odlux/odlux/apps/unmFaultManagementApp/tsconfig.json create mode 100644 features/sdnr/odlux/odlux/apps/unmFaultManagementApp/webpack.config.js create mode 100644 features/sdnr/odlux/odlux/framework/.babelrc create mode 100644 features/sdnr/odlux/odlux/framework/LICENSE create mode 100644 features/sdnr/odlux/odlux/framework/package.json create mode 100644 features/sdnr/odlux/odlux/framework/pom.xml create mode 100644 features/sdnr/odlux/odlux/framework/src/actions/authentication.ts create mode 100644 features/sdnr/odlux/odlux/framework/src/actions/errorActions.ts create mode 100644 features/sdnr/odlux/odlux/framework/src/actions/loginProvider.ts create mode 100644 features/sdnr/odlux/odlux/framework/src/actions/menuAction.ts create mode 100644 features/sdnr/odlux/odlux/framework/src/actions/navigationActions.ts create mode 100644 features/sdnr/odlux/odlux/framework/src/actions/settingsAction.ts create mode 100644 features/sdnr/odlux/odlux/framework/src/actions/snackbarActions.ts create mode 100644 features/sdnr/odlux/odlux/framework/src/actions/titleActions.ts create mode 100644 features/sdnr/odlux/odlux/framework/src/actions/websocketAction.ts create mode 100644 features/sdnr/odlux/odlux/framework/src/app.css create mode 100644 features/sdnr/odlux/odlux/framework/src/app.tsx create mode 100644 features/sdnr/odlux/odlux/framework/src/assets/icons/About.svg create mode 100644 features/sdnr/odlux/odlux/framework/src/assets/icons/Home.svg create mode 100644 features/sdnr/odlux/odlux/framework/src/assets/icons/Menu.svg create mode 100644 features/sdnr/odlux/odlux/framework/src/assets/icons/Tools.svg create mode 100644 features/sdnr/odlux/odlux/framework/src/assets/icons/User.svg create mode 100644 features/sdnr/odlux/odlux/framework/src/assets/icons/ht.Connect.png create mode 100644 features/sdnr/odlux/odlux/framework/src/assets/icons/ht.Connect.svg create mode 100644 features/sdnr/odlux/odlux/framework/src/assets/images/defaultLogo.svg create mode 100644 features/sdnr/odlux/odlux/framework/src/assets/images/defaultLogo.svg.d.ts create mode 100644 features/sdnr/odlux/odlux/framework/src/assets/images/home.svg create mode 100644 features/sdnr/odlux/odlux/framework/src/assets/images/home.svg.d.ts create mode 100644 features/sdnr/odlux/odlux/framework/src/assets/images/odluxLogo.gif create mode 100644 features/sdnr/odlux/odlux/framework/src/assets/images/odluxLogo.gif.d.ts create mode 100644 features/sdnr/odlux/odlux/framework/src/assets/images/onapLogo.gif create mode 100644 features/sdnr/odlux/odlux/framework/src/assets/images/onapLogo.gif.d.ts create mode 100644 features/sdnr/odlux/odlux/framework/src/assets/version.json create mode 100644 features/sdnr/odlux/odlux/framework/src/common/event.ts create mode 100644 features/sdnr/odlux/odlux/framework/src/components/errorDisplay.tsx create mode 100644 features/sdnr/odlux/odlux/framework/src/components/icons/menuIcon.tsx create mode 100644 features/sdnr/odlux/odlux/framework/src/components/logo.tsx create mode 100644 features/sdnr/odlux/odlux/framework/src/components/material-table/columnModel.ts create mode 100644 features/sdnr/odlux/odlux/framework/src/components/material-table/index.tsx create mode 100644 features/sdnr/odlux/odlux/framework/src/components/material-table/showColumnDialog.tsx create mode 100644 features/sdnr/odlux/odlux/framework/src/components/material-table/tableFilter.tsx create mode 100644 features/sdnr/odlux/odlux/framework/src/components/material-table/tableHead.tsx create mode 100644 features/sdnr/odlux/odlux/framework/src/components/material-table/tableToolbar.tsx create mode 100644 features/sdnr/odlux/odlux/framework/src/components/material-table/utilities.ts create mode 100644 features/sdnr/odlux/odlux/framework/src/components/material-ui/index.ts create mode 100644 features/sdnr/odlux/odlux/framework/src/components/material-ui/listItemLink.tsx create mode 100644 features/sdnr/odlux/odlux/framework/src/components/material-ui/loader.tsx create mode 100644 features/sdnr/odlux/odlux/framework/src/components/material-ui/panel.tsx create mode 100644 features/sdnr/odlux/odlux/framework/src/components/material-ui/snackDisplay.tsx create mode 100644 features/sdnr/odlux/odlux/framework/src/components/material-ui/toggleButton.tsx create mode 100644 features/sdnr/odlux/odlux/framework/src/components/material-ui/toggleButtonGroup.tsx create mode 100644 features/sdnr/odlux/odlux/framework/src/components/material-ui/treeView.tsx create mode 100644 features/sdnr/odlux/odlux/framework/src/components/navigationMenu.tsx create mode 100644 features/sdnr/odlux/odlux/framework/src/components/objectDump/index.tsx create mode 100644 features/sdnr/odlux/odlux/framework/src/components/routing/appFrame.tsx create mode 100644 features/sdnr/odlux/odlux/framework/src/components/settings/general.tsx create mode 100644 features/sdnr/odlux/odlux/framework/src/components/titleBar.tsx create mode 100644 features/sdnr/odlux/odlux/framework/src/design/default.ts create mode 100644 features/sdnr/odlux/odlux/framework/src/favicon.ico create mode 100644 features/sdnr/odlux/odlux/framework/src/flux/action.ts create mode 100644 features/sdnr/odlux/odlux/framework/src/flux/connect.tsx create mode 100644 features/sdnr/odlux/odlux/framework/src/flux/middleware.ts create mode 100644 features/sdnr/odlux/odlux/framework/src/flux/store.ts create mode 100644 features/sdnr/odlux/odlux/framework/src/handlers/applicationRegistryHandler.ts create mode 100644 features/sdnr/odlux/odlux/framework/src/handlers/applicationStateHandler.ts create mode 100644 features/sdnr/odlux/odlux/framework/src/handlers/authenticationHandler.ts create mode 100644 features/sdnr/odlux/odlux/framework/src/handlers/navigationStateHandler.ts create mode 100644 features/sdnr/odlux/odlux/framework/src/index.dev.html create mode 100644 features/sdnr/odlux/odlux/framework/src/index.html create mode 100644 features/sdnr/odlux/odlux/framework/src/middleware/api.ts create mode 100644 features/sdnr/odlux/odlux/framework/src/middleware/logger.ts create mode 100644 features/sdnr/odlux/odlux/framework/src/middleware/navigation.ts create mode 100644 features/sdnr/odlux/odlux/framework/src/middleware/policies.ts create mode 100644 features/sdnr/odlux/odlux/framework/src/middleware/thunk.ts create mode 100644 features/sdnr/odlux/odlux/framework/src/models/applicationConfig.ts create mode 100644 features/sdnr/odlux/odlux/framework/src/models/applicationInfo.ts create mode 100644 features/sdnr/odlux/odlux/framework/src/models/authentication.ts create mode 100644 features/sdnr/odlux/odlux/framework/src/models/elasticSearch.ts create mode 100644 features/sdnr/odlux/odlux/framework/src/models/errorInfo.ts create mode 100644 features/sdnr/odlux/odlux/framework/src/models/externalLoginProvider.ts create mode 100644 features/sdnr/odlux/odlux/framework/src/models/iconDefinition.ts create mode 100644 features/sdnr/odlux/odlux/framework/src/models/index.ts create mode 100644 features/sdnr/odlux/odlux/framework/src/models/restService.ts create mode 100644 features/sdnr/odlux/odlux/framework/src/models/settings.ts create mode 100644 features/sdnr/odlux/odlux/framework/src/models/snackbarItem.ts create mode 100644 features/sdnr/odlux/odlux/framework/src/run.ts create mode 100644 features/sdnr/odlux/odlux/framework/src/services/applicationApi.ts create mode 100644 features/sdnr/odlux/odlux/framework/src/services/applicationManager.ts create mode 100644 features/sdnr/odlux/odlux/framework/src/services/authenticationService.ts create mode 100644 features/sdnr/odlux/odlux/framework/src/services/broadcastService.ts create mode 100644 features/sdnr/odlux/odlux/framework/src/services/index.ts create mode 100644 features/sdnr/odlux/odlux/framework/src/services/notificationService.ts create mode 100644 features/sdnr/odlux/odlux/framework/src/services/restAccessorService.ts create mode 100644 features/sdnr/odlux/odlux/framework/src/services/restService.ts create mode 100644 features/sdnr/odlux/odlux/framework/src/services/snackbarService.ts create mode 100644 features/sdnr/odlux/odlux/framework/src/services/storeService.ts create mode 100644 features/sdnr/odlux/odlux/framework/src/services/userSessionService.ts create mode 100644 features/sdnr/odlux/odlux/framework/src/services/userdataService.ts create mode 100644 features/sdnr/odlux/odlux/framework/src/store/applicationStore.ts create mode 100644 features/sdnr/odlux/odlux/framework/src/styles/att.ts create mode 100644 features/sdnr/odlux/odlux/framework/src/utilities/elasticSearch.ts create mode 100644 features/sdnr/odlux/odlux/framework/src/utilities/logLevel.ts create mode 100644 features/sdnr/odlux/odlux/framework/src/utilities/withComponents.ts create mode 100644 features/sdnr/odlux/odlux/framework/src/utilities/yangHelper.ts create mode 100644 features/sdnr/odlux/odlux/framework/src/views/about.tsx create mode 100644 features/sdnr/odlux/odlux/framework/src/views/frame.tsx create mode 100644 features/sdnr/odlux/odlux/framework/src/views/home.tsx create mode 100644 features/sdnr/odlux/odlux/framework/src/views/login.tsx create mode 100644 features/sdnr/odlux/odlux/framework/src/views/settings.tsx create mode 100644 features/sdnr/odlux/odlux/framework/src/views/test.tsx create mode 100644 features/sdnr/odlux/odlux/framework/src2/main/resources/version.json create mode 100644 features/sdnr/odlux/odlux/framework/tsconfig.json create mode 100644 features/sdnr/odlux/odlux/framework/webpack.config.js create mode 100644 features/sdnr/odlux/odlux/framework/webpack.runner.js create mode 100644 features/sdnr/odlux/odlux/framework/webpack.vendor.js create mode 100644 features/sdnr/odlux/odlux/installer/pom.xml create mode 100644 features/sdnr/odlux/odlux/installer/src/assembly/assemble_mvnrepo_zip.xml create mode 100644 features/sdnr/odlux/odlux/jest.json create mode 100644 features/sdnr/odlux/odlux/lerna.json create mode 100644 features/sdnr/odlux/odlux/lib/broadcast/mapChannel.ts create mode 100644 features/sdnr/odlux/odlux/odlux.properties create mode 100644 features/sdnr/odlux/odlux/package.json create mode 100644 features/sdnr/odlux/odlux/pom.xml create mode 100644 features/sdnr/odlux/odlux/proxy.conf.js create mode 100644 features/sdnr/odlux/odlux/test.txt create mode 100644 features/sdnr/odlux/odlux/tsconfig.json create mode 100644 features/sdnr/odlux/odlux/yarn.lock create mode 100644 features/sdnr/odlux/pom.xml create mode 100644 features/sdnr/odlux/readthedocs/README.md create mode 100755 features/sdnr/odlux/readthedocs/convert.sh create mode 100644 features/sdnr/odlux/readthedocs/pom.xml create mode 100644 features/sdnr/odlux/readthedocs/src/assembly/assemble_mvnrepo_zip.xml create mode 100755 features/sdnr/odlux/readthedocs/src/docs/conf.py create mode 100644 features/sdnr/odlux/readthedocs/src/docs/guides/onap-user/applications.rst create mode 100644 features/sdnr/odlux/readthedocs/src/docs/guides/onap-user/home.rst create mode 100644 features/sdnr/odlux/readthedocs/src/docs/guides/onap-user/installation.rst create mode 100644 features/sdnr/odlux/readthedocs/src/docs/guides/onap-user/sdnr_Docker_Image_configuration.rst create mode 100644 features/sdnr/odlux/readthedocs/src/docs/guides/onap-user/sdnr_WT_Service_Configuration_parameters.rst create mode 100644 features/sdnr/odlux/readthedocs/src/docs/index.rst create mode 100644 features/sdnr/odlux/readthedocs/src/docs/requirements.txt create mode 100644 features/sdnr/wt/README.md create mode 100755 features/sdnr/wt/common-yang/iana-crypt-hash/pom.xml create mode 100644 features/sdnr/wt/common-yang/iana-crypt-hash/src/main/yang/iana-crypt-hash@2014-08-06.yang create mode 100755 features/sdnr/wt/common-yang/ietf-alarms/pom.xml create mode 100644 features/sdnr/wt/common-yang/ietf-alarms/src/main/yang/ietf-alarms@2019-09-11.yang create mode 100755 features/sdnr/wt/common-yang/openroadm-pm-types/pom.xml create mode 100644 features/sdnr/wt/common-yang/openroadm-pm-types/src/main/java/org/opendaylight/yang/gen/v1/http/org/openroadm/pm/types/rev191129/PmDataTypeBuilder.java create mode 100644 features/sdnr/wt/common-yang/openroadm-pm-types/src/main/yang/org-openroadm-pm-types@2019-11-29.yang create mode 100755 features/sdnr/wt/common-yang/pom.xml create mode 100755 features/sdnr/wt/common-yang/rfc7317-ietf-system/pom.xml create mode 100644 features/sdnr/wt/common-yang/rfc7317-ietf-system/src/main/yang/ietf-system@2014-08-06.yang create mode 100755 features/sdnr/wt/common-yang/rfc8341/pom.xml create mode 100644 features/sdnr/wt/common-yang/rfc8341/src/main/yang/ietf-netconf-acm.yang create mode 100644 features/sdnr/wt/common-yang/test-yang/pom.xml create mode 100644 features/sdnr/wt/common-yang/test-yang/src/main/java/org/opendaylight/yang/gen/v1/urn/ietf/params/xml/ns/yang/ietf/inet/types/rev130715/HostBuilder.java create mode 100644 features/sdnr/wt/common-yang/test-yang/src/main/java/org/opendaylight/yang/gen/v1/urn/ietf/params/xml/ns/yang/ietf/inet/types/rev130715/IpAddressBuilder.java create mode 100644 features/sdnr/wt/common-yang/test-yang/src/main/java/org/opendaylight/yang/gen/v1/urn/ietf/params/xml/ns/yang/ietf/inet/types/rev130715/IpAddressNoZoneBuilder.java create mode 100644 features/sdnr/wt/common-yang/test-yang/src/main/java/org/opendaylight/yang/gen/v1/urn/ietf/params/xml/ns/yang/ietf/inet/types/rev130715/IpPrefixBuilder.java create mode 100644 features/sdnr/wt/common-yang/test-yang/src/main/yang/test-yang-utils.yang create mode 100644 features/sdnr/wt/common-yang/utils/pom.xml create mode 100644 features/sdnr/wt/common-yang/utils/src/main/java/org/onap/ccsdk/features/sdnr/wt/yang/mapper/YangToolsMapper.java create mode 100644 features/sdnr/wt/common-yang/utils/src/main/java/org/onap/ccsdk/features/sdnr/wt/yang/mapper/YangToolsMapperHelper.java create mode 100644 features/sdnr/wt/common-yang/utils/src/main/java/org/onap/ccsdk/features/sdnr/wt/yang/mapper/builder/DateAndTimeBuilder.java create mode 100644 features/sdnr/wt/common-yang/utils/src/main/java/org/onap/ccsdk/features/sdnr/wt/yang/mapper/mapperextensions/YangToolsBuilderAnnotationIntrospector.java create mode 100644 features/sdnr/wt/common-yang/utils/src/main/java/org/onap/ccsdk/features/sdnr/wt/yang/mapper/mapperextensions/YangToolsDeserializerModifier.java create mode 100644 features/sdnr/wt/common-yang/utils/src/main/java/org/onap/ccsdk/features/sdnr/wt/yang/mapper/mapperextensions/YangToolsModule.java create mode 100644 features/sdnr/wt/common-yang/utils/src/main/java/org/onap/ccsdk/features/sdnr/wt/yang/mapper/mapperextensions/YangtoolsMapDesirializer.java create mode 100644 features/sdnr/wt/common-yang/utils/src/main/java/org/onap/ccsdk/features/sdnr/wt/yang/mapper/serialize/BaseIdentityDeserializer.java create mode 100644 features/sdnr/wt/common-yang/utils/src/main/java/org/onap/ccsdk/features/sdnr/wt/yang/mapper/serialize/BaseIdentitySerializer.java create mode 100644 features/sdnr/wt/common-yang/utils/src/main/java/org/onap/ccsdk/features/sdnr/wt/yang/mapper/serialize/ClassDeserializer.java create mode 100644 features/sdnr/wt/common-yang/utils/src/main/java/org/onap/ccsdk/features/sdnr/wt/yang/mapper/serialize/ContainerNodeSerializer.java create mode 100644 features/sdnr/wt/common-yang/utils/src/main/java/org/onap/ccsdk/features/sdnr/wt/yang/mapper/serialize/DateAndTimeSerializer.java create mode 100644 features/sdnr/wt/common-yang/utils/src/main/java/org/onap/ccsdk/features/sdnr/wt/yang/mapper/serialize/EnumSerializer.java create mode 100644 features/sdnr/wt/common-yang/utils/src/main/java/org/onap/ccsdk/features/sdnr/wt/yang/mapper/serialize/IdentifierDeserializer.java create mode 100644 features/sdnr/wt/common-yang/utils/src/main/java/org/onap/ccsdk/features/sdnr/wt/yang/mapper/serialize/MapSerializer.java create mode 100644 features/sdnr/wt/common-yang/utils/src/main/java/org/onap/ccsdk/features/sdnr/wt/yang/mapper/serialize/SetDeserializer.java create mode 100644 features/sdnr/wt/common-yang/utils/src/main/java/org/onap/ccsdk/features/sdnr/wt/yang/mapper/serialize/TypeObjectDeserializer.java create mode 100644 features/sdnr/wt/common-yang/utils/src/main/java/org/onap/ccsdk/features/sdnr/wt/yang/mapper/serialize/TypeObjectSerializer.java create mode 100644 features/sdnr/wt/common-yang/utils/src/main/java/org/onap/ccsdk/features/sdnr/wt/yang/mapper/serialize/XMLNamespaceSerializer.java create mode 100644 features/sdnr/wt/common-yang/utils/src/test/java/org/onap/ccsdk/features/sdnr/wt/yang/mapper/TestHashMap.java create mode 100644 features/sdnr/wt/common-yang/utils/src/test/java/org/onap/ccsdk/features/sdnr/wt/yang/mapper/TestYangToolsMapper.java create mode 100644 features/sdnr/wt/common-yang/utils/src/test/yang/ietf-inet-types.yang create mode 100644 features/sdnr/wt/common-yang/utils/src/test/yang/test-yang-utils.yang create mode 100644 features/sdnr/wt/common/pom.xml create mode 100644 features/sdnr/wt/common/src/main/java/org/onap/ccsdk/features/sdnr/wt/common/HtAssert.java create mode 100644 features/sdnr/wt/common/src/main/java/org/onap/ccsdk/features/sdnr/wt/common/Resources.java create mode 100644 features/sdnr/wt/common/src/main/java/org/onap/ccsdk/features/sdnr/wt/common/YangHelper.java create mode 100644 features/sdnr/wt/common/src/main/java/org/onap/ccsdk/features/sdnr/wt/common/configuration/Configuration.java create mode 100644 features/sdnr/wt/common/src/main/java/org/onap/ccsdk/features/sdnr/wt/common/configuration/ConfigurationFileRepresentation.java create mode 100644 features/sdnr/wt/common/src/main/java/org/onap/ccsdk/features/sdnr/wt/common/configuration/ISubConfigHandler.java create mode 100644 features/sdnr/wt/common/src/main/java/org/onap/ccsdk/features/sdnr/wt/common/configuration/exception/ConversionException.java create mode 100644 features/sdnr/wt/common/src/main/java/org/onap/ccsdk/features/sdnr/wt/common/configuration/filechange/ConfigFileObserver.java create mode 100644 features/sdnr/wt/common/src/main/java/org/onap/ccsdk/features/sdnr/wt/common/configuration/filechange/IConfigChangedListener.java create mode 100644 features/sdnr/wt/common/src/main/java/org/onap/ccsdk/features/sdnr/wt/common/configuration/subtypes/Section.java create mode 100644 features/sdnr/wt/common/src/main/java/org/onap/ccsdk/features/sdnr/wt/common/configuration/subtypes/SectionValue.java create mode 100644 features/sdnr/wt/common/src/main/java/org/onap/ccsdk/features/sdnr/wt/common/database/Portstatus.java create mode 100644 features/sdnr/wt/common/src/main/java/org/onap/ccsdk/features/sdnr/wt/common/database/data/DatabaseVersion.java create mode 100644 features/sdnr/wt/common/src/main/java/org/onap/ccsdk/features/sdnr/wt/common/file/FileWatchdog.java create mode 100644 features/sdnr/wt/common/src/main/java/org/onap/ccsdk/features/sdnr/wt/common/file/PomFile.java create mode 100644 features/sdnr/wt/common/src/main/java/org/onap/ccsdk/features/sdnr/wt/common/file/PomPropertiesFile.java create mode 100644 features/sdnr/wt/common/src/main/java/org/onap/ccsdk/features/sdnr/wt/common/http/BaseHTTPClient.java create mode 100644 features/sdnr/wt/common/src/main/java/org/onap/ccsdk/features/sdnr/wt/common/http/BaseHTTPResponse.java create mode 100644 features/sdnr/wt/common/src/main/java/org/onap/ccsdk/features/sdnr/wt/common/test/JSONAssert.java create mode 100644 features/sdnr/wt/common/src/main/java/org/onap/ccsdk/features/sdnr/wt/common/test/ServletOutputStreamToByteArrayOutputStream.java create mode 100644 features/sdnr/wt/common/src/main/java/org/onap/ccsdk/features/sdnr/wt/common/test/ServletOutputStreamToStringWriter.java create mode 100644 features/sdnr/wt/common/src/main/java/org/onap/ccsdk/features/sdnr/wt/common/threading/GenericRunnableFactory.java create mode 100644 features/sdnr/wt/common/src/main/java/org/onap/ccsdk/features/sdnr/wt/common/threading/GenericRunnableFactoryCallback.java create mode 100644 features/sdnr/wt/common/src/main/java/org/onap/ccsdk/features/sdnr/wt/common/threading/KeyBasedThreadpool.java create mode 100644 features/sdnr/wt/common/src/main/java/org/onap/ccsdk/features/sdnr/wt/common/util/StackTrace.java create mode 100644 features/sdnr/wt/common/src/test/java/org/onap/ccsdk/features/sdnr/wt/common/test/TestBaseHttpClient.java create mode 100644 features/sdnr/wt/common/src/test/java/org/onap/ccsdk/features/sdnr/wt/common/test/TestConfig.java create mode 100644 features/sdnr/wt/common/src/test/java/org/onap/ccsdk/features/sdnr/wt/common/test/TestDatabaseVersion.java create mode 100644 features/sdnr/wt/common/src/test/java/org/onap/ccsdk/features/sdnr/wt/common/test/TestJsonAssert.java create mode 100644 features/sdnr/wt/common/src/test/java/org/onap/ccsdk/features/sdnr/wt/common/test/TestKeybasedThreadpool.java create mode 100644 features/sdnr/wt/common/src/test/java/org/onap/ccsdk/features/sdnr/wt/common/test/TestPomfile.java create mode 100644 features/sdnr/wt/common/src/test/java/org/onap/ccsdk/features/sdnr/wt/common/test/TestPortstatus.java create mode 100644 features/sdnr/wt/common/src/test/java/org/onap/ccsdk/features/sdnr/wt/common/test/TestResources.java create mode 100644 features/sdnr/wt/common/src/test/resources/log4j.properties create mode 100644 features/sdnr/wt/common/src/test/resources/log4j2.xml create mode 100644 features/sdnr/wt/common/src/test/resources/simplelogger.properties create mode 100644 features/sdnr/wt/common/src/test/resources/testpom.xml create mode 100644 features/sdnr/wt/data-provider/README.md create mode 100644 features/sdnr/wt/data-provider/dblib/pom.xml create mode 100644 features/sdnr/wt/data-provider/dblib/src/main/java/org/onap/ccsdk/features/sdnr/wt/dataprovider/database/sqldb/SqlDBClient.java create mode 100644 features/sdnr/wt/data-provider/dblib/src/main/java/org/onap/ccsdk/features/sdnr/wt/dataprovider/database/sqldb/SqlDBConfig.java create mode 100644 features/sdnr/wt/data-provider/dblib/src/main/java/org/onap/ccsdk/features/sdnr/wt/dataprovider/database/sqldb/data/HtUserdataManagerBase.java create mode 100644 features/sdnr/wt/data-provider/dblib/src/main/java/org/onap/ccsdk/features/sdnr/wt/dataprovider/database/sqldb/data/HtUserdataManagerImpl.java create mode 100644 features/sdnr/wt/data-provider/dblib/src/main/java/org/onap/ccsdk/features/sdnr/wt/dataprovider/database/sqldb/data/PropertyList.java create mode 100644 features/sdnr/wt/data-provider/dblib/src/main/java/org/onap/ccsdk/features/sdnr/wt/dataprovider/database/sqldb/data/SqlDBDataProvider.java create mode 100644 features/sdnr/wt/data-provider/dblib/src/main/java/org/onap/ccsdk/features/sdnr/wt/dataprovider/database/sqldb/data/SqlDbInventoryTreeProvider.java create mode 100644 features/sdnr/wt/data-provider/dblib/src/main/java/org/onap/ccsdk/features/sdnr/wt/dataprovider/database/sqldb/data/SqlPropertyInfo.java create mode 100644 features/sdnr/wt/data-provider/dblib/src/main/java/org/onap/ccsdk/features/sdnr/wt/dataprovider/database/sqldb/data/SqlTable.java create mode 100644 features/sdnr/wt/data-provider/dblib/src/main/java/org/onap/ccsdk/features/sdnr/wt/dataprovider/database/sqldb/data/SqlView.java create mode 100644 features/sdnr/wt/data-provider/dblib/src/main/java/org/onap/ccsdk/features/sdnr/wt/dataprovider/database/sqldb/data/Userdata.java create mode 100644 features/sdnr/wt/data-provider/dblib/src/main/java/org/onap/ccsdk/features/sdnr/wt/dataprovider/database/sqldb/data/UserdataBuilder.java create mode 100644 features/sdnr/wt/data-provider/dblib/src/main/java/org/onap/ccsdk/features/sdnr/wt/dataprovider/database/sqldb/data/entity/DatabaseIdGenerator.java create mode 100644 features/sdnr/wt/data-provider/dblib/src/main/java/org/onap/ccsdk/features/sdnr/wt/dataprovider/database/sqldb/data/entity/FaultEntityManager.java create mode 100644 features/sdnr/wt/data-provider/dblib/src/main/java/org/onap/ccsdk/features/sdnr/wt/dataprovider/database/sqldb/data/entity/HtDatabaseEventsService.java create mode 100644 features/sdnr/wt/data-provider/dblib/src/main/java/org/onap/ccsdk/features/sdnr/wt/dataprovider/database/sqldb/data/entity/HtDatabaseMaintenanceService.java create mode 100644 features/sdnr/wt/data-provider/dblib/src/main/java/org/onap/ccsdk/features/sdnr/wt/dataprovider/database/sqldb/data/rpctypehelper/QueryResult.java create mode 100644 features/sdnr/wt/data-provider/dblib/src/main/java/org/onap/ccsdk/features/sdnr/wt/dataprovider/database/sqldb/database/SqlDBMapper.java create mode 100644 features/sdnr/wt/data-provider/dblib/src/main/java/org/onap/ccsdk/features/sdnr/wt/dataprovider/database/sqldb/database/SqlDBReader.java create mode 100644 features/sdnr/wt/data-provider/dblib/src/main/java/org/onap/ccsdk/features/sdnr/wt/dataprovider/database/sqldb/database/SqlDBReaderWriter.java create mode 100644 features/sdnr/wt/data-provider/dblib/src/main/java/org/onap/ccsdk/features/sdnr/wt/dataprovider/database/sqldb/database/SqlDBReaderWriterFault.java create mode 100644 features/sdnr/wt/data-provider/dblib/src/main/java/org/onap/ccsdk/features/sdnr/wt/dataprovider/database/sqldb/database/SqlDBReaderWriterInventory.java create mode 100644 features/sdnr/wt/data-provider/dblib/src/main/java/org/onap/ccsdk/features/sdnr/wt/dataprovider/database/sqldb/database/SqlDBReaderWriterPm.java create mode 100644 features/sdnr/wt/data-provider/dblib/src/main/java/org/onap/ccsdk/features/sdnr/wt/dataprovider/database/sqldb/database/SqlDBReaderWriterUserdata.java create mode 100644 features/sdnr/wt/data-provider/dblib/src/main/java/org/onap/ccsdk/features/sdnr/wt/dataprovider/database/sqldb/database/SqlDBStatusReader.java create mode 100644 features/sdnr/wt/data-provider/dblib/src/main/java/org/onap/ccsdk/features/sdnr/wt/dataprovider/database/sqldb/query/CountQuery.java create mode 100644 features/sdnr/wt/data-provider/dblib/src/main/java/org/onap/ccsdk/features/sdnr/wt/dataprovider/database/sqldb/query/DeleteQuery.java create mode 100644 features/sdnr/wt/data-provider/dblib/src/main/java/org/onap/ccsdk/features/sdnr/wt/dataprovider/database/sqldb/query/InsertQuery.java create mode 100644 features/sdnr/wt/data-provider/dblib/src/main/java/org/onap/ccsdk/features/sdnr/wt/dataprovider/database/sqldb/query/SelectQuery.java create mode 100644 features/sdnr/wt/data-provider/dblib/src/main/java/org/onap/ccsdk/features/sdnr/wt/dataprovider/database/sqldb/query/SqlQuery.java create mode 100644 features/sdnr/wt/data-provider/dblib/src/main/java/org/onap/ccsdk/features/sdnr/wt/dataprovider/database/sqldb/query/UpdateQuery.java create mode 100644 features/sdnr/wt/data-provider/dblib/src/main/java/org/onap/ccsdk/features/sdnr/wt/dataprovider/database/sqldb/query/UpsertQuery.java create mode 100644 features/sdnr/wt/data-provider/dblib/src/main/java/org/onap/ccsdk/features/sdnr/wt/dataprovider/database/sqldb/query/filters/DBFilterKeyValuePair.java create mode 100644 features/sdnr/wt/data-provider/dblib/src/main/java/org/onap/ccsdk/features/sdnr/wt/dataprovider/database/sqldb/query/filters/DBKeyValuePair.java create mode 100644 features/sdnr/wt/data-provider/dblib/src/main/java/org/onap/ccsdk/features/sdnr/wt/dataprovider/database/sqldb/query/filters/RangeSqlDBFilter.java create mode 100644 features/sdnr/wt/data-provider/dblib/src/main/java/org/onap/ccsdk/features/sdnr/wt/dataprovider/database/sqldb/query/filters/RegexSqlDBFilter.java create mode 100644 features/sdnr/wt/data-provider/dblib/src/main/java/org/onap/ccsdk/features/sdnr/wt/dataprovider/database/sqldb/query/filters/SqlDBFilter.java create mode 100644 features/sdnr/wt/data-provider/dblib/src/main/java/org/onap/ccsdk/features/sdnr/wt/dataprovider/database/sqldb/query/filters/SqlDBSearchFilter.java create mode 100644 features/sdnr/wt/data-provider/dblib/src/test/java/org/onap/ccsdk/features/sdnr/wt/dataprovider/dblib/test/TestCRUDMariaDB.java create mode 100644 features/sdnr/wt/data-provider/dblib/src/test/java/org/onap/ccsdk/features/sdnr/wt/dataprovider/dblib/test/TestConfig.java create mode 100644 features/sdnr/wt/data-provider/dblib/src/test/java/org/onap/ccsdk/features/sdnr/wt/dataprovider/dblib/test/TestMariaDataProvider.java create mode 100644 features/sdnr/wt/data-provider/dblib/src/test/java/org/onap/ccsdk/features/sdnr/wt/dataprovider/dblib/test/TestObjectIds.java create mode 100644 features/sdnr/wt/data-provider/dblib/src/test/java/org/onap/ccsdk/features/sdnr/wt/dataprovider/dblib/test/TestQuerySyntax.java create mode 100644 features/sdnr/wt/data-provider/dblib/src/test/java/org/onap/ccsdk/features/sdnr/wt/dataprovider/dblib/test/util/MariaDBTestBase.java create mode 100644 features/sdnr/wt/data-provider/dblib/src/test/resources/UserdataBuilder.java create mode 100644 features/sdnr/wt/data-provider/dblib/src/test/resources/inventory.json create mode 100644 features/sdnr/wt/data-provider/dblib/src/test/resources/inventory2.json create mode 100644 features/sdnr/wt/data-provider/dblib/src/test/resources/pmdata15m.json create mode 100644 features/sdnr/wt/data-provider/dblib/src/test/resources/pmdata24h.json create mode 100644 features/sdnr/wt/data-provider/feature/pom.xml create mode 100755 features/sdnr/wt/data-provider/installer/pom.xml create mode 100644 features/sdnr/wt/data-provider/installer/src/assembly/assemble_mvnrepo_zip.xml create mode 100644 features/sdnr/wt/data-provider/model/pom.xml create mode 100644 features/sdnr/wt/data-provider/model/src/main/java/org/onap/ccsdk/features/sdnr/wt/dataprovider/model/ArchiveCleanProvider.java create mode 100644 features/sdnr/wt/data-provider/model/src/main/java/org/onap/ccsdk/features/sdnr/wt/dataprovider/model/BaseInventoryTreeProvider.java create mode 100644 features/sdnr/wt/data-provider/model/src/main/java/org/onap/ccsdk/features/sdnr/wt/dataprovider/model/DataInconsistencyException.java create mode 100644 features/sdnr/wt/data-provider/model/src/main/java/org/onap/ccsdk/features/sdnr/wt/dataprovider/model/DataProvider.java create mode 100644 features/sdnr/wt/data-provider/model/src/main/java/org/onap/ccsdk/features/sdnr/wt/dataprovider/model/DatabaseDataProvider.java create mode 100644 features/sdnr/wt/data-provider/model/src/main/java/org/onap/ccsdk/features/sdnr/wt/dataprovider/model/HtDatabaseMaintenance.java create mode 100644 features/sdnr/wt/data-provider/model/src/main/java/org/onap/ccsdk/features/sdnr/wt/dataprovider/model/HtDatabaseMediatorserver.java create mode 100644 features/sdnr/wt/data-provider/model/src/main/java/org/onap/ccsdk/features/sdnr/wt/dataprovider/model/HtUserdataManager.java create mode 100644 features/sdnr/wt/data-provider/model/src/main/java/org/onap/ccsdk/features/sdnr/wt/dataprovider/model/IEntityDataProvider.java create mode 100644 features/sdnr/wt/data-provider/model/src/main/java/org/onap/ccsdk/features/sdnr/wt/dataprovider/model/IEsConfig.java create mode 100644 features/sdnr/wt/data-provider/model/src/main/java/org/onap/ccsdk/features/sdnr/wt/dataprovider/model/InventoryTreeProvider.java create mode 100644 features/sdnr/wt/data-provider/model/src/main/java/org/onap/ccsdk/features/sdnr/wt/dataprovider/model/NetconfTimeStamp.java create mode 100644 features/sdnr/wt/data-provider/model/src/main/java/org/onap/ccsdk/features/sdnr/wt/dataprovider/model/SdnrDbType.java create mode 100644 features/sdnr/wt/data-provider/model/src/main/java/org/onap/ccsdk/features/sdnr/wt/dataprovider/model/StatusChangedHandler.java create mode 100644 features/sdnr/wt/data-provider/model/src/main/java/org/onap/ccsdk/features/sdnr/wt/dataprovider/model/types/DataTreeChildObject.java create mode 100644 features/sdnr/wt/data-provider/model/src/main/java/org/onap/ccsdk/features/sdnr/wt/dataprovider/model/types/DataTreeObject.java create mode 100644 features/sdnr/wt/data-provider/model/src/main/java/org/onap/ccsdk/features/sdnr/wt/dataprovider/model/types/NetconfTimeStampImpl.java create mode 100644 features/sdnr/wt/data-provider/model/src/main/java/org/onap/ccsdk/features/sdnr/wt/dataprovider/model/types/ScalarTypeObject.java create mode 100644 features/sdnr/wt/data-provider/model/src/main/java/org/onap/ccsdk/features/sdnr/wt/dataprovider/model/types/YangHelper.java create mode 100644 features/sdnr/wt/data-provider/model/src/main/yang/data-provider-g826-pm-types.yang create mode 100644 features/sdnr/wt/data-provider/model/src/main/yang/data-provider-openroadm-pm-types.yang create mode 100644 features/sdnr/wt/data-provider/model/src/main/yang/data-provider-units.yang create mode 100644 features/sdnr/wt/data-provider/model/src/main/yang/data-provider@2020-11-10.yang create mode 100755 features/sdnr/wt/data-provider/pom.xml create mode 100644 features/sdnr/wt/data-provider/provider/copyright create mode 100755 features/sdnr/wt/data-provider/provider/java.sh create mode 100644 features/sdnr/wt/data-provider/provider/pom.xml create mode 100644 features/sdnr/wt/data-provider/provider/simplelogger.properties create mode 100644 features/sdnr/wt/data-provider/provider/src/main/java/org/onap/ccsdk/features/sdnr/wt/dataprovider/database/nodb/NoDbDataProvider.java create mode 100644 features/sdnr/wt/data-provider/provider/src/main/java/org/onap/ccsdk/features/sdnr/wt/dataprovider/database/nodb/NoDbDatabaseDataProvider.java create mode 100644 features/sdnr/wt/data-provider/provider/src/main/java/org/onap/ccsdk/features/sdnr/wt/dataprovider/database/nodb/NoDbHtDatabaseMaintenance.java create mode 100644 features/sdnr/wt/data-provider/provider/src/main/java/org/onap/ccsdk/features/sdnr/wt/dataprovider/database/nodb/NoDbHtUserdataManager.java create mode 100644 features/sdnr/wt/data-provider/provider/src/main/java/org/onap/ccsdk/features/sdnr/wt/dataprovider/database/nodb/NoDbInventoryTreeProvider.java create mode 100644 features/sdnr/wt/data-provider/provider/src/main/java/org/onap/ccsdk/features/sdnr/wt/dataprovider/http/DataTreeHttpServlet.java create mode 100644 features/sdnr/wt/data-provider/provider/src/main/java/org/onap/ccsdk/features/sdnr/wt/dataprovider/http/UserdataHttpServlet.java create mode 100644 features/sdnr/wt/data-provider/provider/src/main/java/org/onap/ccsdk/features/sdnr/wt/dataprovider/http/about/AboutHttpServlet.java create mode 100644 features/sdnr/wt/data-provider/provider/src/main/java/org/onap/ccsdk/features/sdnr/wt/dataprovider/http/about/MarkdownTable.java create mode 100644 features/sdnr/wt/data-provider/provider/src/main/java/org/onap/ccsdk/features/sdnr/wt/dataprovider/http/about/ODLVersionLUT.java create mode 100644 features/sdnr/wt/data-provider/provider/src/main/java/org/onap/ccsdk/features/sdnr/wt/dataprovider/http/about/SystemInfo.java create mode 100644 features/sdnr/wt/data-provider/provider/src/main/java/org/onap/ccsdk/features/sdnr/wt/dataprovider/http/yangschema/GetYangSchemaRequest.java create mode 100644 features/sdnr/wt/data-provider/provider/src/main/java/org/onap/ccsdk/features/sdnr/wt/dataprovider/http/yangschema/YangFileProvider.java create mode 100644 features/sdnr/wt/data-provider/provider/src/main/java/org/onap/ccsdk/features/sdnr/wt/dataprovider/http/yangschema/YangFilename.java create mode 100644 features/sdnr/wt/data-provider/provider/src/main/java/org/onap/ccsdk/features/sdnr/wt/dataprovider/http/yangschema/YangSchemaHttpServlet.java create mode 100644 features/sdnr/wt/data-provider/provider/src/main/java/org/onap/ccsdk/features/sdnr/wt/dataprovider/impl/DataProviderConfig.java create mode 100644 features/sdnr/wt/data-provider/provider/src/main/java/org/onap/ccsdk/features/sdnr/wt/dataprovider/impl/DataProviderImpl.java create mode 100644 features/sdnr/wt/data-provider/provider/src/main/java/org/onap/ccsdk/features/sdnr/wt/dataprovider/impl/DataProviderService.java create mode 100644 features/sdnr/wt/data-provider/provider/src/main/java/org/onap/ccsdk/features/sdnr/wt/dataprovider/impl/DataProviderServiceImpl.java create mode 100644 features/sdnr/wt/data-provider/provider/src/main/java/org/onap/ccsdk/features/sdnr/wt/dataprovider/yangtools/DataProviderYangToolsMapper.java create mode 100644 features/sdnr/wt/data-provider/provider/src/main/resources/about/README.json create mode 100644 features/sdnr/wt/data-provider/provider/src/main/resources/about/README.md create mode 100644 features/sdnr/wt/data-provider/provider/src/main/resources/about/test.bmp create mode 100644 features/sdnr/wt/data-provider/provider/src/main/resources/org/opendaylight/blueprint/impl-blueprint.xml create mode 100644 features/sdnr/wt/data-provider/provider/src/test/java/org/onap/ccsdk/features/sdnr/wt/dataprovider/test/TestAbout.java create mode 100644 features/sdnr/wt/data-provider/provider/src/test/java/org/onap/ccsdk/features/sdnr/wt/dataprovider/test/TestDataMappings.java create mode 100644 features/sdnr/wt/data-provider/provider/src/test/java/org/onap/ccsdk/features/sdnr/wt/dataprovider/test/TestMapper.java create mode 100644 features/sdnr/wt/data-provider/provider/src/test/java/org/onap/ccsdk/features/sdnr/wt/dataprovider/test/TestNetconfNodeBuilder.java create mode 100644 features/sdnr/wt/data-provider/provider/src/test/java/org/onap/ccsdk/features/sdnr/wt/dataprovider/test/TestNetconfTimestamp.java create mode 100644 features/sdnr/wt/data-provider/provider/src/test/java/org/onap/ccsdk/features/sdnr/wt/dataprovider/test/TestNoDbDataProvider.java create mode 100644 features/sdnr/wt/data-provider/provider/src/test/java/org/onap/ccsdk/features/sdnr/wt/dataprovider/test/TestNuMappings.java create mode 100644 features/sdnr/wt/data-provider/provider/src/test/java/org/onap/ccsdk/features/sdnr/wt/dataprovider/test/TestYangCloning.java create mode 100644 features/sdnr/wt/data-provider/provider/src/test/java/org/onap/ccsdk/features/sdnr/wt/dataprovider/test/TestYangGenSalMappingOpenRoadm.java create mode 100644 features/sdnr/wt/data-provider/provider/src/test/java/org/onap/ccsdk/features/sdnr/wt/dataprovider/test/TestYangProvider.java create mode 100644 features/sdnr/wt/data-provider/provider/src/test/java/org/onap/ccsdk/features/sdnr/wt/dataprovider/test/issues/TestIssue227.java create mode 100644 features/sdnr/wt/data-provider/provider/src/test/java/org/onap/ccsdk/features/sdnr/wt/dataprovider/test/util/DataBrokerHelper.java create mode 100644 features/sdnr/wt/data-provider/provider/src/test/java/org/onap/ccsdk/features/sdnr/wt/dataprovider/test/util/MariaDBTestBase.java create mode 100644 features/sdnr/wt/data-provider/provider/src/test/resources/TestTree/test1.json create mode 100644 features/sdnr/wt/data-provider/provider/src/test/resources/TestTree/test2.json create mode 100644 features/sdnr/wt/data-provider/provider/src/test/resources/TestTree/test3.json create mode 100644 features/sdnr/wt/data-provider/provider/src/test/resources/TestYangGenSalMappingOpenRoadm/pmdata1.json create mode 100644 features/sdnr/wt/data-provider/provider/src/test/resources/TestYangGenSalMappingOpenRoadm/pmdata2.json create mode 100644 features/sdnr/wt/data-provider/provider/src/test/resources/TestYangGenSalMappingOpenRoadm/pmdata3.json create mode 100644 features/sdnr/wt/data-provider/provider/src/test/resources/log4j.properties create mode 100644 features/sdnr/wt/data-provider/provider/src/test/resources/test.properties create mode 100644 features/sdnr/wt/data-provider/provider/src/test/resources/tlskeys/keys1.json create mode 100644 features/sdnr/wt/data-provider/provider/src/test/resources/userdata/full.json create mode 100644 features/sdnr/wt/data-provider/provider/src/test/resources/userdata/merged.json create mode 100644 features/sdnr/wt/data-provider/provider/src/test/resources/userdata/networkmap.json create mode 100644 features/sdnr/wt/data-provider/setup/pom.xml create mode 100644 features/sdnr/wt/data-provider/setup/src/main/java/org/onap/ccsdk/features/sdnr/wt/dataprovider/setup/DataMigrationProviderImpl.java create mode 100644 features/sdnr/wt/data-provider/setup/src/main/java/org/onap/ccsdk/features/sdnr/wt/dataprovider/setup/DataMigrationProviderService.java create mode 100644 features/sdnr/wt/data-provider/setup/src/main/java/org/onap/ccsdk/features/sdnr/wt/dataprovider/setup/Program.java create mode 100644 features/sdnr/wt/data-provider/setup/src/main/java/org/onap/ccsdk/features/sdnr/wt/dataprovider/setup/ReleaseInformation.java create mode 100644 features/sdnr/wt/data-provider/setup/src/main/java/org/onap/ccsdk/features/sdnr/wt/dataprovider/setup/data/ComponentName.java create mode 100644 features/sdnr/wt/data-provider/setup/src/main/java/org/onap/ccsdk/features/sdnr/wt/dataprovider/setup/data/ConfigData.java create mode 100644 features/sdnr/wt/data-provider/setup/src/main/java/org/onap/ccsdk/features/sdnr/wt/dataprovider/setup/data/ConfigName.java create mode 100644 features/sdnr/wt/data-provider/setup/src/main/java/org/onap/ccsdk/features/sdnr/wt/dataprovider/setup/data/DataMigrationReport.java create mode 100644 features/sdnr/wt/data-provider/setup/src/main/java/org/onap/ccsdk/features/sdnr/wt/dataprovider/setup/data/DatabaseInfo.java create mode 100644 features/sdnr/wt/data-provider/setup/src/main/java/org/onap/ccsdk/features/sdnr/wt/dataprovider/setup/data/DatabaseInfo7.java create mode 100644 features/sdnr/wt/data-provider/setup/src/main/java/org/onap/ccsdk/features/sdnr/wt/dataprovider/setup/data/MariaDBTableInfo.java create mode 100644 features/sdnr/wt/data-provider/setup/src/main/java/org/onap/ccsdk/features/sdnr/wt/dataprovider/setup/data/MavenDatabasePluginInitFile.java create mode 100644 features/sdnr/wt/data-provider/setup/src/main/java/org/onap/ccsdk/features/sdnr/wt/dataprovider/setup/data/Release.java create mode 100644 features/sdnr/wt/data-provider/setup/src/main/java/org/onap/ccsdk/features/sdnr/wt/dataprovider/setup/data/ReleaseGroup.java create mode 100644 features/sdnr/wt/data-provider/setup/src/main/java/org/onap/ccsdk/features/sdnr/wt/dataprovider/setup/database/MariaDbDataMigrationProvider.java create mode 100644 features/sdnr/wt/data-provider/setup/src/main/java/org/onap/ccsdk/features/sdnr/wt/dataprovider/setup/releases/ElAltoReleaseInformation.java create mode 100644 features/sdnr/wt/data-provider/setup/src/main/java/org/onap/ccsdk/features/sdnr/wt/dataprovider/setup/releases/FrankfurtReleaseInformation.java create mode 100644 features/sdnr/wt/data-provider/setup/src/main/java/org/onap/ccsdk/features/sdnr/wt/dataprovider/setup/releases/FrankfurtReleaseInformationR2.java create mode 100644 features/sdnr/wt/data-provider/setup/src/main/java/org/onap/ccsdk/features/sdnr/wt/dataprovider/setup/releases/GuilinReleaseInformation.java create mode 100644 features/sdnr/wt/data-provider/setup/src/main/java/org/onap/ccsdk/features/sdnr/wt/dataprovider/setup/releases/HonoluluReleaseInformation.java create mode 100644 features/sdnr/wt/data-provider/setup/src/main/java/org/onap/ccsdk/features/sdnr/wt/dataprovider/setup/releases/IstanbulReleaseInformation.java create mode 100644 features/sdnr/wt/data-provider/setup/src/main/java/org/onap/ccsdk/features/sdnr/wt/dataprovider/setup/releases/JakartaReleaseInformation.java create mode 100644 features/sdnr/wt/data-provider/setup/src/test/java/org/onap/ccsdk/features/sdnr/wt/dataprovider/setup/MariaDBTestBase.java create mode 100644 features/sdnr/wt/data-provider/setup/src/test/java/org/onap/ccsdk/features/sdnr/wt/dataprovider/setup/TestBaseReleaseInformation.java create mode 100644 features/sdnr/wt/data-provider/setup/src/test/java/org/onap/ccsdk/features/sdnr/wt/dataprovider/setup/TestMariaDBJakarta.java create mode 100644 features/sdnr/wt/data-provider/setup/src/test/java/org/onap/ccsdk/features/sdnr/wt/dataprovider/setup/TestMariaDBMapper.java create mode 100644 features/sdnr/wt/data-provider/setup/src/test/resources/test.bak.json create mode 100644 features/sdnr/wt/data-provider/setup/src/test/resources/test2.bak.json create mode 100644 features/sdnr/wt/devicemanager-core/feature/pom.xml create mode 100755 features/sdnr/wt/devicemanager-core/installer/pom.xml create mode 100644 features/sdnr/wt/devicemanager-core/installer/src/assembly/assemble_mvnrepo_zip.xml create mode 100644 features/sdnr/wt/devicemanager-core/model/pom.xml create mode 100644 features/sdnr/wt/devicemanager-core/model/src/main/java/org/onap/ccsdk/features/sdnr/wt/devicemanager/ne/factory/DevicemanagerNature.java create mode 100644 features/sdnr/wt/devicemanager-core/model/src/main/java/org/onap/ccsdk/features/sdnr/wt/devicemanager/ne/factory/FactoryRegistration.java create mode 100644 features/sdnr/wt/devicemanager-core/model/src/main/java/org/onap/ccsdk/features/sdnr/wt/devicemanager/ne/factory/NetworkElementFactory.java create mode 100644 features/sdnr/wt/devicemanager-core/model/src/main/java/org/onap/ccsdk/features/sdnr/wt/devicemanager/ne/factory/NetworkElementFactory2.java create mode 100644 features/sdnr/wt/devicemanager-core/model/src/main/java/org/onap/ccsdk/features/sdnr/wt/devicemanager/ne/service/DeviceMonitoredNe.java create mode 100644 features/sdnr/wt/devicemanager-core/model/src/main/java/org/onap/ccsdk/features/sdnr/wt/devicemanager/ne/service/InventoryProvider.java create mode 100644 features/sdnr/wt/devicemanager-core/model/src/main/java/org/onap/ccsdk/features/sdnr/wt/devicemanager/ne/service/NetworkElement.java create mode 100644 features/sdnr/wt/devicemanager-core/model/src/main/java/org/onap/ccsdk/features/sdnr/wt/devicemanager/ne/service/NetworkElementService.java create mode 100644 features/sdnr/wt/devicemanager-core/model/src/main/java/org/onap/ccsdk/features/sdnr/wt/devicemanager/ne/service/PerformanceDataProvider.java create mode 100644 features/sdnr/wt/devicemanager-core/model/src/main/java/org/onap/ccsdk/features/sdnr/wt/devicemanager/service/AaiService.java create mode 100644 features/sdnr/wt/devicemanager-core/model/src/main/java/org/onap/ccsdk/features/sdnr/wt/devicemanager/service/DeviceManagerService.java create mode 100644 features/sdnr/wt/devicemanager-core/model/src/main/java/org/onap/ccsdk/features/sdnr/wt/devicemanager/service/DeviceManagerServiceProvider.java create mode 100644 features/sdnr/wt/devicemanager-core/model/src/main/java/org/onap/ccsdk/features/sdnr/wt/devicemanager/service/EquipmentService.java create mode 100644 features/sdnr/wt/devicemanager-core/model/src/main/java/org/onap/ccsdk/features/sdnr/wt/devicemanager/service/EventHandlingService.java create mode 100644 features/sdnr/wt/devicemanager-core/model/src/main/java/org/onap/ccsdk/features/sdnr/wt/devicemanager/service/FaultService.java create mode 100644 features/sdnr/wt/devicemanager-core/model/src/main/java/org/onap/ccsdk/features/sdnr/wt/devicemanager/service/MaintenanceService.java create mode 100644 features/sdnr/wt/devicemanager-core/model/src/main/java/org/onap/ccsdk/features/sdnr/wt/devicemanager/service/NetconfNetworkElementService.java create mode 100644 features/sdnr/wt/devicemanager-core/model/src/main/java/org/onap/ccsdk/features/sdnr/wt/devicemanager/service/NotificationProxyParser.java create mode 100644 features/sdnr/wt/devicemanager-core/model/src/main/java/org/onap/ccsdk/features/sdnr/wt/devicemanager/service/NotificationService.java create mode 100644 features/sdnr/wt/devicemanager-core/model/src/main/java/org/onap/ccsdk/features/sdnr/wt/devicemanager/service/PerformanceManager.java create mode 100644 features/sdnr/wt/devicemanager-core/model/src/main/java/org/onap/ccsdk/features/sdnr/wt/devicemanager/service/VESCollectorCfgService.java create mode 100644 features/sdnr/wt/devicemanager-core/model/src/main/java/org/onap/ccsdk/features/sdnr/wt/devicemanager/service/VESCollectorConfigChangeListener.java create mode 100644 features/sdnr/wt/devicemanager-core/model/src/main/java/org/onap/ccsdk/features/sdnr/wt/devicemanager/service/VESCollectorService.java create mode 100644 features/sdnr/wt/devicemanager-core/model/src/main/java/org/onap/ccsdk/features/sdnr/wt/devicemanager/types/EquipmentData.java create mode 100644 features/sdnr/wt/devicemanager-core/model/src/main/java/org/onap/ccsdk/features/sdnr/wt/devicemanager/types/EventlogNotificationBuilder.java create mode 100644 features/sdnr/wt/devicemanager-core/model/src/main/java/org/onap/ccsdk/features/sdnr/wt/devicemanager/types/FaultData.java create mode 100644 features/sdnr/wt/devicemanager-core/model/src/main/java/org/onap/ccsdk/features/sdnr/wt/devicemanager/types/FaultNotificationBuilder2.java create mode 100644 features/sdnr/wt/devicemanager-core/model/src/main/java/org/onap/ccsdk/features/sdnr/wt/devicemanager/types/InternalConnectionStatus.java create mode 100644 features/sdnr/wt/devicemanager-core/model/src/main/java/org/onap/ccsdk/features/sdnr/wt/devicemanager/types/InventoryInformationDcae.java create mode 100644 features/sdnr/wt/devicemanager-core/model/src/main/java/org/onap/ccsdk/features/sdnr/wt/devicemanager/types/PerformanceDataLtp.java create mode 100644 features/sdnr/wt/devicemanager-core/model/src/main/java/org/onap/ccsdk/features/sdnr/wt/devicemanager/types/VESCommonEventHeaderPOJO.java create mode 100644 features/sdnr/wt/devicemanager-core/model/src/main/java/org/onap/ccsdk/features/sdnr/wt/devicemanager/types/VESFaultFieldsPOJO.java create mode 100644 features/sdnr/wt/devicemanager-core/model/src/main/java/org/onap/ccsdk/features/sdnr/wt/devicemanager/types/VESMessage.java create mode 100644 features/sdnr/wt/devicemanager-core/model/src/main/java/org/onap/ccsdk/features/sdnr/wt/devicemanager/types/VESNotificationFieldsPOJO.java create mode 100644 features/sdnr/wt/devicemanager-core/model/src/main/java/org/onap/ccsdk/features/sdnr/wt/devicemanager/types/VESPNFRegistrationFieldsPOJO.java create mode 100644 features/sdnr/wt/devicemanager-core/model/src/main/java/org/onap/ccsdk/features/sdnr/wt/devicemanager/types/VESStndDefinedFieldsPOJO.java create mode 100644 features/sdnr/wt/devicemanager-core/model/src/main/java/org/onap/ccsdk/features/sdnr/wt/devicemanager/util/InconsistentPMDataException.java create mode 100644 features/sdnr/wt/devicemanager-core/model/src/main/java/org/onap/ccsdk/features/sdnr/wt/devicemanager/util/PmUtil.java create mode 100644 features/sdnr/wt/devicemanager-core/model/src/main/java/org/onap/ccsdk/features/sdnr/wt/devicemanager/util/UnkownDevicemanagerServiceException.java create mode 100644 features/sdnr/wt/devicemanager-core/model/src/main/yang/devicemanager.yang create mode 100755 features/sdnr/wt/devicemanager-core/pom.xml create mode 100644 features/sdnr/wt/devicemanager-core/provider/copyright create mode 100644 features/sdnr/wt/devicemanager-core/provider/pom.xml create mode 100644 features/sdnr/wt/devicemanager-core/provider/src/main/java/org/onap/ccsdk/features/sdnr/wt/devicemanager/aaiconnector/impl/AaiProviderClient.java create mode 100644 features/sdnr/wt/devicemanager-core/provider/src/main/java/org/onap/ccsdk/features/sdnr/wt/devicemanager/aaiconnector/impl/AaiWebApiClient.java create mode 100644 features/sdnr/wt/devicemanager-core/provider/src/main/java/org/onap/ccsdk/features/sdnr/wt/devicemanager/aaiconnector/impl/URLParamEncoder.java create mode 100644 features/sdnr/wt/devicemanager-core/provider/src/main/java/org/onap/ccsdk/features/sdnr/wt/devicemanager/aaiconnector/impl/config/AaiClientPropertiesFile.java create mode 100644 features/sdnr/wt/devicemanager-core/provider/src/main/java/org/onap/ccsdk/features/sdnr/wt/devicemanager/aaiconnector/impl/config/AaiConfig.java create mode 100644 features/sdnr/wt/devicemanager-core/provider/src/main/java/org/onap/ccsdk/features/sdnr/wt/devicemanager/dcaeconnector/impl/DcaeForwarderImpl.java create mode 100644 features/sdnr/wt/devicemanager-core/provider/src/main/java/org/onap/ccsdk/features/sdnr/wt/devicemanager/dcaeconnector/impl/DcaeForwarderInternal.java create mode 100644 features/sdnr/wt/devicemanager-core/provider/src/main/java/org/onap/ccsdk/features/sdnr/wt/devicemanager/dcaeconnector/impl/DcaeMessages.java create mode 100644 features/sdnr/wt/devicemanager-core/provider/src/main/java/org/onap/ccsdk/features/sdnr/wt/devicemanager/dcaeconnector/impl/DcaeProviderClient.java create mode 100644 features/sdnr/wt/devicemanager-core/provider/src/main/java/org/onap/ccsdk/features/sdnr/wt/devicemanager/dcaeconnector/impl/DcaeProviderTask.java create mode 100644 features/sdnr/wt/devicemanager-core/provider/src/main/java/org/onap/ccsdk/features/sdnr/wt/devicemanager/dcaeconnector/impl/DcaeProviderWorker.java create mode 100644 features/sdnr/wt/devicemanager-core/provider/src/main/java/org/onap/ccsdk/features/sdnr/wt/devicemanager/dcaeconnector/impl/DcaeSender.java create mode 100644 features/sdnr/wt/devicemanager-core/provider/src/main/java/org/onap/ccsdk/features/sdnr/wt/devicemanager/dcaeconnector/impl/DcaeSenderImpl.java create mode 100644 features/sdnr/wt/devicemanager-core/provider/src/main/java/org/onap/ccsdk/features/sdnr/wt/devicemanager/dcaeconnector/impl/config/DcaeConfig.java create mode 100644 features/sdnr/wt/devicemanager-core/provider/src/main/java/org/onap/ccsdk/features/sdnr/wt/devicemanager/devicemonitor/impl/Checker.java create mode 100644 features/sdnr/wt/devicemanager-core/provider/src/main/java/org/onap/ccsdk/features/sdnr/wt/devicemanager/devicemonitor/impl/DeviceMonitor.java create mode 100644 features/sdnr/wt/devicemanager-core/provider/src/main/java/org/onap/ccsdk/features/sdnr/wt/devicemanager/devicemonitor/impl/DeviceMonitorEmptyImpl.java create mode 100644 features/sdnr/wt/devicemanager-core/provider/src/main/java/org/onap/ccsdk/features/sdnr/wt/devicemanager/devicemonitor/impl/DeviceMonitorImpl.java create mode 100644 features/sdnr/wt/devicemanager-core/provider/src/main/java/org/onap/ccsdk/features/sdnr/wt/devicemanager/devicemonitor/impl/DeviceMonitorProblems.java create mode 100644 features/sdnr/wt/devicemanager-core/provider/src/main/java/org/onap/ccsdk/features/sdnr/wt/devicemanager/devicemonitor/impl/DeviceMonitorTask.java create mode 100644 features/sdnr/wt/devicemanager-core/provider/src/main/java/org/onap/ccsdk/features/sdnr/wt/devicemanager/devicemonitor/impl/config/DmConfig.java create mode 100644 features/sdnr/wt/devicemanager-core/provider/src/main/java/org/onap/ccsdk/features/sdnr/wt/devicemanager/eventdatahandler/DeviceManagerDatabaseNotificationService.java create mode 100644 features/sdnr/wt/devicemanager-core/provider/src/main/java/org/onap/ccsdk/features/sdnr/wt/devicemanager/eventdatahandler/ODLEventListenerHandler.java create mode 100644 features/sdnr/wt/devicemanager-core/provider/src/main/java/org/onap/ccsdk/features/sdnr/wt/devicemanager/eventdatahandler/RpcPushNotificationsHandler.java create mode 100644 features/sdnr/wt/devicemanager-core/provider/src/main/java/org/onap/ccsdk/features/sdnr/wt/devicemanager/housekeeping/ConnectionStatusHousekeepingService.java create mode 100644 features/sdnr/wt/devicemanager-core/provider/src/main/java/org/onap/ccsdk/features/sdnr/wt/devicemanager/housekeeping/HouseKeepingConfig.java create mode 100644 features/sdnr/wt/devicemanager-core/provider/src/main/java/org/onap/ccsdk/features/sdnr/wt/devicemanager/housekeeping/ResyncNetworkElementHouskeepingService.java create mode 100644 features/sdnr/wt/devicemanager-core/provider/src/main/java/org/onap/ccsdk/features/sdnr/wt/devicemanager/housekeeping/ResyncNetworkElementsListener.java create mode 100644 features/sdnr/wt/devicemanager-core/provider/src/main/java/org/onap/ccsdk/features/sdnr/wt/devicemanager/impl/DeviceManagerApiServiceImpl.java create mode 100644 features/sdnr/wt/devicemanager-core/provider/src/main/java/org/onap/ccsdk/features/sdnr/wt/devicemanager/impl/DeviceManagerImpl.java create mode 100644 features/sdnr/wt/devicemanager-core/provider/src/main/java/org/onap/ccsdk/features/sdnr/wt/devicemanager/impl/DeviceManagerNetconfConnectHandler.java create mode 100644 features/sdnr/wt/devicemanager-core/provider/src/main/java/org/onap/ccsdk/features/sdnr/wt/devicemanager/impl/DeviceManagerNetconfNotConnectHandler.java create mode 100644 features/sdnr/wt/devicemanager-core/provider/src/main/java/org/onap/ccsdk/features/sdnr/wt/devicemanager/impl/NetconfNodeService.java create mode 100644 features/sdnr/wt/devicemanager-core/provider/src/main/java/org/onap/ccsdk/features/sdnr/wt/devicemanager/impl/ProviderClient.java create mode 100644 features/sdnr/wt/devicemanager-core/provider/src/main/java/org/onap/ccsdk/features/sdnr/wt/devicemanager/impl/PushNotifications.java create mode 100644 features/sdnr/wt/devicemanager-core/provider/src/main/java/org/onap/ccsdk/features/sdnr/wt/devicemanager/impl/util/InternalDateAndTime.java create mode 100644 features/sdnr/wt/devicemanager-core/provider/src/main/java/org/onap/ccsdk/features/sdnr/wt/devicemanager/impl/util/InternalSeverity.java create mode 100644 features/sdnr/wt/devicemanager-core/provider/src/main/java/org/onap/ccsdk/features/sdnr/wt/devicemanager/impl/util/NetworkElementConnectionEntitiyUtil.java create mode 100644 features/sdnr/wt/devicemanager-core/provider/src/main/java/org/onap/ccsdk/features/sdnr/wt/devicemanager/impl/util/NotificationProxyParserImpl.java create mode 100644 features/sdnr/wt/devicemanager-core/provider/src/main/java/org/onap/ccsdk/features/sdnr/wt/devicemanager/impl/util/OdlClusterSingleton.java create mode 100644 features/sdnr/wt/devicemanager-core/provider/src/main/java/org/onap/ccsdk/features/sdnr/wt/devicemanager/impl/xml/FaultEntityManager.java create mode 100644 features/sdnr/wt/devicemanager-core/provider/src/main/java/org/onap/ccsdk/features/sdnr/wt/devicemanager/impl/xml/GetEventType.java create mode 100644 features/sdnr/wt/devicemanager-core/provider/src/main/java/org/onap/ccsdk/features/sdnr/wt/devicemanager/impl/xml/MwtNotificationBase.java create mode 100644 features/sdnr/wt/devicemanager-core/provider/src/main/java/org/onap/ccsdk/features/sdnr/wt/devicemanager/impl/xml/ProblemNotificationXml.java create mode 100644 features/sdnr/wt/devicemanager-core/provider/src/main/java/org/onap/ccsdk/features/sdnr/wt/devicemanager/impl/xml/WebSocketServiceClientImpl.java create mode 100644 features/sdnr/wt/devicemanager-core/provider/src/main/java/org/onap/ccsdk/features/sdnr/wt/devicemanager/impl/xml/WebSocketServiceClientInternal.java create mode 100644 features/sdnr/wt/devicemanager-core/provider/src/main/java/org/onap/ccsdk/features/sdnr/wt/devicemanager/maintenance/MaintenanceRPCServiceAPI.java create mode 100644 features/sdnr/wt/devicemanager-core/provider/src/main/java/org/onap/ccsdk/features/sdnr/wt/devicemanager/maintenance/impl/MaintenanceCalculator.java create mode 100644 features/sdnr/wt/devicemanager-core/provider/src/main/java/org/onap/ccsdk/features/sdnr/wt/devicemanager/maintenance/impl/MaintenanceServiceImpl.java create mode 100644 features/sdnr/wt/devicemanager-core/provider/src/main/java/org/onap/ccsdk/features/sdnr/wt/devicemanager/performancemanager/impl/PerformanceManagerImpl.java create mode 100644 features/sdnr/wt/devicemanager-core/provider/src/main/java/org/onap/ccsdk/features/sdnr/wt/devicemanager/performancemanager/impl/PerformanceManagerTask.java create mode 100644 features/sdnr/wt/devicemanager-core/provider/src/main/java/org/onap/ccsdk/features/sdnr/wt/devicemanager/performancemanager/impl/config/PmConfig.java create mode 100644 features/sdnr/wt/devicemanager-core/provider/src/main/java/org/onap/ccsdk/features/sdnr/wt/devicemanager/toggleAlarmFilter/DevicemanagerNotificationDelayService.java create mode 100644 features/sdnr/wt/devicemanager-core/provider/src/main/java/org/onap/ccsdk/features/sdnr/wt/devicemanager/toggleAlarmFilter/NotificationDelayFilter.java create mode 100644 features/sdnr/wt/devicemanager-core/provider/src/main/java/org/onap/ccsdk/features/sdnr/wt/devicemanager/toggleAlarmFilter/NotificationDelayService.java create mode 100644 features/sdnr/wt/devicemanager-core/provider/src/main/java/org/onap/ccsdk/features/sdnr/wt/devicemanager/toggleAlarmFilter/NotificationDelayedListener.java create mode 100644 features/sdnr/wt/devicemanager-core/provider/src/main/java/org/onap/ccsdk/features/sdnr/wt/devicemanager/toggleAlarmFilter/NotificationWithServerTimeStamp.java create mode 100644 features/sdnr/wt/devicemanager-core/provider/src/main/java/org/onap/ccsdk/features/sdnr/wt/devicemanager/toggleAlarmFilter/ToggleAlarmFilterable.java create mode 100644 features/sdnr/wt/devicemanager-core/provider/src/main/java/org/onap/ccsdk/features/sdnr/wt/devicemanager/toggleAlarmFilter/conf/ToggleAlarmConfig.java create mode 100644 features/sdnr/wt/devicemanager-core/provider/src/main/java/org/onap/ccsdk/features/sdnr/wt/devicemanager/vescollectorconnector/impl/VESCollectorServiceImpl.java create mode 100644 features/sdnr/wt/devicemanager-core/provider/src/main/java/org/onap/ccsdk/features/sdnr/wt/devicemanager/vescollectorconnector/impl/config/VESCollectorCfgImpl.java create mode 100644 features/sdnr/wt/devicemanager-core/provider/src/main/resources/org/opendaylight/blueprint/impl-blueprint.xml create mode 100644 features/sdnr/wt/devicemanager-core/provider/src/main/resources/preload.cache.schema/bbf-tr-196-2-0-3-full@2018-04-08.yang create mode 100644 features/sdnr/wt/devicemanager-core/provider/src/main/resources/preload.cache.schema/core-model@2017-03-20.yang create mode 100644 features/sdnr/wt/devicemanager-core/provider/src/main/resources/preload.cache.schema/g.874.1-model@2017-03-20.yang create mode 100644 features/sdnr/wt/devicemanager-core/provider/src/main/resources/preload.cache.schema/iana-crypt-hash@2014-08-06.yang create mode 100644 features/sdnr/wt/devicemanager-core/provider/src/main/resources/preload.cache.schema/ietf-inet-types@2010-09-24.yang create mode 100644 features/sdnr/wt/devicemanager-core/provider/src/main/resources/preload.cache.schema/ietf-inet-types@2013-07-15.yang create mode 100644 features/sdnr/wt/devicemanager-core/provider/src/main/resources/preload.cache.schema/ietf-netconf-acm@2012-02-22.yang create mode 100644 features/sdnr/wt/devicemanager-core/provider/src/main/resources/preload.cache.schema/ietf-netconf-monitoring@2010-10-04.yang create mode 100644 features/sdnr/wt/devicemanager-core/provider/src/main/resources/preload.cache.schema/ietf-netconf-partial-lock@2009-10-19.yang create mode 100644 features/sdnr/wt/devicemanager-core/provider/src/main/resources/preload.cache.schema/ietf-netconf-with-defaults@2011-06-01.yang create mode 100644 features/sdnr/wt/devicemanager-core/provider/src/main/resources/preload.cache.schema/ietf-netconf@2011-06-01.yang create mode 100644 features/sdnr/wt/devicemanager-core/provider/src/main/resources/preload.cache.schema/ietf-ptp-dataset@2017-02-08.yang create mode 100644 features/sdnr/wt/devicemanager-core/provider/src/main/resources/preload.cache.schema/ietf-restconf@2013-10-19.yang create mode 100644 features/sdnr/wt/devicemanager-core/provider/src/main/resources/preload.cache.schema/ietf-system@2014-08-06.yang create mode 100644 features/sdnr/wt/devicemanager-core/provider/src/main/resources/preload.cache.schema/ietf-yang-library@2016-04-09.yang create mode 100644 features/sdnr/wt/devicemanager-core/provider/src/main/resources/preload.cache.schema/ietf-yang-types@2013-07-15.yang create mode 100644 features/sdnr/wt/devicemanager-core/provider/src/main/resources/preload.cache.schema/microwave-model@2017-03-24.yang create mode 100644 features/sdnr/wt/devicemanager-core/provider/src/main/resources/preload.cache.schema/microwave-model@2018-09-07.yang create mode 100644 features/sdnr/wt/devicemanager-core/provider/src/main/resources/preload.cache.schema/microwave-model@2018-10-10.yang create mode 100644 features/sdnr/wt/devicemanager-core/provider/src/main/resources/preload.cache.schema/nc-notifications@2008-07-14.yang create mode 100644 features/sdnr/wt/devicemanager-core/provider/src/main/resources/preload.cache.schema/notifications@2008-07-14.yang create mode 100644 features/sdnr/wt/devicemanager-core/provider/src/main/resources/preload.cache.schema/notifications@2018-05-30.yang create mode 100644 features/sdnr/wt/devicemanager-core/provider/src/main/resources/preload.cache.schema/onf-core-model-conditional-packages@2017-04-02.yang create mode 100644 features/sdnr/wt/devicemanager-core/provider/src/main/resources/preload.cache.schema/onf-ethernet-conditional-packages@2017-04-02.yang create mode 100644 features/sdnr/wt/devicemanager-core/provider/src/main/resources/preload.cache.schema/onf-otn-odu-conditional-packages@2017-10-20.yang create mode 100644 features/sdnr/wt/devicemanager-core/provider/src/main/resources/preload.cache.schema/onf-ptp-dataset@2017-05-08.yang create mode 100644 features/sdnr/wt/devicemanager-core/provider/src/main/resources/preload.cache.schema/photonic-media@2018-09-24.yang create mode 100644 features/sdnr/wt/devicemanager-core/provider/src/main/resources/preload.cache.schema/tapi-common@2018-08-31.yang create mode 100644 features/sdnr/wt/devicemanager-core/provider/src/main/resources/preload.cache.schema/tapi-connectivity@2018-08-31.yang create mode 100644 features/sdnr/wt/devicemanager-core/provider/src/main/resources/preload.cache.schema/tapi-dsr@2018-08-31.yang create mode 100644 features/sdnr/wt/devicemanager-core/provider/src/main/resources/preload.cache.schema/tapi-eth@2018-08-31.yang create mode 100644 features/sdnr/wt/devicemanager-core/provider/src/main/resources/preload.cache.schema/tapi-notification@2018-08-31.yang create mode 100644 features/sdnr/wt/devicemanager-core/provider/src/main/resources/preload.cache.schema/tapi-oam@2018-08-31.yang create mode 100644 features/sdnr/wt/devicemanager-core/provider/src/main/resources/preload.cache.schema/tapi-odu@2018-08-31.yang create mode 100644 features/sdnr/wt/devicemanager-core/provider/src/main/resources/preload.cache.schema/tapi-path-computation@2018-08-31.yang create mode 100644 features/sdnr/wt/devicemanager-core/provider/src/main/resources/preload.cache.schema/tapi-photonic-media@2018-08-31.yang create mode 100644 features/sdnr/wt/devicemanager-core/provider/src/main/resources/preload.cache.schema/tapi-topology@2018-08-31.yang create mode 100644 features/sdnr/wt/devicemanager-core/provider/src/main/resources/preload.cache.schema/tapi-virtual-network@2018-08-31.yang create mode 100644 features/sdnr/wt/devicemanager-core/provider/src/main/resources/version.properties create mode 100644 features/sdnr/wt/devicemanager-core/provider/src/test/java/org/onap/ccsdk/features/sdnr/wt/devicemanager/test/TestAai.java create mode 100644 features/sdnr/wt/devicemanager-core/provider/src/test/java/org/onap/ccsdk/features/sdnr/wt/devicemanager/test/TestDcae.java create mode 100644 features/sdnr/wt/devicemanager-core/provider/src/test/java/org/onap/ccsdk/features/sdnr/wt/devicemanager/test/TestDevMgrPropertiesFile.java create mode 100644 features/sdnr/wt/devicemanager-core/provider/src/test/java/org/onap/ccsdk/features/sdnr/wt/devicemanager/test/TestDeviceMonitor.java create mode 100644 features/sdnr/wt/devicemanager-core/provider/src/test/java/org/onap/ccsdk/features/sdnr/wt/devicemanager/test/TestDevicemanager.java create mode 100644 features/sdnr/wt/devicemanager-core/provider/src/test/java/org/onap/ccsdk/features/sdnr/wt/devicemanager/test/TestMaintenanceTimeFilter.java create mode 100644 features/sdnr/wt/devicemanager-core/provider/src/test/java/org/onap/ccsdk/features/sdnr/wt/devicemanager/test/TestNameSpace.java create mode 100644 features/sdnr/wt/devicemanager-core/provider/src/test/java/org/onap/ccsdk/features/sdnr/wt/devicemanager/test/TestToggleAlarmFilter.java create mode 100644 features/sdnr/wt/devicemanager-core/provider/src/test/java/org/onap/ccsdk/features/sdnr/wt/devicemanager/test/TestVESCollectorClient.java create mode 100644 features/sdnr/wt/devicemanager-core/provider/src/test/java/org/onap/ccsdk/features/sdnr/wt/devicemanager/test/TestsNectconfDateTime.java create mode 100644 features/sdnr/wt/devicemanager-core/provider/src/test/java/org/onap/ccsdk/features/sdnr/wt/devicemanager/test/mock/RpcProviderServiceMock.java create mode 100644 features/sdnr/wt/devicemanager-core/provider/src/test/java/org/onap/ccsdk/features/sdnr/wt/devicemanager/test/util/NetconfTimeStampOld.java create mode 100644 features/sdnr/wt/devicemanager-core/provider/src/test/resources/aaiclient.properties create mode 100644 features/sdnr/wt/devicemanager-core/provider/src/test/resources/captured-akka.conf create mode 100644 features/sdnr/wt/devicemanager-core/provider/src/test/resources/mediator-server.json create mode 100644 features/sdnr/wt/devicemanager-core/provider/src/test/resources/simplelogger.properties create mode 100644 features/sdnr/wt/devicemanager-core/provider/src/test/resources/test.properties create mode 100644 features/sdnr/wt/devicemanager-o-ran-sc/o-ran/ru-fh/feature/pom.xml create mode 100755 features/sdnr/wt/devicemanager-o-ran-sc/o-ran/ru-fh/installer/pom.xml create mode 100644 features/sdnr/wt/devicemanager-o-ran-sc/o-ran/ru-fh/installer/src/assembly/assemble_mvnrepo_zip.xml create mode 100644 features/sdnr/wt/devicemanager-o-ran-sc/o-ran/ru-fh/model/pom.xml create mode 100644 features/sdnr/wt/devicemanager-o-ran-sc/o-ran/ru-fh/model/src/main/yang/devicemanager-oran.yang create mode 100755 features/sdnr/wt/devicemanager-o-ran-sc/o-ran/ru-fh/pom.xml create mode 100644 features/sdnr/wt/devicemanager-o-ran-sc/o-ran/ru-fh/provider/copyright create mode 100644 features/sdnr/wt/devicemanager-o-ran-sc/o-ran/ru-fh/provider/pom.xml create mode 100644 features/sdnr/wt/devicemanager-o-ran-sc/o-ran/ru-fh/provider/src/main/java/org/onap/ccsdk/features/sdnr/wt/devicemanager/oran/config/ORanDMConfig.java create mode 100644 features/sdnr/wt/devicemanager-o-ran-sc/o-ran/ru-fh/provider/src/main/java/org/onap/ccsdk/features/sdnr/wt/devicemanager/oran/dataprovider/ORanDOMToInternalDataModel.java create mode 100644 features/sdnr/wt/devicemanager-o-ran-sc/o-ran/ru-fh/provider/src/main/java/org/onap/ccsdk/features/sdnr/wt/devicemanager/oran/impl/dom/DeviceManagerORanImpl.java create mode 100644 features/sdnr/wt/devicemanager-o-ran-sc/o-ran/ru-fh/provider/src/main/java/org/onap/ccsdk/features/sdnr/wt/devicemanager/oran/impl/dom/ORanDOMNetworkElement.java create mode 100644 features/sdnr/wt/devicemanager-o-ran-sc/o-ran/ru-fh/provider/src/main/java/org/onap/ccsdk/features/sdnr/wt/devicemanager/oran/impl/dom/ORanNetworkElementFactory.java create mode 100644 features/sdnr/wt/devicemanager-o-ran-sc/o-ran/ru-fh/provider/src/main/java/org/onap/ccsdk/features/sdnr/wt/devicemanager/oran/notification/ORanDOMChangeNotificationListener.java create mode 100644 features/sdnr/wt/devicemanager-o-ran-sc/o-ran/ru-fh/provider/src/main/java/org/onap/ccsdk/features/sdnr/wt/devicemanager/oran/notification/ORanDOMFaultNotificationListener.java create mode 100644 features/sdnr/wt/devicemanager-o-ran-sc/o-ran/ru-fh/provider/src/main/java/org/onap/ccsdk/features/sdnr/wt/devicemanager/oran/notification/ORanDOMNotifToVESEventAssembly.java create mode 100644 features/sdnr/wt/devicemanager-o-ran-sc/o-ran/ru-fh/provider/src/main/java/org/onap/ccsdk/features/sdnr/wt/devicemanager/oran/notification/ORanDOMNotificationToXPath.java create mode 100644 features/sdnr/wt/devicemanager-o-ran-sc/o-ran/ru-fh/provider/src/main/java/org/onap/ccsdk/features/sdnr/wt/devicemanager/oran/notification/ORanDOMSupervisionNotificationListener.java create mode 100644 features/sdnr/wt/devicemanager-o-ran-sc/o-ran/ru-fh/provider/src/main/java/org/onap/ccsdk/features/sdnr/wt/devicemanager/oran/notification/ORanNotificationObserver.java create mode 100644 features/sdnr/wt/devicemanager-o-ran-sc/o-ran/ru-fh/provider/src/main/java/org/onap/ccsdk/features/sdnr/wt/devicemanager/oran/notification/ORanNotificationObserverImpl.java create mode 100644 features/sdnr/wt/devicemanager-o-ran-sc/o-ran/ru-fh/provider/src/main/java/org/onap/ccsdk/features/sdnr/wt/devicemanager/oran/notification/ORanNotificationReceivedService.java create mode 100644 features/sdnr/wt/devicemanager-o-ran-sc/o-ran/ru-fh/provider/src/main/java/org/onap/ccsdk/features/sdnr/wt/devicemanager/oran/rpc/ORanSupervisionRPCImpl.java create mode 100644 features/sdnr/wt/devicemanager-o-ran-sc/o-ran/ru-fh/provider/src/main/java/org/onap/ccsdk/features/sdnr/wt/devicemanager/oran/util/ORanDMDOMUtility.java create mode 100644 features/sdnr/wt/devicemanager-o-ran-sc/o-ran/ru-fh/provider/src/main/java/org/onap/ccsdk/features/sdnr/wt/devicemanager/oran/util/ORanDeviceManagerQNames.java create mode 100644 features/sdnr/wt/devicemanager-o-ran-sc/o-ran/ru-fh/provider/src/main/java/org/onap/ccsdk/features/sdnr/wt/devicemanager/oran/vesmapper/ORanDOMFaultToVESFaultMapper.java create mode 100644 features/sdnr/wt/devicemanager-o-ran-sc/o-ran/ru-fh/provider/src/main/java/org/onap/ccsdk/features/sdnr/wt/devicemanager/oran/vesmapper/ORanDOMSupervisionNotifToVESMapper.java create mode 100644 features/sdnr/wt/devicemanager-o-ran-sc/o-ran/ru-fh/provider/src/main/java/org/onap/ccsdk/features/sdnr/wt/devicemanager/oran/vesmapper/ORanRegistrationToVESpnfRegistrationMapper.java create mode 100644 features/sdnr/wt/devicemanager-o-ran-sc/o-ran/ru-fh/provider/src/main/java/org/onap/ccsdk/features/sdnr/wt/devicemanager/oran/yangspecs/ORANFM.java create mode 100644 features/sdnr/wt/devicemanager-o-ran-sc/o-ran/ru-fh/provider/src/main/java/org/onap/ccsdk/features/sdnr/wt/devicemanager/oran/yangspecs/OnapSystem.java create mode 100644 features/sdnr/wt/devicemanager-o-ran-sc/o-ran/ru-fh/provider/src/main/java/org/onap/ccsdk/features/sdnr/wt/devicemanager/oran/yangspecs/YangModule.java create mode 100644 features/sdnr/wt/devicemanager-o-ran-sc/o-ran/ru-fh/provider/src/main/resources/org/opendaylight/blueprint/impl-blueprint.xml create mode 100644 features/sdnr/wt/devicemanager-o-ran-sc/o-ran/ru-fh/provider/src/main/resources/version.properties create mode 100755 features/sdnr/wt/devicemanager-o-ran-sc/o-ran/ru-fh/provider/src/main/yang/iana-hardware.yang create mode 100644 features/sdnr/wt/devicemanager-o-ran-sc/o-ran/ru-fh/provider/src/main/yang/ietf-alarms@2019-09-11.yang create mode 100755 features/sdnr/wt/devicemanager-o-ran-sc/o-ran/ru-fh/provider/src/main/yang/ietf-hardware.yang create mode 100644 features/sdnr/wt/devicemanager-o-ran-sc/o-ran/ru-fh/provider/src/main/yang/ietf-interfaces.yang create mode 100644 features/sdnr/wt/devicemanager-o-ran-sc/o-ran/ru-fh/provider/src/main/yang/o-ran-ald-port@2019-07-03.yang create mode 100644 features/sdnr/wt/devicemanager-o-ran-sc/o-ran/ru-fh/provider/src/main/yang/o-ran-fm.yang create mode 100644 features/sdnr/wt/devicemanager-o-ran-sc/o-ran/ru-fh/provider/src/main/yang/o-ran-hardware.yang create mode 100644 features/sdnr/wt/devicemanager-o-ran-sc/o-ran/ru-fh/provider/src/main/yang/o-ran-hardware@2019-07-03.yang create mode 100644 features/sdnr/wt/devicemanager-o-ran-sc/o-ran/ru-fh/provider/src/main/yang/o-ran-sc-common-alarms-v1.yang create mode 100644 features/sdnr/wt/devicemanager-o-ran-sc/o-ran/ru-fh/provider/src/main/yang/o-ran-sc-cu-cp-alarms-v1.yang create mode 100644 features/sdnr/wt/devicemanager-o-ran-sc/o-ran/ru-fh/provider/src/main/yang/o-ran-sc-cu-up-alarms-v1.yang create mode 100644 features/sdnr/wt/devicemanager-o-ran-sc/o-ran/ru-fh/provider/src/main/yang/o-ran-sc-du-alarms-v1.yang create mode 100644 features/sdnr/wt/devicemanager-o-ran-sc/o-ran/ru-fh/provider/src/main/yang/o-ran-sc-ric-alarms-v1.yang create mode 100644 features/sdnr/wt/devicemanager-o-ran-sc/o-ran/ru-fh/provider/src/main/yang/o-ran-sc-ru-alarms-v1.yang create mode 100644 features/sdnr/wt/devicemanager-o-ran-sc/o-ran/ru-fh/provider/src/test/java/org/onap/ccsdk/features/sdnr/wt/devicemanager/oran/impl/dom/TestDeviceManagerORanImpl.java create mode 100644 features/sdnr/wt/devicemanager-o-ran-sc/o-ran/ru-fh/provider/src/test/java/org/onap/ccsdk/features/sdnr/wt/devicemanager/oran/impl/dom/TestORanDOMFaultNotificationListener.java create mode 100644 features/sdnr/wt/devicemanager-o-ran-sc/o-ran/ru-fh/provider/src/test/java/org/onap/ccsdk/features/sdnr/wt/devicemanager/oran/impl/dom/TestORanDOMNetworkElement.java create mode 100644 features/sdnr/wt/devicemanager-o-ran-sc/o-ran/ru-fh/provider/src/test/java/org/onap/ccsdk/features/sdnr/wt/devicemanager/oran/impl/dom/TestORanDOMNotification.java create mode 100644 features/sdnr/wt/devicemanager-o-ran-sc/o-ran/ru-fh/provider/src/test/java/org/onap/ccsdk/features/sdnr/wt/devicemanager/oran/impl/dom/TestORanDOMToInternalDataModel.java create mode 100644 features/sdnr/wt/devicemanager-o-ran-sc/o-ran/ru-fh/provider/src/test/java/org/onap/ccsdk/features/sdnr/wt/devicemanager/oran/impl/dom/TestORanNetworkElementFactory.java create mode 100644 features/sdnr/wt/devicemanager-o-ran-sc/o-ran/ru-fh/provider/src/test/java/org/onap/ccsdk/features/sdnr/wt/devicemanager/oran/impl/dom/TestORanRegistrationToVESpnfRegistration.java create mode 100644 features/sdnr/wt/devicemanager-o-ran-sc/o-ran/ru-fh/provider/src/test/java/org/onap/ccsdk/features/sdnr/wt/devicemanager/oran/impl/dom/util/TestYangParserUtil.java create mode 100644 features/sdnr/wt/devicemanager-o-ran-sc/o-ran/ru-fh/provider/src/test/resources/Device-ietf-hardware-Output.json create mode 100644 features/sdnr/wt/devicemanager-o-ran-sc/o-ran/ru-fh/provider/src/test/resources/iana-crypt-hash@2014-08-06.yang create mode 100755 features/sdnr/wt/devicemanager-o-ran-sc/o-ran/ru-fh/provider/src/test/resources/iana-hardware.yang create mode 100644 features/sdnr/wt/devicemanager-o-ran-sc/o-ran/ru-fh/provider/src/test/resources/ietf-hardware.xml create mode 100755 features/sdnr/wt/devicemanager-o-ran-sc/o-ran/ru-fh/provider/src/test/resources/ietf-hardware.yang create mode 100644 features/sdnr/wt/devicemanager-o-ran-sc/o-ran/ru-fh/provider/src/test/resources/ietf-inet-types.yang create mode 100644 features/sdnr/wt/devicemanager-o-ran-sc/o-ran/ru-fh/provider/src/test/resources/ietf-netconf-acm.yang create mode 100644 features/sdnr/wt/devicemanager-o-ran-sc/o-ran/ru-fh/provider/src/test/resources/ietf-system@2014-08-06.yang create mode 100644 features/sdnr/wt/devicemanager-o-ran-sc/o-ran/ru-fh/provider/src/test/resources/ietf-yang-types.yang create mode 100644 features/sdnr/wt/devicemanager-o-ran-sc/o-ran/ru-fh/provider/src/test/resources/o-ran-fm@2022-08-15.yang create mode 100644 features/sdnr/wt/devicemanager-o-ran-sc/o-ran/ru-fh/provider/src/test/resources/o-ran-hardware@2019-07-03.yang create mode 100644 features/sdnr/wt/devicemanager-o-ran-sc/o-ran/ru-fh/provider/src/test/resources/onap-system.xml create mode 100644 features/sdnr/wt/devicemanager-o-ran-sc/o-ran/ru-fh/provider/src/test/resources/onap-system.yang create mode 100644 features/sdnr/wt/devicemanager-o-ran-sc/o-ran/ru-fh/provider/src/test/resources/oran-fm-active-alarm.xml create mode 100644 features/sdnr/wt/devicemanager-o-ran-sc/o-ran/ru-fh/provider/src/test/resources/simplelogger.properties create mode 100644 features/sdnr/wt/devicemanager-o-ran-sc/pom.xml create mode 100644 features/sdnr/wt/featureaggregator/feature-devicemanager-base/pom.xml create mode 100644 features/sdnr/wt/featureaggregator/feature-devicemanager/pom.xml create mode 100644 features/sdnr/wt/featureaggregator/feature-oauth/pom.xml create mode 100644 features/sdnr/wt/featureaggregator/feature/pom.xml create mode 100755 features/sdnr/wt/featureaggregator/installer/pom.xml create mode 100755 features/sdnr/wt/featureaggregator/installer/src/assembly/assemble_mvnrepo_zip.xml create mode 100755 features/sdnr/wt/featureaggregator/pom.xml create mode 100644 features/sdnr/wt/mountpoint-registrar/feature/pom.xml create mode 100755 features/sdnr/wt/mountpoint-registrar/installer/pom.xml create mode 100644 features/sdnr/wt/mountpoint-registrar/installer/src/assembly/assemble_mvnrepo_zip.xml create mode 100644 features/sdnr/wt/mountpoint-registrar/model/pom.xml create mode 100644 features/sdnr/wt/mountpoint-registrar/model/src/main/yang/mountpoint-registrar.yang create mode 100755 features/sdnr/wt/mountpoint-registrar/pom.xml create mode 100644 features/sdnr/wt/mountpoint-registrar/provider/copyright create mode 100644 features/sdnr/wt/mountpoint-registrar/provider/pom.xml create mode 100644 features/sdnr/wt/mountpoint-registrar/provider/src/main/java/org/onap/ccsdk/features/sdnr/wt/mountpointregistrar/config/FaultConfig.java create mode 100644 features/sdnr/wt/mountpoint-registrar/provider/src/main/java/org/onap/ccsdk/features/sdnr/wt/mountpointregistrar/config/GeneralConfig.java create mode 100644 features/sdnr/wt/mountpoint-registrar/provider/src/main/java/org/onap/ccsdk/features/sdnr/wt/mountpointregistrar/config/MessageConfig.java create mode 100644 features/sdnr/wt/mountpoint-registrar/provider/src/main/java/org/onap/ccsdk/features/sdnr/wt/mountpointregistrar/config/PNFRegistrationConfig.java create mode 100644 features/sdnr/wt/mountpoint-registrar/provider/src/main/java/org/onap/ccsdk/features/sdnr/wt/mountpointregistrar/config/ProvisioningConfig.java create mode 100644 features/sdnr/wt/mountpoint-registrar/provider/src/main/java/org/onap/ccsdk/features/sdnr/wt/mountpointregistrar/config/StndDefinedFaultConfig.java create mode 100644 features/sdnr/wt/mountpoint-registrar/provider/src/main/java/org/onap/ccsdk/features/sdnr/wt/mountpointregistrar/config/StrimziKafkaConfig.java create mode 100644 features/sdnr/wt/mountpoint-registrar/provider/src/main/java/org/onap/ccsdk/features/sdnr/wt/mountpointregistrar/impl/InvalidMessageException.java create mode 100644 features/sdnr/wt/mountpoint-registrar/provider/src/main/java/org/onap/ccsdk/features/sdnr/wt/mountpointregistrar/impl/MessageClient.java create mode 100644 features/sdnr/wt/mountpoint-registrar/provider/src/main/java/org/onap/ccsdk/features/sdnr/wt/mountpointregistrar/impl/MountpointRegistrarImpl.java create mode 100644 features/sdnr/wt/mountpoint-registrar/provider/src/main/java/org/onap/ccsdk/features/sdnr/wt/mountpointregistrar/impl/StrimziKafkaVESMsgConsumer.java create mode 100644 features/sdnr/wt/mountpoint-registrar/provider/src/main/java/org/onap/ccsdk/features/sdnr/wt/mountpointregistrar/impl/StrimziKafkaVESMsgConsumerImpl.java create mode 100644 features/sdnr/wt/mountpoint-registrar/provider/src/main/java/org/onap/ccsdk/features/sdnr/wt/mountpointregistrar/impl/StrimziKafkaVESMsgConsumerMain.java create mode 100644 features/sdnr/wt/mountpoint-registrar/provider/src/main/java/org/onap/ccsdk/features/sdnr/wt/mountpointregistrar/impl/StrimziKafkaVESMsgValidator.java create mode 100644 features/sdnr/wt/mountpoint-registrar/provider/src/main/java/org/onap/ccsdk/features/sdnr/wt/mountpointregistrar/kafka/VESMsgKafkaConsumer.java create mode 100644 features/sdnr/wt/mountpoint-registrar/provider/src/main/java/org/onap/ccsdk/features/sdnr/wt/mountpointregistrar/vesdomain/cm/CMBasicHeaderFieldsNotification.java create mode 100644 features/sdnr/wt/mountpoint-registrar/provider/src/main/java/org/onap/ccsdk/features/sdnr/wt/mountpointregistrar/vesdomain/cm/CMNotification.java create mode 100644 features/sdnr/wt/mountpoint-registrar/provider/src/main/java/org/onap/ccsdk/features/sdnr/wt/mountpointregistrar/vesdomain/cm/CMNotificationClient.java create mode 100644 features/sdnr/wt/mountpoint-registrar/provider/src/main/java/org/onap/ccsdk/features/sdnr/wt/mountpointregistrar/vesdomain/cm/StrimziKafkaCMVESMsgConsumer.java create mode 100644 features/sdnr/wt/mountpoint-registrar/provider/src/main/java/org/onap/ccsdk/features/sdnr/wt/mountpointregistrar/vesdomain/fault/FaultNotificationClient.java create mode 100644 features/sdnr/wt/mountpoint-registrar/provider/src/main/java/org/onap/ccsdk/features/sdnr/wt/mountpointregistrar/vesdomain/fault/StrimziKafkaFaultVESMsgConsumer.java create mode 100644 features/sdnr/wt/mountpoint-registrar/provider/src/main/java/org/onap/ccsdk/features/sdnr/wt/mountpointregistrar/vesdomain/pnfreg/PNFMountPointClient.java create mode 100644 features/sdnr/wt/mountpoint-registrar/provider/src/main/java/org/onap/ccsdk/features/sdnr/wt/mountpointregistrar/vesdomain/pnfreg/StrimziKafkaPNFRegVESMsgConsumer.java create mode 100644 features/sdnr/wt/mountpoint-registrar/provider/src/main/java/org/onap/ccsdk/features/sdnr/wt/mountpointregistrar/vesdomain/stnddefined/StrimziKafkaStndDefinedFaultVESMsgConsumer.java create mode 100644 features/sdnr/wt/mountpoint-registrar/provider/src/main/resources/org/opendaylight/blueprint/impl-blueprint.xml create mode 100644 features/sdnr/wt/mountpoint-registrar/provider/src/main/resources/version.properties create mode 100644 features/sdnr/wt/mountpoint-registrar/provider/src/test/java/org/onap/ccsdk/features/sdnr/wt/mountpointregistrar/test/TestMapping.java create mode 100644 features/sdnr/wt/mountpoint-registrar/provider/src/test/java/org/onap/ccsdk/features/sdnr/wt/mountpointregistrar/test/TestMountpointRegistrarImpl.java create mode 100644 features/sdnr/wt/mountpoint-registrar/provider/src/test/java/org/onap/ccsdk/features/sdnr/wt/mountpointregistrar/test/client/TestCMBasicHeaderFieldsNotification.java create mode 100644 features/sdnr/wt/mountpoint-registrar/provider/src/test/java/org/onap/ccsdk/features/sdnr/wt/mountpointregistrar/test/client/TestCMNotificationBuilder.java create mode 100644 features/sdnr/wt/mountpoint-registrar/provider/src/test/java/org/onap/ccsdk/features/sdnr/wt/mountpointregistrar/test/client/TestCMNotificationClient.java create mode 100644 features/sdnr/wt/mountpoint-registrar/provider/src/test/java/org/onap/ccsdk/features/sdnr/wt/mountpointregistrar/test/client/TestFaultNotificationClient.java create mode 100644 features/sdnr/wt/mountpoint-registrar/provider/src/test/java/org/onap/ccsdk/features/sdnr/wt/mountpointregistrar/test/client/TestPNFMountPointClient.java create mode 100644 features/sdnr/wt/mountpoint-registrar/provider/src/test/java/org/onap/ccsdk/features/sdnr/wt/mountpointregistrar/test/config/GeneralConfigForTest.java create mode 100644 features/sdnr/wt/mountpoint-registrar/provider/src/test/java/org/onap/ccsdk/features/sdnr/wt/mountpointregistrar/test/config/PNFRegistrationConfigTest.java create mode 100644 features/sdnr/wt/mountpoint-registrar/provider/src/test/java/org/onap/ccsdk/features/sdnr/wt/mountpointregistrar/test/config/TestFaultConfig.java create mode 100644 features/sdnr/wt/mountpoint-registrar/provider/src/test/java/org/onap/ccsdk/features/sdnr/wt/mountpointregistrar/test/config/TestGeneralConfig.java create mode 100644 features/sdnr/wt/mountpoint-registrar/provider/src/test/java/org/onap/ccsdk/features/sdnr/wt/mountpointregistrar/test/config/TestProvisioningConfig.java create mode 100644 features/sdnr/wt/mountpoint-registrar/provider/src/test/java/org/onap/ccsdk/features/sdnr/wt/mountpointregistrar/test/config/TestStrimziKafkaConfig.java create mode 100644 features/sdnr/wt/mountpoint-registrar/provider/src/test/java/org/onap/ccsdk/features/sdnr/wt/mountpointregistrar/test/consumer/TestStrimziKafkaCMVESMsgConsumer.java create mode 100644 features/sdnr/wt/mountpoint-registrar/provider/src/test/java/org/onap/ccsdk/features/sdnr/wt/mountpointregistrar/test/consumer/TestStrimziKafkaFaultVESMsgConsumer.java create mode 100644 features/sdnr/wt/mountpoint-registrar/provider/src/test/java/org/onap/ccsdk/features/sdnr/wt/mountpointregistrar/test/consumer/TestStrimziKafkaPNFRegVESMsgConsumer.java create mode 100644 features/sdnr/wt/mountpoint-registrar/provider/src/test/java/org/onap/ccsdk/features/sdnr/wt/mountpointregistrar/test/consumer/TestStrimziKafkaStndDefinedVESMsgConsumer.java create mode 100644 features/sdnr/wt/mountpoint-registrar/provider/src/test/java/org/onap/ccsdk/features/sdnr/wt/mountpointregistrar/test/consumer/TestStrimziKafkaVESMsgConsumerMain.java create mode 100644 features/sdnr/wt/mountpoint-registrar/provider/src/test/java/org/onap/ccsdk/features/sdnr/wt/mountpointregistrar/test/mock/odlapi/ClusterSingletonServiceProviderMock.java create mode 100644 features/sdnr/wt/mountpoint-registrar/provider/src/test/resources/msgs/cm_invalid.json create mode 100644 features/sdnr/wt/mountpoint-registrar/provider/src/test/resources/msgs/cm_invalid_type.json create mode 100644 features/sdnr/wt/mountpoint-registrar/provider/src/test/resources/msgs/cm_moi_attribute_value_changes.json create mode 100644 features/sdnr/wt/mountpoint-registrar/provider/src/test/resources/msgs/cm_moi_creation.json create mode 100644 features/sdnr/wt/mountpoint-registrar/provider/src/test/resources/msgs/cm_moi_deletion.json create mode 100644 features/sdnr/wt/mountpoint-registrar/provider/src/test/resources/msgs/cm_valid.json create mode 100644 features/sdnr/wt/mountpoint-registrar/provider/src/test/resources/msgs/cm_valid_two_element_moi_changes_array.json create mode 100644 features/sdnr/wt/mountpoint-registrar/provider/src/test/resources/msgs/not_a_json.json create mode 100644 features/sdnr/wt/mountpoint-registrar/provider/src/test/resources/simplelogger.properties create mode 100644 features/sdnr/wt/mountpoint-state-provider/feature/pom.xml create mode 100755 features/sdnr/wt/mountpoint-state-provider/installer/pom.xml create mode 100644 features/sdnr/wt/mountpoint-state-provider/installer/src/assembly/assemble_mvnrepo_zip.xml create mode 100755 features/sdnr/wt/mountpoint-state-provider/pom.xml create mode 100644 features/sdnr/wt/mountpoint-state-provider/provider/copyright create mode 100644 features/sdnr/wt/mountpoint-state-provider/provider/pom.xml create mode 100644 features/sdnr/wt/mountpoint-state-provider/provider/src/main/java/org/onap/ccsdk/features/sdnr/wt/mountpointstateprovider/impl/Constants.java create mode 100644 features/sdnr/wt/mountpoint-state-provider/provider/src/main/java/org/onap/ccsdk/features/sdnr/wt/mountpointstateprovider/impl/MountpointStateProviderImpl.java create mode 100644 features/sdnr/wt/mountpoint-state-provider/provider/src/main/java/org/onap/ccsdk/features/sdnr/wt/mountpointstateprovider/impl/MountpointStateVESMessageFormatter.java create mode 100644 features/sdnr/wt/mountpoint-state-provider/provider/src/main/resources/org/opendaylight/blueprint/impl-blueprint.xml create mode 100644 features/sdnr/wt/mountpoint-state-provider/provider/src/main/resources/version.properties create mode 100644 features/sdnr/wt/mountpoint-state-provider/provider/src/test/java/org/onap/ccsdk/features/sdnr/wt/mountpointstateprovider/test/TestMountpointStateProviderImpl.java create mode 100644 features/sdnr/wt/mountpoint-state-provider/provider/src/test/resources/simplelogger.properties create mode 100644 features/sdnr/wt/mountpoint-state-provider/provider/src/test/resources/testpublisher.properties create mode 100644 features/sdnr/wt/netconfnode-state-service/feature/pom.xml create mode 100755 features/sdnr/wt/netconfnode-state-service/installer/pom.xml create mode 100644 features/sdnr/wt/netconfnode-state-service/installer/src/assembly/assemble_mvnrepo_zip.xml create mode 100644 features/sdnr/wt/netconfnode-state-service/model/pom.xml create mode 100644 features/sdnr/wt/netconfnode-state-service/model/src/main/java/org/onap/ccsdk/features/sdnr/wt/netconfnodestateservice/Capabilities.java create mode 100644 features/sdnr/wt/netconfnode-state-service/model/src/main/java/org/onap/ccsdk/features/sdnr/wt/netconfnodestateservice/DomContext.java create mode 100644 features/sdnr/wt/netconfnode-state-service/model/src/main/java/org/onap/ccsdk/features/sdnr/wt/netconfnodestateservice/NetconfAccessor.java create mode 100644 features/sdnr/wt/netconfnode-state-service/model/src/main/java/org/onap/ccsdk/features/sdnr/wt/netconfnodestateservice/NetconfBindingAccessor.java create mode 100644 features/sdnr/wt/netconfnode-state-service/model/src/main/java/org/onap/ccsdk/features/sdnr/wt/netconfnodestateservice/NetconfDomAccessor.java create mode 100644 features/sdnr/wt/netconfnode-state-service/model/src/main/java/org/onap/ccsdk/features/sdnr/wt/netconfnodestateservice/NetconfNodeConnectListener.java create mode 100644 features/sdnr/wt/netconfnode-state-service/model/src/main/java/org/onap/ccsdk/features/sdnr/wt/netconfnodestateservice/NetconfNodeStateListener.java create mode 100644 features/sdnr/wt/netconfnode-state-service/model/src/main/java/org/onap/ccsdk/features/sdnr/wt/netconfnodestateservice/NetconfNodeStateService.java create mode 100644 features/sdnr/wt/netconfnode-state-service/model/src/main/java/org/onap/ccsdk/features/sdnr/wt/netconfnodestateservice/TransactionUtils.java create mode 100644 features/sdnr/wt/netconfnode-state-service/model/src/main/java/org/onap/ccsdk/features/sdnr/wt/netconfnodestateservice/VesNotificationListener.java create mode 100644 features/sdnr/wt/netconfnode-state-service/model/src/main/yang/config.yang create mode 100644 features/sdnr/wt/netconfnode-state-service/model/src/main/yang/netconfnode-state.yang create mode 100644 features/sdnr/wt/netconfnode-state-service/model/src/test/java/org/onap/ccsdk/features/sdnr/wt/netconfnodestateservice/test/TestCapabilities.java create mode 100755 features/sdnr/wt/netconfnode-state-service/pom.xml create mode 100644 features/sdnr/wt/netconfnode-state-service/provider/copyright create mode 100644 features/sdnr/wt/netconfnode-state-service/provider/pom.xml create mode 100644 features/sdnr/wt/netconfnode-state-service/provider/src/main/java/org/onap/ccsdk/features/sdnr/wt/netconfnodestateservice/impl/NetconfNodeStateServiceImpl.java create mode 100644 features/sdnr/wt/netconfnode-state-service/provider/src/main/java/org/onap/ccsdk/features/sdnr/wt/netconfnodestateservice/impl/access/NetconfAccessorImpl.java create mode 100644 features/sdnr/wt/netconfnode-state-service/provider/src/main/java/org/onap/ccsdk/features/sdnr/wt/netconfnodestateservice/impl/access/NetconfAccessorManager.java create mode 100644 features/sdnr/wt/netconfnode-state-service/provider/src/main/java/org/onap/ccsdk/features/sdnr/wt/netconfnodestateservice/impl/access/NetconfCommunicatorManager.java create mode 100644 features/sdnr/wt/netconfnode-state-service/provider/src/main/java/org/onap/ccsdk/features/sdnr/wt/netconfnodestateservice/impl/access/binding/GenericTransactionUtils.java create mode 100644 features/sdnr/wt/netconfnode-state-service/provider/src/main/java/org/onap/ccsdk/features/sdnr/wt/netconfnodestateservice/impl/access/binding/NetconfBindingAccessorImpl.java create mode 100644 features/sdnr/wt/netconfnode-state-service/provider/src/main/java/org/onap/ccsdk/features/sdnr/wt/netconfnodestateservice/impl/access/dom/CanNotConvertException.java create mode 100644 features/sdnr/wt/netconfnode-state-service/provider/src/main/java/org/onap/ccsdk/features/sdnr/wt/netconfnodestateservice/impl/access/dom/DomContextImpl.java create mode 100644 features/sdnr/wt/netconfnode-state-service/provider/src/main/java/org/onap/ccsdk/features/sdnr/wt/netconfnodestateservice/impl/access/dom/DomParser.java create mode 100644 features/sdnr/wt/netconfnode-state-service/provider/src/main/java/org/onap/ccsdk/features/sdnr/wt/netconfnodestateservice/impl/access/dom/NetconfDomAccessorImpl.java create mode 100644 features/sdnr/wt/netconfnode-state-service/provider/src/main/java/org/onap/ccsdk/features/sdnr/wt/netconfnodestateservice/impl/access/dom/NotificationServiceNotProvided.java create mode 100644 features/sdnr/wt/netconfnode-state-service/provider/src/main/java/org/onap/ccsdk/features/sdnr/wt/netconfnodestateservice/impl/conf/NetconfStateConfig.java create mode 100644 features/sdnr/wt/netconfnode-state-service/provider/src/main/java/org/onap/ccsdk/features/sdnr/wt/netconfnodestateservice/impl/conf/odlAkka/AkkaConfig.java create mode 100644 features/sdnr/wt/netconfnode-state-service/provider/src/main/java/org/onap/ccsdk/features/sdnr/wt/netconfnodestateservice/impl/conf/odlAkka/ClusterConfig.java create mode 100644 features/sdnr/wt/netconfnode-state-service/provider/src/main/java/org/onap/ccsdk/features/sdnr/wt/netconfnodestateservice/impl/conf/odlAkka/ClusterNodeInfo.java create mode 100644 features/sdnr/wt/netconfnode-state-service/provider/src/main/java/org/onap/ccsdk/features/sdnr/wt/netconfnodestateservice/impl/conf/odlGeo/ClusterRoleInfo.java create mode 100644 features/sdnr/wt/netconfnode-state-service/provider/src/main/java/org/onap/ccsdk/features/sdnr/wt/netconfnodestateservice/impl/conf/odlGeo/ClusterRoleInfoCollection.java create mode 100644 features/sdnr/wt/netconfnode-state-service/provider/src/main/java/org/onap/ccsdk/features/sdnr/wt/netconfnodestateservice/impl/conf/odlGeo/GeoConfig.java create mode 100644 features/sdnr/wt/netconfnode-state-service/provider/src/main/java/org/onap/ccsdk/features/sdnr/wt/netconfnodestateservice/impl/mdsal/MdsalApi.java create mode 100644 features/sdnr/wt/netconfnode-state-service/provider/src/main/java/org/onap/ccsdk/features/sdnr/wt/netconfnodestateservice/impl/rpc/NetconfnodeStateServiceRpcApiImpl.java create mode 100644 features/sdnr/wt/netconfnode-state-service/provider/src/main/java/org/onap/ccsdk/features/sdnr/wt/netconfnodestateservice/impl/rpc/RpcApigetStateCallback.java create mode 100644 features/sdnr/wt/netconfnode-state-service/provider/src/main/resources/NetconfAccessorImpl.java create mode 100644 features/sdnr/wt/netconfnode-state-service/provider/src/main/resources/NetconfBindingAccessorImpl.java create mode 100644 features/sdnr/wt/netconfnode-state-service/provider/src/main/resources/example.json create mode 100644 features/sdnr/wt/netconfnode-state-service/provider/src/main/resources/org/opendaylight/blueprint/impl-blueprint.xml create mode 100644 features/sdnr/wt/netconfnode-state-service/provider/src/main/resources/sample.json create mode 100644 features/sdnr/wt/netconfnode-state-service/provider/src/main/resources/version.properties create mode 100644 features/sdnr/wt/netconfnode-state-service/provider/src/test/java/org/onap/ccsdk/features/sdnr/wt/netconfnodestateservice/test/TestAkkaConfig.java create mode 100644 features/sdnr/wt/netconfnode-state-service/provider/src/test/java/org/onap/ccsdk/features/sdnr/wt/netconfnodestateservice/test/TestCapabilites.java create mode 100644 features/sdnr/wt/netconfnode-state-service/provider/src/test/java/org/onap/ccsdk/features/sdnr/wt/netconfnodestateservice/test/TestConfig.java create mode 100644 features/sdnr/wt/netconfnode-state-service/provider/src/test/java/org/onap/ccsdk/features/sdnr/wt/netconfnodestateservice/test/TestGenericTransactionUtils.java create mode 100644 features/sdnr/wt/netconfnode-state-service/provider/src/test/java/org/onap/ccsdk/features/sdnr/wt/netconfnodestateservice/test/TestGeoConfig.java create mode 100644 features/sdnr/wt/netconfnode-state-service/provider/src/test/java/org/onap/ccsdk/features/sdnr/wt/netconfnodestateservice/test/TestNetconfAccessorImpl.java create mode 100644 features/sdnr/wt/netconfnode-state-service/provider/src/test/java/org/onap/ccsdk/features/sdnr/wt/netconfnodestateservice/test/TestNetconfNodeStateService.java create mode 100644 features/sdnr/wt/netconfnode-state-service/provider/src/test/java/org/onap/ccsdk/features/sdnr/wt/netconfnodestateservice/test/example/ExampleConfig.java create mode 100644 features/sdnr/wt/netconfnode-state-service/provider/src/test/java/org/onap/ccsdk/features/sdnr/wt/netconfnodestateservice/test/example/TestNetconfHelper.java create mode 100644 features/sdnr/wt/netconfnode-state-service/provider/src/test/resources/captured-akka.conf create mode 100644 features/sdnr/wt/netconfnode-state-service/provider/src/test/resources/config@2020-12-08.yang create mode 100644 features/sdnr/wt/netconfnode-state-service/provider/src/test/resources/simplelogger.properties create mode 100644 features/sdnr/wt/netconfnode-state-service/provider/src/test/resources/test.properties create mode 100644 features/sdnr/wt/oauth-provider/oauth-cli/pom.xml create mode 100644 features/sdnr/wt/oauth-provider/oauth-cli/src/main/java/org/onap/ccsdk/features/sdnr/wt/oauthprovider/Program.java create mode 100644 features/sdnr/wt/oauth-provider/oauth-core/pom.xml create mode 100644 features/sdnr/wt/oauth-provider/oauth-core/src/main/java/org/onap/ccsdk/features/sdnr/wt/oauthprovider/OAuth2Realm.java create mode 100644 features/sdnr/wt/oauth-provider/oauth-core/src/main/java/org/onap/ccsdk/features/sdnr/wt/oauthprovider/data/Config.java create mode 100644 features/sdnr/wt/oauth-provider/oauth-core/src/main/java/org/onap/ccsdk/features/sdnr/wt/oauthprovider/data/CustomObjectMapper.java create mode 100644 features/sdnr/wt/oauth-provider/oauth-core/src/main/java/org/onap/ccsdk/features/sdnr/wt/oauthprovider/data/InvalidConfigurationException.java create mode 100644 features/sdnr/wt/oauth-provider/oauth-core/src/main/java/org/onap/ccsdk/features/sdnr/wt/oauthprovider/data/KeycloakRole.java create mode 100644 features/sdnr/wt/oauth-provider/oauth-core/src/main/java/org/onap/ccsdk/features/sdnr/wt/oauthprovider/data/KeycloakUserTokenPayload.java create mode 100644 features/sdnr/wt/oauth-provider/oauth-core/src/main/java/org/onap/ccsdk/features/sdnr/wt/oauthprovider/data/NoDefinitionFoundException.java create mode 100644 features/sdnr/wt/oauth-provider/oauth-core/src/main/java/org/onap/ccsdk/features/sdnr/wt/oauthprovider/data/OAuthProviderConfig.java create mode 100644 features/sdnr/wt/oauth-provider/oauth-core/src/main/java/org/onap/ccsdk/features/sdnr/wt/oauthprovider/data/OAuthResponseData.java create mode 100644 features/sdnr/wt/oauth-provider/oauth-core/src/main/java/org/onap/ccsdk/features/sdnr/wt/oauthprovider/data/OAuthToken.java create mode 100644 features/sdnr/wt/oauth-provider/oauth-core/src/main/java/org/onap/ccsdk/features/sdnr/wt/oauthprovider/data/OdlPolicy.java create mode 100644 features/sdnr/wt/oauth-provider/oauth-core/src/main/java/org/onap/ccsdk/features/sdnr/wt/oauthprovider/data/OdlShiroConfiguration.java create mode 100644 features/sdnr/wt/oauth-provider/oauth-core/src/main/java/org/onap/ccsdk/features/sdnr/wt/oauthprovider/data/OdlXmlMapper.java create mode 100644 features/sdnr/wt/oauth-provider/oauth-core/src/main/java/org/onap/ccsdk/features/sdnr/wt/oauthprovider/data/OpenIdConfigResponseData.java create mode 100644 features/sdnr/wt/oauth-provider/oauth-core/src/main/java/org/onap/ccsdk/features/sdnr/wt/oauthprovider/data/UnableToConfigureOAuthService.java create mode 100644 features/sdnr/wt/oauth-provider/oauth-core/src/main/java/org/onap/ccsdk/features/sdnr/wt/oauthprovider/data/UserTokenPayload.java create mode 100644 features/sdnr/wt/oauth-provider/oauth-core/src/main/java/org/onap/ccsdk/features/sdnr/wt/oauthprovider/filters/AnyRoleHttpAuthenticationFilter.java create mode 100644 features/sdnr/wt/oauth-provider/oauth-core/src/main/java/org/onap/ccsdk/features/sdnr/wt/oauthprovider/filters/BearerAndBasicHttpAuthenticationFilter.java create mode 100644 features/sdnr/wt/oauth-provider/oauth-core/src/main/java/org/onap/ccsdk/features/sdnr/wt/oauthprovider/filters/CustomizedMDSALDynamicAuthorizationFilter.java create mode 100644 features/sdnr/wt/oauth-provider/oauth-core/src/main/java/org/onap/ccsdk/features/sdnr/wt/oauthprovider/http/AuthHttpServlet.java create mode 100644 features/sdnr/wt/oauth-provider/oauth-core/src/main/java/org/onap/ccsdk/features/sdnr/wt/oauthprovider/http/HeadersOnlyHttpServletRequest.java create mode 100644 features/sdnr/wt/oauth-provider/oauth-core/src/main/java/org/onap/ccsdk/features/sdnr/wt/oauthprovider/http/client/MappedBaseHttpResponse.java create mode 100644 features/sdnr/wt/oauth-provider/oauth-core/src/main/java/org/onap/ccsdk/features/sdnr/wt/oauthprovider/http/client/MappingBaseHttpClient.java create mode 100644 features/sdnr/wt/oauth-provider/oauth-core/src/main/java/org/onap/ccsdk/features/sdnr/wt/oauthprovider/providers/AuthService.java create mode 100644 features/sdnr/wt/oauth-provider/oauth-core/src/main/java/org/onap/ccsdk/features/sdnr/wt/oauthprovider/providers/GitlabProviderService.java create mode 100644 features/sdnr/wt/oauth-provider/oauth-core/src/main/java/org/onap/ccsdk/features/sdnr/wt/oauthprovider/providers/KeycloakProviderService.java create mode 100644 features/sdnr/wt/oauth-provider/oauth-core/src/main/java/org/onap/ccsdk/features/sdnr/wt/oauthprovider/providers/MdSalAuthorizationStore.java create mode 100644 features/sdnr/wt/oauth-provider/oauth-core/src/main/java/org/onap/ccsdk/features/sdnr/wt/oauthprovider/providers/NextcloudProviderService.java create mode 100644 features/sdnr/wt/oauth-provider/oauth-core/src/main/java/org/onap/ccsdk/features/sdnr/wt/oauthprovider/providers/OAuthProviderFactory.java create mode 100644 features/sdnr/wt/oauth-provider/oauth-core/src/main/java/org/onap/ccsdk/features/sdnr/wt/oauthprovider/providers/PemUtils.java create mode 100644 features/sdnr/wt/oauth-provider/oauth-core/src/main/java/org/onap/ccsdk/features/sdnr/wt/oauthprovider/providers/RSAKeyReader.java create mode 100644 features/sdnr/wt/oauth-provider/oauth-core/src/main/java/org/onap/ccsdk/features/sdnr/wt/oauthprovider/providers/TokenCreator.java create mode 100644 features/sdnr/wt/oauth-provider/oauth-core/src/test/java/org/onap/ccsdk/features/sdnr/wt/oauthprovider/test/TestAuthHttpServlet.java create mode 100644 features/sdnr/wt/oauth-provider/oauth-core/src/test/java/org/onap/ccsdk/features/sdnr/wt/oauthprovider/test/TestConfig.java create mode 100644 features/sdnr/wt/oauth-provider/oauth-core/src/test/java/org/onap/ccsdk/features/sdnr/wt/oauthprovider/test/TestDeserializer.java create mode 100644 features/sdnr/wt/oauth-provider/oauth-core/src/test/java/org/onap/ccsdk/features/sdnr/wt/oauthprovider/test/TestGitlabAuthService.java create mode 100644 features/sdnr/wt/oauth-provider/oauth-core/src/test/java/org/onap/ccsdk/features/sdnr/wt/oauthprovider/test/TestKeycloakAuthService.java create mode 100644 features/sdnr/wt/oauth-provider/oauth-core/src/test/java/org/onap/ccsdk/features/sdnr/wt/oauthprovider/test/TestPolicy.java create mode 100644 features/sdnr/wt/oauth-provider/oauth-core/src/test/java/org/onap/ccsdk/features/sdnr/wt/oauthprovider/test/TestProperty.java create mode 100644 features/sdnr/wt/oauth-provider/oauth-core/src/test/java/org/onap/ccsdk/features/sdnr/wt/oauthprovider/test/TestRSAAlgorithms.java create mode 100644 features/sdnr/wt/oauth-provider/oauth-core/src/test/java/org/onap/ccsdk/features/sdnr/wt/oauthprovider/test/TestRealm.java create mode 100644 features/sdnr/wt/oauth-provider/oauth-core/src/test/java/org/onap/ccsdk/features/sdnr/wt/oauthprovider/test/helper/OdlJsonMapper.java create mode 100644 features/sdnr/wt/oauth-provider/oauth-core/src/test/java/org/onap/ccsdk/features/sdnr/wt/oauthprovider/test/helper/OdlXmlMapper.java create mode 100644 features/sdnr/wt/oauth-provider/oauth-core/src/test/resources/aaa-app-config.test.xml create mode 100644 features/sdnr/wt/oauth-provider/oauth-core/src/test/resources/jwtRS256.key create mode 100644 features/sdnr/wt/oauth-provider/oauth-core/src/test/resources/jwtRS256.key.pub create mode 100644 features/sdnr/wt/oauth-provider/oauth-core/src/test/resources/jwtRS512.key create mode 100644 features/sdnr/wt/oauth-provider/oauth-core/src/test/resources/jwtRS512.key.pub create mode 100644 features/sdnr/wt/oauth-provider/oauth-core/src/test/resources/mdsalDynAuthData.json create mode 100644 features/sdnr/wt/oauth-provider/oauth-core/src/test/resources/oauth/gitlab-groups-response.json create mode 100644 features/sdnr/wt/oauth-provider/oauth-core/src/test/resources/oauth/gitlab-token-response.json create mode 100644 features/sdnr/wt/oauth-provider/oauth-core/src/test/resources/oauth/gitlab-user-response.json create mode 100644 features/sdnr/wt/oauth-provider/oauth-core/src/test/resources/oauth/keycloak-token-response.json create mode 100644 features/sdnr/wt/oauth-provider/oauth-core/src/test/resources/oom.test.config.json create mode 100644 features/sdnr/wt/oauth-provider/oauth-core/src/test/resources/test.config.json create mode 100644 features/sdnr/wt/oauth-provider/oauth-core/src/test/resources/test.configRS256-invalid.json create mode 100644 features/sdnr/wt/oauth-provider/oauth-core/src/test/resources/test.configRS256.json create mode 100644 features/sdnr/wt/oauth-provider/oauth-core/src/test/resources/test.configRS512.json create mode 100644 features/sdnr/wt/oauth-provider/oauth-realm/pom.xml create mode 100644 features/sdnr/wt/oauth-provider/oauth-web/pom.xml create mode 100644 features/sdnr/wt/oauth-provider/oauth-web/src/main/resources/org/opendaylight/blueprint/impl-blueprint.xml create mode 100755 features/sdnr/wt/oauth-provider/pom.xml create mode 100644 features/sdnr/wt/pom.xml create mode 100644 features/sdnr/wt/websocketmanager/feature/pom.xml create mode 100755 features/sdnr/wt/websocketmanager/installer/pom.xml create mode 100644 features/sdnr/wt/websocketmanager/installer/src/assembly/assemble_mvnrepo_zip.xml create mode 100644 features/sdnr/wt/websocketmanager/model/pom.xml create mode 100644 features/sdnr/wt/websocketmanager/model/src/main/java/org/onap/ccsdk/features/sdnr/wt/websocketmanager/model/WebsocketManagerService.java create mode 100644 features/sdnr/wt/websocketmanager/model/src/main/java/org/onap/ccsdk/features/sdnr/wt/websocketmanager/model/data/DOMNotificationOutput.java create mode 100644 features/sdnr/wt/websocketmanager/model/src/main/java/org/onap/ccsdk/features/sdnr/wt/websocketmanager/model/data/INotificationOutput.java create mode 100644 features/sdnr/wt/websocketmanager/model/src/main/java/org/onap/ccsdk/features/sdnr/wt/websocketmanager/model/data/NotificationOutput.java create mode 100644 features/sdnr/wt/websocketmanager/model/src/main/java/org/onap/ccsdk/features/sdnr/wt/websocketmanager/model/data/ReducedSchemaInfo.java create mode 100644 features/sdnr/wt/websocketmanager/model/src/main/java/org/onap/ccsdk/features/sdnr/wt/websocketmanager/model/data/SchemaInfo.java create mode 100644 features/sdnr/wt/websocketmanager/model/src/main/java/org/onap/ccsdk/features/sdnr/wt/websocketmanager/model/data/Scope.java create mode 100644 features/sdnr/wt/websocketmanager/model/src/main/java/org/onap/ccsdk/features/sdnr/wt/websocketmanager/model/data/ScopeRegistration.java create mode 100644 features/sdnr/wt/websocketmanager/model/src/main/java/org/onap/ccsdk/features/sdnr/wt/websocketmanager/model/data/ScopeRegistrationResponse.java create mode 100644 features/sdnr/wt/websocketmanager/model/src/main/yang/websocketmanager.yang create mode 100755 features/sdnr/wt/websocketmanager/pom.xml create mode 100644 features/sdnr/wt/websocketmanager/provider/pom.xml create mode 100644 features/sdnr/wt/websocketmanager/provider/src/main/java/org/onap/ccsdk/features/sdnr/wt/websocketmanager/WebSocketManager.java create mode 100644 features/sdnr/wt/websocketmanager/provider/src/main/java/org/onap/ccsdk/features/sdnr/wt/websocketmanager/WebSocketManagerCreator.java create mode 100644 features/sdnr/wt/websocketmanager/provider/src/main/java/org/onap/ccsdk/features/sdnr/wt/websocketmanager/WebSocketManagerProvider.java create mode 100644 features/sdnr/wt/websocketmanager/provider/src/main/java/org/onap/ccsdk/features/sdnr/wt/websocketmanager/WebSocketManagerSocket.java create mode 100644 features/sdnr/wt/websocketmanager/provider/src/main/java/org/onap/ccsdk/features/sdnr/wt/websocketmanager/config/WebSocketManagerConfig.java create mode 100644 features/sdnr/wt/websocketmanager/provider/src/main/java/org/onap/ccsdk/features/sdnr/wt/websocketmanager/data/TimeRateLimitingQueue.java create mode 100644 features/sdnr/wt/websocketmanager/provider/src/main/java/org/onap/ccsdk/features/sdnr/wt/websocketmanager/utils/AkkaConfig.java create mode 100644 features/sdnr/wt/websocketmanager/provider/src/main/java/org/onap/ccsdk/features/sdnr/wt/websocketmanager/utils/RateFilterManager.java create mode 100644 features/sdnr/wt/websocketmanager/provider/src/main/java/org/onap/ccsdk/features/sdnr/wt/websocketmanager/utils/UserScopes.java create mode 100644 features/sdnr/wt/websocketmanager/provider/src/main/java/org/onap/ccsdk/features/sdnr/wt/websocketmanager/websocket/SyncWebSocketClient.java create mode 100644 features/sdnr/wt/websocketmanager/provider/src/main/resources/org/opendaylight/blueprint/impl-blueprint.xml create mode 100644 features/sdnr/wt/websocketmanager/provider/src/test/java/org/onap/ccsdk/features/sdnr/wt/websocketmanager2/test/AkkaConfigTest.java create mode 100644 features/sdnr/wt/websocketmanager/provider/src/test/java/org/onap/ccsdk/features/sdnr/wt/websocketmanager2/test/RateFilterTest.java create mode 100644 features/sdnr/wt/websocketmanager/provider/src/test/java/org/onap/ccsdk/features/sdnr/wt/websocketmanager2/test/TestDeserialize.java create mode 100644 features/sdnr/wt/websocketmanager/provider/src/test/java/org/onap/ccsdk/features/sdnr/wt/websocketmanager2/test/TestSerializer.java create mode 100644 features/sdnr/wt/websocketmanager/provider/src/test/java/org/onap/ccsdk/features/sdnr/wt/websocketmanager2/test/UserScopeTest.java create mode 100644 features/sdnr/wt/websocketmanager/provider/src/test/java/org/onap/ccsdk/features/sdnr/wt/websocketmanager2/test/WebsockerProviderTest.java create mode 100644 features/sdnr/wt/websocketmanager/provider/src/test/java/org/onap/ccsdk/features/sdnr/wt/websocketmanager2/test/WebsocketClientTest.java create mode 100644 features/sdnr/wt/websocketmanager/provider/src/test/java/org/onap/ccsdk/features/sdnr/wt/websocketmanager2/test/WebsocketMessageTest.java create mode 100644 features/sdnr/wt/websocketmanager/provider/src/test/java/org/onap/ccsdk/features/sdnr/wt/websocketmanager2/test/WebsocketServerConnectTest.java create mode 100644 features/sdnr/wt/websocketmanager/provider/src/test/resources/akka-cluster-local.cfg create mode 100644 features/sdnr/wt/websocketmanager/provider/src/test/resources/akka-cluster.cfg create mode 100644 features/sdnr/wt/websocketmanager/provider/src/test/resources/akka-singlenode.cfg create mode 100644 features/sdnr/wt/websocketmanager/provider/src/test/resources/simplelogger.properties diff --git a/features/sdnr/odlux/helpserver/pom.xml b/features/sdnr/odlux/helpserver/pom.xml new file mode 100755 index 0000000..538f5ad --- /dev/null +++ b/features/sdnr/odlux/helpserver/pom.xml @@ -0,0 +1,40 @@ + + + + + 4.0.0 + + org.o-ran-sc.oam-controller.features.sdnr.odlux + sdnr-odlux-helpserver-top + 1.7.0-SNAPSHOT + pom + + SDNR ODLUX :: ${project.artifactId} + + + provider + + + diff --git a/features/sdnr/odlux/helpserver/provider/README.md b/features/sdnr/odlux/helpserver/provider/README.md new file mode 100644 index 0000000..d35aeb5 --- /dev/null +++ b/features/sdnr/odlux/helpserver/provider/README.md @@ -0,0 +1,34 @@ +##Creating help files + +#### Link and references to pictures. + +All links are relative to the current md-file. + +Link to pages located in the same directory: + +``` +[linkname](file.md) +``` + +Link to subpages located in subdirectories: + +``` +[linkname](subfolder/file.md) +``` + +External Links: + +``` +[linkname](linkurl "linktitle") +``` + +Images: + +``` +![SDN-R in ONAP](./ONAP-SDN-R.png "SDN-R in ONAP") +``` + +#### Supported formates + +md-format: +Picture formats: PNG diff --git a/features/sdnr/odlux/helpserver/provider/bitnami/nginx/help/meta.json b/features/sdnr/odlux/helpserver/provider/bitnami/nginx/help/meta.json new file mode 100644 index 0000000..6dc9c45 --- /dev/null +++ b/features/sdnr/odlux/helpserver/provider/bitnami/nginx/help/meta.json @@ -0,0 +1 @@ +abbccdfkamaosie aksdmais \ No newline at end of file diff --git a/features/sdnr/odlux/helpserver/provider/bitnami/nginx/help/test.css b/features/sdnr/odlux/helpserver/provider/bitnami/nginx/help/test.css new file mode 100644 index 0000000..6dc9c45 --- /dev/null +++ b/features/sdnr/odlux/helpserver/provider/bitnami/nginx/help/test.css @@ -0,0 +1 @@ +abbccdfkamaosie aksdmais \ No newline at end of file diff --git a/features/sdnr/odlux/helpserver/provider/bitnami/nginx/help/test.eps b/features/sdnr/odlux/helpserver/provider/bitnami/nginx/help/test.eps new file mode 100644 index 0000000..6dc9c45 --- /dev/null +++ b/features/sdnr/odlux/helpserver/provider/bitnami/nginx/help/test.eps @@ -0,0 +1 @@ +abbccdfkamaosie aksdmais \ No newline at end of file diff --git a/features/sdnr/odlux/helpserver/provider/bitnami/nginx/help/test.pdf b/features/sdnr/odlux/helpserver/provider/bitnami/nginx/help/test.pdf new file mode 100644 index 0000000..6dc9c45 --- /dev/null +++ b/features/sdnr/odlux/helpserver/provider/bitnami/nginx/help/test.pdf @@ -0,0 +1 @@ +abbccdfkamaosie aksdmais \ No newline at end of file diff --git a/features/sdnr/odlux/helpserver/provider/bitnami/nginx/help/test/test.txt b/features/sdnr/odlux/helpserver/provider/bitnami/nginx/help/test/test.txt new file mode 100644 index 0000000..6dc9c45 --- /dev/null +++ b/features/sdnr/odlux/helpserver/provider/bitnami/nginx/help/test/test.txt @@ -0,0 +1 @@ +abbccdfkamaosie aksdmais \ No newline at end of file diff --git a/features/sdnr/odlux/helpserver/provider/help/meta.json b/features/sdnr/odlux/helpserver/provider/help/meta.json new file mode 100644 index 0000000..6dc9c45 --- /dev/null +++ b/features/sdnr/odlux/helpserver/provider/help/meta.json @@ -0,0 +1 @@ +abbccdfkamaosie aksdmais \ No newline at end of file diff --git a/features/sdnr/odlux/helpserver/provider/help/test/test.txt b/features/sdnr/odlux/helpserver/provider/help/test/test.txt new file mode 100644 index 0000000..6dc9c45 --- /dev/null +++ b/features/sdnr/odlux/helpserver/provider/help/test/test.txt @@ -0,0 +1 @@ +abbccdfkamaosie aksdmais \ No newline at end of file diff --git a/features/sdnr/odlux/helpserver/provider/pom.xml b/features/sdnr/odlux/helpserver/provider/pom.xml new file mode 100644 index 0000000..9a800e4 --- /dev/null +++ b/features/sdnr/odlux/helpserver/provider/pom.xml @@ -0,0 +1,88 @@ + + + + + 4.0.0 + + org.o-ran-sc.oam-controller.features.sdnr.odlux + sdnr-odlux-helpserver-provider + 1.7.0-SNAPSHOT + jar + + SDNR ODLUX :: ${project.artifactId} + + + true + 1.8 + 1.8 + 2.17.1 + 2.17.1 + 4.13.2 + + + + + jakarta.servlet + jakarta.servlet-api + 4.0.4 + + + org.json + json + 20201115 + + + org.slf4j + slf4j-log4j12 + 1.7.29 + + + org.mockito + mockito-core + 3.5.11 + test + + + junit + junit + 4.13.2 + test + + + + + + + + + org.apache.maven.plugins + maven-surefire-plugin + + false + + + + + diff --git a/features/sdnr/odlux/helpserver/provider/src/main/java/org/onap/ccsdk/features/sdnr/wt/helpserver/HelpServlet.java b/features/sdnr/odlux/helpserver/provider/src/main/java/org/onap/ccsdk/features/sdnr/wt/helpserver/HelpServlet.java new file mode 100644 index 0000000..bf93109 --- /dev/null +++ b/features/sdnr/odlux/helpserver/provider/src/main/java/org/onap/ccsdk/features/sdnr/wt/helpserver/HelpServlet.java @@ -0,0 +1,179 @@ +/* + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt + * ================================================================================================= + * 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========================================================================== + */ +package org.onap.ccsdk.features.sdnr.wt.helpserver; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileReader; +import java.io.IOException; +import java.io.OutputStream; +import java.net.URISyntaxException; +import java.net.URLDecoder; +import java.nio.file.Path; +import javax.servlet.Servlet; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import org.onap.ccsdk.features.sdnr.wt.helpserver.data.HelpInfrastructureObject; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class HelpServlet extends HttpServlet implements AutoCloseable { + + private static Logger LOG = LoggerFactory.getLogger(HelpServlet.class); + private static final long serialVersionUID = -4285072760648493461L; + + private static final String BASEURI = "/help"; + + private final Path basePath; + + public HelpServlet() { + LOG.info("Starting HelpServlet instance {}", this.hashCode()); +// HelpInfrastructureObject.createFilesFromResources(); +// this.basePath = HelpInfrastructureObject.getHelpDirectoryBase(); + this.basePath = Path.of("bitnami/nginx/help"); + } + + @Override + public void doOptions(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { + resp.addHeader("Access-Control-Allow-Origin", "*"); + resp.addHeader("Access-Control-Allow-Methods", "OPTIONS, HEAD, GET, POST, PUT, DELETE"); + resp.addHeader("Access-Control-Allow-Headers", "X-Requested-With, Content-Type, Content-Length"); + } + + @Override + public void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { + String query = req.getQueryString(); + resp.addHeader("Access-Control-Allow-Origin", "*"); + resp.addHeader("Access-Control-Allow-Methods", "OPTIONS, HEAD, GET, POST, PUT, DELETE"); + resp.addHeader("Access-Control-Allow-Headers", "X-Requested-With, Content-Type, Content-Length"); +// if (query != null && query.contains("meta")) { +// +// File f = new File(HelpInfrastructureObject.KARAFHELPDIRECTORY, "meta.json"); +// if (f.exists()) { +// LOG.debug("found local meta file"); +// try (BufferedReader rd = new BufferedReader(new FileReader(f));) { +// String line = rd.readLine(); +// while (line != null) { +// resp.getOutputStream().println(line); +// line = rd.readLine(); +// } +// rd.close(); +// } catch (IOException e) { +// LOG.debug("Can not read meta file", e); +// } +// } else { +// LOG.debug("start walking from path=" + basePath.toAbsolutePath().toString()); +// HelpInfrastructureObject o = null; +// try { +// o = new HelpInfrastructureObject(this.basePath); +// } catch (URISyntaxException e) { +// LOG.debug("Can not relsolve URI. ", e); +// } +// resp.getOutputStream().println(o != null ? o.toString() : ""); +// } +// resp.setHeader("Content-Type", "application/json"); +// } else + { + LOG.debug("received get with uri=" + req.getRequestURI()); + String uri = URLDecoder.decode(req.getRequestURI().substring(BASEURI.length()), "UTF-8"); + if (uri.startsWith("/")) { + uri = uri.substring(1); + } + Path p = basePath.resolve(uri); + File f = p.toFile(); + if (f.isFile() && f.exists()) { + LOG.debug("found file for request"); + if (this.isTextFile(f)) { + resp.setHeader("Content-Type", "application/text"); + resp.setHeader("charset", "utf-8"); + } else if (this.isImageFile(f)) { + resp.setHeader("Content-Type", "image/*"); + } else if (this.ispdf(f)) { + resp.setHeader("Content-Type", "application/pdf"); + } else { + LOG.debug("file is not allowed to deliver"); + resp.setStatus(404); + return; + } + LOG.debug("delivering file"); + try (OutputStream out = resp.getOutputStream()) { + try (FileInputStream in = new FileInputStream(f)) { + + byte[] buffer = new byte[1024]; + int len; + while ((len = in.read(buffer)) != -1) { + out.write(buffer, 0, len); + } + in.close(); + out.flush(); + out.close(); + } + } catch (IOException e) { + LOG.warn("Can not write meta file", e); + resp.setStatus(500); + } + } else { + LOG.debug("found not file for request"); + resp.setStatus(404); + } + } + } + + private boolean ispdf(File f) { + return f != null && this.ispdf(f.getName()); + } + + private boolean ispdf(String name) { + return name != null && name.toLowerCase().endsWith("pdf"); + } + + private boolean isImageFile(File f) { + return f != null && this.isImageFile(f.getName()); + } + + private boolean isImageFile(String name) { + + return name != null + ? name.toLowerCase().endsWith("png") || name.toLowerCase().endsWith("jpg") + || name.toLowerCase().endsWith("jpeg") || name.toLowerCase().endsWith("svg") + || name.toLowerCase().endsWith("eps") + : false; + } + + private boolean isTextFile(File f) { + return f != null && this.isTextFile(f.getName()); + + } + + private boolean isTextFile(String name) { + return name != null + ? name.toLowerCase().endsWith("md") || name.toLowerCase().endsWith("txt") + || name.toLowerCase().endsWith("html") || name.toLowerCase().endsWith("htm") + || name.toLowerCase().endsWith("js") || name.toLowerCase().endsWith("css") + : false; + } + + @Override + public void close() throws Exception { + + } + +} diff --git a/features/sdnr/odlux/helpserver/provider/src/main/java/org/onap/ccsdk/features/sdnr/wt/helpserver/data/HelpInfrastructureObject.java b/features/sdnr/odlux/helpserver/provider/src/main/java/org/onap/ccsdk/features/sdnr/wt/helpserver/data/HelpInfrastructureObject.java new file mode 100644 index 0000000..5075b92 --- /dev/null +++ b/features/sdnr/odlux/helpserver/provider/src/main/java/org/onap/ccsdk/features/sdnr/wt/helpserver/data/HelpInfrastructureObject.java @@ -0,0 +1,174 @@ +/* + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt + * ================================================================================================= + * 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========================================================================== + */ +package org.onap.ccsdk.features.sdnr.wt.helpserver.data; + +import java.io.File; +import java.io.IOException; +import java.net.URISyntaxException; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import org.json.JSONObject; +//import org.osgi.framework.Bundle; +//import org.osgi.framework.FrameworkUtil; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class HelpInfrastructureObject extends JSONObject { + + private static final Logger LOG = LoggerFactory.getLogger(HelpInfrastructureObject.class); + private static String HELPBASE = "help"; + + public static class VersionObject extends JSONObject { + private static Comparator comp; + private final String mVersion; + + public String getVersion() { + return this.mVersion; + } + + public VersionObject(String path, String date, String label, String version) { + this.mVersion = version; + this.put("path", path); + this.put("date", date); + this.put("label", label); + } + + public static Comparator getComparer() { + if (comp == null) { + comp = (o1, o2) -> o1.getVersion().compareTo(o2.getVersion()); + } + return comp; + } + + public VersionObject cloneAsLatest() { + return new VersionObject(this.getString("path"), this.getString("date"), this.getString("label"), "latest"); + } + + public VersionObject cloneAsCurrent() { + return new VersionObject(this.getString("path"), this.getString("date"), this.getString("label"), + "current"); + } + } + public static class NodeObject extends JSONObject { + public NodeObject(Path base, File dir, String label, ArrayList versions) { + this.put("label", label); + if (versions != null && !versions.isEmpty()) { + JSONObject o = new JSONObject(); + this.put("versions", o); + for (VersionObject version : versions) { + o.put(version.getVersion(), version); + } + + } + File[] list = dir.listFiles(); + if (list == null) { + return; + } + for (File f : list) { + if (f.isDirectory()) { + ArrayList versions2 = findReadmeVersionFolders(base, f.toPath(), true); + if (versions2 != null && !versions2.isEmpty()) { + JSONObject nodes; + if (!this.has("nodes")) { + this.put("nodes", new JSONObject()); + } + nodes = this.getJSONObject("nodes"); + + NodeObject o = new NodeObject(base, f, f.getName(), versions2); + nodes.put(o.getString("label").toLowerCase(), o); + } + } + } + } + + } + + public HelpInfrastructureObject(Path pRoot) throws URISyntaxException { + File root = pRoot.toFile(); + File[] list = root.listFiles(); + if (list == null) { + return; + } + for (File f : list) { + if (f.isDirectory()) { + ArrayList versions = findReadmeVersionFolders(root.toPath(), f.toPath(), true); + if (versions != null && !versions.isEmpty()) { + NodeObject o = new NodeObject(pRoot, f, f.getName(), versions); + this.put(o.getString("label").toLowerCase(), o); + } + } + } + } + + private static ArrayList findReadmeVersionFolders(Path base, Path root, boolean appendCurrent) { + ArrayList list = new ArrayList<>(); + File[] files = root.toFile().listFiles(); + int baselen = base.toFile().getAbsolutePath().length(); + if (files != null) { + for (File f : files) { + if (f.isDirectory() && new File(f.getAbsolutePath() + "/README.md").exists()) { + list.add(new VersionObject(f.getAbsolutePath().substring(baselen + 1) + "/README.md", "", "", + f.getName())); + } + } + } + Collections.sort(list, VersionObject.getComparer()); + Collections.reverse(list); + if (!list.isEmpty() && appendCurrent) { + list.add(list.get(0).cloneAsCurrent()); + } + return list; + } + + +// public static void createFilesFromResources() { +// +// if (KARAFHELPDIRECTORY.exists()) { +// LOG.debug("Delete existing directory"); +// try { +// ExtactBundleResource.deleteRecursively(KARAFHELPDIRECTORY); +// } catch (IOException e1) { +// LOG.warn(e1.toString()); +// } +// } +// +// LOG.debug("Extract"); +// try { +// Bundle b = FrameworkUtil.getBundle(HelpInfrastructureObject.class); +// if (b == null) { +// LOG.debug("No bundlereference: Use target in filesystem."); +// // URL helpRessource = +// // JarFileUtils.stringToJarURL("target/helpserver-impl-0.4.0-SNAPSHOT.jar",KARAFBUNDLERESOURCEHELPROOT); +// +// } else { +// LOG.debug("Bundle location:{} State:{}", b.getLocation(), b.getState()); +// LOG.debug("Write files from Resource"); +// ExtactBundleResource.copyBundleResoucesRecursively(b, "data/cache/com.highstreet.technologies.", +// KARAFBUNDLERESOURCEHELPROOT); +// } +// } catch (IOException e) { +// LOG.warn("No help files available. Exception: " + e.toString()); +// } +// } +// +// public static Path getHelpDirectoryBase() { +// return KARAFHELPDIRECTORY.toPath(); +// } +} diff --git a/features/sdnr/odlux/helpserver/provider/src/main/resources/help/mediatorserver/README.md b/features/sdnr/odlux/helpserver/provider/src/main/resources/help/mediatorserver/README.md new file mode 100644 index 0000000..c99967d --- /dev/null +++ b/features/sdnr/odlux/helpserver/provider/src/main/resources/help/mediatorserver/README.md @@ -0,0 +1,144 @@ +# MediatorServer + +## Description +The mediator server is the physical device on which multiple instances of the [mediators](mediator/README.md) are running. Additionally a small webserver provides an API to control and create the mediators via HTTP-API. These mediators are translating the requests and responses between the SDN-Controller(netconf) and the device(snmp). Because of the restricted snmp protocol (port 162 only) we have to implement a prerouting automatism that forwards the alarms sent by the devices to another local port so that each mediator only gets the alarms of its device. + + + +## Config-File + +``` +/etc/mediatorserver.conf +``` + +``` +#global config file for mediatorserver + +#Home Directory +home=/opt/snmp + +#HOST IP +host=192.168.178.89 +port=7071 + +#Port range for Netconf +ncrangemin=4000 +ncrangemax=6000 + +#Port Range for SNMP +snmprangemin=10000 +snmprangemax=12000 + +#PortRange for JMX +jmxrangemin=6001 +jmxrangemax=7000 + +#Log (ERROR | WARN | DEBUG | INFO | TRACE ) +loglevel=DEBUG +logfile=/var/log/mediatorserver.log + +#===================================== +#global MediatorConfig + +#set LogLevel (ERROR | WARN | DEBUG | INFO | TRACE ) +MediatorLogLevel=DEBUG + +#set ping timeout in milliseconds +MediatorDevicePingTimeout=2000 + +#set latency for snmp requests +MediatorSnmpLatency=2000 + +#set java memory for mediator instance +MediatorMemory="-Xmx256m -Xms128m" +``` + +## HTTP-API + +``` +http://:/api/?task= +``` + +| Task | additional Parameters | Description | Response (Success) | +| ---- | --------------------- | ----------- | ------------------ | +| create | config=<config-object> | create new mediator instance | {"code":1,"data":"<string>"}| +| delete | name=<name> | delete mediator instance | \{"code":1,"data":<string>"} | +| start | name=<name> | start mediator instance | \{"code":1,"data":"<string>"} | +| stop | name=<name> | stop mediator instance | \{"code":1,"data":"<string>"} | +| getconfig | name=<name>(optional) | Get current Config for all instances / named mediator instance | \{"code":1,"data":[<config-objects>]}| +| getlog | name=<name>(optional) | Get LogEntries for all instances / named mediator instance | \{"code":1,"data":[]} | +| clearlock | name=<name> | Clear Mediator Lock File | \{"code":1,"data":"<string>"} | +| getnemodels | - | get all Network Element Template Filenames | \{"code":1,"data":[<string-array>]} | +| getncports | limit=<limit>(optional) | get next free ports for Netconf Connections | \{"code":1,"data":[<int-array>]} | +| getsnmpports | limit=<limit>(optional) | get next free ports for SNMP Traps | \{"code":1,"data":[<int-array>]} | +| version | - | get version info of server and mediator | \{"code":1,"data":\{"server":"0.1.0","mediator":"0.1.1"\}\}| +| repair | - | try to fix corrupted configs | \{"code":1,"data":[<config-status-objects>]}| + +HTTP-Response is always a json-formatted String with 2 Elements: + +* code ... 1:success 0:failure +* data ... if code==0: <string> else <string | object> + + +### JSON-Objects + +Config-Object +``` +{ + Name:, + DeviceType:, + DeviceIp:, + DevicePort: , + TrapsPort:, + NeModel:, + NcPort:, + ODLConfig:[ + { + Server:, + Port:, + User:, + Password: + } + ], + PID:, + IsLocked:, + AutoRun:, + FwActive:, + IsNetconfConnected:, + IsNeConnected: +} +``` + +Log-Object +``` +{ + ts:"", + lvl:"", + src:"", + msg:"> /var/log/firewall.log +*/2 * * * * /bin/bash /opt/snmp/bin/clean_all.sh > /dev/null 2>&1 +``` + +### Test Accessibility of the HTTP-API with console +``` +curl http://localhost:7070/api/?task=version +``` +or directly in your browser +``` +http://:7070/api/?task=version +``` +should respond with something like this: +``` +{"code":1,"data":{"server":"0.1.0","mediator":"0.1.1"}} +``` + diff --git a/features/sdnr/odlux/helpserver/provider/src/main/resources/help/mediatorserver/mediator/README.md b/features/sdnr/odlux/helpserver/provider/src/main/resources/help/mediatorserver/mediator/README.md new file mode 100644 index 0000000..9fa43a4 --- /dev/null +++ b/features/sdnr/odlux/helpserver/provider/src/main/resources/help/mediatorserver/mediator/README.md @@ -0,0 +1,66 @@ +# Mediator + +## Description + + +The mediator is a piece of software to translate get and set requests between the SDN-Controller and the device. In our case we translate from netconf to snmp and back. Additionally the mediator is listening for snmp traps to push them forward to the SDN-Controller. + +## Usage + +Standalone: +``` +./Netconf2SNMPMediator.sh [--cli] ../test.config ../yang/yangNeModel +``` +[MediatorServer](../../mediatorserver/): +``` +./mediators//start.sh +``` + +## Config-File +``` +{ + "Name":"", + "DeviceType":, + "DeviceIP":"", + "TrapPort":, + "NeXMLFile":"", + "NcPort":, + "ODLConfig":[{"Server":"","Port":,"User":"","Password":""}], + "IsNCConnected":false +} +``` + +## XML Ne File + +The xml network element file is the central element of the mediator. It contains all information about the interfaces, their capabilities and everything else of information which get requested through netconf. To connect specific netconf values to device specific snmp values we use the xml attributes of the node element. + +Attributes: + +|Name | Value | Description | +| ----| ---- | ---------- | +|oid | <oid dotted string> | Attribute with SNMP mapping for given oid. For NETCONF-get, request content from Device| +|access | read-only / read-write | decides if only snmp-get or get and set-requests are allowed| +|conversion | <conv-method> | Convert the snmp-value to netconf-value and back| +|default | <any value> | the default netconf value if there is no response from the device | +|validator | regex | to validate the netconf value to avoid protocol errors | + + +Conversion methods: + +Hint: All conversations shown here are the snmp-to-netconf value conversations. Some of these are working in both directions, some not. + + +| Conversion | bi-directional | Description | Example | +| ---------- |: -------------- :| ----------- | ------- | +|int-to-boolean | yes | Convert 1-true and not 1-false between boolean and int| 1=\>true, 0=\>false | +|int-to-boolean-dd,dd,dd-true | no | Convert listed numbers to true | | +|int-to-boolean-dd,dd,dd-false | no | Convert listed numbers to false| | +|if-dd,dd,dd-term1-term2 | no | if value listed, result is *term1*, if not *term2*| | +|map-dd1,dd2,dd3-term1-term2 | yes | Bidirectional map dd1 to term1, dd2 to term2 and soon | | +|divide-dd1 | yes | Divide value by dd1| divide-10: 99 =\> 9.9 =\> 10| +|dividen-dd1 | yes | Divide value by (-1*dd1)| dividen-10: 99 =\> -9.9 =\> -10| +|internal | yes | use inernally hardcoded conversion method | qpsk =\> 4, 16qam =\> 16 | + + +## Alarms + diff --git a/features/sdnr/odlux/helpserver/provider/src/main/resources/help/meta.json b/features/sdnr/odlux/helpserver/provider/src/main/resources/help/meta.json new file mode 100644 index 0000000..208bee0 --- /dev/null +++ b/features/sdnr/odlux/helpserver/provider/src/main/resources/help/meta.json @@ -0,0 +1,261 @@ +{ + "sdnr": { + "nodes": { + "connectApp": { + "versions": { + "0.4.0": { + "date": "2018-02-24", + "path": "sdnr/connect/README.md", + "label": "Connect" + }, + "current": { + "date": "2018-02-24", + "path": "sdnr/connect/README.md", + "label": "Connect" + } + }, + "label": "Connect" + }, + "faultApp": { + "versions": { + "0.4.0": { + "date": "2018-02-24", + "path": "sdnr/pnfFault/README.md", + "label": "Fault" + }, + "current": { + "date": "2018-02-24", + "path": "sdnr/pnfFault/README.md", + "label": "Fault" + } + }, + "label": "Fault" + }, + "maintenanceApp": { + "versions": { + "0.4.0": { + "date": "2018-09-13", + "path": "sdnr/pnfMaintenance/README.md", + "label": "Maintenance" + }, + "current": { + "date": "2018-09-13", + "path": "sdnr/pnfMaintenance/README.md", + "label": "Maintenance" + } + }, + "label": "Maintenance" + }, + "configurationApp": { + "versions": { + "0.4.0": { + "date": "2018-02-24", + "path": "sdnr/pnfConfig/README.md", + "label": "Config" + }, + "current": { + "date": "2018-02-24", + "path": "sdnr/pnfConfig/README.md", + "label": "Config" + } + }, + "label": "Config" + }, + "performanceHistoryApp": { + "versions": { + "0.4.0": { + "date": "2018-02-24", + "path": "sdnr/pnfPerformance/README.md", + "label": "Performance" + }, + "current": { + "date": "2018-02-24", + "path": "sdnr/pnfPerformance/README.md", + "label": "Performance" + } + }, + "label": "Performance" + }, + "inventoryApp": { + "versions": { + "0.4.0": { + "date": "2018-02-24", + "path": "sdnr/pnfInventory/README.md", + "label": "Inventory" + }, + "current": { + "date": "2018-02-24", + "path": "sdnr/pnfInventory/README.md", + "label": "Inventory" + } + }, + "label": "Inventory" + }, + "mediatorApp": { + "versions": { + "0.4.0": { + "date": "2018-02-24", + "path": "sdnr/pnfMediator/README.md", + "label": "Mediator" + }, + "current": { + "date": "2018-02-24", + "path": "sdnr/pnfMediator/README.md", + "label": "Mediator" + } + }, + "label": "Mediator" + }, + "eventLogApp": { + "versions": { + "0.4.0": { + "date": "2020-02-03", + "path": "sdnr/pnfEventLog/README.md", + "label": "EventLog" + }, + "current": { + "date": "2020-02-03", + "path": "sdnr/pnfEventLog/README.md", + "label": "EventLog" + } + }, + "label": "EventLog" + }, + "networkApp": { + "versions": { + "0.4.0": { + "date": "2018-02-24", + "path": "sdnr/networkMap/README.md", + "label": "NetworkMap" + }, + "current": { + "date": "2018-02-24", + "path": "sdnr/networkMap/README.md", + "label": "NetworkMap" + } + }, + "label": "NetworkMap" + }, + "linkCalculationApp": { + "versions": { + "0.4.0": { + "date": "2018-02-24", + "path": "sdnr/linkCalculator/README.md", + "label": "LinkCalculator" + }, + "current": { + "date": "2018-02-24", + "path": "sdnr/linkCalculator/README.md", + "label": "LinkCalculator" + } + }, + "label": "LinkCalculator" + } + }, + "versions": { + "0.4.0": { + "date": "2018-02-24", + "path": "sdnr/README.md", + "label": "SDN-R" + }, + "current": { + "date": "2018-02-24", + "path": "sdnr/README.md", + "label": "SDN-R" + } + }, + "label": "SDN-R" + }, + "mediatorserver": { + "nodes": { + "installation": { + "versions": { + "0.4.0": { + "date": "", + "path": "mediatorserver/installation/README.md", + "label": "" + }, + "current": { + "date": "", + "path": "mediatorserver/installation/README.md", + "label": "" + } + }, + "label": "Installation" + }, + "mediator": { + "versions": { + "0.4.0": { + "date": "", + "path": "mediatorserver/mediator/README.md", + "label": "" + }, + "current": { + "date": "", + "path": "mediatorserver/mediator/README.md", + "label": "" + } + }, + "label": "Mediator" + } + }, + "versions": { + "0.4.0": { + "date": "", + "path": "mediatorserver/README.md", + "label": "" + }, + "current": { + "date": "", + "path": "mediatorserver/README.md", + "label": "" + } + }, + "label": "MediatorServer" + }, + "faq": { + "versions": { + "0.4.0": { + "date": "2018-02-24", + "path": "sdnr/faq.md", + "label": "FAQ" + }, + "current": { + "date": "2018-02-24", + "path": "sdnr/faq.md", + "label": "FAQ" + } + }, + "label": "FAQ" + }, + "abbreviations": { + "versions": { + "0.4.0": { + "date": "2018-02-24", + "path": "sdnr/abbreviations.md", + "label": "Abbreviations" + }, + "current": { + "date": "2018-02-24", + "path": "sdnr/abbreviations.md", + "label": "Abbreviations" + } + }, + "label": "Abbreviations" + }, + "general": { + "versions": { + "0.4.0": { + "date": "2018-02-24", + "path": "sdnr/general.md", + "label": "General Functionality" + }, + "current": { + "date": "2018-02-24", + "path": "sdnr/general.md", + "label": "General Functionality" + } + }, + "label": "General Functionality" + } +} \ No newline at end of file diff --git a/features/sdnr/odlux/helpserver/provider/src/main/resources/help/sdnr/ONAP-SDN-R.png b/features/sdnr/odlux/helpserver/provider/src/main/resources/help/sdnr/ONAP-SDN-R.png new file mode 100644 index 0000000000000000000000000000000000000000..6a9f2fac886f333cc21c63781b803c34a3fd7648 GIT binary patch literal 195578 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

7oacKzADPx5NUR4_9Abg0m-uff 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 Kn-ZxHe*Xt0A1)yP literal 0 HcmV?d00001 diff --git a/features/sdnr/odlux/helpserver/provider/src/main/resources/help/sdnr/README.md b/features/sdnr/odlux/helpserver/provider/src/main/resources/help/sdnr/README.md new file mode 100644 index 0000000..fa8ce0d --- /dev/null +++ b/features/sdnr/odlux/helpserver/provider/src/main/resources/help/sdnr/README.md @@ -0,0 +1,8 @@ +# SDN controller for 'Radio' (SDN-R) + +SDN-R adds features and functionality to the OpenDaylight-based ONAP controller 'CCSDK/SDNC'. It is built on the Common Controller Framework to control and manage wireless resources. Wireless resources are virtual network functions (e.g. vBBU, vEPC) or physical network functions (e.g. microwave and millimeter wave radios, eNodeB, RRH, DAS equipment). + +SDN-R is integrated into ONAP using DMaaP APIs. It is interfacing with PNFs and VNFs and with other ONAP components, such as A&AI, DCAE and SO. +[See abbreviations](abbreviations.md) + +![SDN-R in ONAP](./ONAP-SDN-R.png "SDN-R in ONAP") diff --git a/features/sdnr/odlux/helpserver/provider/src/main/resources/help/sdnr/abbreviations.md b/features/sdnr/odlux/helpserver/provider/src/main/resources/help/sdnr/abbreviations.md new file mode 100644 index 0000000..120d685 --- /dev/null +++ b/features/sdnr/odlux/helpserver/provider/src/main/resources/help/sdnr/abbreviations.md @@ -0,0 +1,212 @@ +# Abbreviations + +|**Abbreviation**|**Description**| +|----------------|---------------| +| AAF | [Application Authorization Framework](https://wiki.onap.org/display/DW/Application+Authorization+Framework+Project) | +| A&AI | [Active & Available Inventory](https://wiki.onap.org/display/DW/Active+and+Available+Inventory+Project) | +| AAA | [Authentication, Authorization and Accounting](https://en.wikipedia.org/wiki/AAA_(computer_security)) | +| AEC | AT&T Edge Cloud | +| AIC | AT&T Integrated Cloud | +| AID | Architecture Integration Document | +| APPC | [Application Controller](https://wiki.onap.org/display/DW/Application+Controller+Project) | +| BPEL | Business Process Execution Language | +| BPMN | [Business Process Model and Notation or Business Process Management Notation](https://en.wikipedia.org/wiki/Business_Process_Model_and_Notation) | +| BRMS | Business Rules Management System | +| BSS | Business Support System | +| CCSDK | Common Controller SDK project | +| CDAP | [Cask Data Application Platform](https://cdap.io/) | +| CDR | Charging Data Record | +| CDS | Controller Design Studio | +| CI/CD | Continuous Integration / Continuous Delivery | +| Cinder | OpenStack Block Storage | +| CL | Control Loop | +| CLAMP | Closed Loop Automation Management Platform (project) | +| CLI | [Command Line Interface (project)](https://wiki.onap.org/display/DW/Command+Line+Interface+Project) | +| CMA | Change Management Application (within ONAP) | +| CN | Core Network | +| CNF | Cloud Native network Function. | +| COE | Container Orchestration Engine | +| CPE | Customer Premise Equipment | +| CSAR | [Cloud Service ARchive (link)](http://openbaton.github.io/documentation/tosca-CSAR-onboarding/) | +| CU | Centralized Unit | +| DAO | [Data Access Object](https://en.wikipedia.org/wiki/Data_access_object) | +| DCAE | [Data Collection Analytics and Events](https://wiki.onap.org/pages/viewpage.action?pageId=6592895) | +| DDoS | Distributed Denial-of-Service attack | +| DG | Directed Graph | +| DG Builder | Directed Graph Builder | +| DMaaP | [Data Movement as a Platform](https://wiki.onap.org/display/DW/DMaaP) | +| DME | [Direct Messaging Engine (common service within ONAP)](https://wiki.onap.org/display/DW/Common+Services) | +| DMIP | Device Management Interface Profile | +| DNS | Domain Name System | +| DPDK | Data Plane Development Kit | +| DU | Distributed Unit | +| ECA | External Content Adapter | +| ECOMP | Enhanced, Control, Orchestration, Management and Policy | +| EELF | [Event&Error LoggingFramework](https://wiki.onap.org/display/DW/Common+Services) | +| EMS | [Element Management System](https://en.wikipedia.org/wiki/Element_management_system) | +| ESR | [External System Register](https://wiki.onap.org/pages/viewpage.action?pageId=5734948) | +| ETSI | [European Telecommunications Standards Institute](http://www.etsi.org/technologies-clusters/technologies/689-network-functions-virtualisation) | +| EUAG | [ONAP End User Advisory Group](https://wiki.lfnetworking.org/pages/viewpage.action?pageId=2916362) | +| FCAPS | Fault Configuration Accounting Performance Security | +| FM | Fault Management | +| FQDN | Fully Qualified Domain Name | +| GBP | [Group-Based Policy ](https://wiki.openstack.org/wiki/GroupBasedPolicy) | +| Glance | OpenStack Image repository | +| GNFC | [Genric Network Function Controller](https://wiki.onap.org/download/attachments/45300148/ONAP_GNF_ControllersSOL003.pptx?version=1&modificationDate=1548619943000&api=v2) | +| GUI | [Graphical User Interface](https://en.wikipedia.org/wiki/Graphical_user_interface) | +| HAS | [Homing and Allocation Service](https://wiki.onap.org/pages/viewpage.action?pageId=16005528) | +| HDFS | Hadoop Distributed File System | +| Horizon | OpenStack GUI | +| HPA | Hardware Platform Awareness | +| HTTP | HyperText Transfer Protococol | +| HV VES | [High Volume Virtual function Event Stream](https://wiki.onap.org/display/DW/High+Volume+VES+Collector) | +| IAM/IDAM | Identity and Access Management | +| ICE | Incubation and Certification Environment | +| ICMMS | Syniverse Inter-carrier messaging solution | +| IDS | [Intrusion Detection System](https://en.wikipedia.org/wiki/Intrusion_detection_system) | +| IETF | [Internet Engineering Task Force](http://www.ietf.org) | +| IKE | [Internet KeyExchange](https://en.wikipedia.org/wiki/Internet_Key_Exchange) | +| IPS | [Intrusion Prevention System](https://en.wikipedia.org/wiki/Intrusion_detection_system) | +| IPSEC | [Internet Protocol Security](https://en.wikipedia.org/wiki/IPsec) | +| JAR | [Java Archive](https://en.wikipedia.org/wiki/JAR_(file_format)) | +| JSC | [JavaServiceContainer](https://wiki.onap.org/display/DW/Common+Services) | +| JSON | JavaScript Object Notation | +| Keystone | OpenStack Authorization Project | +| KVM | Kernel-based Virtual machine | +| LCM | Life Cycle Management | +| LDAP | Lightweight Directory Access Protocol | +| LFN CVC | [Linux Foundation Networking Compliance/Verification Committee](https://wiki.onap.org/display/DW/LFN+CVC+Testing+in+VNFSDK) | +| LFN CVP | [Linux Foundation Networking Compliance/Verification Program](https://wiki.onap.org/display/DW/LFN+CVC+Testing+in+VNFSDK) | +| LRM | Local Resource Monitor | +| M0 | Release Kick-off milestone. See also Release Lifecycle | +| M1 | Release Planning milestone. See also Release Lifecycle | +| M2 | Release Functionality Freeze milestone. See also Release Lifecycle | +| M3 | Release API Freeze milestone. See also Release Lifecycle | +| M4 | Release Code Freeze milestone. See also Release Lifecycle | +| MACD | (Vendor specific) Move Add Change Delete/Disconnect | +| MANO | MANagement and Organization of NFV | +| MD-SAL | Model Driven Service Abstraction Layer | +| MIND | Master Integrated Network Directory | +| MR | Message Router (a Common Service of ONAP) | +| MOP | Method of Procedure | +| MOTS | Mechanized Operations Tracking System | +| MSB | Microservice Bus | +| MSO | [Master Service Orchestrator](https://wiki.onap.org/pages/viewpage.action?pageId=1015834) | +| MVP | [Minimum ViableProduct](https://en.wikipedia.org/wiki/Minimum_viable_product) | +| NAI | Network Artificial Intelligence | +| NANCSP | Network Cloud Service Provider | +| NBI | North Bound Interface | +| NEP | Network Equipment Provider | +| NETCONF | [Network Configuration Protocol](https://en.wikipedia.org/wiki/NETCONF) | +| Neutron | OpenStack Networking | +| NFMF | Network Function Management Function | +| NFV | [Network Function Virtualization](https://en.wikipedia.org/wiki/Network_function_virtualization) | +| NFVI | [network functions virtualization infrastructure](https://www.sdxcentral.com/nfv/definitions/nfv-mano/) | +| NOD | Network On Demand | +| Nova | OpenStack compute | +| NS | Network Services | +| NS | (Vendor Specific) New Start | +| NSI | Network Slice Subnet Instance | +| NSMF | Network Slice Management Function | +| NSSMF | Network Slice Subnet Management Function | +| O-RAN | [O-RAN Alliance , OperatorDefined NextGeneration RadioAccessNetworksAlliance ](https://www.o-ran.org) | +| O-RAN-SC | [O-RAN SoftwareCommunity](https://o-ran-sc.org) | +| OAM | Operation and Maintenance | +| OA&M | Operations, Administration and Management | +| OMF | Operational Management Framework (of ONAP) | +| OMSA | ONAP Microservice Architecture | +| ONAP | [Open Network Automation Platform](https://wiki.onap.org/pages/viewpage.action?pageId=1015843) | +| OOF | ONAP Optimization Framework | +| ODL | [OpenDaylight](https://www.opendaylight.org/) | +| OOM | [ONAP Operations Manager](https://wiki.onap.org/display/DW/OOM+User+Guide) | +| OpenStack | A cloud operating system | +| OPNFV | [Open Platform for NFV Project](https://www.sdxcentral.com/nfv/definitions/opnfv/) | +| Originato | MMSC Any Non-AT&T MMSC that originally sent the request to ICMMS | +| OSAM | [Open Source Access Manager](https://wiki.onap.org/display/DW/OpenSource+Access+Manager+%28OSAM%29+Use+Case) | +| OSC | O-RAN Software Community or Optical Supervisory Channel | +| OSS | Operations Support System | +| PAP | Policy Administration Point (ONAP) | +| PCE | Path Computation and Element (ONAP) | +| PCI | Physical Cell ID | +| pCPE | physical Customer Premise Equipment | +| PDP-x | Policy Decision Point - XACML (ONAP) | +| PDP-d | Policy Decision Point - Drools (ONAP) | +| PM | Performance Management | +| PNDA | [OpensourcePlatform for Network DataAnalytics](https://wiki.onap.org/display/DW/Integrating+PNDA) | +| PNF | Physical Network Function | +| PnP | Plug and Play | +| PO | Platform Orchestrator | +| PoC | Proof of Concept | +| POMBA | [Post Orchestration Model Based Audit](https://wiki.onap.org/display/DW/POMBA) | +| PRH | Phyiical Network Function Registration Handler | +| Pub/Sub | Publisher/Subscriber | +| RAN | Radio Access Network | +| RCA | Root Cause Analysis | +| RCT | Reference Connection Tool | +| REST | [Representational State Transfer](https://en.wikipedia.org/wiki/Representational_state_transfer) | +| RESTCONF | A protocol based on HTTP for configuring data defined in YANG | +| RO | Resource Orchestrator | +| RPC | Remote Procedure Call | +| RU | Radio Unit | +| RRH | Remote Radio Head | +| S3P | [Stability, Security, Scalability, Performance](https://wiki.onap.org/pages/viewpage.action?pageId=16003367) | +| SDC | Service Design and Creation (component of ONAP for visual modeling and design) | +| SDN | [Software-defined networking](https://en.wikipedia.org/wiki/Software-defined_networking) | +| SDN-C | [SDN-Controller](https://wiki.onap.org/display/DW/SDN+Controller+Development+Guide) | +| SDN-R | [SDN-Radio, Router, ROADM - a set of model-driven application based on SDN-C](https://wiki.onap.org/display/DW/SDN-R) | +| SDN-GP | Software Defined Network - Global Platform | +| SEBA | SDN-Enabled Broadband Access, see also | +| SLA | Service Level Agreement | +| SLI | Service Logic Interpreter | +| SME | Subject Matter Expert | +| SMTP | Simple Mail Transfer Protocol | +| SNMP | Simple Network Management Protocol | +| SO | [Service Orchestrator (project)](https://wiki.onap.org/display/DW/Service+Orchestrator+Project) | +| SON | Self-Organizing Networks | +| SOT | Source Of Truth | +| SR-IOV | [Single-Root Input/Output Virtualization](https://en.wikipedia.org/wiki/Single-root_input/output_virtualization) | +| SRS | Software Requirements Specification | +| SSL | [SecureSocketsLayer](https://en.wikipedia.org/wiki/Transport_Layer_Security) | +| SUPP | (Vendor Specific) short for supplement, changing a connection before activation | +| Swagger | legacy name for the OpenAPI Specification | +| Swift | OpenStack Object storage | +| TCP | Transmission Control Protocol | +| TEM | Telecom Electronics Manufacturer | +| TLS | [Transport Layer Security](https://en.wikipedia.org/wiki/Transport_Layer_Security) | +| TN | Transport Network | +| TOSCA | [Topology and Orchestration Specification for Cloud Applications](https://www.oasis-open.org/committees/tc_home.php?wg_abbrev=tosca) | +| TPS | Transactions Per Second | +| TSC | Technical Steering Committee | +| U-UI | [Usecase UserInterface](https://wiki.onap.org/display/DW/Usecase+UI+Project) | +| UI | [User Interface](https://en.wikipedia.org/wiki/User_interface) | +| UUI | User to User Information | +| UX | [User Experience](https://en.wikipedia.org/wiki/User_experience) | +| vCE | virtual CE (Customer Edge) router (an example VNF) | +| vCPE | Virtual Customer Premise Equipment | +| vDNS | Virtual Domain Name Server (an example VNF) | +| VDU | [Virtualisation Deployment Unit](https://wiki.onap.org/display/DW/Comparison+of+Current+R3+Clean+Version+with+IFA011+v2.5.1) | +| VES | [Virtual function EventStream](https://wiki.opnfv.org/download/attachments/6819329/OPNVF%20VES.pptx?version=4&modificationDate=1466395653000&api=v2) | +| vF | Virtual Firewall (an example VNF) | +| VF | Virtual Function | +| VFC | [Virtual Function Controller](https://wiki.onap.org/display/DW/Virtual+Function+Controller+Project) | +| VFC | Virtual Function Component (Resource Onboarding) | +| vfModule | Virtual Function Module | +| VID | Virtual Instantiation Deployment | +| VID | [Virtual Infrastructure Deployment (Project)](https://wiki.onap.org/display/DW/Virtual+Infrastructure+Deployment+Project) | +| VIM | Virtualized Infrastructure Manager | +| VLAN | Virtual Local Area Network | +| VM | Virtual Machine | +| VNF | [Virtual Network Function](http://searchsdn.techtarget.com/definition/virtual-network-functions) | +| VNFC | Virtual Network Function Component | +| VNFD | [VNF Descriptor](https://wiki.onap.org/pages/viewpage.action?pageId=8226059) | +| VNFM | VNF Manager | +| VNO | Virtual Network Operator | +| vPE | virtual PE (Provider Edge) router (an example of a VNF) | +| VPP | [Vector Packet Processing](https://wiki.fd.io/view/VPP/What_is_VPP%3F) | +| VSP | Vendor Software Product (from SDC Demo Guide) | +| VTP | [VNF Test Platform](https://wiki.onap.org/pages/viewpage.action?pageId=43386304) | +| VVP | [VNF Validation Program](https://wiki.onap.org/display/DW/VNF+Validation+Program+Project) | +| WAR | [Web application Archive](https://en.wikipedia.org/wiki/WAR_(file_format)) | +| xNF | The combination of PNF and VNF; Network Function | +| YANG | Yet Another Next Generation - a Data Modeling Language for the Network Configuration Protocol (NETCONF) | + diff --git a/features/sdnr/odlux/helpserver/provider/src/main/resources/help/sdnr/connect/README.md b/features/sdnr/odlux/helpserver/provider/src/main/resources/help/sdnr/connect/README.md new file mode 100644 index 0000000..584fcdf --- /dev/null +++ b/features/sdnr/odlux/helpserver/provider/src/main/resources/help/sdnr/connect/README.md @@ -0,0 +1,19 @@ +# Connect + +The 'Connect' application on OpenDaylight provides up-to-date connectivity information about the network nodes exposing a NETCONF/YANG interface. It automatically displays new Nodes and their connection status. Usually, the NETCONF servers of the Nodes mount themselves. If necessary, they can be mounted manually by right-clicking on the row representing a node and selecting the 'mount' action. For better understanding of alarms and status, a connection status log lists all the connection status changes of OpenDaylight mount points. + +## Views + +The graphical user interface is divided into two sections. + +### Nodes + +Nodes are network functions with a NETCONF/YANG management and control interface. A table view shows all configured and connected NETCONF Servers of the SDN-R cluster. This view also allows to manually configure/mount a node via the '+' button. The SDN controller will start connecting to the NETCONF server. + +Nodes can be marked as 'required'. If a node is required, it will stay available even if disconnected. If a node is not required, it will be deleted once disconnected. + +By right-clicking on a row representing a node, an action menu opens. The menu allows to mount, unmount, view the details, edit and remove the node. Additionally, it links to several applications like Fault and Configure, which will be filtered to display information relevant to the selected node. + +### Connection Status Log + +The log lists the connection status changes between SDN Controller and NETCONF servers (devices, Network Elements, network functions). \ No newline at end of file diff --git a/features/sdnr/odlux/helpserver/provider/src/main/resources/help/sdnr/faq.md b/features/sdnr/odlux/helpserver/provider/src/main/resources/help/sdnr/faq.md new file mode 100644 index 0000000..f9d5e50 --- /dev/null +++ b/features/sdnr/odlux/helpserver/provider/src/main/resources/help/sdnr/faq.md @@ -0,0 +1,72 @@ +# Frequently asked questions + + + +## Which browser should I use to operate Opendaylight SDN-R User interface? + +An actual version of [Google Chromium](https://www.chromium.org/getting-involved/download-chromium "Download Chromium") or +[Google Chrome](https://www.google.de/search?q=chrome+download&oq=chrome+download&aqs=chrome..69i57j0l5.2718j0j4&sourceid=chrome&ie=UTF-8 "Download Chrome") is recommended. + +- - - + + +## How to enable detailed logs in karaf for SDN-R applications + +If you like to see more details in karaf logs for the NetConf communication between ODL and NetConf servers (mediators/devices) please invoke the following commands in the karaf console. + +``` +# Logging settings (on) +log:set DEBUG org.opendaylight.mwtn +log:set TRACE org.opendaylight.netconf +log:set TRACE com.highstreet.technologies.odl.app +``` + +Please note, setting the debug level to 'TRACE' may impact the performance on the controller. In production environment make sure to set back the debug level to 'INFO' as soon possible. + + +``` +# Logging settings (off) +log:set INFO org.opendaylight.mwtn +log:set INFO org.opendaylight.netconf +log:set INFO com.highstreet.technologies.odl.app +``` + +- - - + +## Which commands should be used to analyse karaf logs? + +``` +cd $ODL_KARAF_HOME/data/log +rm *.txt +grep -anr --include=*.log* "| ERROR |" . | grep 2018 >> 01-error.txt +grep -anr --include=*.log* "RemoteDevice{" . | grep 2018 >> 02-devices.txt +grep -anr --include=*.log* "RemoteDevice{" . | grep "Unable to build schema context, unsatisfied imports" | grep 2018 >> 03-schema-issue.txt +grep -anr --include=*.log* "Matched request:" . | grep 2018 >> 04-matched-request.txt +grep -anr --include=*.log* "network-element" . | grep 2018 >> 05-network-element.txt +grep -anr --include=*.log* "urn:onf:params:xml:ns:yang:core-model" . | grep 2018 >> 06-core-module.txt +grep -anr --include=*.log* "PerformanceManagerTask" . | grep 2018 >> 07-pm-tick.txt +grep -anr --include=*.log* "Unable to read NE data for mountpoint" . | grep 2018 >> 08-unable-to-read.txt +grep -anr --include=*.log* "LKCYFL79Q01M01MSS801" . | grep 2018 >> 09-LKCYFL79Q01M01MSS801.txt +``` + + +## How to report an odlux issue + +If you would like to report an odlux issue which you have noticed in the Graphical User Interface, please provide the following information: + +1. **Description**: In which application you have noticed the issue? + +2. **Environment**: + - Which browser is used and the version of the browser. eg: *Google chrome - version 71.0.3578.80 / Mozilla Firefox.* + - Which Operating system and version. eg: *Linux/ Windows 10 - version 1803.* + - In which language you are using the application. + - The application URL which is available on the browser address bar. eg: *http://hostname/odlux/index.html#/connectApp* + +3. **Expected Result**: What is the expected result you are looking for? + +4. **Actual Result**: What is the actual result you got? + +5. **Steps to reproduce**: Describe the steps to reproduce the scenario. If possible, please provide the screenshots + +The above information helps us to analyze the problem quicker. + diff --git a/features/sdnr/odlux/helpserver/provider/src/main/resources/help/sdnr/general.md b/features/sdnr/odlux/helpserver/provider/src/main/resources/help/sdnr/general.md new file mode 100644 index 0000000..a91b485 --- /dev/null +++ b/features/sdnr/odlux/helpserver/provider/src/main/resources/help/sdnr/general.md @@ -0,0 +1,25 @@ +# General functionality + +The following functionality is common to all applications. + +### Table data export + +Every table can export its data via the '︙' button. +The data, which gets exported is the currently viewed data in the table. As the default pagination is set to 10, only the first 10 rows or filtered rows will be exported. To increase the number of exported rows, change the pagination. + +The behavior of the export can vary based on the browser: + +a) Some browsers allow you to save the file with the predefined name export.csv. In case your browser does not offer this function please use the 'Save as...' option and define the filename with extension csv. + +b) Some browsers save the file automatically with the alphanumeric name but without an extension. In such a case navigate to the downloaded file location and rename the file. The extension (csv) must be appended to the name. The result should look like 'export_file.csv'. + +### Table filters + +The following filters are supported by all tables based on the data type of the column. + +|Data type | Possible Filter | Example | +| ---------|---------------|---------| +| Text | Any characters or numbers, matches exactly unless a * or a ? are used. Both special characters act as wildcards, which can be used for contains, ends with and begins with queries. The * matches any number of characters whereas the ? matches exactly one character. Both wildcards can be used in the same query. |Test, T*, *st, Te?t, ?est | +| Numeric | < or <= or > or >= or exact number |>5000, 20, <=82 | +| Boolean |None (no filter set), true or false |true, false | + diff --git a/features/sdnr/odlux/helpserver/provider/src/main/resources/help/sdnr/linkCalculator/README.md b/features/sdnr/odlux/helpserver/provider/src/main/resources/help/sdnr/linkCalculator/README.md new file mode 100644 index 0000000..0c7e202 --- /dev/null +++ b/features/sdnr/odlux/helpserver/provider/src/main/resources/help/sdnr/linkCalculator/README.md @@ -0,0 +1,51 @@ +# Link Calculator + +The 'Link calculator' analyzes the microwave propagation measurements of the wireless links. It can be accessed through the Network Map by clicking on the 'link calculation' button available in microwave links. + +## View + +The app includes two view possibilities. If it is accessed via the menu, the view provides a form table and a blank information table. The form table offers inputs for latitude and longitude values of the two points of a link. By entering this geographical information the data in the information table gets updated. + +The information table contains the calculation inputs and outputs. If the Link Calculator is accessed through the 'calculate link' button, only this table with pre-filled geographical locations is shown. Currently, input variables of the link calculation include Polarization, Frequency, Rain Model, and Rainfall Rate. Outputs of the calculation are Free Space Loss and Rain Loss. The results will be visible upon clicking the 'Calculate' button at the bottom of the table. + +### Average Mean Sea Level + +Denotes the ground elevation of the sites on each end. + +### Antenna Height Above Ground + +Is the height at which the antenna is mounted from the ground. + +### Distance + +The distance in the information table is auto-filled when the microwave link is selected and the calculator is accessed through the 'calculate link' button in the Network Map. If the points are entered manually, the distance is calculated after clicking the 'Calculate' button. + +### Polarization + +A selection of Vertical and Horizontal polarization is possible. + +### Frequency + +A selection of known and regulated microwave bands is possible. + +### Rainfall Rate + +Rainfall rate can be entered in the field, however if the local information is not available, the digital map and rainfall values of ITU-R P.837-7 [^1] is used. The latitude grid is from -90 North degrees to +90 North degrees and the longitude grid is from -180 degrees East to +180 East. For this calculation, the pre-computed R_0.01 map is used. A selection is possible through the Rain Model drop-down list. When the ITU model is selected, the rainfall rate will be shown in the rainfall rate field after clicking the 'Calculate' button. + +## Calculations + +Wireless signal attenuation is calculated based on ITU Recommendations for Propagation. At the moment these calculations include the free space loss and rain loss. + +### Free Space Loss + +Calculates the Free Space Path Loss for a point-to-point non-terrestrial link using the recommended formula in ITU-R P.525-4 [^2]. The output is shown in dB hence the distance is attributed in the calculation. + +### Rain Loss + +Calculates the rain induced attenuation on microwave signal. The calculation is based on the recommended formula in ITU-R P.838-3 [^3], taking into account the polarization of the signal, rainfall rate, and distance. The manual calculation is also possible if 'Specific Rain' is selected as rain model. After selecting the inputs, rain loss will be calculated by clicking the 'Calculate' button. + +------------------------------------------------ + +[^1]: Radiocommunication Sector of International Telecommunication Union. ITU-R P.837-7: Characteristics of precipitation for propagation modelling 2017. +[^2]: Radiocommunication Sector of International Telecommunication Union. ITU-R P.525-4: Calculation of free-space attenuation 2019. +[^3]: Radiocommunication Sector of International Telecommunication Union. ITU-R P.838-3: Specific attenuation model for rain for use in prediction methods 2005. \ No newline at end of file diff --git a/features/sdnr/odlux/helpserver/provider/src/main/resources/help/sdnr/networkMap/README.md b/features/sdnr/odlux/helpserver/provider/src/main/resources/help/sdnr/networkMap/README.md new file mode 100644 index 0000000..b538eb1 --- /dev/null +++ b/features/sdnr/odlux/helpserver/provider/src/main/resources/help/sdnr/networkMap/README.md @@ -0,0 +1,46 @@ +# Network Map + +The 'Network Map' visualizes a network by showing the location of a site and its connections (links) to other sites in a geographical context. + +## Views + +The 'Network Map' consists of two side-by-side views: The map and the details-panel. + +### Map + +The geographical map visualizes sites and links of a network. Sites are usually displayed as blue circles and links are shown as lines connecting sites. + +If a link or site is clicked, its information is presented in the details panel. If more than one site or link is clicked, or if links or sites are too close together to determine which element should be selected, a selection popup appears to select one of the elements. + +The map offers statistics information to visualize the number of links and sites in the currently shown map area. The statistics information gets updated when the map stops moving. + +Additionally, the map offers a search field. The user can enter the name of a site or link. If an element was found, the map will center on the given element and its information is loaded by the details panel. + +If the zoom level is bigger than 11 and the loaded sites have a type of high-rise building, datacenter, factory, or street-lamp, the blue circles are swapped against icons, which visualizes the type of site. + +The swapping of icons can be activated or deactivated via a switch on the left-hand site of the map. The switch only becomes visible, if the zoom level is bigger than 9. + +The map supports zoom levels between 0 (furthest zoomed out, the entire world is visible) and 18 (most detailed). + +Whenever the map stops moving, it updates the URL with its current latitude, longitude, and zoom values. If the 'Network Map' application is opened with those URL parameters present, it will display the given area. That way, the map can be bookmarked or shared and will always display the same result. + + +### Details + +The details panel shows information specific to the selected element. + +Sites offer information about itself, such as name, address and owner, and a short overview of its links and nodes data. The nodes are physical network elements, comparable to the elements of the 'connect' application, and offer an interface to other apps via buttons, such as connect, configure, and fault. Currently, those buttons are disabled. By clicking on a link, the given link is loaded into details. + +If a link of type 'microwave' is selected, the 'calculate link' button is available, which opens the [Link Calculator](linkCalculator.html) in a new tab or page. + +Just like the map, the details panel updates the URL if data is loaded. Once again, the 'Network Map' application will try to load the element specified in the URL, if one is present. + +## Connection Error + +If no tile or network data is available, an error popup is shown. + +## Load Network- and Tile-Data + +On startup of the sdnc-web container, a topology URL for the network data and a tile URL for the tiles can be specified. + +A ready-to-use topology server offering pre-defined network data is available here. There is no way to import generic network data as of now. \ No newline at end of file diff --git a/features/sdnr/odlux/helpserver/provider/src/main/resources/help/sdnr/pnfConfig/README.md b/features/sdnr/odlux/helpserver/provider/src/main/resources/help/sdnr/pnfConfig/README.md new file mode 100644 index 0000000..62bf907 --- /dev/null +++ b/features/sdnr/odlux/helpserver/provider/src/main/resources/help/sdnr/pnfConfig/README.md @@ -0,0 +1,22 @@ +# Configuration + +The application rely on yang-specifications provided by the device. It is Generic and generated user interface, generated by yang specs and capabilites of a device. Each view of a functional element is divided into capabilities, configuration, status, current problem, current performance and history performance information according to TR-532. + +A separate window is available for modifying the configuration. All changes made are sent to the device in a single NetConf bulk request. The operator is notified about successfully configuring the device. + +### Features + +Read yang specifications and generate related User interface out of it + + - Provide yang description as tool tip for fields + + - Consider specified type for input fields + + - Provide read and write access + +## Implementation + +The application is implemented as ODLUX web application using the RestConf northbound interface of the SDN controller. The key frameworks are: Typescript, React and material-ui. + +Connection status information is updated automatically using a web socket for notifications from OpenDaylight to the browser. + diff --git a/features/sdnr/odlux/helpserver/provider/src/main/resources/help/sdnr/pnfEventLog/README.md b/features/sdnr/odlux/helpserver/provider/src/main/resources/help/sdnr/pnfEventLog/README.md new file mode 100644 index 0000000..e289f42 --- /dev/null +++ b/features/sdnr/odlux/helpserver/provider/src/main/resources/help/sdnr/pnfEventLog/README.md @@ -0,0 +1,6 @@ +# Event Log + +The 'EventLog' application displays application logs and messages automatically created by the different active applications. +SDN-R offers a common log service so that PNFs or other ONAP components can log their data and users can analyze and export the data in a common way. + + diff --git a/features/sdnr/odlux/helpserver/provider/src/main/resources/help/sdnr/pnfFault/README.md b/features/sdnr/odlux/helpserver/provider/src/main/resources/help/sdnr/pnfFault/README.md new file mode 100644 index 0000000..cfac06d --- /dev/null +++ b/features/sdnr/odlux/helpserver/provider/src/main/resources/help/sdnr/pnfFault/README.md @@ -0,0 +1,31 @@ +# Fault Management + +To operate a network, it is important to get an overview about the currently raised alarms. The application offers basic fault management of devices supporting ONF-TR-532. The alarms are classified according to the severity level (warning, minor, major, critical). + +## Views + +The graphical user interface is separated into three views. + +### Current Problem List + +Lists all current active faults in the network. In addition, it also lists alarms sent by the SDN controller itself, which detects connection losses to the NetConf server (connectionLossOAM) or to a device via a mediator to a device (connectionLossNeOAM). + +### Alarm Notifications + +As long as the view is open, all alarm notifications received by the SDN Controller are listed. Please note that refreshing the view will start the collection again. Previous alarm notification can be viewed in the alarm log. + +### Alarm Log + +Next to the current active alarms an alarm log lists all alarm notifications of the past. + +## Implementation + +The application has two parts. While the server is listening for NetConf notifications to store them in the database, the client retrieves the information from the database and displays them in a table. + +The server synchronizes with the current alarm lists of the devices. Based on raised and cleared notifications, the current alarm status of the network is calculated. The current alarms are stored in a database. In addition, all Problem Notifications received by the SDN controller are stored. There is no logic implemented on the client side. + +An alarm status bar in the header of the web application informs the operator about the health status of the network. + +The ODLUX web application uses web sockets to update the graphical user interface of the Alarm Notifications (devices) and Connection Status Notifications in real-time. + + diff --git a/features/sdnr/odlux/helpserver/provider/src/main/resources/help/sdnr/pnfInventory/README.md b/features/sdnr/odlux/helpserver/provider/src/main/resources/help/sdnr/pnfInventory/README.md new file mode 100644 index 0000000..1f2dd88 --- /dev/null +++ b/features/sdnr/odlux/helpserver/provider/src/main/resources/help/sdnr/pnfInventory/README.md @@ -0,0 +1,30 @@ +# Inventory + +The application offers basic inventory management of devices supporting ONF-TR-512 and ietf-hardware. + +## Views + +The inventory application offers two different ways to visualize inventory data. + +### Tableview + +The view displays the inventory data of the network element – for example, serial-numbers and part-numbers according to the containment of the equipment – as a table. By right-clicking on an entry, the element can be viewed in the treeview. + +### Treeview + +The treeview visualizes relations between the inventory data of a network element. To load all relations, a '*' can be entered in the search-field. + + +##### Inventory Export: + +The '︙' button in the upper right corner of the table allows exporting the inventory data as a CSV file. + +Only the currently viewed table data is exported. As the default pagination is set to 10, only the first 10 rows or filtered rows would be exported. To increase the number of exported rows change the pagination. + +The behavior of the export can vary based on different browsers: + +a) Some browsers allow you to save the file with the predefined name export.csv. In case your browser does not offer this function please use the 'Save as...' option and define the filename with extension csv. + +b) Some browsers save the file automatically with the alphanumeric name but without an extension. In such a case navigate to the downloaded file location and rename the file. The extension (csv) must be appended to the name. The result should look like export_file.csv. + + diff --git a/features/sdnr/odlux/helpserver/provider/src/main/resources/help/sdnr/pnfMaintenance/README.md b/features/sdnr/odlux/helpserver/provider/src/main/resources/help/sdnr/pnfMaintenance/README.md new file mode 100644 index 0000000..f6bf538 --- /dev/null +++ b/features/sdnr/odlux/helpserver/provider/src/main/resources/help/sdnr/pnfMaintenance/README.md @@ -0,0 +1,7 @@ +# Maintenance + +The 'Maintenance' application on OpenDaylight provides information about planned maintenances of Network Elements, currently or in the future. Users can manage devices to set the maintenance mode so that no unnecessary alarms are created. When the device is in maintenance mode, alarms are not forwarded to DCAE. As soon as the maintenance is finished, the alarms will start flowing again. + +The 'active' field in the table shows if the Network Element is currently in maintenance mode or not. If it is 'active' it means the Network Element is currently undergoing maintenance, if 'not active' it means maintenance might have been set for a future date or is already completed. + +Users can disable the maintenance mode or change its start and end dates by using the available options in the actions column. diff --git a/features/sdnr/odlux/helpserver/provider/src/main/resources/help/sdnr/pnfMediator/README.md b/features/sdnr/odlux/helpserver/provider/src/main/resources/help/sdnr/pnfMediator/README.md new file mode 100644 index 0000000..ef20c15 --- /dev/null +++ b/features/sdnr/odlux/helpserver/provider/src/main/resources/help/sdnr/pnfMediator/README.md @@ -0,0 +1,7 @@ +# Mediator + +Some device vendors (Altiostar, CommScope, Dragonwave-X) use the [generic mediator framework](https://github.com/Melacon/NetConf2SNMP). Such mediators offer an API to create, delete, start and stop mediator instances. + +New mediator servers can be added via the '+' button. Afterward, a server can be selected to view all available mediator instances. + +A mediator instance can be started, stopped and deleted using the available actions. Additionally, its details can be viewed. The '+' button allows the user to add a new instance. During the creation, at least one 'ODL auto connect' configuration must be added. diff --git a/features/sdnr/odlux/helpserver/provider/src/main/resources/help/sdnr/pnfPerformance/README.md b/features/sdnr/odlux/helpserver/provider/src/main/resources/help/sdnr/pnfPerformance/README.md new file mode 100644 index 0000000..c217b5c --- /dev/null +++ b/features/sdnr/odlux/helpserver/provider/src/main/resources/help/sdnr/pnfPerformance/README.md @@ -0,0 +1,14 @@ +# Performance + +Performance Monitoring values measured by the devices are necessary to analyze and optimize the network. Therefore the application automatically retrieves all historical performance values from the devices and stores them in a database. The client retrieves the values from the database and displays them in a graphical user interface. + +## Performance history values + +After selecting a connected PNF supporting ONF-TR-532 and a physical interface, the application collects the received and centralized stored performance values for this interface. + +The values are visualized using two views: a line chart and a table, with the chart always shown first. To switch between them, toggle buttons can be used. The chart view offers a filter to quickly limit the shown values. To keep both views in sync, the filters of the chart and the table are connected. If one view is filtered, the other one gets updated in the background. + + + + + diff --git a/features/sdnr/odlux/helpserver/provider/src/test/java/org/onap/ccsdk/features/sdnr/wt/helpserver/test/TestMyServlet.java b/features/sdnr/odlux/helpserver/provider/src/test/java/org/onap/ccsdk/features/sdnr/wt/helpserver/test/TestMyServlet.java new file mode 100644 index 0000000..b7a1dcc --- /dev/null +++ b/features/sdnr/odlux/helpserver/provider/src/test/java/org/onap/ccsdk/features/sdnr/wt/helpserver/test/TestMyServlet.java @@ -0,0 +1,177 @@ +/* + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt + * ================================================================================================= + * 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========================================================================== + */ +package org.onap.ccsdk.features.sdnr.wt.helpserver.test; + +import static java.nio.file.StandardOpenOption.CREATE; +import static java.nio.file.StandardOpenOption.CREATE_NEW; +import static java.nio.file.StandardOpenOption.TRUNCATE_EXISTING; +import static java.nio.file.StandardOpenOption.WRITE; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; +import java.io.File; +import java.io.IOException; +import java.io.StringWriter; +import java.nio.file.Files; +import java.nio.file.OpenOption; +import javax.servlet.ServletException; +import javax.servlet.ServletOutputStream; +import javax.servlet.WriteListener; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mockito; +import org.onap.ccsdk.features.sdnr.wt.helpserver.HelpServlet; + + +public class TestMyServlet extends Mockito { + + private static final String GETHELPDIRECTORYBASE = "data"; + private static final String CONTENT = "abbccdfkamaosie aksdmais"; + + public static void createHelpFile(String filename, String content) { + File file = new File("bitnami/nginx/help" + filename); + File folder = file.getParentFile(); + if (!folder.exists()) { + folder.mkdirs(); + } + try { + if (file.exists()) { + file.delete(); + } + Files.write(file.toPath(), content.getBytes(), + new OpenOption[] {WRITE, CREATE_NEW, CREATE, TRUNCATE_EXISTING}); + } catch (IOException e1) { + fail(e1.getMessage()); + } + } + +// @Before +// public void init() { +// try { +// ExtactBundleResource.deleteRecursively(new File(GETHELPDIRECTORYBASE)); +// } catch (IOException e) { +// e.printStackTrace(); +// } +// } + + + // @Test We dont have implementation of meta in HelpServlet + public void testServlet() throws Exception { + + System.out.println("Test get"); + + HttpServletRequest request = mock(HttpServletRequest.class); + HttpServletResponse response = mock(HttpServletResponse.class); + + when(request.getRequestURI()).thenReturn("help/"); + when(request.getQueryString()).thenReturn("?meta"); + + ServletOutputStreamToStringWriter out = new ServletOutputStreamToStringWriter(); + when(response.getOutputStream()).thenReturn(out); + + HelpServlet helpServlet = null; + try { + helpServlet = new HelpServlet(); + System.out.println("Server created"); + createHelpFile("/meta.json", CONTENT); + + helpServlet.doOptions(request, response); + System.out.println("Get calling"); + helpServlet.doGet(request, response); + System.out.println("Get called"); + } catch (Exception e) { + fail(e.getMessage()); + } + if (helpServlet != null) { + helpServlet.close(); + } + + String result = out.getStringWriter().toString().trim(); + System.out.println("Result: '" + result + "'"); + assertEquals(CONTENT, result); + } + + @Test + public void testServlet2() { + this.testGetRequest("test/test.txt"); + this.testGetRequest("test.css"); + this.testGetRequest("test.eps"); + this.testGetRequest("test.pdf"); + } + + private void testGetRequest(String fn) { + HelpServlet helpServlet = new HelpServlet(); + createHelpFile("/" + fn, CONTENT); + HttpServletRequest request = mock(HttpServletRequest.class); + HttpServletResponse response = mock(HttpServletResponse.class); + + when(request.getRequestURI()).thenReturn("help/" + fn); + ServletOutputStreamToStringWriter out = new ServletOutputStreamToStringWriter(); + try { + when(response.getOutputStream()).thenReturn(out); + helpServlet.doGet(request, response); + } catch (ServletException | IOException e) { + fail(e.getMessage()); + } + try { + out.close(); + } catch (Exception e) { + } + try { + helpServlet.close(); + } catch (Exception e) { + } + + assertEquals("compare content for " + fn, CONTENT, out.getStringWriter().toString().trim()); + } + + public class ServletOutputStreamToStringWriter extends ServletOutputStream { + + // variables + private StringWriter out = new StringWriter(); + // end of variables + + public StringWriter getStringWriter() { + return out; + } + + @Override + public void write(int arg0) throws IOException { + out.write(arg0); + } + + @Override + public String toString() { + return out.toString(); + } + + @Override + public boolean isReady() { + return false; + } + + @Override + public void setWriteListener(WriteListener writeListener) { + } + + + } + +} diff --git a/features/sdnr/odlux/helpserver/provider/src/test/resources/simplelogger.properties b/features/sdnr/odlux/helpserver/provider/src/test/resources/simplelogger.properties new file mode 100644 index 0000000..1aa3824 --- /dev/null +++ b/features/sdnr/odlux/helpserver/provider/src/test/resources/simplelogger.properties @@ -0,0 +1,58 @@ +# +# ============LICENSE_START======================================================= +# ONAP : ccsdk features +# ================================================================================ +# Copyright (C) 2020 AT&T 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======================================================= +# +# + +# SLF4J's SimpleLogger configuration file +# Simple implementation of Logger that sends all enabled log messages, for all defined loggers, to System.err. + +# Default logging detail level for all instances of SimpleLogger. +# Must be one of ("trace", "debug", "info", "warn", or "error"). +# If not specified, defaults to "info". +org.slf4j.simpleLogger.defaultLogLevel=trace + +# Logging detail level for a SimpleLogger instance named "xxx.yyy.zzz". +# Must be one of ("trace", "debug", "info", "warn", or "error"). +# If not specified, the default logging detail level is used. +# org.slf4j.simpleLogger.log.xxx.yyy=debug +org.slf4j.simpleLogger.log.org.onap.ccsdk.features.sdnr.wt.devicemanager=debug +org.slf4j.simpleLogger.log.org.onap.ccsdk.features.sdnr.wt.devicemanager.base.internalTypes.Resources=info +org.slf4j.simpleLogger.log.org.onap.ccsdk.features.sdnr.wt.devicemanager.base.netconf.container=trace + +# Set to true if you want the current date and time to be included in output messages. +# Default is false, and will output the number of milliseconds elapsed since startup. +#org.slf4j.simpleLogger.showDateTime=false + +# The date and time format to be used in the output messages. +# The pattern describing the date and time format is the same that is used in java.text.SimpleDateFormat. +# If the format is not specified or is invalid, the default format is used. +# The default format is yyyy-MM-dd HH:mm:ss:SSS Z. +#org.slf4j.simpleLogger.dateTimeFormat=yyyy-MM-dd HH:mm:ss:SSS Z + +# Set to true if you want to output the current thread name. +# Defaults to true. +#org.slf4j.simpleLogger.showThreadName=true + +# Set to true if you want the Logger instance name to be included in output messages. +# Defaults to true. +#org.slf4j.simpleLogger.showLogName=true + +# Set to true if you want the last component of the name to be included in output messages. +# Defaults to false. +#org.slf4j.simpleLogger.showShortLogName=false diff --git a/features/sdnr/odlux/helpserver/provider/test/test.txt b/features/sdnr/odlux/helpserver/provider/test/test.txt new file mode 100644 index 0000000..6dc9c45 --- /dev/null +++ b/features/sdnr/odlux/helpserver/provider/test/test.txt @@ -0,0 +1 @@ +abbccdfkamaosie aksdmais \ No newline at end of file diff --git a/features/sdnr/odlux/odlux/.eslintignore b/features/sdnr/odlux/odlux/.eslintignore new file mode 100644 index 0000000..0a14783 --- /dev/null +++ b/features/sdnr/odlux/odlux/.eslintignore @@ -0,0 +1,13 @@ +**/dist/** +**/build/** +**/node_modules/** +apps/eventApp/**/* +apps/faultApp/**/* +apps/helpApp/**/* +apps/inventoryApp/**/* +apps/maintenanceApp/**/* +apps/mediatorApp/**/* +apps/performanceHistoryApp/**/* +#apps/microwaveApp/**/* +#apps/siteManagerApp/**/* +#apps/unmFaultManagementApp/**/* \ No newline at end of file diff --git a/features/sdnr/odlux/odlux/.eslintrc.json b/features/sdnr/odlux/odlux/.eslintrc.json new file mode 100644 index 0000000..f98e65b --- /dev/null +++ b/features/sdnr/odlux/odlux/.eslintrc.json @@ -0,0 +1,54 @@ +{ + "root": true, + "parser": "@typescript-eslint/parser", + "plugins": [ + "import", + "@typescript-eslint", + "react", + "react-hooks" + ], + "extends": [ + "airbnb-typescript" + ], + "parserOptions": { + "project": [ + "./tsconfig.json" + ], + "sourceType": "module" + }, + "settings": { + "react": { + "version": "detect" + } + }, + "rules": { + "no-console": "off", + "no-debugger": "off", + "import/no-cycle": "off", + "quotes": [ "error", "single" ], + "import/prefer-default-export": "off", + "lines-between-class-members": "off", + "no-nested-ternary": "off", + "no-unused-vars": "off", + "object-curly-newline": "off", //["error", { "multiline": true, "minProperties": 8, "consistent": true }], + "max-len": [ 2, 280, 2, { "ignoreUrls": true } ], + "@typescript-eslint/lines-between-class-members": [ "error", "always", { "exceptAfterOverload": true } ], + "@typescript-eslint/quotes": [ "error", "single" , { "avoidEscape": true } ], + "@typescript-eslint/no-unused-vars": [ "error", { "argsIgnorePattern": "^_", "varsIgnorePattern": "^_", "caughtErrorsIgnorePattern": "^_" } ], + "@typescript-eslint/naming-convention": [ "error", + { "format": [ "camelCase", "PascalCase", "UPPER_CASE", "snake_case" ], "leadingUnderscore": "allow", "selector": "default", "filter": { "regex": "(^&)|(^\\w+(-\\w+)+)", "match": false } } ], + "no-underscore-dangle": [ "error", { "allowAfterThis": true } ], + "no-param-reassign": [ "error", { "props": false } ], + "react/prop-types": [ "error", { "skipUndeclared": true } ], + "@typescript-eslint/member-delimiter-style": ["error"] + }, + "overrides": [ + { + "files": "**/handlers/*Handler.ts", + "rules": { + "no-param-reassign": "off", + "@typescript-eslint/default-param-last": "off" + } + } + ] +} \ No newline at end of file diff --git a/features/sdnr/odlux/odlux/.gitignore b/features/sdnr/odlux/odlux/.gitignore new file mode 100644 index 0000000..1577b0b --- /dev/null +++ b/features/sdnr/odlux/odlux/.gitignore @@ -0,0 +1,7 @@ +package-lock.json +npm +yarn-error.log +node/ +node_modules/ +dist/ +.vscode/ diff --git a/features/sdnr/odlux/odlux/LICENSE b/features/sdnr/odlux/odlux/LICENSE new file mode 100644 index 0000000..c5487c3 --- /dev/null +++ b/features/sdnr/odlux/odlux/LICENSE @@ -0,0 +1,15 @@ +============LICENSE_START======================================================================== +ONAP : ccsdk feature sdnr wt +================================================================================================= +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========================================================================== diff --git a/features/sdnr/odlux/odlux/README.md b/features/sdnr/odlux/odlux/README.md new file mode 100644 index 0000000..6e5d596 --- /dev/null +++ b/features/sdnr/odlux/odlux/README.md @@ -0,0 +1,111 @@ +# Developing a ODLUX application + +## Introduction + +ODLUX bundle contains the Browser based Grapical User Interface for SDN-R. +ODLUX is available as OSGi bundle that is running in Opendaylight Karaf environment, using the configured jetty server of Opendaylight. +Since ONAP Frankfurt a second WEB Server setup "sdncweb" is available, that extracts the JavaScrip files. + +## Prerequisites + +Actual version in framework pom.xml in the frontend-maven-plugin definition. + * Node + * Yarn + * Lerna + +You can install these globally or let it be installed by maven due "mvn clean install" + +* Maven: 3 or higher +* Java: 8 + +## Dev-Environment Installation + + * install NodeJS LTS https://nodejs.org/en/ or via packetmanager + * sudo npm install -g yarn + * sudo yarn global add lerna + * get framework from repository: git clone https://gerrit.onap.org/r/ccsdk/features + * in features/sdnr/wt/odlux you find a structure like this: + ``` + odlux + |-apps + |-core + |-framework + + ``` + * go to features/sdnr/wt/odlux/apps and create your app: + ``` + mvn archetype:generate -DarchetypeGroupId=org.onap.ccsdk.features.sdnr.wt \ + -DarchetypeArtifactId=odlux-app-archetype \ + -DgroupId= \ + -DartifactId= \ + -Dversion= \ + -DappName= + ``` + + * your start folder for your web application is src/ + * in src2/main/java are located the Java files and in src2/main/resources/ is the blueprint located + * with ```yarn start``` you can run your application due runtime in your application folder + * by default this will run on http://localhost:3100/index.html + * if you have added new dependencies you have to run ```lerna bootstrap``` in odlux/ + * build your app for development version you can use ```yarn run build``` or ```yarn run build:dev``` + * build for karaf with ```mvn clean install``` + + +## Including app into karaf environment + + * copy maven repository files to karaf repository e.g.: ```cp ~/.m2/repository/path/of/groupId/artifactId $KARAF_HOME/system/path/of/groupId/``` + * check if odlux-core is started in karaf console: ```feature:list | grep odlux``` + * if not install: ```sdnr-wt-odlux-core-feature``` + * start your app in karaf console: ```bundle:install -s mvn://``` + +## Including into ONAP sdnc docker container + + * add maven module to odlux/pom.xml + * add dependency to odlux/apps/app-feature/pom.xml and odlux/apps/app-installer/pom.xml + * build odlux/pom.xml + * this will automatically package your app into the packaged zip file of the installer + +## Details + +### Default menu positions + + * from 0 for top to 999 for bottom. + +``` +0 Connect +10 Fault +20 Maintenance +30 Configuration +40 Protection +50 Performance +60 Security +70 Inventory +80 Topology +90 Mediator +100 Help +``` + +### blueprint.xml + +``` + + + + + + + + +``` + * bundleName defines the applicationName => default javascript file: .js + * index defines the menu position. + +### MyOdluxBundle.java + + * is just for getting access to the resources of its bundle (implemented because of OSGi access restrictions) + +### pom.xml + + * The pom.xml in the framework subdirectory is the reference for ODLUX creation. [framework pom](framework/pom.xml) + * The node and yarn versions are specified + * A specific variant of "frontend-maven-plugin" is used to create the environment to compile to javascript. This modified frontend-maven-plugin installs node, yarn and (optionally lerna) to compile the typescript sources to javascript. These will be build into the dist folder. diff --git a/features/sdnr/odlux/odlux/apps/apiDemo/.babelrc b/features/sdnr/odlux/odlux/apps/apiDemo/.babelrc new file mode 100644 index 0000000..3d8cd12 --- /dev/null +++ b/features/sdnr/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/features/sdnr/odlux/odlux/apps/apiDemo/package.json b/features/sdnr/odlux/odlux/apps/apiDemo/package.json new file mode 100644 index 0000000..ff9e3c4 --- /dev/null +++ b/features/sdnr/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/features/sdnr/odlux/odlux/apps/apiDemo/pom.xml b/features/sdnr/odlux/odlux/apps/apiDemo/pom.xml new file mode 100644 index 0000000..10c3026 --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/apiDemo/pom.xml @@ -0,0 +1,105 @@ + + + + + 4.0.0 + + org.o-ran-sc.oam-controller.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/features/sdnr/odlux/odlux/apps/apiDemo/src/actions/modulesSuccess.ts b/features/sdnr/odlux/odlux/apps/apiDemo/src/actions/modulesSuccess.ts new file mode 100644 index 0000000..12fd3fc --- /dev/null +++ b/features/sdnr/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/features/sdnr/odlux/odlux/apps/apiDemo/src/handlers/apiDemoRootHandler.ts b/features/sdnr/odlux/odlux/apps/apiDemo/src/handlers/apiDemoRootHandler.ts new file mode 100644 index 0000000..128a032 --- /dev/null +++ b/features/sdnr/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/features/sdnr/odlux/odlux/apps/apiDemo/src/handlers/modulesHandler.ts b/features/sdnr/odlux/odlux/apps/apiDemo/src/handlers/modulesHandler.ts new file mode 100644 index 0000000..1984a2d --- /dev/null +++ b/features/sdnr/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/features/sdnr/odlux/odlux/apps/apiDemo/src/index.html b/features/sdnr/odlux/odlux/apps/apiDemo/src/index.html new file mode 100644 index 0000000..c01df6b --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/apiDemo/src/index.html @@ -0,0 +1,24 @@ + + + + + + + + + API Demo App + + + +

+ + + + + + \ No newline at end of file diff --git a/features/sdnr/odlux/odlux/apps/apiDemo/src/models/module.ts b/features/sdnr/odlux/odlux/apps/apiDemo/src/models/module.ts new file mode 100644 index 0000000..48772a7 --- /dev/null +++ b/features/sdnr/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/features/sdnr/odlux/odlux/apps/apiDemo/src/plugin.tsx b/features/sdnr/odlux/odlux/apps/apiDemo/src/plugin.tsx new file mode 100644 index 0000000..2f70d8e --- /dev/null +++ b/features/sdnr/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/features/sdnr/odlux/odlux/apps/apiDemo/tsconfig.json b/features/sdnr/odlux/odlux/apps/apiDemo/tsconfig.json new file mode 100644 index 0000000..ca65092 --- /dev/null +++ b/features/sdnr/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/features/sdnr/odlux/odlux/apps/apiDemo/webpack.config.js b/features/sdnr/odlux/odlux/apps/apiDemo/webpack.config.js new file mode 100644 index 0000000..6564bef --- /dev/null +++ b/features/sdnr/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/features/sdnr/odlux/odlux/apps/app-installer/pom.xml b/features/sdnr/odlux/odlux/apps/app-installer/pom.xml new file mode 100755 index 0000000..284bc06 --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/app-installer/pom.xml @@ -0,0 +1,181 @@ + + + + + 4.0.0 + + org.o-ran-sc.oam-controller.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} + + + ${project.groupId} + sdnr-odlux-app-networkMapApp + ${project.version} + + + ${project.groupId} + sdnr-odlux-app-microwaveApp + ${project.version} + + + + ${project.groupId} + sdnr-odlux-app-siteManagerApp + ${project.version} + + + ${project.groupId} + sdnr-odlux-app-unmFaultManagementApp + ${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/features/sdnr/odlux/odlux/apps/app-installer/src/assembly/assemble_mvnrepo_zip.xml b/features/sdnr/odlux/odlux/apps/app-installer/src/assembly/assemble_mvnrepo_zip.xml new file mode 100644 index 0000000..dfe5060 --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/app-installer/src/assembly/assemble_mvnrepo_zip.xml @@ -0,0 +1,47 @@ + + + + + + repo + + zip + + + + false + + + + target/assembly/ + . + + + + + + diff --git a/features/sdnr/odlux/odlux/apps/configurationApp/.babelrc b/features/sdnr/odlux/odlux/apps/configurationApp/.babelrc new file mode 100644 index 0000000..3d8cd12 --- /dev/null +++ b/features/sdnr/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/features/sdnr/odlux/odlux/apps/configurationApp/package.json b/features/sdnr/odlux/odlux/apps/configurationApp/package.json new file mode 100644 index 0000000..b1d7d95 --- /dev/null +++ b/features/sdnr/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/features/sdnr/odlux/odlux/apps/configurationApp/policies.json b/features/sdnr/odlux/odlux/apps/configurationApp/policies.json new file mode 100644 index 0000000..91a0abf --- /dev/null +++ b/features/sdnr/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/features/sdnr/odlux/odlux/apps/configurationApp/pom.xml b/features/sdnr/odlux/odlux/apps/configurationApp/pom.xml new file mode 100644 index 0000000..2093723 --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/configurationApp/pom.xml @@ -0,0 +1,109 @@ + + + + + 4.0.0 + + org.o-ran-sc.oam-controller.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/features/sdnr/odlux/odlux/apps/configurationApp/src/actions/deviceActions.ts b/features/sdnr/odlux/odlux/apps/configurationApp/src/actions/deviceActions.ts new file mode 100644 index 0000000..615faae --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/configurationApp/src/actions/deviceActions.ts @@ -0,0 +1,685 @@ +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, + createViewData, +} 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 class UpdateNewData extends Action { + constructor(public newData: 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; + + let newData: any = 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 (newData) { + // update view data + newData = newData[property]; + + } else if (viewElement.isList && !key) { + // handle new list element without key + if (pathParts[ind][1] === null) { + + // create new data if not already exists + newData = getState().configuration.viewDescription.newData; + if (!newData && viewElement && 'viewId' in viewElement) { + newData = createViewData(namespace, views[+viewElement.viewId], views); + dispatch(new UpdateNewData(newData)); + } + + } else if ((pathParts.length) - 1 > ind) { + // handle list without key which is not a new element + dispatch(new SetCollectingSelectionData(false)); + throw new Error('No key for list [' + property + ']'); + } + + 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 { + dataPath += `/${property}${key ? `=${key.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 & flatten + 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; + + } + } + + if (newData) { + // 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, newData, ds)); + } + + 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.status === 409) { + // special case: if this is a list without any response + if (isViewElementList(viewElement!)) { + // 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)); + } else { + // create display specification + const ds: DisplaySpecification = { + displayMode: DisplayModeType.displayAsObject, + viewSpecification: resolveViewDescription(defaultNS, vPath, viewSpecification), + }; + // update display specification + return dispatch(postProcessDisplaySpecificationActionCreator(vPath, { }, ds)); + } + + } else 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; + + let newData: any = null; + let newElement: any = 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 (newElement) { + // update view data + if (pathParts.length - 1 === ind) { + newElement[property] = data; + return dispatch(new UpdateNewData(newData)); + } else { + newElement[property] = Array.isArray(newElement[property]) ? [ ...newElement[property] ] : { ...newElement[property] }; + } + } else 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 (key === null) { + // set new data + const stateData = getState().configuration.viewDescription.newData; + newElement = newData = Array.isArray(stateData) ? [ ...stateData ] : { ...stateData }; + + 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 + ']'); + } + } + + } else if (pathParts.length - 1 > ind) { + dispatch(new SetCollectingSelectionData(false)); + throw new Error('No key for 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 element 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 || newData + ? [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 (newData) { + dispatch(new UpdateNewData(null)); + } + + 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/features/sdnr/odlux/odlux/apps/configurationApp/src/assets/icons/configurationAppIcon.svg b/features/sdnr/odlux/odlux/apps/configurationApp/src/assets/icons/configurationAppIcon.svg new file mode 100644 index 0000000..1b74cc4 --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/configurationApp/src/assets/icons/configurationAppIcon.svg @@ -0,0 +1,20 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/features/sdnr/odlux/odlux/apps/configurationApp/src/components/baseProps.ts b/features/sdnr/odlux/odlux/apps/configurationApp/src/components/baseProps.ts new file mode 100644 index 0000000..7187c0a --- /dev/null +++ b/features/sdnr/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/features/sdnr/odlux/odlux/apps/configurationApp/src/components/ifWhenTextInput.tsx b/features/sdnr/odlux/odlux/apps/configurationApp/src/components/ifWhenTextInput.tsx new file mode 100644 index 0000000..b176e5d --- /dev/null +++ b/features/sdnr/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/features/sdnr/odlux/odlux/apps/configurationApp/src/components/uiElementBoolean.tsx b/features/sdnr/odlux/odlux/apps/configurationApp/src/components/uiElementBoolean.tsx new file mode 100644 index 0000000..56fb93c --- /dev/null +++ b/features/sdnr/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/features/sdnr/odlux/odlux/apps/configurationApp/src/components/uiElementLeafList.tsx b/features/sdnr/odlux/odlux/apps/configurationApp/src/components/uiElementLeafList.tsx new file mode 100644 index 0000000..669ddff --- /dev/null +++ b/features/sdnr/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/features/sdnr/odlux/odlux/apps/configurationApp/src/components/uiElementNumber.tsx b/features/sdnr/odlux/odlux/apps/configurationApp/src/components/uiElementNumber.tsx new file mode 100644 index 0000000..b034278 --- /dev/null +++ b/features/sdnr/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/features/sdnr/odlux/odlux/apps/configurationApp/src/components/uiElementReference.tsx b/features/sdnr/odlux/odlux/apps/configurationApp/src/components/uiElementReference.tsx new file mode 100644 index 0000000..e3bb8f0 --- /dev/null +++ b/features/sdnr/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/features/sdnr/odlux/odlux/apps/configurationApp/src/components/uiElementSelection.tsx b/features/sdnr/odlux/odlux/apps/configurationApp/src/components/uiElementSelection.tsx new file mode 100644 index 0000000..ebd04da --- /dev/null +++ b/features/sdnr/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/features/sdnr/odlux/odlux/apps/configurationApp/src/components/uiElementString.tsx b/features/sdnr/odlux/odlux/apps/configurationApp/src/components/uiElementString.tsx new file mode 100644 index 0000000..8381d99 --- /dev/null +++ b/features/sdnr/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/features/sdnr/odlux/odlux/apps/configurationApp/src/components/uiElementUnion.tsx b/features/sdnr/odlux/odlux/apps/configurationApp/src/components/uiElementUnion.tsx new file mode 100644 index 0000000..8d232f5 --- /dev/null +++ b/features/sdnr/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/features/sdnr/odlux/odlux/apps/configurationApp/src/handlers/configurationAppRootHandler.ts b/features/sdnr/odlux/odlux/apps/configurationApp/src/handlers/configurationAppRootHandler.ts new file mode 100644 index 0000000..9cbd916 --- /dev/null +++ b/features/sdnr/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/features/sdnr/odlux/odlux/apps/configurationApp/src/handlers/connectedNetworkElementsHandler.ts b/features/sdnr/odlux/odlux/apps/configurationApp/src/handlers/connectedNetworkElementsHandler.ts new file mode 100644 index 0000000..d2863dd --- /dev/null +++ b/features/sdnr/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/features/sdnr/odlux/odlux/apps/configurationApp/src/handlers/deviceDescriptionHandler.ts b/features/sdnr/odlux/odlux/apps/configurationApp/src/handlers/deviceDescriptionHandler.ts new file mode 100644 index 0000000..cd01b09 --- /dev/null +++ b/features/sdnr/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/features/sdnr/odlux/odlux/apps/configurationApp/src/handlers/valueSelectorHandler.ts b/features/sdnr/odlux/odlux/apps/configurationApp/src/handlers/valueSelectorHandler.ts new file mode 100644 index 0000000..70d5eb2 --- /dev/null +++ b/features/sdnr/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/features/sdnr/odlux/odlux/apps/configurationApp/src/handlers/viewDescriptionHandler.ts b/features/sdnr/odlux/odlux/apps/configurationApp/src/handlers/viewDescriptionHandler.ts new file mode 100644 index 0000000..4d361bf --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/configurationApp/src/handlers/viewDescriptionHandler.ts @@ -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========================================================================== + */ + +import { IActionHandler } from '../../../../framework/src/flux/action'; + +import { UpdateViewDescription, UpdateOutputData, UpdateNewData } 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; + newData?: any; + 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, + }; + } else if (action instanceof UpdateNewData) { + state = { + ...state, + newData: action.newData, + }; + } + return state; +}; diff --git a/features/sdnr/odlux/odlux/apps/configurationApp/src/index.html b/features/sdnr/odlux/odlux/apps/configurationApp/src/index.html new file mode 100644 index 0000000..4a0496b --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/configurationApp/src/index.html @@ -0,0 +1,30 @@ + + + + + + + + + Configuration App + + + +
+ + + + + + \ No newline at end of file diff --git a/features/sdnr/odlux/odlux/apps/configurationApp/src/models/networkElementConnection.ts b/features/sdnr/odlux/odlux/apps/configurationApp/src/models/networkElementConnection.ts new file mode 100644 index 0000000..e1ef1ea --- /dev/null +++ b/features/sdnr/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/features/sdnr/odlux/odlux/apps/configurationApp/src/models/uiModels.ts b/features/sdnr/odlux/odlux/apps/configurationApp/src/models/uiModels.ts new file mode 100644 index 0000000..c9f5e80 --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/configurationApp/src/models/uiModels.ts @@ -0,0 +1,242 @@ +/** + * ============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; +}; + +export const isViewElementList = (viewElement: ViewElement): viewElement is ViewElementList => { + return viewElement && viewElement.uiType === 'object' && !!viewElement.isList; +}; + +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('ResolveFunction'); + +export type ViewSpecification = { + id: string; + ns?: string; + name?: string; + title?: string; + parentView?: string; + language: string; + ifFeature?: string; + augmentations?: 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/features/sdnr/odlux/odlux/apps/configurationApp/src/models/yang.ts b/features/sdnr/odlux/odlux/apps/configurationApp/src/models/yang.ts new file mode 100644 index 0000000..e4e59fb --- /dev/null +++ b/features/sdnr/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/features/sdnr/odlux/odlux/apps/configurationApp/src/pluginConfiguration.tsx b/features/sdnr/odlux/odlux/apps/configurationApp/src/pluginConfiguration.tsx new file mode 100644 index 0000000..7dd2d6a --- /dev/null +++ b/features/sdnr/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/features/sdnr/odlux/odlux/apps/configurationApp/src/services/restServices.ts b/features/sdnr/odlux/odlux/apps/configurationApp/src/services/restServices.ts new file mode 100644 index 0000000..0fcd945 --- /dev/null +++ b/features/sdnr/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, method: 'PUT' | 'POST' = 'PUT') { + return requestRestExt<{ [key: string]: any }>(path, { method, 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/features/sdnr/odlux/odlux/apps/configurationApp/src/services/yangService.ts b/features/sdnr/odlux/odlux/apps/configurationApp/src/services/yangService.ts new file mode 100644 index 0000000..bbd051a --- /dev/null +++ b/features/sdnr/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/features/sdnr/odlux/odlux/apps/configurationApp/src/utilities/verifyer.ts b/features/sdnr/odlux/odlux/apps/configurationApp/src/utilities/verifyer.ts new file mode 100644 index 0000000..9dd1203 --- /dev/null +++ b/features/sdnr/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/features/sdnr/odlux/odlux/apps/configurationApp/src/utilities/viewEngineHelper.ts b/features/sdnr/odlux/odlux/apps/configurationApp/src/utilities/viewEngineHelper.ts new file mode 100644 index 0000000..9a17936 --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/configurationApp/src/utilities/viewEngineHelper.ts @@ -0,0 +1,351 @@ +import { storeService } from '../../../../framework/src/services/storeService'; +import { WhenAST, WhenTokenType } from '../yang/whenParser'; + +import { + ViewSpecification, + ViewElement, + isViewElementReference, + isViewElementList, + isViewElementObjectOrList, + isViewElementRpc, + isViewElementChoice, + ViewElementChoiceCase, + isViewElementObject, +} 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 | undefined | null)][] => { + const pathParts: [string, (string | undefined | null)][] = []; + let partMatch: RegExpExecArray | null; + if (vPath) do { + partMatch = vPathParser.exec(vPath); + if (partMatch) { + pathParts.push([partMatch[1], partMatch[2] || (partMatch[0].includes('[]') ? null : 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 } })); +}; + +export const createViewData = (namespace: string | null, viewSpecification: ViewSpecification, views: ViewSpecification[]) => Object.keys(viewSpecification.elements).reduce<{ [name: string]: any }>((acc, cur) => { + const elm = viewSpecification.elements[cur]; + let currentNamespace = namespace; + const key = elm.id; + if (elm.default) { + acc[key] = elm.default || ''; + } else if (elm.uiType === 'boolean') { + acc[key] = false; + } else if (elm.uiType === 'number') { + acc[key] = 0; + } else if (elm.uiType === 'string') { + acc[key] = ''; + } else if (isViewElementObject(elm)) { + const view = views[+elm.viewId]; + if (view) { + if (view.ns) { + currentNamespace = view.ns; + } + acc[key] = createViewData(currentNamespace, view, views); + } + } else if (isViewElementList(elm)) { + acc[key] = []; + } + return acc; +}, {}); diff --git a/features/sdnr/odlux/odlux/apps/configurationApp/src/views/configurationApplication.tsx b/features/sdnr/odlux/odlux/apps/configurationApp/src/views/configurationApplication.tsx new file mode 100644 index 0000000..6280950 --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/configurationApp/src/views/configurationApplication.tsx @@ -0,0 +1,951 @@ +/** + * ============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 CheckIcon from '@mui/icons-material/Check'; +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; + isNewSubElement: 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, + isNewSubElement: 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?.includes('[]') || false; + const isNewSubElement: boolean = nextProps.vPath?.includes('[]') && !nextProps.vPath?.endsWith('[]') || false; + + const state = { + ...prevState, + isNew: isNew, + editMode: isNew, + isNewSubElement: isNewSubElement, + 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 => encodeURIComponent(String(row[id]))).join(','); + } else { + keyId = encodeURIComponent(String(row[listKeyProperty])); + } + if (listKeyProperty) { + navigate(`[${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, isNew, isNewSubElement } = 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) { + if (isNewSubElement || isNew) { + const index = this.props.vPath.lastIndexOf('[]'); + const newVPath = this.props.vPath.substring(0, index + ( isNewSubElement ? 2 : 0 )); + this.props.history.replace(`/configuration/${nodeId}/${newVPath}`); + } else { + 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 && this.props.vPath) { + // ensure only active choices will be contained + const resultingViewData = this.collectData(displaySpecification.viewSpecification.elements); + this.props.onUpdateData(this.props.vPath!, resultingViewData); + + const index = this.props.vPath.lastIndexOf('[]'); + const newVPath = this.props.vPath.substring(0, index + ( isNewSubElement ? 2 : 0 )); + this.props.history.replace(`/configuration/${nodeId}/${newVPath}`); + } + this.setState({ editMode: !editMode }); + }}> + { editMode + ? isNewSubElement + ? + : + : + } + +
|| 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/features/sdnr/odlux/odlux/apps/configurationApp/src/views/networkElementSelector.tsx b/features/sdnr/odlux/odlux/apps/configurationApp/src/views/networkElementSelector.tsx new file mode 100644 index 0000000..e96f40d --- /dev/null +++ b/features/sdnr/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/features/sdnr/odlux/odlux/apps/configurationApp/src/yang/whenParser.ts b/features/sdnr/odlux/odlux/apps/configurationApp/src/yang/whenParser.ts new file mode 100644 index 0000000..4956b13 --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/configurationApp/src/yang/whenParser.ts @@ -0,0 +1,289 @@ +enum WhenTokenType { + AND = 'AND', + OR = 'OR', + NOT = 'NOT', + EQUALS = 'EQUALS', + NOT_EQUALS = 'NOT_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) => { + if (!char) return false; + const code = char.charCodeAt(0); + return (code >= 65 && code <= 90) || (code >= 97 && code <= 122); +}; + +const isAlphaNumeric = (char: string) => { + if (!char) return false; + const code = char.charCodeAt(0); + return ( + isAlpha(char) || + (code >= 48 && code <= 57) || + code === 95 || // underscore + code === 45 || // hyphen + code === 47 || // slash + code === 58 || // colon + code === 46 // dot + ); +}; + +const isOperator = (char: string) => { + if (!char) return false; + const code = char.charCodeAt(0); + return code === 33 || code === 38 || code === 124 || code === 61; +}; + +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; + } + + if (isOperator(char)) { + let value = ''; + while (isOperator(char)) { + value += char; + char = input[++current]; + } + + switch (value) { + case '&&': + tokens.push({ type: WhenTokenType.AND }); + break; + case '||': + tokens.push({ type: WhenTokenType.OR }); + break; + case '!': + tokens.push({ type: WhenTokenType.NOT }); + break; + case '==': + tokens.push({ type: WhenTokenType.EQUALS }); + break; + case '!=': + tokens.push({ type: WhenTokenType.NOT_EQUALS }); + break; + default: + throw new TypeError(`I don't know what this operator is: ${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/features/sdnr/odlux/odlux/apps/configurationApp/src/yang/yangParser.ts b/features/sdnr/odlux/odlux/apps/configurationApp/src/yang/yangParser.ts new file mode 100644 index 0000000..ec242db --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/configurationApp/src/yang/yangParser.ts @@ -0,0 +1,1730 @@ +/* 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; + } + + private calculateExecutionOrder(moduleName: string, visited: Set = new Set()): number { + if (visited.has(moduleName)) { + return 0; + } + visited.add(moduleName); + + const module = this.modules[moduleName]; + const augments = module?.augments || {}; + const augmentPaths = Object.keys(augments); + + if (augmentPaths.length === 0) { + module.executionOrder = 0; + return 0; + } + + const orders = augmentPaths.map((path) => { + const pathParts = path.split('/'); + const lastPart = pathParts[pathParts.length - 1]; + const [ns] = lastPart.split(':'); + const baseModuleOrder = this.calculateExecutionOrder(ns, visited); + return baseModuleOrder + 1; + }); + + const maxOrder = Math.max(...orders); + module.executionOrder = maxOrder; + return maxOrder; + } + + public postProcess() { + // process all type refs + this._typeRefToResolve.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}]`); + } + }); + + // process all augmentations + Object.keys(this.modules).forEach((moduleName) => { + this.calculateExecutionOrder(moduleName); + }); + + const orderedModules = Object.values(this.modules) + .filter((m) => m.executionOrder) + .sort((a, b) => { + return a.executionOrder! - b.executionOrder!; + }); + + orderedModules.forEach((module) => { + 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 viewToAugment = this.resolveView(augKey); + const augmentations = module.augments[augKey]; + + if (!viewToAugment) { + console.warn(`Could not find view to augment [${augKey}] from [${module.name}].`); + return; + } + + if (augmentations && viewToAugment) { + augmentations.forEach(({ id }) => { + viewToAugment.augmentations = viewToAugment.augmentations || []; + viewToAugment.augmentations.push(id); + }); + } + }); + }); + + // build a map of views with all their augmentation level + const viewsWithNestedAugmentations = new Map(); + + // helper function to get maximum augmentation depth + const calculateAugmentationDepth = (view: ViewSpecification): number => { + // Return cached value if already calculated + if (viewsWithNestedAugmentations.has(view)) { + return viewsWithNestedAugmentations.get(view)!; + } + + // Base case: no augmentations + if (!view.augmentations || view.augmentations.length === 0) { + viewsWithNestedAugmentations.set(view, 0); + return 0; + } + + // Get depths of all child augmentations + let maxDepth = 0; + for (const augId of view.augmentations) { + const augView = this.views[+augId]; + for (const nestedAugId in augView.elements || {}) { + const nestedAug = augView.elements[nestedAugId]; + if (isViewElementObjectOrList(nestedAug)) { + const nestedView = this.views[+nestedAug.viewId]; + maxDepth = nestedView ? Math.max(maxDepth, calculateAugmentationDepth(nestedView)) : maxDepth; + } + } + } + + // Add 1 for current level and cache result + const totalDepth = maxDepth + 1; + viewsWithNestedAugmentations.set(view, totalDepth); + return totalDepth; + }; + + // process views from lowest to highest augmentation depth + const viewEntries = Object.entries(this.views.filter((v) => v.augmentations && v.augmentations.length > 0)) + .map(([, view]) => view) + .sort((a, b) => calculateAugmentationDepth(a) - calculateAugmentationDepth(b)); + + for (const view of viewEntries) { + if (!view.augmentations || view.augmentations.length === 0) continue; + + for (const augId of view.augmentations) { + const augmentation = this.views[+augId]; + + // merge elements from augmentation into main view + Object.keys(augmentation.elements).forEach((key) => { + const elm = augmentation.elements[key]; + + const when = + elm.when && augmentation.when + ? { + type: WhenTokenType.AND, + left: elm.when, + right: augmentation.when, + } + : elm.when || augmentation.when; + + const ifFeature = elm.ifFeature ? `(${augmentation.ifFeature}) and (${elm.ifFeature})` : augmentation.ifFeature; + + view.elements[key] = { + ...augmentation.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._modulesToResolve.forEach(cb => { + try { cb(); } catch (error) { + console.warn(error.message); + } + }); + + // execute all post processes like resolving in proper order + this._unionsToResolve.forEach(cb => { + try { cb(); } catch (error) { + console.warn(error.message); + } + }); + + const knownViews: ViewSpecification[] = []; + // resolve readOnly + const resolveReadOnly = (view: ViewSpecification, parentConfig: boolean) => { + if (knownViews.includes(view)) return; + knownViews.push(view); + + // 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 resolveViewElement(name: string, referenceView: ViewSpecification): ViewElement | null { + let element: ViewElement | null = null; + element = referenceView.elements[name]; + + if (element) { + return element; + } + + const augmentViewIds = referenceView.augmentations; + if (augmentViewIds) { + for (let i = 0; i < augmentViewIds.length; ++i) { + const augmentView = this._views[+augmentViewIds[i]]; + if (augmentView) { + element = this.resolveViewElement(name, augmentView); + if (element) break; + } + } + } + return element; + } + + + 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]!; + view = Object.values(this.views).find((v) => v.parentView === '0' && v.ns === moduleName) || null; + if (view) { + element = this.resolveViewElement(`${moduleName}:${partMatch[2]!}`, view); + } + } else if (isViewElementObjectOrList(element)) { + view = this._views[+element.viewId]; + if (moduleName !== partMatch[1]) { + moduleName = partMatch[1]; + element = this.resolveViewElement(`${moduleName}:${partMatch[2]}`, view); + } else { + element = this.resolveViewElement(partMatch[2], view); + } + } 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/features/sdnr/odlux/odlux/apps/configurationApp/tsconfig.json b/features/sdnr/odlux/odlux/apps/configurationApp/tsconfig.json new file mode 100644 index 0000000..c950056 --- /dev/null +++ b/features/sdnr/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/features/sdnr/odlux/odlux/apps/configurationApp/webpack.config.js b/features/sdnr/odlux/odlux/apps/configurationApp/webpack.config.js new file mode 100644 index 0000000..2f7ab11 --- /dev/null +++ b/features/sdnr/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/features/sdnr/odlux/odlux/apps/connectApp/.babelrc b/features/sdnr/odlux/odlux/apps/connectApp/.babelrc new file mode 100644 index 0000000..3d8cd12 --- /dev/null +++ b/features/sdnr/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/features/sdnr/odlux/odlux/apps/connectApp/package.json b/features/sdnr/odlux/odlux/apps/connectApp/package.json new file mode 100644 index 0000000..9a3c35b --- /dev/null +++ b/features/sdnr/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/features/sdnr/odlux/odlux/apps/connectApp/policies.json b/features/sdnr/odlux/odlux/apps/connectApp/policies.json new file mode 100644 index 0000000..2ec7361 --- /dev/null +++ b/features/sdnr/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/features/sdnr/odlux/odlux/apps/connectApp/pom.xml b/features/sdnr/odlux/odlux/apps/connectApp/pom.xml new file mode 100644 index 0000000..c0b0525 --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/connectApp/pom.xml @@ -0,0 +1,109 @@ + + + + + 4.0.0 + + org.o-ran-sc.oam-controller.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/features/sdnr/odlux/odlux/apps/connectApp/src/actions/commonNetworkElementsActions.ts b/features/sdnr/odlux/odlux/apps/connectApp/src/actions/commonNetworkElementsActions.ts new file mode 100644 index 0000000..2942c1a --- /dev/null +++ b/features/sdnr/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) { + + // 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 || 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/features/sdnr/odlux/odlux/apps/connectApp/src/actions/infoNetworkElementActions.ts b/features/sdnr/odlux/odlux/apps/connectApp/src/actions/infoNetworkElementActions.ts new file mode 100644 index 0000000..120f991 --- /dev/null +++ b/features/sdnr/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/features/sdnr/odlux/odlux/apps/connectApp/src/actions/mountedNetworkElementsActions.ts b/features/sdnr/odlux/odlux/apps/connectApp/src/actions/mountedNetworkElementsActions.ts new file mode 100644 index 0000000..11bac10 --- /dev/null +++ b/features/sdnr/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/features/sdnr/odlux/odlux/apps/connectApp/src/actions/networkElementsActions.ts b/features/sdnr/odlux/odlux/apps/connectApp/src/actions/networkElementsActions.ts new file mode 100644 index 0000000..d22a6c6 --- /dev/null +++ b/features/sdnr/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/features/sdnr/odlux/odlux/apps/connectApp/src/actions/tlsKeyActions.ts b/features/sdnr/odlux/odlux/apps/connectApp/src/actions/tlsKeyActions.ts new file mode 100644 index 0000000..65d23c4 --- /dev/null +++ b/features/sdnr/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/features/sdnr/odlux/odlux/apps/connectApp/src/assets/icons/connectAppIcon.svg b/features/sdnr/odlux/odlux/apps/connectApp/src/assets/icons/connectAppIcon.svg new file mode 100644 index 0000000..5aca4fa --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/connectApp/src/assets/icons/connectAppIcon.svg @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + diff --git a/features/sdnr/odlux/odlux/apps/connectApp/src/components/connectionStatusLog.tsx b/features/sdnr/odlux/odlux/apps/connectApp/src/components/connectionStatusLog.tsx new file mode 100644 index 0000000..6a8c924 --- /dev/null +++ b/features/sdnr/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/features/sdnr/odlux/odlux/apps/connectApp/src/components/editNetworkElementDialog.tsx b/features/sdnr/odlux/odlux/apps/connectApp/src/components/editNetworkElementDialog.tsx new file mode 100644 index 0000000..b0db634 --- /dev/null +++ b/features/sdnr/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/features/sdnr/odlux/odlux/apps/connectApp/src/components/infoNetworkElementDialog.tsx b/features/sdnr/odlux/odlux/apps/connectApp/src/components/infoNetworkElementDialog.tsx new file mode 100644 index 0000000..4841b93 --- /dev/null +++ b/features/sdnr/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/features/sdnr/odlux/odlux/apps/connectApp/src/components/networkElements.tsx b/features/sdnr/odlux/odlux/apps/connectApp/src/components/networkElements.tsx new file mode 100644 index 0000000..fed176c --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/connectApp/src/components/networkElements.tsx @@ -0,0 +1,315 @@ +/** +* ============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/features/sdnr/odlux/odlux/apps/connectApp/src/components/refreshConnectionStatusLogDialog.tsx b/features/sdnr/odlux/odlux/apps/connectApp/src/components/refreshConnectionStatusLogDialog.tsx new file mode 100644 index 0000000..a4aea7f --- /dev/null +++ b/features/sdnr/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/features/sdnr/odlux/odlux/apps/connectApp/src/components/refreshNetworkElementsDialog.tsx b/features/sdnr/odlux/odlux/apps/connectApp/src/components/refreshNetworkElementsDialog.tsx new file mode 100644 index 0000000..e41fd27 --- /dev/null +++ b/features/sdnr/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/features/sdnr/odlux/odlux/apps/connectApp/src/handlers/connectAppRootHandler.ts b/features/sdnr/odlux/odlux/apps/connectApp/src/handlers/connectAppRootHandler.ts new file mode 100644 index 0000000..b386dcd --- /dev/null +++ b/features/sdnr/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/features/sdnr/odlux/odlux/apps/connectApp/src/handlers/connectionStatusLogHandler.ts b/features/sdnr/odlux/odlux/apps/connectApp/src/handlers/connectionStatusLogHandler.ts new file mode 100644 index 0000000..264b6c1 --- /dev/null +++ b/features/sdnr/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/features/sdnr/odlux/odlux/apps/connectApp/src/handlers/infoNetworkElementHandler.ts b/features/sdnr/odlux/odlux/apps/connectApp/src/handlers/infoNetworkElementHandler.ts new file mode 100644 index 0000000..692e63a --- /dev/null +++ b/features/sdnr/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/features/sdnr/odlux/odlux/apps/connectApp/src/handlers/networkElementsHandler.ts b/features/sdnr/odlux/odlux/apps/connectApp/src/handlers/networkElementsHandler.ts new file mode 100644 index 0000000..71d62f9 --- /dev/null +++ b/features/sdnr/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) { + 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/features/sdnr/odlux/odlux/apps/connectApp/src/handlers/tlsKeyHandler.ts b/features/sdnr/odlux/odlux/apps/connectApp/src/handlers/tlsKeyHandler.ts new file mode 100644 index 0000000..20badcb --- /dev/null +++ b/features/sdnr/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/features/sdnr/odlux/odlux/apps/connectApp/src/index.html b/features/sdnr/odlux/odlux/apps/connectApp/src/index.html new file mode 100644 index 0000000..1a16876 --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/connectApp/src/index.html @@ -0,0 +1,28 @@ + + + + + + + + + connectApp + + + +
+ + + + + + \ No newline at end of file diff --git a/features/sdnr/odlux/odlux/apps/connectApp/src/models/connectionStatusLog.ts b/features/sdnr/odlux/odlux/apps/connectApp/src/models/connectionStatusLog.ts new file mode 100644 index 0000000..82b49a0 --- /dev/null +++ b/features/sdnr/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/features/sdnr/odlux/odlux/apps/connectApp/src/models/guiCutTrough.ts b/features/sdnr/odlux/odlux/apps/connectApp/src/models/guiCutTrough.ts new file mode 100644 index 0000000..0fd46a8 --- /dev/null +++ b/features/sdnr/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/features/sdnr/odlux/odlux/apps/connectApp/src/models/networkElementBase.ts b/features/sdnr/odlux/odlux/apps/connectApp/src/models/networkElementBase.ts new file mode 100644 index 0000000..a46a30e --- /dev/null +++ b/features/sdnr/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/features/sdnr/odlux/odlux/apps/connectApp/src/models/networkElementConnection.ts b/features/sdnr/odlux/odlux/apps/connectApp/src/models/networkElementConnection.ts new file mode 100644 index 0000000..6a5d00d --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/connectApp/src/models/networkElementConnection.ts @@ -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========================================================================== + */ + +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; + }[]; + }; + mountMethod?: 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/features/sdnr/odlux/odlux/apps/connectApp/src/models/networkElementConnectionLog.ts b/features/sdnr/odlux/odlux/apps/connectApp/src/models/networkElementConnectionLog.ts new file mode 100644 index 0000000..4b4e283 --- /dev/null +++ b/features/sdnr/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/features/sdnr/odlux/odlux/apps/connectApp/src/models/panelId.ts b/features/sdnr/odlux/odlux/apps/connectApp/src/models/panelId.ts new file mode 100644 index 0000000..2861f10 --- /dev/null +++ b/features/sdnr/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/features/sdnr/odlux/odlux/apps/connectApp/src/models/topologyNetconf.ts b/features/sdnr/odlux/odlux/apps/connectApp/src/models/topologyNetconf.ts new file mode 100644 index 0000000..85a1a71 --- /dev/null +++ b/features/sdnr/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/features/sdnr/odlux/odlux/apps/connectApp/src/models/yangCapabilitiesType.ts b/features/sdnr/odlux/odlux/apps/connectApp/src/models/yangCapabilitiesType.ts new file mode 100644 index 0000000..b69993f --- /dev/null +++ b/features/sdnr/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/features/sdnr/odlux/odlux/apps/connectApp/src/pluginConnect.tsx b/features/sdnr/odlux/odlux/apps/connectApp/src/pluginConnect.tsx new file mode 100644 index 0000000..c290716 --- /dev/null +++ b/features/sdnr/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/features/sdnr/odlux/odlux/apps/connectApp/src/services/connectService.ts b/features/sdnr/odlux/odlux/apps/connectApp/src/services/connectService.ts new file mode 100644 index 0000000..6330aa0 --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/connectApp/src/services/connectService.ts @@ -0,0 +1,310 @@ +/** + * ============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', + ' 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/features/sdnr/odlux/odlux/apps/connectApp/src/views/connectView.tsx b/features/sdnr/odlux/odlux/apps/connectApp/src/views/connectView.tsx new file mode 100644 index 0000000..a6fcb7c --- /dev/null +++ b/features/sdnr/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/features/sdnr/odlux/odlux/apps/connectApp/tsconfig.json b/features/sdnr/odlux/odlux/apps/connectApp/tsconfig.json new file mode 100644 index 0000000..18956db --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/connectApp/tsconfig.json @@ -0,0 +1,6 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": "./dist", + }, +} diff --git a/features/sdnr/odlux/odlux/apps/connectApp/webpack.config.js b/features/sdnr/odlux/odlux/apps/connectApp/webpack.config.js new file mode 100644 index 0000000..b7aebb9 --- /dev/null +++ b/features/sdnr/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/features/sdnr/odlux/odlux/apps/demoApp/.babelrc b/features/sdnr/odlux/odlux/apps/demoApp/.babelrc new file mode 100644 index 0000000..3d8cd12 --- /dev/null +++ b/features/sdnr/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/features/sdnr/odlux/odlux/apps/demoApp/package.json b/features/sdnr/odlux/odlux/apps/demoApp/package.json new file mode 100644 index 0000000..6a31bc3 --- /dev/null +++ b/features/sdnr/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/features/sdnr/odlux/odlux/apps/demoApp/pom.xml b/features/sdnr/odlux/odlux/apps/demoApp/pom.xml new file mode 100644 index 0000000..24dcd41 --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/demoApp/pom.xml @@ -0,0 +1,109 @@ + + + + + 4.0.0 + + org.o-ran-sc.oam-controller.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/features/sdnr/odlux/odlux/apps/demoApp/src/actions/authorActions.ts b/features/sdnr/odlux/odlux/apps/demoApp/src/actions/authorActions.ts new file mode 100644 index 0000000..f22d1e0 --- /dev/null +++ b/features/sdnr/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/features/sdnr/odlux/odlux/apps/demoApp/src/components/counter.tsx b/features/sdnr/odlux/odlux/apps/demoApp/src/components/counter.tsx new file mode 100644 index 0000000..1aad974 --- /dev/null +++ b/features/sdnr/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/features/sdnr/odlux/odlux/apps/demoApp/src/handlers/demoAppRootHandler.ts b/features/sdnr/odlux/odlux/apps/demoApp/src/handlers/demoAppRootHandler.ts new file mode 100644 index 0000000..1f920f2 --- /dev/null +++ b/features/sdnr/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/features/sdnr/odlux/odlux/apps/demoApp/src/handlers/editAuthorHandler.ts b/features/sdnr/odlux/odlux/apps/demoApp/src/handlers/editAuthorHandler.ts new file mode 100644 index 0000000..1d37a36 --- /dev/null +++ b/features/sdnr/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/features/sdnr/odlux/odlux/apps/demoApp/src/handlers/listAuthorsHandler.ts b/features/sdnr/odlux/odlux/apps/demoApp/src/handlers/listAuthorsHandler.ts new file mode 100644 index 0000000..c85eaff --- /dev/null +++ b/features/sdnr/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/features/sdnr/odlux/odlux/apps/demoApp/src/index.html b/features/sdnr/odlux/odlux/apps/demoApp/src/index.html new file mode 100644 index 0000000..521c890 --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/demoApp/src/index.html @@ -0,0 +1,26 @@ + + + + + + + + + Demo App + + + +
+ + + + + + \ No newline at end of file diff --git a/features/sdnr/odlux/odlux/apps/demoApp/src/models/author.ts b/features/sdnr/odlux/odlux/apps/demoApp/src/models/author.ts new file mode 100644 index 0000000..bdd414c --- /dev/null +++ b/features/sdnr/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/features/sdnr/odlux/odlux/apps/demoApp/src/plugin.tsx b/features/sdnr/odlux/odlux/apps/demoApp/src/plugin.tsx new file mode 100644 index 0000000..7b29b40 --- /dev/null +++ b/features/sdnr/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/features/sdnr/odlux/odlux/apps/demoApp/src/services/authorService.ts b/features/sdnr/odlux/odlux/apps/demoApp/src/services/authorService.ts new file mode 100644 index 0000000..deaa2ff --- /dev/null +++ b/features/sdnr/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/features/sdnr/odlux/odlux/apps/demoApp/src/views/authorsList.tsx b/features/sdnr/odlux/odlux/apps/demoApp/src/views/authorsList.tsx new file mode 100644 index 0000000..5d9f13a --- /dev/null +++ b/features/sdnr/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/features/sdnr/odlux/odlux/apps/demoApp/src/views/editAuthor.tsx b/features/sdnr/odlux/odlux/apps/demoApp/src/views/editAuthor.tsx new file mode 100644 index 0000000..0da619b --- /dev/null +++ b/features/sdnr/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/features/sdnr/odlux/odlux/apps/demoApp/tsconfig.json b/features/sdnr/odlux/odlux/apps/demoApp/tsconfig.json new file mode 100644 index 0000000..ca65092 --- /dev/null +++ b/features/sdnr/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/features/sdnr/odlux/odlux/apps/demoApp/webpack.config.js b/features/sdnr/odlux/odlux/apps/demoApp/webpack.config.js new file mode 100644 index 0000000..0476c30 --- /dev/null +++ b/features/sdnr/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/features/sdnr/odlux/odlux/apps/eventLogApp/.babelrc b/features/sdnr/odlux/odlux/apps/eventLogApp/.babelrc new file mode 100644 index 0000000..3d8cd12 --- /dev/null +++ b/features/sdnr/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/features/sdnr/odlux/odlux/apps/eventLogApp/package.json b/features/sdnr/odlux/odlux/apps/eventLogApp/package.json new file mode 100644 index 0000000..fb6cedf --- /dev/null +++ b/features/sdnr/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/features/sdnr/odlux/odlux/apps/eventLogApp/pom.xml b/features/sdnr/odlux/odlux/apps/eventLogApp/pom.xml new file mode 100644 index 0000000..9e8da5d --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/eventLogApp/pom.xml @@ -0,0 +1,109 @@ + + + + + 4.0.0 + + org.o-ran-sc.oam-controller.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/features/sdnr/odlux/odlux/apps/eventLogApp/src/assets/icons/eventLogAppIcon.svg b/features/sdnr/odlux/odlux/apps/eventLogApp/src/assets/icons/eventLogAppIcon.svg new file mode 100644 index 0000000..743167d --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/eventLogApp/src/assets/icons/eventLogAppIcon.svg @@ -0,0 +1,21 @@ + + + + + + + + + + + + diff --git a/features/sdnr/odlux/odlux/apps/eventLogApp/src/components/refreshEventLogDialog.tsx b/features/sdnr/odlux/odlux/apps/eventLogApp/src/components/refreshEventLogDialog.tsx new file mode 100644 index 0000000..d7c4732 --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/eventLogApp/src/components/refreshEventLogDialog.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, { FC } 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 { useApplicationDispatch } from '../../../../framework/src/flux/connect'; +import { eventLogReloadAction } from '../handlers/eventLogHandler'; + +export enum RefreshEventLogDialogMode { + None = 'none', + RefreshEventLogTable = 'RefreshEventLogTable', +} + +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 = { + mode: RefreshEventLogDialogMode; + onClose: () => void; +}; + +const RefreshEventLogDialogComponent: FC = (props) => { + const dispatch = useApplicationDispatch(); + const refreshEventLog = () => dispatch(eventLogReloadAction); + const setting = settings[props.mode]; + + const onRefresh = () => { + refreshEventLog(); + props.onClose(); + }; + + const onCancel = () => { + props.onClose(); + }; + + return ( + + {setting.dialogTitle} + + + {setting.dialogDescription} + + + + + + + + ); +}; + +export const RefreshEventLogDialog = RefreshEventLogDialogComponent; +export default RefreshEventLogDialog; diff --git a/features/sdnr/odlux/odlux/apps/eventLogApp/src/handlers/eventLogAppRootHandler.ts b/features/sdnr/odlux/odlux/apps/eventLogApp/src/handlers/eventLogAppRootHandler.ts new file mode 100644 index 0000000..c0fd28e --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/eventLogApp/src/handlers/eventLogAppRootHandler.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========================================================================== +*/ +// main state handler + +import { combineActionHandler } from '../../../../framework/src/flux/middleware'; + +// ** do not remove ** +import { IApplicationStoreState } from '../../../../framework/src/store/applicationStore'; +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/features/sdnr/odlux/odlux/apps/eventLogApp/src/handlers/eventLogHandler.tsx b/features/sdnr/odlux/odlux/apps/eventLogApp/src/handlers/eventLogHandler.tsx new file mode 100644 index 0000000..321ddd9 --- /dev/null +++ b/features/sdnr/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 elastic 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/features/sdnr/odlux/odlux/apps/eventLogApp/src/index.html b/features/sdnr/odlux/odlux/apps/eventLogApp/src/index.html new file mode 100644 index 0000000..8027509 --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/eventLogApp/src/index.html @@ -0,0 +1,26 @@ + + + + + + + + + EventLog App + + + +
+ + + + + + \ No newline at end of file diff --git a/features/sdnr/odlux/odlux/apps/eventLogApp/src/models/eventLogType.ts b/features/sdnr/odlux/odlux/apps/eventLogApp/src/models/eventLogType.ts new file mode 100644 index 0000000..80ad380 --- /dev/null +++ b/features/sdnr/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/features/sdnr/odlux/odlux/apps/eventLogApp/src/pluginEventLog.tsx b/features/sdnr/odlux/odlux/apps/eventLogApp/src/pluginEventLog.tsx new file mode 100644 index 0000000..64c9198 --- /dev/null +++ b/features/sdnr/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/features/sdnr/odlux/odlux/apps/eventLogApp/src/views/eventLog.tsx b/features/sdnr/odlux/odlux/apps/eventLogApp/src/views/eventLog.tsx new file mode 100644 index 0000000..d14c9f6 --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/eventLogApp/src/views/eventLog.tsx @@ -0,0 +1,87 @@ +/** + * ============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, { useEffect, useState } from 'react'; + +import Refresh from '@mui/icons-material/Refresh'; +import { MaterialTable, MaterialTableCtorType } from '../../../../framework/src/components/material-table'; +import { useApplicationDispatch, useSelectApplicationState } from '../../../../framework/src/flux/connect'; + +import { IApplicationStoreState } from '../../../../framework/src/store/applicationStore'; +import RefreshEventLogDialog, { RefreshEventLogDialogMode } from '../components/refreshEventLogDialog'; +import { createEventLogActions, createEventLogProperties } from '../handlers/eventLogHandler'; +import { EventLogType } from '../models/eventLogType'; + +const EventLogTable = MaterialTable as MaterialTableCtorType; + +const EventLog = () => { + useSelectApplicationState((state: IApplicationStoreState) => state.eventLog.logEntries); + const eventLogProperties = useSelectApplicationState((state: IApplicationStoreState) => createEventLogProperties(state)); + + const dispatch = useApplicationDispatch(); + const eventLogActions = createEventLogActions(dispatch); + + const [refreshEventLogEditorMode, setRefreshEventLogEditorMode] = useState(RefreshEventLogDialogMode.None); + let initalSorted = false; + + useEffect(() => { + if (!initalSorted) { + initalSorted = true; + eventLogActions.onHandleExplicitRequestSort('timestamp', 'desc'); + } else { + eventLogActions.onRefresh(); + } + }, []); + + const refreshEventLogAction = { + icon: Refresh, tooltip: 'Refresh Event log', ariaLabel: 'refresh', onClick: () => { + setRefreshEventLogEditorMode(RefreshEventLogDialogMode.RefreshEventLogTable); + }, + }; + + const onCloseRefreshEventLogDialog = () => { + setRefreshEventLogEditorMode(RefreshEventLogDialogMode.None); + }; + + return ( + <> + + + + ); +}; +export default EventLog; \ No newline at end of file diff --git a/features/sdnr/odlux/odlux/apps/eventLogApp/tsconfig.json b/features/sdnr/odlux/odlux/apps/eventLogApp/tsconfig.json new file mode 100644 index 0000000..ca65092 --- /dev/null +++ b/features/sdnr/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/features/sdnr/odlux/odlux/apps/eventLogApp/webpack.config.js b/features/sdnr/odlux/odlux/apps/eventLogApp/webpack.config.js new file mode 100644 index 0000000..521cb02 --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/eventLogApp/webpack.config.js @@ -0,0 +1,199 @@ +/** + * 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: { + "/about": { + target: "http://sdncweb:8080", + secure: false + }, + "/yang-schema/": { + target: "http://sdncweb:8080", + secure: false + }, + "/oauth/": { + target: "http://sdncweb:8080", + secure: false + }, + "/database/": { + target: "http://sdncweb:8080", + secure: false + }, + "/restconf/": { + target: "http://sdncweb:8080", + secure: false + }, + "/rests/": { + target: "http://sdncweb:8080", + secure: false + }, + "/userdata": { + target: "http://sdncweb:8080", + secure: false + }, + "/userdata/": { + target: "http://sdncweb:8080", + secure: false + }, + "/help/": { + target: "http://sdncweb:8080", + secure: false + }, + "/about/": { + target: "http://sdncweb:8080", + secure: false + }, + "/tree/": { + target: "http://sdncweb:8080", + secure: false + }, + "/websocket": { + target: "http://sdncweb:8080", + ws: true, + changeOrigin: true, + secure: false + }, + "/apidoc": { + target: "http://sdncweb:8080", + ws: true, + changeOrigin: true, + secure: false + } + } + + } + }]; +} diff --git a/features/sdnr/odlux/odlux/apps/faultApp/.babelrc b/features/sdnr/odlux/odlux/apps/faultApp/.babelrc new file mode 100644 index 0000000..3d8cd12 --- /dev/null +++ b/features/sdnr/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/features/sdnr/odlux/odlux/apps/faultApp/package.json b/features/sdnr/odlux/odlux/apps/faultApp/package.json new file mode 100644 index 0000000..9531055 --- /dev/null +++ b/features/sdnr/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/features/sdnr/odlux/odlux/apps/faultApp/pom.xml b/features/sdnr/odlux/odlux/apps/faultApp/pom.xml new file mode 100644 index 0000000..bead906 --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/faultApp/pom.xml @@ -0,0 +1,109 @@ + + + + + 4.0.0 + + org.o-ran-sc.oam-controller.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/features/sdnr/odlux/odlux/apps/faultApp/src/actions/clearStuckAlarmsAction.ts b/features/sdnr/odlux/odlux/apps/faultApp/src/actions/clearStuckAlarmsAction.ts new file mode 100644 index 0000000..7aac8ba --- /dev/null +++ b/features/sdnr/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/features/sdnr/odlux/odlux/apps/faultApp/src/actions/notificationActions.ts b/features/sdnr/odlux/odlux/apps/faultApp/src/actions/notificationActions.ts new file mode 100644 index 0000000..584e7cd --- /dev/null +++ b/features/sdnr/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/features/sdnr/odlux/odlux/apps/faultApp/src/actions/panelChangeActions.ts b/features/sdnr/odlux/odlux/apps/faultApp/src/actions/panelChangeActions.ts new file mode 100644 index 0000000..fb29e9c --- /dev/null +++ b/features/sdnr/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/features/sdnr/odlux/odlux/apps/faultApp/src/actions/statusActions.ts b/features/sdnr/odlux/odlux/apps/faultApp/src/actions/statusActions.ts new file mode 100644 index 0000000..8b631b9 --- /dev/null +++ b/features/sdnr/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/features/sdnr/odlux/odlux/apps/faultApp/src/assets/icons/faultAppIcon.svg b/features/sdnr/odlux/odlux/apps/faultApp/src/assets/icons/faultAppIcon.svg new file mode 100644 index 0000000..aabbf4c --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/faultApp/src/assets/icons/faultAppIcon.svg @@ -0,0 +1,19 @@ + + + + + + + + + + + + diff --git a/features/sdnr/odlux/odlux/apps/faultApp/src/components/clearStuckAlarmsDialog.tsx b/features/sdnr/odlux/odlux/apps/faultApp/src/components/clearStuckAlarmsDialog.tsx new file mode 100644 index 0000000..7624132 --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/faultApp/src/components/clearStuckAlarmsDialog.tsx @@ -0,0 +1,116 @@ +/** + * ============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'; + +import { Button, Dialog, DialogActions, DialogContent, DialogContentText, DialogTitle } from '@mui/material'; + +import { useApplicationDispatch } from '../../../../framework/src/flux/connect'; + +import { clearStuckAlarmAsyncAction } from '../actions/clearStuckAlarmsAction'; +import { currentAlarmsReloadAction } from '../handlers/currentAlarmsHandler'; + +export enum ClearStuckAlarmsDialogMode { + None = 'none', + Show = 'show', +} + +type ClearStuckAlarmsProps = { + numberDevices: number; + mode: ClearStuckAlarmsDialogMode; + stuckAlarms: string[]; + onClose: () => void; +}; + +const ClearStuckAlarmsDialogComponent: FC = (props) => { + const dispatch = useApplicationDispatch(); + const clearStuckAlarmsAsync = clearStuckAlarmAsyncAction(dispatch); + const reloadCurrentAlarmsAction = () => dispatch(currentAlarmsReloadAction); + + const [clearAlarmsSuccessful, setClearAlarmsSuccessful] = useState(true); + const [errormessage, setErrormessage] = useState(''); + const [unclearedAlarms, setUnclearedAlarms] = useState([]); + + const onCloseDialog = (event: React.MouseEvent) => { + event.stopPropagation(); + event.preventDefault(); + props.onClose(); + }; + + const onRefresh = async (event: React.MouseEvent) => { + event.stopPropagation(); + event.preventDefault(); + const result = await clearStuckAlarmsAsync(props.stuckAlarms); + + if (result && result['devicemanager:output'].nodenames && result['devicemanager:output'].nodenames.length !== props.stuckAlarms.length) { + const undeletedAlarm = props.stuckAlarms.filter(item => !result['devicemanager:output'].nodenames.includes(item)); + const error = 'The alarms of the following devices couldn\'t be refreshed: '; + setClearAlarmsSuccessful(false); + setErrormessage(error); + setUnclearedAlarms(undeletedAlarm); + } else { + setClearAlarmsSuccessful(false); + setErrormessage('Alarms couldn\'t be refreshed.'); + setUnclearedAlarms([]); + } + reloadCurrentAlarmsAction(); + props.onClose(); + }; + + const onOk = (event: React.MouseEvent) => { + event.stopPropagation(); + event.preventDefault(); + if (unclearedAlarms.length > 0) + reloadCurrentAlarmsAction(); + props.onClose(); + }; + + const device = props.numberDevices > 1 ? 'devices' : 'device'; + const defaultMessage = `Are you sure you want to refresh all alarms for ${props.numberDevices} ${device}?`; + const message = clearAlarmsSuccessful ? defaultMessage : errormessage; + + const defaultTitle = 'Refresh Confirmation'; + const title = clearAlarmsSuccessful ? defaultTitle : 'Refresh Result'; + + return ( + + {title} + + + {message} + + {unclearedAlarms.map(item => + + {item} + , + )} + + + {clearAlarmsSuccessful && ( + <> + + + + )} + {!clearAlarmsSuccessful && } + + + ); +}; + +const ClearStuckAlarmsDialog = ClearStuckAlarmsDialogComponent; +export default ClearStuckAlarmsDialog; diff --git a/features/sdnr/odlux/odlux/apps/faultApp/src/components/dashboardHome.tsx b/features/sdnr/odlux/odlux/apps/faultApp/src/components/dashboardHome.tsx new file mode 100644 index 0000000..241db82 --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/faultApp/src/components/dashboardHome.tsx @@ -0,0 +1,450 @@ +/** + * ============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 } from 'react'; +import { Doughnut } from 'react-chartjs-2'; +import { RouteComponentProps, withRouter } from 'react-router-dom'; + +import { NavigateToApplication } from '../../../../framework/src/actions/navigationActions'; +import { useApplicationDispatch, useSelectApplicationState } from '../../../../framework/src/flux/connect'; +import { IApplicationStoreState } from '../../../../framework/src/store/applicationStore'; + +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; + + +type HomeComponentProps = RouteComponentProps; + +const DashboardHome: FC = () => { + const alarmStatus = useSelectApplicationState((state: IApplicationStoreState) => state.fault.faultStatus); + const dispatch = useApplicationDispatch(); + const navigateToApplication = (applicationName: string, path?: string) => dispatch(new NavigateToApplication(applicationName, path)); + if (!alarmStatus.isLoadingConnectionStatusChart) { + connectionStatusDataLoad = [ + alarmStatus.Connected, + alarmStatus.Connecting, + alarmStatus.Disconnected, + alarmStatus.UnableToConnect, + alarmStatus.Undefined, + ]; + connectionTotalCount = alarmStatus.Connected + alarmStatus.Connecting + + alarmStatus.Disconnected + alarmStatus.UnableToConnect + alarmStatus.Undefined; + + } + + if (!alarmStatus.isLoadingAlarmStatusChart) { + alarmStatusDataLoad = [ + alarmStatus.critical, + alarmStatus.major, + alarmStatus.minor, + alarmStatus.warning, + ]; + alarmTotalCount = alarmStatus.critical + alarmStatus.major + + alarmStatus.minor + alarmStatus.warning; + } + + /** Available Network Connection Status chart data */ + const connectionStatusData = { + labels: [ + 'Connected: ' + alarmStatus.Connected, + 'Connecting: ' + alarmStatus.Connecting, + 'Disconnected: ' + alarmStatus.Disconnected, + 'UnableToConnect: ' + alarmStatus.UnableToConnect, + 'Undefined: ' + 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) => { + setTimeout(() => { + if (item[0]) { + let connectionStatus = labels[item[0]._index] + ''; + navigateToApplication('connect', '/connectionStatus/' + connectionStatus); + } + }, 0); + }, + }; + + /** 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 : ' + alarmStatus.critical, + 'Major : ' + alarmStatus.major, + 'Minor : ' + alarmStatus.minor, + 'Warning : ' + 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) => { + setTimeout(() => { + if (item[0]) { + let severity = alarmLabels[item[0]._index] + ''; + navigateToApplication('fault', '/alarmStatus/' + severity); + } + }, 0); + }, + }; + + /** 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(); + }, + }]; + + /** Check if connection status data available */ + const checkConnectionStatus = () => { + let statusCount = 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 */ + const checkElementsAreLoaded = () => { + let isLoadingCheck = alarmStatus; + if (connectionStatusInitialLoad && !isLoadingCheck.isLoadingConnectionStatusChart) { + if (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 */ + const checkAlarmStatus = () => { + let alarmCount = 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 */ + const checkAlarmsAreLoaded = () => { + let isLoadingCheck = alarmStatus; + if (alarmStatusInitialLoad && !isLoadingCheck.isLoadingAlarmStatusChart) { + if (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; + }; + + return ( + <> +
+

Welcome to ODLUX

+
+ {checkElementsAreLoaded() ? + checkConnectionStatus() && connectionTotalCount != 0 ? + + : + : + } +
+
+ {checkAlarmsAreLoaded() ? + checkAlarmStatus() && alarmTotalCount != 0 ? + + : + : + } +
+
+ + ); +}; + +export default (withRouter(DashboardHome)); \ No newline at end of file diff --git a/features/sdnr/odlux/odlux/apps/faultApp/src/components/faultStatus.tsx b/features/sdnr/odlux/odlux/apps/faultApp/src/components/faultStatus.tsx new file mode 100644 index 0000000..8720aff --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/faultApp/src/components/faultStatus.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 { faExclamationTriangle } from '@fortawesome/free-solid-svg-icons'; // select app icon +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import Tooltip from '@mui/material/Tooltip'; +import Typography from '@mui/material/Typography'; +import makeStyles from '@mui/styles/makeStyles'; + +import { useSelectApplicationState } from '../../../../framework/src/flux/connect'; +import { IApplicationStoreState } from '../../../../framework/src/store/applicationStore'; + +const styles = makeStyles({ + icon: { + marginLeft: 8, + marginRight: 8, + }, + critical: { + color: 'red', + }, + major: { + color: 'orange', + }, + minor: { + color: '#f7f700', + }, + warning: { + color: '#428bca', + }, +}); + +const FaultStatusComponent = () => { + const faultStatus = useSelectApplicationState((state: IApplicationStoreState) => state.fault.faultStatus); + const classes = styles(); + return ( + <> + + Alarm Status: + + + + + + {faultStatus.critical} | + + + + + + + + + {faultStatus.major} | + + + + + + + + + {faultStatus.minor} | + + + + + + + + + {faultStatus.warning} | + + + ); +}; + +export const FaultStatus = FaultStatusComponent; +export default FaultStatus; \ No newline at end of file diff --git a/features/sdnr/odlux/odlux/apps/faultApp/src/components/refreshAlarmLogDialog.tsx b/features/sdnr/odlux/odlux/apps/faultApp/src/components/refreshAlarmLogDialog.tsx new file mode 100644 index 0000000..1d4a149 --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/faultApp/src/components/refreshAlarmLogDialog.tsx @@ -0,0 +1,108 @@ +/** + * ============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 { useApplicationDispatch } from '../../../../framework/src/flux/connect'; + +import { alarmLogEntriesReloadAction } from '../handlers/alarmLogEntriesHandler'; + +export enum RefreshAlarmLogDialogMode { + None = 'none', + RefreshAlarmLogTable = 'RefreshAlarmLogTable', +} +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 DialogSettings = { + dialogTitle: string; + dialogDescription: string; + applyButtonText: string; + cancelButtonText: string; + enableMountIdEditor: boolean; + enableUsernameEditor: boolean; + enableExtendedEditor: boolean; +}; + +type RefreshAlarmLogDialogComponentProps = { + mode: RefreshAlarmLogDialogMode; + onClose: () => void; +}; + +const RefreshAlarmLogDialogComponent: React.FC = (props) => { + const dispatch = useApplicationDispatch(); + const refreshAlarmLog = () => dispatch(alarmLogEntriesReloadAction); + const setting = settings[props.mode]; + + const onRefresh = () => { + refreshAlarmLog(); + props.onClose(); + }; + + const onCancel = () => { + props.onClose(); + }; + + return ( + + + {setting.dialogTitle} + + + + {setting.dialogDescription} + + + + + + + + ); +}; + +export const RefreshAlarmLogDialog = RefreshAlarmLogDialogComponent; +export default RefreshAlarmLogDialog; diff --git a/features/sdnr/odlux/odlux/apps/faultApp/src/components/refreshCurrentAlarmsDialog.tsx b/features/sdnr/odlux/odlux/apps/faultApp/src/components/refreshCurrentAlarmsDialog.tsx new file mode 100644 index 0000000..75b4725 --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/faultApp/src/components/refreshCurrentAlarmsDialog.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 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 { useApplicationDispatch } from '../../../../framework/src/flux/connect'; + +import { currentAlarmsReloadAction } from '../handlers/currentAlarmsHandler'; + +export enum RefreshCurrentAlarmsDialogMode { + None = 'none', + RefreshCurrentAlarmsTable = 'RefreshCurrentAlarmsTable', +} + +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 = { + mode: RefreshCurrentAlarmsDialogMode; + onClose: () => void; +}; + +const RefreshCurrentAlarmsDialogComponent: React.FC = (props) => { + const dispatch = useApplicationDispatch(); + const refreshCurrentAlarms = () => dispatch(currentAlarmsReloadAction); + const setting = settings[props.mode]; + + const onRefresh = () => { + refreshCurrentAlarms(); + props.onClose(); + }; + + const onCancel = () => { + props.onClose(); + }; + + return ( + + + {setting.dialogTitle} + + + + {setting.dialogDescription} + + + + + + + + ); +}; + +export const RefreshCurrentAlarmsDialog = RefreshCurrentAlarmsDialogComponent; +export default RefreshCurrentAlarmsDialog; diff --git a/features/sdnr/odlux/odlux/apps/faultApp/src/handlers/alarmLogEntriesHandler.ts b/features/sdnr/odlux/odlux/apps/faultApp/src/handlers/alarmLogEntriesHandler.ts new file mode 100644 index 0000000..bdd4596 --- /dev/null +++ b/features/sdnr/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/features/sdnr/odlux/odlux/apps/faultApp/src/handlers/clearStuckAlarmsHandler.ts b/features/sdnr/odlux/odlux/apps/faultApp/src/handlers/clearStuckAlarmsHandler.ts new file mode 100644 index 0000000..0d5a8c7 --- /dev/null +++ b/features/sdnr/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/features/sdnr/odlux/odlux/apps/faultApp/src/handlers/currentAlarmsHandler.ts b/features/sdnr/odlux/odlux/apps/faultApp/src/handlers/currentAlarmsHandler.ts new file mode 100644 index 0000000..70aa1c2 --- /dev/null +++ b/features/sdnr/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/features/sdnr/odlux/odlux/apps/faultApp/src/handlers/faultAppRootHandler.ts b/features/sdnr/odlux/odlux/apps/faultApp/src/handlers/faultAppRootHandler.ts new file mode 100644 index 0000000..e4a19ae --- /dev/null +++ b/features/sdnr/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/features/sdnr/odlux/odlux/apps/faultApp/src/handlers/faultStatusHandler.ts b/features/sdnr/odlux/odlux/apps/faultApp/src/handlers/faultStatusHandler.ts new file mode 100644 index 0000000..21b033e --- /dev/null +++ b/features/sdnr/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/features/sdnr/odlux/odlux/apps/faultApp/src/handlers/notificationsHandler.ts b/features/sdnr/odlux/odlux/apps/faultApp/src/handlers/notificationsHandler.ts new file mode 100644 index 0000000..3d960bf --- /dev/null +++ b/features/sdnr/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/features/sdnr/odlux/odlux/apps/faultApp/src/index.html b/features/sdnr/odlux/odlux/apps/faultApp/src/index.html new file mode 100644 index 0000000..26e6d3c --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/faultApp/src/index.html @@ -0,0 +1,26 @@ + + + + + + + + + Fault App + + + +
+ + + + + + \ No newline at end of file diff --git a/features/sdnr/odlux/odlux/apps/faultApp/src/models/fault.ts b/features/sdnr/odlux/odlux/apps/faultApp/src/models/fault.ts new file mode 100644 index 0000000..c70253e --- /dev/null +++ b/features/sdnr/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/features/sdnr/odlux/odlux/apps/faultApp/src/models/panelId.ts b/features/sdnr/odlux/odlux/apps/faultApp/src/models/panelId.ts new file mode 100644 index 0000000..daebad0 --- /dev/null +++ b/features/sdnr/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/features/sdnr/odlux/odlux/apps/faultApp/src/pluginFault.tsx b/features/sdnr/odlux/odlux/apps/faultApp/src/pluginFault.tsx new file mode 100644 index 0000000..2ef243c --- /dev/null +++ b/features/sdnr/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/features/sdnr/odlux/odlux/apps/faultApp/src/services/faultStatusService.ts b/features/sdnr/odlux/odlux/apps/faultApp/src/services/faultStatusService.ts new file mode 100644 index 0000000..0c7a215 --- /dev/null +++ b/features/sdnr/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/features/sdnr/odlux/odlux/apps/faultApp/src/views/faultApplication.tsx b/features/sdnr/odlux/odlux/apps/faultApp/src/views/faultApplication.tsx new file mode 100644 index 0000000..e36ed42 --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/faultApp/src/views/faultApplication.tsx @@ -0,0 +1,208 @@ +/** + * ============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, useEffect, useState } 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 { useApplicationDispatch, useSelectApplicationState } 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'; + +type FaultApplicationComponentProps = RouteComponentProps; + +const FaultTable = MaterialTable as MaterialTableCtorType; +const FaultAlarmNotificationTable = MaterialTable as MaterialTableCtorType; + +let currentAlarmsInitialSorted = false; +let alarmLogInitialSorted = false; + +const FaultApplicationComponent: FC = () => { + const panelId = useSelectApplicationState((state: IApplicationStoreState) => state.fault.currentOpenPanel); + const currentAlarmsProperties = useSelectApplicationState((state: IApplicationStoreState) => createCurrentAlarmsProperties(state)); + const faultNotifications = useSelectApplicationState((state: IApplicationStoreState) => state.fault.faultNotifications); + const alarmLogEntriesProperties = useSelectApplicationState((state: IApplicationStoreState) => createAlarmLogEntriesProperties(state)); + + const dispatch = useApplicationDispatch(); + const currentAlarmsActions = createCurrentAlarmsActions(dispatch); + const alarmLogEntriesActions = createAlarmLogEntriesActions(dispatch); + const reloadCurrentAlarms = () => dispatch(currentAlarmsReloadAction); + const reloadAlarmLogEntries = () => dispatch(alarmLogEntriesReloadAction); + const switchActivePanel = (panel: PanelId) => dispatch(setPanelAction(panel)); + + const [clearAlarmDialogMode, setClearAlarmDialogMode] = useState(ClearStuckAlarmsDialogMode.None); + const [stuckAlarms, setStuckAlarms] = useState([]); + const [refreshAlarmLogEditorMode, setRefreshAlarmLogEditorMode] = useState(RefreshAlarmLogDialogMode.None); + const [refreshCurrentAlarmsEditorMode, setRefreshCurrentAlarmsEditorMode] = useState(RefreshCurrentAlarmsDialogMode.None); + const areFaultsAvailable = currentAlarmsProperties.rows && currentAlarmsProperties.rows.length > 0; + + const onToggleTabs = (activePanelId: PanelId) => { + switchActivePanel(activePanelId); + switch (activePanelId) { + case 'CurrentAlarms': + if (!currentAlarmsInitialSorted) { + currentAlarmsInitialSorted = true; + currentAlarmsActions.onHandleExplicitRequestSort('timestamp', 'desc'); + } else { + reloadCurrentAlarms(); + } + break; + case 'AlarmLog': + if (!alarmLogInitialSorted) { + alarmLogInitialSorted = true; + alarmLogEntriesActions.onHandleExplicitRequestSort('timestamp', 'desc'); + } else { + reloadAlarmLogEntries(); + } + break; + case 'AlarmNotifications': + case null: + default: + // nothing to do + break; + } + }; + + const onDialogClose = () => { + setClearAlarmDialogMode(ClearStuckAlarmsDialogMode.None); + setStuckAlarms([]); + }; + + const onDialogOpen = () => { + const stuckAlarmsList = [...new Set(currentAlarmsProperties.rows.map(item => item.nodeId))]; + setStuckAlarms(stuckAlarmsList); + setClearAlarmDialogMode(ClearStuckAlarmsDialogMode.Show); + }; + + const onHandleTabChange = (event: React.SyntheticEvent, newValue: PanelId) => { + onToggleTabs(newValue); + }; + + const onCloseRefreshAlarmLogDialog = () => { + setRefreshAlarmLogEditorMode(RefreshAlarmLogDialogMode.None); + }; + + const onCloseRefreshCurrentAlarmsDialog = () => { + setRefreshCurrentAlarmsEditorMode(RefreshCurrentAlarmsDialogMode.None); + }; + + const clearAlarmsAction = { + icon: Sync, tooltip: 'Clear stuck alarms', ariaLabel: 'clear-stuck-alarms', onClick: onDialogOpen, + }; + + const refreshCurrentAlarmsAction = { + icon: Refresh, tooltip: 'Refresh Current Alarms List', ariaLabel: 'refresh', onClick: () => { + setRefreshCurrentAlarmsEditorMode(RefreshCurrentAlarmsDialogMode.RefreshCurrentAlarmsTable); + }, + }; + + const refreshAlarmLogAction = { + icon: Refresh, tooltip: 'Refresh Alarm log table', ariaLabel: 'refresh', onClick: () => { + setRefreshAlarmLogEditorMode(RefreshAlarmLogDialogMode.RefreshAlarmLogTable); + }, + }; + + const customActions = areFaultsAvailable ? [clearAlarmsAction, refreshCurrentAlarmsAction] : [refreshCurrentAlarmsAction]; + + useEffect(() => { + if (panelId === null) { //set default tab if none is set + onToggleTabs('CurrentAlarms'); + } else { + onToggleTabs(panelId); + } + }, []); + + return ( + <> + + + + + + + + { + panelId === 'CurrentAlarms' && + <> + + + + } + {panelId === 'AlarmNotifications' && + + + } + {panelId === 'AlarmLog' && + <> + + + + } + { + clearAlarmDialogMode !== ClearStuckAlarmsDialogMode.None && + } + + ); +}; + +export const FaultApplication = withRouter(FaultApplicationComponent); +export default FaultApplication; diff --git a/features/sdnr/odlux/odlux/apps/faultApp/tsconfig.json b/features/sdnr/odlux/odlux/apps/faultApp/tsconfig.json new file mode 100644 index 0000000..ca65092 --- /dev/null +++ b/features/sdnr/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/features/sdnr/odlux/odlux/apps/faultApp/webpack.config.js b/features/sdnr/odlux/odlux/apps/faultApp/webpack.config.js new file mode 100644 index 0000000..caed302 --- /dev/null +++ b/features/sdnr/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://sdncweb:8080", + secure: false + }, + "/database/": { + target: "http://sdncweb:8080", + secure: false + }, + "/restconf/": { + target: "http://sdncweb:8080", + secure: false + }, + "/rests/": { + target: "http://sdncweb:8080", + secure: false + }, + "/help/": { + target: "http://sdncweb:8080", + secure: false + }, + "/websocket": { + target: "http://sdncweb:8080", + ws: true, + changeOrigin: true, + secure: false + } + } + } + }]; +} diff --git a/features/sdnr/odlux/odlux/apps/helpApp/.babelrc b/features/sdnr/odlux/odlux/apps/helpApp/.babelrc new file mode 100644 index 0000000..3d8cd12 --- /dev/null +++ b/features/sdnr/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/features/sdnr/odlux/odlux/apps/helpApp/package.json b/features/sdnr/odlux/odlux/apps/helpApp/package.json new file mode 100644 index 0000000..5bcfdce --- /dev/null +++ b/features/sdnr/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/features/sdnr/odlux/odlux/apps/helpApp/pom.xml b/features/sdnr/odlux/odlux/apps/helpApp/pom.xml new file mode 100644 index 0000000..17a04c7 --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/helpApp/pom.xml @@ -0,0 +1,109 @@ + + + + + 4.0.0 + + org.o-ran-sc.oam-controller.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/features/sdnr/odlux/odlux/apps/helpApp/src/actions/helpActions.ts b/features/sdnr/odlux/odlux/apps/helpApp/src/actions/helpActions.ts new file mode 100644 index 0000000..3cebfd6 --- /dev/null +++ b/features/sdnr/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/features/sdnr/odlux/odlux/apps/helpApp/src/assets/icons/helpAppIcon.svg b/features/sdnr/odlux/odlux/apps/helpApp/src/assets/icons/helpAppIcon.svg new file mode 100644 index 0000000..298eaa1 --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/helpApp/src/assets/icons/helpAppIcon.svg @@ -0,0 +1,27 @@ + + + + + + + + + + + + diff --git a/features/sdnr/odlux/odlux/apps/helpApp/src/components/helpStatus.tsx b/features/sdnr/odlux/odlux/apps/helpApp/src/components/helpStatus.tsx new file mode 100644 index 0000000..985b404 --- /dev/null +++ b/features/sdnr/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/features/sdnr/odlux/odlux/apps/helpApp/src/components/markdown.tsx b/features/sdnr/odlux/odlux/apps/helpApp/src/components/markdown.tsx new file mode 100644 index 0000000..a713783 --- /dev/null +++ b/features/sdnr/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/features/sdnr/odlux/odlux/apps/helpApp/src/components/tocEntry.tsx b/features/sdnr/odlux/odlux/apps/helpApp/src/components/tocEntry.tsx new file mode 100644 index 0000000..295b3ca --- /dev/null +++ b/features/sdnr/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/features/sdnr/odlux/odlux/apps/helpApp/src/handlers/helpAppRootHandler.ts b/features/sdnr/odlux/odlux/apps/helpApp/src/handlers/helpAppRootHandler.ts new file mode 100644 index 0000000..29e0795 --- /dev/null +++ b/features/sdnr/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/features/sdnr/odlux/odlux/apps/helpApp/src/index.html b/features/sdnr/odlux/odlux/apps/helpApp/src/index.html new file mode 100644 index 0000000..2344708 --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/helpApp/src/index.html @@ -0,0 +1,29 @@ + + + + + + + + + Minimal App + + + +
+ + + + + + \ No newline at end of file diff --git a/features/sdnr/odlux/odlux/apps/helpApp/src/models/tocNode.ts b/features/sdnr/odlux/odlux/apps/helpApp/src/models/tocNode.ts new file mode 100644 index 0000000..dbefeec --- /dev/null +++ b/features/sdnr/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/features/sdnr/odlux/odlux/apps/helpApp/src/plugin.tsx b/features/sdnr/odlux/odlux/apps/helpApp/src/plugin.tsx new file mode 100644 index 0000000..5d860e5 --- /dev/null +++ b/features/sdnr/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/features/sdnr/odlux/odlux/apps/helpApp/src/services/helpService.ts b/features/sdnr/odlux/odlux/apps/helpApp/src/services/helpService.ts new file mode 100644 index 0000000..728f243 --- /dev/null +++ b/features/sdnr/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/features/sdnr/odlux/odlux/apps/helpApp/src/utilities/path.ts b/features/sdnr/odlux/odlux/apps/helpApp/src/utilities/path.ts new file mode 100644 index 0000000..412bdfb --- /dev/null +++ b/features/sdnr/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/features/sdnr/odlux/odlux/apps/helpApp/src/views/helpApplication.tsx b/features/sdnr/odlux/odlux/apps/helpApp/src/views/helpApplication.tsx new file mode 100644 index 0000000..5940517 --- /dev/null +++ b/features/sdnr/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/features/sdnr/odlux/odlux/apps/helpApp/src/views/helpTocApp.tsx b/features/sdnr/odlux/odlux/apps/helpApp/src/views/helpTocApp.tsx new file mode 100644 index 0000000..6a6a891 --- /dev/null +++ b/features/sdnr/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/features/sdnr/odlux/odlux/apps/helpApp/tsconfig.json b/features/sdnr/odlux/odlux/apps/helpApp/tsconfig.json new file mode 100644 index 0000000..ca65092 --- /dev/null +++ b/features/sdnr/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/features/sdnr/odlux/odlux/apps/helpApp/webpack.config.js b/features/sdnr/odlux/odlux/apps/helpApp/webpack.config.js new file mode 100644 index 0000000..a48f7b9 --- /dev/null +++ b/features/sdnr/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/features/sdnr/odlux/odlux/apps/inventoryApp/.babelrc b/features/sdnr/odlux/odlux/apps/inventoryApp/.babelrc new file mode 100644 index 0000000..3d8cd12 --- /dev/null +++ b/features/sdnr/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/features/sdnr/odlux/odlux/apps/inventoryApp/package.json b/features/sdnr/odlux/odlux/apps/inventoryApp/package.json new file mode 100644 index 0000000..5e37c52 --- /dev/null +++ b/features/sdnr/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/features/sdnr/odlux/odlux/apps/inventoryApp/pom.xml b/features/sdnr/odlux/odlux/apps/inventoryApp/pom.xml new file mode 100644 index 0000000..4d7b941 --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/inventoryApp/pom.xml @@ -0,0 +1,110 @@ + + + + + 4.0.0 + + org.o-ran-sc.oam-controller.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/features/sdnr/odlux/odlux/apps/inventoryApp/src/actions/inventoryDeviceListActions.ts b/features/sdnr/odlux/odlux/apps/inventoryApp/src/actions/inventoryDeviceListActions.ts new file mode 100644 index 0000000..d1f69d2 --- /dev/null +++ b/features/sdnr/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/features/sdnr/odlux/odlux/apps/inventoryApp/src/actions/inventoryTreeActions.ts b/features/sdnr/odlux/odlux/apps/inventoryApp/src/actions/inventoryTreeActions.ts new file mode 100644 index 0000000..bb57ef8 --- /dev/null +++ b/features/sdnr/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/features/sdnr/odlux/odlux/apps/inventoryApp/src/actions/panelActions.ts b/features/sdnr/odlux/odlux/apps/inventoryApp/src/actions/panelActions.ts new file mode 100644 index 0000000..d666082 --- /dev/null +++ b/features/sdnr/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/features/sdnr/odlux/odlux/apps/inventoryApp/src/assets/icons/inventoryAppIcon.svg b/features/sdnr/odlux/odlux/apps/inventoryApp/src/assets/icons/inventoryAppIcon.svg new file mode 100644 index 0000000..507a835 --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/inventoryApp/src/assets/icons/inventoryAppIcon.svg @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + diff --git a/features/sdnr/odlux/odlux/apps/inventoryApp/src/components/refreshInventoryDialog.tsx b/features/sdnr/odlux/odlux/apps/inventoryApp/src/components/refreshInventoryDialog.tsx new file mode 100644 index 0000000..3b7d0b0 --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/inventoryApp/src/components/refreshInventoryDialog.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, { FC } 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 { useApplicationDispatch } from '../../../../framework/src/flux/connect'; + +import { inventoryElementsReloadAction } from '../handlers/inventoryElementsHandler'; + +export enum RefreshInventoryDialogMode { + None = 'none', + RefreshInventoryTable = 'RefreshInventoryTable', +} + +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 = { + mode: RefreshInventoryDialogMode; + onClose: () => void; +}; + +const RefreshInventoryDialogComponent: FC = (props) => { + const dispatch = useApplicationDispatch(); + const refreshInventory = () => dispatch(inventoryElementsReloadAction); + + const onRefresh = () => { + refreshInventory(); + props.onClose(); + }; + + const onCancel = () => { + props.onClose(); + }; + + const setting = settings[props.mode]; + + return ( + + {setting.dialogTitle} + + + {setting.dialogDescription} + + + + + + + + ); + +}; + +export const RefreshInventoryDialog = RefreshInventoryDialogComponent; +export default RefreshInventoryDialog; \ No newline at end of file diff --git a/features/sdnr/odlux/odlux/apps/inventoryApp/src/fakeData/index.ts b/features/sdnr/odlux/odlux/apps/inventoryApp/src/fakeData/index.ts new file mode 100644 index 0000000..136b908 --- /dev/null +++ b/features/sdnr/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/features/sdnr/odlux/odlux/apps/inventoryApp/src/handlers/inventoryAppRootHandler.ts b/features/sdnr/odlux/odlux/apps/inventoryApp/src/handlers/inventoryAppRootHandler.ts new file mode 100644 index 0000000..b1a0c58 --- /dev/null +++ b/features/sdnr/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/features/sdnr/odlux/odlux/apps/inventoryApp/src/handlers/inventoryDeviceListActionHandler.ts b/features/sdnr/odlux/odlux/apps/inventoryApp/src/handlers/inventoryDeviceListActionHandler.ts new file mode 100644 index 0000000..7c06cad --- /dev/null +++ b/features/sdnr/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/features/sdnr/odlux/odlux/apps/inventoryApp/src/handlers/inventoryElementsHandler.ts b/features/sdnr/odlux/odlux/apps/inventoryApp/src/handlers/inventoryElementsHandler.ts new file mode 100644 index 0000000..7bac8f6 --- /dev/null +++ b/features/sdnr/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/features/sdnr/odlux/odlux/apps/inventoryApp/src/handlers/inventoryTreeHandler.ts b/features/sdnr/odlux/odlux/apps/inventoryApp/src/handlers/inventoryTreeHandler.ts new file mode 100644 index 0000000..fe90d98 --- /dev/null +++ b/features/sdnr/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/features/sdnr/odlux/odlux/apps/inventoryApp/src/handlers/panelHandler.ts b/features/sdnr/odlux/odlux/apps/inventoryApp/src/handlers/panelHandler.ts new file mode 100644 index 0000000..7912d0e --- /dev/null +++ b/features/sdnr/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/features/sdnr/odlux/odlux/apps/inventoryApp/src/index.html b/features/sdnr/odlux/odlux/apps/inventoryApp/src/index.html new file mode 100644 index 0000000..2c44424 --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/inventoryApp/src/index.html @@ -0,0 +1,28 @@ + + + + + + + + + Inventory App + + + +
+ + + + + + \ No newline at end of file diff --git a/features/sdnr/odlux/odlux/apps/inventoryApp/src/models/inventory.ts b/features/sdnr/odlux/odlux/apps/inventoryApp/src/models/inventory.ts new file mode 100644 index 0000000..a09fd7e --- /dev/null +++ b/features/sdnr/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/features/sdnr/odlux/odlux/apps/inventoryApp/src/models/inventoryDeviceListType.ts b/features/sdnr/odlux/odlux/apps/inventoryApp/src/models/inventoryDeviceListType.ts new file mode 100644 index 0000000..ab24114 --- /dev/null +++ b/features/sdnr/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/features/sdnr/odlux/odlux/apps/inventoryApp/src/models/networkElementConnection.ts b/features/sdnr/odlux/odlux/apps/inventoryApp/src/models/networkElementConnection.ts new file mode 100644 index 0000000..e1ef1ea --- /dev/null +++ b/features/sdnr/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/features/sdnr/odlux/odlux/apps/inventoryApp/src/models/panelId.ts b/features/sdnr/odlux/odlux/apps/inventoryApp/src/models/panelId.ts new file mode 100644 index 0000000..8f8224c --- /dev/null +++ b/features/sdnr/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/features/sdnr/odlux/odlux/apps/inventoryApp/src/pluginInventory.tsx b/features/sdnr/odlux/odlux/apps/inventoryApp/src/pluginInventory.tsx new file mode 100644 index 0000000..a59a0cf --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/inventoryApp/src/pluginInventory.tsx @@ -0,0 +1,87 @@ +/** +* ============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'; +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/features/sdnr/odlux/odlux/apps/inventoryApp/src/services/inventoryService.ts b/features/sdnr/odlux/odlux/apps/inventoryApp/src/services/inventoryService.ts new file mode 100644 index 0000000..294a350 --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/inventoryApp/src/services/inventoryService.ts @@ -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 { 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 maintenance entries related actions. + */ +const InventoryService = { + getInventoryTree: async (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; + }, + + getInventoryEntry: async (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. + */ + getInventoryDeviceList: async (): 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 default InventoryService; diff --git a/features/sdnr/odlux/odlux/apps/inventoryApp/src/views/dashboard.tsx b/features/sdnr/odlux/odlux/apps/inventoryApp/src/views/dashboard.tsx new file mode 100644 index 0000000..7f35ea6 --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/inventoryApp/src/views/dashboard.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========================================================================== + */ + +import React, { useEffect, useState } 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 { useApplicationDispatch, useSelectApplicationState } 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 InventoryDeviceListTable = MaterialTable as MaterialTableCtorType; + +let inventoryInitialSorted = false; +const InventoryComponent: React.FC = () => { + + const panelId = useSelectApplicationState((state: IApplicationStoreState) => state.inventory.currentOpenPanel); + const inventoryElementsProperties = useSelectApplicationState((state: IApplicationStoreState) => createInventoryElementsProperties(state)); + const inventoryDeviceList = useSelectApplicationState((state: IApplicationStoreState) => state.inventory.inventoryDeviceList.inventoryDeviceList); + + const dispatch = useApplicationDispatch(); + const switchActivePanel = (panelId: PanelId) => { dispatch(setPanelAction(panelId)); }; + const inventoryElementsActions = createInventoryElementsActions(dispatch); + const navigateToApplication = (applicationName: string, path?: string) => dispatch(new NavigateToApplication(applicationName, path)); + const updateInventoryTree = (mountId: string, searchTerm?: string) => dispatch(updateInventoryTreeAsyncAction(mountId, searchTerm)); + const getAllInventoryDeviceList = async () => { await dispatch(loadAllInventoryDeviceListAsync); }; + + const [refreshInventoryEditorMode, setRefreshInventoryEditorMode] = useState(RefreshInventoryDialogMode.None); + + const onHandleTabChange = (event: React.SyntheticEvent, newValue: PanelId) => { + onTogglePanel(newValue); + }; + + const onTogglePanel = (panelId: PanelId) => { + const nextActivePanel = panelId; + switchActivePanel(nextActivePanel); + + switch (nextActivePanel) { + case 'Equipment': + if (!inventoryInitialSorted) { + inventoryElementsActions.onHandleExplicitRequestSort('nodeId', 'asc'); + inventoryInitialSorted = true; + } else { + inventoryElementsActions.onRefresh(); + } + break; + case 'TreeView': + getAllInventoryDeviceList(); + break; + case null: + // do nothing if all panels are closed + break; + default: + console.warn('Unknown nextActivePanel [' + nextActivePanel + '] in connectView'); + break; + } + }; + + const getContextMenu = (rowData: InventoryType) => { + return [ + { updateInventoryTree(rowData.nodeId, rowData.uuid); navigateToApplication('inventory', rowData.nodeId); }}>View in Treeview, + ]; + }; + + + const refreshInventoryAction = { + icon: Refresh, + tooltip: 'Refresh Inventory', + ariaLabel: 'refresh', + onClick: () => { + setRefreshInventoryEditorMode(RefreshInventoryDialogMode.RefreshInventoryTable); + }, + }; + + + useEffect(() => { + if (panelId === null) { //set default tab if none is set + onTogglePanel('Equipment'); + } + }, [panelId]); + + return ( + <> + + + + + + + + {panelId === 'Equipment' && ( + <> + getContextMenu(rowData)} + /> + setRefreshInventoryEditorMode(RefreshInventoryDialogMode.None)} /> + + )} + + {panelId === 'TreeView' && ( + <> + { + navigateToApplication('inventory', row.nodeId); + updateInventoryTree(row.nodeId, '*'); + }} + rows={inventoryDeviceList} + asynchronus + columns={[{ property: 'nodeId', title: 'Node Name', type: ColumnType.text }]} + idProperty="nodeId" + /> + + )} + + ); +}; + +export const Dashboard = withRouter(InventoryComponent); +export default Dashboard; diff --git a/features/sdnr/odlux/odlux/apps/inventoryApp/src/views/detail.tsx b/features/sdnr/odlux/odlux/apps/inventoryApp/src/views/detail.tsx new file mode 100644 index 0000000..8d47ec3 --- /dev/null +++ b/features/sdnr/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/features/sdnr/odlux/odlux/apps/inventoryApp/src/views/treeview.tsx b/features/sdnr/odlux/odlux/apps/inventoryApp/src/views/treeview.tsx new file mode 100644 index 0000000..f27588f --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/inventoryApp/src/views/treeview.tsx @@ -0,0 +1,133 @@ +/** + * ============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, { useEffect, useState } from 'react'; + +import Breadcrumbs from '@mui/material/Breadcrumbs'; +import Link from '@mui/material/Link'; +import { Theme } from '@mui/material/styles'; +import makeStyles from '@mui/styles/makeStyles'; +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 { useApplicationDispatch, useSelectApplicationState } from '../../../../framework/src/flux/connect'; +import { IApplicationStoreState } from '../../../../framework/src/store/applicationStore'; +import { UpdateExpandedNodesAction, UpdateSelectedNodeAction, selectInventoryNodeAsyncAction, setSearchTermAction, updateInventoryTreeAsyncAction } from '../actions/inventoryTreeActions'; +import { TreeDemoItem } from '../models/inventory'; + +const useStyles = makeStyles((theme: Theme) => ({ + 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 InventoryTree = TreeView as any as TreeViewCtorType; + +interface TreeviewComponentProps extends RouteComponentProps<{ mountId: string }> { + isBusy: boolean; + rootNodes: TreeDemoItem[]; + searchTerm: string; + selectedNode: TreeDemoItem | null; + expendedItems: TreeDemoItem[]; + updateExpendedNodes: (expendedNodes: TreeDemoItem[]) => void; + updateInventoryTree: (mountId: string, searchTerm?: string) => void; + selectTreeNode: (nodeId?: string) => void; + setSearchTerm: (searchTerm: string) => void; +} + +const InventoryTreeView: React.FC = (props) => { + + const rootNodes = useSelectApplicationState((state: IApplicationStoreState) => state.inventory.inventoryTree.rootNodes); + const searchTerm = useSelectApplicationState((state: IApplicationStoreState) => state.inventory.inventoryTree.searchTerm); + const selectedNode = useSelectApplicationState((state: IApplicationStoreState) => state.inventory.inventoryTree.selectedNode); + const expendedItems = useSelectApplicationState((state: IApplicationStoreState) => state.inventory.inventoryTree.expandedItems); + + const dispatch = useApplicationDispatch(); + const updateExpendedNodes = (expendedNodes: TreeDemoItem[]) => dispatch(new UpdateExpandedNodesAction(expendedNodes)); + const updateInventoryTree = (mountId: string, searchTerm?: string) => dispatch(updateInventoryTreeAsyncAction(mountId, searchTerm)); + const selectTreeNode = (nodeId?: string) => nodeId ? dispatch(selectInventoryNodeAsyncAction(nodeId)) : dispatch(new UpdateSelectedNodeAction(undefined)); + const setSearchTerm = (searchTerm: string) => dispatch(setSearchTermAction(searchTerm)); + + const classes = useStyles(); + const [cachedRootNodes, setCachedRootNodes] = useState([]); + + useEffect(() => { + if (cachedRootNodes !== rootNodes) { + setCachedRootNodes(rootNodes); + } + }, [cachedRootNodes, rootNodes]); + + useEffect(() => { + return () => { + setSearchTerm('*'); + }; + }, []); + + const scrollbar = { overflow: 'auto', paddingRight: '20px' }; + let filteredDashboardPath = `/inventory/dashboard/${props.match.params.mountId}`; + let basePath = `/inventory/${props.match.params.mountId}`; + + return ( +
+
+ + ) => { + event.preventDefault(); + props.history.push(filteredDashboardPath); + }}>Back + ) => { + event.preventDefault(); + props.history.push(basePath); + }}>{props.match.params.mountId} + +
+
+
+ updateInventoryTree(props.match.params.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')} +
+
+
+ ); +}; + +export default InventoryTreeView; diff --git a/features/sdnr/odlux/odlux/apps/inventoryApp/tsconfig.json b/features/sdnr/odlux/odlux/apps/inventoryApp/tsconfig.json new file mode 100644 index 0000000..ca65092 --- /dev/null +++ b/features/sdnr/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/features/sdnr/odlux/odlux/apps/inventoryApp/webpack.config.js b/features/sdnr/odlux/odlux/apps/inventoryApp/webpack.config.js new file mode 100644 index 0000000..2ecbc55 --- /dev/null +++ b/features/sdnr/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://sdncweb:8080", + secure: false + }, + "/database/": { + target: "http://sdncweb:8080", + secure: false + }, + "/restconf/": { + target: "http://sdncweb:8080", + secure: false + }, + "/rests/": { + target: "http://sdncweb:8080", + secure: false + }, + "/help/": { + target: "http://sdncweb:8080", + secure: false + }, + "/tree/": { + target: "http://sdncweb:8080", + secure: false + }, + "/websocket": { + target: "http://sdncweb:8080", + ws: true, + changeOrigin: true, + secure: false + }, + "/yang-schema": { + target: "http://sdncweb:8080", + ws: true, + changeOrigin: true, + secure: false + } + } + } + }]; +} diff --git a/features/sdnr/odlux/odlux/apps/maintenanceApp/.babelrc b/features/sdnr/odlux/odlux/apps/maintenanceApp/.babelrc new file mode 100644 index 0000000..3d8cd12 --- /dev/null +++ b/features/sdnr/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/features/sdnr/odlux/odlux/apps/maintenanceApp/package.json b/features/sdnr/odlux/odlux/apps/maintenanceApp/package.json new file mode 100644 index 0000000..d7c3254 --- /dev/null +++ b/features/sdnr/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/features/sdnr/odlux/odlux/apps/maintenanceApp/pom.xml b/features/sdnr/odlux/odlux/apps/maintenanceApp/pom.xml new file mode 100644 index 0000000..be3159f --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/maintenanceApp/pom.xml @@ -0,0 +1,109 @@ + + + + + 4.0.0 + + org.o-ran-sc.oam-controller.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/features/sdnr/odlux/odlux/apps/maintenanceApp/src/actions/maintenenceActions.ts b/features/sdnr/odlux/odlux/apps/maintenanceApp/src/actions/maintenenceActions.ts new file mode 100644 index 0000000..6e52673 --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/maintenanceApp/src/actions/maintenenceActions.ts @@ -0,0 +1,89 @@ +/* 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 maintenance entries. */ +export const addMaintenenceEntryAsyncActionCreator = (entry: MaintenanceEntry) => (dispatch: Dispatch) => { + MaintenenceService.writeMaintenenceEntry(entry).then(result => { + result && window.setTimeout(() => { + // dispatch(loadAllMountedNetworkElementsAsync); + dispatch(new UpdateMaintenanceEntry(entry)); + dispatch(new AddSnackbarNotification({ message: `Successfully created maintenance settings for [${entry.nodeId}]`, options: { variant: 'success' } })); + }, 900); + dispatch(maintenanceEntriesReloadAction); + }); +}; + +/** Represents an async thunk action creator to delete an element from the maintenance 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); + }); +}; + +/** Represents an async thunk action creator to update maintenance entry. */ +export const updateMaintenenceEntryAsyncActionCreator = (entry: MaintenanceEntry) => (dispatch: Dispatch) => { + MaintenenceService.updateMaintenenceEntry(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); + }); +}; + +// 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/features/sdnr/odlux/odlux/apps/maintenanceApp/src/assets/icons/maintenanceAppIcon.svg b/features/sdnr/odlux/odlux/apps/maintenanceApp/src/assets/icons/maintenanceAppIcon.svg new file mode 100644 index 0000000..8b99a5e --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/maintenanceApp/src/assets/icons/maintenanceAppIcon.svg @@ -0,0 +1,50 @@ + + + + + + + + + + + diff --git a/features/sdnr/odlux/odlux/apps/maintenanceApp/src/components/editMaintenenceEntryDialog.tsx b/features/sdnr/odlux/odlux/apps/maintenanceApp/src/components/editMaintenenceEntryDialog.tsx new file mode 100644 index 0000000..62bc882 --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/maintenanceApp/src/components/editMaintenenceEntryDialog.tsx @@ -0,0 +1,189 @@ +/* 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 React, { FC, useEffect, useState } from 'react'; + +import { FormControl, InputLabel, MenuItem, Select, Typography } from '@mui/material'; +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 { useApplicationDispatch } from '../../../../framework/src/flux/connect'; + +import { addMaintenenceEntryAsyncActionCreator, removeFromMaintenenceEntrysAsyncActionCreator, updateMaintenenceEntryAsyncActionCreator } from '../actions/maintenenceActions'; +import { MaintenanceEntry } from '../models/maintenanceEntryType'; + +export enum EditMaintenanceEntryDialogMode { + None = 'none', + AddMaintenanceEntry = 'addMaintenanceEntry', + EditMaintenanceEntry = 'editMaintenanceEntry', + RemoveMaintenanceEntry = 'removeMaintenanceEntry', +} + + +type DialogSettings = { + dialogTitle: string; + dialogDescription: string; + applyButtonText: string; + cancelButtonText: string; + enableMountIdEditor: boolean; + enableTimeEditor: boolean; +}; + +const settings: { [key: string]: DialogSettings } = { + [EditMaintenanceEntryDialogMode.None]: { + dialogTitle: '', + dialogDescription: '', + applyButtonText: '', + cancelButtonText: '', + enableMountIdEditor: false, + enableTimeEditor: false, + }, + [EditMaintenanceEntryDialogMode.AddMaintenanceEntry]: { + dialogTitle: 'Add new maintenance entry', + dialogDescription: '', + applyButtonText: 'Add', + cancelButtonText: 'Cancel', + enableMountIdEditor: true, + enableTimeEditor: true, + }, + [EditMaintenanceEntryDialogMode.EditMaintenanceEntry]: { + dialogTitle: 'Edit maintenance entry', + dialogDescription: '', + applyButtonText: 'Save', + cancelButtonText: 'Cancel', + enableMountIdEditor: false, + enableTimeEditor: true, + }, + [EditMaintenanceEntryDialogMode.RemoveMaintenanceEntry]: { + dialogTitle: 'Remove maintenance entry', + dialogDescription: '', + applyButtonText: 'Remove', + cancelButtonText: 'Cancel', + enableMountIdEditor: false, + enableTimeEditor: false, + }, +}; + +type EditMaintenanceEntryDIalogComponentProps = { + mode: EditMaintenanceEntryDialogMode; + initialMaintenanceEntry: MaintenanceEntry; + onClose: () => void; +}; + + +const EditMaintenanceEntryDIalogComponent: FC = (props) => { + + const dispatch = useApplicationDispatch(); + const addMaintenanceEntry = (entry: MaintenanceEntry) => { + dispatch(addMaintenenceEntryAsyncActionCreator(entry)); + }; + const updateMaintenanceEntry = (entry: MaintenanceEntry) => { + dispatch(updateMaintenenceEntryAsyncActionCreator(entry)); + }; + const removeMaintenanceEntry = (entry: MaintenanceEntry) => { + dispatch(removeFromMaintenenceEntrysAsyncActionCreator(entry)); + }; + + const [maintenanceEntry, setMaintenanceEntry] = useState({ ...props.initialMaintenanceEntry }); + const [isErrorVisible, setIsErrorVisible] = useState(false); + + const setting = settings[props.mode]; + + + const onApply = (entry: MaintenanceEntry) => { + props.onClose && props.onClose(); + switch (props.mode) { + case EditMaintenanceEntryDialogMode.AddMaintenanceEntry: + entry && addMaintenanceEntry(entry); + break; + case EditMaintenanceEntryDialogMode.EditMaintenanceEntry: + entry && updateMaintenanceEntry(entry); + break; + case EditMaintenanceEntryDialogMode.RemoveMaintenanceEntry: + entry && removeMaintenanceEntry(entry); + break; + } + }; + + const onCancel = () => { + props.onClose && props.onClose(); + }; + + useEffect(() => { + setMaintenanceEntry(props.initialMaintenanceEntry); + }, [props.initialMaintenanceEntry]); + + return ( + + {setting.dialogTitle} + + + {setting.dialogDescription} + + { setMaintenanceEntry({ ...maintenanceEntry, nodeId: event.target.value }); }} /> + {isErrorVisible && Name must not be empty.} + { setMaintenanceEntry({ ...maintenanceEntry, start: event.target.value }) }} /> + { setMaintenanceEntry({ ...maintenanceEntry, end: event.target.value }) }} /> + + Active + + + + + + + + + ); +} + +export const EditMaintenanceEntryDIalog = EditMaintenanceEntryDIalogComponent; +export default EditMaintenanceEntryDIalog; \ No newline at end of file diff --git a/features/sdnr/odlux/odlux/apps/maintenanceApp/src/components/refreshMaintenanceEntries.tsx b/features/sdnr/odlux/odlux/apps/maintenanceApp/src/components/refreshMaintenanceEntries.tsx new file mode 100644 index 0000000..988b0dc --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/maintenanceApp/src/components/refreshMaintenanceEntries.tsx @@ -0,0 +1,103 @@ +/** + * ============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 } 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 { useApplicationDispatch } from '../../../../framework/src/flux/connect'; + +import { maintenanceEntriesReloadAction } from '../handlers/maintenanceEntriesHandler'; + +export enum RefreshMaintenanceEntriesDialogMode { + None = 'none', + RefreshMaintenanceEntriesTable = 'RefreshMaintenanceEntriesTable', +} + +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 = { + mode: RefreshMaintenanceEntriesDialogMode; + onClose: () => void; +}; + +const RefreshMaintenanceEntriesDialogComponent: FC = (props) => { + const dispatch = useApplicationDispatch(); + const refreshMaintenanceEntries = () => dispatch(maintenanceEntriesReloadAction); + const setting = settings[props.mode]; + + const onRefresh = () => { + refreshMaintenanceEntries(); + props.onClose(); + }; + + const onCancel = () => { + props.onClose(); + }; + + return ( + + {setting.dialogTitle} + + + {setting.dialogDescription} + + + + + + + + ); +}; + +export const RefreshMaintenanceEntriesDialog = RefreshMaintenanceEntriesDialogComponent; +export default RefreshMaintenanceEntriesDialog; diff --git a/features/sdnr/odlux/odlux/apps/maintenanceApp/src/handlers/maintenanceAppRootHandler.ts b/features/sdnr/odlux/odlux/apps/maintenanceApp/src/handlers/maintenanceAppRootHandler.ts new file mode 100644 index 0000000..ced7f21 --- /dev/null +++ b/features/sdnr/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/features/sdnr/odlux/odlux/apps/maintenanceApp/src/handlers/maintenanceEntriesHandler.ts b/features/sdnr/odlux/odlux/apps/maintenanceApp/src/handlers/maintenanceEntriesHandler.ts new file mode 100644 index 0000000..c3fe51e --- /dev/null +++ b/features/sdnr/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/features/sdnr/odlux/odlux/apps/maintenanceApp/src/index.html b/features/sdnr/odlux/odlux/apps/maintenanceApp/src/index.html new file mode 100644 index 0000000..bbf0109 --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/maintenanceApp/src/index.html @@ -0,0 +1,26 @@ + + + + + + + + + Maintenance App + + + +
+ + + + + + \ No newline at end of file diff --git a/features/sdnr/odlux/odlux/apps/maintenanceApp/src/models/maintenanceEntryType.ts b/features/sdnr/odlux/odlux/apps/maintenanceApp/src/models/maintenanceEntryType.ts new file mode 100644 index 0000000..27cdc8c --- /dev/null +++ b/features/sdnr/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/features/sdnr/odlux/odlux/apps/maintenanceApp/src/pluginMaintenance.tsx b/features/sdnr/odlux/odlux/apps/maintenanceApp/src/pluginMaintenance.tsx new file mode 100644 index 0000000..accbc5e --- /dev/null +++ b/features/sdnr/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/features/sdnr/odlux/odlux/apps/maintenanceApp/src/services/maintenenceService.ts b/features/sdnr/odlux/odlux/apps/maintenanceApp/src/services/maintenenceService.ts new file mode 100644 index 0000000..01f7b50 --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/maintenanceApp/src/services/maintenenceService.ts @@ -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 { 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. + */ +const MaintenenceService = { + /** + * Adds maintenence entry to the backend. + */ + writeMaintenenceEntry: async (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; + }, + + /** + * Updates maintenence entry to the backend. + */ + updateMaintenenceEntry: async (maintenenceEntry: MaintenanceEntry): Promise => { + const path = '/rests/operations/data-provider:update-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. + */ + deleteMaintenenceEntry: async (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 default MaintenenceService; \ No newline at end of file diff --git a/features/sdnr/odlux/odlux/apps/maintenanceApp/src/utils/timeUtils.ts b/features/sdnr/odlux/odlux/apps/maintenanceApp/src/utils/timeUtils.ts new file mode 100644 index 0000000..0fde5fc --- /dev/null +++ b/features/sdnr/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/features/sdnr/odlux/odlux/apps/maintenanceApp/src/views/maintenanceView.tsx b/features/sdnr/odlux/odlux/apps/maintenanceApp/src/views/maintenanceView.tsx new file mode 100644 index 0000000..82b4fa8 --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/maintenanceApp/src/views/maintenanceView.tsx @@ -0,0 +1,177 @@ +/** + * ============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, { useEffect, useState } 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, Typography } from '@mui/material'; + +import MaterialTable, { ColumnType, MaterialTableCtorType } from '../../../../framework/src/components/material-table'; +import { useApplicationDispatch, useSelectApplicationState } from '../../../../framework/src/flux/connect'; +import { IApplicationStoreState } from '../../../../framework/src/store/applicationStore'; + +import EditMaintenanceEntryDialog, { EditMaintenanceEntryDialogMode } 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 MaintenanceEntriesTable = MaterialTable as MaterialTableCtorType; + +const emptyMaintenanceEntry: MaintenanceEntry = { + mId: '', + nodeId: '', + description: '', + start: convertToLocaleString(new Date().valueOf()), + end: convertToLocaleString(new Date().valueOf()), + active: false, +}; + +const MaintenanceView = () => { + + const maintenanceEntriesProperties = useSelectApplicationState((state: IApplicationStoreState) => createmaintenanceEntriesProperties(state)); + const dispatch = useApplicationDispatch(); + const maintenanceEntriesActions = createmaintenanceEntriesActions(dispatch); + const onLoadMaintenanceEntries = async () => { await dispatch(maintenanceEntriesReloadAction); }; + + let initialSorted = false; + const [maintenanceEntryToEdit, setMaintenanceEntryToEdit] = useState(emptyMaintenanceEntry); + const [maintenanceEntryEditorMode, setMaintenanceEntryEditorMode] = useState(EditMaintenanceEntryDialogMode.None); + const [refreshMaintenanceEntriesEditorMode, setRefreshMaintenanceEntriesEditorMode] = useState(RefreshMaintenanceEntriesDialogMode.None); + + useEffect(() => { + if (!initialSorted) { + initialSorted = true; + maintenanceEntriesActions.onHandleRequestSort('node-id'); + } else { + onLoadMaintenanceEntries(); + } + }, []); + + const getContextMenu = (rowData: MaintenanceEntry): JSX.Element[] => { + return [ + onOpenPlus1hEditMaintenanceEntryDialog(event, rowData)}>+1h, + onOpenPlus8hEditMaintenanceEntryDialog(event, rowData)}>+8h, + , + onOpenEditMaintenanceEntryDialog(event, rowData)}>Edit, + onOpenRemoveMaintenanceEntryDialog(event, rowData)}>Remove, + ]; + }; + + const addMaintenanceEntryAction = { + icon: AddIcon, tooltip: 'Add', ariaLabel: 'add-element', onClick: () => { + const startTime = (new Date().valueOf()); + const endTime = startTime; + setMaintenanceEntryToEdit({ + ...emptyMaintenanceEntry, + start: convertToLocaleString(startTime), + end: convertToLocaleString(endTime), + }); + setMaintenanceEntryEditorMode(EditMaintenanceEntryDialogMode.AddMaintenanceEntry); + }, + }; + + const refreshMaintenanceEntriesAction = { + icon: Refresh, tooltip: 'Refresh Maintenance Entries', ariaLabel: 'refresh', onClick: () => { + setRefreshMaintenanceEntriesEditorMode(RefreshMaintenanceEntriesDialogMode.RefreshMaintenanceEntriesTable); + }, + }; + + const onOpenPlus1hEditMaintenanceEntryDialog = (event: React.MouseEvent, entry: MaintenanceEntry) => { + const startTime = (new Date().valueOf()); + const endTime = startTime + (1 * 60 * 60 * 1000); + setMaintenanceEntryToEdit({ + ...entry, + start: convertToLocaleString(startTime), + end: convertToLocaleString(endTime), + }); + setMaintenanceEntryEditorMode(EditMaintenanceEntryDialogMode.EditMaintenanceEntry); + } + + const onOpenPlus8hEditMaintenanceEntryDialog = (event: React.MouseEvent, entry: MaintenanceEntry) => { + const startTime = (new Date().valueOf()); + const endTime = startTime + (8 * 60 * 60 * 1000); + setMaintenanceEntryToEdit({ + ...entry, + start: convertToLocaleString(startTime), + end: convertToLocaleString(endTime), + }); + setMaintenanceEntryEditorMode(EditMaintenanceEntryDialogMode.EditMaintenanceEntry); + } + + const onOpenEditMaintenanceEntryDialog = (event: React.MouseEvent, entry: MaintenanceEntry) => { + const startTime = (new Date().valueOf()); + const endTime = startTime; + setMaintenanceEntryToEdit({ + ...entry, + ...(entry.start && endTime ? { start: convertToLocaleString(entry.start), end: convertToLocaleString(entry.end) } : { start: convertToLocaleString(startTime), end: convertToLocaleString(endTime) }), + }); + setMaintenanceEntryEditorMode(EditMaintenanceEntryDialogMode.EditMaintenanceEntry); + } + + const onOpenRemoveMaintenanceEntryDialog = (event: React.MouseEvent, entry: MaintenanceEntry) => { + const startTime = (new Date().valueOf()); + const endTime = startTime; + setMaintenanceEntryToEdit({ + ...entry, + ...(entry.start && endTime ? { start: convertToLocaleString(entry.start), end: convertToLocaleString(entry.end) } : { start: convertToLocaleString(startTime), end: convertToLocaleString(endTime) }), + }); + setMaintenanceEntryEditorMode(EditMaintenanceEntryDialogMode.RemoveMaintenanceEntry); + } + + const onCloseEditMaintenanceEntryDialog = () => { + setMaintenanceEntryToEdit(emptyMaintenanceEntry); + setMaintenanceEntryEditorMode(EditMaintenanceEntryDialogMode.None); + } + + const onCloseRefreshMaintenanceEntryDialog = () => { + setRefreshMaintenanceEntriesEditorMode(RefreshMaintenanceEntriesDialogMode.None); + } + + 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'}{...maintenanceEntriesActions} {...maintenanceEntriesProperties} asynchronus createContextMenu={rowData => { + return getContextMenu(rowData); + }} > + + + + + ); +}; + +export default MaintenanceView; diff --git a/features/sdnr/odlux/odlux/apps/maintenanceApp/tsconfig.json b/features/sdnr/odlux/odlux/apps/maintenanceApp/tsconfig.json new file mode 100644 index 0000000..ca65092 --- /dev/null +++ b/features/sdnr/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/features/sdnr/odlux/odlux/apps/maintenanceApp/webpack.config.js b/features/sdnr/odlux/odlux/apps/maintenanceApp/webpack.config.js new file mode 100644 index 0000000..fb69e37 --- /dev/null +++ b/features/sdnr/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://sdncweb:8080", + secure: false + }, + "/database/": { + target: "http://sdncweb:8080", + secure: false + }, + "/restconf/": { + target: "http://sdncweb:8080", + secure: false + }, + "/rests/": { + target: "http://sdncweb:8080", + secure: false + }, + "/help/": { + target: "http://sdncweb:8080", + secure: false + }, + "/websocket": { + target: "http://sdncweb:8080", + ws: true, + changeOrigin: true, + secure: false + } + } + } + }]; +} diff --git a/features/sdnr/odlux/odlux/apps/mediatorApp/.babelrc b/features/sdnr/odlux/odlux/apps/mediatorApp/.babelrc new file mode 100644 index 0000000..3d8cd12 --- /dev/null +++ b/features/sdnr/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/features/sdnr/odlux/odlux/apps/mediatorApp/package.json b/features/sdnr/odlux/odlux/apps/mediatorApp/package.json new file mode 100644 index 0000000..867a879 --- /dev/null +++ b/features/sdnr/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/features/sdnr/odlux/odlux/apps/mediatorApp/pom.xml b/features/sdnr/odlux/odlux/apps/mediatorApp/pom.xml new file mode 100644 index 0000000..cc5ff10 --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/mediatorApp/pom.xml @@ -0,0 +1,105 @@ + + + + + 4.0.0 + + org.o-ran-sc.oam-controller.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/features/sdnr/odlux/odlux/apps/mediatorApp/src/actions/avaliableMediatorServersActions.ts b/features/sdnr/odlux/odlux/apps/mediatorApp/src/actions/avaliableMediatorServersActions.ts new file mode 100644 index 0000000..3f56b05 --- /dev/null +++ b/features/sdnr/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/features/sdnr/odlux/odlux/apps/mediatorApp/src/actions/mediatorConfigActions.ts b/features/sdnr/odlux/odlux/apps/mediatorApp/src/actions/mediatorConfigActions.ts new file mode 100644 index 0000000..516515a --- /dev/null +++ b/features/sdnr/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/features/sdnr/odlux/odlux/apps/mediatorApp/src/actions/mediatorServerActions.ts b/features/sdnr/odlux/odlux/apps/mediatorApp/src/actions/mediatorServerActions.ts new file mode 100644 index 0000000..79e46df --- /dev/null +++ b/features/sdnr/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/features/sdnr/odlux/odlux/apps/mediatorApp/src/assets/icons/mediatorAppIcon.svg b/features/sdnr/odlux/odlux/apps/mediatorApp/src/assets/icons/mediatorAppIcon.svg new file mode 100644 index 0000000..b819aa6 --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/mediatorApp/src/assets/icons/mediatorAppIcon.svg @@ -0,0 +1,49 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/features/sdnr/odlux/odlux/apps/mediatorApp/src/components/editMediatorConfigDialog.tsx b/features/sdnr/odlux/odlux/apps/mediatorApp/src/components/editMediatorConfigDialog.tsx new file mode 100644 index 0000000..34ffc5e --- /dev/null +++ b/features/sdnr/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/features/sdnr/odlux/odlux/apps/mediatorApp/src/components/editMediatorServerDialog.tsx b/features/sdnr/odlux/odlux/apps/mediatorApp/src/components/editMediatorServerDialog.tsx new file mode 100644 index 0000000..c8b1587 --- /dev/null +++ b/features/sdnr/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/features/sdnr/odlux/odlux/apps/mediatorApp/src/components/refreshMediatorDialog.tsx b/features/sdnr/odlux/odlux/apps/mediatorApp/src/components/refreshMediatorDialog.tsx new file mode 100644 index 0000000..db1ef87 --- /dev/null +++ b/features/sdnr/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/features/sdnr/odlux/odlux/apps/mediatorApp/src/components/showMeditaorInfoDialog.tsx b/features/sdnr/odlux/odlux/apps/mediatorApp/src/components/showMeditaorInfoDialog.tsx new file mode 100644 index 0000000..f8691ab --- /dev/null +++ b/features/sdnr/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/features/sdnr/odlux/odlux/apps/mediatorApp/src/handlers/avaliableMediatorServersHandler.ts b/features/sdnr/odlux/odlux/apps/mediatorApp/src/handlers/avaliableMediatorServersHandler.ts new file mode 100644 index 0000000..c22252d --- /dev/null +++ b/features/sdnr/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/features/sdnr/odlux/odlux/apps/mediatorApp/src/handlers/mediatorAppRootHandler.ts b/features/sdnr/odlux/odlux/apps/mediatorApp/src/handlers/mediatorAppRootHandler.ts new file mode 100644 index 0000000..2642ec8 --- /dev/null +++ b/features/sdnr/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/features/sdnr/odlux/odlux/apps/mediatorApp/src/handlers/mediatorServerHandler.ts b/features/sdnr/odlux/odlux/apps/mediatorApp/src/handlers/mediatorServerHandler.ts new file mode 100644 index 0000000..246634c --- /dev/null +++ b/features/sdnr/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/features/sdnr/odlux/odlux/apps/mediatorApp/src/index.html b/features/sdnr/odlux/odlux/apps/mediatorApp/src/index.html new file mode 100644 index 0000000..95bf9ec --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/mediatorApp/src/index.html @@ -0,0 +1,29 @@ + + + + + + + + + Mediator App + + + +
+ + + + + + \ No newline at end of file diff --git a/features/sdnr/odlux/odlux/apps/mediatorApp/src/models/mediatorServer.ts b/features/sdnr/odlux/odlux/apps/mediatorApp/src/models/mediatorServer.ts new file mode 100644 index 0000000..6ab6db8 --- /dev/null +++ b/features/sdnr/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/features/sdnr/odlux/odlux/apps/mediatorApp/src/plugin.tsx b/features/sdnr/odlux/odlux/apps/mediatorApp/src/plugin.tsx new file mode 100644 index 0000000..1c30cfc --- /dev/null +++ b/features/sdnr/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/features/sdnr/odlux/odlux/apps/mediatorApp/src/services/mediatorService.ts b/features/sdnr/odlux/odlux/apps/mediatorApp/src/services/mediatorService.ts new file mode 100644 index 0000000..ede6817 --- /dev/null +++ b/features/sdnr/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/features/sdnr/odlux/odlux/apps/mediatorApp/src/views/mediatorApplication.tsx b/features/sdnr/odlux/odlux/apps/mediatorApp/src/views/mediatorApplication.tsx new file mode 100644 index 0000000..03ce453 --- /dev/null +++ b/features/sdnr/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/features/sdnr/odlux/odlux/apps/mediatorApp/src/views/mediatorServerSelection.tsx b/features/sdnr/odlux/odlux/apps/mediatorApp/src/views/mediatorServerSelection.tsx new file mode 100644 index 0000000..fb12f26 --- /dev/null +++ b/features/sdnr/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/features/sdnr/odlux/odlux/apps/mediatorApp/tsconfig.json b/features/sdnr/odlux/odlux/apps/mediatorApp/tsconfig.json new file mode 100644 index 0000000..c950056 --- /dev/null +++ b/features/sdnr/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/features/sdnr/odlux/odlux/apps/mediatorApp/tslint.json b/features/sdnr/odlux/odlux/apps/mediatorApp/tslint.json new file mode 100644 index 0000000..b5143e9 --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/mediatorApp/tslint.json @@ -0,0 +1,4 @@ +{ + "rules":{ + } +} \ No newline at end of file diff --git a/features/sdnr/odlux/odlux/apps/mediatorApp/webpack.config.js b/features/sdnr/odlux/odlux/apps/mediatorApp/webpack.config.js new file mode 100644 index 0000000..813c28f --- /dev/null +++ b/features/sdnr/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/features/sdnr/odlux/odlux/apps/microwaveApp/.babelrc b/features/sdnr/odlux/odlux/apps/microwaveApp/.babelrc new file mode 100644 index 0000000..3d8cd12 --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/microwaveApp/.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/features/sdnr/odlux/odlux/apps/microwaveApp/package.json b/features/sdnr/odlux/odlux/apps/microwaveApp/package.json new file mode 100644 index 0000000..202b5da --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/microwaveApp/package.json @@ -0,0 +1,55 @@ +{ + "name": "@odlux/microwave-app", + "version": "0.1.0", + "description": "A react based modular UI to do link analysis.", + "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": "Mohammad Boroon", + "license": "Apache-2.0", + "dependencies": { + "@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": "*", + "@types/d3": "^6.7.0", + "@types/node": "^12.0.0", + "d3": "^7.0.0", + "d3-polygon": "^3.0.1", + "maplibre-gl": "^3.0.0", + "object.values": "^1.1.1" + }, + "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" + }, + "devDependencies": { + "css-loader": "1.0.0", + "webpack": "^4.44.2" + } +} diff --git a/features/sdnr/odlux/odlux/apps/microwaveApp/pom.xml b/features/sdnr/odlux/odlux/apps/microwaveApp/pom.xml new file mode 100644 index 0000000..4a8bcb4 --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/microwaveApp/pom.xml @@ -0,0 +1,109 @@ + + + + + 4.0.0 + + org.o-ran-sc.oam-controller.features.sdnr.odlux + sdnr-odlux-app-microwaveApp + 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/features/sdnr/odlux/odlux/apps/microwaveApp/src/index.html b/features/sdnr/odlux/odlux/apps/microwaveApp/src/index.html new file mode 100644 index 0000000..f76b445 --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/microwaveApp/src/index.html @@ -0,0 +1,29 @@ + + + + + + + + + Microwave App + + + +
+ + + + + + \ No newline at end of file diff --git a/features/sdnr/odlux/odlux/apps/microwaveApp/src/lineOfSight/actions/lineOfSightCommonActions.ts b/features/sdnr/odlux/odlux/apps/microwaveApp/src/lineOfSight/actions/lineOfSightCommonActions.ts new file mode 100644 index 0000000..b508c5d --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/microwaveApp/src/lineOfSight/actions/lineOfSightCommonActions.ts @@ -0,0 +1,49 @@ +/** + * ============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 { Height } from '../model/lineOfSightHeight'; +import { LatLon } from '../model/lineOfSightLatLon'; + +import { calculateMidPoint } from '../utils/lineOfSightMap'; +import { isNumber } from '../utils/lineOfSightMath'; + +export class SetPassedInValuesAction extends Action { + constructor(public start: LatLon, public end: LatLon, public center: LatLon, public heightA: Height | null, public heightB: Height | null) { + super(); + } +} + +export class SetReachableAction extends Action { + constructor(public reachable: boolean | null) { + super(); + } +} + +export const SetPassedInValues = (values: (string | null)[]) => (dispatcher: Dispatch) => { + const start: LatLon = { latitude: Number(values[0]), longitude: Number(values[1]) }; + const end: LatLon = { latitude: Number(values[2]), longitude: Number(values[3]) }; + const midpoint = calculateMidPoint(start.latitude, start.longitude, end.latitude, end.longitude); + const center: LatLon = { latitude: midpoint.latitude, longitude: midpoint.longitude }; + const heightA: Height | null = isNumber(values[4]) && isNumber(values[5]) ? { amsl: +values[4]!, antennaHeight: +values[5]! } : null; + const heightB: Height | null = isNumber(values[6]) && isNumber(values[7]) ? { amsl: +values[6]!, antennaHeight: +values[7]! } : null; + dispatcher(new SetPassedInValuesAction(start, end, center, heightA, heightB)); +}; \ No newline at end of file diff --git a/features/sdnr/odlux/odlux/apps/microwaveApp/src/lineOfSight/actions/lineOfSightMapActions.ts b/features/sdnr/odlux/odlux/apps/microwaveApp/src/lineOfSight/actions/lineOfSightMapActions.ts new file mode 100644 index 0000000..ca5d773 --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/microwaveApp/src/lineOfSight/actions/lineOfSightMapActions.ts @@ -0,0 +1,68 @@ +/** + * ============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 { Height } from '../model/lineOfSightHeight'; +import { LatLon } from '../model/lineOfSightLatLon'; + +export class SetChartAction extends Action { + constructor(public startPoint: LatLon, public endPoint: LatLon, public heightA: Height, public heightB: Height) { + super(); + } +} + +export class SetStartPointAction extends Action { + constructor(public startPoint: LatLon | null) { + super(); + } +} + +export class SetEndpointAction extends Action { + constructor(public endPoint: LatLon | null) { + super(); + } +} + +export class SetHeightA extends Action { + constructor(public height: Height) { + super(); + } +} + +export class SetHeightB extends Action { + constructor(public height: Height) { + super(); + } +} + +export class ClearSavedChartAction extends Action { + // eslint-disable-next-line @typescript-eslint/no-useless-constructor + constructor() { + super(); + } +} + +export class SetMapCenterAction extends Action { + /** + * + */ + constructor(public point: LatLon, public zoom: number) { + super(); + + } +} \ No newline at end of file diff --git a/features/sdnr/odlux/odlux/apps/microwaveApp/src/lineOfSight/assets/lineOfSightAppIcon.svg b/features/sdnr/odlux/odlux/apps/microwaveApp/src/lineOfSight/assets/lineOfSightAppIcon.svg new file mode 100644 index 0000000..47b881e --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/microwaveApp/src/lineOfSight/assets/lineOfSightAppIcon.svg @@ -0,0 +1,19 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/features/sdnr/odlux/odlux/apps/microwaveApp/src/lineOfSight/components/lineOfSightConnectionErrorPoup.tsx b/features/sdnr/odlux/odlux/apps/microwaveApp/src/lineOfSight/components/lineOfSightConnectionErrorPoup.tsx new file mode 100644 index 0000000..5a97234 --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/microwaveApp/src/lineOfSight/components/lineOfSightConnectionErrorPoup.tsx @@ -0,0 +1,39 @@ +/** + * ============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 React, { FC } from 'react'; + +import { faExclamationTriangle } from '@fortawesome/free-solid-svg-icons'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { Paper, Typography } from '@mui/material'; + +type ConnectionErrorPopupProps = { reachable: boolean | null }; + +const ConnectionErrorPopup: FC = (props) => { + + return (props.reachable != null && !props.reachable ? + +
+
Connection Error
+ Service unavailable +
+
: null + ); +}; + +export default ConnectionErrorPopup; \ No newline at end of file diff --git a/features/sdnr/odlux/odlux/apps/microwaveApp/src/lineOfSight/components/lineOfSightHeightChart.tsx b/features/sdnr/odlux/odlux/apps/microwaveApp/src/lineOfSight/components/lineOfSightHeightChart.tsx new file mode 100644 index 0000000..f4730c5 --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/microwaveApp/src/lineOfSight/components/lineOfSightHeightChart.tsx @@ -0,0 +1,122 @@ +/** + * ============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 React, { FC } from 'react'; + +import * as d3 from 'd3'; + +import { useD3 } from '../hooks/d3'; +import { GPSProfileResult } from '../model/lineOfSightGPSProfileResult'; +import { max } from '../utils/lineOfSightMath'; + +type HeightMapProps = { + data: GPSProfileResult[]; + dataMin: GPSProfileResult; + dataMax: GPSProfileResult; + width: number; + height: number; + heightPosA: number; + heightPosB: number; +}; + +const HeightChart: FC = (props) => { + const { data, dataMin, dataMax, heightPosA, heightPosB } = props; + let ref: React.RefObject; + + const drawSvg = () => { + ref = useD3( + (svg) => { + const margin = 100; + const width = Number(svg.attr('width')) - margin; + const height = Number(svg.attr('height')) - margin; + + // Add X axis + const x = d3.scaleBand() + .range([0, width]) + .domain(data.map(d => (`${d.gps.latitude},${d.gps.latitude}`))) + .padding(0.2); + + const maxHeight = max([dataMax.height, heightPosA, heightPosB], d => d); + + // Add Y axis + const y = d3.scaleLinear() + .domain([dataMin.height, maxHeight]) + .range([height, 0]); + + svg.append('g') + .attr('transform', `translate(${margin / 2}, ${margin / 2})`) + .call(d3.axisLeft(y)); + + // Bars + svg.selectAll('myBar') + .data(data) + .join('rect') + .attr('transform', `translate(${margin / 2}, ${margin / 2})`) + .attr('x', d => x(`${d.gps.latitude},${d.gps.latitude}`) || '') + .attr('y', d => y(d.height)) + .attr('width', x.bandwidth()) + .attr('fill', '#69b3a2b0') + .attr('height', d => height - y(d.height)); // always equal to 0 + + const firstX = `${data[0].gps.latitude},${data[0].gps.latitude}`; + const lastX = `${data[data.length - 1].gps.latitude},${data[data.length - 1].gps.latitude}`; + + //add line + const x1 = x(firstX)!; + const x2 = x(lastX)!; + + svg.append('line') + .attr('transform', `translate(${margin / 2}, ${margin / 2})`) + .attr('x1', x1) + .attr('y1', y(props.heightPosA)) + .attr('x2', x2) + .attr('y2', y(props.heightPosB)) + + .style('stroke', '#88A') + .attr('stroke-width', '3px'); + + //append circle on start and end + + svg.append('circle') + .attr('transform', `translate(${margin / 2}, ${margin / 2})`) + .attr('cx', x1) + .attr('cy', y(props.heightPosA)) + .attr('r', 10) + .attr('stroke', '#223b53') + .attr('fill', '#225ba3'); + + svg.append('circle') + .attr('transform', `translate(${margin / 2}, ${margin / 2})`) + .attr('cx', x2) + .attr('cy', y(props.heightPosB)) + .attr('r', 10) + .attr('stroke', '#223b53') + .attr('fill', '#225ba3'); + }, + [data], + ); + }; + + drawSvg(); + + return ( + + ); +}; + +export { HeightChart }; diff --git a/features/sdnr/odlux/odlux/apps/microwaveApp/src/lineOfSight/components/lineOfSightMap.tsx b/features/sdnr/odlux/odlux/apps/microwaveApp/src/lineOfSight/components/lineOfSightMap.tsx new file mode 100644 index 0000000..4b55779 --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/microwaveApp/src/lineOfSight/components/lineOfSightMap.tsx @@ -0,0 +1,290 @@ +/* eslint-disable @typescript-eslint/no-shadow */ +/** + * ============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 React, { FC, useEffect, useRef, useState } from 'react'; +import { RouteComponentProps, withRouter } from 'react-router-dom'; + +import makeStyles from '@mui/styles/makeStyles'; + +import maplibre from 'maplibre-gl'; +import 'maplibre-gl/dist/maplibre-gl.css'; + +import { render } from 'react-dom'; + +import { useApplicationDispatch, useSelectApplicationState } from '../../../../../framework/src/flux/connect'; + +import { SetReachableAction } from '../actions/lineOfSightCommonActions'; +import { ClearSavedChartAction, SetEndpointAction, SetHeightA, SetHeightB, SetMapCenterAction, SetStartPointAction } from '../actions/lineOfSightMapActions'; + +import { GPSProfileResult } from '../model/lineOfSightGPSProfileResult'; +import { Height } from '../model/lineOfSightHeight'; +import { LatLon } from '../model/lineOfSightLatLon'; + +import { getGPSProfile } from '../service/lineOfSightHeightService'; +import { addBaseLayer, addBaseSource, addPoint, calculateMidPoint } from '../utils/lineOfSightMap'; +import { max, min } from '../utils/lineOfSightMath'; + +import ConnectionErrorPopup from './lineOfSightConnectionErrorPoup'; +import { HeightChart } from './lineOfSightHeightChart'; +import MapContextMenu from './lineOfSightMapContextMenu'; +import MapInfo from './lineOfSightMapInfo'; + +import { OSM_STYLE, URL_BASEPATH } from '../config'; + +type MapProps = RouteComponentProps; + +const styles = makeStyles({ + chart: { + position: 'absolute', + top: 0, + bottom: 0, + left: 0, + right: 0, + }, +}); + +let map: maplibregl.Map; + +const Map: FC = (props) => { + + const center = useSelectApplicationState(state => state.microwave.map.center); + const zoom = useSelectApplicationState(state => state.microwave.map.zoom); + const start = useSelectApplicationState(state => state.microwave.map.start); + const end = useSelectApplicationState(state => state.microwave.map.end); + const siteAHeight = useSelectApplicationState(state => state.microwave.map.heightA); + const siteBHeight = useSelectApplicationState(state => state.microwave.map.heightB); + const ready = useSelectApplicationState(state => state.microwave.map.ready); + + const dispatch = useApplicationDispatch(); + const clearChartAction = () => dispatch(new ClearSavedChartAction); + const setMapPosition = (point: LatLon, zoom: number) => dispatch(new SetMapCenterAction(point, zoom)); + const setHeightStart = (height: Height) => dispatch(new SetHeightA(height)); + const setHeightEnd = (height: Height) => dispatch(new SetHeightB(height)); + const setStartPosition = (position: LatLon | null) => dispatch(new SetStartPointAction(position)); + const setEndPosition = (position: LatLon | null) => dispatch(new SetEndpointAction(position)); + const setReachable = (reachable: boolean | null) => dispatch(new SetReachableAction(reachable)); + + const [data, setData] = useState(Number.NaN); + const [dataMin, setDataMin] = useState(); + const [dataMax, setDataMax] = useState(); + const [isMapLoaded, setMapLoaded] = useState(false); + + const mapRef = useRef<{ map: maplibregl.Map | null }>({ map: null }); + const mapContainerRef = useRef(null); + + const classes = styles(); + + const heightA = siteAHeight !== null ? siteAHeight.amsl + siteAHeight.antennaHeight : 0; + const heightB = siteBHeight !== null ? siteBHeight.amsl + siteBHeight.antennaHeight : 0; + + const handleResize = () => { + + if (map) { + // wait a moment until resizing actually happened + window.setTimeout(() => map.resize(), 500); + } + }; + + useEffect(() => { + window.addEventListener('menu-resized', handleResize); + return () => { + window.removeEventListener('menu-resized', handleResize); + const center = mapRef.current.map?.getCenter(); + const mapZoom = mapRef.current.map?.getZoom(); + if (center) { + setMapPosition({ latitude: center.lat, longitude: center.lng }, mapZoom!); + } + setReachable(null); + }; + + }, []); + + + + const zoomInOnLink = (start: LatLon, end: LatLon) => { + const center = calculateMidPoint(start.latitude, start.longitude, end.latitude, end.longitude); + const newBounds = new maplibre.LngLatBounds(); + const allValues = { center, start, end }; + Object.values(allValues).forEach(value => { + if (value) { + newBounds.extend([value.longitude, value.latitude]); + } + }); + //zooms in/out to accumulate bounding box + map.fitBounds(newBounds, { padding: 20 }); + }; + + const drawChart = () => { + if (start && end) { + addBaseSource(map, 'route'); + addBaseLayer(map, 'route'); + zoomInOnLink(start, end); + const json = `{ + "type": "Feature", + "properties": {}, + "geometry": { + "type": "LineString", + "coordinates": [ + [${start.longitude}, ${start.latitude}], + [${end.longitude}, ${end.latitude}] + ]} + }`; + (map.getSource('route') as maplibregl.GeoJSONSource).setData(JSON.parse(json)); + getGPSProfile({ latitude: start.latitude, longitude: start.longitude }, { latitude: end.latitude, longitude: end.longitude }).then(data => { + if (Array.isArray(data)) { + setDataMin(min(data, d => d.height)); + setDataMax(max(data, d => d.height)); + } + setData(data); + }); + } else if (start || end) { + const point = start !== null ? start : end!; + addBaseSource(map, 'route'); + addBaseLayer(map, 'route'); + addPoint(map, point); + } else { + //delete layers and source + //used instead of clearing source data because it has better performance + //(setting data to empty results in a noticeable lag of line being cleared) + // eslint-disable-next-line @typescript-eslint/no-unused-expressions + mapRef.current.map?.getLayer('line') && mapRef.current.map?.removeLayer('line') && + mapRef.current.map?.removeLayer('points') && mapRef.current.map?.removeSource('route'); + } + }; + + const updateLosUrl = () => { + if (start && end) { + const locationPart = `lat1=${start.latitude}&lon1=${start.longitude}&lat2=${end.latitude}&lon2=${end.longitude}`; + let heightPart = ''; + if (siteAHeight && siteBHeight) { + heightPart = `&amslA=${siteAHeight.amsl}&antennaHeightA=${siteAHeight.antennaHeight}&amslB=${siteBHeight.amsl}&antennaHeightB=${siteBHeight.antennaHeight}`; + } + props.history.replace(`/${URL_BASEPATH}/los?${locationPart}${heightPart}`); + } else if (!start && !end) { + props.history.replace(`/${URL_BASEPATH}`); + } + }; + + const updateHeightA = (value: number, value2: number) => { + setHeightStart({ amsl: value, antennaHeight: value2 }); + }; + + const updateHeightB = (value: number, value2: number) => { + setHeightEnd({ amsl: value, antennaHeight: value2 }); + }; + + const OnEndPosition = (position: maplibregl.LngLat) => { + setEndPosition({ latitude: position.lat, longitude: position.lng }); + }; + + const OnStartPosition = (position: maplibregl.LngLat) => { + setStartPosition({ latitude: position.lat, longitude: position.lng }); + }; + + const mapMoveEnd = () => { + const mapZoom = Number(map.getZoom().toFixed(2)); + const lat = Number(map.getCenter().lat.toFixed(4)); + const lon = Number(map.getCenter().lng.toFixed(4)); + setMapPosition({ latitude: lat, longitude: lon }, mapZoom); + }; + + const setupMap = () => { + let initialLat = center.latitude; + let initialLon = center.longitude; + let initialZoom = zoom; + + map = new maplibre.Map({ + container: mapContainerRef.current!, + style: OSM_STYLE as any, + center: [initialLon, initialLat], + zoom: initialZoom, + }); + + mapRef.current.map = map; + + map.on('load', () => { + + map.setMaxZoom(18); + setMapLoaded(true); + + //add source, layer + addBaseSource(map, 'route'); + addBaseLayer(map, 'route'); + + }); + + let currentPopup: maplibregl.Popup | null = null; + map.on('contextmenu', (e) => { + if (currentPopup) + currentPopup.remove(); + //change height if start/end changes + const popupNode = document.createElement('div'); + render( + { OnStartPosition(p); if (currentPopup) currentPopup.remove(); }} + onEnd={(p) => { OnEndPosition(p); if (currentPopup) currentPopup.remove(); }} + onHeightA={(p, p1) => updateHeightA(p, p1)} + onHeightB={(p, p1) => updateHeightB(p, p1)} />, + popupNode); + + currentPopup = new maplibre.Popup() + .setLngLat(e.lngLat) + .setDOMContent(popupNode) + .addTo(map); + }); + map.on('moveend', mapMoveEnd); + }; + + useEffect(() => { + if (ready) { + setupMap(); + } + }, [ready]); + + useEffect(() => { + if (ready && isMapLoaded) { + drawChart(); + updateLosUrl(); + } + }, [start, end, isMapLoaded]); + + return <> +
+ + + + {typeof data === 'object' + ? ( + < div className={classes.chart} onClick={() => { + setData(Number.NaN); + setDataMax(undefined); + setDataMin(undefined); + clearChartAction(); + }}> + +
+ ) + : null + } + +
+ ; +}; + +export default withRouter(Map); \ No newline at end of file diff --git a/features/sdnr/odlux/odlux/apps/microwaveApp/src/lineOfSight/components/lineOfSightMapContextMenu.tsx b/features/sdnr/odlux/odlux/apps/microwaveApp/src/lineOfSight/components/lineOfSightMapContextMenu.tsx new file mode 100644 index 0000000..6eab2c3 --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/microwaveApp/src/lineOfSight/components/lineOfSightMapContextMenu.tsx @@ -0,0 +1,82 @@ +/** + * ============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 React, { FC, useEffect, useState } from 'react'; + +import { Button, InputAdornment, TextField, Tooltip } from '@mui/material'; +import makeStyles from '@mui/styles/makeStyles'; + +import { getGPSHeight } from '../service/lineOfSightHeightService'; + +type MapContextMenuProps = { + pos: maplibregl.LngLat; + onStart: (pos: maplibregl.LngLat) => void; + onEnd: (pos: maplibregl.LngLat) => void; + onHeightA: (height: number, antennaHeight: number) => void; + onHeightB: (height: number, antennaHeight: number) => void; +}; + +const styles = makeStyles({ + flexContainer: { display: 'flex', flexDirection: 'row' }, + textField: { width: 60 }, + button: { marginRight: 5, marginTop: 5, flexGrow: 2 }, +}); + +const MapContextMenu: FC = (props) => { + const { pos, onStart, onEnd } = props; + const [height, setHeight] = useState(undefined); + const [value1, setValue1] = useState(''); + const [value2, setValue2] = useState(''); + const classes = styles(); + + useEffect(() => { + getGPSHeight({ longitude: pos.lng, latitude: pos.lat }).then(setHeight); + }, [pos.lat, pos.lng]); + + const handleChangeHeight = (e: React.ChangeEvent, id: 'heightA' | 'heightB') => { + //sanitize non numbers + const onlyNums = e.target.value.replace(/[^0-9]/g, ''); + if (id === 'heightA') { + setValue1(onlyNums); + } else { + setValue2(onlyNums); + } + }; + + return ( +
+
Height: {height} m
+
+
+ + + handleChangeHeight(e, 'heightA')} InputProps={{ endAdornment: m }} /> + +
+
+ + + handleChangeHeight(e, 'heightB')} InputProps={{ endAdornment: m }} /> + +
+
+
+ ); +}; + +export default MapContextMenu; \ No newline at end of file diff --git a/features/sdnr/odlux/odlux/apps/microwaveApp/src/lineOfSight/components/lineOfSightMapInfo.tsx b/features/sdnr/odlux/odlux/apps/microwaveApp/src/lineOfSight/components/lineOfSightMapInfo.tsx new file mode 100644 index 0000000..b6bb69a --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/microwaveApp/src/lineOfSight/components/lineOfSightMapInfo.tsx @@ -0,0 +1,144 @@ +/** + * ============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 React, { FC, useEffect, useState } from 'react'; + +import ExpandMoreIcon from '@mui/icons-material/ExpandMore'; +import { Accordion, AccordionDetails, AccordionSummary, Typography } from '@mui/material'; +import makeStyles from '@mui/styles/makeStyles'; +import { useSelectApplicationState } from '../../../../../framework/src/flux/connect'; +import { GPSProfileResult } from '../model/lineOfSightGPSProfileResult'; +import { calculateDistanceInMeter } from '../utils/lineOfSightMap'; + +type MapInfoProps = { + minHeight: GPSProfileResult | undefined; + maxHeight: GPSProfileResult | undefined; +}; + +const styles = (props: any) => makeStyles({ + accordion: { padding: 5, position: 'absolute', top: 10, width: props.width, marginLeft: 10, zIndex: 1 }, + container: { flexDirection: 'column', marginLeft: 10, padding: 5 }, + caption: { width: '40%' }, + subTitleRow: { width: '60%' }, + titleRowElement: { width: '40%', fontWeight: 'bold' }, + secondRow: { width: '25%' }, + thirdRow: { width: '20%' }, +}); + +const MapInfo: FC = (props) => { + + const center = useSelectApplicationState(state => state.microwave.map.center); + const zoom = useSelectApplicationState(state => state.microwave.map.zoom); + const start = useSelectApplicationState(state => state.microwave.map.start); + const end = useSelectApplicationState(state => state.microwave.map.end); + const heightA = useSelectApplicationState(state => state.microwave.map.heightA); + const heightB = useSelectApplicationState(state => state.microwave.map.heightB); + const [expanded, setExpanded] = useState(false); + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const [width, setWidth] = useState(470); + const [length, setLength] = useState(); + const classes = styles({ width: width })(); + const { minHeight, maxHeight } = props; + + useEffect(() => { + if (start && end) { + setLength(calculateDistanceInMeter(start.latitude, start.longitude, end.latitude, end.longitude).toFixed(3)); + } else { + setLength(undefined); + } + }, [start, end]); + + const handleChange = (event: any, isExpanded: boolean) => { + setExpanded(isExpanded); + }; + + return + } + aria-controls="panel1a-content" + id="panel1a-header"> + Map Info + + + + Map Center +
+
+ Longitude{center.longitude}
+
+ Latitude{center.latitude}
+
+ Zoom {zoom}
+
+ + Link +
+
+ Start + End +
+ +
+ Longitude + {start?.longitude.toFixed(3)} + {end?.longitude.toFixed(3)} +
+ +
+ Latitude + {start?.latitude.toFixed(3)} + {end?.latitude.toFixed(3)} +
+ +
+ Meassured height [m] + {heightA?.amsl} + {heightB?.amsl} +
+ +
+ Antenna height [m] + {heightA?.antennaHeight} + {heightB?.antennaHeight} +
+ +
+ Length [m] + {length} +
+ +
+ Max height @ position + {maxHeight ? maxHeight.height + ' m' : ''} + {maxHeight?.gps.longitude.toFixed(3)} + {maxHeight?.gps.latitude.toFixed(3)} +
+ +
+ Min height @ position + {minHeight ? minHeight.height + ' m' : ''} + {minHeight?.gps.longitude.toFixed(3)} + {minHeight?.gps.latitude.toFixed(3)} +
+ +
+
+
; +}; + +export default MapInfo; \ No newline at end of file diff --git a/features/sdnr/odlux/odlux/apps/microwaveApp/src/lineOfSight/config.ts b/features/sdnr/odlux/odlux/apps/microwaveApp/src/lineOfSight/config.ts new file mode 100644 index 0000000..473fff9 --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/microwaveApp/src/lineOfSight/config.ts @@ -0,0 +1,46 @@ +/** + * ============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========================================================================== + */ + +export const URL_BASEPATH = 'microwave/lineOfSightMap'; + +export const TERRAIN_URL = '/terrain'; //http://10.20.11.249:5200 maybe? /terrain +export const TILE_URL = '/tiles'; //http://tile.openstreetmap.org /tiles + +export const OSM_STYLE = { + 'version': 8, + 'sources': { + 'raster-tiles': { + 'type': 'raster', + 'tiles': [ + TILE_URL + '/{z}/{x}/{y}.png', + ], + 'tileSize': 256, + 'attribution': + '© OpenStreetMap contributors', + }, + }, + 'layers': [ + { + 'id': 'simple-tiles', + 'type': 'raster', + 'source': 'raster-tiles', + 'minZoom': 0, + 'maxZoom': 18, + }, + ], +}; \ No newline at end of file diff --git a/features/sdnr/odlux/odlux/apps/microwaveApp/src/lineOfSight/handlers/lineOfSightMapHandler.ts b/features/sdnr/odlux/odlux/apps/microwaveApp/src/lineOfSight/handlers/lineOfSightMapHandler.ts new file mode 100644 index 0000000..8f789c1 --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/microwaveApp/src/lineOfSight/handlers/lineOfSightMapHandler.ts @@ -0,0 +1,76 @@ +/** + * ============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 { SetPassedInValuesAction, SetReachableAction } from '../actions/lineOfSightCommonActions'; +import { ClearSavedChartAction, SetChartAction, SetEndpointAction, SetHeightA, SetHeightB, SetMapCenterAction, SetStartPointAction } from '../actions/lineOfSightMapActions'; + +import { Height } from '../model/lineOfSightHeight'; +import { LatLon } from '../model/lineOfSightLatLon'; + + +export interface IMap { + center: LatLon; + zoom: number; + start: LatLon | null; + heightA: Height | null; + end: LatLon | null; + heightB: Height | null; + ready: boolean | null; +} + +const initialState: IMap = { + center: { latitude: 52.4003, longitude: 13.0584 }, + zoom: 12, + start: null, + end: null, + ready: null, + heightA: null, + heightB: null, + +}; + +export const mapHandler: IActionHandler = (state = initialState, action) => { + if (action instanceof SetPassedInValuesAction) { + state = { ...state, start: action.start, end: action.end, center: action.center, heightA: action.heightA, heightB: action.heightB }; + } else if (action instanceof SetReachableAction) { + state = { ...state, ready: action.reachable }; + + } else if (action instanceof SetChartAction) { + state = { ...state, start: action.startPoint, end: action.endPoint, heightA: action.heightA, heightB: action.heightB }; + } else if (action instanceof SetStartPointAction) { + state = { ...state, start: action.startPoint }; + + } else if (action instanceof SetEndpointAction) { + state = { ...state, end: action.endPoint }; + + } else if (action instanceof SetHeightA) { + state = { ...state, heightA: action.height }; + + } else if (action instanceof SetHeightB) { + state = { ...state, heightB: action.height }; + + } else if (action instanceof ClearSavedChartAction) { + state = { ...state, start: null, end: null, heightA: null, heightB: null }; + } else if (action instanceof SetMapCenterAction) { + state = { ...state, zoom: action.zoom, center: action.point }; + } + + return state; +}; \ No newline at end of file diff --git a/features/sdnr/odlux/odlux/apps/microwaveApp/src/lineOfSight/hooks/d3.ts b/features/sdnr/odlux/odlux/apps/microwaveApp/src/lineOfSight/hooks/d3.ts new file mode 100644 index 0000000..dfebe86 --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/microwaveApp/src/lineOfSight/hooks/d3.ts @@ -0,0 +1,37 @@ +/** + * ============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========================================================================== + */ + +/* eslint-disable react-hooks/exhaustive-deps */ +import { useEffect, useRef } from 'react'; +import type { DependencyList } from 'react'; + +import * as d3 from 'd3'; + + +type SelectionType = d3.Selection; + +export const useD3 = (renderChartFn: (selection: SelectionType) => void, dependencies: DependencyList) => { + const ref = useRef(null); + + useEffect(() => { + if (ref.current) renderChartFn(d3.select(ref.current)); + return () => { }; + }, dependencies); + + return ref; +} diff --git a/features/sdnr/odlux/odlux/apps/microwaveApp/src/lineOfSight/model/lineOfSightGPSProfileResult.ts b/features/sdnr/odlux/odlux/apps/microwaveApp/src/lineOfSight/model/lineOfSightGPSProfileResult.ts new file mode 100644 index 0000000..bf7be25 --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/microwaveApp/src/lineOfSight/model/lineOfSightGPSProfileResult.ts @@ -0,0 +1,19 @@ +/** + * ============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========================================================================== + */ + +export type GPSProfileResult = { height: number; gps: { latitude: number; longitude: number }; band: string; zone: number; easting: number; northing: number }; \ No newline at end of file diff --git a/features/sdnr/odlux/odlux/apps/microwaveApp/src/lineOfSight/model/lineOfSightHeight.tsx b/features/sdnr/odlux/odlux/apps/microwaveApp/src/lineOfSight/model/lineOfSightHeight.tsx new file mode 100644 index 0000000..9bafec2 --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/microwaveApp/src/lineOfSight/model/lineOfSightHeight.tsx @@ -0,0 +1,22 @@ +/** + * ============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========================================================================== + */ + +export type Height = { + amsl: number; + antennaHeight: number; +}; \ No newline at end of file diff --git a/features/sdnr/odlux/odlux/apps/microwaveApp/src/lineOfSight/model/lineOfSightLatLon.ts b/features/sdnr/odlux/odlux/apps/microwaveApp/src/lineOfSight/model/lineOfSightLatLon.ts new file mode 100644 index 0000000..28cdba4 --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/microwaveApp/src/lineOfSight/model/lineOfSightLatLon.ts @@ -0,0 +1,46 @@ +/** + * ============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========================================================================== + */ + +export type LatLon = { + latitude: number; + longitude: number; +}; + +type LinkDetailLocation = { + lon: number; + lat: number; + siteId: number; + name: string | null; + amslM: number | null; + azimuthDeg: number | null; + radioAntenna: { + id: number; + operationalParameters: { + agl: number; + }; + }; +}; + +export type Link = { + id: number; + name: string; + operator: string; + siteA: LinkDetailLocation; + siteB: LinkDetailLocation; + type: string; +}; \ No newline at end of file diff --git a/features/sdnr/odlux/odlux/apps/microwaveApp/src/lineOfSight/service/lineOfSightHeightService.ts b/features/sdnr/odlux/odlux/apps/microwaveApp/src/lineOfSight/service/lineOfSightHeightService.ts new file mode 100644 index 0000000..9deec1f --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/microwaveApp/src/lineOfSight/service/lineOfSightHeightService.ts @@ -0,0 +1,68 @@ +/** + * ============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 { TERRAIN_URL } from '../config'; +import { GPSProfileResult } from '../model/lineOfSightGPSProfileResult'; +import { LatLon } from '../model/lineOfSightLatLon'; + +import { requestRest } from '../../../../../framework/src/services/restService'; + +export const apiUrlBase = 'api/Query'; + +export const getGPSProfile = async (start: LatLon, end: LatLon) => { + const url = `${TERRAIN_URL}/${apiUrlBase}/GPSProfileRecords`; + + const result = await fetch(url, { + method: 'POST', + body: JSON.stringify({ start, end }), + headers: { + 'Content-Type': 'application/json', + // 'Content-Type': 'application/x-www-form-urlencoded', + }, + }); + if (result.ok) { + const data = await result.json() as GPSProfileResult[]; + return data; + } + return Number.NaN; +}; + +export const getGPSHeight = async (gpsCoord: LatLon) => { + const url = `${TERRAIN_URL}/${apiUrlBase}/GPSHeight`; + const result = await fetch(url, { + method: 'POST', + body: JSON.stringify(gpsCoord), + headers: { + 'Content-Type': 'application/json', + // 'Content-Type': 'application/x-www-form-urlencoded', + }, + }); + if (result.ok) { + const data = await result.json() as { height: number }; + return data.height; + } else { + return undefined; + } +}; + +const LINK_DETAILS_URL = '/topology/linkcalculator'; +export const getLinkDetails = async (linkId: any) => { + const result = requestRest(LINK_DETAILS_URL + '/link/' + linkId); + return result; +}; + diff --git a/features/sdnr/odlux/odlux/apps/microwaveApp/src/lineOfSight/styles/index.css b/features/sdnr/odlux/odlux/apps/microwaveApp/src/lineOfSight/styles/index.css new file mode 100644 index 0000000..ec2585e --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/microwaveApp/src/lineOfSight/styles/index.css @@ -0,0 +1,13 @@ +body { + margin: 0; + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', + 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', + sans-serif; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +code { + font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', + monospace; +} diff --git a/features/sdnr/odlux/odlux/apps/microwaveApp/src/lineOfSight/styles/mapbox-gl.css b/features/sdnr/odlux/odlux/apps/microwaveApp/src/lineOfSight/styles/mapbox-gl.css new file mode 100644 index 0000000..03c479a --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/microwaveApp/src/lineOfSight/styles/mapbox-gl.css @@ -0,0 +1 @@ +.mapboxgl-map{font:12px/20px Helvetica Neue,Arial,Helvetica,sans-serif;overflow:hidden;position:relative;-webkit-tap-highlight-color:rgba(0,0,0,0);text-align:left}.mapboxgl-map:-webkit-full-screen{width:100%;height:100%}.mapboxgl-canary{background-color:salmon}.mapboxgl-canvas-container.mapboxgl-interactive,.mapboxgl-ctrl-group button.mapboxgl-ctrl-compass{cursor:-webkit-grab;cursor:-moz-grab;cursor:grab;-moz-user-select:none;-webkit-user-select:none;-ms-user-select:none;user-select:none}.mapboxgl-canvas-container.mapboxgl-interactive.mapboxgl-track-pointer{cursor:pointer}.mapboxgl-canvas-container.mapboxgl-interactive:active,.mapboxgl-ctrl-group button.mapboxgl-ctrl-compass:active{cursor:-webkit-grabbing;cursor:-moz-grabbing;cursor:grabbing}.mapboxgl-canvas-container.mapboxgl-touch-zoom-rotate,.mapboxgl-canvas-container.mapboxgl-touch-zoom-rotate .mapboxgl-canvas{touch-action:pan-x pan-y}.mapboxgl-canvas-container.mapboxgl-touch-drag-pan,.mapboxgl-canvas-container.mapboxgl-touch-drag-pan .mapboxgl-canvas{touch-action:pinch-zoom}.mapboxgl-canvas-container.mapboxgl-touch-zoom-rotate.mapboxgl-touch-drag-pan,.mapboxgl-canvas-container.mapboxgl-touch-zoom-rotate.mapboxgl-touch-drag-pan .mapboxgl-canvas{touch-action:none}.mapboxgl-ctrl-bottom-left,.mapboxgl-ctrl-bottom-right,.mapboxgl-ctrl-top-left,.mapboxgl-ctrl-top-right{position:absolute;pointer-events:none;z-index:2}.mapboxgl-ctrl-top-left{top:0;left:0}.mapboxgl-ctrl-top-right{top:0;right:0}.mapboxgl-ctrl-bottom-left{bottom:0;left:0}.mapboxgl-ctrl-bottom-right{right:0;bottom:0}.mapboxgl-ctrl{clear:both;pointer-events:auto;transform:translate(0)}.mapboxgl-ctrl-top-left .mapboxgl-ctrl{margin:10px 0 0 10px;float:left}.mapboxgl-ctrl-top-right .mapboxgl-ctrl{margin:10px 10px 0 0;float:right}.mapboxgl-ctrl-bottom-left .mapboxgl-ctrl{margin:0 0 10px 10px;float:left}.mapboxgl-ctrl-bottom-right .mapboxgl-ctrl{margin:0 10px 10px 0;float:right}.mapboxgl-ctrl-group{border-radius:4px;background:#fff}.mapboxgl-ctrl-group:not(:empty){-moz-box-shadow:0 0 2px rgba(0,0,0,.1);-webkit-box-shadow:0 0 2px rgba(0,0,0,.1);box-shadow:0 0 0 2px rgba(0,0,0,.1)}@media (-ms-high-contrast:active){.mapboxgl-ctrl-group:not(:empty){box-shadow:0 0 0 2px ButtonText}}.mapboxgl-ctrl-group button{width:29px;height:29px;display:block;padding:0;outline:none;border:0;box-sizing:border-box;background-color:transparent;cursor:pointer}.mapboxgl-ctrl-group button+button{border-top:1px solid #ddd}.mapboxgl-ctrl button .mapboxgl-ctrl-icon{display:block;width:100%;height:100%;background-repeat:no-repeat;background-position:50%}@media (-ms-high-contrast:active){.mapboxgl-ctrl-icon{background-color:transparent}.mapboxgl-ctrl-group button+button{border-top:1px solid ButtonText}}.mapboxgl-ctrl button::-moz-focus-inner{border:0;padding:0}.mapboxgl-ctrl-group button:focus{box-shadow:0 0 2px 2px #0096ff}.mapboxgl-ctrl button:disabled{cursor:not-allowed}.mapboxgl-ctrl button:disabled .mapboxgl-ctrl-icon{opacity:.25}.mapboxgl-ctrl button:not(:disabled):hover{background-color:rgba(0,0,0,.05)}.mapboxgl-ctrl-group button:focus:focus-visible{box-shadow:0 0 2px 2px #0096ff}.mapboxgl-ctrl-group button:focus:not(:focus-visible){box-shadow:none}.mapboxgl-ctrl-group button:focus:first-child{border-radius:4px 4px 0 0}.mapboxgl-ctrl-group button:focus:last-child{border-radius:0 0 4px 4px}.mapboxgl-ctrl-group button:focus:only-child{border-radius:inherit}.mapboxgl-ctrl button.mapboxgl-ctrl-zoom-out .mapboxgl-ctrl-icon{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg width='29' height='29' viewBox='0 0 29 29' xmlns='http://www.w3.org/2000/svg' fill='%23333'%3E%3Cpath d='M10 13c-.75 0-1.5.75-1.5 1.5S9.25 16 10 16h9c.75 0 1.5-.75 1.5-1.5S19.75 13 19 13h-9z'/%3E%3C/svg%3E")}.mapboxgl-ctrl button.mapboxgl-ctrl-zoom-in .mapboxgl-ctrl-icon{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg width='29' height='29' viewBox='0 0 29 29' xmlns='http://www.w3.org/2000/svg' fill='%23333'%3E%3Cpath d='M14.5 8.5c-.75 0-1.5.75-1.5 1.5v3h-3c-.75 0-1.5.75-1.5 1.5S9.25 16 10 16h3v3c0 .75.75 1.5 1.5 1.5S16 19.75 16 19v-3h3c.75 0 1.5-.75 1.5-1.5S19.75 13 19 13h-3v-3c0-.75-.75-1.5-1.5-1.5z'/%3E%3C/svg%3E")}@media (-ms-high-contrast:active){.mapboxgl-ctrl button.mapboxgl-ctrl-zoom-out .mapboxgl-ctrl-icon{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg width='29' height='29' viewBox='0 0 29 29' xmlns='http://www.w3.org/2000/svg' fill='%23fff'%3E%3Cpath d='M10 13c-.75 0-1.5.75-1.5 1.5S9.25 16 10 16h9c.75 0 1.5-.75 1.5-1.5S19.75 13 19 13h-9z'/%3E%3C/svg%3E")}.mapboxgl-ctrl button.mapboxgl-ctrl-zoom-in .mapboxgl-ctrl-icon{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg width='29' height='29' viewBox='0 0 29 29' xmlns='http://www.w3.org/2000/svg' fill='%23fff'%3E%3Cpath d='M14.5 8.5c-.75 0-1.5.75-1.5 1.5v3h-3c-.75 0-1.5.75-1.5 1.5S9.25 16 10 16h3v3c0 .75.75 1.5 1.5 1.5S16 19.75 16 19v-3h3c.75 0 1.5-.75 1.5-1.5S19.75 13 19 13h-3v-3c0-.75-.75-1.5-1.5-1.5z'/%3E%3C/svg%3E")}}@media (-ms-high-contrast:black-on-white){.mapboxgl-ctrl button.mapboxgl-ctrl-zoom-out .mapboxgl-ctrl-icon{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg width='29' height='29' viewBox='0 0 29 29' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M10 13c-.75 0-1.5.75-1.5 1.5S9.25 16 10 16h9c.75 0 1.5-.75 1.5-1.5S19.75 13 19 13h-9z'/%3E%3C/svg%3E")}.mapboxgl-ctrl button.mapboxgl-ctrl-zoom-in .mapboxgl-ctrl-icon{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg width='29' height='29' viewBox='0 0 29 29' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M14.5 8.5c-.75 0-1.5.75-1.5 1.5v3h-3c-.75 0-1.5.75-1.5 1.5S9.25 16 10 16h3v3c0 .75.75 1.5 1.5 1.5S16 19.75 16 19v-3h3c.75 0 1.5-.75 1.5-1.5S19.75 13 19 13h-3v-3c0-.75-.75-1.5-1.5-1.5z'/%3E%3C/svg%3E")}}.mapboxgl-ctrl button.mapboxgl-ctrl-fullscreen .mapboxgl-ctrl-icon{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg width='29' height='29' viewBox='0 0 29 29' xmlns='http://www.w3.org/2000/svg' fill='%23333'%3E%3Cpath d='M24 16v5.5c0 1.75-.75 2.5-2.5 2.5H16v-1l3-1.5-4-5.5 1-1 5.5 4 1.5-3h1zM6 16l1.5 3 5.5-4 1 1-4 5.5 3 1.5v1H7.5C5.75 24 5 23.25 5 21.5V16h1zm7-11v1l-3 1.5 4 5.5-1 1-5.5-4L6 13H5V7.5C5 5.75 5.75 5 7.5 5H13zm11 2.5c0-1.75-.75-2.5-2.5-2.5H16v1l3 1.5-4 5.5 1 1 5.5-4 1.5 3h1V7.5z'/%3E%3C/svg%3E")}.mapboxgl-ctrl button.mapboxgl-ctrl-shrink .mapboxgl-ctrl-icon{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg width='29' height='29' viewBox='0 0 29 29' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M18.5 16c-1.75 0-2.5.75-2.5 2.5V24h1l1.5-3 5.5 4 1-1-4-5.5 3-1.5v-1h-5.5zM13 18.5c0-1.75-.75-2.5-2.5-2.5H5v1l3 1.5L4 24l1 1 5.5-4 1.5 3h1v-5.5zm3-8c0 1.75.75 2.5 2.5 2.5H24v-1l-3-1.5L25 5l-1-1-5.5 4L17 5h-1v5.5zM10.5 13c1.75 0 2.5-.75 2.5-2.5V5h-1l-1.5 3L5 4 4 5l4 5.5L5 12v1h5.5z'/%3E%3C/svg%3E")}@media (-ms-high-contrast:active){.mapboxgl-ctrl button.mapboxgl-ctrl-fullscreen .mapboxgl-ctrl-icon{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg width='29' height='29' viewBox='0 0 29 29' xmlns='http://www.w3.org/2000/svg' fill='%23fff'%3E%3Cpath d='M24 16v5.5c0 1.75-.75 2.5-2.5 2.5H16v-1l3-1.5-4-5.5 1-1 5.5 4 1.5-3h1zM6 16l1.5 3 5.5-4 1 1-4 5.5 3 1.5v1H7.5C5.75 24 5 23.25 5 21.5V16h1zm7-11v1l-3 1.5 4 5.5-1 1-5.5-4L6 13H5V7.5C5 5.75 5.75 5 7.5 5H13zm11 2.5c0-1.75-.75-2.5-2.5-2.5H16v1l3 1.5-4 5.5 1 1 5.5-4 1.5 3h1V7.5z'/%3E%3C/svg%3E")}.mapboxgl-ctrl button.mapboxgl-ctrl-shrink .mapboxgl-ctrl-icon{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg width='29' height='29' viewBox='0 0 29 29' xmlns='http://www.w3.org/2000/svg' fill='%23fff'%3E%3Cpath d='M18.5 16c-1.75 0-2.5.75-2.5 2.5V24h1l1.5-3 5.5 4 1-1-4-5.5 3-1.5v-1h-5.5zM13 18.5c0-1.75-.75-2.5-2.5-2.5H5v1l3 1.5L4 24l1 1 5.5-4 1.5 3h1v-5.5zm3-8c0 1.75.75 2.5 2.5 2.5H24v-1l-3-1.5L25 5l-1-1-5.5 4L17 5h-1v5.5zM10.5 13c1.75 0 2.5-.75 2.5-2.5V5h-1l-1.5 3L5 4 4 5l4 5.5L5 12v1h5.5z'/%3E%3C/svg%3E")}}@media (-ms-high-contrast:black-on-white){.mapboxgl-ctrl button.mapboxgl-ctrl-fullscreen .mapboxgl-ctrl-icon{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg width='29' height='29' viewBox='0 0 29 29' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M24 16v5.5c0 1.75-.75 2.5-2.5 2.5H16v-1l3-1.5-4-5.5 1-1 5.5 4 1.5-3h1zM6 16l1.5 3 5.5-4 1 1-4 5.5 3 1.5v1H7.5C5.75 24 5 23.25 5 21.5V16h1zm7-11v1l-3 1.5 4 5.5-1 1-5.5-4L6 13H5V7.5C5 5.75 5.75 5 7.5 5H13zm11 2.5c0-1.75-.75-2.5-2.5-2.5H16v1l3 1.5-4 5.5 1 1 5.5-4 1.5 3h1V7.5z'/%3E%3C/svg%3E")}.mapboxgl-ctrl button.mapboxgl-ctrl-shrink .mapboxgl-ctrl-icon{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg width='29' height='29' viewBox='0 0 29 29' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M18.5 16c-1.75 0-2.5.75-2.5 2.5V24h1l1.5-3 5.5 4 1-1-4-5.5 3-1.5v-1h-5.5zM13 18.5c0-1.75-.75-2.5-2.5-2.5H5v1l3 1.5L4 24l1 1 5.5-4 1.5 3h1v-5.5zm3-8c0 1.75.75 2.5 2.5 2.5H24v-1l-3-1.5L25 5l-1-1-5.5 4L17 5h-1v5.5zM10.5 13c1.75 0 2.5-.75 2.5-2.5V5h-1l-1.5 3L5 4 4 5l4 5.5L5 12v1h5.5z'/%3E%3C/svg%3E")}}.mapboxgl-ctrl button.mapboxgl-ctrl-compass .mapboxgl-ctrl-icon{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg width='29' height='29' viewBox='0 0 29 29' xmlns='http://www.w3.org/2000/svg' fill='%23333'%3E%3Cpath d='M10.5 14l4-8 4 8h-8z'/%3E%3Cpath d='M10.5 16l4 8 4-8h-8z' fill='%23ccc'/%3E%3C/svg%3E")}@media (-ms-high-contrast:active){.mapboxgl-ctrl button.mapboxgl-ctrl-compass .mapboxgl-ctrl-icon{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg width='29' height='29' viewBox='0 0 29 29' xmlns='http://www.w3.org/2000/svg' fill='%23fff'%3E%3Cpath d='M10.5 14l4-8 4 8h-8z'/%3E%3Cpath d='M10.5 16l4 8 4-8h-8z' fill='%23999'/%3E%3C/svg%3E")}}@media (-ms-high-contrast:black-on-white){.mapboxgl-ctrl button.mapboxgl-ctrl-compass .mapboxgl-ctrl-icon{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg width='29' height='29' viewBox='0 0 29 29' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M10.5 14l4-8 4 8h-8z'/%3E%3Cpath d='M10.5 16l4 8 4-8h-8z' fill='%23ccc'/%3E%3C/svg%3E")}}.mapboxgl-ctrl button.mapboxgl-ctrl-geolocate .mapboxgl-ctrl-icon{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg width='29' height='29' viewBox='0 0 20 20' xmlns='http://www.w3.org/2000/svg' fill='%23333'%3E%3Cpath d='M10 4C9 4 9 5 9 5v.1A5 5 0 005.1 9H5s-1 0-1 1 1 1 1 1h.1A5 5 0 009 14.9v.1s0 1 1 1 1-1 1-1v-.1a5 5 0 003.9-3.9h.1s1 0 1-1-1-1-1-1h-.1A5 5 0 0011 5.1V5s0-1-1-1zm0 2.5a3.5 3.5 0 110 7 3.5 3.5 0 110-7z'/%3E%3Ccircle cx='10' cy='10' r='2'/%3E%3C/svg%3E")}.mapboxgl-ctrl button.mapboxgl-ctrl-geolocate:disabled .mapboxgl-ctrl-icon{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg width='29' height='29' viewBox='0 0 20 20' xmlns='http://www.w3.org/2000/svg' fill='%23aaa'%3E%3Cpath d='M10 4C9 4 9 5 9 5v.1A5 5 0 005.1 9H5s-1 0-1 1 1 1 1 1h.1A5 5 0 009 14.9v.1s0 1 1 1 1-1 1-1v-.1a5 5 0 003.9-3.9h.1s1 0 1-1-1-1-1-1h-.1A5 5 0 0011 5.1V5s0-1-1-1zm0 2.5a3.5 3.5 0 110 7 3.5 3.5 0 110-7z'/%3E%3Ccircle cx='10' cy='10' r='2'/%3E%3Cpath d='M14 5l1 1-9 9-1-1 9-9z' fill='red'/%3E%3C/svg%3E")}.mapboxgl-ctrl button.mapboxgl-ctrl-geolocate.mapboxgl-ctrl-geolocate-active .mapboxgl-ctrl-icon{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg width='29' height='29' viewBox='0 0 20 20' xmlns='http://www.w3.org/2000/svg' fill='%2333b5e5'%3E%3Cpath d='M10 4C9 4 9 5 9 5v.1A5 5 0 005.1 9H5s-1 0-1 1 1 1 1 1h.1A5 5 0 009 14.9v.1s0 1 1 1 1-1 1-1v-.1a5 5 0 003.9-3.9h.1s1 0 1-1-1-1-1-1h-.1A5 5 0 0011 5.1V5s0-1-1-1zm0 2.5a3.5 3.5 0 110 7 3.5 3.5 0 110-7z'/%3E%3Ccircle cx='10' cy='10' r='2'/%3E%3C/svg%3E")}.mapboxgl-ctrl button.mapboxgl-ctrl-geolocate.mapboxgl-ctrl-geolocate-active-error .mapboxgl-ctrl-icon{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg width='29' height='29' viewBox='0 0 20 20' xmlns='http://www.w3.org/2000/svg' fill='%23e58978'%3E%3Cpath d='M10 4C9 4 9 5 9 5v.1A5 5 0 005.1 9H5s-1 0-1 1 1 1 1 1h.1A5 5 0 009 14.9v.1s0 1 1 1 1-1 1-1v-.1a5 5 0 003.9-3.9h.1s1 0 1-1-1-1-1-1h-.1A5 5 0 0011 5.1V5s0-1-1-1zm0 2.5a3.5 3.5 0 110 7 3.5 3.5 0 110-7z'/%3E%3Ccircle cx='10' cy='10' r='2'/%3E%3C/svg%3E")}.mapboxgl-ctrl button.mapboxgl-ctrl-geolocate.mapboxgl-ctrl-geolocate-background .mapboxgl-ctrl-icon{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg width='29' height='29' viewBox='0 0 20 20' xmlns='http://www.w3.org/2000/svg' fill='%2333b5e5'%3E%3Cpath d='M10 4C9 4 9 5 9 5v.1A5 5 0 005.1 9H5s-1 0-1 1 1 1 1 1h.1A5 5 0 009 14.9v.1s0 1 1 1 1-1 1-1v-.1a5 5 0 003.9-3.9h.1s1 0 1-1-1-1-1-1h-.1A5 5 0 0011 5.1V5s0-1-1-1zm0 2.5a3.5 3.5 0 110 7 3.5 3.5 0 110-7z'/%3E%3C/svg%3E")}.mapboxgl-ctrl button.mapboxgl-ctrl-geolocate.mapboxgl-ctrl-geolocate-background-error .mapboxgl-ctrl-icon{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg width='29' height='29' viewBox='0 0 20 20' xmlns='http://www.w3.org/2000/svg' fill='%23e54e33'%3E%3Cpath d='M10 4C9 4 9 5 9 5v.1A5 5 0 005.1 9H5s-1 0-1 1 1 1 1 1h.1A5 5 0 009 14.9v.1s0 1 1 1 1-1 1-1v-.1a5 5 0 003.9-3.9h.1s1 0 1-1-1-1-1-1h-.1A5 5 0 0011 5.1V5s0-1-1-1zm0 2.5a3.5 3.5 0 110 7 3.5 3.5 0 110-7z'/%3E%3C/svg%3E")}.mapboxgl-ctrl button.mapboxgl-ctrl-geolocate.mapboxgl-ctrl-geolocate-waiting .mapboxgl-ctrl-icon{-webkit-animation:mapboxgl-spin 2s linear infinite;-moz-animation:mapboxgl-spin 2s infinite linear;-o-animation:mapboxgl-spin 2s infinite linear;-ms-animation:mapboxgl-spin 2s infinite linear;animation:mapboxgl-spin 2s linear infinite}@media (-ms-high-contrast:active){.mapboxgl-ctrl button.mapboxgl-ctrl-geolocate .mapboxgl-ctrl-icon{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg width='29' height='29' viewBox='0 0 20 20' xmlns='http://www.w3.org/2000/svg' fill='%23fff'%3E%3Cpath d='M10 4C9 4 9 5 9 5v.1A5 5 0 005.1 9H5s-1 0-1 1 1 1 1 1h.1A5 5 0 009 14.9v.1s0 1 1 1 1-1 1-1v-.1a5 5 0 003.9-3.9h.1s1 0 1-1-1-1-1-1h-.1A5 5 0 0011 5.1V5s0-1-1-1zm0 2.5a3.5 3.5 0 110 7 3.5 3.5 0 110-7z'/%3E%3Ccircle cx='10' cy='10' r='2'/%3E%3C/svg%3E")}.mapboxgl-ctrl button.mapboxgl-ctrl-geolocate:disabled .mapboxgl-ctrl-icon{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg width='29' height='29' viewBox='0 0 20 20' xmlns='http://www.w3.org/2000/svg' fill='%23999'%3E%3Cpath d='M10 4C9 4 9 5 9 5v.1A5 5 0 005.1 9H5s-1 0-1 1 1 1 1 1h.1A5 5 0 009 14.9v.1s0 1 1 1 1-1 1-1v-.1a5 5 0 003.9-3.9h.1s1 0 1-1-1-1-1-1h-.1A5 5 0 0011 5.1V5s0-1-1-1zm0 2.5a3.5 3.5 0 110 7 3.5 3.5 0 110-7z'/%3E%3Ccircle cx='10' cy='10' r='2'/%3E%3Cpath d='M14 5l1 1-9 9-1-1 9-9z' fill='red'/%3E%3C/svg%3E")}.mapboxgl-ctrl button.mapboxgl-ctrl-geolocate.mapboxgl-ctrl-geolocate-active .mapboxgl-ctrl-icon{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg width='29' height='29' viewBox='0 0 20 20' xmlns='http://www.w3.org/2000/svg' fill='%2333b5e5'%3E%3Cpath d='M10 4C9 4 9 5 9 5v.1A5 5 0 005.1 9H5s-1 0-1 1 1 1 1 1h.1A5 5 0 009 14.9v.1s0 1 1 1 1-1 1-1v-.1a5 5 0 003.9-3.9h.1s1 0 1-1-1-1-1-1h-.1A5 5 0 0011 5.1V5s0-1-1-1zm0 2.5a3.5 3.5 0 110 7 3.5 3.5 0 110-7z'/%3E%3Ccircle cx='10' cy='10' r='2'/%3E%3C/svg%3E")}.mapboxgl-ctrl button.mapboxgl-ctrl-geolocate.mapboxgl-ctrl-geolocate-active-error .mapboxgl-ctrl-icon{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg width='29' height='29' viewBox='0 0 20 20' xmlns='http://www.w3.org/2000/svg' fill='%23e58978'%3E%3Cpath d='M10 4C9 4 9 5 9 5v.1A5 5 0 005.1 9H5s-1 0-1 1 1 1 1 1h.1A5 5 0 009 14.9v.1s0 1 1 1 1-1 1-1v-.1a5 5 0 003.9-3.9h.1s1 0 1-1-1-1-1-1h-.1A5 5 0 0011 5.1V5s0-1-1-1zm0 2.5a3.5 3.5 0 110 7 3.5 3.5 0 110-7z'/%3E%3Ccircle cx='10' cy='10' r='2'/%3E%3C/svg%3E")}.mapboxgl-ctrl button.mapboxgl-ctrl-geolocate.mapboxgl-ctrl-geolocate-background .mapboxgl-ctrl-icon{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg width='29' height='29' viewBox='0 0 20 20' xmlns='http://www.w3.org/2000/svg' fill='%2333b5e5'%3E%3Cpath d='M10 4C9 4 9 5 9 5v.1A5 5 0 005.1 9H5s-1 0-1 1 1 1 1 1h.1A5 5 0 009 14.9v.1s0 1 1 1 1-1 1-1v-.1a5 5 0 003.9-3.9h.1s1 0 1-1-1-1-1-1h-.1A5 5 0 0011 5.1V5s0-1-1-1zm0 2.5a3.5 3.5 0 110 7 3.5 3.5 0 110-7z'/%3E%3C/svg%3E")}.mapboxgl-ctrl button.mapboxgl-ctrl-geolocate.mapboxgl-ctrl-geolocate-background-error .mapboxgl-ctrl-icon{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg width='29' height='29' viewBox='0 0 20 20' xmlns='http://www.w3.org/2000/svg' fill='%23e54e33'%3E%3Cpath d='M10 4C9 4 9 5 9 5v.1A5 5 0 005.1 9H5s-1 0-1 1 1 1 1 1h.1A5 5 0 009 14.9v.1s0 1 1 1 1-1 1-1v-.1a5 5 0 003.9-3.9h.1s1 0 1-1-1-1-1-1h-.1A5 5 0 0011 5.1V5s0-1-1-1zm0 2.5a3.5 3.5 0 110 7 3.5 3.5 0 110-7z'/%3E%3C/svg%3E")}}@media (-ms-high-contrast:black-on-white){.mapboxgl-ctrl button.mapboxgl-ctrl-geolocate .mapboxgl-ctrl-icon{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg width='29' height='29' viewBox='0 0 20 20' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M10 4C9 4 9 5 9 5v.1A5 5 0 005.1 9H5s-1 0-1 1 1 1 1 1h.1A5 5 0 009 14.9v.1s0 1 1 1 1-1 1-1v-.1a5 5 0 003.9-3.9h.1s1 0 1-1-1-1-1-1h-.1A5 5 0 0011 5.1V5s0-1-1-1zm0 2.5a3.5 3.5 0 110 7 3.5 3.5 0 110-7z'/%3E%3Ccircle cx='10' cy='10' r='2'/%3E%3C/svg%3E")}.mapboxgl-ctrl button.mapboxgl-ctrl-geolocate:disabled .mapboxgl-ctrl-icon{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg width='29' height='29' viewBox='0 0 20 20' xmlns='http://www.w3.org/2000/svg' fill='%23666'%3E%3Cpath d='M10 4C9 4 9 5 9 5v.1A5 5 0 005.1 9H5s-1 0-1 1 1 1 1 1h.1A5 5 0 009 14.9v.1s0 1 1 1 1-1 1-1v-.1a5 5 0 003.9-3.9h.1s1 0 1-1-1-1-1-1h-.1A5 5 0 0011 5.1V5s0-1-1-1zm0 2.5a3.5 3.5 0 110 7 3.5 3.5 0 110-7z'/%3E%3Ccircle cx='10' cy='10' r='2'/%3E%3Cpath d='M14 5l1 1-9 9-1-1 9-9z' fill='red'/%3E%3C/svg%3E")}}@-webkit-keyframes mapboxgl-spin{0%{-webkit-transform:rotate(0deg)}to{-webkit-transform:rotate(1turn)}}@-moz-keyframes mapboxgl-spin{0%{-moz-transform:rotate(0deg)}to{-moz-transform:rotate(1turn)}}@-o-keyframes mapboxgl-spin{0%{-o-transform:rotate(0deg)}to{-o-transform:rotate(1turn)}}@-ms-keyframes mapboxgl-spin{0%{-ms-transform:rotate(0deg)}to{-ms-transform:rotate(1turn)}}@keyframes mapboxgl-spin{0%{transform:rotate(0deg)}to{transform:rotate(1turn)}}a.mapboxgl-ctrl-logo{width:88px;height:23px;margin:0 0 -4px -4px;display:block;background-repeat:no-repeat;cursor:pointer;overflow:hidden;background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg width='88' height='23' viewBox='0 0 88 23' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' fill-rule='evenodd'%3E%3Cdefs%3E%3Cpath id='a' d='M11.5 2.25c5.105 0 9.25 4.145 9.25 9.25s-4.145 9.25-9.25 9.25-9.25-4.145-9.25-9.25 4.145-9.25 9.25-9.25zM6.997 15.983c-.051-.338-.828-5.802 2.233-8.873a4.395 4.395 0 013.13-1.28c1.27 0 2.49.51 3.39 1.42.91.9 1.42 2.12 1.42 3.39 0 1.18-.449 2.301-1.28 3.13C12.72 16.93 7 16 7 16l-.003-.017zM15.3 10.5l-2 .8-.8 2-.8-2-2-.8 2-.8.8-2 .8 2 2 .8z'/%3E%3Cpath id='b' d='M50.63 8c.13 0 .23.1.23.23V9c.7-.76 1.7-1.18 2.73-1.18 2.17 0 3.95 1.85 3.95 4.17s-1.77 4.19-3.94 4.19c-1.04 0-2.03-.43-2.74-1.18v3.77c0 .13-.1.23-.23.23h-1.4c-.13 0-.23-.1-.23-.23V8.23c0-.12.1-.23.23-.23h1.4zm-3.86.01c.01 0 .01 0 .01-.01.13 0 .22.1.22.22v7.55c0 .12-.1.23-.23.23h-1.4c-.13 0-.23-.1-.23-.23V15c-.7.76-1.69 1.19-2.73 1.19-2.17 0-3.94-1.87-3.94-4.19 0-2.32 1.77-4.19 3.94-4.19 1.03 0 2.02.43 2.73 1.18v-.75c0-.12.1-.23.23-.23h1.4zm26.375-.19a4.24 4.24 0 00-4.16 3.29c-.13.59-.13 1.19 0 1.77a4.233 4.233 0 004.17 3.3c2.35 0 4.26-1.87 4.26-4.19 0-2.32-1.9-4.17-4.27-4.17zM60.63 5c.13 0 .23.1.23.23v3.76c.7-.76 1.7-1.18 2.73-1.18 1.88 0 3.45 1.4 3.84 3.28.13.59.13 1.2 0 1.8-.39 1.88-1.96 3.29-3.84 3.29-1.03 0-2.02-.43-2.73-1.18v.77c0 .12-.1.23-.23.23h-1.4c-.13 0-.23-.1-.23-.23V5.23c0-.12.1-.23.23-.23h1.4zm-34 11h-1.4c-.13 0-.23-.11-.23-.23V8.22c.01-.13.1-.22.23-.22h1.4c.13 0 .22.11.23.22v.68c.5-.68 1.3-1.09 2.16-1.1h.03c1.09 0 2.09.6 2.6 1.55.45-.95 1.4-1.55 2.44-1.56 1.62 0 2.93 1.25 2.9 2.78l.03 5.2c0 .13-.1.23-.23.23h-1.41c-.13 0-.23-.11-.23-.23v-4.59c0-.98-.74-1.71-1.62-1.71-.8 0-1.46.7-1.59 1.62l.01 4.68c0 .13-.11.23-.23.23h-1.41c-.13 0-.23-.11-.23-.23v-4.59c0-.98-.74-1.71-1.62-1.71-.85 0-1.54.79-1.6 1.8v4.5c0 .13-.1.23-.23.23zm53.615 0h-1.61c-.04 0-.08-.01-.12-.03-.09-.06-.13-.19-.06-.28l2.43-3.71-2.39-3.65a.213.213 0 01-.03-.12c0-.12.09-.21.21-.21h1.61c.13 0 .24.06.3.17l1.41 2.37 1.4-2.37a.34.34 0 01.3-.17h1.6c.04 0 .08.01.12.03.09.06.13.19.06.28l-2.37 3.65 2.43 3.7c0 .05.01.09.01.13 0 .12-.09.21-.21.21h-1.61c-.13 0-.24-.06-.3-.17l-1.44-2.42-1.44 2.42a.34.34 0 01-.3.17zm-7.12-1.49c-1.33 0-2.42-1.12-2.42-2.51 0-1.39 1.08-2.52 2.42-2.52 1.33 0 2.42 1.12 2.42 2.51 0 1.39-1.08 2.51-2.42 2.52zm-19.865 0c-1.32 0-2.39-1.11-2.42-2.48v-.07c.02-1.38 1.09-2.49 2.4-2.49 1.32 0 2.41 1.12 2.41 2.51 0 1.39-1.07 2.52-2.39 2.53zm-8.11-2.48c-.01 1.37-1.09 2.47-2.41 2.47s-2.42-1.12-2.42-2.51c0-1.39 1.08-2.52 2.4-2.52 1.33 0 2.39 1.11 2.41 2.48l.02.08zm18.12 2.47c-1.32 0-2.39-1.11-2.41-2.48v-.06c.02-1.38 1.09-2.48 2.41-2.48s2.42 1.12 2.42 2.51c0 1.39-1.09 2.51-2.42 2.51z'/%3E%3C/defs%3E%3Cmask id='c'%3E%3Crect width='100%25' height='100%25' fill='%23fff'/%3E%3Cuse xlink:href='%23a'/%3E%3Cuse xlink:href='%23b'/%3E%3C/mask%3E%3Cg opacity='.3' stroke='%23000' stroke-width='3'%3E%3Ccircle mask='url(%23c)' cx='11.5' cy='11.5' r='9.25'/%3E%3Cuse xlink:href='%23b' mask='url(%23c)'/%3E%3C/g%3E%3Cg opacity='.9' fill='%23fff'%3E%3Cuse xlink:href='%23a'/%3E%3Cuse xlink:href='%23b'/%3E%3C/g%3E%3C/svg%3E")}a.mapboxgl-ctrl-logo.mapboxgl-compact{width:23px}@media (-ms-high-contrast:active){a.mapboxgl-ctrl-logo{background-color:transparent;background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg width='88' height='23' viewBox='0 0 88 23' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' fill-rule='evenodd'%3E%3Cdefs%3E%3Cpath id='a' d='M11.5 2.25c5.105 0 9.25 4.145 9.25 9.25s-4.145 9.25-9.25 9.25-9.25-4.145-9.25-9.25 4.145-9.25 9.25-9.25zM6.997 15.983c-.051-.338-.828-5.802 2.233-8.873a4.395 4.395 0 013.13-1.28c1.27 0 2.49.51 3.39 1.42.91.9 1.42 2.12 1.42 3.39 0 1.18-.449 2.301-1.28 3.13C12.72 16.93 7 16 7 16l-.003-.017zM15.3 10.5l-2 .8-.8 2-.8-2-2-.8 2-.8.8-2 .8 2 2 .8z'/%3E%3Cpath id='b' d='M50.63 8c.13 0 .23.1.23.23V9c.7-.76 1.7-1.18 2.73-1.18 2.17 0 3.95 1.85 3.95 4.17s-1.77 4.19-3.94 4.19c-1.04 0-2.03-.43-2.74-1.18v3.77c0 .13-.1.23-.23.23h-1.4c-.13 0-.23-.1-.23-.23V8.23c0-.12.1-.23.23-.23h1.4zm-3.86.01c.01 0 .01 0 .01-.01.13 0 .22.1.22.22v7.55c0 .12-.1.23-.23.23h-1.4c-.13 0-.23-.1-.23-.23V15c-.7.76-1.69 1.19-2.73 1.19-2.17 0-3.94-1.87-3.94-4.19 0-2.32 1.77-4.19 3.94-4.19 1.03 0 2.02.43 2.73 1.18v-.75c0-.12.1-.23.23-.23h1.4zm26.375-.19a4.24 4.24 0 00-4.16 3.29c-.13.59-.13 1.19 0 1.77a4.233 4.233 0 004.17 3.3c2.35 0 4.26-1.87 4.26-4.19 0-2.32-1.9-4.17-4.27-4.17zM60.63 5c.13 0 .23.1.23.23v3.76c.7-.76 1.7-1.18 2.73-1.18 1.88 0 3.45 1.4 3.84 3.28.13.59.13 1.2 0 1.8-.39 1.88-1.96 3.29-3.84 3.29-1.03 0-2.02-.43-2.73-1.18v.77c0 .12-.1.23-.23.23h-1.4c-.13 0-.23-.1-.23-.23V5.23c0-.12.1-.23.23-.23h1.4zm-34 11h-1.4c-.13 0-.23-.11-.23-.23V8.22c.01-.13.1-.22.23-.22h1.4c.13 0 .22.11.23.22v.68c.5-.68 1.3-1.09 2.16-1.1h.03c1.09 0 2.09.6 2.6 1.55.45-.95 1.4-1.55 2.44-1.56 1.62 0 2.93 1.25 2.9 2.78l.03 5.2c0 .13-.1.23-.23.23h-1.41c-.13 0-.23-.11-.23-.23v-4.59c0-.98-.74-1.71-1.62-1.71-.8 0-1.46.7-1.59 1.62l.01 4.68c0 .13-.11.23-.23.23h-1.41c-.13 0-.23-.11-.23-.23v-4.59c0-.98-.74-1.71-1.62-1.71-.85 0-1.54.79-1.6 1.8v4.5c0 .13-.1.23-.23.23zm53.615 0h-1.61c-.04 0-.08-.01-.12-.03-.09-.06-.13-.19-.06-.28l2.43-3.71-2.39-3.65a.213.213 0 01-.03-.12c0-.12.09-.21.21-.21h1.61c.13 0 .24.06.3.17l1.41 2.37 1.4-2.37a.34.34 0 01.3-.17h1.6c.04 0 .08.01.12.03.09.06.13.19.06.28l-2.37 3.65 2.43 3.7c0 .05.01.09.01.13 0 .12-.09.21-.21.21h-1.61c-.13 0-.24-.06-.3-.17l-1.44-2.42-1.44 2.42a.34.34 0 01-.3.17zm-7.12-1.49c-1.33 0-2.42-1.12-2.42-2.51 0-1.39 1.08-2.52 2.42-2.52 1.33 0 2.42 1.12 2.42 2.51 0 1.39-1.08 2.51-2.42 2.52zm-19.865 0c-1.32 0-2.39-1.11-2.42-2.48v-.07c.02-1.38 1.09-2.49 2.4-2.49 1.32 0 2.41 1.12 2.41 2.51 0 1.39-1.07 2.52-2.39 2.53zm-8.11-2.48c-.01 1.37-1.09 2.47-2.41 2.47s-2.42-1.12-2.42-2.51c0-1.39 1.08-2.52 2.4-2.52 1.33 0 2.39 1.11 2.41 2.48l.02.08zm18.12 2.47c-1.32 0-2.39-1.11-2.41-2.48v-.06c.02-1.38 1.09-2.48 2.41-2.48s2.42 1.12 2.42 2.51c0 1.39-1.09 2.51-2.42 2.51z'/%3E%3C/defs%3E%3Cmask id='c'%3E%3Crect width='100%25' height='100%25' fill='%23fff'/%3E%3Cuse xlink:href='%23a'/%3E%3Cuse xlink:href='%23b'/%3E%3C/mask%3E%3Cg stroke='%23000' stroke-width='3'%3E%3Ccircle mask='url(%23c)' cx='11.5' cy='11.5' r='9.25'/%3E%3Cuse xlink:href='%23b' mask='url(%23c)'/%3E%3C/g%3E%3Cg fill='%23fff'%3E%3Cuse xlink:href='%23a'/%3E%3Cuse xlink:href='%23b'/%3E%3C/g%3E%3C/svg%3E")}}@media (-ms-high-contrast:black-on-white){a.mapboxgl-ctrl-logo{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg width='88' height='23' viewBox='0 0 88 23' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' fill-rule='evenodd'%3E%3Cdefs%3E%3Cpath id='a' d='M11.5 2.25c5.105 0 9.25 4.145 9.25 9.25s-4.145 9.25-9.25 9.25-9.25-4.145-9.25-9.25 4.145-9.25 9.25-9.25zM6.997 15.983c-.051-.338-.828-5.802 2.233-8.873a4.395 4.395 0 013.13-1.28c1.27 0 2.49.51 3.39 1.42.91.9 1.42 2.12 1.42 3.39 0 1.18-.449 2.301-1.28 3.13C12.72 16.93 7 16 7 16l-.003-.017zM15.3 10.5l-2 .8-.8 2-.8-2-2-.8 2-.8.8-2 .8 2 2 .8z'/%3E%3Cpath id='b' d='M50.63 8c.13 0 .23.1.23.23V9c.7-.76 1.7-1.18 2.73-1.18 2.17 0 3.95 1.85 3.95 4.17s-1.77 4.19-3.94 4.19c-1.04 0-2.03-.43-2.74-1.18v3.77c0 .13-.1.23-.23.23h-1.4c-.13 0-.23-.1-.23-.23V8.23c0-.12.1-.23.23-.23h1.4zm-3.86.01c.01 0 .01 0 .01-.01.13 0 .22.1.22.22v7.55c0 .12-.1.23-.23.23h-1.4c-.13 0-.23-.1-.23-.23V15c-.7.76-1.69 1.19-2.73 1.19-2.17 0-3.94-1.87-3.94-4.19 0-2.32 1.77-4.19 3.94-4.19 1.03 0 2.02.43 2.73 1.18v-.75c0-.12.1-.23.23-.23h1.4zm26.375-.19a4.24 4.24 0 00-4.16 3.29c-.13.59-.13 1.19 0 1.77a4.233 4.233 0 004.17 3.3c2.35 0 4.26-1.87 4.26-4.19 0-2.32-1.9-4.17-4.27-4.17zM60.63 5c.13 0 .23.1.23.23v3.76c.7-.76 1.7-1.18 2.73-1.18 1.88 0 3.45 1.4 3.84 3.28.13.59.13 1.2 0 1.8-.39 1.88-1.96 3.29-3.84 3.29-1.03 0-2.02-.43-2.73-1.18v.77c0 .12-.1.23-.23.23h-1.4c-.13 0-.23-.1-.23-.23V5.23c0-.12.1-.23.23-.23h1.4zm-34 11h-1.4c-.13 0-.23-.11-.23-.23V8.22c.01-.13.1-.22.23-.22h1.4c.13 0 .22.11.23.22v.68c.5-.68 1.3-1.09 2.16-1.1h.03c1.09 0 2.09.6 2.6 1.55.45-.95 1.4-1.55 2.44-1.56 1.62 0 2.93 1.25 2.9 2.78l.03 5.2c0 .13-.1.23-.23.23h-1.41c-.13 0-.23-.11-.23-.23v-4.59c0-.98-.74-1.71-1.62-1.71-.8 0-1.46.7-1.59 1.62l.01 4.68c0 .13-.11.23-.23.23h-1.41c-.13 0-.23-.11-.23-.23v-4.59c0-.98-.74-1.71-1.62-1.71-.85 0-1.54.79-1.6 1.8v4.5c0 .13-.1.23-.23.23zm53.615 0h-1.61c-.04 0-.08-.01-.12-.03-.09-.06-.13-.19-.06-.28l2.43-3.71-2.39-3.65a.213.213 0 01-.03-.12c0-.12.09-.21.21-.21h1.61c.13 0 .24.06.3.17l1.41 2.37 1.4-2.37a.34.34 0 01.3-.17h1.6c.04 0 .08.01.12.03.09.06.13.19.06.28l-2.37 3.65 2.43 3.7c0 .05.01.09.01.13 0 .12-.09.21-.21.21h-1.61c-.13 0-.24-.06-.3-.17l-1.44-2.42-1.44 2.42a.34.34 0 01-.3.17zm-7.12-1.49c-1.33 0-2.42-1.12-2.42-2.51 0-1.39 1.08-2.52 2.42-2.52 1.33 0 2.42 1.12 2.42 2.51 0 1.39-1.08 2.51-2.42 2.52zm-19.865 0c-1.32 0-2.39-1.11-2.42-2.48v-.07c.02-1.38 1.09-2.49 2.4-2.49 1.32 0 2.41 1.12 2.41 2.51 0 1.39-1.07 2.52-2.39 2.53zm-8.11-2.48c-.01 1.37-1.09 2.47-2.41 2.47s-2.42-1.12-2.42-2.51c0-1.39 1.08-2.52 2.4-2.52 1.33 0 2.39 1.11 2.41 2.48l.02.08zm18.12 2.47c-1.32 0-2.39-1.11-2.41-2.48v-.06c.02-1.38 1.09-2.48 2.41-2.48s2.42 1.12 2.42 2.51c0 1.39-1.09 2.51-2.42 2.51z'/%3E%3C/defs%3E%3Cmask id='c'%3E%3Crect width='100%25' height='100%25' fill='%23fff'/%3E%3Cuse xlink:href='%23a'/%3E%3Cuse xlink:href='%23b'/%3E%3C/mask%3E%3Cg stroke='%23fff' stroke-width='3' fill='%23fff'%3E%3Ccircle mask='url(%23c)' cx='11.5' cy='11.5' r='9.25'/%3E%3Cuse xlink:href='%23b' mask='url(%23c)'/%3E%3C/g%3E%3Cuse xlink:href='%23a'/%3E%3Cuse xlink:href='%23b'/%3E%3C/svg%3E")}}.mapboxgl-ctrl.mapboxgl-ctrl-attrib{padding:0 5px;background-color:hsla(0,0%,100%,.5);margin:0}@media screen{.mapboxgl-ctrl-attrib.mapboxgl-compact{min-height:20px;padding:0;margin:10px;position:relative;background-color:#fff;border-radius:3px 12px 12px 3px}.mapboxgl-ctrl-attrib.mapboxgl-compact:hover{padding:2px 24px 2px 4px;visibility:visible;margin-top:6px}.mapboxgl-ctrl-bottom-left>.mapboxgl-ctrl-attrib.mapboxgl-compact:hover,.mapboxgl-ctrl-top-left>.mapboxgl-ctrl-attrib.mapboxgl-compact:hover{padding:2px 4px 2px 24px;border-radius:12px 3px 3px 12px}.mapboxgl-ctrl-attrib.mapboxgl-compact .mapboxgl-ctrl-attrib-inner{display:none}.mapboxgl-ctrl-attrib.mapboxgl-compact:hover .mapboxgl-ctrl-attrib-inner{display:block}.mapboxgl-ctrl-attrib.mapboxgl-compact:after{content:"";cursor:pointer;position:absolute;background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg width='24' height='24' viewBox='0 0 20 20' xmlns='http://www.w3.org/2000/svg' fill-rule='evenodd'%3E%3Cpath d='M4 10a6 6 0 1012 0 6 6 0 10-12 0m5-3a1 1 0 102 0 1 1 0 10-2 0m0 3a1 1 0 112 0v3a1 1 0 11-2 0'/%3E%3C/svg%3E");background-color:hsla(0,0%,100%,.5);width:24px;height:24px;box-sizing:border-box;border-radius:12px}.mapboxgl-ctrl-bottom-right>.mapboxgl-ctrl-attrib.mapboxgl-compact:after{bottom:0;right:0}.mapboxgl-ctrl-top-right>.mapboxgl-ctrl-attrib.mapboxgl-compact:after{top:0;right:0}.mapboxgl-ctrl-top-left>.mapboxgl-ctrl-attrib.mapboxgl-compact:after{top:0;left:0}.mapboxgl-ctrl-bottom-left>.mapboxgl-ctrl-attrib.mapboxgl-compact:after{bottom:0;left:0}}@media screen and (-ms-high-contrast:active){.mapboxgl-ctrl-attrib.mapboxgl-compact:after{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg width='24' height='24' viewBox='0 0 20 20' xmlns='http://www.w3.org/2000/svg' fill-rule='evenodd' fill='%23fff'%3E%3Cpath d='M4 10a6 6 0 1012 0 6 6 0 10-12 0m5-3a1 1 0 102 0 1 1 0 10-2 0m0 3a1 1 0 112 0v3a1 1 0 11-2 0'/%3E%3C/svg%3E")}}@media screen and (-ms-high-contrast:black-on-white){.mapboxgl-ctrl-attrib.mapboxgl-compact:after{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg width='24' height='24' viewBox='0 0 20 20' xmlns='http://www.w3.org/2000/svg' fill-rule='evenodd'%3E%3Cpath d='M4 10a6 6 0 1012 0 6 6 0 10-12 0m5-3a1 1 0 102 0 1 1 0 10-2 0m0 3a1 1 0 112 0v3a1 1 0 11-2 0'/%3E%3C/svg%3E")}}.mapboxgl-ctrl-attrib a{color:rgba(0,0,0,.75);text-decoration:none}.mapboxgl-ctrl-attrib a:hover{color:inherit;text-decoration:underline}.mapboxgl-ctrl-attrib .mapbox-improve-map{font-weight:700;margin-left:2px}.mapboxgl-attrib-empty{display:none}.mapboxgl-ctrl-scale{background-color:hsla(0,0%,100%,.75);font-size:10px;border:2px solid #333;border-top:#333;padding:0 5px;color:#333;box-sizing:border-box}.mapboxgl-popup{position:absolute;top:0;left:0;display:-webkit-flex;display:flex;will-change:transform;pointer-events:none}.mapboxgl-popup-anchor-top,.mapboxgl-popup-anchor-top-left,.mapboxgl-popup-anchor-top-right{-webkit-flex-direction:column;flex-direction:column}.mapboxgl-popup-anchor-bottom,.mapboxgl-popup-anchor-bottom-left,.mapboxgl-popup-anchor-bottom-right{-webkit-flex-direction:column-reverse;flex-direction:column-reverse}.mapboxgl-popup-anchor-left{-webkit-flex-direction:row;flex-direction:row}.mapboxgl-popup-anchor-right{-webkit-flex-direction:row-reverse;flex-direction:row-reverse}.mapboxgl-popup-tip{width:0;height:0;border:10px solid transparent;z-index:1}.mapboxgl-popup-anchor-top .mapboxgl-popup-tip{-webkit-align-self:center;align-self:center;border-top:none;border-bottom-color:#fff}.mapboxgl-popup-anchor-top-left .mapboxgl-popup-tip{-webkit-align-self:flex-start;align-self:flex-start;border-top:none;border-left:none;border-bottom-color:#fff}.mapboxgl-popup-anchor-top-right .mapboxgl-popup-tip{-webkit-align-self:flex-end;align-self:flex-end;border-top:none;border-right:none;border-bottom-color:#fff}.mapboxgl-popup-anchor-bottom .mapboxgl-popup-tip{-webkit-align-self:center;align-self:center;border-bottom:none;border-top-color:#fff}.mapboxgl-popup-anchor-bottom-left .mapboxgl-popup-tip{-webkit-align-self:flex-start;align-self:flex-start;border-bottom:none;border-left:none;border-top-color:#fff}.mapboxgl-popup-anchor-bottom-right .mapboxgl-popup-tip{-webkit-align-self:flex-end;align-self:flex-end;border-bottom:none;border-right:none;border-top-color:#fff}.mapboxgl-popup-anchor-left .mapboxgl-popup-tip{-webkit-align-self:center;align-self:center;border-left:none;border-right-color:#fff}.mapboxgl-popup-anchor-right .mapboxgl-popup-tip{-webkit-align-self:center;align-self:center;border-right:none;border-left-color:#fff}.mapboxgl-popup-close-button{position:absolute;right:0;top:0;border:0;border-radius:0 3px 0 0;cursor:pointer;background-color:transparent}.mapboxgl-popup-close-button:hover{background-color:rgba(0,0,0,.05)}.mapboxgl-popup-content{position:relative;background:#fff;border-radius:3px;box-shadow:0 1px 2px rgba(0,0,0,.1);padding:10px 10px 15px;pointer-events:auto}.mapboxgl-popup-anchor-top-left .mapboxgl-popup-content{border-top-left-radius:0}.mapboxgl-popup-anchor-top-right .mapboxgl-popup-content{border-top-right-radius:0}.mapboxgl-popup-anchor-bottom-left .mapboxgl-popup-content{border-bottom-left-radius:0}.mapboxgl-popup-anchor-bottom-right .mapboxgl-popup-content{border-bottom-right-radius:0}.mapboxgl-popup-track-pointer{display:none}.mapboxgl-popup-track-pointer *{pointer-events:none;user-select:none}.mapboxgl-map:hover .mapboxgl-popup-track-pointer{display:flex}.mapboxgl-map:active .mapboxgl-popup-track-pointer{display:none}.mapboxgl-marker{position:absolute;top:0;left:0;will-change:transform}.mapboxgl-user-location-dot,.mapboxgl-user-location-dot:before{background-color:#1da1f2;width:15px;height:15px;border-radius:50%}.mapboxgl-user-location-dot:before{content:"";position:absolute;-webkit-animation:mapboxgl-user-location-dot-pulse 2s infinite;-moz-animation:mapboxgl-user-location-dot-pulse 2s infinite;-ms-animation:mapboxgl-user-location-dot-pulse 2s infinite;animation:mapboxgl-user-location-dot-pulse 2s infinite}.mapboxgl-user-location-dot:after{border-radius:50%;border:2px solid #fff;content:"";height:19px;left:-2px;position:absolute;top:-2px;width:19px;box-sizing:border-box;box-shadow:0 0 3px rgba(0,0,0,.35)}@-webkit-keyframes mapboxgl-user-location-dot-pulse{0%{-webkit-transform:scale(1);opacity:1}70%{-webkit-transform:scale(3);opacity:0}to{-webkit-transform:scale(1);opacity:0}}@-ms-keyframes mapboxgl-user-location-dot-pulse{0%{-ms-transform:scale(1);opacity:1}70%{-ms-transform:scale(3);opacity:0}to{-ms-transform:scale(1);opacity:0}}@keyframes mapboxgl-user-location-dot-pulse{0%{transform:scale(1);opacity:1}70%{transform:scale(3);opacity:0}to{transform:scale(1);opacity:0}}.mapboxgl-user-location-dot-stale{background-color:#aaa}.mapboxgl-user-location-dot-stale:after{display:none}.mapboxgl-user-location-accuracy-circle{background-color:rgba(29,161,242,.2);width:1px;height:1px;border-radius:100%}.mapboxgl-crosshair,.mapboxgl-crosshair .mapboxgl-interactive,.mapboxgl-crosshair .mapboxgl-interactive:active{cursor:crosshair}.mapboxgl-boxzoom{position:absolute;top:0;left:0;width:0;height:0;background:#fff;border:2px dotted #202020;opacity:.5}@media print{.mapbox-improve-map{display:none}} \ No newline at end of file diff --git a/features/sdnr/odlux/odlux/apps/microwaveApp/src/lineOfSight/utils/lineOfSightMap.ts b/features/sdnr/odlux/odlux/apps/microwaveApp/src/lineOfSight/utils/lineOfSightMap.ts new file mode 100644 index 0000000..472e0db --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/microwaveApp/src/lineOfSight/utils/lineOfSightMap.ts @@ -0,0 +1,120 @@ +/* eslint-disable no-param-reassign */ +/** + * ============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 { LatLon } from '../model/lineOfSightLatLon'; + +export const addBaseSource = (map: maplibregl.Map, name: string) => { + if (!map.getSource(name)) + map.addSource(name, { + type: 'geojson', + data: { type: 'FeatureCollection', features: [] }, + }); +}; + +export const addPoint = (map: maplibregl.Map, point: LatLon) => { + const json = `{ + "type": "Feature", + "properties": {}, + "geometry": { + "type": "Point", + "coordinates": + [${point.longitude}, ${point.latitude}] + } + }`; + (map.getSource('route') as maplibregl.GeoJSONSource).setData(JSON.parse(json)); +}; + +export const addBaseLayer = (map: maplibregl.Map, sourceName: string) => { + if (!map.getLayer('line')) + map.addLayer({ + 'id': 'line', + 'type': 'line', + 'source': sourceName, + 'layout': { + 'line-join': 'round', + 'line-cap': 'round', + }, + 'paint': { + 'line-color': '#88A', + 'line-width': 6, + 'line-opacity': 0.75, + }, + }); + + if (!map.getLayer('points')) + map.addLayer({ + id: 'points', + type: 'circle', + source: sourceName, + paint: { + 'circle-radius': 5, + 'circle-color': '#223b53', + 'circle-stroke-color': '#225ba3', + 'circle-stroke-width': 3, + 'circle-opacity': 0.5, + }, + }); +}; + +const degrees_to_radians = (degrees: number) => { + return degrees * (Math.PI / 180); +}; + +const radians_to_degrees = (radians: number) => { + var pi = Math.PI; + return radians * (180 / pi); +}; + +//taken from https://www.movable-type.co.uk/scripts/latlong.html +export const calculateMidPoint = (lat1: number, lon1: number, lat2: number, lon2: number): LatLon => { + const dLon = degrees_to_radians(lon2 - lon1); + //convert to radians + lat1 = degrees_to_radians(lat1); + lat2 = degrees_to_radians(lat2); + lon1 = degrees_to_radians(lon1); + + const Bx = Math.cos(lat2) * Math.cos(dLon); + const By = Math.cos(lat2) * Math.sin(dLon); + const lat3 = Math.atan2(Math.sin(lat1) + Math.sin(lat2), Math.sqrt((Math.cos(lat1) + Bx) * (Math.cos(lat1) + Bx) + By * By)); + const lon3 = lon1 + Math.atan2(By, Math.cos(lat1) + Bx); + const coordinate: LatLon = { latitude: radians_to_degrees(lat3), longitude: radians_to_degrees(lon3) }; + return coordinate; +}; + +function toRad(value: number) { + return (value * Math.PI) / 180; +} + +export const calculateDistanceInMeter = (lat1: number, lon1: number, lat2: number, lon2: number) => { + const lonRad1 = toRad(lon1); + const latRad1 = toRad(lat1); + const lonRad2 = toRad(lon2); + const latRad2 = toRad(lat2); + const dLon = lonRad2 - lonRad1; + const dLat = latRad2 - latRad1; + const a = Math.pow(Math.sin(dLat / 2), 2) + + Math.cos(latRad1) * Math.cos(latRad2) * + Math.pow(Math.sin(dLon / 2), 2); + const c = 2 * Math.asin(Math.sqrt(a)); + + return 6378 * c; +}; + + + diff --git a/features/sdnr/odlux/odlux/apps/microwaveApp/src/lineOfSight/utils/lineOfSightMath.ts b/features/sdnr/odlux/odlux/apps/microwaveApp/src/lineOfSight/utils/lineOfSightMath.ts new file mode 100644 index 0000000..e4ea962 --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/microwaveApp/src/lineOfSight/utils/lineOfSightMath.ts @@ -0,0 +1,29 @@ +/** + * ============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========================================================================== + */ + +export const max = (a: T[], p: (v: T) => Number) => a.reduce((m, x) => p(m) > p(x) ? m : x, a[0]); +export const min = (a: T[], p: (v: T) => Number) => a.reduce((m, x) => p(m) < p(x) ? m : x, a[0]); + +export const isNumber = (value: string | null) => { + if (!value) { + return false; + } else { + const num = Number(value); + return !isNaN(num); + } +}; \ No newline at end of file diff --git a/features/sdnr/odlux/odlux/apps/microwaveApp/src/lineOfSight/views/lineOfSightMain.tsx b/features/sdnr/odlux/odlux/apps/microwaveApp/src/lineOfSight/views/lineOfSightMain.tsx new file mode 100644 index 0000000..293ea38 --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/microwaveApp/src/lineOfSight/views/lineOfSightMain.tsx @@ -0,0 +1,30 @@ +/** + * ============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 React, { FC } from 'react'; +import Map from '../components/lineOfSightMap'; + +const LineOfSightMainView: FC = () => { + return ( +
+ +
+ ); +}; + +export default LineOfSightMainView; \ No newline at end of file diff --git a/features/sdnr/odlux/odlux/apps/microwaveApp/src/linkCalculator/actions/adaptiveModulationAction.ts b/features/sdnr/odlux/odlux/apps/microwaveApp/src/linkCalculator/actions/adaptiveModulationAction.ts new file mode 100644 index 0000000..99ac55c --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/microwaveApp/src/linkCalculator/actions/adaptiveModulationAction.ts @@ -0,0 +1,128 @@ +/** + * ============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 { AdaptiveModulationInput } from '../model/adaptiveModulationInput'; +import { Dispatch } from '../../../../../framework/src/flux/store'; +import { IApplicationStoreState } from '../../../../../framework/src/store/applicationStore'; +import dataService from '../service/dataService'; +import { Action } from '../../../../../framework/src/flux/action'; +import { AdaptiveModulationTable } from '../model/adaptiveModulationTable'; + + +export class UpdateAdaptiveModulationTableAction extends Action { + constructor(public adaptiveModulationTableAtoB: AdaptiveModulationTable[] | null, public adaptiveModulationTableBtoA: AdaptiveModulationTable[] | null, public message: string, public status: number) { + super(); + } +} +export class UpdateAdaptiveModulationProcessing extends Action { + constructor(public processing: boolean) { + super(); + } +} + +export const adaptiveModulationInputCreator = () => async (dispatch: Dispatch, getState: () => IApplicationStoreState) => { + + const { + microwave: { + link: { polarization, linkId }, + radio: { band, txPowerA, txPowerB, radioIdSiteA, radioIdSiteB, modulationA, modulationB }, + atmosphere: { attenuationMethod, worstMonth, rainVal, rainMethod }, + antenna: { antennaIdSiteA, antennaIdSiteB, antennaHeightA, antennaHeightB }, + waveguide: { waveguideIdSiteA, waveguideIdSiteB, waveguideLengthACalculate, waveguideLengthBCalculate }, + bandPlan: { region }, + }, + } = getState(); + + let adaptiveModulationInput: AdaptiveModulationInput = { + linkId: 0, + linkOperationalParameters: { + bandKeyId: '0', + bandplanKeyId:'', + polarization: '', + absorptionMethod: '', + calculationPeriod: '', + rainRate: 0, + rainModel: '', + }, + siteA: { + radioModelId: 0, + waveguideModelId: 0, + radioAntennaModelId: 0, + modulationType: '', + transmissionPower: 0, + waveguideLength: 0, + agl: 0, + }, + siteB: { + radioModelId: 0, + waveguideModelId: 0, + radioAntennaModelId: 0, + modulationType: '', + transmissionPower: 0, + waveguideLength: 0, + agl: 0, + }, + }; + + // setting link attributes + adaptiveModulationInput = { + ...adaptiveModulationInput, + linkId: linkId, + linkOperationalParameters: { + bandKeyId: band.keyId, + bandplanKeyId: region.keyId, + polarization: polarization!, + absorptionMethod: attenuationMethod, + calculationPeriod: worstMonth === true ? 'WORSTMONTH' : 'ANNUAL', + rainRate: rainVal, + rainModel: rainMethod, + }, + }; + // setting site A attributes + adaptiveModulationInput = { + ...adaptiveModulationInput, + siteA: { + radioModelId: radioIdSiteA, + waveguideModelId: waveguideIdSiteA, + radioAntennaModelId: antennaIdSiteA, + modulationType: modulationA, + transmissionPower: txPowerA, + waveguideLength: waveguideLengthACalculate, + agl: antennaHeightA, + }, + }; + // setting SiteB attributes + adaptiveModulationInput = { + ...adaptiveModulationInput, + siteB: { + radioModelId: radioIdSiteB, + waveguideModelId: waveguideIdSiteB, + radioAntennaModelId: antennaIdSiteB, + modulationType: modulationB, + transmissionPower: txPowerB, + waveguideLength: waveguideLengthBCalculate, + agl: antennaHeightB, + }, + }; + + const adaptivemodulationPromise = dataService.adaptiveModulationTable(adaptiveModulationInput); + + adaptivemodulationPromise.then((response ) => { + dispatch(new UpdateAdaptiveModulationTableAction(response.data?.aToB!, response.data?.bToA!, response.message!, response.status)); + }); +}; \ No newline at end of file diff --git a/features/sdnr/odlux/odlux/apps/microwaveApp/src/linkCalculator/actions/antennaActions.ts b/features/sdnr/odlux/odlux/apps/microwaveApp/src/linkCalculator/actions/antennaActions.ts new file mode 100644 index 0000000..aa531b7 --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/microwaveApp/src/linkCalculator/actions/antennaActions.ts @@ -0,0 +1,38 @@ +/** + * ============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 { Antenna } from '../model/antenna'; + +export class UpdateAntennaDBAction extends Action { + constructor(public antenna: Antenna[]) { + super(); + } +} +export class UpdateAntennaAction extends Action { + constructor(public antennaA: Antenna | null, public antennaB: Antenna | null) { + super(); + } +} +export class antennaMandatoryAction extends Action { + constructor(public antennaMandatoryParameters: boolean) { + super(); + } +} + + diff --git a/features/sdnr/odlux/odlux/apps/microwaveApp/src/linkCalculator/actions/atmosphericLossAction.ts b/features/sdnr/odlux/odlux/apps/microwaveApp/src/linkCalculator/actions/atmosphericLossAction.ts new file mode 100644 index 0000000..5950589 --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/microwaveApp/src/linkCalculator/actions/atmosphericLossAction.ts @@ -0,0 +1,45 @@ +/** + * ============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 UpdateRainValAction extends Action { + constructor(public rainVal: number) { + super(); + } +} +export class UpdateRainMethodAction extends Action { + constructor(public rainMethod: string) { + super(); + } +} +export class UpdateAttenuationMethodAction extends Action { + constructor(public attenuationMethod: string) { + super(); + } +} +export class UpdateWorstMonthAction extends Action { + constructor(public worstMonth: boolean) { + super(); + } +} +export class attenuationMandatoryParametersAction extends Action { + constructor(public attenuationMandatoryParameters: boolean) { + super(); + } +} diff --git a/features/sdnr/odlux/odlux/apps/microwaveApp/src/linkCalculator/actions/bandPlanAction.ts b/features/sdnr/odlux/odlux/apps/microwaveApp/src/linkCalculator/actions/bandPlanAction.ts new file mode 100644 index 0000000..944404d --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/microwaveApp/src/linkCalculator/actions/bandPlanAction.ts @@ -0,0 +1,128 @@ +/** + * ============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 { Dispatch } from '../../../../../framework/src/flux/store'; +import { Channel, ChannelTable, FrequencyPlan, RegionRegulator } from '../model/bandPlan'; +import { RadioBand } from '../model/topologyTypes'; +import { SaveChannel } from '../model/updateLink'; +import dataService from '../service/dataService'; +import { UpdateFrequencyPlanAction } from './linkAction'; +import { UpdateBandList } from './queryActions'; + + +export class UpdateRegionRegulatorAction extends Action { + constructor(public region: RegionRegulator) { + super(); + } +} +export class UpdateChannelListQueryAction extends Action { + constructor(public channelList: Channel[]) { + super(); + } +} +export class UpdateChannelListLoadingAction extends Action { + constructor(public channelListLoading: boolean) { + super(); + } +} +export class UpdateChannelListAction extends Action { + constructor(public channels: ChannelTable[]) { + super(); + } +} +export class ResetChannelTableAction extends Action {} + +export class UpdateFrequencyPlans extends Action { + constructor(public siteAFrequencyPlan: FrequencyPlan[], public siteBFrequencyPlan: FrequencyPlan[]) { + super(); + } +} + +export const updateSavedChannels = (channels: SaveChannel[], allChannel: Channel[]) => async (dispatcher: Dispatch) => { + let channelTable: ChannelTable[] = []; + + + channels.forEach(x => { + allChannel.forEach(y => { + if (y.keyId === x.channelKeyId) { + channelTable.push({ + name: y.name, + bandwidthMHz: y.bandwidthMHz.bandwidthMHz, + centerFrequencyHigh: y.centerFrequencyHigh, + centerFrequencyLow: y.centerFrequencyLow, + availability: y.availability.name, + xPolCondition: y.xPolCondition.name, + keyId: y.keyId, + polarization: x.channelPolarizationEnum, + }); + } + }, + ); + }); + dispatcher(new UpdateChannelListAction(channelTable)); +}; + +export const UpdateChannelQuery = (bandplanKeyId: string, bandKeyId: string) => async (dispatcher: Dispatch) => { + + dataService.channelQuery(bandplanKeyId, bandKeyId).then(result => { + dispatcher(new UpdateChannelListQueryAction(result?.data!)); + dispatcher(new UpdateChannelListLoadingAction(false)); + }); +}; + + + +export const getAllBands = (bandplanKeyId: string) => async (dispatcher: Dispatch) => { + await dataService.bandListQuery(bandplanKeyId).then(result => { + if (result.data) { + let bandList: RadioBand[] = []; + result.data.forEach(x => bandList.push(x)); + dispatcher(new UpdateBandList(bandList)); + } + }); +}; + +export const getFrequencyplans = (siteIdA: number, siteIdB: number, bandKeyId: string) => async (dispatcher: Dispatch) => { + let siteAFrequencyPlan: FrequencyPlan[] = []; + let siteBFrequencyPlan: FrequencyPlan[] = []; + + await dataService.frequencyPlanQuery(siteIdA).then(result => { + siteAFrequencyPlan = result.data; + }); + await dataService.frequencyPlanQuery(siteIdB).then(result => { + siteBFrequencyPlan = result.data; + + }); + dispatcher(new UpdateFrequencyPlans(siteAFrequencyPlan, siteBFrequencyPlan)); + let frequencyPlanA: string = ''; + let frequencyPlanB: string = ''; + siteAFrequencyPlan.forEach(x => { + if (x.band.keyId === bandKeyId) { + frequencyPlanA = x.configuration; + } + }); + siteBFrequencyPlan.forEach(x => { + if (x.band.keyId === bandKeyId) { + frequencyPlanB = x.configuration; + } + }); + dispatcher(new UpdateFrequencyPlanAction(frequencyPlanA as 'HIGH' | 'LOW', frequencyPlanB as 'HIGH' | 'LOW')); +}; + diff --git a/features/sdnr/odlux/odlux/apps/microwaveApp/src/linkCalculator/actions/commonActions.ts b/features/sdnr/odlux/odlux/apps/microwaveApp/src/linkCalculator/actions/commonActions.ts new file mode 100644 index 0000000..bcb8dbd --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/microwaveApp/src/linkCalculator/actions/commonActions.ts @@ -0,0 +1,34 @@ +/** + * ============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 { TabId } from '../model/tabId'; +import { Action } from '../../../../../framework/src/flux/action'; +import { CalculationResult } from '../model/calculationResult'; + +export class UpdateCalculationResultAction extends Action { + constructor(public result: CalculationResult) { + super(); + } +} +export class UpdateTabAction extends Action { + constructor(public openTab: TabId) { + super(); + } +} + + diff --git a/features/sdnr/odlux/odlux/apps/microwaveApp/src/linkCalculator/actions/errorAction.ts b/features/sdnr/odlux/odlux/apps/microwaveApp/src/linkCalculator/actions/errorAction.ts new file mode 100644 index 0000000..4e29cbf --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/microwaveApp/src/linkCalculator/actions/errorAction.ts @@ -0,0 +1,74 @@ + +/** + * ============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 { Dispatch } from '../../../../../framework/src/flux/store'; +import { IApplicationStoreState } from '../../../../../framework/src/store/applicationStore'; + +export class UpdateLatitudeErrorAction extends Action { + constructor(public error1: string | null, public error2: string | null) { + super(); + } +} +export class UpdateLongitudeErrorAction extends Action { + constructor(public error1: string | null, public error2: string | null) { + super(); + } +} +export class UpdateFrequencyErrorAction extends Action { + constructor(public error: string) { + super(); + } +} +export class UpdateRainMethodErrorAction extends Action { + constructor(public error: string) { + super(); + } +} +export class UpdateAttenuationMethodErrorAction extends Action { + constructor(public error: string) { + super(); + } +} +export class FirstMandatoryCheckAction extends Action { +} + + + +export const formValid = () => async (dispatcher: Dispatch, getState: () => IApplicationStoreState) => { + + const siteState = getState().microwave.site; + + const latitude1Error = siteState.lat1 === 0 ? 'Enter a number between -90 to 90' : null; + const latitude2Error = siteState.lat2 === 0 ? 'Enter a number between -90 to 90' : null; + const longitude1Error = siteState.lon1 === 0 ? 'Enter a number between -180 to 180' : null; + const longitude2Error = siteState.lon2 === 0 ? 'Enter a number between -180 to 180' : null; + const frequencyError = getState().microwave.radio.band.frequency === 0 ? 'Select a frequency' : ''; + const rainMethodError = getState().microwave.atmosphere.rainMethod === '0' ? 'Select the rain method' : ''; + const attenuationMethodError = getState().microwave.atmosphere.attenuationMethod === '0' ? 'Select the attenuation method' : ''; + + dispatcher(new UpdateLatitudeErrorAction(latitude1Error, latitude2Error)); + dispatcher(new UpdateLongitudeErrorAction(longitude1Error, longitude2Error)); + dispatcher(new UpdateFrequencyErrorAction(frequencyError)); + dispatcher(new UpdateRainMethodErrorAction(rainMethodError)); + dispatcher(new UpdateAttenuationMethodErrorAction(attenuationMethodError)); + + return latitude1Error === null && latitude2Error === null && longitude1Error === null && longitude2Error === null && frequencyError === ''; +}; + diff --git a/features/sdnr/odlux/odlux/apps/microwaveApp/src/linkCalculator/actions/handleButtonAction.ts b/features/sdnr/odlux/odlux/apps/microwaveApp/src/linkCalculator/actions/handleButtonAction.ts new file mode 100644 index 0000000..5369340 --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/microwaveApp/src/linkCalculator/actions/handleButtonAction.ts @@ -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 dataService from '../service/dataService'; +import { Dispatch } from '../../../../../framework/src/flux/store'; +import { IApplicationStoreState } from '../../../../../framework/src/store/applicationStore'; +import { UpdateDistanceAction } from './linkAction'; +import { CalculationResult } from '../model/calculationResult'; +import { UpdateCalculationResultAction } from './commonActions'; + +export const calculateButtonAction = () => async (dispatch: Dispatch, getState: () => IApplicationStoreState) => { + + const { + microwave: { + site: { lat1, lon1, lat2, lon2 }, + link: { polarization, distance }, + radio: { band, txPowerA, txPowerB, thresholdBER3A, thresholdBER3B, radioMandatoryParameters, frequencyMandatoryParameters }, + atmosphere: { worstMonth, rainVal, attenuationMethod, rainMethod, attenuationMandatoryParameters }, + antenna: { antennaGainA, antennaGainB, antennaMandatoryParameters }, + waveguide: { waveguideMandatoryParameters, waveguideIdSiteA, waveguideIdSiteB, waveguideLengthACalculate, waveguideLengthBCalculate }, + }, + } = getState(); + + let distanceInKm; + // const autoDistance = await dataServices.updateAutoDistance(lat1, lon1, lat2, lon2); + + if (distance !== 0) { + distanceInKm = distance; + } else { + distanceInKm = (await dataService.updateAutoDistance(lat1, lon1, lat2, lon2))!.distanceInKm; + dispatch(new UpdateDistanceAction(distanceInKm)); + } + + let rainLoss; + if (rainMethod === 'ITURP8377') { + rainLoss = await dataService.rainAttenuation(lat1, lon1, lat2, lon2, band.keyId, polarization!, worstMonth); + } else if (rainMethod === 'MANUAL') { + if (rainVal !== 0) { + rainLoss = await dataService.manualRain(rainVal, band.keyId, distanceInKm, polarization!); + } + } + let linkBudget; + let waveguideLoss; + const freeSpaceLoss = await dataService.FSL(distanceInKm, band.keyId); + const absorptionLoss = await dataService.AbsorptionAtt(lat1, lon1, lat2, lon2, band.keyId, worstMonth, attenuationMethod); + + waveguideLoss = await dataService.waveguideLoss(waveguideIdSiteA, waveguideIdSiteB, waveguideLengthACalculate, waveguideLengthBCalculate); + + if (antennaMandatoryParameters && radioMandatoryParameters && frequencyMandatoryParameters && attenuationMandatoryParameters && waveguideMandatoryParameters && waveguideLoss) { + linkBudget = await dataService.linkBudget(lat1, lon1, lat2, lon2, band.keyId, attenuationMethod, polarization!, antennaGainA, antennaGainB, waveguideLoss.waveguideLossA, waveguideLoss.waveguideLossB, txPowerA!, txPowerB!, thresholdBER3A!, thresholdBER3B!); + } + let result = new CalculationResult(); + if (rainLoss && rainLoss.rainAttenuation) result.rainLoss = rainLoss; + if (freeSpaceLoss) result.freeSpaceLoss = freeSpaceLoss; + if (absorptionLoss && absorptionLoss.oxygenLoss && absorptionLoss.waterLoss) result.absorptionLoss = absorptionLoss; + if (waveguideLoss) result.waveguideLoss = waveguideLoss; + if (linkBudget && linkBudget.receivedPowerA && linkBudget.receivedPowerB ) result.linkBudget = linkBudget; + if (result) dispatch(new UpdateCalculationResultAction(result)); +}; + diff --git a/features/sdnr/odlux/odlux/apps/microwaveApp/src/linkCalculator/actions/linkAction.ts b/features/sdnr/odlux/odlux/apps/microwaveApp/src/linkCalculator/actions/linkAction.ts new file mode 100644 index 0000000..ec24571 --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/microwaveApp/src/linkCalculator/actions/linkAction.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'; + + +export class UpdatePolAction extends Action { + constructor(public polarization: 'HORIZONTAL' | 'VERTICAL' | null) { + super(); + } +} +export class UpdateDistanceAction extends Action { + constructor(public distance: number | null) { + super(); + } +} +export class UpdateFrequencyPlanAction extends Action { + constructor(public frequencyPlanA: 'HIGH' | 'LOW', public frequencyPlanB :'HIGH' | 'LOW' ) { + super(); + } +} diff --git a/features/sdnr/odlux/odlux/apps/microwaveApp/src/linkCalculator/actions/queryActions.ts b/features/sdnr/odlux/odlux/apps/microwaveApp/src/linkCalculator/actions/queryActions.ts new file mode 100644 index 0000000..d798595 --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/microwaveApp/src/linkCalculator/actions/queryActions.ts @@ -0,0 +1,39 @@ +/** + * ============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 { RegionRegulator } from '../model/bandPlan'; +import { ModelType, RadioBand } from '../model/topologyTypes'; + +export class UpdateBandList extends Action { + constructor(public bandList: RadioBand[]) { + super(); + } +} +export class UpdateRegionRegulatorListAction extends Action { + constructor(public regionRegulatorList : RegionRegulator[]) { + super(); + } +} +export class UpdateModelTypesAction extends Action { + constructor(public ModelTypes : ModelType[]) { + super(); + } +} + diff --git a/features/sdnr/odlux/odlux/apps/microwaveApp/src/linkCalculator/actions/radioActions.ts b/features/sdnr/odlux/odlux/apps/microwaveApp/src/linkCalculator/actions/radioActions.ts new file mode 100644 index 0000000..4ceb37c --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/microwaveApp/src/linkCalculator/actions/radioActions.ts @@ -0,0 +1,177 @@ +/** + * ============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 dataService from '../service/dataService'; +import { Action } from '../../../../../framework/src/flux/action'; +import { Dispatch } from '../../../../../framework/src/flux/store'; +import { Radio, RadioEverything } from '../model/radio'; +import { Modulation } from '../model/modulation'; + +import { Antenna } from '../model/antenna'; +import { Waveguide } from '../model/waveguide'; +import { FirstMandatoryCheckAction } from './errorAction'; +import { Link } from '../model/link'; +import { radioService } from '../service/processingService'; + +export class UpdateFrequencyAction extends Action { + constructor(public frequency: number) { + super(); + + } +} +export class UpdateTxPowerAction extends Action { + constructor(public txPowerA: number | null, public txPowerB: number | null) { + super(); + } +} +export class UpdateRxSensitivityAction extends Action { + constructor(public thresholdBER3A: number | null, public thresholdBER6A: number | null, public thresholdBER3B: number | null, public thresholdBER6B: number | null) { + super(); + } +} +export class UpdateRxPowerAction extends Action { + constructor(public rxPowerA: number, public rxPowerB: number) { + super(); + } +} +export class UpdateSomAction extends Action { + constructor(public somA: number, public somB: number) { + super(); + } +} +export class UpdateRadioListAction extends Action { + constructor(public radioNameList: string[]) { + super(); + } +} + +export class UpdateRadioAction extends Action { + constructor(public radioNameA: string | null, public radioNameB: string | null) { + super(); + } +} +export class radioMandatoryParametersAction extends Action { + constructor(public radioMandatoryParameters: boolean) { + super(); + } +} +export class frequencyMandatoryParametersAction extends Action { + constructor(public frequencyMandatoryParameters: boolean) { + super(); + } +} + +export class UpdateRadioParametersAction extends Action { + constructor(public radioParameters: Radio[]) { + super(); + } +} +export class radioBandwidthAction extends Action { + constructor(public radioBandwidthA: number | null, public radioBandwidthB: number | null) { + super(); + } +} +export class UpdateRadioIdAction extends Action { + constructor(public radioIdSiteA: number | null, public radioIdSiteB: number | null) { + super(); + } +} + +export class UpdateModulationListAction extends Action { + constructor(public modulationListA: string[] | null, public modulationListB: string[] | null) { + super(); + } +} +export class UpdateModulationAction extends Action { + constructor(public modulationA: string | null, public modulationB: string | null) { + super(); + } +} + +export class UpdateModulationParametersAction extends Action { + constructor(public modulationParametersA: Modulation | null, public modulationParametersB: Modulation | null) { + super(); + } +} + +export class ResetAction extends Action { +} +export class UpdateDeviceListsOnBandChangeAction extends Action { + constructor( + public antennas: Antenna[], + public radioList: Radio[], + public waveguideList: Waveguide[]) { + super(); + } +} +export class UpdateDevicesOnFirstLoad extends UpdateDeviceListsOnBandChangeAction { + constructor( + public antennas: Antenna[], + public radioList: Radio[], + public waveguideList: Waveguide[], + public linkAttributes: Link ) { + super(antennas, radioList, waveguideList); + } +} + +export class UpdateRadioEverything extends Action { + constructor(public transport: RadioEverything) { + super(); + } +} +export class UpdateTotalBandwidthAction extends Action { + constructor(public totalBandwidthMHz: number) { + super(); + } +} + +export class UpdateEnabeldAdaptiveModulations extends Action { + constructor(public enabledAdaptiveModulations: string[]) { + super(); + } +} +export const updateFrequencyBand = (bandKeyId: string) => async (dispatcher: Dispatch) => { + dispatcher( new ResetAction()); + dispatcher(new UpdateFrequencyAction(Number(bandKeyId.replace('$', '')))); + dispatcher(new FirstMandatoryCheckAction()); + + + let radios: Radio[] = []; + let antennas: Antenna[] = []; + let waveguides: Waveguide[] = []; + await dataService.getModels(bandKeyId, 'radio')!.then((x: Radio[]) => { + radios = x; + }); + await dataService.getModels(bandKeyId, 'radio-antenna')!.then((x: Antenna[]) => { + antennas = x; + }); + await dataService.getModels(bandKeyId, 'radio-to-antenna-link')!.then((x: Waveguide[]) => { + waveguides = x; + }); + dispatcher(new UpdateDeviceListsOnBandChangeAction(antennas, radios, waveguides)); +}; +export const revertAntennaRadioWaveguideattributes = () => async (dispatcher: Dispatch) => { + dispatcher(new ResetAction()); +}; +export const updateRadioAttributes = (radioA: string | null, radioB: string | null, radioParameters: Radio[]) => async (dispatcher: Dispatch) => { + dispatcher(new UpdateRadioAction(radioA, radioB)); + const radios: RadioEverything = await radioService(radioA, radioB, radioParameters); + dispatcher(new UpdateRadioEverything(radios)); +}; + + diff --git a/features/sdnr/odlux/odlux/apps/microwaveApp/src/linkCalculator/actions/saveLinkAction.ts b/features/sdnr/odlux/odlux/apps/microwaveApp/src/linkCalculator/actions/saveLinkAction.ts new file mode 100644 index 0000000..eec15c0 --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/microwaveApp/src/linkCalculator/actions/saveLinkAction.ts @@ -0,0 +1,95 @@ +/** + * ============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 { Dispatch } from '../../../../../framework/src/flux/store'; +import { Action } from '../../../../../framework/src/flux/action'; + +import { IApplicationStoreState } from '../../../../../framework/src/store/applicationStore'; + +import dataService from '../service/dataService'; +import { SaveChannel, UpdateLink } from '../model/updateLink'; + + + +export class linkSavedSuccessfulAction extends Action { + constructor(public saved: any) { + super(); + } +} +export class linkBeingSavedAction extends Action { + constructor(public saving: boolean) { + super(); + } +} +export const saveLinkCallAsync = () => async (dispatch: Dispatch, getState: () => IApplicationStoreState) => { + dispatch(new linkBeingSavedAction(true)); + const { + microwave: { + link: { polarization }, + radio: { band, txPowerA, txPowerB, radioIdSiteA, radioIdSiteB, modulationA, modulationB, enabledAdaptiveModulations }, + atmosphere: { attenuationMethod, worstMonth, rainMethod, rainVal }, + antenna: { antennaIdSiteA, antennaIdSiteB, antennaHeightA, antennaHeightB }, + waveguide: { waveguideIdSiteA, waveguideIdSiteB, waveguideLengthACalculate, waveguideLengthBCalculate }, + query: { linkAttributes }, + bandPlan:{ region, savedChannels }, + }, + } = getState(); + + let saveChannels: SaveChannel[] = []; + savedChannels.map(x => { + saveChannels.push({ channelKeyId: x.keyId, channelPolarizationEnum: x.polarization }); + }); + + let link: UpdateLink = { + + siteA: { modulationType :modulationA, + transmissionPower:txPowerA, + waveguideLength:waveguideLengthACalculate, + agl:antennaHeightA, + radioModelId: radioIdSiteA, + waveguideModelId:waveguideIdSiteA, + radioAntennaModelId:antennaIdSiteA, + enabledAdmModulations: enabledAdaptiveModulations, + }, + siteB: { modulationType :modulationB, + transmissionPower:txPowerB, + waveguideLength:waveguideLengthBCalculate, + agl:antennaHeightB, + radioModelId: radioIdSiteB, + waveguideModelId:waveguideIdSiteB, + radioAntennaModelId:antennaIdSiteB, + enabledAdmModulations: enabledAdaptiveModulations, + }, + linkOperationalParameters: { + bandKeyId: band.keyId, + rainPolarity: polarization!, + rainModel: rainMethod, + rainRate: rainVal, + absorptionMethod: attenuationMethod, + calculationPeriod: worstMonth ? 'WORSTMONTH' : 'ANNUAL', + bandplanKeyId: region.keyId, + selectedChannelList: saveChannels, + }, + + + }; + + const callLinkPromise = (await dataService.saveLink(link as any, linkAttributes.id)); + dispatch(new linkSavedSuccessfulAction(callLinkPromise)); +}; \ No newline at end of file diff --git a/features/sdnr/odlux/odlux/apps/microwaveApp/src/linkCalculator/actions/siteAction.ts b/features/sdnr/odlux/odlux/apps/microwaveApp/src/linkCalculator/actions/siteAction.ts new file mode 100644 index 0000000..e9bac4e --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/microwaveApp/src/linkCalculator/actions/siteAction.ts @@ -0,0 +1,41 @@ +/** + * ============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'; + + + +//TODO: calculate distance if all lat/lons are set +export class UpdateLatLonAction extends Action { + constructor( + public lat1: number, + public lon1: number, + public lat2: number, + public lon2: number, + ) { + super(); + + } +} +export class locationMandatoryAction extends Action { + constructor(public locationMandatoryParameters: boolean) { + super(); + } +} + diff --git a/features/sdnr/odlux/odlux/apps/microwaveApp/src/linkCalculator/actions/viewAction.ts b/features/sdnr/odlux/odlux/apps/microwaveApp/src/linkCalculator/actions/viewAction.ts new file mode 100644 index 0000000..b2a5a02 --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/microwaveApp/src/linkCalculator/actions/viewAction.ts @@ -0,0 +1,40 @@ +/** + * ============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 isCalculationServerReachableAction extends Action { + constructor(public reachable: boolean) { + super(); + } +} + +export class ResetFormAction extends Action { +} + +export class UpdateRainMethodDisplayAction extends Action { + constructor(public rainDisplay: boolean) { + super(); + } +} +export class PluginDoneLoadingAction extends Action { + constructor(public loadingComplete: boolean) { + super(); + } +} diff --git a/features/sdnr/odlux/odlux/apps/microwaveApp/src/linkCalculator/actions/waveguideActions.ts b/features/sdnr/odlux/odlux/apps/microwaveApp/src/linkCalculator/actions/waveguideActions.ts new file mode 100644 index 0000000..917543f --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/microwaveApp/src/linkCalculator/actions/waveguideActions.ts @@ -0,0 +1,65 @@ +/** + * ============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 { Waveguide } from '../model/waveguide'; +import { Action } from '../../../../../framework/src/flux/action'; + +export class UpdateWaveguideLossAction extends Action { + constructor(public waveguideLossA: number, public waveguideLossB: number) { + super(); + } +} +export class UpdatewaveguideListAction extends Action { + constructor(public waveguideListName: string[]) { + super(); + } +} + +export class updateWaveguideNameAction extends Action { + constructor(public waveguideNameA: string | null, public waveguideNameB: string | null) { + super(); + } +} +export class waveguideMandatoryAction extends Action { + constructor(public waveguideMandatoryParameters: boolean) { + super(); + } +} +export class UpdateWaveguideIdAction extends Action { + constructor(public waveguideIdSiteA: number | null, public waveguideIdSiteB: number | null) { + super(); + } +} +export class updateWaveguideTypeAction extends Action { + constructor(public waveguideTypeA: string | null, public waveguideTypeB: string | null) { + super(); + } +} +export class UpdateWaveguideParametersAction extends Action { + constructor(public waveguide: Waveguide[]) { + super(); + } +} +export class UpdateNewWaveguideParametersAction extends Action { + constructor(public waveguideParametersA: Waveguide | null, public waveguideParametersB: Waveguide | null) { + super(); + } +} + + + diff --git a/features/sdnr/odlux/odlux/apps/microwaveApp/src/linkCalculator/assets/icons/microwaveAppIcon.svg b/features/sdnr/odlux/odlux/apps/microwaveApp/src/linkCalculator/assets/icons/microwaveAppIcon.svg new file mode 100644 index 0000000..47b881e --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/microwaveApp/src/linkCalculator/assets/icons/microwaveAppIcon.svg @@ -0,0 +1,19 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/features/sdnr/odlux/odlux/apps/microwaveApp/src/linkCalculator/components/adaptiveModulationDialog.tsx b/features/sdnr/odlux/odlux/apps/microwaveApp/src/linkCalculator/components/adaptiveModulationDialog.tsx new file mode 100644 index 0000000..8587635 --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/microwaveApp/src/linkCalculator/components/adaptiveModulationDialog.tsx @@ -0,0 +1,157 @@ + +/** + * ============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 React, { FC, useEffect, useState } from 'react'; + +import CloseIcon from '@mui/icons-material/Close'; +import makeStyles from '@mui/styles/makeStyles'; +import { InputProps } from '@mui/material/Input'; +import Dialog from '@mui/material/Dialog'; +import DialogContentText from '@mui/material/DialogContentText'; +import DialogTitle from '@mui/material/DialogTitle'; +import DialogContent from '@mui/material/DialogContent'; +import DialogActions from '@mui/material/DialogActions'; +import IconButton from '@mui/material/IconButton'; +import Box from '@mui/material/Box'; + +import MaterialTable, { ColumnType, MaterialTableCtorType } from '../../../../../framework/src/components/material-table'; + +import { AdaptiveModulationTable } from '../model/adaptiveModulationTable'; +import { Modulation } from '../model/modulation'; +import Checkbox from '@mui/material/Checkbox'; +import Button from '@mui/material/Button'; + + +type PropTypes = InputProps & { + style?: React.CSSProperties; + open: boolean; + close(): void; + row: AdaptiveModulationTable[]; + direction: string; + selectedElement: string[]; + updateModulation(data: string[]): void; +}; + +const ModulationTable = MaterialTable as MaterialTableCtorType; +const styles = makeStyles({ + closeIcon: { + position: 'absolute', + top: 0, + right: 0, + color: 'black', + }, + closeButton: { + marginTop: 20, position: 'absolute', top: '850px', right: '50px', + }, + applyButton: { + marginTop: 20, position: 'absolute', top: '850px', right: '160px', + }, +}); + +export const AdaptiveModulationDialog: FC = ({ open, row, close, direction, selectedElement, updateModulation }) => { + const [selectedElements, setSelectedElements] = useState(selectedElement); + const classes = styles(); + + const onChange = (element: any) => { + const data: { modulation: string; parameters: Modulation | null } = { modulation: element.target.value, parameters: null }; + setSelectedElements([element.target.value]); + + if (selectedElements.includes(data.modulation)) { + + setSelectedElements(selectedElements.filter((i) => i !== data.modulation)); + + } else { + var newData = [...selectedElements, data.modulation]; + setSelectedElements(newData); + + } + }; + + const onClose = () => { + close(); + }; + + const onSave = () => { + updateModulation(selectedElements); + close(); + }; + useEffect(() => { + setSelectedElements(selectedElement); + }, []); + + return ( +
+ onClose()} open={open} fullWidth maxWidth={'lg'} > + + + Adaptive Modulation + + {direction} + + + onClose()} + size="large"> + + + + + + {row !== null ? + <> ( onChange(e)} />), + }, + { property: 'dataRate', title: 'Data Rate (Mbit/s)', type: ColumnType.numeric }, + { property: 'receiverThresholdBER-3', title: 'Thrs BER 10-3 (dBm)', type: ColumnType.numeric }, + { property: 'receiverThresholdBER-6', title: 'Thrs BER 10-6 (dBm)', type: ColumnType.numeric }, + { property: 'receivedSignalLevel', title: 'RSL (dBm)', type: ColumnType.numeric }, + { property: 'linkMarginBER-3', title: 'Margin (dB)\n BER 10-3', type: ColumnType.numeric }, + { property: 'linkMarginBER-6', title: 'Margin (dB)\n BER 10-6', type: ColumnType.numeric }, + { property: 'txPowerMin', title: 'txPower\nMin(dBm)', type: ColumnType.numeric }, + { property: 'txPowerMax', title: 'txPower\nMax(dBm)', type: ColumnType.numeric }, + { property: 'rainAvailabilityBER-3', title: 'Rain Availability\n BER10-3', type: ColumnType.numeric }, + { property: 'rainAvailabilityBER-6', title: 'Rain Availability\n BER10-6', type: ColumnType.numeric }, + { property: 'multipathAvailabilityBER-3', title: 'multipath Fading\n BER10-3', type: ColumnType.numeric }, + { property: 'multipathAvailabilityBER-6', title: 'multipath Fading\n BER10-6', type: ColumnType.numeric }, + ]} /> + + + + + + : null + } + + +
+ ); +}; +export default AdaptiveModulationDialog; diff --git a/features/sdnr/odlux/odlux/apps/microwaveApp/src/linkCalculator/components/antenna.tsx b/features/sdnr/odlux/odlux/apps/microwaveApp/src/linkCalculator/components/antenna.tsx new file mode 100644 index 0000000..8951f4f --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/microwaveApp/src/linkCalculator/components/antenna.tsx @@ -0,0 +1,255 @@ +/** + * ============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 React, { FC, useEffect } from 'react'; + +import FormControl from '@mui/material/FormControl'; +import FormHelperText from '@mui/material/FormHelperText'; +import InputLabel from '@mui/material/InputLabel'; +import MenuItem from '@mui/material/MenuItem'; +import Select from '@mui/material/Select'; +import Stack from '@mui/material/Stack'; +import TextField from '@mui/material/TextField'; +import Typography from '@mui/material/Typography'; +import makeStyles from '@mui/styles/makeStyles'; + +import { useApplicationDispatch, useSelectApplicationState } from '../../../../../framework/src/flux/connect'; + +import { antennaMandatoryAction, UpdateAntennaAction } from '../actions/antennaActions'; +import { Antenna } from '../model/antenna'; +import { TextFieldwithAdornment } from './textFieldwithAdornment'; + + + +const antennaStyles = makeStyles({ + column: { + width: '220px', + }, + container: { + display: 'flex', flexDirection: 'column', width: 'column', + }, + component: { + flexDirection: 'row', justifyContent: 'space-around', + }, +}); + +const AntennaView: FC = (() => { + const antennaDb = useSelectApplicationState(state => state.microwave.antenna.antenna); + const eirpA = useSelectApplicationState(state => state.microwave.antenna.eirpA).toFixed(3); + const eirpB = useSelectApplicationState(state => state.microwave.antenna.eirpB).toFixed(3); + const antennaGainA = useSelectApplicationState(state => state.microwave.antenna.antennaGainA); + const antennaGainB = useSelectApplicationState(state => state.microwave.antenna.antennaGainB); + const aglA = useSelectApplicationState(state => state.microwave.antenna.antennaHeightA); + const aglB = useSelectApplicationState(state => state.microwave.antenna.antennaHeightB); + const lat1 = useSelectApplicationState(state => state.microwave.site.lat1); + const lon1 = useSelectApplicationState(state => state.microwave.site.lon1); + const lat2 = useSelectApplicationState(state => state.microwave.site.lat2); + const lon2 = useSelectApplicationState(state => state.microwave.site.lon2); + const antennaNameList = useSelectApplicationState(state => state.microwave.antenna.antennaNameList); + const antennaModelA = useSelectApplicationState(state => state.microwave.antenna.antennaNameA); + const antennaModelB = useSelectApplicationState(state => state.microwave.antenna.antennaNameB); + + const dispatch = useApplicationDispatch(); + + const updateAntennaParameters = (antennaA: Antenna | null, antennaB: Antenna | null) => { + dispatch(new UpdateAntennaAction(antennaA, antennaB)); + }; + const updateMandatoryParameters = (mandatoryParameters: boolean) => { + dispatch(new antennaMandatoryAction(mandatoryParameters)); + }; + + const classes = antennaStyles(); + + const checkMandatoryParameters = () => { + if (antennaModelA !== '0' && antennaModelB !== '0' && antennaGainA !== 0 && antennaGainB !== 0) { + updateMandatoryParameters(true); + } else { + updateMandatoryParameters(false); + } + }; + const updateAntennaName = async (antennaA: string | null, antennaB: string | null) => { + antennaDb.forEach(antenna => { + if (antennaA === antenna.modelName) { + updateAntennaParameters(antenna, null); + } + if (antenna.modelName === antennaB) { + updateAntennaParameters(null, antenna); + } + }); + + }; + + useEffect(() => checkMandatoryParameters(), [antennaModelA, antennaModelB]); + + return ( +
+ + Site A + Site B + + + + + + --Antenna-- + + { + antennaModelA === '' && *Required + } + + + + --Antenna-- + + { + antennaModelB === '' && *Required + } + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ ); + +}); +AntennaView.displayName = 'Antenna'; +export default AntennaView; \ No newline at end of file diff --git a/features/sdnr/odlux/odlux/apps/microwaveApp/src/linkCalculator/components/attenuations.tsx b/features/sdnr/odlux/odlux/apps/microwaveApp/src/linkCalculator/components/attenuations.tsx new file mode 100644 index 0000000..4015f06 --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/microwaveApp/src/linkCalculator/components/attenuations.tsx @@ -0,0 +1,298 @@ + +/** + * ============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 React, { FC, useEffect } from 'react'; + +import FormControl from '@mui/material/FormControl'; +import FormControlLabel from '@mui/material/FormControlLabel'; +import FormHelperText from '@mui/material/FormHelperText'; +import Grid from '@mui/material/Grid'; +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 Stack from '@mui/material/Stack'; +import makeStyles from '@mui/styles/makeStyles'; + + +import { attenuationMandatoryParametersAction, UpdateAttenuationMethodAction, UpdateRainMethodAction, UpdateRainValAction, UpdateWorstMonthAction } from '../actions/atmosphericLossAction'; +import { UpdatePolAction } from '../actions/linkAction'; +import { UpdateRainMethodDisplayAction } from '../actions/viewAction'; +import OutlinedDiv from './outlinedDiv'; +import { TextFieldwithAdornment } from './textFieldwithAdornment'; +import { useApplicationDispatch, useSelectApplicationState } from '../../../../../framework/src/flux/connect'; + + +const styles = makeStyles({ + + stack: { + display: 'flex', + justifyContent: 'center', + marginTop: '5', + paddingTop: '15px', + alignItems:'center', + }, + grid: { + display: 'flex', + flexDirection: 'column', + width: '500px', + }, + formStyle: { + paddingTop: '10px', + width: '40%', + }, + waterAbsorptionStyle: { + display: 'flex', + placeSelf: 'flex-end', + width: '35%', + paddingRight: '62px', + }, + textFieldwithAdornment: { + paddingTop: '10px', + width: '80%', + }, + insideGridStack: { + display: 'flex', + flexDirection: 'row', + justifyContent: 'space-around', + }, + centerColumn: { + display: 'flex', + justifyContent: 'center', + marginTop: '5', + }, + +}); + +const AttenuationView: FC = (() => { + const classes = styles(); + const dispatch = useApplicationDispatch(); + const rainAtt = useSelectApplicationState(state => state.microwave.atmosphere.rainAtt); + const rainVal = useSelectApplicationState(state => state.microwave.atmosphere.rainVal); + const absorptionOxygen = useSelectApplicationState(state => state.microwave.atmosphere.absorptionOxygen); + const absorptionWater = useSelectApplicationState(state => state.microwave.atmosphere.absorptionWater); + const worstMonth = useSelectApplicationState(state => state.microwave.atmosphere.worstMonth); + const rainMethod = useSelectApplicationState(state => state.microwave.atmosphere.rainMethod); + const attenuationMethod = useSelectApplicationState(state => state.microwave.atmosphere.attenuationMethod); + const rainDisplay = useSelectApplicationState(state => state.microwave.view.rainDisplay); + const fsl = useSelectApplicationState(state => state.microwave.atmosphere.fsl); + const polarization = useSelectApplicationState(state => state.microwave.link.polarization); + + const updateRainValue = async (rainRate: number) => dispatch(new UpdateRainValAction(rainRate)); + const updateRainMethod = (rainMethodSelect: string) => dispatch(new UpdateRainMethodAction(rainMethodSelect)); + + const updateAttenuationMethod = (attenuationMethodSelect: string) => dispatch(new UpdateAttenuationMethodAction(attenuationMethodSelect)); + const updateRainMethodDisplay = (rainMethodDisplay: boolean) => dispatch(new UpdateRainMethodDisplayAction(rainMethodDisplay)); + const updateWorstMonth = (worstMonthSelect: string) => { + if (worstMonthSelect === 'Annual') { + dispatch(new UpdateWorstMonthAction(false)); + } else dispatch(new UpdateWorstMonthAction(true)); + }; + const updatePolarization = (polarizationSelect: 'HORIZONTAL' | 'VERTICAL' | null) => dispatch(new UpdatePolAction(polarizationSelect)); + const updateMandatoryParameters = (mandatoryParametersPresent: boolean) => dispatch(new attenuationMandatoryParametersAction(mandatoryParametersPresent)); + + + const checkMandatoryParameters = () => { + let error: boolean = false; + if (rainVal !== 0 && rainMethod.length > 0 && attenuationMethod.length > 0) { + error = true; + } + if (rainVal === 0 && rainMethod === 'ITURP8377' && attenuationMethod.length > 0) { + error = true; + } + updateMandatoryParameters(error); + }; + + useEffect(() => { + checkMandatoryParameters(); + }, [rainVal, rainMethod, attenuationMethod]); + + + const setRainValue = async (rainfall: number) => { + await updateRainValue(rainfall); + }; + const handleSelectChange = async (event: any) => { + if (event.target.name === 'rainmethod') { + await updateRainMethod(event.target.value as any); + if (await event.target.value === 'ITURP8377') { + updateRainMethodDisplay(false); + } else { + updateRainMethodDisplay(true); + } + + } else if (event.target.name === 'absorptionmethod') { + await updateAttenuationMethod(event.target.value as any); + } + }; + + const onRadioSelect = (e: any) => { + + if (e.target.name === 'worstmonth') { + updateWorstMonth(e.target.value); + } else if (e.target.name === 'polarization') { + updatePolarization(e.target.value); + } + }; + + + + return ( +
+
+ + } label="Annual" + onChange={onRadioSelect} /> + } label="Worst Month" + onChange={onRadioSelect} /> + +
+
+ +
+ + + + + + --Rain Method-- + + {rainMethod.length === 0 && * Required } + + + } + label="Horizontal" + onChange={onRadioSelect} + /> + } + label="Vertical" + onChange={onRadioSelect} + /> + + + + { + await setRainValue(Number(e.target.value)); + }} + value={rainVal} + /> + + + + + + + + + + + --Absorption Method-- + + {(attenuationMethod.length === 0) && * Required } + + + + + + + + + +
+ ); +}); + +export default AttenuationView; \ No newline at end of file diff --git a/features/sdnr/odlux/odlux/apps/microwaveApp/src/linkCalculator/components/channelListDialog.tsx b/features/sdnr/odlux/odlux/apps/microwaveApp/src/linkCalculator/components/channelListDialog.tsx new file mode 100644 index 0000000..c26743e --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/microwaveApp/src/linkCalculator/components/channelListDialog.tsx @@ -0,0 +1,316 @@ + +/** + * ============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 React, { useEffect, FC } from 'react'; + +import CloseIcon from '@mui/icons-material/Close'; +import makeStyles from '@mui/styles/makeStyles'; +import { InputProps } from '@mui/material/Input'; +import Dialog from '@mui/material/Dialog'; +import DialogContentText from '@mui/material/DialogContentText'; +import DialogTitle from '@mui/material/DialogTitle'; +import DialogContent from '@mui/material/DialogContent'; +import DialogActions from '@mui/material/DialogActions'; +import IconButton from '@mui/material/IconButton'; +import Box from '@mui/material/Box'; +import Stack from '@mui/material/Stack'; +import Button from '@mui/material/Button'; +import Checkbox from '@mui/material/Checkbox'; +import FormControl from '@mui/material/FormControl'; +import Select from '@mui/material/Select'; +import MenuItem from '@mui/material/MenuItem'; +import FormHelperText from '@mui/material/FormHelperText'; +import TextField from '@mui/material/TextField'; + +import MaterialTable, { ColumnType, MaterialTableCtorType } from '../../../../../framework/src/components/material-table'; +import { ChannelTable } from '../model/bandPlan'; +import { TextFieldwithAdornment } from './textFieldwithAdornment'; +import ConnectionInfo from './connectionInfo'; + + +type PropTypes = InputProps & { + style?: React.CSSProperties; + linkId: number; + open: boolean; + close(): void; + row: ChannelTable[]; + updatechannels(data: ChannelTable[], totalBandwidthMHz: number): void; + frequencyPlanSiteA: 'HIGH' | 'LOW'; + frequencyPlanSiteB: 'HIGH' | 'LOW'; + band: number; + selectedElementProp: ChannelTable[]; +}; + +const ChannelSelectTable = MaterialTable as MaterialTableCtorType; +const styles = makeStyles({ + closeIcon: { + position: 'absolute', + top: 0, + right: 0, + color: 'black', + }, + closeButton: { + marginTop: 10, position: 'absolute', top: '750px', right: '50px', + }, + applyButton: { + marginTop: 10, position: 'absolute', top: '750px', right: '160px', + }, + tables: { + height: '700px', + }, + dialogContent: { + height: '900px', + }, + summary: { + position: 'absolute', right: '160px', + }, + error: { + height: '200px', + }, +}); + +export const ChannelListDialog: FC = ({ open, row, close, updatechannels, linkId, band, frequencyPlanSiteA, frequencyPlanSiteB, selectedElementProp }) => { + + const [polarizationAlert, setPolarizationAlert] = React.useState(false); + const [selectedElements, setSelectedElements] = React.useState(selectedElementProp); + const [totalBandwidthMHz, setTotalBandwidthMHz] = React.useState(0); + + const classes = styles(); + + const onChange = (tableRow: ChannelTable, checked: boolean) => { + + if (checked) { + setSelectedElements(selectedElements.filter((x) => x.name !== tableRow.name)); + if (tableRow.polarization.length !== 0) { + if (tableRow.polarization === 'XPOL') { + setTotalBandwidthMHz(totalBandwidthMHz - 2 * tableRow.bandwidthMHz); + } else setTotalBandwidthMHz(totalBandwidthMHz - tableRow.bandwidthMHz); + } + + + } else { + tableRow.polarization = ''; + setSelectedElements([...selectedElements, tableRow]); + // setTotalBandwidthMHz(totalBandwidthMHz + tableRow.bandwidthMHz); + } + }; + + const onClose = () => { + close(); + }; + + const onSave = () => { + let checker = false; + selectedElements.forEach(channel => { + if (channel.polarization === '') { + checker = true; + setPolarizationAlert(true); + } + }); + if (!checker) { + setPolarizationAlert(false); + updatechannels(selectedElements, totalBandwidthMHz); + close(); + } + }; + + + const setChannelPolarization = (tableRow: ChannelTable, polarization: 'HORIZONTAL' | 'VERTICAL' | 'XPOL' | '') => { + + if (tableRow.polarization === 'HORIZONTAL' || tableRow.polarization === 'VERTICAL') { + if (polarization === 'XPOL') { + setTotalBandwidthMHz(totalBandwidthMHz + tableRow.bandwidthMHz); + } else setTotalBandwidthMHz(totalBandwidthMHz); + } else if (tableRow.polarization === '') { + if (polarization === 'XPOL') { + setTotalBandwidthMHz(totalBandwidthMHz + 2 * tableRow.bandwidthMHz); + } else { + setTotalBandwidthMHz(totalBandwidthMHz + tableRow.bandwidthMHz); + } + } else if (polarization === 'XPOL') { + setTotalBandwidthMHz(totalBandwidthMHz ); + } else { + debugger; + setTotalBandwidthMHz(totalBandwidthMHz - tableRow.bandwidthMHz); + } + + + tableRow.polarization = polarization; + setSelectedElements(selectedElements.map(x => { + if (x.keyId === tableRow.keyId) { + return tableRow; + } else return x; + })); + }; + + useEffect(() => { + let bandwidthList: number[] = []; + row.forEach(x => { + selectedElementProp.forEach(tableRow => { + if (tableRow.keyId === x.keyId) { + if (tableRow.polarization === 'XPOL') { + bandwidthList.push(2 * x.bandwidthMHz); + } else bandwidthList.push(x.bandwidthMHz); + } + }); + }); + row.map(eachRow => { + selectedElements.forEach(selectedRow => { + if (eachRow.keyId === selectedRow.keyId) { + eachRow.polarization = selectedRow.polarization; + } + }); + }); + setTotalBandwidthMHz(bandwidthList.reduce((partialSum, a) => partialSum + a, 0)); + }, []); + + + + return ( + <> + onClose()} open={open} fullWidth maxWidth={'lg'} > + + + Channel + + +
+ Channel Select for link : {linkId} + + x.name)} + /> + + +
+ + + + onClose()} + size="large"> + + + + {row !== null ? + <> + + + ( i.name === rowData.name).length > 0} + value={rowData.name} onClick={() => onChange(rowData, selectedElements.filter(i => i.name === rowData.name).length > 0)} />), width: 8, + }, + { property: 'bandwidthMHz', title: 'Bandwidth (MHz)', type: ColumnType.numeric, width: 3 }, + { property: 'centerFrequencyHigh', title: 'Frequency ' + frequencyPlanSiteA + '(MHz)', type: ColumnType.numeric, width: 3 }, + { property: 'centerFrequencyLow', title: 'Frequency ' + frequencyPlanSiteB + '(MHz)', type: ColumnType.numeric, width: 3 }, + { + property: 'polarization', type: ColumnType.custom, title: 'Polarization', customControl: ({ rowData }) => ( + + + + {selectedElements.filter(i => i.name === rowData.name).length > 0 && rowData.polarization.length === 0 && *Required } + + + ), + }, + { property: 'availability', title: 'Availability', type: ColumnType.text, width: 3 }, + { property: 'xPolCondition', title: 'XPol', type: ColumnType.text, width: 3 }, + ]} /> + + + + + + + + + : null + + } +
+ +
+ + { + polarizationAlert && + + + + + + setPolarizationAlert(false)} + size="large"> + + + + + + + + + + } + + + + + + ); +}; +export default ChannelListDialog; diff --git a/features/sdnr/odlux/odlux/apps/microwaveApp/src/linkCalculator/components/connectionInfo.tsx b/features/sdnr/odlux/odlux/apps/microwaveApp/src/linkCalculator/components/connectionInfo.tsx new file mode 100644 index 0000000..87f7eca --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/microwaveApp/src/linkCalculator/components/connectionInfo.tsx @@ -0,0 +1,66 @@ +/** + * ============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 React, { FC } from 'react'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { faExclamationTriangle } from '@fortawesome/free-solid-svg-icons'; + +import makeStyles from '@mui/styles/makeStyles'; +import { InputProps } from '@mui/material/Input'; +import Paper from '@mui/material/Paper'; +import Typography from '@mui/material/Typography'; + + +type PropTypes = InputProps & { + message : string; + messageType: string; + reachable: boolean; +}; +const ConnectionInfo: FC = (props : PropTypes) => { + const connectionStyles = makeStyles({ + pasperStyle: { + padding: 5, width: 230, position: 'absolute', top: '40%', left: '40%', + }, + wrappingDiv: { + display: 'flex', flexDirection: 'column', + }, + componentDiv: { + 'alignSelf': 'center', marginBottom: 5, + }, + }); + const classes = connectionStyles(); + return ( + (props.reachable === false) ? + +
+
+ + + {props.messageType} + +
+ {props.message} +
+
: null + + ); +}; + + +export default ConnectionInfo; + diff --git a/features/sdnr/odlux/odlux/apps/microwaveApp/src/linkCalculator/components/frequencyChannel.tsx b/features/sdnr/odlux/odlux/apps/microwaveApp/src/linkCalculator/components/frequencyChannel.tsx new file mode 100644 index 0000000..59e5a4c --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/microwaveApp/src/linkCalculator/components/frequencyChannel.tsx @@ -0,0 +1,386 @@ +/** + * ============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 React, { useEffect, FC, useState } from 'react'; + +import makeStyles from '@mui/styles/makeStyles'; +import Stack from '@mui/material/Stack'; +import InputLabel from '@mui/material/InputLabel'; +import FormControl from '@mui/material/FormControl'; +import Select from '@mui/material/Select'; +import MenuItem from '@mui/material/MenuItem'; +import FormHelperText from '@mui/material/FormHelperText'; +import Grid from '@mui/material/Grid'; +import RadioGroup from '@mui/material/RadioGroup'; +import FormControlLabel from '@mui/material/FormControlLabel'; +import Radio from '@mui/material/Radio'; +import TextField from '@mui/material/TextField'; +import Box from '@mui/material/Box'; +import Button from '@mui/material/Button'; +import Dialog from '@mui/material/Dialog'; +import DialogContent from '@mui/material/DialogContent'; +import DialogContentText from '@mui/material/DialogContentText'; +import Paper from '@mui/material/Paper'; +import DialogActions from '@mui/material/DialogActions'; +import IconButton from '@mui/material/IconButton'; +import CloseIcon from '@mui/icons-material/Close'; +import CircularProgress from '@mui/material/CircularProgress'; + +import { useApplicationDispatch, useSelectApplicationState } from '../../../../../framework/src/flux/connect'; + +import { UpdateFrequencyPlanAction } from '../actions/linkAction'; +import { frequencyMandatoryParametersAction, ResetAction, updateFrequencyBand, UpdateTotalBandwidthAction } from '../actions/radioActions'; +import OutlinedDiv from './outlinedDiv'; +import { getAllBands, UpdateChannelQuery, UpdateChannelListLoadingAction, UpdateRegionRegulatorAction, UpdateChannelListAction } from '../actions/bandPlanAction'; +import { ChannelTable, RegionRegulator } from '../model/bandPlan'; +import ChannelListDialog from './channelListDialog'; +import ConnectionInfo from './connectionInfo'; +import { TextFieldwithAdornment } from './textFieldwithAdornment'; + +const styles = makeStyles({ + fitContent: { + width: 'fit-content', + }, + column: { + width: '220px', + }, + container: { + display: 'flex', flexDirection: 'column', width: 'column', + }, + component: { + flexDirection: 'row', justifyContent: 'space-around', + }, + centerColumn: { + display: 'flex', + alignItems: 'center', + marginTop: '5', + }, + channelRow: { + flexDirection: 'row', justifyContent: 'space-evenly', + }, + loading: { + position: 'absolute', top: 0, left: 0, width: '100%', height: '100%', display: 'flex', justifyContent: 'center', alignItems: 'center', backgroundColor: 'rgba(255, 255, 255, 0.7)', zIndex: 9999, + + }, + closeIcon: { + marginTop: 20, top: '-75px', right: '-40px', + }, +}); + +const FrequencyChannelView: FC = (() => { + const [isOpenDialog, setOpenDialog] = useState(false); + const band = useSelectApplicationState(state => state.microwave.radio.band); + const frequencyPlanA = useSelectApplicationState(state => state.microwave.bandPlan.frequencyPlanA); + const frequencyPlanB = useSelectApplicationState(state => state.microwave.bandPlan.frequencyPlanB); + const bandList = useSelectApplicationState(state => state.microwave.bandPlan.bandList); + const regionRegulatorList = useSelectApplicationState(state => state.microwave.bandPlan.regionRegulatorList); + const regulator = useSelectApplicationState(state => state.microwave.bandPlan.region); + const processing = useSelectApplicationState(state => state.microwave.bandPlan.channelListLoading); + const linkId = useSelectApplicationState(state => state.microwave.link.linkId); + const channelListQuery = useSelectApplicationState(state => state.microwave.bandPlan.channelListQuery); + const allChannels = useSelectApplicationState(state => state.microwave.bandPlan.allChannels); + const totalBandwidthMHz = useSelectApplicationState(state => state.microwave.radio.totalBandwidthMHz); + const frequencyPlanProcessing = useSelectApplicationState(state => state.microwave.bandPlan.frequencyPlanProcessing); + const savedChannels = useSelectApplicationState(state => state.microwave.bandPlan.savedChannels); + const dispatch = useApplicationDispatch(); + + const updateFrequency = (bandKeyId: string) => dispatch(updateFrequencyBand(bandKeyId)); + const updateFrequencyPlan = (planA: 'HIGH' | 'LOW', planB: 'HIGH' | 'LOW') => dispatch(new UpdateFrequencyPlanAction(planA, planB)); + const updateMandatoryParameters = (mandatoryParameters: boolean) => dispatch(new frequencyMandatoryParametersAction(mandatoryParameters)); + const updateRegion = (region: RegionRegulator) => { + dispatch(new UpdateRegionRegulatorAction(region)); + dispatch(new ResetAction()); + }; + const getBandList = (bandplanKeyId: string) => dispatch(getAllBands(bandplanKeyId)); + const channelList = async (regionId: string, bandKeyId: string) => dispatch(UpdateChannelQuery(regionId, bandKeyId)); + const updatechannelsAndBandwidth = (channels: ChannelTable[], bandwidthMHz: number) => { + dispatch(new UpdateChannelListAction(channels)); + dispatch(new UpdateTotalBandwidthAction(bandwidthMHz)); + }; + const updateChannelListLoading = (proccessing: boolean) => dispatch(new UpdateChannelListLoadingAction(proccessing)); + + const classes = styles(); + + const frequencyChange = async (frequency: number) => { + if (frequency !== band.frequency) { + bandList.forEach(x => { + if (x.name === frequency.toString()) { + updateFrequency(x.keyId); + } + }); + + } + + }; + const regionChange = async (regionAsString: string) => { + if (regionAsString !== regulator.name) { + regionRegulatorList.forEach(async region => { + if (regionAsString === region.name) { + await updateRegion(region); + getBandList(region.keyId); + } + }); + } + + }; + const onRadioSelect = (e: any) => { + if (e.target.name === 'site-a-frequency-plan') { + if (e.target.value === 'HIGH') { + updateFrequencyPlan(e.target.value, 'LOW'); + } else { + updateFrequencyPlan(e.target.value, 'HIGH'); + } + } else if (e.target.name === 'site-b-frequency-plan') { + if (e.target.value === 'HIGH') { + updateFrequencyPlan('LOW', e.target.value); + } else { + updateFrequencyPlan('HIGH', e.target.value); + } + } + }; + + + const checkMandatoryParameters = () => { + if (band.frequency !== 0) { + updateMandatoryParameters(true); + } else { + updateMandatoryParameters(false); + } + }; + const handleClickOpen = (show: boolean) => { + setOpenDialog(show); + }; + const getChannelList = async () => { + + bandList.forEach(x => { + if (x.keyId === band.keyId) { + channelList(regulator.keyId, band.keyId); + } + }); + handleClickOpen(true); + }; + const handleDialogClose = () => { + updateChannelListLoading(true); + setOpenDialog(false); + + }; + const updatechannels = (selectedChannels: ChannelTable[], bandwidthMHz: number) => { + updatechannelsAndBandwidth(selectedChannels, bandwidthMHz); + + }; + useEffect(() => { + checkMandatoryParameters(); + }, [band.frequency]); + + return ( +
+ {frequencyPlanProcessing ? + +
+ +
+
+ : +
+ + + --Region-- + + {band.frequency == 0 && *Required } + + + + + --Band (GHz) -- + + {band.frequency == 0 && *Required } + + + + + + + + + + } label="High" onChange={onRadioSelect} /> + } label="Low" onChange={onRadioSelect} /> + + + + + + + } label="High" onChange={onRadioSelect} /> + } label="Low" onChange={onRadioSelect} /> + + + + + + x.polarization === 'XPOL' ? x.name + '[xpol]' : x.name)} + /> + + + + + + x.polarization === 'XPOL' ? x.name + '[xpol]' : x.name)} + /> + +
+ {isOpenDialog && + <> + {processing ? + + + + + Channel Select + + + + + + + +
+ +

Processing ...

+
+
+
+
+
+ : + <> + {channelListQuery.length > 0 ? + + + + : +
+ + + + Channel + + + + + + + + + + + +
+ + } + + } + + } + +
+
+ } +
+ ); + +}); +FrequencyChannelView.displayName = 'Frequency'; +export default FrequencyChannelView; + + diff --git a/features/sdnr/odlux/odlux/apps/microwaveApp/src/linkCalculator/components/linkTable.tsx b/features/sdnr/odlux/odlux/apps/microwaveApp/src/linkCalculator/components/linkTable.tsx new file mode 100644 index 0000000..5f07bbd --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/microwaveApp/src/linkCalculator/components/linkTable.tsx @@ -0,0 +1,123 @@ +/** + * ============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, useEffect } from 'react'; + +import { Loader } from '../../../../../framework/src/components/material-ui/loader'; + +import { NavigateToApplication } from '../../../../../framework/src/actions/navigationActions'; +import { ColumnType, MaterialTable, MaterialTableCtorType } from '../../../../../framework/src/components/material-table'; +import { useApplicationDispatch, useSelectApplicationState } from '../../../../../framework/src/flux/connect'; +import { Link } from '../../lineOfSight/model/lineOfSightLatLon'; +import { getLinkDetails } from '../../lineOfSight/service/lineOfSightHeightService'; +import { createLinkTableActions, createLinkTableProperties } from '../handlers/linkTableHandler'; +import MenuItem from '@mui/material/MenuItem'; +import Typography from '@mui/material/Typography'; + + + +const LinkTable = MaterialTable as MaterialTableCtorType; + + +let initialSorted = false; + +const LinkTableComponent: FC = (() => { + + const linkTableProperties = createLinkTableProperties(useSelectApplicationState(state => state)); + const loading = linkTableProperties.loading; + const dispatch = useApplicationDispatch(); + const linkTableActions = createLinkTableActions(dispatch); + const navigateToApplication = (applicationName: string, path?: string) => dispatch(new NavigateToApplication(applicationName, path)); + + useEffect(() => { + if (!initialSorted) { + initialSorted = true; + linkTableActions.onHandleRequestSort('id'); + } else { + linkTableActions.onRefresh(); + } + }, []); + + const getContextMenu = (rowData: any) => { + return [ + { + navigateToApplication('microwave', `calculateLink/?linkId=${rowData.id}`); + }}> + Calculate Link + , + { + await getLinkDetails(rowData.id).then((data: Link) => { + let heightPart = `&amslA=${data.siteA.amslM}&antennaHeightA=${data.siteA.radioAntenna.operationalParameters.agl}&amslB=${data.siteB.amslM}&antennaHeightB=${data.siteB.radioAntenna.operationalParameters.agl}`; + navigateToApplication('microwave', `lineOfSightMap/los?lat1=${data.siteA.lat}&lon1=${data.siteA.lon}&lat2=${data.siteB.lat}&lon2=${data.siteB.lon}${heightPart}`); + }); + }}> + Line Of Sight + , + ]; + }; + + return ( +
+
+ {loading && ( +
+ +

Loading...

+
+ ) + } +
+ { + return <>{rowData.siteA.id}; + }, + }, + { + property: 'siteB', title: 'SiteB', type: ColumnType.custom, + customControl: ({ rowData }) => { + return <>{rowData.siteB.id}; + }, + }, + { property: 'operationalState', title: 'operational State', type: ColumnType.text }, + { property: 'operatorId', title: 'Operator Id', type: ColumnType.text }, + { property: 'lifecycleState', title: 'life Cycle State', type: ColumnType.text }, + ]} + idProperty="id" {...linkTableActions} {...linkTableProperties} + createContextMenu={(rowData) => { + return getContextMenu(rowData); + }} /> +
+ ); +}); + + +export default LinkTableComponent; \ No newline at end of file diff --git a/features/sdnr/odlux/odlux/apps/microwaveApp/src/linkCalculator/components/location.tsx b/features/sdnr/odlux/odlux/apps/microwaveApp/src/linkCalculator/components/location.tsx new file mode 100644 index 0000000..0b88a89 --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/microwaveApp/src/linkCalculator/components/location.tsx @@ -0,0 +1,262 @@ +/** + * ============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 from '@mui/material/TextField'; +import Typography from '@mui/material/Typography'; +import makeStyles from '@mui/styles/makeStyles'; +import Stack from '@mui/material/Stack'; + +import { useSelectApplicationState } from '../../../../../framework/src/flux/connect'; +import { LatLonToDMS } from '../utils/geoConverter'; + +import { TextFieldwithAdornment } from './textFieldwithAdornment'; +import ManualLocationEnter from './manualLocationEntr'; + +const styles = makeStyles({ + column: { + width: '220px', + }, + container: { + display: 'flex', flexDirection: 'column', width: 'column', + }, + component: { + flexDirection: 'row', justifyContent: 'space-around', + }, + centerColumn: { + display: 'flex', + alignItems: 'center', + marginTop: '5', + }, +}); + + +const LocationView: React.FC = (() => { + const linkId = useSelectApplicationState(state => state.microwave.link.linkId); + const lat1 = useSelectApplicationState(state => state.microwave.site.lat1); + const lon1 = useSelectApplicationState(state => state.microwave.site.lon1); + const lat2 = useSelectApplicationState(state => state.microwave.site.lat2); + const lon2 = useSelectApplicationState(state => state.microwave.site.lon2); + const formView = useSelectApplicationState(state => state.microwave.view.formView); + const siteA = useSelectApplicationState(state => state.microwave.site.siteNameA); + const siteB = useSelectApplicationState(state => state.microwave.site.siteNameB); + const distance = useSelectApplicationState(state => state.microwave.link.distance); + const amslA = useSelectApplicationState(state => state.microwave.site.amslA); + const amslB = useSelectApplicationState(state => state.microwave.site.amslB); + const siteAId = useSelectApplicationState(state => state.microwave.site.siteIdA); + const siteBId = useSelectApplicationState(state => state.microwave.site.siteIdB); + const azimuthDegSiteA = useSelectApplicationState(state => state.microwave.site.azimuthA); + const azimuthDegSiteB = useSelectApplicationState(state => state.microwave.site.azimuthB); + const tiltDegSiteA = useSelectApplicationState(state => state.microwave.site.tiltDegA); + const tiltDegSiteB = useSelectApplicationState(state => state.microwave.site.tiltDegB); + + + const classes = styles(); + + return ( + <> + + {!formView && + + } +
+ + + + + + Site A + + Site B + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + ); + +}); +export default LocationView; diff --git a/features/sdnr/odlux/odlux/apps/microwaveApp/src/linkCalculator/components/manualLocationEntr.tsx b/features/sdnr/odlux/odlux/apps/microwaveApp/src/linkCalculator/components/manualLocationEntr.tsx new file mode 100644 index 0000000..9d2c104 --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/microwaveApp/src/linkCalculator/components/manualLocationEntr.tsx @@ -0,0 +1,164 @@ + +/** + * ============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 React, { useState } from 'react'; + +import Stack from '@mui/material/Stack'; +import TextField from '@mui/material/TextField'; +import Typography from '@mui/material/Typography'; +import makeStyles from '@mui/styles/makeStyles'; + +import { locationMandatoryAction, UpdateLatLonAction } from '../actions/siteAction'; +import { useApplicationDispatch, useSelectApplicationState } from '../../../../../framework/src/flux/connect'; + + + + + + +const styles = makeStyles({ + row: { flexDirection: 'column', display: 'flex', width: '60%', marginLeft: '10px' }, +}); + +const ManualLocationEnter: React.FC = (() => { + + const [latitude1Error, setLatitude1Error] = useState(''); + const [latitude2Error, setLatitude2Error] = useState(''); + const [longitude1Error, setLongitude1Error] = useState(''); + const [longitude2Error, setLongitude2Error] = useState(''); + + const lat1 = useSelectApplicationState(state => state.microwave.site.lat1); + const lon1 = useSelectApplicationState(state => state.microwave.site.lon1); + const lat2 = useSelectApplicationState(state => state.microwave.site.lat2); + const lon2 = useSelectApplicationState(state => state.microwave.site.lon2); + // const latitude1Error = useSelectApplicationState(state => state.microwave.error.latitude1Error); + // const latitude2Error = useSelectApplicationState(state => state.microwave.error.latitude2Error); + // const longitude1Error = useSelectApplicationState(state => state.microwave.error.longitude1Error); + // const longitude2Error = useSelectApplicationState(state => state.microwave.error.longitude2Error); + const loadingComplete = useSelectApplicationState(state => state.microwave.view.loadingComplete); + + + const dispatch = useApplicationDispatch(); + const updateLatLon = (updateLat1: number, updateLon1: number, updateLat2: number, updateLon2: number) => dispatch(new UpdateLatLonAction(updateLat1, updateLon1, updateLat2, updateLon2)); + const updateMandatoryParameters = (mandatory: boolean) => dispatch(new locationMandatoryAction(mandatory)); + + const classes = styles(); + + const checkMandatoryParameters = () => { + if (latitude1Error === '' && longitude1Error === '' && latitude1Error === '' && longitude1Error === '') { + updateMandatoryParameters(true); + } else updateMandatoryParameters(false); + }; + React.useEffect(() => { + checkMandatoryParameters(); + }, [loadingComplete]); + + const changeLatLon = (e: any) => { + + if (e.target.id == 'Lat1') { + updateLatLon(e.target.value, lon1, lat2, lon2); + } + if (e.target.id == 'Lon1') { + updateLatLon(lat1, e.target.value, lat2, lon2); + } + if (e.target.id == 'Lat2') { + updateLatLon(lat1, lon1, e.target.value, lon2); + } + if (e.target.id == 'Lon2') { + updateLatLon(lat1, e.target.value, lat2, e.target.value); + } + }; + + const handleChange = (e: any) => { + + if (e.target.id === 'Lat1') { + if (e.target.value > 90 || e.target.value < -90) { + setLatitude1Error('Enter a number between -90 to 90'); + } else { + changeLatLon(e); + setLatitude1Error(''); + } + } else if (e.target.id === 'Lat2') { + if (e.target.value > 90 || e.target.value < -90) { + setLatitude2Error('Enter a number between -90 to 90'); + } else { + changeLatLon(e); + setLatitude2Error(''); + } + } else if (e.target.id === 'Lon1') { + if (e.target.value > 180 || e.target.value < -180) { + setLongitude1Error('Enter a number between -180 to 180'); + } else { + changeLatLon(e); + setLongitude1Error(''); + } + } else if (e.target.id === 'Lon2') { + if (e.target.value > 180 || e.target.value < -180) { + setLongitude2Error('Enter a number between -180 to 180'); + } else { + changeLatLon(e); + setLongitude2Error(''); + } + } + checkMandatoryParameters(); + }; + + + return ( + + + Site A + Site B + + + + 0 || latitude1Error!.length > 0} + onChange={(e: any) => { + handleChange(e); + }} /> + 0 || longitude1Error!.length > 0} + onChange={(e: any) => { + handleChange(e); + }} /> + + + + 0 || latitude2Error!.length > 0} + onChange={(e: any) => { + handleChange(e); + }} /> + 0 || longitude2Error!.length > 0} + onChange={(e: any) => { + handleChange(e); + }} /> + + + + + ); +}); +export default ManualLocationEnter; diff --git a/features/sdnr/odlux/odlux/apps/microwaveApp/src/linkCalculator/components/missingInformation.tsx b/features/sdnr/odlux/odlux/apps/microwaveApp/src/linkCalculator/components/missingInformation.tsx new file mode 100644 index 0000000..6698e4a --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/microwaveApp/src/linkCalculator/components/missingInformation.tsx @@ -0,0 +1,45 @@ +/** + * ============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 React, { FC } from 'react'; + +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { faExclamationTriangle } from '@fortawesome/free-solid-svg-icons'; + +import Paper from '@mui/material/Paper'; +import Typography from '@mui/material/Typography'; +import { useSelectApplicationState } from '../../../../../framework/src/flux/connect'; + + +const MissingInformation: FC = (() => { + const isCalculationServerReachable = useSelectApplicationState(state => state.microwave.view.reachable); + const formView = useSelectApplicationState(state => state.microwave.view.formView); + return ( + (isCalculationServerReachable && formView) ? +
+
Link Information Missing
+ +
+
: null + + ); +}); + + +export default MissingInformation; + diff --git a/features/sdnr/odlux/odlux/apps/microwaveApp/src/linkCalculator/components/outlinedDiv.tsx b/features/sdnr/odlux/odlux/apps/microwaveApp/src/linkCalculator/components/outlinedDiv.tsx new file mode 100644 index 0000000..806035f --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/microwaveApp/src/linkCalculator/components/outlinedDiv.tsx @@ -0,0 +1,48 @@ + +/** + * ============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 React, { CSSProperties, FC } from 'react'; + +import TextField from '@mui/material/TextField'; +import { InputProps } from '@mui/material/Input'; + +type PropTypes = InputProps & { + label: string; + style?: CSSProperties; +}; + +const InputComponent : FC = ({ ...other }) =>
; + +export const OutlinedDiv: FC = ({ label, children, style }) => { + return ( + + ); +}; +export default OutlinedDiv; diff --git a/features/sdnr/odlux/odlux/apps/microwaveApp/src/linkCalculator/components/radio.tsx b/features/sdnr/odlux/odlux/apps/microwaveApp/src/linkCalculator/components/radio.tsx new file mode 100644 index 0000000..6421964 --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/microwaveApp/src/linkCalculator/components/radio.tsx @@ -0,0 +1,597 @@ + +/** + * ============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 React, { FC, useEffect, useState } from 'react'; + +import CloseIcon from '@mui/icons-material/Close'; +import Divider from '@mui/material/Divider'; +import makeStyles from '@mui/styles/makeStyles'; +import Stack from '@mui/material/Stack'; +import Typography from '@mui/material/Typography'; +import FormControl from '@mui/material/FormControl'; +import InputLabel from '@mui/material/InputLabel'; +import Select from '@mui/material/Select'; +import MenuItem from '@mui/material/MenuItem'; +import FormHelperText from '@mui/material/FormHelperText'; +import Box from '@mui/material/Box'; +import Button from '@mui/material/Button'; +import Dialog from '@mui/material/Dialog'; +import DialogContent from '@mui/material/DialogContent'; +import DialogContentText from '@mui/material/DialogContentText'; +import Paper from '@mui/material/Paper'; +import DialogActions from '@mui/material/DialogActions'; +import IconButton from '@mui/material/IconButton'; + + + +import { adaptiveModulationInputCreator, UpdateAdaptiveModulationProcessing } from '../../linkCalculator/actions/adaptiveModulationAction'; +import { radioMandatoryParametersAction, UpdateEnabeldAdaptiveModulations, UpdateModulationAction, updateRadioAttributes, UpdateModulationParametersAction, UpdateTxPowerAction } from '../actions/radioActions'; +import { Modulation } from '../model/modulation'; +import AdaptiveModulationDialog from './adaptiveModulationDialog'; +import { Radio } from '../model/radio'; +import { isNumber } from '../utils/math'; +import { TextFieldwithAdornment } from './textFieldwithAdornment'; +import ConnectionInfo from './connectionInfo'; +import CircularProgress from '@mui/material/CircularProgress'; +import { useApplicationDispatch, useSelectApplicationState } from '../../../../../framework/src/flux/connect'; + + + + + + + + + + + + + +const styles = makeStyles({ + modulationControl: { width: '230px' }, + adaptiveModulationButton: { paddingTop: '10px', width: '250px' }, + radioResultBox: { display: 'flex', flexDirection: 'row', justifyContent: 'space-around' }, + loading: { + position: 'absolute', top: 0, left: 0, width: '100%', height: '100%', display: 'flex', justifyContent: 'center', alignItems: 'center', backgroundColor: 'rgba(255, 255, 255, 0.7)', zIndex: 9999, + }, + pasperStyle: { + padding: 5, width: 230, position: 'absolute', top: '40%', left: '40%', + }, + componentDiv: { + 'alignSelf': 'center', marginBottom: 5, + }, + wrappingDiv: { + display: 'flex', flexDirection: 'column', + }, + closeIcon: { + marginTop: 20, top: '-75px', right: '-40px', + }, + container: { + display: 'flex', flexDirection: 'column', width: 'column', + }, + component: { + flexDirection: 'row', justifyContent: 'space-around', + }, + textField: { + width: '85%', + }, + column: { + width: '220px', + }, +}); + +const RadioView: FC = (() => { + const [isOpenDialog, setOpenDialog] = useState(false); + const [adaptiveModulationSite, setAdaptiveModulationSite] = useState(''); + const classes = styles(); + const radioNameList = useSelectApplicationState(state => state.microwave.radio.radioNameList); + const rxPowerA = useSelectApplicationState(state => state.microwave.radio.rxPowerA); + const rxPowerB = useSelectApplicationState(state => state.microwave.radio.rxPowerB); + const systemOperatingMarginA = useSelectApplicationState(state => state.microwave.radio.systemOperatingMarginA); + const systemOperatingMarginB = useSelectApplicationState(state => state.microwave.radio.systemOperatingMarginB); + const radioModelA = useSelectApplicationState(state => state.microwave.radio.radioNameA); + const radioModelB = useSelectApplicationState(state => state.microwave.radio.radioNameB); + const radioTxPowerA = useSelectApplicationState(state => state.microwave.radio.txPowerA); + const radioTxPowerB = useSelectApplicationState(state => state.microwave.radio.txPowerB); + const radioParameters = useSelectApplicationState(state => state.microwave.radio.radioParameters); + const radioBandwidthA = useSelectApplicationState(state => state.microwave.radio.radioBandwidthA); + const radioBandwidthB = useSelectApplicationState(state => state.microwave.radio.radioBandwidthB); + const modulationListA = useSelectApplicationState(state => state.microwave.radio.modulationListA); + const modulationListB = useSelectApplicationState(state => state.microwave.radio.modulationListB); + const radioModulationA = useSelectApplicationState(state => state.microwave.radio.modulationA); + const radioModulationB = useSelectApplicationState(state => state.microwave.radio.modulationB); + const modulationParametersA = useSelectApplicationState(state => state.microwave.radio.modulationParametersA); + const modulationParametersB = useSelectApplicationState(state => state.microwave.radio.modulationParametersB); + const adaptiveModulationTableBtoA = useSelectApplicationState(state => state.microwave.radio.adaptiveModulationTableBtoA); + const adaptiveModulationTableAtoB = useSelectApplicationState(state => state.microwave.radio.adaptiveModulationTableAtoB); + const enabledAdaptiveModulations = useSelectApplicationState(state => state.microwave.radio.enabledAdaptiveModulations); + const rxThresholdBER3A = useSelectApplicationState(state => state.microwave.radio.thresholdBER3A); + const rxThresholdBER6A = useSelectApplicationState(state => state.microwave.radio.thresholdBER6A); + const rxThresholdBER3B = useSelectApplicationState(state => state.microwave.radio.thresholdBER3B); + const rxThresholdBER6B = useSelectApplicationState(state => state.microwave.radio.thresholdBER6B); + const processing = useSelectApplicationState(state => state.microwave.radio.processing); + const admTableMessage = useSelectApplicationState(state => state.microwave.radio.admMessage); + const admTableStatus = useSelectApplicationState(state => state.microwave.radio.admStatus); + const radios = useSelectApplicationState(state => state.microwave.radio.radioParameters); + + const dispatch = useApplicationDispatch(); + + const UpdateTxPower = async (txPowerA: number | null, txPowerB: number | null) => dispatch(new UpdateTxPowerAction(txPowerA, txPowerB)); + const updateRadio = async (radioA: string | null, radioB: string | null) => dispatch(updateRadioAttributes(radioA ? radioA : radioB, radioB ? radioB : radioA, radios)); + const updateMandatoryParameters = (parametersComplete: boolean) => dispatch(new radioMandatoryParametersAction(parametersComplete)); + const updateRadioModulation = async (modulationA: string | null, modulationB: string | null) => dispatch(new UpdateModulationAction(modulationA, modulationB)); + const updateModulationParameters = (newModulationParametersA: Modulation | null, newModulationParametersB: Modulation | null) => dispatch(new UpdateModulationParametersAction(newModulationParametersA, newModulationParametersB)); + const adaptiveModulation = async () => dispatch(adaptiveModulationInputCreator()); + const updateADMlist = (admList: any) => dispatch(new UpdateEnabeldAdaptiveModulations(admList.map((x: any) => x.modulation))); + const resetAdapativeModulationTableProcessing = () => dispatch(new UpdateAdaptiveModulationProcessing(true)); + const handleClickOpen = (show: boolean) => { + setOpenDialog(show); + }; + + const calculateAdaptiveModulation = async () => { + handleClickOpen(true); + await adaptiveModulation(); + }; + + const checkMandatoryParameters = () => { + + if (radioModelA.length > 0 && radioModelB.length > 0) { + if (radioModulationA.length > 0 && radioModulationB.length > 0) { + if (isNumber(radioTxPowerA) || radioTxPowerA === 0) { + if (isNumber(radioTxPowerB) || radioTxPowerB === 0) { + if (Number(modulationParametersA?.txMax!)) { + if (Number(modulationParametersB?.txMax!)) { + updateMandatoryParameters(true); + } + } + } + } else updateMandatoryParameters(false); + } + } + }; + + useEffect(() => { + checkMandatoryParameters(); + }, [radioModelA, radioModelB, radioModulationA, radioModulationB, radioTxPowerA, radioTxPowerB, modulationParametersA?.txMax, modulationParametersB?.txMax]); + + + + const handleDialogClose = () => { + resetAdapativeModulationTableProcessing(); + setOpenDialog(false); + + }; + + const setModulationParameters = (modulationA: string | null, modulationB: string | null) => { + updateRadioModulation(modulationA, modulationB); + + radioParameters.forEach(async (element: Radio) => { + if (radioModelA !== null && radioModelA === element.modelName) { + Object.entries(element.operationalParameters?.modulations!).forEach((elementModulation) => { + if (elementModulation[0] === modulationA) { + updateModulationParameters(elementModulation[1] as any, null); + } + }); + } + if (radioModelB !== null && radioModelB === element.modelName) { + Object.entries(element.operationalParameters?.modulations!).forEach((elementModulation) => { + if (elementModulation[0] === modulationB) { + updateModulationParameters(null, elementModulation[1] as any); + } + }); + } + }); + }; + + + const selectModulation = async (modulationA: string | null, modulationB: string | null) => { + setModulationParameters(modulationA ? modulationA : modulationB, modulationB ? modulationB : modulationA); + }; + const updateTxPower = async (txPowerA: number | null, txPowerB: number | null) => { + await UpdateTxPower(txPowerA, txPowerB); + }; + + const updateADMModulation = (modulationList: string[]) => { + let admList: { modulation: string; parameter: Modulation | null }[] = []; + radioParameters.forEach(y => { + if (y.modelName === radioModelA) { + modulationList.forEach(modulation => { + let entry: { modulation: string; parameter: Modulation | null } = { modulation: '', parameter: null }; + entry.modulation = modulation; + const parameter: any = y.operationalParameters?.modulations!; + if (parameter[modulation]) { + entry.parameter = parameter[modulation]; + admList.push(entry); + } + }); + } + }); + updateADMlist(admList); + }; + return ( +
+ + + + Site A + + Site B + + + + + + + --Radio-- + + { + radioModelA === '' && *Required + } + + + + --Radio-- + + { + radioModelB === '' && *Required + } + + + + + + + + + + + + + --Reference Modulation-- + + { + radioModulationA === '' && *Required + } + + + + --Reference Modulation-- + + { + radioModulationB === '' && *Required + } + + + + + + + + + + + + +
+ {isOpenDialog && + <> + {processing ? + + + + + + Adaptive Modulation + + + + + + + +
+ +

Processing ...

+
+
+
+
+
+ : + <> + {admTableStatus === 200 ? + + + + + + : +
+ + + + Adaptive Modulation + + + + + + + + + + + +
+ + } + + } + + } + +
+ + + { + updateTxPower(Number(e.target.value), null); + }} + /> + { + updateTxPower(null, Number(e.target.value)); + }} + /> + +
+
+ + + + + + + + + + + +
+ +
+ + + + + + + + + + +
+
+
+ + ); + +}); + +export default RadioView; \ No newline at end of file diff --git a/features/sdnr/odlux/odlux/apps/microwaveApp/src/linkCalculator/components/textFieldwithAdornment.tsx b/features/sdnr/odlux/odlux/apps/microwaveApp/src/linkCalculator/components/textFieldwithAdornment.tsx new file mode 100644 index 0000000..346200d --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/microwaveApp/src/linkCalculator/components/textFieldwithAdornment.tsx @@ -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 React, { FC } from 'react'; + +import Input, { InputProps } from '@mui/material/Input'; +import FormControl from '@mui/material/FormControl'; +import InputLabel from '@mui/material/InputLabel'; + +import FormHelperText from '@mui/material/FormHelperText'; + +type TextFieldProps = InputProps & { + label: string; + andornmentUnit: string; + error: boolean; + errorText: string; + +}; + +export const TextFieldwithAdornment : FC = (props: TextFieldProps) => { + + const { id, label, errorText, error, style, andornmentUnit, ...otherProps } = props; + + + return ( + + {label} + + {errorText} + + ); +}; \ No newline at end of file diff --git a/features/sdnr/odlux/odlux/apps/microwaveApp/src/linkCalculator/components/waveguide.tsx b/features/sdnr/odlux/odlux/apps/microwaveApp/src/linkCalculator/components/waveguide.tsx new file mode 100644 index 0000000..c368c2a --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/microwaveApp/src/linkCalculator/components/waveguide.tsx @@ -0,0 +1,328 @@ + +/** + * ============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 React, { FC, useEffect, useState } from 'react'; + +import makeStyles from '@mui/styles/makeStyles'; + +import { useApplicationDispatch, useSelectApplicationState } from '../../../../../framework/src/flux/connect'; + + +import { UpdateNewWaveguideParametersAction, waveguideMandatoryAction } from '../actions/waveguideActions'; +import { Waveguide } from '../model/waveguide'; +import { TextFieldwithAdornment } from './textFieldwithAdornment'; +import Stack from '@mui/material/Stack'; +import Typography from '@mui/material/Typography'; +import FormControl from '@mui/material/FormControl'; +import InputLabel from '@mui/material/InputLabel'; +import Select from '@mui/material/Select'; +import MenuItem from '@mui/material/MenuItem'; +import FormHelperText from '@mui/material/FormHelperText'; +import TextField from '@mui/material/TextField'; + + + + +const waveguideStyles = makeStyles({ + siteLabel: { + marginTop: '10px', + }, + column: { + width: '220px', + }, + container: { + display: 'flex', flexDirection: 'column', width: 'column', + }, + component: { + flexDirection: 'row', justifyContent: 'space-around', + }, +}); + + + + +const WaveguideView: FC = (() => { + const [enterWaveguideLengthA, setEnterWaveguideLengthA] = useState(false); + const [enterWaveguideLengthB, setEnterWaveguideLengthB] = useState(false); + + const waveguideLossA = useSelectApplicationState(state => state.microwave.waveguide.waveguideLossA); + const waveguideLossB = useSelectApplicationState(state => state.microwave.waveguide.waveguideLossB); + const waveguideSiteA = useSelectApplicationState(state => state.microwave.waveguide.waveguideNameA); + const waveguideSiteB = useSelectApplicationState(state => state.microwave.waveguide.waveguideNameB); + const waveguidelengthA = useSelectApplicationState(state => state.microwave.waveguide.waveguideLengthDisplayA); + const waveguidelengthB = useSelectApplicationState(state => state.microwave.waveguide.waveguideLengthDisplayB); + const waveguideTypeA = useSelectApplicationState(state => state.microwave.waveguide.waveguideTypeA); + const waveguideTypeB = useSelectApplicationState(state => state.microwave.waveguide.waveguideTypeB); + const waveguideParameters = useSelectApplicationState(state => state.microwave.waveguide.waveguideParameters); + const waveguideLengthACalculate = useSelectApplicationState(state => state.microwave.waveguide.waveguideLengthACalculate); + const waveguideLengthBCalculate = useSelectApplicationState(state => state.microwave.waveguide.waveguideLengthBCalculate); + const waveguideNameList = useSelectApplicationState(state => state.microwave.waveguide.waveguideNameList); + + const dispatch = useApplicationDispatch(); + + const updateMandatoryParameters = (mandatoryParameters: boolean) => dispatch(new waveguideMandatoryAction(mandatoryParameters)); + const updateWaveguideParameters = (waveguideParametersA: Waveguide | null, waveguideParametersB: Waveguide | null) => dispatch(new UpdateNewWaveguideParametersAction(waveguideParametersA, waveguideParametersB)); + + const classes = waveguideStyles(); + const updateWaveguide = async (waveguideA: string | null, waveguideB: string | null) => { + let waveguideAParameters: Waveguide; + let waveguideBParameters: Waveguide; + + waveguideParameters.forEach(async (element: Waveguide) => { + if (waveguideA !== null && waveguideA === element.modelName) { + + waveguideAParameters = element; + await updateWaveguideParameters(waveguideAParameters, waveguideBParameters); + + if (element.operationalParameters?.type === 'rigid') + setEnterWaveguideLengthA(false); + else + setEnterWaveguideLengthA(true); + } + if (waveguideB !== null && waveguideB === element.modelName) { + waveguideBParameters = element; + await updateWaveguideParameters(waveguideAParameters, waveguideBParameters); + + if (element.operationalParameters?.type === 'rigid') setEnterWaveguideLengthB(false); + else { + setEnterWaveguideLengthB(true); + } + } + }); + + }; + + const onChangeWaveguideLength = async (waveguideLengthA: number | null, waveguideLengthB: number | null) => { + + + let waveguideLengthAstate: number = waveguidelengthA; + let waveguideLengthBstate: number = waveguidelengthB; + let waveguideLengthACalculated = waveguideLengthACalculate; + let waveguideLengthBCalculated = waveguideLengthBCalculate; + if (waveguideLengthA && waveguideLengthA !== waveguideLengthAstate) { + + waveguideLengthAstate = waveguideLengthA; + waveguideLengthACalculated = waveguideLengthA; + + } + if (waveguideLengthB && waveguideLengthB !== waveguideLengthBstate) { + waveguideLengthBstate = waveguideLengthB; + waveguideLengthBCalculated = waveguideLengthB; + } + type Subset = { + [attr in keyof Waveguide]?: Waveguide[attr] extends object ? Subset : Waveguide[attr]; + }; + type Nested = { + [value in keyof Subset]?: Waveguide[value] extends object ? Nested : Subset; + }; + let waveguideAParameters: Nested | null = null; + let waveguideBParameters: Nested | null = null; + waveguideParameters.forEach(x => { + if (x.modelName === waveguideSiteA) { + + waveguideAParameters = { ...x, operationalParameters: { length: waveguideLengthAstate, type: x.operationalParameters?.type }, calculationParameters: { waveguideLength: waveguideLengthACalculated } }; + + } + + if (x.modelName === waveguideSiteB) { + + waveguideBParameters = { ...x, operationalParameters: { length: waveguideLengthBstate, type: x.operationalParameters?.type }, calculationParameters: { waveguideLength: waveguideLengthBCalculated } }; + + } + }); + + updateWaveguideParameters(waveguideAParameters, waveguideBParameters); + + }; + + const checkMandatoryParameters = () => { + if (waveguideTypeA === 'rigid') { + if (waveguidelengthA !== 0) { + updateMandatoryParameters(true); + } else updateMandatoryParameters(false); + } + if (waveguideTypeB === 'rigid') { + if (waveguidelengthB !== 0) { + updateMandatoryParameters(true); + } else updateMandatoryParameters(false); + } + if (waveguideTypeA === 'flexible_twistable') { + if (waveguideTypeB === 'flexible_twistable') { + updateMandatoryParameters(true); + } + } + }; + + useEffect(() => { + checkMandatoryParameters(); + + }, [waveguidelengthA, waveguidelengthB]); + + useEffect(() => { + if (waveguideTypeA === 'rigid') { + setEnterWaveguideLengthA(false); + } else setEnterWaveguideLengthA(true); + if (waveguideTypeB === 'rigid') { + setEnterWaveguideLengthB(false); + } else setEnterWaveguideLengthB(true); + checkMandatoryParameters(); + + }, [waveguideSiteA, waveguideSiteB]); + + + return ( + +
+ + + + Site A + Site B + + + + + + + --Waveguide-- + + { + waveguideSiteA === '' && *Required + } + + + + + --Waveguide-- + + { + waveguideSiteB === '' && *Required + } + + + + + + + + { + + onChangeWaveguideLength(Number(e.target.value), null); + }} + /> + { + + onChangeWaveguideLength(null, Number(e.target.value)); + }} + /> + + + + + + +
+ + ); + +}); + +export default WaveguideView; \ No newline at end of file diff --git a/features/sdnr/odlux/odlux/apps/microwaveApp/src/linkCalculator/handlers/antennaHandler.ts b/features/sdnr/odlux/odlux/apps/microwaveApp/src/linkCalculator/handlers/antennaHandler.ts new file mode 100644 index 0000000..2e0dd15 --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/microwaveApp/src/linkCalculator/handlers/antennaHandler.ts @@ -0,0 +1,104 @@ +/** + * ============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 { IActionHandler } from '../../../../../framework/src/flux/action'; +import { antennaMandatoryAction, UpdateAntennaAction, UpdateAntennaDBAction } from '../actions/antennaActions'; +import { UpdateCalculationResultAction } from '../actions/commonActions'; +import { FirstMandatoryCheckAction } from '../actions/errorAction'; +import { ResetAction, UpdateDeviceListsOnBandChangeAction, UpdateDevicesOnFirstLoad } from '../actions/radioActions'; +import { ResetFormAction } from '../actions/viewAction'; +import { Antenna } from '../model/antenna'; + +export type antennaState = { + antenna: Antenna[]; + antennaIdSiteA: number; + antennaIdSiteB: number; + eirpA: number; + eirpB: number; + antennaGainA: number; + antennaGainB: number; + antennaNameA: string; + antennaNameB: string; + antennaNameList: string[]; + antennaMandatoryParameters: boolean; + antennaHeightA: number; + antennaHeightB: number; + +}; + +const initialState: antennaState = { + antennaIdSiteA: 0, + antennaIdSiteB: 0, + antenna: [], + eirpA: 0, + eirpB: 0, + antennaGainA: 0, + antennaGainB: 0, + antennaNameA: '', + antennaNameB: '', + antennaNameList: [], + antennaMandatoryParameters: true, + antennaHeightA: 0, + antennaHeightB: 0, + +}; + +export const AntennaHandler: IActionHandler = (state = initialState, action) => { + if (action instanceof UpdateAntennaAction) { + if (action.antennaA && action.antennaA.operationalParameters) { + state = { ...state, antennaNameA: action.antennaA.modelName, antennaGainA: action.antennaA.operationalParameters.gain, antennaIdSiteA: action.antennaA.id }; + } + if (action.antennaB && action.antennaB.operationalParameters) { + state = { ...state, antennaNameB: action.antennaB.modelName, antennaGainB: action.antennaB.operationalParameters.gain, antennaIdSiteB: action.antennaB.id }; + } + } else if (action instanceof ResetFormAction) { + state = Object.assign({}, initialState, { antennaMandatoryParameters: false }); + } else if (action instanceof UpdateAntennaDBAction) { + state = Object.assign({}, state, { antenna: action.antenna, antennaNameList: action.antenna.map(x => { return x.modelName; }) }); + } else if (action instanceof FirstMandatoryCheckAction) { + if (state.antennaNameA !== '' && state.antennaNameB !== '' && state.antennaGainA !== 0 && state.antennaGainB !== 0) { + state = Object.assign({}, state, { ...state, antennaMandatoryParameters: true }); + } else state = Object.assign({}, state, { antennaMandatoryParameters: false }); + } else if (action instanceof antennaMandatoryAction) { + state = Object.assign({}, state, { antennaMandatoryParameters: action.antennaMandatoryParameters }); + } else if (action instanceof ResetAction) { + state = { ...state, antennaNameA: '', antennaNameB: '', antennaIdSiteA: 0, antennaIdSiteB: 0, antennaGainA: 0, antennaGainB: 0 }; + } else if (action instanceof UpdateDevicesOnFirstLoad) { + + state = { ...state, antennaNameList: action.antennas.map(e => e.modelName), antenna: action.antennas, + antennaHeightA: action.linkAttributes.siteA.radioAntenna?.operationalParameters?.agl!, + antennaHeightB: action.linkAttributes.siteB.radioAntenna?.operationalParameters?.agl! }; + action.antennas.map(antenna => { + + if (antenna.modelName === action.linkAttributes.siteA.radioAntenna?.modelName) { + state = { ...state, antennaNameA: antenna.modelName, antennaGainA: antenna.operationalParameters?.gain!, antennaIdSiteA: antenna.id }; + } + if (antenna.modelName === action.linkAttributes.siteB.radioAntenna?.modelName) { + state = { ...state, antennaNameB: antenna.modelName, antennaGainB: antenna.operationalParameters?.gain!, antennaIdSiteB: antenna.id }; + } + }); + } else if (action instanceof UpdateDeviceListsOnBandChangeAction) { + state = { ...state, antennaNameList: action.antennas.map(e => e.modelName), antenna: action.antennas }; + } else if (action instanceof UpdateCalculationResultAction) { + if (action.result.linkBudget) { + state = Object.assign({}, state, { ...state, eirpA: action.result.linkBudget.eirpA, eirpB: action.result.linkBudget.eirpB }); + } + } + + return state; +}; \ No newline at end of file diff --git a/features/sdnr/odlux/odlux/apps/microwaveApp/src/linkCalculator/handlers/atmosphericLossHandler.ts b/features/sdnr/odlux/odlux/apps/microwaveApp/src/linkCalculator/handlers/atmosphericLossHandler.ts new file mode 100644 index 0000000..f9c612d --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/microwaveApp/src/linkCalculator/handlers/atmosphericLossHandler.ts @@ -0,0 +1,91 @@ +/** + * ============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 { IActionHandler } from '../../../../../framework/src/flux/action'; +import { attenuationMandatoryParametersAction, UpdateAttenuationMethodAction, UpdateRainMethodAction, UpdateRainValAction, UpdateWorstMonthAction } from '../actions/atmosphericLossAction'; +import { UpdateCalculationResultAction } from '../actions/commonActions'; +import { FirstMandatoryCheckAction } from '../actions/errorAction'; +import { UpdateDevicesOnFirstLoad } from '../actions/radioActions'; +import { ResetFormAction } from '../actions/viewAction'; + + +export type atmosphericLossState = { + rainVal: number; + rainAtt: number; + absorptionWater: number; + absorptionOxygen: number; + month: string; + fsl: number; + rainMethod: string; + attenuationMethod: string; + worstMonth: boolean; + attenuationMandatoryParameters: boolean; +}; + +const initialState: atmosphericLossState = { + fsl: 0, + rainVal: 0, + rainAtt: 0, + absorptionWater: 0, + absorptionOxygen: 0, + month: '', + rainMethod: '', + attenuationMethod: '', + worstMonth: false, + attenuationMandatoryParameters: true, + +}; +export const AtmosphericLossHandler: IActionHandler = (state = initialState, action) => { + + if (action instanceof UpdateRainValAction) { + state = Object.assign({}, state, { rainVal: action.rainVal }); + } else if (action instanceof ResetFormAction) { + state = Object.assign({}, initialState, { attenuationMandatoryParameters:false }); + } else if (action instanceof UpdateRainMethodAction) { + state = Object.assign({}, state, { rainMethod: action.rainMethod, rainVal: 0, rainAtt: 0 }); + } else if (action instanceof UpdateAttenuationMethodAction) { + state = Object.assign({}, state, { attenuationMethod: action.attenuationMethod, absorptionWater: 0, absorptionOxygen: 0 }); + } else if (action instanceof UpdateWorstMonthAction) { + state = Object.assign({}, state, { worstMonth: action.worstMonth }); + } else if (action instanceof attenuationMandatoryParametersAction) { + state = Object.assign({}, state, { attenuationMandatoryParameters: action.attenuationMandatoryParameters }); + } else if (action instanceof UpdateCalculationResultAction) { + if (action.result.rainLoss) { + state = Object.assign({}, state, { rainVal: action.result.rainLoss.rainFall.rainrate.toFixed(2), rainAtt: action.result.rainLoss.rainAttenuation, month: action.result.rainLoss.rainFall.period }); + } + if (action.result.absorptionLoss) { + state = Object.assign({}, state, { absorptionOxygen: action.result.absorptionLoss.oxygenLoss, absorptionWater: action.result.absorptionLoss.oxygenLoss }); + } + if (action.result.freeSpaceLoss) { + state = Object.assign({}, state, { fsl: action.result.freeSpaceLoss.fspl }); + } + } else if (action instanceof UpdateDevicesOnFirstLoad) { + state = Object.assign({}, state, { + rainMethod: action.linkAttributes.operationalParameters.rainModel == null ? state.rainMethod : action.linkAttributes.operationalParameters.rainModel, + attenuationMethod: action.linkAttributes.operationalParameters.absorptionMethod == null ? state.attenuationMethod : action.linkAttributes.operationalParameters.absorptionMethod, + worstMonth: action.linkAttributes.operationalParameters.calculationPeriod === 'ANNUAL' ? false : true, + }); + + } else if (action instanceof FirstMandatoryCheckAction) { + if (state.attenuationMethod !== '' && state.rainMethod !== '' ) { + state = Object.assign({}, state, { attenuationMandatoryParameters : true }); + } else state = Object.assign({}, state, { attenuationMandatoryParameters : false }); + } + return state; + +}; diff --git a/features/sdnr/odlux/odlux/apps/microwaveApp/src/linkCalculator/handlers/bandPlanHandler.ts b/features/sdnr/odlux/odlux/apps/microwaveApp/src/linkCalculator/handlers/bandPlanHandler.ts new file mode 100644 index 0000000..e7eff6f --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/microwaveApp/src/linkCalculator/handlers/bandPlanHandler.ts @@ -0,0 +1,113 @@ +/** + * ============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 { IActionHandler } from '../../../../../framework/src/flux/action'; +import { UpdateChannelListAction, UpdateChannelListLoadingAction, UpdateChannelListQueryAction, UpdateFrequencyPlans, UpdateRegionRegulatorAction, ResetChannelTableAction } from '../actions/bandPlanAction'; +import { UpdateFrequencyPlanAction } from '../actions/linkAction'; +import { UpdateBandList, UpdateRegionRegulatorListAction } from '../actions/queryActions'; +import { ResetAction, UpdateDevicesOnFirstLoad } from '../actions/radioActions'; +import { Channel, ChannelTable, FrequencyPlan, RegionRegulator } from '../model/bandPlan'; +import { RadioBand } from '../model/topologyTypes'; + + + +export type bandPlanState = { + region: RegionRegulator; + channelListQuery: Channel[]; + bandList: RadioBand[]; + channelListLoading: boolean; + regionRegulatorList: RegionRegulator[]; + allChannels: ChannelTable[]; + siteAFrequencyPlan: FrequencyPlan[]; + siteBFrequencyPlan: FrequencyPlan[]; + frequencyPlanA: 'HIGH' | 'LOW'; + frequencyPlanB: 'HIGH' | 'LOW'; + frequencyPlanProcessing: boolean; + savedChannels: ChannelTable[] ; +}; + +const initialState: bandPlanState = { + region: { + name: '', keyId: '-1', country: '', regulatorName: '', + }, + channelListQuery: [], + bandList: [], + channelListLoading: true, + allChannels: [], + regionRegulatorList: [], + siteAFrequencyPlan: [], + siteBFrequencyPlan: [], + frequencyPlanA: 'HIGH', + frequencyPlanB: 'LOW', + frequencyPlanProcessing: true, + savedChannels: [], +}; + +export const bandPlanHandler: IActionHandler = (state = initialState, action) => { + if (action instanceof UpdateBandList) { + state = Object.assign({}, state, { bandList: action.bandList }); + } else if (action instanceof UpdateRegionRegulatorListAction) { + state = Object.assign({}, state, { regionRegulatorList: action.regionRegulatorList }); + } else if (action instanceof UpdateRegionRegulatorAction) { + state = { ...state, region: action.region }; + } + if (action instanceof UpdateChannelListQueryAction) { + let table: ChannelTable[] = []; + + action.channelList.forEach(x => { + table.push({ + name: x.name, + bandwidthMHz: x.bandwidthMHz.bandwidthMHz, + centerFrequencyHigh: x.centerFrequencyHigh, + centerFrequencyLow: x.centerFrequencyLow, + availability: x.availability.name, + xPolCondition: x.xPolCondition.name, + keyId: x.keyId, + polarization: '', + }); + }); + + state = { + ...state, + channelListQuery: action.channelList!, + allChannels: table, + }; + + } else if (action instanceof UpdateChannelListLoadingAction) { + state = { ...state, channelListLoading: action.channelListLoading }; + } else if (action instanceof UpdateFrequencyPlanAction) { + state = Object.assign({}, state, { frequencyPlanA: action.frequencyPlanA, frequencyPlanB: action.frequencyPlanB, frequencyPlanProcessing: false }); + } else if (action instanceof UpdateChannelListAction) { + + state = { ...state, savedChannels: action.channels }; + } else if (action instanceof ResetAction) { + state = { ...state, savedChannels: [] }; + } else if (action instanceof UpdateDevicesOnFirstLoad) { + state.regionRegulatorList.map(x => { + if (x.keyId === action.linkAttributes.operationalParameters.bandplanKeyId) { + state = { ...state, region: x }; + } + }); + } else if (action instanceof UpdateFrequencyPlans) { + state = { ...state, siteAFrequencyPlan: action.siteAFrequencyPlan, siteBFrequencyPlan: action.siteBFrequencyPlan }; + } else if (action instanceof ResetChannelTableAction) { + state = { ...state, channelListQuery : [], allChannels : [] }; + } + + return state; +}; \ No newline at end of file diff --git a/features/sdnr/odlux/odlux/apps/microwaveApp/src/linkCalculator/handlers/errorHandler.ts b/features/sdnr/odlux/odlux/apps/microwaveApp/src/linkCalculator/handlers/errorHandler.ts new file mode 100644 index 0000000..8e06060 --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/microwaveApp/src/linkCalculator/handlers/errorHandler.ts @@ -0,0 +1,57 @@ +/** + * ============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 { UpdateAttenuationMethodErrorAction, UpdateFrequencyErrorAction, UpdateLatitudeErrorAction, UpdateLongitudeErrorAction, UpdateRainMethodErrorAction } from '../actions/errorAction'; +import { IActionHandler } from '../../../../../framework/src/flux/action'; + +export type errorState = { + latitude1Error: string | null; + latitude2Error: string | null; + longitude1Error: string | null; + longitude2Error: string | null; + frequencyError : string | null; + rainMethodError: string | null; + attenuationMethodError : string | null; +}; + +const initialState: errorState = { + latitude1Error: '', + latitude2Error: '', + longitude1Error: '', + longitude2Error: '', + frequencyError : '', + rainMethodError: '', + attenuationMethodError : '', +}; + +export const ErrorHandler: IActionHandler = (state = initialState, action) => { + if (action instanceof UpdateLatitudeErrorAction) { + state = Object.assign({}, state, { latitude1Error: action.error1, latitude2Error: action.error2 }); + } else if (action instanceof UpdateLongitudeErrorAction) { + state = Object.assign({}, state, { longitude1Error: action.error1, longitude2Error: action.error2 }); + } else if (action instanceof UpdateFrequencyErrorAction) { + state = Object.assign({}, state, { frequencyError: action.error }); + } else if (action instanceof UpdateRainMethodErrorAction) { + state = Object.assign({}, state, { rainMethodError: action.error }); + } else if (action instanceof UpdateAttenuationMethodErrorAction) { + state = Object.assign({}, state, { attenuationMethodError: action.error }); + } + + return state; +}; diff --git a/features/sdnr/odlux/odlux/apps/microwaveApp/src/linkCalculator/handlers/linkHandler.ts b/features/sdnr/odlux/odlux/apps/microwaveApp/src/linkCalculator/handlers/linkHandler.ts new file mode 100644 index 0000000..2cbfed8 --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/microwaveApp/src/linkCalculator/handlers/linkHandler.ts @@ -0,0 +1,57 @@ +/** + * ============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 { IActionHandler } from '../../../../../framework/src/flux/action'; +import { UpdateDistanceAction, UpdatePolAction } from '../actions/linkAction'; +import { UpdateDevicesOnFirstLoad } from '../actions/radioActions'; +import { ResetFormAction } from '../actions/viewAction'; + + +export type linkState = { + + polarization: 'HORIZONTAL' | 'VERTICAL' | null; + distance: number; + linkId: number; +}; + +const initialState: linkState = { + + distance: 0, + polarization: null, + linkId:0, +}; + +export const LinkHandler: IActionHandler = (state = initialState, action) => { + + + if (action instanceof UpdateDistanceAction) { + state = Object.assign({}, state, { distance: action.distance }); + } else if (action instanceof UpdatePolAction) { + state = Object.assign({}, state, { polarization: action.polarization }); + } else if (action instanceof ResetFormAction) { + state = Object.assign({}, initialState, {}); + } else if (action instanceof UpdateDevicesOnFirstLoad) { + state = Object.assign({}, state, { + distance: action.linkAttributes.lengthKm, + polarization : action.linkAttributes.operationalParameters.rainPolarity, + linkId: action.linkAttributes.id, + }); + } + + return state; +}; \ No newline at end of file diff --git a/features/sdnr/odlux/odlux/apps/microwaveApp/src/linkCalculator/handlers/linkTableHandler.ts b/features/sdnr/odlux/odlux/apps/microwaveApp/src/linkCalculator/handlers/linkTableHandler.ts new file mode 100644 index 0000000..b501f52 --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/microwaveApp/src/linkCalculator/handlers/linkTableHandler.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 { LinkDetails } from '../model/linkTable'; +export interface ILinkTableState extends IExternalTableState { } + +// create elastic search material data fetch handler +const linkTableSearchHandler = createSearchDataHandler('microwave-link', true, { 'type': 'microwave' }); + +export const { + actionHandler: linkTableActionHandler, + createActions: createLinkTableActions, + createProperties: createLinkTableProperties, + createPreActions: createLinkTablePreActions, + reloadAction: linkTableReloadAction, + + // set value action, to change a value +} = createExternal(linkTableSearchHandler, appState => appState.microwave.linkTable); + diff --git a/features/sdnr/odlux/odlux/apps/microwaveApp/src/linkCalculator/handlers/queryHandler.ts b/features/sdnr/odlux/odlux/apps/microwaveApp/src/linkCalculator/handlers/queryHandler.ts new file mode 100644 index 0000000..dd0f502 --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/microwaveApp/src/linkCalculator/handlers/queryHandler.ts @@ -0,0 +1,83 @@ +/** + * ============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 { IActionHandler } from '../../../../../framework/src/flux/action'; + +import { UpdateModelTypesAction } from '../actions/queryActions'; +import { UpdateDevicesOnFirstLoad } from '../actions/radioActions'; +import { linkBeingSavedAction, linkSavedSuccessfulAction } from '../actions/saveLinkAction'; + +import { Link } from '../model/link'; +import { ModelType } from '../model/topologyTypes'; + +export type queryState = { + + modelTypeList: string[]; + linkAttributes: Link; + linkSave: any; + modelTypes:ModelType[]; + savingComplete: boolean; + linkSaving: boolean; +}; + +const initialState: queryState = { + + modelTypeList: [], + linkAttributes: { + id: 0, name: '', type: '', operator: '', + lengthKm: 0, + siteA: { + lat: 0, lon: 0, id: 0, name: '', amslM: 0, azimuthDeg: 0, tiltDeg: 0, + radioAntenna: { modelId: 0, id: 0, modelName: '', operationalParameters: { agl: 0 }, gainDb: 0, tiltDeg: 0 }, + radio: { id: 0, modelId: 0, modelName: '', operationalParameters: { transmissionPower: null, modulationType: '', enabledAdmModulations: [] } }, + waveguide: { modelId: 0, id: 0, modelName: '', type: '', lengthM: 0, lossDbPerM: 0, operationalParameters: { waveguideLength: 0 } }, + + }, + siteB: { + lat: 0, lon: 0, id: 0, name: '', amslM: 0, azimuthDeg: 0, tiltDeg: 0, + radioAntenna: { id: 0, modelId: 0, modelName: '', operationalParameters: { agl: 0 }, gainDb: 0, tiltDeg: 0 }, + radio: { id: 0, modelId: 0, modelName: '', operationalParameters: { transmissionPower: null, modulationType: '', enabledAdmModulations: [] } }, + waveguide: { modelId: 0, id: 0, modelName: '', type: '', lengthM: 0, lossDbPerM: 0, operationalParameters: { waveguideLength: 0 } }, + + }, + operationalParameters: { bandKeyId: '0', rainPolarity: '', rainModel: '', absorptionMethod: '', calculationPeriod: '', rainRate: 0, inheritedFrequencyPlanA:'', inheritedFrequencyPlanB:'', bandplanKeyId:'0', selectedChannelList:[] }, + + }, + linkSave: [], + + modelTypes: [{ + name: '', id: -1, description: '', + }], + savingComplete: false, + linkSaving: false, +}; + +export const QueryHandler: IActionHandler = (state = initialState, action) => { + + if (action instanceof UpdateDevicesOnFirstLoad) { + state = Object.assign({}, state, { linkAttributes: action.linkAttributes }); + } else if (action instanceof linkSavedSuccessfulAction) { + state = Object.assign({}, state, { linkSave: action.saved, savingComplete: true, linkSaving: false }); + } else if (action instanceof UpdateModelTypesAction) { + state = Object.assign({}, state, { modelTypes: action.ModelTypes }); + } else if (action instanceof linkBeingSavedAction) { + state = Object.assign({}, state, { linkSaving: action.saving, savingComplete: false }); + } + + return state; +}; diff --git a/features/sdnr/odlux/odlux/apps/microwaveApp/src/linkCalculator/handlers/radioHandler.ts b/features/sdnr/odlux/odlux/apps/microwaveApp/src/linkCalculator/handlers/radioHandler.ts new file mode 100644 index 0000000..1251b1e --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/microwaveApp/src/linkCalculator/handlers/radioHandler.ts @@ -0,0 +1,327 @@ +/** + * ============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 { IActionHandler } from '../../../../../framework/src/flux/action'; +import { AdaptiveModulationTable } from '../model/adaptiveModulationTable'; +import { UpdateCalculationResultAction } from '../actions/commonActions'; +import { FirstMandatoryCheckAction } from '../actions/errorAction'; +import { + frequencyMandatoryParametersAction, radioBandwidthAction, radioMandatoryParametersAction, ResetAction, + UpdateDeviceListsOnBandChangeAction, UpdateDevicesOnFirstLoad, UpdateEnabeldAdaptiveModulations, + UpdateFrequencyAction, UpdateModulationAction, UpdateModulationListAction, UpdateRadioAction, UpdateRadioEverything, UpdateRadioIdAction, + UpdateRadioListAction, UpdateRadioParametersAction, UpdateModulationParametersAction, UpdateRxPowerAction, UpdateRxSensitivityAction, UpdateSomAction, + UpdateTxPowerAction, + UpdateTotalBandwidthAction, +} from '../actions/radioActions'; +import { Modulation } from '../model/modulation'; +import { Radio } from '../model/radio'; +import { ResetFormAction } from '../actions/viewAction'; +import { UpdateAdaptiveModulationProcessing, UpdateAdaptiveModulationTableAction } from '../actions/adaptiveModulationAction'; + +export type radioState = { + systemOperatingMarginA: number; + systemOperatingMarginB: number; + txPowerA: number; + txPowerB: number; + thresholdBER3A: number | null; + thresholdBER6A: number | null; + thresholdBER3B: number | null; + thresholdBER6B: number | null; + rxPowerA: number; + rxPowerB: number; + band: { + frequency: number; + keyId:string; + }; + radioNameA: string; + radioNameB: string; + radioMandatoryParameters: boolean; + frequencyMandatoryParameters: boolean; + radioParameters: Radio[]; + radioBandwidthA: number; + radioBandwidthB: number; + modulationListA: string[]; + modulationListB: string[]; + radioNameList: string[]; + modulationA: string; + modulationB: string; + radioIdSiteA: number; + radioIdSiteB: number; + modulationParametersA: Modulation | null; + modulationParametersB: Modulation | null; + adaptiveModulationTableAtoB: AdaptiveModulationTable[] | null | undefined; + adaptiveModulationTableBtoA: AdaptiveModulationTable[] | null | undefined; + enabledAdaptiveModulations: string[]; + processing: boolean; + admStatus: number; + admMessage: string; + totalBandwidthMHz: number; +}; + +const initialState: radioState = { + systemOperatingMarginA: 0, + systemOperatingMarginB: 0, + txPowerA: 0, + txPowerB: 0, + thresholdBER3A: 0, + thresholdBER6A: 0, + thresholdBER3B: 0, + thresholdBER6B: 0, + radioNameList: [], + rxPowerA: 0, + rxPowerB: 0, + band: { + frequency: 0, + keyId:'0', + }, + radioNameA: '', + radioNameB: '', + radioMandatoryParameters: true, + frequencyMandatoryParameters: true, + radioParameters: [], + radioBandwidthA: 0, + radioBandwidthB: 0, + modulationListA: [], + modulationListB: [], + modulationA: '', + modulationB: '', + radioIdSiteA: 0, + radioIdSiteB: 0, + modulationParametersA: { + capE1: '', + dfm56QAM: '', + fktb: '', + minDelay: '', + minSigBw: '', + minSigHt: '', + modDsOffset: '', + netFilterDf: '', + nonMinDelay: '', + nonMinSigBw: '', + nonMinSigHt: '', + rslDist: '', + rslMin: '', + rxMin: '', + rxThr3BER: 0, + rxThr6BER: 0, + throughput: '', + txMax: '', + txMin: '', + xpif: '', + }, + modulationParametersB: { + capE1: '', + dfm56QAM: '', + fktb: '', + minDelay: '', + minSigBw: '', + minSigHt: '', + modDsOffset: '', + netFilterDf: '', + nonMinDelay: '', + nonMinSigBw: '', + nonMinSigHt: '', + rslDist: '', + rslMin: '', + rxMin: '', + rxThr3BER: 0, + rxThr6BER: 0, + throughput: '', + txMax: '', + txMin: '', + xpif: '', + }, + adaptiveModulationTableAtoB: [], + adaptiveModulationTableBtoA: [], + enabledAdaptiveModulations: [], + processing: true, + admStatus: 0, + admMessage: '', + totalBandwidthMHz: 0, +}; + + +export const RadioHandler: IActionHandler = (state = initialState, action) => { + if (action instanceof UpdateFrequencyAction) { + state = Object.assign({}, state, { band:{ frequency: action.frequency, keyId: '$' + action.frequency.toString() } }); + + } else if (action instanceof UpdateTxPowerAction) { + state = Object.assign({}, state, { txPowerA: action.txPowerA || state.txPowerA, txPowerB: action.txPowerB || state.txPowerB }); + } else if (action instanceof UpdateRxSensitivityAction) { + state = Object.assign({}, state, { + thresholdBER3A: action.thresholdBER3A == null ? state.thresholdBER3A : action.thresholdBER3A, + thresholdBER6A: action.thresholdBER6A == null ? state.thresholdBER6A : action.thresholdBER6A, + thresholdBER3B: action.thresholdBER3B == null ? state.thresholdBER3B : action.thresholdBER3B, + thresholdBER6B: action.thresholdBER6B == null ? state.thresholdBER6B : action.thresholdBER6B, + }); + } else if (action instanceof UpdateRxPowerAction) { + state = Object.assign({}, state, { rxPowerA: action.rxPowerA, rxPowerB: action.rxPowerB }); + } else if (action instanceof UpdateSomAction) { + state = Object.assign({}, state, { systemOperatingMarginA: action.somA, systemOperatingMarginB: action.somB }); + } else if (action instanceof ResetFormAction) { + state = Object.assign({}, initialState, { radioMandatoryParameters: false, frequencyMandatoryParameters: false }); + } else if (action instanceof UpdateRadioListAction) { + state = Object.assign({}, state, { radioNameList: action.radioNameList }); + } else if (action instanceof UpdateRadioAction) { + state = Object.assign({}, state, { radioNameA: action.radioNameA == null ? state.radioNameA : action.radioNameA, radioNameB: action.radioNameB == null ? state.radioNameB : action.radioNameB }); + } else if (action instanceof radioMandatoryParametersAction) { + state = Object.assign({}, state, { radioMandatoryParameters: action.radioMandatoryParameters }); + } else if (action instanceof frequencyMandatoryParametersAction) { + state = Object.assign({}, state, { frequencyMandatoryParameters: action.frequencyMandatoryParameters }); + } else if (action instanceof UpdateRadioParametersAction) { + state = Object.assign({}, state, { radioParameters: action.radioParameters }); + } else if (action instanceof radioBandwidthAction) { + state = Object.assign({}, state, { radioBandwidthA: action.radioBandwidthA == null ? state.radioBandwidthA : action.radioBandwidthA, radioBandwidthB: action.radioBandwidthB == null ? state.radioBandwidthB : action.radioBandwidthB }); + } else if (action instanceof UpdateModulationListAction) { + state = Object.assign({}, state, { modulationListA: action.modulationListA == null ? state.modulationListA : action.modulationListA, modulationListB: action.modulationListB == null ? state.modulationListB : action.modulationListB }); + } else if (action instanceof UpdateModulationAction) { + state = Object.assign({}, state, { + modulationA: action.modulationA == null ? state.modulationA : action.modulationA, + modulationB: action.modulationB == null ? state.modulationB : action.modulationB, + enabledAdaptiveModulations: [action.modulationA], + }); + } else if (action instanceof UpdateModulationParametersAction) { + state = Object.assign({}, state, { + modulationParametersA: action.modulationParametersA == null ? state.modulationParametersA : action.modulationParametersA, + modulationParametersB: action.modulationParametersB == null ? state.modulationParametersB : action.modulationParametersB, + txPowerA: action.modulationParametersA?.txMax || state.txPowerA, txPowerB: action.modulationParametersB?.txMax || state.txPowerB, + thresholdBER3A: action.modulationParametersA?.rxThr3BER || state.thresholdBER3A, thresholdBER6A: action.modulationParametersA?.rxThr6BER || state.thresholdBER6A, + thresholdBER3B: action.modulationParametersB?.rxThr3BER || state.thresholdBER3B, thresholdBER6B: action.modulationParametersB?.rxThr6BER || state.thresholdBER6B, + }); + } else if (action instanceof UpdateRadioIdAction) { + state = Object.assign({}, state, { radioIdSiteA: action.radioIdSiteA == null ? state.radioIdSiteA : action.radioIdSiteA, radioIdSiteB: action.radioIdSiteB == null ? state.radioIdSiteB : action.radioIdSiteB }); + } else if (action instanceof UpdateAdaptiveModulationTableAction) { + state = Object.assign({}, state, { adaptiveModulationTableAtoB: action.adaptiveModulationTableAtoB, adaptiveModulationTableBtoA: action.adaptiveModulationTableBtoA, admMessage: action.message, admStatus: action.status, processing: false }); + } else if (action instanceof ResetAction) { + state = { + ...state, radioIdSiteA: 0, radioIdSiteB: 0, radioNameA: '', radioNameB: '', radioBandwidthA: 0, + radioBandwidthB: 0, modulationA: '', modulationB: '', rxPowerA: 0, rxPowerB: 0, txPowerA: 0, txPowerB: 0, thresholdBER3A: 0, + thresholdBER3B: 0, thresholdBER6A: 0, thresholdBER6B: 0, modulationListA: [], modulationListB: [], totalBandwidthMHz : 0, + }; + } else if (action instanceof UpdateDevicesOnFirstLoad) { + state = { + ...state, band:{ frequency: Number(action.linkAttributes.operationalParameters.bandKeyId.replace('$', '')), keyId: action.linkAttributes.operationalParameters.bandKeyId }, + radioNameList: action.radioList.map(e => e.modelName), + radioParameters: action.radioList, + enabledAdaptiveModulations: action.linkAttributes.siteA.radio?.operationalParameters.enabledAdmModulations!, + }; + action.radioList.map(radio => { + if (radio.modelName === action.linkAttributes.siteA.radio?.modelName) { + state = { + ...state, radioNameA: radio.modelName, + modulationListA: Object.keys(radio.operationalParameters?.modulations!), + radioIdSiteA: radio.id, + radioBandwidthA: Number(radio.operationalParameters?.bandwith), + }; + if (action.linkAttributes.siteA.radio?.operationalParameters.modulationType) { + Object.entries(radio.operationalParameters?.modulations!).map(modulationParameters => { + if (modulationParameters[0] === action.linkAttributes.siteA.radio?.operationalParameters.modulationType) { + const modulationParametersA: any = modulationParameters[1]; + state = { + ...state, + thresholdBER3A: modulationParametersA.rxThr3BER, + thresholdBER6A: modulationParametersA.rxThr6BER, + }; + } + }); + } + } + if (radio.modelName === action.linkAttributes.siteB.radio?.modelName) { + state = { + ...state, radioNameB: radio.modelName, + modulationListB: Object.keys(radio.operationalParameters?.modulations!), + radioIdSiteB: radio.id, + radioBandwidthB: Number(radio.operationalParameters?.bandwith), + }; + if (action.linkAttributes.siteB.radio?.operationalParameters.modulationType) { + Object.entries(radio.operationalParameters?.modulations!).map(modulationParameters => { + if (modulationParameters[0] === action.linkAttributes.siteB.radio?.operationalParameters.modulationType) { + const modulationParametersB: any = modulationParameters[1]; + state = { + ...state, + thresholdBER3B: modulationParametersB.rxThr3BER, + thresholdBER6B: modulationParametersB.rxThr6BER, + }; + } + }); + } + } + }); + if (action.linkAttributes.siteA.radio?.operationalParameters?.modulationType) { + state = { + ...state, + modulationA: action.linkAttributes.siteA.radio?.operationalParameters?.modulationType, + txPowerA: action.linkAttributes.siteA.radio.operationalParameters.transmissionPower!, + }; + } + if (action.linkAttributes.siteB.radio?.operationalParameters?.modulationType) { + state = { + ...state, + modulationB: action.linkAttributes.siteB.radio?.operationalParameters?.modulationType, + txPowerB: action.linkAttributes.siteB.radio.operationalParameters.transmissionPower!, + }; + } + } else if (action instanceof UpdateCalculationResultAction) { + if (action.result.linkBudget) { + state = Object.assign({}, state, { + systemOperatingMarginA: action.result.linkBudget.systemOperatingMarginA, systemOperatingMarginB: action.result.linkBudget.systemOperatingMarginB, + rxPowerA: action.result.linkBudget.receivedPowerA, rxPowerB: action.result.linkBudget.receivedPowerB, + }); + } + } else if (action instanceof FirstMandatoryCheckAction) { + if (state.band.frequency !== 0) { + state = { ...state, frequencyMandatoryParameters: true }; + } else state = Object.assign({}, state, { frequencyMandatoryParameters: false }); + if (state.radioNameA !== '' && state.radioNameB !== '' && state.modulationA !== '' && state.modulationB !== '' && state.txPowerA !== null && state.txPowerB !== null) { + state = { ...state, radioMandatoryParameters: true }; + } else state = Object.assign({}, state, { radioMandatoryParameters: false }); + } else if (action instanceof UpdateRadioEverything) { + if (action.transport.operationalParametersA) { + state = { + ...state, radioBandwidthA: Number(action.transport.operationalParametersA?.bandwith), + modulationListA: action.transport.modulationListA!, + radioIdSiteA: action.transport.radioIdSiteA!, + modulationA: initialState.modulationA, + txPowerA: initialState.txPowerA, + thresholdBER3A: initialState.thresholdBER3A, + thresholdBER6A: initialState.thresholdBER6A, + }; + } + if (action.transport.operationalParametersB) { + state = { + ...state, radioBandwidthB: Number(action.transport.operationalParametersB?.bandwith), + modulationListB: action.transport.modulationListB!, + radioIdSiteB: action.transport.radioIdSiteB!, + modulationB: initialState.modulationB, + txPowerB: initialState.txPowerB, + thresholdBER3B: initialState.thresholdBER3B, + thresholdBER6B: initialState.thresholdBER6B, + }; + } + } else if (action instanceof UpdateDeviceListsOnBandChangeAction) { + state = { ...state, radioNameList: action.radioList.map(e => e.modelName), radioParameters: action.radioList, txPowerA: 0, txPowerB: 0 }; + } else if (action instanceof UpdateEnabeldAdaptiveModulations) { + state = { ...state, enabledAdaptiveModulations: action.enabledAdaptiveModulations }; + } else if (action instanceof UpdateAdaptiveModulationProcessing) { + state = { ...state, processing: action.processing }; + } else if (action instanceof UpdateTotalBandwidthAction) { + state = { ...state, totalBandwidthMHz: action.totalBandwidthMHz }; + } + + return state; +}; diff --git a/features/sdnr/odlux/odlux/apps/microwaveApp/src/linkCalculator/handlers/rootHandler.ts b/features/sdnr/odlux/odlux/apps/microwaveApp/src/linkCalculator/handlers/rootHandler.ts new file mode 100644 index 0000000..10bddc3 --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/microwaveApp/src/linkCalculator/handlers/rootHandler.ts @@ -0,0 +1,87 @@ +/** +* ============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 { combineActionHandler } from '../../../../../framework/src/flux/middleware'; + +import { AntennaHandler, antennaState } from './antennaHandler'; +import { AtmosphericLossHandler, atmosphericLossState } from './atmosphericLossHandler'; +import { errorState } from './errorHandler'; +import { LinkHandler, linkState } from './linkHandler'; +import { QueryHandler, queryState } from './queryHandler'; +import { RadioHandler, radioState } from './radioHandler'; +import { SiteHandler, siteState } from './siteHandler'; +import { ViewHandler, viewState } from './viewHandler'; +import { WaveguideHandler, waveguideState } from './waveguideHandler'; +import { ErrorHandler } from './errorHandler'; +import { IActionHandler } from '../../../../../framework/src/flux/action'; +import { UpdateTabAction } from '../actions/commonActions'; +import { TabId } from '../model/tabId'; +import { ILinkTableState, linkTableActionHandler } from './linkTableHandler'; +import { IMap, mapHandler } from '../../lineOfSight/handlers/lineOfSightMapHandler'; +import { bandPlanHandler, bandPlanState } from './bandPlanHandler'; + +interface IMicrowaveAppStateStore { + antenna: antennaState; + atmosphere: atmosphericLossState; + link: linkState; + radio: radioState; + site: siteState; + view: viewState; + waveguide: waveguideState; + query: queryState; + error: errorState; + currentTab: TabId; + linkTable: ILinkTableState; + bandPlan: bandPlanState; + map: IMap; +} + + +declare module '../../../../../framework/src/store/applicationStore' { + interface IApplicationStoreState { + microwave: IMicrowaveAppStateStore; + } +} + +export const TabHandler: IActionHandler = (state = 'linkTable', action) => { + if (action instanceof UpdateTabAction) { + state = action.openTab; + } + return state; +}; +const appHandler = { + antenna: AntennaHandler, + atmosphere: AtmosphericLossHandler, + link: LinkHandler, + radio: RadioHandler, + site: SiteHandler, + view: ViewHandler, + waveguide: WaveguideHandler, + query: QueryHandler, + error: ErrorHandler, + currentTab: TabHandler, + linkTable: linkTableActionHandler, + map: mapHandler, + bandPlan: bandPlanHandler, +}; + +export const RootHandler = combineActionHandler(appHandler); +export default RootHandler; + + + diff --git a/features/sdnr/odlux/odlux/apps/microwaveApp/src/linkCalculator/handlers/siteHandler.ts b/features/sdnr/odlux/odlux/apps/microwaveApp/src/linkCalculator/handlers/siteHandler.ts new file mode 100644 index 0000000..00a2225 --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/microwaveApp/src/linkCalculator/handlers/siteHandler.ts @@ -0,0 +1,87 @@ +/** + * ============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 { IActionHandler } from '../../../../../framework/src/flux/action'; +import { FirstMandatoryCheckAction } from '../actions/errorAction'; +import { UpdateDevicesOnFirstLoad } from '../actions/radioActions'; +import { locationMandatoryAction, UpdateLatLonAction } from '../actions/siteAction'; +import { ResetFormAction } from '../actions/viewAction'; + + +export type siteState = { + lat1: number; + lon1: number; + lat2: number; + lon2: number; + siteIdA:number; + siteIdB:number; + locationMandatoryParameters: boolean; + siteNameA: string; + siteNameB: string; + amslA: number; + amslB: number; + azimuthA: number; + azimuthB: number; + tiltDegA: number; + tiltDegB: number; +}; + +const initialState: siteState = { + lat1: 0, + lon1: 0, + lat2: 0, + lon2: 0, + siteIdA:0, + siteIdB:0, + locationMandatoryParameters: true, + siteNameA: '', + siteNameB: '', + amslA: 0, + amslB: 0, + azimuthA: 0, + azimuthB: 0, + tiltDegA: 0, + tiltDegB: 0, +}; + +export const SiteHandler: IActionHandler = (state = initialState, action) => { + if (action instanceof UpdateLatLonAction) { + state = Object.assign({}, state, { lat1: action.lat1, lon1: action.lon1, lat2: action.lat2, lon2: action.lon2 }); + } + if (action instanceof ResetFormAction) { + state = Object.assign({}, initialState, { locationMandatoryParameters:false }); + } else if (action instanceof locationMandatoryAction) { + state = Object.assign({}, state, { locationMandatoryParameters: action.locationMandatoryParameters }); + } else if (action instanceof UpdateDevicesOnFirstLoad) { + state = Object.assign({}, state, { + lat1: action.linkAttributes.siteA.lat, lon1: action.linkAttributes.siteA.lon, + lat2: action.linkAttributes.siteB.lat, lon2: action.linkAttributes.siteB.lon, + siteIdA: action.linkAttributes.siteA.id, siteIdB:action.linkAttributes.siteB.id, + siteNameA:action.linkAttributes.siteA.name, siteNameB:action.linkAttributes.siteB.name, + amslA : action.linkAttributes.siteA.amslM, amslB: action.linkAttributes.siteB.amslM, + azimuthA : action.linkAttributes.siteA.azimuthDeg, azimuthB: action.linkAttributes.siteB.azimuthDeg, + tiltDegA: action.linkAttributes.siteA.tiltDeg, tiltDegB: action.linkAttributes.siteB.tiltDeg, + }); + } else if (action instanceof FirstMandatoryCheckAction) { + if (state.lat1 !== 0 && state.lon1 !== 0 && state.lat2 !== 0 && state.lon2 !== 0) { + state = Object.assign({}, state, { locationMandatoryParameters : true }); + } else state = Object.assign({}, state, { locationMandatoryParameters : false }); + } + + return state; +}; \ No newline at end of file diff --git a/features/sdnr/odlux/odlux/apps/microwaveApp/src/linkCalculator/handlers/viewHandler.ts b/features/sdnr/odlux/odlux/apps/microwaveApp/src/linkCalculator/handlers/viewHandler.ts new file mode 100644 index 0000000..3473923 --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/microwaveApp/src/linkCalculator/handlers/viewHandler.ts @@ -0,0 +1,56 @@ +/** + * ============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 { IActionHandler } from '../../../../../framework/src/flux/action'; +import { UpdateDevicesOnFirstLoad } from '../actions/radioActions'; +import { isCalculationServerReachableAction, PluginDoneLoadingAction, ResetFormAction, UpdateRainMethodDisplayAction } from '../actions/viewAction'; + + +export type viewState = { + formView: boolean; + reachable: boolean; + rainDisplay: boolean; + loadingComplete : boolean; + processing: boolean; +}; + +const initialState: viewState = { + formView: true, + reachable: true, + rainDisplay: false, + loadingComplete : false, + processing : true, +}; + +export const ViewHandler: IActionHandler = (state = initialState, action) => { + + if (action instanceof UpdateDevicesOnFirstLoad) { + if (action.linkAttributes) { + state = Object.assign({}, state, { formView: true }); + } + } else if (action instanceof isCalculationServerReachableAction) { + state = Object.assign({}, state, { reachable: action.reachable }); + } else if (action instanceof ResetFormAction) { + state = Object.assign({}, state, { formView: false, processing : false }); + } else if (action instanceof UpdateRainMethodDisplayAction) { + state = Object.assign({}, state, { rainDisplay: action.rainDisplay }); + } else if (action instanceof PluginDoneLoadingAction) { + state = Object.assign({}, state, { loadingComplete: action.loadingComplete, processing: false }); + } + return state; +}; \ No newline at end of file diff --git a/features/sdnr/odlux/odlux/apps/microwaveApp/src/linkCalculator/handlers/waveguideHandler.ts b/features/sdnr/odlux/odlux/apps/microwaveApp/src/linkCalculator/handlers/waveguideHandler.ts new file mode 100644 index 0000000..dc473c7 --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/microwaveApp/src/linkCalculator/handlers/waveguideHandler.ts @@ -0,0 +1,154 @@ +/** + * ============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 { IActionHandler } from '../../../../../framework/src/flux/action'; +import { UpdateCalculationResultAction } from '../actions/commonActions'; +import { FirstMandatoryCheckAction } from '../actions/errorAction'; +import { ResetAction, UpdateDeviceListsOnBandChangeAction, UpdateDevicesOnFirstLoad } from '../actions/radioActions'; +import { ResetFormAction } from '../actions/viewAction'; +import { UpdateNewWaveguideParametersAction, UpdateWaveguideIdAction, UpdatewaveguideListAction, + UpdateWaveguideLossAction, updateWaveguideNameAction, UpdateWaveguideParametersAction, updateWaveguideTypeAction, waveguideMandatoryAction } from '../actions/waveguideActions'; +import { Waveguide } from '../model/waveguide'; + + +export type waveguideState = { + waveguideLossA: number; + waveguideLossB: number; + waveguideNameA: string | null; + waveguideNameB: string | null; + waveguideMandatoryParameters: boolean; + waveguideLengthDisplayA: number; + waveguideLengthDisplayB: number; + waveguideTypeA: string; + waveguideTypeB: string; + waveguideIdSiteA: number; + waveguideIdSiteB: number; + waveguideParameters: Waveguide[]; + waveguideLengthACalculate: number ; + waveguideLengthBCalculate: number ; + waveguideNameList: string[]; +}; + +const initialState: waveguideState = { + waveguideLossA: 0, + waveguideLossB: 0, + waveguideNameA: '', + waveguideNameB: '', + waveguideMandatoryParameters: true, + waveguideLengthDisplayA: 0, + waveguideLengthDisplayB: 0, + waveguideTypeA: '', + waveguideTypeB: '', + waveguideIdSiteA: 0, + waveguideIdSiteB: 0, + waveguideParameters: [], + waveguideLengthACalculate: 0, + waveguideLengthBCalculate: 0, + waveguideNameList: [], + +}; + +export const WaveguideHandler: IActionHandler = (state = initialState, action) => { + if (action instanceof UpdateWaveguideLossAction) { + state = Object.assign({}, state, { waveguideLossA: action.waveguideLossA, waveguideLossB: action.waveguideLossB }); + } else if (action instanceof UpdatewaveguideListAction) { + state = Object.assign({}, state, { waveguideList: action.waveguideListName }); + } else if (action instanceof updateWaveguideNameAction) { + state = Object.assign({}, state, { waveguideNameA: action.waveguideNameA == null ? state.waveguideNameA : action.waveguideNameA, waveguideNameB: action.waveguideNameB == null ? state.waveguideNameB : action.waveguideNameB }); + } else if (action instanceof waveguideMandatoryAction) { + state = Object.assign({}, state, { waveguideMandatoryParameters: action.waveguideMandatoryParameters }); + } else if (action instanceof UpdateWaveguideIdAction) { + state = Object.assign({}, state, { waveguideIdSiteA: action.waveguideIdSiteA == null ? state.waveguideIdSiteA : action.waveguideIdSiteA, waveguideIdSiteB: action.waveguideIdSiteB == null ? state.waveguideIdSiteB : action.waveguideIdSiteB }); + } else if (action instanceof updateWaveguideTypeAction) { + state = Object.assign({}, state, { waveguideTypeA: action.waveguideTypeA == null ? state.waveguideTypeA : action.waveguideTypeA, waveguideTypeB: action.waveguideTypeB == null ? state.waveguideTypeB : action.waveguideTypeB }); + } else if (action instanceof UpdateWaveguideParametersAction) { + state = Object.assign({}, state, { waveguideParameters: action.waveguide }); + } else if (action instanceof ResetAction) { + state = Object.assign({}, state, { waveguideIdSiteA: 0, waveguideIdSiteB: 0, waveguideNameA: '', waveguideNameB: '', waveguideLengthDisplayA: 0, waveguideLengthDisplayB: 0, waveguideLossA: 0, waveguideLossB: 0, waveguideTypeA: '', waveguideTypeB: '' }); + } else if (action instanceof UpdateDevicesOnFirstLoad) { + + state = { ...state, waveguideNameList: action.waveguideList.map(x => x.modelName), waveguideParameters: action.waveguideList }; + if (action.linkAttributes.siteA.waveguide?.operationalParameters) { + state = { ...state, waveguideLengthACalculate : action.linkAttributes.siteA.waveguide?.operationalParameters.waveguideLength }; + + } + if (action.linkAttributes.siteB.waveguide?.operationalParameters) { + state = { ...state, waveguideLengthBCalculate : action.linkAttributes.siteB.waveguide?.operationalParameters.waveguideLength }; + } + action.waveguideList.forEach(element => { + + if (element.modelName === action.linkAttributes.siteA.waveguide?.modelName) { + state = { + ...state, waveguideTypeA: element.operationalParameters?.type!, + waveguideNameA: element.modelName, + waveguideIdSiteA: element.id, + waveguideLengthDisplayA: element.operationalParameters?.length!, + + }; + if (element.operationalParameters?.type! === 'rigid') { + state = { ...state, waveguideLengthDisplayA : action.linkAttributes.siteA.waveguide.operationalParameters?.waveguideLength === null ? element.operationalParameters?.length! : action.linkAttributes.siteA.waveguide.operationalParameters?.waveguideLength!, + }; + } else { + state = { ...state, waveguideLengthACalculate: -1, + }; + } + } + if (element.modelName === action.linkAttributes.siteB.waveguide?.modelName) { + state = { + ...state, + waveguideTypeB: element.operationalParameters?.type!, + waveguideLengthDisplayB: element.operationalParameters?.length!, + waveguideNameB: element.modelName, + waveguideIdSiteB: element.id, + }; + if (element.operationalParameters?.type! === 'rigid') { + state = { ...state, waveguideLengthDisplayB : action.linkAttributes.siteB.waveguide.operationalParameters?.waveguideLength === null ? element.operationalParameters?.length! : action.linkAttributes.siteB.waveguide.operationalParameters?.waveguideLength!, + }; + } else { + state = { ...state, waveguideLengthBCalculate:-1, + }; + } + } + }); + } else if (action instanceof UpdateDeviceListsOnBandChangeAction) { + state = { ...state, waveguideNameList: action.waveguideList.map(e => e.modelName), waveguideParameters: action.waveguideList }; + } else if (action instanceof UpdateNewWaveguideParametersAction) { + if (action.waveguideParametersA && action.waveguideParametersA.operationalParameters) { + + state = { ...state, waveguideNameA: action.waveguideParametersA?.modelName, waveguideIdSiteA: action.waveguideParametersA?.id, + waveguideLengthDisplayA: action.waveguideParametersA?.operationalParameters.length, waveguideTypeA: action.waveguideParametersA.operationalParameters.type, + waveguideLengthACalculate: action.waveguideParametersA.operationalParameters.type === 'rigid' ? action.waveguideParametersA.calculationParameters?.waveguideLength! : -1 }; + } + if (action.waveguideParametersB && action.waveguideParametersB.operationalParameters) { + state = { ...state, waveguideNameB: action.waveguideParametersB?.modelName, waveguideIdSiteB: action.waveguideParametersB?.id, + waveguideLengthDisplayB: action.waveguideParametersB?.operationalParameters.length, waveguideTypeB: action.waveguideParametersB.operationalParameters.type, + waveguideLengthBCalculate: action.waveguideParametersB.operationalParameters.type === 'rigid' ? action.waveguideParametersB.calculationParameters?.waveguideLength! : -1 }; + } + } else if (action instanceof UpdateCalculationResultAction) { + if (action.result.waveguideLoss) { + state = Object.assign({}, state, { waveguideLossA: action.result.waveguideLoss.waveguideLossA, waveguideLossB: action.result.waveguideLoss.waveguideLossB }); + } + } else if (action instanceof FirstMandatoryCheckAction) { + if (state.waveguideNameA !== '' && state.waveguideNameB !== '' && state.waveguideLengthDisplayA !== 0 && state.waveguideLengthDisplayB !== 0) { + state = Object.assign({}, state, { waveguideMandatoryParameters: true }); + } else state = Object.assign({}, state, { waveguideMandatoryParameters: false }); + } else if (action instanceof ResetFormAction) { + state = { ...initialState, waveguideMandatoryParameters:false }; + } + return state; +}; \ No newline at end of file diff --git a/features/sdnr/odlux/odlux/apps/microwaveApp/src/linkCalculator/model/adaptiveModulationInput.ts b/features/sdnr/odlux/odlux/apps/microwaveApp/src/linkCalculator/model/adaptiveModulationInput.ts new file mode 100644 index 0000000..19bdd14 --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/microwaveApp/src/linkCalculator/model/adaptiveModulationInput.ts @@ -0,0 +1,49 @@ +/** + * ============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========================================================================== + */ + +export type AdaptiveModulationInput = { + linkId: number; + linkOperationalParameters: { + bandKeyId: string; + bandplanKeyId: string; + polarization: string; + absorptionMethod: string; + calculationPeriod: string; + rainRate: number; + rainModel: string; + }; + siteA: { + radioModelId: number; + waveguideModelId: number; + radioAntennaModelId: number; + modulationType: string; + transmissionPower: number; + waveguideLength: number | null; + agl: number; + }; + siteB: { + radioModelId: number; + waveguideModelId: number; + radioAntennaModelId: number; + modulationType: string; + transmissionPower: number; + waveguideLength: number | null; + agl: number; + }; +}; + diff --git a/features/sdnr/odlux/odlux/apps/microwaveApp/src/linkCalculator/model/adaptiveModulationTable.ts b/features/sdnr/odlux/odlux/apps/microwaveApp/src/linkCalculator/model/adaptiveModulationTable.ts new file mode 100644 index 0000000..0ebf5c9 --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/microwaveApp/src/linkCalculator/model/adaptiveModulationTable.ts @@ -0,0 +1,37 @@ +/** + * ============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========================================================================== + */ + +export type AdaptiveModulationResponse = { + aToB: AdaptiveModulationTable[]; + bToA: AdaptiveModulationTable[]; +}; +export type AdaptiveModulationTable = { + modulation: string; + dataRate: number; + 'receiverThresholdBER-3': number; + 'receiverThresholdBER-6': number; + receivedSignalLevel: number; + 'linkMarginBER-3': number; + 'linkMarginBER-6': number; + 'txPowerMin':number; + 'txPowerMax':number; + 'rainAvailabilityBER-3': number; + 'rainAvailabilityBER-6' : number; + 'multipathAvailabilityBER-3': number; + 'multipathAvailabilityBER-6' : number; +}; diff --git a/features/sdnr/odlux/odlux/apps/microwaveApp/src/linkCalculator/model/antenna.ts b/features/sdnr/odlux/odlux/apps/microwaveApp/src/linkCalculator/model/antenna.ts new file mode 100644 index 0000000..b364842 --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/microwaveApp/src/linkCalculator/model/antenna.ts @@ -0,0 +1,45 @@ +/** + * ============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========================================================================== + */ + +export type Antenna = { + modelId: number; + id:number; + modelName: string; + type: { + id: number; + name: string; + description: string; + } | null; + operationalParameters: AntennaOperationalParameters | null; + calculationParameters : AntennaCalculationPrameters | null; +}; +export type AntennaOperationalParameters = { + style: string; + xpol: string; + band: number; + diameter: string; + agl: number; + amsl:number; + gain: number; + xpd: string; + ipi: string; + fbRation: string; +}; +export type AntennaCalculationPrameters = { + +}; diff --git a/features/sdnr/odlux/odlux/apps/microwaveApp/src/linkCalculator/model/bandPlan.ts b/features/sdnr/odlux/odlux/apps/microwaveApp/src/linkCalculator/model/bandPlan.ts new file mode 100644 index 0000000..308f325 --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/microwaveApp/src/linkCalculator/model/bandPlan.ts @@ -0,0 +1,84 @@ +/** + * ============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========================================================================== + */ + +export type RegionRegulator = { + country: string; + keyId: string; + name: string; + regulatorName: string; +}; + +// export type ChannelQuery = { +// data: Channel[]; +// message?: string; +// status: number; +// }; +export type Channel = { + keyId: string; + number: number; + name: string; + channelSpacing: ChannelSpacing; + bandwidthMHz: BandwidthMhz; + centerFrequencyHigh: number; + centerFrequencyLow: number; + xPolCondition: XpolCondition; + availability: Availability; + description: string; + polarization: 'HORIZONTAL' | 'VERTICAL' | 'XPOL' | ''; +}; + +export type ChannelTable = { + keyId: string; + name: string; + bandwidthMHz: number; + centerFrequencyHigh: number; + centerFrequencyLow: number; + availability: string; + xPolCondition: string; + polarization: 'HORIZONTAL' | 'VERTICAL' | 'XPOL' | ''; +}; +export type ChannelSpacing = { + id: number; + name: string; +}; +export type BandwidthMhz = { + id: number; + name: string; + bandwidthMHz: number; +}; +export type XpolCondition = { + id: number; + name: string; +}; +export type Availability = { + id: number; + name: string; +}; + +export type FrequencyPlan = { + configuration: string; + id: number; + siteId: number; + band: { + keyId: string; + name: string; + duplexspacingMHz: number; + }; + status: string; + comment: string; +}; \ No newline at end of file diff --git a/features/sdnr/odlux/odlux/apps/microwaveApp/src/linkCalculator/model/calculationResult.ts b/features/sdnr/odlux/odlux/apps/microwaveApp/src/linkCalculator/model/calculationResult.ts new file mode 100644 index 0000000..740ee5e --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/microwaveApp/src/linkCalculator/model/calculationResult.ts @@ -0,0 +1,33 @@ +/** + * ============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 { Absorption, FreeSpaceLoss, LinkBudget, RainLoss } from './topologyTypes'; +import { WaveguideLoss } from './waveguide'; + +export class CalculationResult { + rainLoss: RainLoss | undefined; + + freeSpaceLoss: FreeSpaceLoss | undefined; + + absorptionLoss: Absorption | undefined; + + waveguideLoss: WaveguideLoss | undefined; + + linkBudget: LinkBudget | undefined; + +} \ No newline at end of file diff --git a/features/sdnr/odlux/odlux/apps/microwaveApp/src/linkCalculator/model/link.ts b/features/sdnr/odlux/odlux/apps/microwaveApp/src/linkCalculator/model/link.ts new file mode 100644 index 0000000..e81fe64 --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/microwaveApp/src/linkCalculator/model/link.ts @@ -0,0 +1,89 @@ +/** + * ============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========================================================================== + */ + + + +type Site = { + lon: number; + lat: number; + id: number; + tiltDeg: number; + name: string; + amslM: number | null; + radioAntenna: Antenna | null; + azimuthDeg: number; + radio: Radio | null; + waveguide: Waveguide | null; + +}; +type LinkOperationalParameters = { + rainPolarity: string; + rainModel: string; + rainRate: number; + absorptionMethod:string; + calculationPeriod: string; + bandKeyId: string; + bandplanKeyId: string; + selectedChannelList: string[]; + inheritedFrequencyPlanA: 'INHERIT' | 'INVERTED' | ''; + inheritedFrequencyPlanB: 'INHERIT' | 'INVERTED' | ''; +}; +export type Link = { + id: number; + name: string; + operator: string; + lengthKm: number; + type: string; + siteA: Site; + siteB: Site; + operationalParameters : LinkOperationalParameters; +}; + +type Radio = { + id: number; + modelId: number; + modelName : string; + operationalParameters: { + modulationType: string; + transmissionPower: number | null; + enabledAdmModulations : string[]; + }; +}; + +type Antenna = { + gainDb: number; + id: number; + modelId: number; + modelName: string; + tiltDeg: number; + operationalParameters: { + agl : number; + }; +}; + +type Waveguide = { + id: number; + modelId: number; + modelName: string; + lengthM: number; + lossDbPerM: number; + type: string; + operationalParameters: { + waveguideLength : number; + }; +}; \ No newline at end of file diff --git a/features/sdnr/odlux/odlux/apps/microwaveApp/src/linkCalculator/model/linkTable.ts b/features/sdnr/odlux/odlux/apps/microwaveApp/src/linkCalculator/model/linkTable.ts new file mode 100644 index 0000000..31fbb1b --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/microwaveApp/src/linkCalculator/model/linkTable.ts @@ -0,0 +1,52 @@ +/** + * ============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========================================================================== + */ + + +export type LinkDetails = { + administrativeState: string; + deviceA: { id: number; nodeId: string; uuid: string }; + deviceB: { id: number; nodeId: string; uuid: string }; + id: number; + labels: []; + lifecycleState: string; + name: string; + operationalState: string; + operatorId: string; + siteA: { + areaId: string; + areaName: string; + id: number; + latitude: number; + longitude: number; + name: string; + operatorId: string; + uuid: string; + }; + siteB: { + areaId: string; + areaName: string; + id: number; + latitude: number; + longitude: number; + name: string; + operatorId: string; + uuid: string; + }; + type: string; + uuid: string; +}; \ No newline at end of file diff --git a/features/sdnr/odlux/odlux/apps/microwaveApp/src/linkCalculator/model/modulation.ts b/features/sdnr/odlux/odlux/apps/microwaveApp/src/linkCalculator/model/modulation.ts new file mode 100644 index 0000000..5c997b4 --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/microwaveApp/src/linkCalculator/model/modulation.ts @@ -0,0 +1,40 @@ +/** + * ============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========================================================================== + */ + +export type Modulation = { + capE1: string; + dfm56QAM: string; + fktb: string; + minDelay: string; + minSigBw: string; + minSigHt: string; + modDsOffset: string; + netFilterDf: string; + nonMinDelay: string; + nonMinSigBw: string; + nonMinSigHt: string; + rslDist: string; + rslMin: string; + rxMin: string; + rxThr3BER: number; + rxThr6BER: number; + throughput: string; + txMax: string; + txMin: string; + xpif: string; +}; \ No newline at end of file diff --git a/features/sdnr/odlux/odlux/apps/microwaveApp/src/linkCalculator/model/radio.ts b/features/sdnr/odlux/odlux/apps/microwaveApp/src/linkCalculator/model/radio.ts new file mode 100644 index 0000000..785fdac --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/microwaveApp/src/linkCalculator/model/radio.ts @@ -0,0 +1,62 @@ +/** + * ============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 { Modulation } from './modulation'; + +export type Radio = { + id: number; + modelId:number; + modelName: string; + type: { + id: number; + name: string; + description: string; + } | null; + operationalParameters: RadioOperationalParameters | null; + calculationParameters: RadioCalculationPrameters | null; +}; + +export type RadioOperationalParameters = { + modulations: Modulation; + C0I: string; + band: string; + bandwith: string; + cir: string; + eth: string; + lagE1: string; + lagEth: string; + mpls: string; + pdh: string; + rxMax: string; + sdh: string; + xpic: string; +}; + +export type RadioEverything = { + operationalParametersA : RadioOperationalParameters | null; + operationalParametersB : RadioOperationalParameters | null; + modulationListA: string[] | null; + modulationListB: string[] | null; + radioIdSiteA: number | null; + radioIdSiteB: number | null; +}; + +export type RadioCalculationPrameters = { + transmissionPower:number; + referenceModulation:string; +}; \ No newline at end of file diff --git a/features/sdnr/odlux/odlux/apps/microwaveApp/src/linkCalculator/model/tabId.ts b/features/sdnr/odlux/odlux/apps/microwaveApp/src/linkCalculator/model/tabId.ts new file mode 100644 index 0000000..ca4fd84 --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/microwaveApp/src/linkCalculator/model/tabId.ts @@ -0,0 +1,21 @@ +/** + * ============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========================================================================== + */ + + + +export type TabId = 'linkTable' | 'linkCalculation' | 'lineOfSight'; \ No newline at end of file diff --git a/features/sdnr/odlux/odlux/apps/microwaveApp/src/linkCalculator/model/topologyTypes.ts b/features/sdnr/odlux/odlux/apps/microwaveApp/src/linkCalculator/model/topologyTypes.ts new file mode 100644 index 0000000..db16687 --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/microwaveApp/src/linkCalculator/model/topologyTypes.ts @@ -0,0 +1,80 @@ +/** + * ============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========================================================================== + */ +// export type TopologyTypes ={ +// rainLoss : RainLoss, +// freeSpaceLoss: FreeSpaceLoss, +// absorption : Absorption +// } + +export type RainLoss = { + rainAttenuation: number; + rainFall: { + rainrate: number; + period: string; + }; +}; + + +export type FreeSpaceLoss = { + fspl: number; +}; + +export type Absorption = { + oxygenLoss: number; + waterLoss: number; + totalAbsorptionLoss: number; + period: string; +}; + + +export type LinkBudget = { + systemOperatingMarginA: number; + systemOperatingMarginB: number; + eirpA: number; + eirpB: number; + receivedPowerA: number; + receivedPowerB: number; +}; + +export type Distance = { + distanceInKm: number; +}; + +export type RadioBand = { + keyId: string; + name: string; + duplexspacingMHz: number; + centerFrequencyMHz: number; +}; + +export type Model = { + modelId: number; + id: number; + modelName: string; + operationalParameters: any; + calculationParameters: any; + type: { id: number; name: string; description: string }; + band: RadioBand; +}; + +export type ModelType = { + id: number; + name: string; + description: string; +}; + diff --git a/features/sdnr/odlux/odlux/apps/microwaveApp/src/linkCalculator/model/updateLink.ts b/features/sdnr/odlux/odlux/apps/microwaveApp/src/linkCalculator/model/updateLink.ts new file mode 100644 index 0000000..4d427b6 --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/microwaveApp/src/linkCalculator/model/updateLink.ts @@ -0,0 +1,50 @@ +/** + * ============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========================================================================== + */ + + type Site = { + modulationType: string; + transmissionPower: number; + waveguideLength: number | null; + agl: number; + radioModelId: number; + waveguideModelId: number; + radioAntennaModelId: number; + enabledAdmModulations: string[]; + + }; + type LinkOperationalParameters = { + rainPolarity: string; + rainModel: string; + rainRate: number; + absorptionMethod: string; + calculationPeriod: string; + bandKeyId: string; + bandplanKeyId: string; + selectedChannelList: SaveChannel[]; + }; + +export type UpdateLink = { + siteA: Site; + siteB: Site; + linkOperationalParameters: LinkOperationalParameters; +}; + +export type SaveChannel = { + channelKeyId : string; + channelPolarizationEnum: 'HORIZONTAL' | 'VERTICAL' | 'XPOL' | ''; +}; \ No newline at end of file diff --git a/features/sdnr/odlux/odlux/apps/microwaveApp/src/linkCalculator/model/waveguide.ts b/features/sdnr/odlux/odlux/apps/microwaveApp/src/linkCalculator/model/waveguide.ts new file mode 100644 index 0000000..7ba1e9a --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/microwaveApp/src/linkCalculator/model/waveguide.ts @@ -0,0 +1,52 @@ +/** + * ============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========================================================================== + */ +export type Waveguide = { + modelId: number; + id: number; + modelName: string; + type: { + id: number; + name: string; + description: string; + } | null; + operationalParameters: WaveguideOperationalParameters | null; + calculationParameters: WaveguideCalculationPrameters | null; +}; +export type WaveguideLoss = { + waveguideLossPerMeter: number; + waveguideLossdB: number; + waveguideName: string; + waveguideLength: number; + waveguideType: string; + waveguideLossA: number; + waveguideLossB: number; + +}; + +export type WaveguideOperationalParameters = { + band: string; + type: string; + shape: string; + length: number; + loss: string; + +}; + +export type WaveguideCalculationPrameters = { + waveguideLength: number; +}; \ No newline at end of file diff --git a/features/sdnr/odlux/odlux/apps/microwaveApp/src/linkCalculator/service/dataService.ts b/features/sdnr/odlux/odlux/apps/microwaveApp/src/linkCalculator/service/dataService.ts new file mode 100644 index 0000000..d0d81e6 --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/microwaveApp/src/linkCalculator/service/dataService.ts @@ -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========================================================================== + */ + + +// Put Button handler stuff, REST Calls +import { requestRest, requestRestExt } from '../../../../../framework/src/services/restService'; + +import { Link } from '../model/link'; +import { AdaptiveModulationInput } from '../model/adaptiveModulationInput'; +import { AdaptiveModulationResponse } from '../model/adaptiveModulationTable'; +import { Absorption, Distance, FreeSpaceLoss, LinkBudget, ModelType, RadioBand, RainLoss } from '../model/topologyTypes'; +import { WaveguideLoss } from '../model/waveguide'; +import { Channel } from '../model/bandPlan'; + +const LINKCALCULATOR_BASE_URL = '/topology/linkcalculator'; +const MICROWAVE_URL = '/topology/microwave'; +const NETWORK_URL = MICROWAVE_URL + '/network'; + +const dataService = { + bandPlanRegulators : () => { + return requestRest((MICROWAVE_URL + '/bandplans/')); + }, + + getModels : (bandKeyId: string, modelTypeName: string) => { + const result = requestRest(LINKCALCULATOR_BASE_URL + '/query/modellist/' + modelTypeName + '/' + bandKeyId); + return result; + }, + + rainAttenuation : (lat1: any, lon1: any, lat2: any, lon2: any, bandKeyId: string, polarization: string, worstmonth: boolean) => { + if (!worstmonth) { + const result = requestRest(LINKCALCULATOR_BASE_URL + '/rain/' + lat1 + ',' + lon1 + ',' + lat2 + ',' + lon2 + '/' + bandKeyId + '/' + polarization.toUpperCase() + '/' + 'ANNUAL'); + return result; + } else { + const result = requestRest(LINKCALCULATOR_BASE_URL + '/rain/' + lat1 + ',' + lon1 + ',' + lat2 + ',' + lon2 + '/' + bandKeyId + '/' + polarization.toUpperCase() + '/' + 'WORSTMONTH'); + return result; + } + }, + + manualRain : (rainfall: number, bandKeyId: string, distance: number, polarization: string) => { + const result = requestRest(LINKCALCULATOR_BASE_URL + '/rain/' + '/manual/' + rainfall + '/' + bandKeyId + '/' + distance + '/' + polarization.toUpperCase()); + return result; + }, + + + FSL : (distance: number, bandKeyId: string) => { + const result = requestRest(LINKCALCULATOR_BASE_URL + '/fsl/' + distance + '/' + bandKeyId); + return result; + }, + + AbsorptionAtt : (lat1: number, lon1: number, lat2: number, lon2: number, bandKeyId: string, worstmonth: boolean, absorptionMethod: string) => { + if (!worstmonth) { + + const result = requestRest(LINKCALCULATOR_BASE_URL + '/absorption/annual/' + lat1 + ',' + lon1 + ',' + lat2 + ',' + lon2 + '/' + bandKeyId + '/' + absorptionMethod); + return result; + } else { + const result = requestRest(LINKCALCULATOR_BASE_URL + '/absorption/annual/' + lat1 + ',' + lon1 + ',' + lat2 + ',' + lon2 + '/' + bandKeyId + '/' + absorptionMethod); + return result; + } + }, + + linkBudget : (lat1: number, lon1: number, lat2: number, lon2: number, bandKeyId: string, absorptionMethod: string, + polarization: string, antennaGainA: number, antennaGainB: number, waveguideLossA: number, waveguideLossB: number, transmissionPowerA: number, + transmissionPowerB: number, rxSensitivityA: number, rxSensitivityB: number) => { + + const url = LINKCALCULATOR_BASE_URL + '/linkbudget/' + lat1 + ',' + lon1 + ',' + lat2 + ',' + lon2 + '/' + + absorptionMethod + '/' + polarization.toUpperCase() + '/' + antennaGainA + '/' + antennaGainB + '/' + + waveguideLossA + '/' + waveguideLossB + '/' + transmissionPowerA + '/' + transmissionPowerB + '/' + rxSensitivityA + '/' + rxSensitivityB; + + + const result = requestRest(url + '?bandKeyId=' + bandKeyId); + return result; + }, + + updateAutoDistance : (lat1: number, lon1: number, lat2: number, lon2: number) => { + const result = requestRest(LINKCALCULATOR_BASE_URL + '/distance/' + lat1 + ',' + lon1 + ',' + lat2 + ',' + lon2); + return result; + }, + + waveguideLoss : (waveguideIdSiteA: number, waveguideIdSiteB: number, waveguideLengthA: number, waveguideLengthB: number) => { + + const result = requestRest(LINKCALCULATOR_BASE_URL + '/waveguide/' + waveguideIdSiteA + '/' + waveguideIdSiteB + '/' + waveguideLengthA + '/' + waveguideLengthB); + return result; + }, + + adaptiveModulationTable : async (adaptiveModulationInput: AdaptiveModulationInput) => { + const result = requestRestExt(LINKCALCULATOR_BASE_URL + '/link/' + adaptiveModulationInput.linkId + '/calculate/', { + method: 'POST', // or 'PUT' + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(adaptiveModulationInput), + }); + return result; + }, + + bandListQuery : async (bandplanKeyId: string) => { + const result = requestRestExt(MICROWAVE_URL + '/bandplans/' + bandplanKeyId + '/bands'); + return result; + }, + + modelTypeListQuery : async () => { + const result = requestRest(LINKCALCULATOR_BASE_URL + '/query/modeltypelist/'); + return result; + }, + + channelQuery : async (bandplanKeyId: string, bandKeyId: string) => { + + const response = await requestRestExt(MICROWAVE_URL + '/bandplans/' + bandplanKeyId + '/bands/' + bandKeyId + '/channels'); + return response; + }, + + saveLink : async (link: Link, id: number) => { + type Message = { 'message': string; saveFail: boolean }; + const response = await requestRestExt(LINKCALCULATOR_BASE_URL + '/link/' + id, { + method: 'PUT', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(link), + }); + + if (response.status === 200) { + return response; + } else { + let message: Message = { message: '', saveFail: true }; + if (response.data?.message) { + message.message = 'Save failed ' + response.data.message; + return message; + } else { + message.message = 'Something went wrong ' + response.message; + return message; + } + + } + }, + + frequencyPlanQuery : (siteId: number) => { + const result = requestRestExt(NETWORK_URL + '/sites/' + siteId + '/frequencyplan'); + return result; + }, + +}; +export default dataService; diff --git a/features/sdnr/odlux/odlux/apps/microwaveApp/src/linkCalculator/service/processingService.ts b/features/sdnr/odlux/odlux/apps/microwaveApp/src/linkCalculator/service/processingService.ts new file mode 100644 index 0000000..1ec4733 --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/microwaveApp/src/linkCalculator/service/processingService.ts @@ -0,0 +1,94 @@ +/** + * ============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 { Model } from '../model/topologyTypes'; +import { Radio, RadioEverything, RadioOperationalParameters } from '../model/radio'; +import { Waveguide } from '../model/waveguide'; +import { Antenna } from '../model/antenna'; + +export const getWaveguideList = (modelTypeList: Model[]) => { + let waveguides: Waveguide[] = []; + + modelTypeList.forEach(modeltype => { + if (modeltype.type?.name === 'radio-to-antenna-link') { + waveguides.push(modeltype); + + + } + }); + return waveguides; +}; +export const radioService = async (radioA: string | null, radioB: string | null, radios : Radio []) => { + let operationalParametersA: RadioOperationalParameters | null = null; + let operationalParametersB: RadioOperationalParameters | null = null; + let modulationListA: string[] = []; + let modulationListB: string[] = []; + let radioIdSiteA: number | null = null; + let radioIdSiteB: number | null = null; + + radios.forEach((element: Radio) => { + if (radioA !== null && radioA === element.modelName) { + operationalParametersA = element.operationalParameters; + + radioIdSiteA = element.id; + Object.keys(operationalParametersA?.modulations!).forEach((modulation) => { + if (modulation.endsWith('QAM') || modulation.endsWith('PSK')) { + modulationListA.push(modulation); + } + }); + } + }); + radios.forEach((element: Radio) => { + if (radioB !== null && radioB === element.modelName) { + operationalParametersB = element.operationalParameters; + radioIdSiteB = element.id; + + Object.keys(operationalParametersB?.modulations!).forEach((modulation) => { + if (modulation.endsWith('QAM') || modulation.endsWith('PSK')) { + modulationListB.push(modulation); + } + }); + } + }); + const transport: RadioEverything = { operationalParametersA, operationalParametersB, modulationListA, modulationListB, radioIdSiteA, radioIdSiteB }; + return transport; +}; +export const getRadioList = (modelTypeList: Model[] | undefined | null) => { + + let radios: Radio[] = []; + modelTypeList!.forEach(modeltype => { + if (modeltype.type.name === 'radio') { + + radios.push(modeltype); + } + }); + return radios; +}; +export const getAntennaList = (modelTypeList: Model[] | undefined | null) => { + let antennas: Antenna[] = []; + + modelTypeList!.forEach(element => { + + if (element.type.name === 'radio-antenna') { + antennas.push(element); + + + } + }); + + return antennas; +}; diff --git a/features/sdnr/odlux/odlux/apps/microwaveApp/src/linkCalculator/utils/checkLink.ts b/features/sdnr/odlux/odlux/apps/microwaveApp/src/linkCalculator/utils/checkLink.ts new file mode 100644 index 0000000..8eaf21d --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/microwaveApp/src/linkCalculator/utils/checkLink.ts @@ -0,0 +1,77 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2022 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 { Link } from '../model/link'; + +export const check = (linktAttributes: Link):String => { + if (!linktAttributes) { + return 'Missing Link Attributes'; + } + var missingParameter:string = ''; + + const checkParameterNotExist = (val: number | null, message: string, description: string):string => { + //Check for null, undefined and NaN + return val == null || isNaN(val) ? message.concat(description + ':' + val) : message; + }; + missingParameter = checkParameterNotExist(linktAttributes.siteA.amslM, missingParameter, ' A-amslM'); + missingParameter = checkParameterNotExist(linktAttributes.siteB.amslM, missingParameter, ' B-amslM'); + missingParameter = checkParameterNotExist(linktAttributes.siteA.lat, missingParameter, ' A-lat'); + missingParameter = checkParameterNotExist(linktAttributes.siteA.lon, missingParameter, ' A-lon'); + missingParameter = checkParameterNotExist(linktAttributes.siteB.lat, missingParameter, ' B-lat'); + missingParameter = checkParameterNotExist(linktAttributes.siteB.lon, missingParameter, ' B-lon'); + + // for (var prop in linktAttributes.siteA.antenna) { + // if (prop !== null) { + // continue + // } else return false + // } + // for (var prop in linktAttributes.siteB.antenna) { + // if (prop !== null) { + // continue + // } else return false + // } + // for (var prop in linktAttributes.siteA.radio) { + // if (prop !== null) { + // continue + // } else return false + // } + // for (var prop in linktAttributes.siteB.radio) { + // if (prop !== null) { + // continue + // } else return false + // } + // for (var prop in linktAttributes.siteA.waveguide) { + // if (prop !== null) { + // continue + // } else return false + // } + // for (var prop in linktAttributes.siteB.waveguide) { + // if (prop !== null) { + // continue + // } else return false + // } + + if (missingParameter === '') { + return ''; + } else { + missingParameter = 'Missing parameters: '.concat(missingParameter); + console.log(missingParameter); + return missingParameter; + } +}; \ No newline at end of file diff --git a/features/sdnr/odlux/odlux/apps/microwaveApp/src/linkCalculator/utils/geoConverter.ts b/features/sdnr/odlux/odlux/apps/microwaveApp/src/linkCalculator/utils/geoConverter.ts new file mode 100644 index 0000000..b7cad62 --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/microwaveApp/src/linkCalculator/utils/geoConverter.ts @@ -0,0 +1,34 @@ + +/** + * ============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 const LatLonToDMS = (value: number, isLon: boolean) => { + const absoluteValue = Math.abs(value); + const d = Math.floor(absoluteValue); + const m = Math.floor((absoluteValue - d) * 60); + const s = (absoluteValue - d - m / 60) * 3600; + const dms = `${d}° ${m}' ${s.toFixed(2)}"`; + + const sign = Math.sign(value); + + if (isLon) { + return (sign === -1 || sign === -0) ? dms + ' W' : dms + ' E'; + } else { + return (sign === -1 || sign === -0) ? dms + ' S' : dms + ' N'; + } +}; \ No newline at end of file diff --git a/features/sdnr/odlux/odlux/apps/microwaveApp/src/linkCalculator/utils/math.ts b/features/sdnr/odlux/odlux/apps/microwaveApp/src/linkCalculator/utils/math.ts new file mode 100644 index 0000000..4d60f01 --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/microwaveApp/src/linkCalculator/utils/math.ts @@ -0,0 +1,30 @@ + +/** + * ============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========================================================================== + */ + +export const max = (a: T[], p: (v: T) => Number) => a.reduce((m, x) => p(m) > p(x) ? m : x, a[0]); +export const min = (a: T[], p: (v: T) => Number) => a.reduce((m, x) => p(m) < p(x) ? m : x, a[0]); + +export const isNumber = (value: number | null) =>{ + + if (!value) { + return false; + } else { + return !Number.isNaN(value); + } +}; \ No newline at end of file diff --git a/features/sdnr/odlux/odlux/apps/microwaveApp/src/linkCalculator/views/linkCalculation.tsx b/features/sdnr/odlux/odlux/apps/microwaveApp/src/linkCalculator/views/linkCalculation.tsx new file mode 100644 index 0000000..29b58d9 --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/microwaveApp/src/linkCalculator/views/linkCalculation.tsx @@ -0,0 +1,377 @@ +/** + * ============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 React, { FC, useState } from 'react'; + +import Paper from '@mui/material/Paper'; +import Accordion from '@mui/material/Accordion'; +import Typography from '@mui/material/Typography'; +import AccordionSummary from '@mui/material/AccordionSummary'; +import FormHelperText from '@mui/material/FormHelperText'; +import AccordionDetails from '@mui/material/AccordionDetails'; +import Box from '@mui/material/Box'; +import Button from '@mui/material/Button'; +import { ExpandMoreOutlined } from '@mui/icons-material'; +import makeStyles from '@mui/styles/makeStyles'; +import CircularProgress from '@mui/material/CircularProgress'; +import Dialog from '@mui/material/Dialog'; +import DialogContent from '@mui/material/DialogContent'; +import DialogContentText from '@mui/material/DialogContentText'; +import Stack from '@mui/material/Stack'; +import DialogActions from '@mui/material/DialogActions'; +import IconButton from '@mui/material/IconButton'; +import CloseIcon from '@mui/icons-material/Close'; + + +import { useApplicationDispatch, useSelectApplicationState } from '../../../../../framework/src/flux/connect'; +import { saveLinkCallAsync } from '../actions/saveLinkAction'; +import { isCalculationServerReachableAction } from '../actions/viewAction'; +import ConnectionInfo from '../components/connectionInfo'; +import AntennaView from '../components/antenna'; +import AttenuationView from '../components/attenuations'; +import FrequencyChannelView from '../components/frequencyChannel'; +import LocationView from '../components/location'; +import RadioView from '../components/radio'; +import WaveguideView from '../components/waveguide'; +import MissingInformation from '../components/missingInformation'; +import { BASE_URL } from '../../pluginMicrowave'; +import { check } from '../utils/checkLink'; +import { calculateButtonAction } from '../actions/handleButtonAction'; + + + +const styles = makeStyles({ + sectionMargin: { + position: 'relative', background: 'rgb(187, 189, 191)', padding: '20px', overflow: 'auto', + }, + loading: { + position: 'absolute', top: 0, left: 0, width: '100%', height: '180px', display: 'flex', justifyContent: 'center', alignItems: 'center', backgroundColor: 'rgba(255, 255, 255, 0.7)', zIndex: 9999, + }, + outerDiv: { + paddingLeft: '15px', paddingRight: '15px', paddingTop: '0px', display: 'flex', flexDirection: 'column', flex: 1, + }, + accordionTitle: { + width: '33%', flexShrink: 0, + }, + justifyCenter: { + justifyContent: 'center', + }, + calculateButton: { + marginTop: 20, position: 'fixed', top: '150px', right: '70px', + }, + saveButton: { + marginTop: 20, position: 'fixed', top: '200px', right: '70px', + }, + saveResponseOk: { + marginTop: 65, position: 'fixed', top: '250px', right: '70px', + }, + serverResponse: { + marginTop: 65, position: 'fixed', top: '250px', right: '70px', color: 'red', + }, + paper: { + display: 'flex', flexDirection: 'row', + }, + buttonBox: { width: '170px' }, + closeIcon: { + marginTop: 20, top: '-75px', right: '-40px', + }, +}); + +const LinkCalculation: FC = (() => { + const attenuationMandatoryParameters = useSelectApplicationState( state=> state.microwave.atmosphere.attenuationMandatoryParameters); + const radioMandatoryParameters = useSelectApplicationState( state=> state.microwave.radio.radioMandatoryParameters); + const waveguideMandatoryParameters = useSelectApplicationState(state=> state.microwave.waveguide.waveguideMandatoryParameters); + const antennaMandatoryParameters = useSelectApplicationState( state=> state.microwave.antenna.antennaMandatoryParameters); + const frequencyMandatoryParameters = useSelectApplicationState( state=> state.microwave.radio.frequencyMandatoryParameters); + const locationMandatoryParameters = useSelectApplicationState( state=> state.microwave.site.locationMandatoryParameters); + const linkAttributes = useSelectApplicationState( state=> state.microwave.query.linkAttributes); + // const loadingComplete = useSelectApplicationState( state=> state.microwave.view.loadingComplete); + const processing = useSelectApplicationState( state=> state.microwave.view.processing); + // const formView = useSelectApplicationState( state=> state.microwave.view.formView); + const reachable = useSelectApplicationState( state=> state.microwave.view.reachable); + const save = useSelectApplicationState( state=> state.microwave.query.linkSave); + const isCalculationServerReachable = useSelectApplicationState( state=> state.microwave.view.reachable); + const savingComplete = useSelectApplicationState( state=> state.microwave.query.savingComplete); + const linkSaving = useSelectApplicationState( state=> state.microwave.query.linkSaving); + + const [isFrequencyAccordionOpen, setFrequencyAccordionOpen] = useState(false); + const [isAntennaAccordionOpen, setAntennaAccordionOpen] = useState(false); + const [isWaveguideAccordionOpen, setWaveguideAccordionOpen] = useState(false); + const [isRadioAccordionOpen, setRadioAccordionOpen] = useState(false); + const [isOpenDialog, setOpenDialog] = useState(false); + const dispatch = useApplicationDispatch(); + + const UpdateConectivity = (serverReachable: boolean) => dispatch(new isCalculationServerReachableAction(serverReachable)); + + + + const restCallsActionAsync = () => dispatch(calculateButtonAction()); + const saveCallsActionAsync = () => dispatch(saveLinkCallAsync()); + + const classes = styles(); + const handleDialogClose = () => setOpenDialog(false); + + const handleButton = (e: any) => { + if (e.target.value === 'CALCULATE') { + restCallsActionAsync(); + } + if (e.target.value === 'SAVE') { + setOpenDialog(true); + saveCallsActionAsync(); + } + }; + + React.useEffect(() => { + + }, []); + React.useEffect(() => { + fetch(BASE_URL + '/fsl/1/' + '$13') + .then(res => { if (res.ok) { UpdateConectivity(true); } else { UpdateConectivity(false); } }); + }, []); + + + return ( +
+ + + + + { + reachable && + <> + {processing ? + + + + + Channel Select + + + + + + + +
+ +

Processing ...

+
+
+
+
+
+ : + <> + {(check(linkAttributes)) !== '' ? + : +
+ + + } + aria-label="location-panel-header" + id="location-panel-header" + > + Location + {!locationMandatoryParameters && + < Typography > + * Required Fields Missing * + + } + + + + + + + + setFrequencyAccordionOpen(!isFrequencyAccordionOpen)}> + } + aria-label="frequeny-panel-header" + id="frequency-header" + > + Frequency and Channel + {!frequencyMandatoryParameters && + < Typography > + * Required Fields Missing * + + } + + + {isFrequencyAccordionOpen && + + } + + + setAntennaAccordionOpen(!isAntennaAccordionOpen)}> + } + aria-label="antenna-header" + id="antenna-header" + > + Antenna + {!antennaMandatoryParameters && + < Typography > + * Required Fields Missing * + + } + + + + + + setWaveguideAccordionOpen(!isWaveguideAccordionOpen)}> + } + aria-label="waveguide-header" + id="waveguide-header" + > + Waveguide + {!waveguideMandatoryParameters && + < Typography > + * Required Fields Missing * + + } + + + + + + setRadioAccordionOpen(!isRadioAccordionOpen)}> + } + aria-label="radio-header" + id="radio-header" + > + Radio + {!radioMandatoryParameters && + < Typography > + * Required Fields Missing * + + } + + + + + + + } + aria-label="attenuation-header" + id="attenuation-header" + > + Attenuation + {!attenuationMandatoryParameters && + < Typography > + * Required Fields Missing * + + } + + + + + + +
+ } + + + +
+ {isOpenDialog && + + <> + { + linkSaving ? + + + + + + Save + + + + + + + +
+ +

Saving ...

+
+
+
+
+
+ + : <> + { + save.saveFail ? + + + + + + + + + + : savingComplete && + save.status === 200 && + Link Saved + } + + } + + } +
+
+ + } + + } +
+
+ ); +}); + +export default LinkCalculation; diff --git a/features/sdnr/odlux/odlux/apps/microwaveApp/src/linkCalculator/views/mainView.tsx b/features/sdnr/odlux/odlux/apps/microwaveApp/src/linkCalculator/views/mainView.tsx new file mode 100644 index 0000000..647820e --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/microwaveApp/src/linkCalculator/views/mainView.tsx @@ -0,0 +1,88 @@ +/** + * ============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 React, { FC, useEffect, useState, SyntheticEvent } from 'react'; +import { RouteComponentProps, withRouter } from 'react-router-dom'; + +import AppBar from '@mui/material/AppBar'; +import Tab from '@mui/material/Tab'; +import Tabs from '@mui/material/Tabs'; + +import { NavigateToApplication } from '../../../../../framework/src/actions/navigationActions'; +import { useApplicationDispatch } from '../../../../../framework/src/flux/connect'; + +import LineOfSightMainView from '../../lineOfSight/views/lineOfSightMain'; +import { TabId } from '../model/tabId'; +import LinkCalculation from './linkCalculation'; +import LinkTableComponent from '../components/linkTable'; + +type mainComponentProps = RouteComponentProps & { activePanel: string }; + +export const MainView: FC = (props) => { + + const [panel, setPanel] = useState(props.activePanel); + const dispatch = useApplicationDispatch(); + const navigateToApplication = (applicationName: string, path?: string) => dispatch(new NavigateToApplication(applicationName, path, '')); + + // const dispatch = useApplicationDispatch(); + // const updateTab =(value:TabId) => dispatch(new UpdateTabAction(value)) + + const changeTab = (event: SyntheticEvent, value: TabId) => { + setPanel(value); + }; + + const linkTableTab = () => { + navigateToApplication('microwave', ''); + }; + + const calculateLinkTab = () => { + navigateToApplication('microwave', 'calculateLink'); + }; + + const lineOfSightTab = () => { + navigateToApplication('microwave', 'lineOfSightMap'); + }; + + useEffect(() => { + if (panel == null) { + setPanel('linkTable'); + } + }, []); + + return ( + <> + + + + + + + + {panel === 'linkTable' + ? + : panel === 'linkCalculation' + ? + : panel === 'lineOfSight' + ? + : null + } + + ); +}; + +export default withRouter(MainView); \ No newline at end of file diff --git a/features/sdnr/odlux/odlux/apps/microwaveApp/src/pluginMicrowave.tsx b/features/sdnr/odlux/odlux/apps/microwaveApp/src/pluginMicrowave.tsx new file mode 100644 index 0000000..4e7f9fe --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/microwaveApp/src/pluginMicrowave.tsx @@ -0,0 +1,213 @@ +/** +* ============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========================================================================== +*/ + +// app configuration and main entry point for the app + +import React, { FC } from 'react'; +import applicationManager from '../../../framework/src/services/applicationManager'; +import { requestRest } from '../../../framework/src/services/restService'; + +import { Antenna } from './linkCalculator/model/antenna'; +import { Link } from './linkCalculator/model/link'; +import { Radio } from './linkCalculator/model/radio'; +import { ModelType } from './linkCalculator/model/topologyTypes'; +import { Waveguide } from './linkCalculator/model/waveguide'; + +import { Redirect, Route, RouteComponentProps, Switch, useLocation, withRouter } from 'react-router-dom'; +import { useApplicationDispatch } from '../../../framework/src/flux/connect'; +import { SetPassedInValues, SetReachableAction } from './lineOfSight/actions/lineOfSightCommonActions'; +import { TERRAIN_URL, TILE_URL } from './lineOfSight/config'; +import { isNumber } from './lineOfSight/utils/lineOfSightMath'; +import { FirstMandatoryCheckAction } from './linkCalculator/actions/errorAction'; +import { UpdateModelTypesAction, UpdateRegionRegulatorListAction } from './linkCalculator/actions/queryActions'; +import { UpdateDevicesOnFirstLoad } from './linkCalculator/actions/radioActions'; +import { PluginDoneLoadingAction, ResetFormAction } from './linkCalculator/actions/viewAction'; +import RootHandler from './linkCalculator/handlers/rootHandler'; +import dataService from './linkCalculator/service/dataService'; +import MainView from './linkCalculator/views/mainView'; +import { Channel, RegionRegulator } from './linkCalculator/model/bandPlan'; +import { getAllBands, getFrequencyplans, updateSavedChannels } from './linkCalculator/actions/bandPlanAction'; +import { SaveChannel } from './linkCalculator/model/updateLink'; + +const appIcon = require('./linkCalculator/assets/icons/microwaveAppIcon.svg'); // select app icon + +export const BASE_URL = '/topology/linkcalculator'; + + + + + +const useQuery = () => { + return new URLSearchParams(useLocation().search); +}; + +const MicrowaveRouteAdapter = ((props: RouteComponentProps<{ mountId?: string }>) => { + const dispatch = useApplicationDispatch(); + + const resetForm = () => dispatch(new ResetFormAction()); + const updateRegulatorList = (regionRegulatorList: RegionRegulator[]) => dispatch(new UpdateRegionRegulatorListAction(regionRegulatorList)); + const updateModelTypes = (modelTypes: ModelType[]) => dispatch(new UpdateModelTypesAction(modelTypes)); + const updateDevicesOnFirstLoad = async (antennas: Antenna[], radios: Radio[], waveguides: Waveguide[], linkAttributes: Link) => { + dispatch(await new UpdateDevicesOnFirstLoad(antennas, radios, waveguides, linkAttributes)); + dispatch(new FirstMandatoryCheckAction()); + dispatch(new PluginDoneLoadingAction(true)); + dispatch(getAllBands(linkAttributes.operationalParameters.bandplanKeyId)); + dispatch(getFrequencyplans(linkAttributes.siteA.id, linkAttributes.siteB.id, linkAttributes.operationalParameters.bandKeyId)); + + }; + const getSavedChannels = async (savedChannels: SaveChannel[], allChannels: Channel[]) => dispatch(updateSavedChannels(savedChannels, allChannels)); + let query = useQuery(); + // called when component finished mounting + + const extractLinkIDFromURL = (queryParam: URLSearchParams) => { + return queryParam.get('linkId'); + }; + + React.useEffect(() => { + const linkId = extractLinkIDFromURL(query); + const linkAttributes = requestRest(`${BASE_URL}/link/${linkId}`); + const regulators = dataService.bandPlanRegulators(); + + + const modelTypeListQuery = dataService.modelTypeListQuery(); + const queryArray = [regulators, modelTypeListQuery, linkAttributes]; + if (linkId) { + Promise.all(queryArray).then(async values => { + + let radios: Radio[] = []; + let antennas: Antenna[] = []; + let waveguides: Waveguide[] = []; + await dataService.getModels(values[2].operationalParameters.bandKeyId, 'radio')!.then((x: Radio[]) => { + radios = x; + }); + await dataService.getModels(values[2].operationalParameters.bandKeyId, 'radio-antenna')!.then((x: Antenna[]) => { + antennas = x; + }); + await dataService.getModels(values[2].operationalParameters.bandKeyId, 'radio-to-antenna-link')!.then((x: Waveguide[]) => { + waveguides = x; + }); + updateRegulatorList(values[0]); + updateModelTypes(values[1]); + + if (values[2].operationalParameters.selectedChannelList && values[2].operationalParameters.selectedChannelList.length > 0) { + const channelQueryPromise = await dataService.channelQuery(values[2].operationalParameters.bandplanKeyId, values[2].operationalParameters.bandKeyId); + if (channelQueryPromise.data) { + getSavedChannels(values[2].operationalParameters.selectedChannelList, channelQueryPromise.data); + } + } + + updateDevicesOnFirstLoad(antennas, radios, waveguides, values[2]); + + }); + } else { + resetForm(); + } + }, []); + + + React.useEffect(() => { + if (props.location.pathname === '/microwave' && props.location.search.length == 0) { + resetForm(); + } + }, [props.location.pathname, props.location.search]); + + return ( + + ); +}); + +type AppProps = RouteComponentProps; +const LineOfSightApplicationRouteAdapter: FC = () => { + const dispatch = useApplicationDispatch(); + const setPassedInValues = (values: (string | null)[]) => dispatch(SetPassedInValues(values)); + const setReachable = (reachable: boolean) => dispatch(new SetReachableAction(reachable)); + let query = useQuery(); + + /*** + * + * Checks if lat1, lon1, lat2, lon2 were passed in as url parameters + */ + const areMandatoryParamsPresent = (queryParams: URLSearchParams) => { + return isNumber(queryParams.get('lat1')) && isNumber(queryParams.get('lon1')) && isNumber(queryParams.get('lat2')) && isNumber(queryParams.get('lon2')); + }; + + const extractValuesFromURL = (queryParams: URLSearchParams) => { + return [queryParams.get('lat1'), queryParams.get('lon1'), queryParams.get('lat2'), queryParams.get('lon2'), queryParams.get('amslA'), queryParams.get('antennaHeightA'), query.get('amslB'), queryParams.get('antennaHeightB')]; + }; + + const extractAndDispatchUrlValues = () => { + //if mandatory values aren't there, do nothing + if (areMandatoryParamsPresent(query)) { + const values = extractValuesFromURL(query); + setPassedInValues(values); + } + }; + + const tryCheckConnection = () => { + const terrain = fetch(`${TERRAIN_URL}/`); + const tiles = fetch(`${TILE_URL}/10/0/0.png`); + + Promise.all([terrain, tiles]) + .then((result) => { + setReachable(result[0].ok && result[1].ok); + }) + .catch(error => { + console.error('services not reachable.'); + console.error(error); + setReachable(false); + }); + }; + + // called when component finshed mounting + React.useEffect(() => { + extractAndDispatchUrlValues(); + //check tiles/terrain connectivity + tryCheckConnection(); + + }, []); + + return ( + + ); +}; + +const App = withRouter((props: RouteComponentProps) => { + props.history.action = 'POP'; + return ( + + + + + + + + ); +}); + +export function register() { + applicationManager.registerApplication({ + name: 'microwave', + icon: appIcon, + rootActionHandler: RootHandler, + rootComponent: App, + menuEntry: 'Microwave', + }); +} + + + diff --git a/features/sdnr/odlux/odlux/apps/microwaveApp/tsconfig.json b/features/sdnr/odlux/odlux/apps/microwaveApp/tsconfig.json new file mode 100644 index 0000000..18956db --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/microwaveApp/tsconfig.json @@ -0,0 +1,6 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": "./dist", + }, +} diff --git a/features/sdnr/odlux/odlux/apps/microwaveApp/webpack.config.js b/features/sdnr/odlux/odlux/apps/microwaveApp/webpack.config.js new file mode 100644 index 0000000..2a0c5f8 --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/microwaveApp/webpack.config.js @@ -0,0 +1,147 @@ +/** + * 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 __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: { + microwaveApp: ["./pluginMicrowave.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$/i, + use: ["style-loader", "css-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: proxyConf, + } + }]; +} \ No newline at end of file diff --git a/features/sdnr/odlux/odlux/apps/minimumApp/.babelrc b/features/sdnr/odlux/odlux/apps/minimumApp/.babelrc new file mode 100644 index 0000000..3d8cd12 --- /dev/null +++ b/features/sdnr/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/features/sdnr/odlux/odlux/apps/minimumApp/package.json b/features/sdnr/odlux/odlux/apps/minimumApp/package.json new file mode 100644 index 0000000..2c88f30 --- /dev/null +++ b/features/sdnr/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/features/sdnr/odlux/odlux/apps/minimumApp/pom.xml b/features/sdnr/odlux/odlux/apps/minimumApp/pom.xml new file mode 100644 index 0000000..6f9d913 --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/minimumApp/pom.xml @@ -0,0 +1,109 @@ + + + + + 4.0.0 + + org.o-ran-sc.oam-controller.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/features/sdnr/odlux/odlux/apps/minimumApp/src/assets/icons/minimumAppIcon.svg b/features/sdnr/odlux/odlux/apps/minimumApp/src/assets/icons/minimumAppIcon.svg new file mode 100644 index 0000000..298eaa1 --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/minimumApp/src/assets/icons/minimumAppIcon.svg @@ -0,0 +1,27 @@ + + + + + + + + + + + + diff --git a/features/sdnr/odlux/odlux/apps/minimumApp/src/handlers/minimumAppRootHandler.ts b/features/sdnr/odlux/odlux/apps/minimumApp/src/handlers/minimumAppRootHandler.ts new file mode 100644 index 0000000..4a4dc61 --- /dev/null +++ b/features/sdnr/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/features/sdnr/odlux/odlux/apps/minimumApp/src/index.html b/features/sdnr/odlux/odlux/apps/minimumApp/src/index.html new file mode 100644 index 0000000..58865ed --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/minimumApp/src/index.html @@ -0,0 +1,24 @@ + + + + + + + + + Minimal App + + + +
+ + + + + + \ No newline at end of file diff --git a/features/sdnr/odlux/odlux/apps/minimumApp/src/plugin.tsx b/features/sdnr/odlux/odlux/apps/minimumApp/src/plugin.tsx new file mode 100644 index 0000000..5c8500a --- /dev/null +++ b/features/sdnr/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/features/sdnr/odlux/odlux/apps/minimumApp/tsconfig.json b/features/sdnr/odlux/odlux/apps/minimumApp/tsconfig.json new file mode 100644 index 0000000..ca65092 --- /dev/null +++ b/features/sdnr/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/features/sdnr/odlux/odlux/apps/minimumApp/webpack.config.js b/features/sdnr/odlux/odlux/apps/minimumApp/webpack.config.js new file mode 100644 index 0000000..50b24e7 --- /dev/null +++ b/features/sdnr/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/features/sdnr/odlux/odlux/apps/networkMapApp/.babelrc b/features/sdnr/odlux/odlux/apps/networkMapApp/.babelrc new file mode 100644 index 0000000..3d8cd12 --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/networkMapApp/.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/features/sdnr/odlux/odlux/apps/networkMapApp/icons/README.md b/features/sdnr/odlux/odlux/apps/networkMapApp/icons/README.md new file mode 100644 index 0000000..b26fbc2 --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/networkMapApp/icons/README.md @@ -0,0 +1,29 @@ +Copyright of icons is as followes: + + + +datacenter.png and lamp.png + +Taken from MS Word + +According to https://support.microsoft.com/en-us/office/insert-icons-in-microsoft-office-e2459f17-3996-4795-996e-b9a13486fa79 (date: October 9th, 2019) +"These icons are free to use; there's no royalty or copyright." + + \ No newline at end of file diff --git a/features/sdnr/odlux/odlux/apps/networkMapApp/icons/apartment.png b/features/sdnr/odlux/odlux/apps/networkMapApp/icons/apartment.png new file mode 100644 index 0000000000000000000000000000000000000000..d4a1c5e7c966ae888bab4dd677b359d5c44eeb6b GIT binary patch literal 1717 zcmeAS@N?(olHy`uVBq!ia0y~yU_1oE985rw&sA|kK#Hv-$S;`TKM0yfNY?@dvOHZJ zLn`LHz2=xF?a0G$Fm=KI`k6bQ$Z+H(r>BP3Hq2?g^5D%o)&t>xE%!1UNS_?o;Nr$8 zB+2TLBBC&H&~qkTf64kIJNW#7*g z8W%s`ahiGm?Z5m_6l3Tc7?40W+Ff1MzWvOZm!b{PwO(@v#0|hyaPsvRrt@2Om-o${ zX?;9a?#Er557&!&>6bF#De&Z*cQ0k{85u7RXUO}iSvnwY0A-2wm$_}Ozq@Cdm-bGk zeE;nI&!0}N+b;j#cK?|H$vI%>fHKa%2`F+H>}Cv9wk5yFpS1t;HK*q2 TodR2d^$>%ntDnm{r-UW|cAOWW literal 0 HcmV?d00001 diff --git a/features/sdnr/odlux/odlux/apps/networkMapApp/icons/apartment.png.d.ts b/features/sdnr/odlux/odlux/apps/networkMapApp/icons/apartment.png.d.ts new file mode 100644 index 0000000..bf398f5 --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/networkMapApp/icons/apartment.png.d.ts @@ -0,0 +1,2 @@ +declare const apartment: string; +export default apartment; \ No newline at end of file diff --git a/features/sdnr/odlux/odlux/apps/networkMapApp/icons/customize.png b/features/sdnr/odlux/odlux/apps/networkMapApp/icons/customize.png new file mode 100644 index 0000000000000000000000000000000000000000..91dbf6824b66146e6d8307a67f1536dc6264ee73 GIT binary patch literal 399 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 Cl}~H{ literal 0 HcmV?d00001 diff --git a/features/sdnr/odlux/odlux/apps/networkMapApp/icons/factory.png.d.ts b/features/sdnr/odlux/odlux/apps/networkMapApp/icons/factory.png.d.ts new file mode 100644 index 0000000..b5c4f19 --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/networkMapApp/icons/factory.png.d.ts @@ -0,0 +1,2 @@ +declare const factory: string; +export default factory; \ No newline at end of file diff --git a/features/sdnr/odlux/odlux/apps/networkMapApp/icons/factoryred.png b/features/sdnr/odlux/odlux/apps/networkMapApp/icons/factoryred.png new file mode 100644 index 0000000000000000000000000000000000000000..959603ab12cc97c228277bbe4f138b2969e6a053 GIT binary patch literal 1336 zcmeAS@N?(olHy`uVBq!ia0vp^uYmXq2NRH#($f6|q}WP={DK+&gP?hYbS+SXv%n*= zn1MmI9fTSCpMFebU|_l7>EaktG3V{wgN3)lL>vOO9U(3nq20w+=;txIX zo>9X&SwHcUst2->WcSZly=icjc`Fnf&>3!e)>sdPMqpK?` zW-gm5Ke6# zum8^iEI)ny(djp5?rS;v$@GwAYNY~LT0cH`&f>Uz1zY0w-78EeTc)45x906WnVp4h z5&6Fk1?tD^Gg`iS%IywxR4SbLqN+19E_wWA}SaagEN$kh^>sk9s^)G+<`|#oK!_MD- zf2iOJKlAv!+&mEZr;dNZS1UdHW6 zoBQu-&wdwr_si$LB#*$m#M?W+1@4)B_xs}G0rw00mlwQc-q|9ZE-sb)=$ik*PapD_ zW4*ZwH8`-bz@++l~(}%>(oV>aORS-!GAWaCY+%_9JQKuP>K3bVlDVvrD>J zv;9w@@k71`V$0t%Jan^HFx!3mRxSID!prgkv+u6GV<8Q4OJdpErw?^^e`N@-Z3vF= zu)hD_>_Dje0}-*U3H!opE&ozUtcAz1NZtvq#EtQ0QrrwAV9FbB_tVKU2XOSnx4;y85}Sb4q9e E0H=7IeEw6PSAEz{`mPa@t29hQVF-i)T3=0OhI+CXP5wszeM1Zj4oB$zdB{U{xd?o!J% z*LGL^qNy)V;Af@U-RUxo*+^^a>`FS18SsjPAcwMOq?b555w;4r|EYBnZ%U|-#&AKD z%S3}ilRbmnJf_}DKtDI0`Hh%u_56cX6`nG(bHZ#qv>?#B}t zkQ=&zONcv>eJcUqA#qzrjrYMLoujM<8{jiXP^C(B%&p;!4oUYqYMl>0ZcSA1P(yCn z5mW_G6;9^Nr=RG*D_Lt{#|k`HEeWt(!HTBUNUDk>n9(A+^{zto$Iyq}GBfj0Ux+u_ z&yz*NA-Nn#`#Mte8niK;;uSq4r9?ytt7hNbe9DAP$}Bi&UY zXi85s%8hL;wG8nb_l?+&r}SnPS<~Cyw6hUx3`@eoy^ESw$_+&8saj@+^b!>shL*Z5 zIV8*dAl0+@4MaUV&g^92=xys^MIhV$6j=yr95|PTZItP*ecGH#xr?|*Wq{1-5x48K zLlsa(9x#)O?4q?0dW`tW(!tUOtVr>8{y>#O2Sq3{7fS~{3N|Zq*Xd!)Jz$#}UD@KH zGkl-ynJySv)vur&d^~HUm@ciRbFc;Sk;QholqOTJBry9sIY*b5e`?p$i_2g3%2$P4 z#2w>Z{61oaiQ>ZLxKS#0Fdqiy?F-XPSl5GlN$T>yVA-VB#SF@Rm~pUde2vvc`8O*bqO5!@C3_2UyK&hayb2XE-zqAZ_~2 zF%GToV;t{HO84!sPg!b5Q+xhdJ@49nGIw5>PUc<|+W%9o@Fk1E?fLAytv) zw_U-Jsh|uta;D()Twz|7W-#ti7h^~qK%z>T8tn&bJ!d;<=WUN-c>2{jY4ADmR)1)Y zjzzL19&woZ=5Tc+6WeHTF0LM+lbxp%T+>c+Ll2I>W(>`4{b)O0qjs+W=$+1SCa94> zf1+TP?<%H&{K4$y7JgMB$x?qI0nUm!W++HCU(!mH(RNJzAaT_OI?gH~N&8xg2IG@qBH~uDY*mfa=*yr{ZY|8$h8Wav^@hWpnXoC9XQ*WpDKRaIz z>Q;^}HCBlKgSRBQ_iLZ7`C@Eg8w^<+yPuYR)q^BuSI~>QUsGgxFAHzebh7+QlWI(m yFISZRd%ac)R9R?^?U_*@{6bXn=dD$e3=TL=ww@iZ$kE>-h&?|AV;=bbeC}_I%b*7U literal 0 HcmV?d00001 diff --git a/features/sdnr/odlux/odlux/apps/networkMapApp/icons/lamp.png.d.ts b/features/sdnr/odlux/odlux/apps/networkMapApp/icons/lamp.png.d.ts new file mode 100644 index 0000000..9634b12 --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/networkMapApp/icons/lamp.png.d.ts @@ -0,0 +1,2 @@ +declare const lamp: string; +export default lamp; \ No newline at end of file diff --git a/features/sdnr/odlux/odlux/apps/networkMapApp/icons/lampred.png b/features/sdnr/odlux/odlux/apps/networkMapApp/icons/lampred.png new file mode 100644 index 0000000000000000000000000000000000000000..4678ce91cabe6d61da661f1b753d5a92b0d9ae1e GIT binary patch literal 1853 zcmd^AdpOg37@w}_q6>$Ngzmy~oVM9iQk)D;C5Ox<$|c&*Wm+idp$jRO!phvbu$bYr zp3QWQj!P)E#Dk6B;$UWOYtkmO?fk0q^v^keod3`Bectc;em>88`CgvqebbKNj;zr( z)P})eYp~9aCm{U}5}D=_Xm6E%LPBaq>#iR+>y^o3n4z@|I?IN$d^?1;Rj|f))Ddv z-5=zNo|Z|MGKKBtZneETwK|FTa$rBWb~2cukvCmI=w7^%FyYN~LW^3QvA;{?WP7r3 zB+f0paGM=b_Lp8V2xc5uE^4&V7=X&QU^kx3mG1edz+y>$B;hgEWBK zZv_^cSBln|<|B~gwXQ_#R|)hgbSs`@5zdwV(Cm|c#$Ow2je-tZx5(BV?W%gjc4AS* zbtUWjN(q#ZLj3196#RHk7NHxIkS><4VXQre{3#p&WX84e9xn!`hda)ebj=li<5Uu$o^rx0ZL-o;cOEtaO5a+t!RySKfb1}p}`pGI*xdY}@Ji!S>w z*z~tH1+w81^J&Yh@CYDps4FigzWR?~lk6i0I;t!BnK&IlYr3LF>Ms+MkyJ^rBpuAKQe-*AVAH&`$iJ3ZKSY5U{cez2Avo*>*G6Ge8X z#qrNCRGtj59}4Qse4>Uvt4|a<&oI9QGBYkAtd_HyXFG;_QzpEz06k71nyQ}3%DTjI z;!^B{ooVpy5LMeZNhdI|ecBd)l@}C%eW3d;Lt*Tg?t@*mmlfMG0H`hHuBvh668_Z5CyajUM5ttcGTvK>ucNzURsMFQjIw~?a>R!P^qNDHATX~rWIYJB|)qnJd} zYO`hJxej2s^wmQOe1Vs|kC@V|WhH(f(EYSmKdMc+(!)eiJJP)%GeV|MHgI^nO!Vyjltvr(1p;xMau>#bqL4|eUV zF3{*h9`<3xl$;m255^S_gFn38?VNmt)LeqXH3dYj>%TJtS%06|Ij|C-G0dop)o$LnM6?3U4jjg|u!??Ua;b@2M2+Xjy@ p$<}w;Y=Rzi1IFK#|Fii#N#hnzZWk0v!$8vyhIPU@RvkK>{1>B&!g~M! literal 0 HcmV?d00001 diff --git a/features/sdnr/odlux/odlux/apps/networkMapApp/icons/lampred.png.d.ts b/features/sdnr/odlux/odlux/apps/networkMapApp/icons/lampred.png.d.ts new file mode 100644 index 0000000..12a8f91 --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/networkMapApp/icons/lampred.png.d.ts @@ -0,0 +1,2 @@ +declare const lampred: string; +export default lampred; \ No newline at end of file diff --git a/features/sdnr/odlux/odlux/apps/networkMapApp/package.json b/features/sdnr/odlux/odlux/apps/networkMapApp/package.json new file mode 100644 index 0000000..eb50534 --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/networkMapApp/package.json @@ -0,0 +1,49 @@ +{ + "name": "@odlux/transport-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": "Aijana Schumann", + "license": "Apache-2.0", + "dependencies": { + "@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": "*", + "history": "^4.9.0", + "maplibre-gl": "^3.0.0", + "object.values": "^1.1.1" + }, + "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-split-pane": "0.1.92" + } +} diff --git a/features/sdnr/odlux/odlux/apps/networkMapApp/pom.xml b/features/sdnr/odlux/odlux/apps/networkMapApp/pom.xml new file mode 100644 index 0000000..eb0b0ce --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/networkMapApp/pom.xml @@ -0,0 +1,106 @@ + + + + + 4.0.0 + + org.o-ran-sc.oam-controller.features.sdnr.odlux + sdnr-odlux-app-networkMapApp + 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/features/sdnr/odlux/odlux/apps/networkMapApp/src/actions/connectivityAction.ts b/features/sdnr/odlux/odlux/apps/networkMapApp/src/actions/connectivityAction.ts new file mode 100644 index 0000000..16df2ad --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/networkMapApp/src/actions/connectivityAction.ts @@ -0,0 +1,64 @@ +/** + * ============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 { Dispatch } from '../../../../framework/src/flux/store'; +import { IApplicationStoreState } from '../../../../framework/src/store/applicationStore'; + +export class IsTopologyServerReachableAction extends Action { + constructor(public reachable: boolean) { + super(); + } +} + +export class IsTileServerReachableAction extends Action { + constructor(public reachable: boolean) { + super(); + } +} + +export class IsBusyCheckingConnectivityAction extends Action { + constructor(public isBusy: boolean) { + super(); + } +} + +export const handleConnectionError = () => (dispatcher: Dispatch, getState: () => IApplicationStoreState) => { + const { network: { connectivity: { isTopologyServerAvailable } } } = getState(); + if (isTopologyServerAvailable) { + dispatcher(new IsTopologyServerReachableAction(false)); + } +}; + +export const handleConnectionChange = (newState: boolean) => (dispatcher: Dispatch, _getState: () => IApplicationStoreState) => { + dispatcher(new IsTopologyServerReachableAction(newState)); +}; + +export const setTileServerReachableAction = (isReachable: boolean) => (dispatcher: Dispatch, getState: () => IApplicationStoreState) => { + const { network: { connectivity: { isTileServerAvailable } } } = getState(); + if (isReachable !== isTileServerAvailable) { + dispatcher(new IsTileServerReachableAction(isReachable)); + } +}; + +export const setTopologyServerReachableAction = (isReachable: boolean) => (dispatcher: Dispatch, getState: () => IApplicationStoreState) => { + const { network: { connectivity: { isTopologyServerAvailable } } } = getState(); + if (isReachable !== isTopologyServerAvailable) { + dispatcher(new IsTopologyServerReachableAction(isReachable)); + } +}; \ No newline at end of file diff --git a/features/sdnr/odlux/odlux/apps/networkMapApp/src/actions/detailsAction.ts b/features/sdnr/odlux/odlux/apps/networkMapApp/src/actions/detailsAction.ts new file mode 100644 index 0000000..d00d3ae --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/networkMapApp/src/actions/detailsAction.ts @@ -0,0 +1,146 @@ +/** + * ============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 { Dispatch } from '../../../../framework/src/flux/store'; +import { requestRest } from '../../../../framework/src/services/restService'; +import { IApplicationStoreState } from '../../../../framework/src/store/applicationStore'; + +import { Link, Site, Device, Service } from '../model/topologyTypes'; +import { HistoryEntry } from '../model/historyEntry'; +import { NetworkElementConnection } from '../model/networkElementConnection'; +import { dataService } from '../services/dataService'; +import { highlightElementAction } from './mapActions'; +import { handleConnectionError } from './connectivityAction'; + +import { SITEDOC_URL } from '../config'; + +export class SelectElementAction extends Action { + constructor(public data: Link | Site | Service) { + super(); + } +} + +export class ClearDetailsAction extends Action { +} + +export class AddToHistoryAction extends Action { + constructor(public entry: HistoryEntry) { + super(); + } +} + +export class ClearHistoryAction extends Action { +} + +export class IsBusyCheckingDeviceListAction extends Action { + constructor(public isBusy: boolean) { + super(); + } +} + +export class FinishedLoadingDeviceListAction extends Action { + constructor(public devices: Device[]) { + super(); + } +} + +export class ClearLoadedDevicesAction extends Action { +} + +export class InitializeLoadedDevicesAction extends Action { + constructor(public devices: Device[]) { + super(); + } +} + +export class IsSitedocReachableAction extends Action { + constructor(public isReachable: boolean) { + super(); + } +} + +export const UpdateDetailsView = (nodeId: string) => (dispatcher: Dispatch, getState: () => IApplicationStoreState) => { + const { network: { details: { checkedDevices } } } = getState(); + if (checkedDevices !== null) { + const index = checkedDevices.findIndex(item => item.name === nodeId); + if (index !== -1) + requestRest('/rests/operational/network-topology:network-topology/topology/topology-netconf/node/' + nodeId, { method: 'GET' }) + .then(result => { + if (result !== null) { + checkedDevices[index].status = result.node[0]['netconf-node-topology:connection-status']; + } else { + checkedDevices[index].status = 'Not connected'; + } + dispatcher(new FinishedLoadingDeviceListAction(checkedDevices)); + }); + } +}; + +export const CheckDeviceList = (list: Device[]) => async (dispatcher: Dispatch, getState: () => IApplicationStoreState) => { + + const { network: { details: { isBusyCheckingDeviceList } } } = getState(); + + if (isBusyCheckingDeviceList) return; + dispatcher(new IsBusyCheckingDeviceListAction(true)); + + const ids: string[] = list + .filter(el => el.name && el.name.length > 0) + .map((device) => { + return device.name; + }); + + const resultData = await dataService.getAdditionalInfoOnDevices(ids); + + if (resultData) { + resultData.forEach((data: NetworkElementConnection) => { + + const index = list.findIndex(el => { return el.name === data.id; }); + if (index !== -1) { + list[index].status = data.status; + list[index].type = data['device-type']; + } + }); + } + + dispatcher(new FinishedLoadingDeviceListAction(list)); + dispatcher(new IsBusyCheckingDeviceListAction(false)); +}; + +export const CheckSitedocReachability = () => async (dispatcher: Dispatch) => { + requestRest(SITEDOC_URL + '/app/versioninfo').then(response => { + console.log(response); + if (response) { + dispatcher(new IsSitedocReachableAction(true)); + } else { + dispatcher(new IsSitedocReachableAction(false)); + } + }); +}; + +export const LoadNetworkElementDetails = (type: string, id: string, zoomToElement = false) => async (dispatcher: Dispatch) => { + const response = await dataService.getDetailsData(type, id); + if (response !== null) { + dispatcher(new SelectElementAction(response)); + dispatcher(highlightElementAction(response, zoomToElement)); + dispatcher(new ClearHistoryAction()); + } else { + dispatcher(handleConnectionError()); + } +}; + diff --git a/features/sdnr/odlux/odlux/apps/networkMapApp/src/actions/filterActions.ts b/features/sdnr/odlux/odlux/apps/networkMapApp/src/actions/filterActions.ts new file mode 100644 index 0000000..7f12c09 --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/networkMapApp/src/actions/filterActions.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 SetFilterValueAction extends Action { + constructor(public value: string) { + super(); + } +} \ No newline at end of file diff --git a/features/sdnr/odlux/odlux/apps/networkMapApp/src/actions/mapActions.ts b/features/sdnr/odlux/odlux/apps/networkMapApp/src/actions/mapActions.ts new file mode 100644 index 0000000..58c4024 --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/networkMapApp/src/actions/mapActions.ts @@ -0,0 +1,196 @@ +/** + * ============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 { Dispatch } from '../../../../framework/src/flux/store'; +import { IApplicationStoreState } from '../../../../framework/src/store/applicationStore'; + +import { Coordinate } from '../model/coordinates'; +import { BoundingBox } from '../model/boundingBox'; +import { Link, Site, Service, isLink, isService, isSite } from '../model/topologyTypes'; + +import { dataService } from '../services/dataService'; +import { URL_API } from '../config'; +import { updateMapLayers } from '../services/settingsService'; +import { calculateMidPoint } from '../utils/mapUtils'; + +export class HighlightLinkAction extends Action { + constructor(public link: Link) { + super(); + } +} + +export class HighlightSiteAction extends Action { + constructor(public site: Site) { + super(); + } +} + +export class HighlightServiceAction extends Action { + constructor(public service: Service) { + super(); + } +} + +export class RemoveHighlightingAction extends Action { +} + +export class ZoomToSearchResultAction extends Action { + constructor(public center: Coordinate, public start?: Coordinate, public end?: Coordinate, public zoom?: number) { + super(); + } +} + +export class ZoomToFinishedAction extends Action { +} + +export class AddAlarmAction extends Action { + constructor(public site: Site) { + super(); + } +} + +export class RemoveAlarmAction extends Action { + constructor(public site: Site) { + super(); + } +} + +export class SetCoordinatesAction extends Action { + constructor(public lat: string, public lon: string, public zoom: string) { + super(); + } +} + +export class SetStatistics extends Action { + constructor(public siteCount: string, public linkCount: string, public serviceCount: string) { + super(); + } +} + +export class SetIconSwitchAction extends Action { + constructor(public enable: boolean) { + super(); + } +} + +export class LayerChangedAction extends Action { + constructor(public layerName: string, public displayed: boolean, public isBaseLayer: boolean) { + super(); + } +} + +export class UpdateLayersVisibilityAction extends Action { + constructor(public layerVisibility: { [key: string]: boolean }) { + super(); + } +} + +export class LayersLoadedAction extends Action { + constructor(public layers: string[]) { + super(); + } +} + +export class OpenLayersAction extends Action { + constructor(public open: boolean) { + super(); + } +} + +export class OpenStatisticsAction extends Action { + constructor(public open: boolean) { + super(); + } +} + +export const updateLayerAsyncAction = (layerName: string, displayed: boolean, isBaseLayer = true) => (dispatcher: Dispatch, getState: () => IApplicationStoreState) => { + dispatcher(new LayerChangedAction(layerName, displayed, isBaseLayer)); + + const { network: { map: { layersContainer } } } = getState(); + const newLayerSettings = layersContainer.elements.reduce((acc, layer) => { + acc[layer.name] = layer.displayed; + return acc; + }, {} as { [key: string]: boolean }); + + return updateMapLayers(newLayerSettings).then(() => true); +}; + +export const loadLayers = () => (dispatcher: Dispatch, getState: () => IApplicationStoreState) => { + const currentLayers = getState().network.map.layersContainer.elements; + return dataService.getLabels().then(res => { + const data = (res || []).filter(i => i !== 'default'); + const filteredLayers = data.filter(i => !currentLayers.find(el => el.name == i)); + dispatcher(new LayersLoadedAction(filteredLayers)); + }).then(() => true); +}; + +// Not used as of right now +export const findSiteToAlarm = (alarmedNodeId: string) => (dispatcher: Dispatch) => { + + fetch(URL_API + '/sites/devices/' + alarmedNodeId) + .then(res => res.json()) + .then(result => { + dispatcher(new AddAlarmAction(result)); + }); +}; + +export const highlightElementAction = (data: Service | Link | Site, zoomToElement = false) => (dispatcher: Dispatch) => { + if (isSite(data)) { + dispatcher(new HighlightSiteAction(data)); + if (zoomToElement) { + dispatcher(new ZoomToSearchResultAction(data.location)); + } + } else if (isLink(data)) { + dispatcher(new HighlightLinkAction(data)); + if (zoomToElement) { + const midPoint = calculateMidPoint(data.siteA.lat, data.siteA.lon, data.siteB.lat, data.siteB.lon); + const startPoint = { lat: data.siteA.lat, lon: data.siteA.lon }; + const endPoint = { lat: data.siteB.lat, lon: data.siteB.lon }; + dispatcher(new ZoomToSearchResultAction(midPoint, startPoint, endPoint)); + } + } else if (isService(data)) { + dispatcher(new HighlightServiceAction(data)); + if (zoomToElement) { + const midPoint = calculateMidPoint(data.route[0].lat, data.route[0].lon, data.route[data.route.length - 1].lat, data.route[data.route.length - 1].lon); + const startPoint = { lat: data.route[0].lat, lon: data.route[0].lon }; + const endPoint = { lat: data.route[data.route.length - 1].lat, lon: data.route[data.route.length - 1].lon }; + dispatcher(new ZoomToSearchResultAction(midPoint, startPoint, endPoint)); + } + } +}; + +export const updateZoomAction = (zoom: string) => (dispatcher: Dispatch, getState: () => IApplicationStoreState) => { + const { network: { map: { coordinates: { lat, lon } } } } = getState(); + dispatcher(new SetCoordinatesAction(lat, lon, zoom)); +}; + +export const updateStatistics = (boundingBox: BoundingBox) => (dispatcher: Dispatch, getState: () => IApplicationStoreState) => { + + const { network: { map: { statistics: { links, sites, services } } } } = getState(); + + dataService.getStatistics(boundingBox.west, boundingBox.south, boundingBox.east, boundingBox.north) + .then(result => { + if (result) { + // do not dispatch if data didn't change + if (result.links !== links || result.sites !== sites || result.services !== services) { + dispatcher(new SetStatistics(result.sites, result.links, result.services)); + } + } + }); +}; \ No newline at end of file diff --git a/features/sdnr/odlux/odlux/apps/networkMapApp/src/actions/searchAction.ts b/features/sdnr/odlux/odlux/apps/networkMapApp/src/actions/searchAction.ts new file mode 100644 index 0000000..7885ce0 --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/networkMapApp/src/actions/searchAction.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 SetSearchValueAction extends Action { + constructor(public value: string) { + super(); + } +} diff --git a/features/sdnr/odlux/odlux/apps/networkMapApp/src/actions/settingsAction.ts b/features/sdnr/odlux/odlux/apps/networkMapApp/src/actions/settingsAction.ts new file mode 100644 index 0000000..75d1a36 --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/networkMapApp/src/actions/settingsAction.ts @@ -0,0 +1,86 @@ +/** + * ============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 { NetworkMapSettings, NetworkMapThemes } from '../model/settings'; +import { Action } from '../../../../framework/src/flux/action'; +import { Dispatch } from '../../../../framework/src/flux/store'; +import { getMapSettings, updateMapSettings } from '../services/settingsService'; +import { UpdateLayersVisibilityAction } from './mapActions'; + +export class SetMapSettingsAction extends Action { + + constructor(public settings: NetworkMapSettings) { + super(); + } +} + +export class SetThemeSettingsAction extends Action { + + constructor(public settings: NetworkMapThemes) { + super(); + } +} + +export class SetBusyLoadingAction extends Action { + + constructor(public busy: boolean) { + super(); + + } +} + +/** + * An async action that loads the settings from the backend. + * @returns void + */ +export const getSettings = () => async (dispatch: Dispatch) => { + dispatch(new SetBusyLoadingAction(true)); + + getMapSettings().then(result => { + if (result) { + if (result.networkMap) { + dispatch(new SetMapSettingsAction(result.networkMap)); + } + if (result.networkMapThemes) { + dispatch(new SetThemeSettingsAction(result.networkMapThemes)); + } + if (result.networkMapLayers) { + dispatch(new UpdateLayersVisibilityAction(result.networkMapLayers)); + } + } else { + console.warn('settings couldn\'t be loaded.'); + } + dispatch(new SetBusyLoadingAction(false)); + }); +}; + +/** + * An async action that updates and saves the settings on the backend. + * @param mapSettings The new settings. + * @returns void + */ +export const updateSettings = (mapSettings: NetworkMapSettings) => async (dispatcher: Dispatch) =>{ + + const result = await updateMapSettings(mapSettings); + dispatcher(new SetMapSettingsAction(mapSettings)); + + if (!result) { + console.warn('settings couldn\'t be saved.'); + } + +}; diff --git a/features/sdnr/odlux/odlux/apps/networkMapApp/src/actions/sitedocManagementAction.ts b/features/sdnr/odlux/odlux/apps/networkMapApp/src/actions/sitedocManagementAction.ts new file mode 100644 index 0000000..95c9c54 --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/networkMapApp/src/actions/sitedocManagementAction.ts @@ -0,0 +1,74 @@ +/** + * ============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 { SitedocOrderTask, UserListItem } from '../model/siteDocTypes'; +import { sitedocDataService } from '../services/sitedocDataService'; + +export class SetAllUsersAction extends Action { + constructor(public users: UserListItem[]) { + super(); + } +} + +export class SetTSSRAction extends Action { + constructor(public isTSSR: boolean) { + super(); + } +} + +export class UpdateNoteAction extends Action { + constructor(public note: string) { + super(); + } +} + +export class UpdateStateAction extends Action { + constructor(public state: string) { + super(); + } +} + +export class UpdateTasks extends Action { + constructor(public tasks: SitedocOrderTask[]) { + super(); + } +} + +export class ResetAction extends Action { + +} + +export class SelectUserAction extends Action { + constructor(public user: string) { + super(); + } +} + +export class SetSiteExists extends Action { + constructor(public exists: boolean) { + super(); + } +} + +export const getUsersAction = () => async (dispatcher: Dispatch) => { + const users = await sitedocDataService.getAllUsers(); + dispatcher(new SetAllUsersAction(users)); +}; \ No newline at end of file diff --git a/features/sdnr/odlux/odlux/apps/networkMapApp/src/app.tsx b/features/sdnr/odlux/odlux/apps/networkMapApp/src/app.tsx new file mode 100644 index 0000000..a897870 --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/networkMapApp/src/app.tsx @@ -0,0 +1,166 @@ +/** + * ============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 React, { FC, useEffect } from 'react'; +import { useLocation } from 'react-router-dom'; + +import SplitPane from 'react-split-pane'; +import makeStyles from '@mui/styles/makeStyles'; + +import { useApplicationDispatch, useSelectApplicationState } from '../../../framework/src/flux/connect'; + +import { Map } from './components/map/map'; +import Details from './components/details/details'; +import { RemoveHighlightingAction, SetCoordinatesAction, updateZoomAction } from './actions/mapActions'; +import { ClearHistoryAction, LoadNetworkElementDetails } from './actions/detailsAction'; + +const useQuery = () => { + return new URLSearchParams(useLocation().search); +}; + +const useStyles = makeStyles({ + networkMap: { + position: 'relative', + flex: 1, + height: '100%', + }, + resizer: { + background: '#000', + opacity: 0.2, + zIndex: 1, + MozBoxSizing: 'border-box', + WebkitBoxSizing: 'border-box', + boxSizing: 'border-box', + MozBackgroundClip: 'padding', + WebkitBackgroundClip: 'padding', + backgroundClip: 'padding-box', + + '&:hover': { + WebkitTransition: 'all 2s ease', + transition: 'all 2s ease', + }, + + '&.horizontal': { + height: 11, + margin: '-5px 0', + borderTop: '5px solid rgba(255, 255, 255, 0)', + borderBottom: '5px solid rgba(255, 255, 255, 0)', + cursor: 'row-resize', + width: '100%', + }, + + '&.horizontal:hover': { + borderTop: '5px solid rgba(0, 0, 0, 0.5)', + borderBottom: '5px solid rgba(0, 0, 0, 0.5)', + }, + + '&.vertical': { + width: 11, + margin: '0 -5px', + borderLeft: '5px solid rgba(255, 255, 255, 0)', + borderRight: '5px solid rgba(255, 255, 255, 0)', + cursor: 'col-resize', + }, + + '&.vertical:hover': { + borderLeft: '5px solid rgba(0, 0, 0, 0.5)', + borderRight: '5px solid rgba(0, 0, 0, 0.5)', + }, + + '&.disabled': { + cursor: 'not-allowed', + }, + + '&.disabled:hover': { + borderColor: 'transparent', + }, + }, +}); + +const MainView: FC = () => { + + const query = useQuery(); + + const [ newLatParam, newLonParam ] = query.get('center')?.split(',') ?? []; + const newZoomParam = query.get('zoom'); + + const newLat = newLatParam ? Number(newLatParam).toFixed(4) : undefined; + const newLon = newLonParam ? Number(newLonParam).toFixed(4) : undefined; + const newZoom = newZoomParam ? Number(newZoomParam).toFixed(2) : undefined; + + const newSiteId = query.get('siteId'); + const newLinkId = query.get('linkId'); + const newServiceId = query.get('serviceId'); + + const { lat, lon, zoom } = useSelectApplicationState(state => state.network.map.coordinates); + const selectedLink = useSelectApplicationState(state => state.network.map.selectedLink); + const selectedSite = useSelectApplicationState(state => state.network.map.selectedSite); + const selectedService = useSelectApplicationState(state => state.network.map.selectedService); + + const dispatch = useApplicationDispatch(); + + useEffect(() => { + const coordinateHasChanged = (newLat !== lat) || (newLon !== lon) || (newZoom !== zoom); + const atLeastOneCoordinateIsDefined = newLat || newLon || newZoom; + + if (coordinateHasChanged && atLeastOneCoordinateIsDefined) { + dispatch(new SetCoordinatesAction(newLat || lat, newLon || lon, newZoom || zoom)); + } + + const handleSelectedElement = async () => { + // network element has changed + if (!newSiteId && !newLinkId && !newServiceId) { + dispatch(new ClearHistoryAction()); + dispatch(new RemoveHighlightingAction()); + return; + } else if (newSiteId) { + if (selectedSite?.properties.id !== Number(newSiteId) || coordinateHasChanged) { + await dispatch(LoadNetworkElementDetails('site', newSiteId, !newLat && !newLon)); + } + } else if (newLinkId) { + if (selectedLink?.properties.id !== Number(newLinkId) || coordinateHasChanged) { + await dispatch(LoadNetworkElementDetails('link', newLinkId, !newLat && !newLon)); + } + } else if (newServiceId) { + if (selectedService?.properties.id !== Number(newServiceId) || coordinateHasChanged) { + await dispatch(LoadNetworkElementDetails('service', newServiceId, !newLat && !newLon)); + } + } + + if (newZoom) { + dispatch(updateZoomAction(newZoom)); + } + }; + + handleSelectedElement(); + + }, [newLat, newLon, newZoom, newSiteId, newLinkId, newServiceId]); + + const styles = useStyles(); + + return ( +
+ + +
+ +
+ ); +}; + +export default MainView; diff --git a/features/sdnr/odlux/odlux/apps/networkMapApp/src/assets/icons/networkMapAppIcon.svg b/features/sdnr/odlux/odlux/apps/networkMapApp/src/assets/icons/networkMapAppIcon.svg new file mode 100644 index 0000000..1abc667 --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/networkMapApp/src/assets/icons/networkMapAppIcon.svg @@ -0,0 +1,26 @@ + + + + + + + + + + + diff --git a/features/sdnr/odlux/odlux/apps/networkMapApp/src/components/customize/networkMapSetup.tsx b/features/sdnr/odlux/odlux/apps/networkMapApp/src/components/customize/networkMapSetup.tsx new file mode 100644 index 0000000..e6210ed --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/networkMapApp/src/components/customize/networkMapSetup.tsx @@ -0,0 +1,311 @@ +/** + * ============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 React, { useEffect, useRef, useState, ChangeEvent, MouseEvent } from 'react'; + +import maplibregl from 'maplibre-gl'; +import 'maplibre-gl/dist/maplibre-gl.css'; + +import makeStyles from '@mui/styles/makeStyles'; +import Typography from '@mui/material/Typography/Typography'; +import TextField from '@mui/material/TextField/TextField'; +import Grid from '@mui/material/Grid/Grid'; +import Slider from '@mui/material/Slider/Slider'; +import InputLabel from '@mui/material/InputLabel/InputLabel'; +import FormControlLabel from '@mui/material/FormControlLabel/FormControlLabel'; +import Button from '@mui/material/Button/Button'; +import MenuItem from '@mui/material/MenuItem/MenuItem'; +import Select from '@mui/material/Select/Select'; +import Switch from '@mui/material/Switch/Switch'; + +import { useApplicationDispatch, useSelectApplicationState } from '../../../../../framework/src/flux/connect'; +import { requestRest } from '../../../../../framework/src/services/restService'; +import { IApplicationStoreState } from '../../../../../framework/src/store/applicationStore'; +import { SettingsComponentProps } from '../../../../../framework/src/models/settings'; + +import { OSM_STYLE } from '../../config'; +import { updateSettings } from '../../actions/settingsAction'; +import { NetworkMapSettings } from '../../model/settings'; +import mapLayerService from '../../utils/mapLayers'; + +import { ThemeEntry } from './themeElement'; + +const defaultBoundingBox = '12.882544785787754,52.21421979821472,13.775455214211949,52.80406241672602'; + +const useStyles = makeStyles({ + sectionMargin: { + marginTop: '30px', + marginBottom: '15px', + }, + elementMargin: { + marginLeft: '10px', + }, + settingsTable: { + display: 'flex', flexDirection: 'row', flexGrow: 1, height: '100%', position: 'relative', + }, + settingsRow: { + width: '60%', flexDirection: 'column', position: 'relative', marginRight: '15px', + }, +}); + +type NetworkMapSetupProps = SettingsComponentProps; + +const NetworkMapSetup = (props: NetworkMapSetupProps) => { + + const mapRef = useRef(); + const mapContainerRef = useRef(null); + + const mapSettings = useSelectApplicationState((state: IApplicationStoreState) => state.network.settings.mapSettings); + const mapThemes = useSelectApplicationState((state: IApplicationStoreState) => state.network.settings.themes); + + const dispatch = useApplicationDispatch(); + const updateMapSettings = (newMapSettings: NetworkMapSettings) => dispatch(updateSettings(newMapSettings)); + + const [opacity, setOpacity] = useState(Number(mapSettings?.tileOpacity) || 100); + const [theme, setTheme] = useState(mapSettings?.styling?.theme || ''); + const [latitude, setLatitude] = useState(Number(mapSettings?.startupPosition?.latitude) || 52.5); + const [longitude, setLongitude] = useState(Number(mapSettings?.startupPosition?.longitude) || 13.35); + const [zoom, setZoom] = useState(Number(mapSettings?.startupPosition?.zoom) || 10); + const [areIconsEnabled, setEnableIcons] = useState(mapSettings?.areIconsEnabled || true); + + // used to make opacity available within the map event-listeners + // (hook state values are snapshotted at initialization and not updated afterwards / only updated inside other hooks, thus use a ref here) + const myOpacityRef = useRef(opacity); + const setOpacityState = (data: any) => { + myOpacityRef.current = data; + setOpacity(data); + }; + + const updateSampleData = () => { + const map = mapRef.current; + if (!map) { + return; + } + + // get data of bounding box from networkmap + + const links = requestRest('/topology/network/links/geojson/' + defaultBoundingBox); + const sites = requestRest('/topology/network/sites/geojson/' + defaultBoundingBox); + + Promise.all([links, sites]).then(results => { + if (map.getSource('lines')) { + (map.getSource('lines') as maplibregl.GeoJSONSource).setData(results[0]); + } + + if (map.getSource('points')) { + (map.getSource('points') as maplibregl.GeoJSONSource).setData(results[1]); + } + + if (map.getSource('selectedPoints')) { + (map.getSource('selectedPoints') as maplibregl.GeoJSONSource).setData(results[1].features[0]); + } + }); + }; + + const recenterMap = () => { + const map = mapRef.current; + if (map && !isNaN(latitude) && !isNaN(longitude) && !isNaN(zoom)) + map.flyTo({ + center: [ + longitude, + latitude, + ], zoom: zoom, + essential: false, + }); + }; + + const setState = () => { + const map = mapRef.current; + if (!map) { + return; + } + + if (mapSettings?.styling) { + setTheme(mapSettings.styling.theme); + mapLayerService.changeTheme(map, mapSettings.styling.theme); + } + + if (mapSettings?.areIconsEnabled !== undefined) { + setEnableIcons(mapSettings!.areIconsEnabled); + } + + const propOpacity = mapSettings?.tileOpacity; + if (propOpacity) { + setOpacityState(propOpacity); + } + }; + + const styles = useStyles(); + const currentTheme = mapThemes.find(el => el.key === theme); + + useEffect(() => { + mapLayerService.availableThemes = mapThemes; + mapRef.current = new maplibregl.Map({ + container: mapContainerRef.current!, + style: OSM_STYLE as any, + center: [longitude, latitude], + zoom: zoom, + }); + + mapRef.current.on('load', () => { + mapLayerService.addBaseSources(mapRef.current!, null, null, null); + if (mapSettings?.styling?.theme !== theme) { + mapLayerService.addBaseLayers(mapRef.current!); + } else { + mapLayerService.addBaseLayers(mapRef.current!); + } + mapLayerService.changeMapOpacity(mapRef.current!, myOpacityRef.current); + updateSampleData(); + }); + + mapRef.current.on('moveend', () => { + const center = mapRef.current!.getCenter(); + setZoom(Number(mapRef.current!.getZoom().toFixed(4))); + setLatitude(Number(center.lat.toFixed(4))); + setLongitude(Number(center.lng.toFixed(4))); + }); + + }, []); + + useEffect(() => { + recenterMap(); + }, [latitude, longitude, zoom]); + + useEffect(() => { + setState(); + }, [mapSettings]); + + const handleOpacityChange = (event: Event, newValue: number) => { + setOpacity(newValue); + mapLayerService.changeMapOpacity(mapRef.current!, newValue); + }; + + const handleShowIconsChange = (event: ChangeEvent, newValue: boolean) => { + setEnableIcons(newValue); + }; + + const handleChangeTheme = (e: any) => { + + const newTheme = e.target.value; + setTheme(newTheme); + mapLayerService.changeTheme(mapRef.current!, newTheme); + }; + + const handleCancel = (e: MouseEvent) => { + e.preventDefault(); + props.onClose(); + }; + + const handleSaveSettings = async (e: MouseEvent) => { + e.preventDefault(); + + const updatedSettings: NetworkMapSettings = { + tileOpacity: opacity.toString(), + styling: { theme: theme }, + areIconsEnabled: areIconsEnabled, + startupPosition: { + latitude: latitude.toString(), + longitude: longitude.toString(), + zoom: zoom.toString(), + }, + }; + + await updateMapSettings(updatedSettings); + props.onClose(); + }; + + /** + * Style property names to readable text + * @param text property name + * @returns readable text + */ + const styleText = (text: string) => { + const textParts = text.split(/(?=[A-Z])/); //split on uppercase character + const newText = textParts.join(' '); + return newText.charAt(0).toUpperCase() + newText.slice(1); + }; + + return ( + <> +

Settings

+
+
+ Startup Position +
+ setLatitude(e.target.value as any)} style={{ marginLeft: 10 }} label="Latitude" /> + setLongitude(e.target.value as any)} style={{ marginLeft: 5 }} label="Longitude" /> + setZoom(e.target.value as any)} style={{ marginLeft: 5 }} label="Zoom" /> +
+ + + Tile Opacity + + + 0 + + + + 100 + + + + Display icons + + } + label="Show icons" + labelPlacement="end" + /> + + Style of properties + + Theme + + {currentTheme && +
+ { + ['site', 'selectedSite', 'fiberLink', 'microwaveLink'].map(el => ( + + )) + } +
+ } +
+ + +
+
+
+
+ + ); +}; + +NetworkMapSetup.displayName = 'NetworkMapSetup'; + +export { NetworkMapSetup }; diff --git a/features/sdnr/odlux/odlux/apps/networkMapApp/src/components/customize/themeElement.tsx b/features/sdnr/odlux/odlux/apps/networkMapApp/src/components/customize/themeElement.tsx new file mode 100644 index 0000000..8841723 --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/networkMapApp/src/components/customize/themeElement.tsx @@ -0,0 +1,48 @@ +/** + * ============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 React from 'react'; +import { Typography } from '@mui/material'; + +type ThemeEntryProps = { + color: string; + text: string; +}; + +const ThemeEntry = (props: ThemeEntryProps) => { + const circleStyle = { + padding: 10, + margin: 20, + backgroundColor: props.color, + borderRadius: '50%', + width: 10, + height: 10, + left: 0, + top: 0, + }; + + return
+
+ {props.text} +
; + +}; + +ThemeEntry.displayName = 'ThemeEntry'; + +export { ThemeEntry }; \ No newline at end of file diff --git a/features/sdnr/odlux/odlux/apps/networkMapApp/src/components/denseTable.tsx b/features/sdnr/odlux/odlux/apps/networkMapApp/src/components/denseTable.tsx new file mode 100644 index 0000000..b2770d5 --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/networkMapApp/src/components/denseTable.tsx @@ -0,0 +1,159 @@ +/** + * ============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 React, { FC, MouseEvent } from 'react'; +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'; + +import Tooltip from '@mui/material/Tooltip'; +import Button from '@mui/material/Button'; +import makeStyles from '@mui/styles/makeStyles'; + +const useStyles = makeStyles({ + denseTable: { + borderRadius: '0px', + }, + button: { + margin: 0, + padding: '6px 6px', + minWidth: 'unset', + }, +}); + +type DenseTableProps = { + actions?: boolean; + headers: string[]; + height: number; + hover: boolean; + ariaLabelRow: string; + ariaLabelColumn?: string[]; + verticalTable?: boolean; + navigate?(applicationName: string, path?: string): void; + onLinkClick?(id: string): void; data: any[]; + onClick?(id: string): void; +}; + +const DenseTable: FC = (props) => { + const { + ariaLabelRow, + data, + headers, + height, + hover, + ariaLabelColumn, + onClick = () => undefined, + navigate = () => undefined, + verticalTable, + } = props; + + const styles = useStyles(); + + const handleClick = (event: MouseEvent, id: string) => { + event.preventDefault(); + onClick(id); + }; + + return ( + +
+ + + + { + headers.map((tableHeader) => ({tableHeader})) + } + + + + {data.map((row, index) => { + + const values = (typeof row === 'string' || row instanceof String) ? [row] : Object.keys(row).map(function (e) { return row[e]; }); + + return ( + handleClick(e, row.name)}> + { + values.map((value, i) => { + if (value !== undefined) { + + if (!verticalTable) { + const ariaLabel = ariaLabelColumn === undefined ? headers[i].toLowerCase() : ariaLabelColumn[i]; + if (ariaLabel.length > 0) { + return {value}; + } else { + return {value}; + } + } else { + // skip adding aria label to 'header' column + if (i === 0) { + return {value}; + } else { + const ariaLabel = props.ariaLabelColumn === undefined ? props.headers[index].toLowerCase() : props.ariaLabelColumn[index]; + return {value}; + } + } + } else + return null; + }) + } + { + props.actions && +
+ + + + + + +
+
+ } +
); + }) + } +
+
+
+
+ ); +}; + +DenseTable.displayName = 'DenseTable'; + +export default DenseTable; \ No newline at end of file diff --git a/features/sdnr/odlux/odlux/apps/networkMapApp/src/components/details/details.tsx b/features/sdnr/odlux/odlux/apps/networkMapApp/src/components/details/details.tsx new file mode 100644 index 0000000..b955789 --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/networkMapApp/src/components/details/details.tsx @@ -0,0 +1,125 @@ +/** + * ============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 React from 'react'; + +import makeStyles from '@mui/styles/makeStyles'; +import Breadcrumbs from '@mui/material/Breadcrumbs'; +import LinkComponent from '@mui/material/Link'; +import Paper from '@mui/material/Paper'; +import Typography from '@mui/material/Typography'; + +import { useApplicationDispatch, useSelectApplicationState } from '../../../../../framework/src/flux/connect'; +import { AddToHistoryAction, ClearHistoryAction, SelectElementAction } from '../../actions/detailsAction'; +import { highlightElementAction } from '../../actions/mapActions'; + +import { HistoryEntry } from '../../model/historyEntry'; +import { Link, Site, Service, isService, isSite, isLink } from '../../model/topologyTypes'; + +import detailsUtils from '../../utils/detailsUtils'; + +import LinkDetails from './linkDetails'; +import ServiceDetails from './serviceDetails'; +import SiteDetails from './siteDetails'; + +const useStyles = makeStyles({ + mapDetails: { + background: '#bbbdbf', + padding: '20px', + alignSelf: 'stretch', + flex: '1 1 0', + }, + container: { + marginLeft: '15px', + marginTop: '5px', + }, + message: { + marginTop: '5px', + }, +}); + +const Details: React.FC = () => { + + const data = useSelectApplicationState(state => state.network.details.data); + const breadcrumbs = useSelectApplicationState(state => state.network.details.history); + + const dispatch = useApplicationDispatch(); + const displayElement = (element: Site | Link | Service) => dispatch(new SelectElementAction(element)); + const highlightElementOnMap = (element: Link | Site | Service) => dispatch(highlightElementAction(element)); + const addHistory = (newEntry: HistoryEntry) => dispatch(new AddToHistoryAction(newEntry)); + const clearHistory = () => dispatch(new ClearHistoryAction()); + + const [message, _setMessage] = React.useState('No data selected.'); + + const onLinkClick = async (id: string) => { + detailsUtils.loadData('link', id, (result) => { + displayElement(result); + highlightElementOnMap(result); + addHistory({ id: data!.name, data: data! as any }); + }); + }; + + const backClick = (e: any) => { + displayElement(breadcrumbs[0].data); + highlightElementOnMap(breadcrumbs[0].data); + clearHistory(); + e.preventDefault(); + }; + + const renderDetailPanel = (element: Site | Link | Service) => { + if (isSite(element)) { + return ; + } else if (isService(element)) { + return ; + } else if (isLink(element)) { + return ; + } + return null; + }; + + const panelId = data !== null ? (isSite(data) ? 'site-details-panel' : 'link-details-panel') : 'details-panel'; + + const styles = useStyles(); + + return ( +
+ + { breadcrumbs.length > 0 + ? ( + + + {breadcrumbs[0].id} + + + {data?.name} + + + ) + : null + } + { data + ? renderDetailPanel(data) + : {message} + } + +
); +}; + +Details.displayName = 'MapDetails'; + +export default Details; \ No newline at end of file diff --git a/features/sdnr/odlux/odlux/apps/networkMapApp/src/components/details/linkDetails.tsx b/features/sdnr/odlux/odlux/apps/networkMapApp/src/components/details/linkDetails.tsx new file mode 100644 index 0000000..4ecfe44 --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/networkMapApp/src/components/details/linkDetails.tsx @@ -0,0 +1,152 @@ +/** + * ============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 React from 'react'; + +import { AppBar, Button, Tab, Tabs, TextField, Typography } from '@mui/material'; + +import { useApplicationDispatch } from '../../../../../framework/src/flux/connect'; +import { AddErrorInfoAction } from '../../../../../framework/src/actions/errorActions'; + +import { Link } from '../../model/topologyTypes'; +import { LatLonToDMS } from '../../utils/mapUtils'; +import DenseTable from '../denseTable'; + +type MandatoryParametersLOS = { lat: number | null; lon: number | null; amsl: number | null; antennaHeight: number | null }; + +type LinkProps = { link: Link }; + +const LinkDetails: React.FC = (props) => { + + const dispatch = useApplicationDispatch(); + const showErrorMessage = (message: string) => dispatch(new AddErrorInfoAction({ title: 'Problem', message: message })); + + const [height, setHeight] = React.useState(330); + + const handleResize = () => { + + // table does not adhere to flex-box dimensions, so set height explicit + + const el = document.getElementById('link-details-panel')?.getBoundingClientRect(); + const el2 = document.getElementById('link-site-details')?.getBoundingClientRect(); + + if (el && el2) { + if (props.link?.feature?.properties?.subType === 'microwave') + setHeight(el!.height - el2!.y - 80); + else + setHeight(el!.height - el2!.y + 20); + } + }; + + //on mount + React.useEffect(() => { + handleResize(); + + //window.addEventListener("resize", handleResize); + }, []); + + React.useEffect(() => { + handleResize(); + }, [props.link]); + + const getEmptyProperties = (hint:string, data: any) => { + const entries = Object.entries(data); + const emptyProperties: string[] = entries.filter(propery => propery[1] === null || propery[1] === undefined).map(properties => hint + properties[0]); + return emptyProperties; + }; + + const checkLOSMandatoryParameters = (checkObject: { siteA: MandatoryParametersLOS; siteB: MandatoryParametersLOS }) => { + + const emptyA = getEmptyProperties('SiteA:', checkObject.siteA); + const emptyB = getEmptyProperties('SiteB:', checkObject.siteB); + const emptyValues = [...new Set([...emptyA, ...emptyB])]; + return emptyValues; + }; + + const handleCalculateLinkClick = (e: React.MouseEvent) => { + e.preventDefault(); + + const linkId = props.link.id; + const baseUrl = window.location.pathname.split('#')[0]; + window.open(`${baseUrl}#/microwave/calculateLink/?linkId=${linkId}`); + }; + + const handleLineOfSightClick = (e: React.MouseEvent) => { + e.preventDefault(); + + const siteA = props.link.siteA; + const siteB = props.link.siteB; + + const checkObject = { + siteA: { lat: siteA.lat, lon: siteA.lon, amsl: siteA.amsl, antennaHeight: siteA.antenna?.height }, + siteB: { lat: siteB.lat, lon: siteB.lon, amsl: siteB.amsl, antennaHeight: siteB.antenna?.height }, + }; + + var emptyValues = checkLOSMandatoryParameters(checkObject); + + if (emptyValues.length === 0) { + + let heightPart = `&amslA=${siteA.amsl}&antennaHeightA=${siteA.antenna.height}&amslB=${siteB.amsl}&antennaHeightB=${siteB.antenna.height}`; + const baseUrl = window.location.pathname.split('#')[0]; + window.open(`${baseUrl}#/microwave/lineofsightMap/los?lat1=${siteA.lat}&lon1=${siteA.lon}&lat2=${siteB.lat}&lon2=${siteB.lon}${heightPart}`); + } else { + showErrorMessage('Line of Sight App cannot be opened. Data is missing: ' + emptyValues); + } + }; + + const amslAvailable = props.link.siteA.amsl && props.link.siteB.amsl; + + const data = [ + { name: 'Site Id', val1: props.link.siteA.siteId, val2: props.link.siteB.siteId }, + { name: 'Site Name', val1: props.link.siteA.siteName, val2: props.link.siteB.siteName }, + { name: 'Latitude', val1: LatLonToDMS(props.link.siteA.lat), val2: LatLonToDMS(props.link.siteB.lat) }, + { name: 'Longitude', val1: LatLonToDMS(props.link.siteA.lon, true), val2: LatLonToDMS(props.link.siteB.lon, true) }, + props.link?.feature?.properties?.subType == 'microwave' && amslAvailable !== null && { name: 'Amsl in m', val1: props.link.siteA.amsl, val2: props.link.siteB.amsl }, + props.link?.feature?.properties?.subType == 'microwave' && amslAvailable !== null && props.link.siteA.antenna !== null && { name: 'Antenna height in m', val1: props.link.siteA.antenna.height, val2: props.link.siteB.antenna.height }, + props.link.siteA.azimuth != null && props.link.siteB.azimuth != null && { name: 'Azimuth in °', val1: props.link.siteA.azimuth.toFixed(2), val2: props.link.siteB.azimuth.toFixed(2) }, + ]; + + return (
+ + {props.link.id} + { + props.link.name !== null ? + + : null + } + 0 ? props.link.operator : 'unknown'} label="Operator" /> + + + {props.link.polarization ? : null} + + + + + + + + { + props.link?.feature?.properties?.subType === 'microwave' && <> + + + + } +
); +}; + +export default LinkDetails; \ No newline at end of file diff --git a/features/sdnr/odlux/odlux/apps/networkMapApp/src/components/details/serviceDetails.tsx b/features/sdnr/odlux/odlux/apps/networkMapApp/src/components/details/serviceDetails.tsx new file mode 100644 index 0000000..48a8a37 --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/networkMapApp/src/components/details/serviceDetails.tsx @@ -0,0 +1,48 @@ +/** + * ============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 React from 'react'; + +import Typography from '@mui/material/Typography/Typography'; +import TextField from '@mui/material/TextField/TextField'; + +import { Service } from '../../model/topologyTypes'; + +type ServiceDetailsProps = { service: Service }; + +const ServiceDetails: React.FC = (props) => { + + return ( +
+ {props.service.id} + + + + { + props.service.backupForServiceId && + + } + + + + + +
); +}; + +export default ServiceDetails; \ No newline at end of file diff --git a/features/sdnr/odlux/odlux/apps/networkMapApp/src/components/details/siteDetails.tsx b/features/sdnr/odlux/odlux/apps/networkMapApp/src/components/details/siteDetails.tsx new file mode 100644 index 0000000..34d9180 --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/networkMapApp/src/components/details/siteDetails.tsx @@ -0,0 +1,258 @@ +/** + * ============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 React from 'react'; + +import LanOutlinedIcon from '@mui/icons-material/LanOutlined'; +import { AppBar, Button, IconButton, Tab, Tabs, TextField, Typography } from '@mui/material'; + +import { useApplicationDispatch, useSelectApplicationState } from '../../../../../framework/src/flux/connect'; +import { NavigateToApplication } from '../../../../../framework/src/actions/navigationActions'; +import { requestRest } from '../../../../../framework/src/services/restService'; + +import { CheckDeviceList, InitializeLoadedDevicesAction } from '../../actions/detailsAction'; +import { Address, Device, Site } from '../../model/topologyTypes'; +import StadokSite from '../../model/stadokSite'; +import { LatLonToDMS } from '../../utils/mapUtils'; + +import DenseTable from '../denseTable'; +import StadokDetailsPopup from '../stadok/stadokDetailsPopup'; + +const buildAddress = (address: Address) => { + switch (address.country) { + case 'de': + return `${address.streetAndNr}, ${address.zipCode !== null ? address.zipCode : ''} ${address.city}`; + + case 'us': + return `${address.streetAndNr}, ${address.city} ${address.zipCode !== null ? address.zipCode : ''}`; + + default: + console.log('address formatting for country {' + address.country + '} not recognized, defaulting.'); + return `${address.streetAndNr}, ${address.zipCode !== null ? address.zipCode : ''} ${address.city}`; + } +}; + +type PanelId = 'links' | 'nodes'; +type LinkRow = { name: string; azimuth?: string }; +type DeviceRow = { id: string; type: string; name: string; manufacturer: string; owner: string; status?: string; port: number[] }; + +type SiteDetailProps = { + site: Site; + onLinkClick(id: string): void; +}; + +const SiteDetails: React.FC = (props) => { + + const updatedDevices = useSelectApplicationState(state => state.network.details.checkedDevices); + const isSitedocReachable = useSelectApplicationState(state => state.network.details.isSitedocReachable); + + const dispatch = useApplicationDispatch(); + const initializeDevices = (devices: Device[]) => dispatch(new InitializeLoadedDevicesAction(devices)); + const loadDevices = async (networkElements: Device[]) => dispatch(CheckDeviceList(networkElements)); + const navigateToApplication = (applicationName: string, path?: string) => dispatch(new NavigateToApplication(applicationName, path, '')); + + const [value, setValue] = React.useState('links'); + const [height, setHeight] = React.useState(330); + const [openPopup, setOpenPopup] = React.useState(false); + const [staSite, setStaSite] = React.useState(null); + + const hasFurtherInfo = props.site.furtherInformation !== null && props.site.furtherInformation.length > 0; + + const handleResize = () => { + //table currently likes to overflow the available area -> force set a height to the container + const el = document.getElementById('site-details-panel')?.getBoundingClientRect(); + const el2 = document.getElementById('site-tabs')?.getBoundingClientRect(); + + if (el && el2) { + if (!hasFurtherInfo) { + setHeight(el!.height - el2!.y - 10); + } else { + setHeight(el!.height - el2!.y - 65); + } + } + }; + + //on mount + React.useEffect(() => { + handleResize(); + window.addEventListener('resize', () => { handleResize(); }); + + return () => { + window.removeEventListener('resize', () => { handleResize(); }); + }; + }, []); + + // on update + React.useEffect(() => { + + if (props.site.devices !== null && props.site.devices.length > 0) { + + //TODO: why two? + initializeDevices(props.site.devices); + loadDevices(props.site.devices); + } + handleResize(); + + }, [props.site]); + + const onHandleTabChange = (event: React.SyntheticEvent, newValue: PanelId) => { + setValue(newValue); + }; + + const getFurtherInformation = (url: string) => { + + const request = requestRest(url, { method: 'GET' }); + request.then(result => { + if (result) { + setStaSite(result); + setOpenPopup(true); + } else { + console.error(result); + } + }); + }; + + const closePopup = () => { + setOpenPopup(false); + }; + + //prepare link table + + let hasAzimuth = false; + const linkRows: LinkRow[] = props.site.links?.map(link => { + if (link.azimuth !== null) { + hasAzimuth = true; + return { name: link.name, azimuth: link.azimuth.toFixed(2) }; + } else { + return { name: link.name ? link.name : link.id.toString() }; + } + }); + + const linkTableHeader = hasAzimuth ? ['Link Name', 'Azimuth in °'] : ['Link Name']; + + //prepare device table + const deviceRows: DeviceRow[] = updatedDevices?.map(device => { + return { + id: device.id, + name: device.name, + type: device.type ? device.type : 'unknown', + status: (device.status?.length == 0 || device.status === undefined || device.status === null) ? 'Not Connected' : device.status, + manufacturer: device.manufacturer, + owner: device.owner, + port: device.port, + }; + }); + + const addressString = props.site.address == null ? null : buildAddress(props.site.address); + + return (
+ + {props.site.id} +
+ { + const baseUrl = window.location.pathname.split('#')[0]; + const siteId = props.site.id + ''; + const url = `${baseUrl}#/siteManager/treeview/${siteId}`; + window.open(url); + }}> + + +
+ + { + props.site.operator !== '' && props.site.operator !== null ? + : + + } + { + props.site?.feature?.properties?.layer !== undefined && props.site?.feature?.properties?.layer.length > 0 && + + } + { + addressString !== null && + + } + { + props.site.heightAmslInMeters !== undefined && props.site.heightAmslInMeters > 0 && + + } + { + props.site.antennaHeightAmslInMeters !== undefined && props.site.antennaHeightAmslInMeters > 0 && + + } + + + + + + + + + { + value === 'links' && + <> + { + props.site.links == null && + No links available. + } + { + props.site.links?.length > 0 && + + } + + } + { + value === 'nodes' && + <> + { + props.site.devices === null && + No nodes available. + } + { + props.site.devices?.length > 0 && updatedDevices !== null && + + } + + } + { + isSitedocReachable && + <> + { + hasFurtherInfo && + + } + + } + { + staSite !== null && openPopup && + } +
+ ); +}; + + + +export default SiteDetails; \ No newline at end of file diff --git a/features/sdnr/odlux/odlux/apps/networkMapApp/src/components/map/connectionInfo.tsx b/features/sdnr/odlux/odlux/apps/networkMapApp/src/components/map/connectionInfo.tsx new file mode 100644 index 0000000..e04906d --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/networkMapApp/src/components/map/connectionInfo.tsx @@ -0,0 +1,88 @@ +/** + * ============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 React from 'react'; +import { faExclamationTriangle } from '@fortawesome/free-solid-svg-icons'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; + +import Paper from '@mui/material/Paper'; +import Typography from '@mui/material/Typography'; +import makeStyles from '@mui/styles/makeStyles'; +import Button from '@mui/material/Button'; + +import { useSelectApplicationState, useApplicationDispatch } from '../../../../../framework/src/flux/connect'; +import { IsTileServerReachableAction, IsTopologyServerReachableAction } from '../../actions/connectivityAction'; + +const useStyles = makeStyles({ + connectionInfo: { + padding: 5, + position: 'absolute', + top: 160, + width: 230, + left: '40%', + zIndex: 1, + }, + container: { + display: 'flex', + flexDirection: 'column', + }, + errorIcon:{ + 'alignSelf': 'center', + marginBottom: 5, + }, +}); + +const ConnectionInfo: React.FC = () => { + + const dispatch = useApplicationDispatch(); + + const isTopoServerReachable = useSelectApplicationState(state => state.network.connectivity.isTopologyServerAvailable); + const isTileServerReachable = useSelectApplicationState(state => state.network.connectivity.isTileServerAvailable); + + const handleClose = () => { + dispatch(new IsTopologyServerReachableAction(true)); + dispatch(new IsTileServerReachableAction(true)); + }; + + const styles = useStyles(); + + return ((!isTopoServerReachable || !isTileServerReachable) ? + +
+
+ Connection Error +
+ { + !isTileServerReachable + ? Tile data can't be loaded. + : null + } + { + !isTopoServerReachable + ? < Typography variant="body1" aria-label="network-data-unavailable"> Network data can't be loaded. + : null + } + +
+
: null + ); +}; + +ConnectionInfo.displayName = 'ConnectionInfo'; + +export { ConnectionInfo }; \ No newline at end of file diff --git a/features/sdnr/odlux/odlux/apps/networkMapApp/src/components/map/filterBar.tsx b/features/sdnr/odlux/odlux/apps/networkMapApp/src/components/map/filterBar.tsx new file mode 100644 index 0000000..69cefc8 --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/networkMapApp/src/components/map/filterBar.tsx @@ -0,0 +1,88 @@ +import React, { FC, useEffect, useState } from 'react'; +import { useApplicationDispatch, useSelectApplicationState } from '../../../../../framework/src/flux/connect'; + +import FilterIcon from '@mui/icons-material/FilterList'; +import makeStyles from '@mui/styles/makeStyles'; + +import { SetFilterValueAction } from '../../actions/filterActions'; +import Paper from '@mui/material/Paper'; +import InputBase from '@mui/material/InputBase'; +import Divider from '@mui/material/Divider'; +import IconButton from '@mui/material/IconButton'; + +const useStyles = makeStyles({ + filterBar: { + padding: '2px 4px', + position: 'absolute', + display: 'flex', + alignItems: 'center', + top: 70, + marginLeft: 5, + width: 400, + zIndex: 1, + }, + input: { + flex: 1, + marginLeft: 5, + }, + iconButton: { + padding: 10, + }, + divider: { + height: 28, + margin: 4, + }, +}); + +const FilterBar: FC = () => { + + const filterValue = useSelectApplicationState(state => state.network.filter.value); + + const dispatch = useApplicationDispatch(); + const onFilterChange = (value: string) => dispatch(new SetFilterValueAction(value)); + + const [filterText, setFilterText] = useState(filterValue); + + useEffect(() => { + setFilterText(filterValue); + }, [filterValue]); + + const handleFilterApply = () => { + onFilterChange(filterText); + }; + + const styles = useStyles(); + + return ( + + setFilterText(e.target.value)} + onKeyPress={(e) => { + if (e.key === 'Enter') { + e.preventDefault(); + e.stopPropagation(); + handleFilterApply(); + } + }} + readOnly={false} + /> + + + + + + ); +}; + +FilterBar.displayName = 'FilterBar'; + +export { FilterBar }; diff --git a/features/sdnr/odlux/odlux/apps/networkMapApp/src/components/map/iconSwitch.tsx b/features/sdnr/odlux/odlux/apps/networkMapApp/src/components/map/iconSwitch.tsx new file mode 100644 index 0000000..a74c9ff --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/networkMapApp/src/components/map/iconSwitch.tsx @@ -0,0 +1,101 @@ +/** + * ============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 React, { useEffect } from 'react'; +import { FormControlLabel, Switch } from '@mui/material'; + +import { useApplicationDispatch, useSelectApplicationState } from '../../../../../framework/src/flux/connect'; + +import { SetIconSwitchAction } from '../../actions/mapActions'; +import { updateSettings } from '../../actions/settingsAction'; +import { NetworkMapSettings } from '../../model/settings'; + +type IconSwitchProps = { visible: boolean }; + +const IconSwitch: React.FC = (props) =>{ + + const areIconsEnabled = useSelectApplicationState(state => state.network.map.allowIconSwitch); + const settings = useSelectApplicationState(state => state.network.settings.mapSettings); + + const dispatch = useApplicationDispatch(); + const toggle = (enable:boolean) => dispatch(new SetIconSwitchAction(enable)); + const updateUserSettings = (mapSettings: NetworkMapSettings) => dispatch(updateSettings(mapSettings)); + + //??? TODO: look into please + //use ref to be available within map events + const iconsEnabledRef = React.useRef(areIconsEnabled); + + const setIconsEnabled = (data: boolean) => { + iconsEnabledRef.current = data; + }; + + const handleToggleChanged = () => { + setIconsEnabled(!areIconsEnabled); + toggle(!areIconsEnabled); + }; + + const handleDefaultValue = () => { + const settingsIconsEnabled = settings?.areIconsEnabled; + if (settingsIconsEnabled !== undefined) { + setIconsEnabled(settingsIconsEnabled); + toggle(settingsIconsEnabled); + } + }; + + const saveSettings = (data: any) => { + data.areIconsEnabled = iconsEnabledRef.current; + updateUserSettings(data); + }; + + const saveChanges = () => { + if (settings === null) { + saveSettings({ networkMap:{} }); + } else if (settings && iconsEnabledRef.current !== settings.areIconsEnabled) { + saveSettings(settings); + } + }; + + window.onbeforeunload = () => { //called right before browser refreshes or some such + saveChanges(); + }; + + useEffect(()=>{ + + handleDefaultValue(); + return () => { // executed on unmount + saveChanges(); + }; + }, []); + + useEffect(()=>{ + handleDefaultValue(); + }, [settings?.areIconsEnabled]); + + return ( + props.visible ? + } + label="Show icons" + labelPlacement="end" + /> : null); +}; + +IconSwitch.displayName = 'IconSwitch'; + +export { IconSwitch } ; \ No newline at end of file diff --git a/features/sdnr/odlux/odlux/apps/networkMapApp/src/components/map/layerSelection.tsx b/features/sdnr/odlux/odlux/apps/networkMapApp/src/components/map/layerSelection.tsx new file mode 100644 index 0000000..d00a30d --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/networkMapApp/src/components/map/layerSelection.tsx @@ -0,0 +1,123 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2022 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 } from 'react'; + +import Accordion from '@mui/material/Accordion'; +import AccordionSummary from '@mui/material/AccordionSummary'; +import AccordionDetails from '@mui/material/AccordionDetails'; +import Checkbox from '@mui/material/Checkbox'; +import ExpandMoreIcon from '@mui/icons-material/ExpandMore'; +import FormGroup from '@mui/material/FormGroup'; +import FormControlLabel from '@mui/material/FormControlLabel'; +import Typography from '@mui/material/Typography'; +import makeStyles from '@mui/styles/makeStyles'; + +import { useApplicationDispatch, useSelectApplicationState } from '../../../../../framework/src/flux/connect'; + +import { loadLayers, OpenLayersAction, updateLayerAsyncAction } from '../../actions/mapActions'; + +const useStyles = makeStyles({ + mapLayer: { + position: 'absolute', + display: 'flex', + flexDirection: 'column', + top: 323, + width: 200, + marginLeft: 5, + zIndex: 1, + }, + title:{ + fontWeight: 'bold', + }, +}); + +const MapLayer : FC = () => { + + const elements = useSelectApplicationState(state => state.network.map.layersContainer.elements); + const isOpen = useSelectApplicationState(state => state.network.map.layersContainer.isOpen); + const areFurtherLayersAvailable = useSelectApplicationState(state => state.network.map.layersContainer.areFurtherLayersAvailable); + + const dispatch = useApplicationDispatch(); + const updateLayer = (name: string, displayed: boolean) => dispatch(updateLayerAsyncAction(name, displayed, true)); + const loadMapLayers = () => dispatch(loadLayers()); + const open = (newOpenState: boolean) => dispatch(new OpenLayersAction(newOpenState)); + + React.useEffect(() => { + loadMapLayers(); + }, []); + + const classes = useStyles(); + return ( + open(!isOpen)} className={classes.mapLayer}> + } + aria-label="map-layer-accordion"> + Layers + + + + { + elements.map((el, i) => el.base + ? { updateLayer(el.name, e.target.checked); }} + /> + } + label={el.name} + /> + : null, + ) + } + + { + areFurtherLayersAvailable + ? ( <> + Further Layers + + { + elements.map((el, i) => !el.base + ? { updateLayer(el.name, e.target.checked); }} + /> + } + label={el.name} + /> + : null, + ) + } + + ) + : null + } + + ); +}; + +MapLayer.displayName = 'MapLayer'; + +export default MapLayer; diff --git a/features/sdnr/odlux/odlux/apps/networkMapApp/src/components/map/map.tsx b/features/sdnr/odlux/odlux/apps/networkMapApp/src/components/map/map.tsx new file mode 100644 index 0000000..3f66f3e --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/networkMapApp/src/components/map/map.tsx @@ -0,0 +1,791 @@ +/** + * ============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 React, { FC, memo, useCallback, useEffect, useRef, useState } from 'react'; +import makeStyles from '@mui/styles/makeStyles'; + +import maplibregl from 'maplibre-gl'; +import 'maplibre-gl/dist/maplibre-gl.css'; + +import { useApplicationDispatch, useSelectApplicationState } from '../../../../../framework/src/flux/connect'; +import { handleConnectionChange, IsBusyCheckingConnectivityAction, setTileServerReachableAction, setTopologyServerReachableAction } from '../../actions/connectivityAction'; +import { LoadNetworkElementDetails } from '../../actions/detailsAction'; +import { loadLayers, SetCoordinatesAction, updateStatistics, ZoomToFinishedAction } from '../../actions/mapActions'; +import { OSM_STYLE, URL_API } from '../../config'; +import { BoundingBox } from '../../model/boundingBox'; +import { Coordinate, MapCoordinate } from '../../model/coordinates'; +import { Feature } from '../../model/topologyTypes'; +import { dataService } from '../../services/dataService'; +import { addImages } from '../../services/mapImagesService'; +import mapLayerService from '../../utils/mapLayers'; +import { getUniqueFeatures, increaseBoundingBox } from '../../utils/mapUtils'; +import { ConnectionInfo } from './connectionInfo'; +import { FilterBar } from './filterBar'; +import MapLayer from './layerSelection'; +import MapControl from './mapControl'; +import MapPopup from './mapPopup'; +import SearchBar from './searchBar'; +import Statistics from './statistics'; + +const MAX_ZOOM = 18; +const MIN_ZOOM = 0; + +const LogLevel = +(localStorage.getItem('log.odlux.mapComponent') || 0); + +const NetworkFilterObserver: FC<{ value: string; onDidUpdate: (val: string) => void }> = ({ value, onDidUpdate }) => { + useEffect(() => { + onDidUpdate(value); + }, [value]); + return null; +}; + +type MapSelectionPopup = { + isOpen: boolean; + elements: Feature[]; + type: string; + position: { + left: number; + top: number; + }; +} | null; + +let map: maplibregl.Map; +let lastBoundingBox: maplibregl.LngLatBounds; + +const useStyles = makeStyles({ + map: { + display: 'flex', + position: 'relative', + height: '100%', + }, +}); + +type MapContext = { + furtherLayerNames: string[]; + areLayersLoaded: boolean; + isLoadingInProgress: boolean; + enqueuedBoundingBoxes: maplibregl.LngLatBounds[]; + isInitialLoadOfMap: boolean; +}; + +const Map: FC = memo(() => { + + const mapContext = useRef({ + furtherLayerNames: [], + enqueuedBoundingBoxes: [], + areLayersLoaded: false, + isLoadingInProgress: false, + isInitialLoadOfMap: false, + }); + + const networkFilter = useSelectApplicationState(state => state.network.filter); + const selectedLink = useSelectApplicationState(state => state.network.map.selectedLink); + const selectedSite = useSelectApplicationState(state => state.network.map.selectedSite); + const selectedService = useSelectApplicationState(state => state.network.map.selectedService); + const zoomToElement = useSelectApplicationState(state => state.network.map.zoomToElement); + + const { lat, lon, zoom } = useSelectApplicationState(state => state.network.map.coordinates); + + const isTopologyServerReachable = useSelectApplicationState(state => state.network.connectivity.isTopologyServerAvailable); + const isTileServerReachable = useSelectApplicationState(state => state.network.connectivity.isTileServerAvailable); + const isConnectivityCheckBusy = useSelectApplicationState(state => state.network.connectivity.isBusy); + const showIcons = useSelectApplicationState(state => state.network.map.allowIconSwitch); + const settings = useSelectApplicationState(state => state.network.settings); + const layers = useSelectApplicationState(state => state.network.map.layersContainer.elements); + + const dispatch = useApplicationDispatch(); + + const updateLayers = () => dispatch(loadLayers()); + const loadSelectedElement = (type: string, id: string) => dispatch(LoadNetworkElementDetails(type, id)); + const updateMapPosition = (pLat: string, pLon: string, pZoom: string) => dispatch(new SetCoordinatesAction(pLat, pLon, pZoom)); + const updateMapStatistics = (boundingBox: BoundingBox) => dispatch(updateStatistics(boundingBox)); + const setTileServerLoaded = (reachable: boolean) => dispatch(setTileServerReachableAction(reachable)); + const setTopologyServerLoaded = (reachable: boolean) => dispatch(setTopologyServerReachableAction(reachable)); + const connectionChanged = (newState: boolean) => dispatch(handleConnectionChange(newState)); + const setConnectivityCheck = (done: boolean) => dispatch(new IsBusyCheckingConnectivityAction(done)); + const zoomFinished = () => dispatch(new ZoomToFinishedAction()); + + const mapContainerRef = React.useRef(null); + const mapRef = React.useRef(); + + const [bearing, setBearing] = useState(0); + const [popup, setPopup] = useState(null); + + const updateTheme = () => { + mapLayerService.availableThemes = settings.themes; + if (settings.mapSettings?.styling?.theme) { + mapLayerService.selectedTheme = settings.mapSettings?.styling.theme; + } + }; + + const updateOpacity = () => { + if (settings.mapSettings && settings.mapSettings.tileOpacity) { + mapLayerService.changeMapOpacity(map, Number(settings.mapSettings.tileOpacity)); + } + }; + + const loadGeoJsonAndAddToMapLayer = async (layer: string, url: string) => { + + //No actions used because the geojson data is only used and needed for the map + //maybe move out into another map service + + const response = await dataService.getGeojsonData(url); + + if (response.status === 404 || response.status === 400) { + return true; + } + if (response.status !== 200) { + return false; + } + if (map.getSource(layer)) { + (map.getSource(layer) as maplibregl.GeoJSONSource).setData(response.data); + } + return true; + + }; + + /*** + * Load bounding box data and add it to the map + */ + const loadBBoxDataAndAddToMap = useCallback(async (startLat: number, startLon: number, endLat: number, endLon: number) => { + + if (LogLevel > 3) { + console.log(`MapComponent::loadBBoxDataAndAddToMap - lat: ${startLat}, lon: ${startLon}, lat2: ${endLat}, lon2: ${endLon}, networkFilter: ${networkFilter.value}`); + } + + const servicesLoaded = loadGeoJsonAndAddToMapLayer('services', `${URL_API}/services/geojson/${startLat},${startLon},${endLat},${endLon}?filter=${encodeURIComponent(networkFilter.value)}`); + const linksLoaded = loadGeoJsonAndAddToMapLayer('lines', `${URL_API}/links/geojson/${startLat},${startLon},${endLat},${endLon}?filter=${encodeURIComponent(networkFilter.value)}`); + const sitesLoaded = loadGeoJsonAndAddToMapLayer('points', `${URL_API}/sites/geojson/${startLat},${startLon},${endLat},${endLon}?filter=${encodeURIComponent(networkFilter.value)}`); + + const results = await Promise.all([servicesLoaded, linksLoaded, sitesLoaded]); + + mapContext.current.isLoadingInProgress = false; + + const areGeojsonEndpointsReachable = (results[0] && results[1] && results[2]); + + if (isTopologyServerReachable !== areGeojsonEndpointsReachable) { + connectionChanged(areGeojsonEndpointsReachable); + } + + if (LogLevel > 3) { + console.log(`MapComponent::loadBBoxDataAndAddToMap - loaded data for bbox: ${startLat},${startLon},${endLat},${endLon}`, results); + } + + return results; + }, [networkFilter.value]); + + const loadMapData = async (bbox: maplibregl.LngLatBounds, force = false) => { + + // Todo: (if we get the time) maybe split bounding box into smaller parts to increase loading speed + // currently the entire bbox gets loaded (-> potentially huge load) + + if (LogLevel > 3) { + console.log(`MapComponent::loadMapData - bbox: ${bbox.getNorth()}, ${bbox.getWest()}, ${bbox.getSouth()}, ${bbox.getEast()} : isLoadingInProgress: ${mapContext.current.isLoadingInProgress}`); + } + + try { + if (mapContext.current.isInitialLoadOfMap) { + mapContext.current.isInitialLoadOfMap = false; + + await loadBBoxDataAndAddToMap(lastBoundingBox.getWest(), lastBoundingBox.getSouth(), lastBoundingBox.getEast(), lastBoundingBox.getNorth()); + } else { + if (!mapContext.current.isLoadingInProgress) { // only load data if loading not in progress + mapContext.current.isLoadingInProgress = true; + + // new bbox is bigger than old one + if (bbox.contains(lastBoundingBox.getNorthEast()) && bbox.contains(lastBoundingBox.getSouthWest()) && lastBoundingBox !== bbox) { //if new bb is bigger than old one + + //calculate new boundingBox + const increasedBoundingBox = increaseBoundingBox(map); + lastBoundingBox = bbox; + await loadBBoxDataAndAddToMap(increasedBoundingBox.west, increasedBoundingBox.south, increasedBoundingBox.east, increasedBoundingBox.north); + + } else if (!force && lastBoundingBox.contains(bbox.getNorthEast()) && lastBoundingBox.contains(bbox.getSouthWest())) { // last one contains new one + // bbox is contained in last one, do nothing + mapContext.current.isLoadingInProgress = false; + + } else { // bbox is not fully contained in old one, extend + + lastBoundingBox.extend(bbox); + await loadBBoxDataAndAddToMap(lastBoundingBox.getWest(), lastBoundingBox.getSouth(), lastBoundingBox.getEast(), lastBoundingBox.getNorth()); + } + + mapContext.current.isLoadingInProgress = false; + + if (mapContext.current.enqueuedBoundingBoxes.length > 0) { // load last not loaded bounding box + loadMapData(mapContext.current.enqueuedBoundingBoxes.pop()!); + mapContext.current.enqueuedBoundingBoxes = []; + } + } else { + mapContext.current.enqueuedBoundingBoxes.push(bbox); + } + } + } catch (e) { + mapContext.current.isLoadingInProgress = false; + } + }; + + const isLayerVisible = (layer: string) => { + const layerEl = layers.find(el => el.name === layer); + return layerEl ? layerEl.displayed : false; + }; + + const changeSizeOfPoints = (newSize: number, displaySelectedPoints: boolean, overrideCircleStyle?: true) => { + if (map.getLayer('points') && isLayerVisible('Sites')) { + map.setPaintProperty('points', 'circle-radius', overrideCircleStyle ? newSize : [ 'match', ['get', 'xPonder'], 'true', 9, 7 ]); + } + }; + + const changeSizeOfLines = (newSize: number) => { + map.setPaintProperty('fibre-lines', 'line-width', newSize); + map.setPaintProperty('microwave-lines', 'line-width', newSize); + }; + + const reduceSizeOfFeatures = () => { + changeSizeOfPoints(2, false, true); + if (map.getZoom() <= 4) { + changeSizeOfLines(1); + } else { + changeSizeOfLines(2); + } + }; + + const adjustPointStyle = (mapZoom: number) => { + if (mapZoom > 11) { + changeSizeOfPoints(7, true); + } else if (mapZoom > 10) { + changeSizeOfPoints(5, true); + } else if (mapZoom > 9) { + changeSizeOfPoints(3, true); + } else { + reduceSizeOfFeatures(); + } + }; + + /*** + * Show selection popup if multiple map elements (sites / links / ...) were clicked + */ + const showSelectionPopup = (features: maplibregl.MapGeoJSONFeature[], type: 'site' | 'link', top: number, left: number) => { + const elements: Feature[] = features.map(feature => { + return { + properties: feature.properties as any, + geometry: feature.geometry as any, + type: feature.type, + }; + }); + + setPopup({ isOpen: true, elements: elements, type: type, position: { left: left, top: top } }); + }; + + const loadDetails = (feature: maplibregl.MapGeoJSONFeature[], type: 'site' | 'link', top: number, left: number) => { + if (feature.length > 1) { + showSelectionPopup(feature, type, top, left); + } else { + // load details data + const id = feature[0].properties!.id; + const typeOfFeature = feature[0].properties?.layer || 'site'; + loadSelectedElement(typeOfFeature, id); + } + }; + + const handleResize = () => { + if (map) { + // wait a moment until resizing actually happened + window.setTimeout(() => map.resize(), 500); + } + }; + + const handleMapMove = () => { + const mapZoom = map.getZoom(); + const boundingBox = map.getBounds(); + loadMapData(boundingBox); + adjustPointStyle(mapZoom); + }; + + const handleMapClick = (e: any) => { + const linkLayerNames = mapContext.current.furtherLayerNames.map(layer => layer + '-links'); + const serviceLayerNames = mapContext.current.furtherLayerNames.map(layer => layer + '-services'); + + if (!map.getLayer('point-lamps')) { // data is shown as points + + const clickedLines = getUniqueFeatures(map.queryRenderedFeatures([[e.point.x - 5, e.point.y - 5], + [e.point.x + 5, e.point.y + 5]], { + layers: ['microwave-lines', 'fibre-lines', 'services', 'backup-services', ...linkLayerNames, ...serviceLayerNames], + }), 'id'); + + const clickedSites = getUniqueFeatures(map.queryRenderedFeatures([[e.point.x - 5, e.point.y - 5], + [e.point.x + 5, e.point.y + 5]], { + layers: ['points', ...mapContext.current.furtherLayerNames], + }), 'id'); + + if (clickedSites.length != 0) { + + loadDetails(clickedSites, 'site', e.point.x, e.point.y); + } else if (clickedLines.length != 0) { + loadDetails(clickedLines, 'link', e.point.x, e.point.y); + } + + } else { // data is shown as icons + + const clickedSites = getUniqueFeatures(map.queryRenderedFeatures(e.point, { layers: ['point-lamps', 'point-building', 'point-data-center', 'point-factory', 'points', ...mapContext.current.furtherLayerNames] }), 'id'); + const clickedLines = getUniqueFeatures(map.queryRenderedFeatures([[e.point.x - 5, e.point.y - 5], + [e.point.x + 5, e.point.y + 5]], { + layers: ['microwave-lines', 'fibre-lines', 'services', 'backup-services', ...linkLayerNames, ...serviceLayerNames ], + }), 'id'); + + if (clickedSites.length > 0) + loadDetails(clickedSites, 'site', e.point.x, e.point.y); + else if (clickedLines.length != 0) { + loadDetails(clickedLines, 'link', e.point.x, e.point.y); + } + } + }; + + const handleMapMoveEnd = () => { + + const boundingBox = map.getBounds(); + loadMapData(boundingBox); + + const mapZoom = map.getZoom().toFixed(2); + const mapLat = map.getCenter().lat.toFixed(4); + const mapLon = map.getCenter().lng.toFixed(4); + + if (LogLevel > 3) { + console.log(`MapComponent::map move end - lat: ${mapLat} lon: ${mapLon} zoom: ${mapZoom}`); + } + + if (lat !== mapLat || lon !== mapLon || zoom !== mapZoom) { + updateMapPosition(mapLat, mapLon, mapZoom); + updateMapStatistics(BoundingBox.createFromBoundingBox(map.getBounds())); + } + + //switch icon layers if applicable + mapLayerService.showIconLayers(map, showIcons); + }; + + const getAdditionalLayers = (pMap: maplibregl.Map) => { + + layers.forEach((el) => { + + if (!pMap.getLayer(el.name) && !el.base) { //base layers skipped ("points", "links", ... are added via addCommonLayers) + + mapContext.current.furtherLayerNames.push(el.name); + } + }); + }; + + const isZoomValid = (pZoom: number) => !Number.isNaN(pZoom) && pZoom >= MIN_ZOOM && pZoom <= MAX_ZOOM; + + const areCoordinatesValid = (pLat: number, pLon: number) => { + + const isLatValid = !Number.isNaN(pLat) && pLat >= -90 && pLat <= 90; + const isLonValid = !Number.isNaN(pLon) && pLon >= -180 && pLon <= 180; + + if (isLatValid && isLonValid) { + return true; + } else { + return false; + } + }; + + /** + * Moves map to coordinates and zooms in / out + * @param coordinates lat, lon, zoom + */ + const moveMapToCoordinates = (pMap: maplibregl.Map, coordinates: MapCoordinate) => { + if (LogLevel > 3) { + console.log(`MapComponent::move map to coordinates - lat: ${coordinates.lat}, lon: ${coordinates.lon}, zoom: ${coordinates.zoom}`); + } + + if (areCoordinatesValid(coordinates.lat, coordinates.lon)) { + let newZoom = -1; + + if (isZoomValid(coordinates.zoom)) { + newZoom = coordinates.zoom; + } + + pMap?.flyTo({ + center: [ + coordinates.lon, + coordinates.lat, + ], zoom: newZoom !== -1 ? newZoom : newZoom, + essential: true, + }); + } + }; + + /** + * + * Moves map to include center, start and endpoint of a feature + * + * @param center + * @param start + * @param end + */ + const centerMapOnFeature = (pMap: maplibregl.Map, center: Coordinate | null, start?: Coordinate, end?: Coordinate) => { + + let newBounds = new maplibregl.LngLatBounds(); + const allValues = { center, start, end }; + Object.values(allValues).forEach(value => { + if (value) { + newBounds.extend([value.lon, value.lat]); + } + }); + + //zooms in/out to accommodate bounding box + pMap?.fitBounds(newBounds, { padding: 20 }); + }; + + const updateMapBasedOnLayers = (pMap: maplibregl.Map) => { + + layers.forEach(el => { + + //deactivate / activate layers + + if (el.name == 'Links') { + pMap.setLayoutProperty('fibre-lines', 'visibility', el.displayed ? 'visible' : 'none'); + pMap.setLayoutProperty('microwave-lines', 'visibility', el.displayed ? 'visible' : 'none'); + + } else if (el.name == 'Sites') { + pMap.setLayoutProperty('points', 'visibility', el.displayed ? 'visible' : 'none'); + + if (el.displayed) { + const mapZoom = pMap.getZoom(); + adjustPointStyle(mapZoom); + } + } else if (el.name == 'Services') { + pMap.setLayoutProperty('services', 'visibility', el.displayed ? 'visible' : 'none'); + } else if (pMap.getLayer(el.name)) { + + pMap.setLayoutProperty(el.name, 'visibility', el.displayed ? 'visible' : 'none'); + if (pMap.getLayer(el.name + '-links') && pMap.getLayer(el.name + '-services')) { + + pMap.setLayoutProperty(el.name + '-links', 'visibility', el.displayed ? 'visible' : 'none'); + pMap.setLayoutProperty(el.name + '-services', 'visibility', el.displayed ? 'visible' : 'none'); + } + } + }); + }; + + const rearrangeLayers = (pMap: maplibregl.Map) => { + pMap.moveLayer('services'); + pMap.moveLayer('selectedPoints'); + }; + + const setupMap = () => { + + let initialLat = lat; + let initialLon = lon; + let initialZoom = zoom; + mapContext.current.isInitialLoadOfMap = true; + + if (settings.mapSettings?.startupPosition) { + if (settings.mapSettings.startupPosition.latitude) { + initialLat = settings.mapSettings.startupPosition.latitude; + } + + if (settings.mapSettings.startupPosition.longitude) { + initialLon = settings.mapSettings.startupPosition.longitude; + } + + if (settings.mapSettings.startupPosition.zoom) { + initialZoom = settings.mapSettings.startupPosition.zoom; + } + } + + map = new maplibregl.Map({ + container: mapContainerRef.current!, + style: OSM_STYLE as any, + center: [Number(initialLon), Number(initialLat)], + zoom: Number(initialZoom), + }); + + mapRef.current = map; + + map.on('load', () => { + + if (LogLevel > 3) { + console.log('MapComponent::map loaded'); + } + + map.setMaxZoom(MAX_ZOOM); + const bbox = map.getBounds(); + + if (lastBoundingBox == null) { + lastBoundingBox = bbox; + } + + updateMapPosition(bbox.getCenter().lat.toFixed(4), bbox.getCenter().lng.toFixed(4), map.getZoom().toFixed(2)); + mapLayerService.addBaseSources(map, selectedSite, selectedLink, selectedService); + + //loading icons used in "icon switch" logic + addImages(map, () => { + if (map.getZoom() > 11 && showIcons) { + mapLayerService.addIconLayers(map); + } else { + mapLayerService.addBaseLayers(map); + if (map.getZoom() < 9) { + reduceSizeOfFeatures(); + } + } + updateOpacity(); + if (!mapContext.current.areLayersLoaded) { + mapContext.current.areLayersLoaded = true; + getAdditionalLayers(map); + mapLayerService.addLayersToMap(map, mapContext.current.furtherLayerNames); + rearrangeLayers(map); + } + updateMapBasedOnLayers(map); + }); + + const boundingBox = map.getBounds(); + loadMapData(boundingBox); + updateMapStatistics(BoundingBox.createFromBoundingBox(boundingBox)); + map.on('click', handleMapClick); + map.on('move', handleMapMove); + map.on('moveend', handleMapMoveEnd); + map.on('rotate', (ev) => { + const targetMap = ev.target; + setBearing(targetMap.getBearing()); + }); + }); + }; + + useEffect(() => { + + // resize the map, if menu gets collapsed + window.addEventListener('menu-resized', handleResize); + + //pass themes to mapLayerService + updateTheme(); + + // try if connection to tile + topology server are available + Promise.all([ + dataService.tryReachTileServer(), + dataService.tryReachTopologyServer(), + updateLayers(), + ]).then(([tileServerReachableResult, topologyServerReachableResult]) => { + setTileServerLoaded(tileServerReachableResult); + setTopologyServerLoaded(topologyServerReachableResult); + + //both done + setConnectivityCheck(false); + }); + + return () => { + //unregister events + window.removeEventListener('menu-resized', handleResize); + + if (mapRef.current) { + mapRef.current.off('click', handleMapClick); + mapRef.current.off('moveend', handleMapMoveEnd); + mapRef.current.off('move', handleMapMove); + } + + // will be checked again on next load + setConnectivityCheck(true); + }; + }, []); + + useEffect(() => { + updateTheme(); + }, [settings]); + + // load map + useEffect(() => { + + // if everything done loading/reachable, load map + if (!isConnectivityCheckBusy && isTopologyServerReachable && isTileServerReachable && !settings.isLoadingData) { + + if (mapRef.current === undefined) { + setupMap(); + } else + if (mapRef.current.getContainer() !== mapContainerRef.current) { + // reload map, because the current container (fresh div) doesn't hold the map and changing containers isn't supported + mapRef.current.remove(); + setupMap(); + } + } + return () => { + if (mapRef.current) { + mapRef.current.remove(); + mapRef.current = undefined; + } + }; + }, [isConnectivityCheckBusy, isTopologyServerReachable, isTileServerReachable, settings.isLoadingData]); + + useEffect(() => { + if (!mapRef.current) return; + + if (selectedSite !== null) { + + if (mapRef.current.getSource('selectedPoints')) { + (mapRef.current.getSource('selectedLine') as maplibregl.GeoJSONSource).setData({ type: 'FeatureCollection', features: [] }); + (mapRef.current.getSource('selectedPoints') as maplibregl.GeoJSONSource).setData({ type: 'FeatureCollection', features: [selectedSite] }); + } + + if (mapRef.current.getLayer('point-lamps')) { + + //reset filters (remove earlier 'selected' (bigger) icons) + + mapRef.current.setFilter('point-lamps', ['==', 'type', 'street lamp']); + mapRef.current.setFilter('point-data-center', ['==', 'type', 'data center']); + mapRef.current.setFilter('point-building', ['==', 'type', 'high rise building']); + mapRef.current.setFilter('point-factory', ['==', 'type', 'factory']); + } + } else if (selectedLink !== null) { + if (mapRef.current.getLayer('point-lamps')) { + mapRef.current.setFilter('point-lamps', ['==', 'type', 'street lamp']); + mapRef.current.setFilter('point-data-center', ['==', 'type', 'data center']); + mapRef.current.setFilter('point-building', ['==', 'type', 'high rise building']); + mapRef.current.setFilter('point-factory', ['==', 'type', 'factory']); + } + + if (mapRef.current.getSource('selectedLine')) { + (mapRef.current.getSource('selectedPoints') as maplibregl.GeoJSONSource).setData({ type: 'FeatureCollection', features: [] }); + (mapRef.current.getSource('selectedLine') as maplibregl.GeoJSONSource).setData({ type: 'FeatureCollection', features: [selectedLink] }); + } + } else if (selectedService !== null) { + if (mapRef.current.getLayer('point-lamps')) { + mapRef.current.setFilter('point-lamps', ['==', 'type', 'street lamp']); + mapRef.current.setFilter('point-data-center', ['==', 'type', 'data center']); + mapRef.current.setFilter('point-building', ['==', 'type', 'high rise building']); + mapRef.current.setFilter('point-factory', ['==', 'type', 'factory']); + } + + if (mapRef.current.getSource('selectedLine')) { + (mapRef.current.getSource('selectedPoints') as maplibregl.GeoJSONSource).setData({ type: 'FeatureCollection', features: [] }); + (mapRef.current.getSource('selectedLine') as maplibregl.GeoJSONSource).setData({ type: 'FeatureCollection', features: [selectedService] }); + } + } else if (selectedLink == null && selectedService == null && selectedSite == null) { + //if nothing is selected, remove highlighting (happens after eg changing the details url / element not found) + if (mapRef.current.getSource('selectedLine')) { + (mapRef.current.getSource('selectedPoints') as maplibregl.GeoJSONSource).setData({ type: 'FeatureCollection', features: [] }); + (mapRef.current.getSource('selectedLine') as maplibregl.GeoJSONSource).setData({ type: 'FeatureCollection', features: [] }); + } + } + + }, [selectedSite, selectedLink, selectedService]); + + useEffect(() => { + if (!mapRef.current) return; + + if (mapRef.current?.getZoom() > 11) { + mapLayerService.showIconLayers(map, showIcons); + } + }, [showIcons]); + + useEffect(() => { + if (LogLevel > 3) { + console.log(`MapComponent:: store state coordinates changed to: lat: ${lat} lon: ${lon} zoom: ${zoom}`); + } + + if (!mapRef.current) { + return; + } + + const center = mapRef.current.getCenter(); + const isMapAtSamePosition = center.lat.toFixed(4) === lat && center.lng.toFixed(4) === lon && mapRef.current.getZoom().toFixed(2) === zoom; + + if (!isMapAtSamePosition) { + moveMapToCoordinates(mapRef.current, { lat: Number(lat), lon: Number(lon), zoom: Number(zoom) }); + } + + }, [lat, lon, zoom]); + + useEffect(() => { + if (!mapRef.current) return; + + if (zoomToElement) { + centerMapOnFeature(mapRef.current, zoomToElement.center, zoomToElement.start, zoomToElement.end); + mapRef.current.once('zoomend', () => { zoomFinished(); }); + } + }, [zoomToElement]); + + useEffect(() => { + if (!mapRef.current) return; + + if (!mapContext.current.areLayersLoaded) { + mapContext.current.areLayersLoaded = true; + getAdditionalLayers(mapRef.current); + mapLayerService.addLayersToMap(mapRef.current, mapContext.current.furtherLayerNames); + rearrangeLayers(mapRef.current); + } + updateMapBasedOnLayers(mapRef.current); + }, [layers]); + + const handleZoomIn = () => { + if (!map.isZooming()) + map.zoomIn(); + }; + + const handleZoomOut = () => { + if (!map.isZooming()) + map.zoomOut(); + }; + + const handleAlignBearingNorth = () => { + if (!map.isMoving()) + map.resetNorth(); + }; + + const styles = useStyles(); + + if (LogLevel > 3) { + console.log(`MapComponent::render - lat: ${lat}, lon: ${lon}, zoom: ${zoom}, networkFilter: ${networkFilter.value}, showIcons: ${showIcons}`); + } + + return ( + <> + { + if (LogLevel > 3) { + console.log(`MapComponent::render - networkFilter changed to ${networkFilter.value} ${mapRef.current}`); + } + if (!mapRef.current) { + return; + } + const boundingBox = mapRef.current.getBounds(); + loadMapData(boundingBox, true); + }} /> + { + !settings.isLoadingData + ? ( +
+ {(popup && popup.isOpen) + ? ( { setPopup(null); }} />) + : null + } + + + + + + +
+ ) + : ( +
+ ) + } + + ); +}); + +Map.displayName = 'Map'; + +export { Map }; diff --git a/features/sdnr/odlux/odlux/apps/networkMapApp/src/components/map/mapControl.tsx b/features/sdnr/odlux/odlux/apps/networkMapApp/src/components/map/mapControl.tsx new file mode 100644 index 0000000..49d0b83 --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/networkMapApp/src/components/map/mapControl.tsx @@ -0,0 +1,116 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2022 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 } from 'react'; + +import { makeStyles } from '@mui/styles'; +import Paper from '@mui/material/Paper/Paper'; +import IconButton from '@mui/material/IconButton/IconButton'; + +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { faCompass, faMinus, faPlus } from '@fortawesome/free-solid-svg-icons'; + +type MapControlProps = { + onZoomIn(): void; + onZoomOut(): void; + onAlignNorth(): void; + bearing: number; + align?: AllowedAlignment; +}; + +type AllowedAlignment = 'top-right' | 'top-left' | 'bottom-right' | 'bottom-left'; + +const useStyles = makeStyles({ + mapControl: { + zIndex: 2, + borderRadius: 4, + display: 'flex', + flexDirection: 'column', + position: 'absolute', + }, + mapControlButton: { + borderRadius: 0, + height: 29, + width: 29, + }, + zoomIcon: { + color: 'black', + height: '15px', + }, + biggerZoomIcon: { + color: 'black', + height: '17px', + }, + borderBottom: { + borderBottom: '1px solid lightgray', + }, +}); + +// created because normal zoom controls caused error (see: https://git-highstreet-technologies.com/highstreet/odlux/-/issues/367) + +const MapControl: FC = (props) => { + + const getAlignment = (align: AllowedAlignment | undefined) => { + const defaultCss = { top: 0, right: 0, margin: '10px 10px 0 0' }; + + if (!align) { + return defaultCss; + } + + let alignment: React.CSSProperties; + + switch (align) { + case 'top-right': + alignment = defaultCss; + break; + case 'top-left': + alignment = { top: 0, left: 0, margin: '10px 0 0 10px' }; + break; + case 'bottom-right': + alignment = { bottom: 0, right: 0, margin: '0 10px 45px 0' }; + break; + case 'bottom-left': + alignment = { bottom: 0, left: 0, margin: '0 0 10px 10px' }; + break; + } + + return alignment; + }; + + const positioning = getAlignment(props.align); + const bearing = props.bearing - 44; //align icon towards north + + const classes = useStyles(); + return ( + + + + + + + + + + + + ); +}; + +MapControl.displayName = 'MapControl'; + +export default MapControl; \ No newline at end of file diff --git a/features/sdnr/odlux/odlux/apps/networkMapApp/src/components/map/mapPopup.tsx b/features/sdnr/odlux/odlux/apps/networkMapApp/src/components/map/mapPopup.tsx new file mode 100644 index 0000000..d7028f2 --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/networkMapApp/src/components/map/mapPopup.tsx @@ -0,0 +1,81 @@ +/** + * ============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 React, { FC, useState } from 'react'; + +import Paper from '@mui/material/Paper'; +import Popover from '@mui/material/Popover'; +import Select from '@mui/material/Select'; +import Typography from '@mui/material/Typography'; + +import { useApplicationDispatch } from '../../../../../framework/src/flux/connect'; + +import { LoadNetworkElementDetails } from '../../actions/detailsAction'; +import { Feature } from '../../model/topologyTypes'; + +type MapPopupProps = { + elements: Feature[]; + type: string; + position: { left: number; top: number }; + onClose(): void; +}; + +const MapPopup: FC = (props) => { + const { type, position, elements, onClose } = props; + + const dispatch = useApplicationDispatch(); + + const [value, setValue] = useState(''); + + const handleElementSelected = (event: any) => { + setValue(event.target.value); + const element = elements[event.target.value]; + const typeOfFeature = element?.properties?.layer || 'site'; + const id = element.properties.id; + dispatch(LoadNetworkElementDetails(typeOfFeature, String(id))); + onClose(); + }; + + return ( + + + {`Multiple ${type}s were selected`} + Please select one. + + + + ); +}; + +MapPopup.displayName = 'MapPopup'; + +export default MapPopup; \ No newline at end of file diff --git a/features/sdnr/odlux/odlux/apps/networkMapApp/src/components/map/searchBar.tsx b/features/sdnr/odlux/odlux/apps/networkMapApp/src/components/map/searchBar.tsx new file mode 100644 index 0000000..d9692f6 --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/networkMapApp/src/components/map/searchBar.tsx @@ -0,0 +1,192 @@ +/** + * ============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 React, { FC } from 'react'; + +import SearchIcon from '@mui/icons-material/Search'; +import Divider from '@mui/material/Divider/Divider'; +import IconButton from '@mui/material/IconButton/IconButton'; +import Paper from '@mui/material/Paper/Paper'; +import makeStyles from '@mui/styles/makeStyles'; + +import { useApplicationDispatch, useSelectApplicationState } from '../../../../../framework/src/flux/connect'; + +import { Coordinate } from '../../model/coordinates'; +import { Link, Site, Service, isLink, isService, isSite } from '../../model/topologyTypes'; +import { SearchResult } from '../../model/searchResult'; + +import { SelectElementAction } from '../../actions/detailsAction'; +import { highlightElementAction, ZoomToSearchResultAction } from '../../actions/mapActions'; +import { SetSearchValueAction } from '../../actions/searchAction'; + +import { dataService } from '../../services/dataService'; +import { calculateMidPoint } from '../../utils/mapUtils'; + +import SearchResultDisplay from './searchResultDisplay'; +import InputBase from '@mui/material/InputBase/InputBase'; +import Popover from '@mui/material/Popover/Popover'; +import Typography from '@mui/material/Typography/Typography'; + +const useStyles = makeStyles({ + searchBar: { + padding: '2px 4px', + position: 'absolute', + display: 'flex', + alignItems: 'center', + top: 15, + marginLeft: 5, + width: 400, + zIndex: 1, + }, + input: { + flex: 1, + marginLeft: 5, + }, + iconButton: { + padding: 10, + }, + divider: { + height: 28, + margin: 4, + }, +}); + + +const SearchBar: FC = () => { + + const defaultSearchResult = { links: [], sites: [], services: [] }; + + const searchTerm = useSelectApplicationState(state => state.network.search.value); + const isTopoServerReachable = useSelectApplicationState(state => state.network.connectivity.isTopologyServerAvailable); + const isTileServerReachable = useSelectApplicationState(state => state.network.connectivity.isTileServerAvailable); + + const dispatch = useApplicationDispatch(); + const selectElement = (site: Site | Link | Service) => dispatch(new SelectElementAction(site)); + const highlightElement = (data: Link | Site | Service) => dispatch(highlightElementAction(data)); + const setSearchTerm = (value: string) => dispatch(new SetSearchValueAction(value)); + const zoomToSearchResult = (center: Coordinate, start?: Coordinate, end?: Coordinate) => dispatch(new ZoomToSearchResultAction(center, start, end)); + + const [anchorEl, setAnchorEl] = React.useState(null); + const [errorMessage, setErrorMessage] = React.useState(''); + const [searchResult, setSearchResults] = React.useState(defaultSearchResult); + + const divRef: any = React.useRef(); + + const handleSearchButtonClick = (e: any) => { + + setAnchorEl(null); + if (searchTerm.length > 0) { + + const result = dataService.search(searchTerm); + result.then((data: SearchResult) => { + + if ((data.links == null || data.links.length == 0) && (data.sites == null || data.sites.length == 0) && (data.services == null || data.services.length == 0)) { + setAnchorEl(divRef.current); + setSearchResults(defaultSearchResult); + setErrorMessage('No element found.'); + // hide message after 3 sec + window.setTimeout(() => { setAnchorEl(null); }, 3000); + } else { + setAnchorEl(divRef.current); + setErrorMessage(''); + setSearchResults(data); + } + }); + + e.preventDefault(); + } + }; + + const onSearchResultClick = (result: Site | Link | Service) => { + + selectElement(result); + highlightElement(result); + + if (isSite(result)) { + + const center = { lat: result.location.lat, lon: result.location.lon }; + zoomToSearchResult(center); + + } else if (isLink(result)) { + + const midPoint = calculateMidPoint(result.siteA.lat, result.siteA.lon, result.siteB.lat, result.siteB.lon); + const startPoint = { lat: result.siteA.lat, lon: result.siteA.lon }; + const endPoint = { lat: result.siteB.lat, lon: result.siteB.lon }; + + zoomToSearchResult(midPoint, startPoint, endPoint); + } else if (isService(result)) { + + const midPoint = calculateMidPoint(result.route[0].lat, result.route[0].lon, result.route[result.route.length - 1].lat, result.route[result.route.length - 1].lon); + const startPoint = { lat: result.route[0].lat, lon: result.route[0].lon }; + const endPoint = { lat: result.route[result.route.length - 1].lat, lon: result.route[result.route.length - 1].lon }; + + zoomToSearchResult(midPoint, startPoint, endPoint); + } + setAnchorEl(null); + }; + + const dataFound = searchResult.links && searchResult.links.length > 0 || searchResult.sites && searchResult.sites.length > 0 || searchResult.services && searchResult.services.length > 0; + const reachable = isTopoServerReachable && isTileServerReachable; + const open = Boolean(anchorEl); + + const styles = useStyles(); + return ( + <> + + setSearchTerm(e.currentTarget.value)} + /> + + + + + + setAnchorEl(null)} anchorEl={anchorEl} anchorOrigin={{ + vertical: 'bottom', + horizontal: 'left', + }}> + + { + errorMessage + ? {errorMessage} + : null + } + { + dataFound + ? < SearchResultDisplay searchResult={searchResult} onResultClick={onSearchResultClick} /> + : null + } + + + + ); +}; + +SearchBar.displayName = 'SearchBar'; + +export default SearchBar; \ No newline at end of file diff --git a/features/sdnr/odlux/odlux/apps/networkMapApp/src/components/map/searchResultDisplay.tsx b/features/sdnr/odlux/odlux/apps/networkMapApp/src/components/map/searchResultDisplay.tsx new file mode 100644 index 0000000..0932519 --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/networkMapApp/src/components/map/searchResultDisplay.tsx @@ -0,0 +1,74 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2022 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 } from 'react'; + +import Typography from '@mui/material/Typography'; + +import { Link, Site, Service, isLink } from '../../model/topologyTypes'; +import { SearchResult } from '../../model/searchResult'; + +type SearchResultElement = Link | Service | Site; + +const createSearchDisplay = (elements: SearchResultElement[], title: string, onResultClick: (element: SearchResultElement) => void) => ( + + elements && elements.length > 0 + ? ( + <> + Found {title} +
+ {elements.map((element, i) => { + // type specific info + const linkMessage = isLink(element) + ? element.polarization + : ''; + return ( + onResultClick(element)} + style={{ marginLeft: '5px', cursor: 'pointer' }} + variant="body1"> + {element.id} - {element.name ?? ''} {linkMessage} {element.feature?.properties?.layer} + + ); + })} +
+ + ) : null +); + +type SearchResultDisplayProps = { + searchResult: SearchResult; + onResultClick: (element: SearchResultElement) => void; +}; + +const SearchResultDisplay: FC = (props) => { + const { searchResult, onResultClick } = props; + + const displays = Object.keys(searchResult).map((el) => { + const elements = (searchResult as any)[el]; + return createSearchDisplay(elements, el, onResultClick); + }); + + return <>{displays}; +}; + +SearchResultDisplay.displayName = 'SearchResultDisplay'; + +export default SearchResultDisplay; + diff --git a/features/sdnr/odlux/odlux/apps/networkMapApp/src/components/map/statistics.tsx b/features/sdnr/odlux/odlux/apps/networkMapApp/src/components/map/statistics.tsx new file mode 100644 index 0000000..02cd3ce --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/networkMapApp/src/components/map/statistics.tsx @@ -0,0 +1,69 @@ +/** + * ============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 React, { FC } from 'react'; +import { useApplicationDispatch, useSelectApplicationState } from '../../../../../framework/src/flux/connect'; + +import Accordion from '@mui/material/Accordion'; +import AccordionSummary from '@mui/material/AccordionSummary'; +import AccordionDetails from '@mui/material/AccordionDetails'; +import ExpandMoreIcon from '@mui/icons-material/ExpandMore'; +import InfoIcon from '@mui/icons-material/Info'; +import Tooltip from '@mui/material/Tooltip'; +import Typography from '@mui/material/Typography'; + +import { OpenStatisticsAction } from '../../actions/mapActions'; + +const Statistics: FC = () => { + + const linkCount = useSelectApplicationState(state => state.network.map.statistics.links); + const siteCount = useSelectApplicationState(state => state.network.map.statistics.sites); + const serviceCount = useSelectApplicationState(state => state.network.map.statistics.services); + const isOpen = useSelectApplicationState(state => state.network.map.statistics.isOpen); + const isTopoServerReachable = useSelectApplicationState(state => state.network.connectivity.isTopologyServerAvailable); + const isTileServerReachable = useSelectApplicationState(state => state.network.connectivity.isTileServerAvailable); + + const dispatch = useApplicationDispatch(); + const openStatistics = (open: boolean) => dispatch(new OpenStatisticsAction(open)); + + const reachable = isTopoServerReachable && isTileServerReachable; + + return ( + openStatistics(!isOpen)} style={{ position: 'absolute', display: 'flex', flexDirection: 'column', top: 140, width: 200, marginLeft: 5, zIndex: 1 }}> + } + aria-label="statistics-accordion"> +
+ Statistics + + + +
+
+ + Sites: {siteCount} + Links: {linkCount} + Services: {serviceCount} + +
+ ); +}; + +Statistics.displayName = 'Statistics'; + +export default Statistics; \ No newline at end of file diff --git a/features/sdnr/odlux/odlux/apps/networkMapApp/src/components/stadok/stadokDetailsPopup.tsx b/features/sdnr/odlux/odlux/apps/networkMapApp/src/components/stadok/stadokDetailsPopup.tsx new file mode 100644 index 0000000..8b5ad92 --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/networkMapApp/src/components/stadok/stadokDetailsPopup.tsx @@ -0,0 +1,314 @@ +/** + * ============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 React, { FC, SyntheticEvent, useEffect, useState } from 'react'; + +import MuiDialogTitle from '@mui/material/DialogTitle'; +import { AppBar, Button, Dialog, DialogContent, IconButton, Tab, Tabs, TextField, Typography } from '@mui/material'; +import CloseIcon from '@mui/icons-material/Close'; +import { Theme } from '@mui/material/styles'; +import withStyles from '@mui/styles/withStyles'; +import createStyles from '@mui/styles/createStyles'; +import makeStyles from '@mui/styles/makeStyles'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { faFileAlt } from '@fortawesome/free-solid-svg-icons'; + +import StadokSite from '../../model/stadokSite'; +import { LatLonToDMS } from '../../utils/mapUtils'; +import DenseTable from '../../components/denseTable'; +import { requestRest } from '../../../../../framework/src/services/restService'; +import { OrderToDisplay, StadokOrder } from '../../model/stadokOrder'; +import { SITEDOC_URL } from '../../config'; + +const stadokImage = (siteId: string, imageName: string, className: string) => { + const url = `${SITEDOC_URL}/site/${siteId}/files/${imageName}`; + return window.open(url)} />; +}; + +const styles = (theme: Theme) => createStyles({ + root: { + margin: 0, + padding: theme.spacing(2), + }, + closeButton: { + position: 'absolute', + right: theme.spacing(1), + top: theme.spacing(1), + color: theme.palette.grey[500], + }, +}); + +const useStyles = makeStyles({ + largeImage:{ cursor:'pointer', width:300 }, + smallImage:{ cursor:'pointer', width: 50, marginTop:'10px', marginLeft:'10px' }, +}); + +const DialogTitle = withStyles(styles)((props: any) => { + const { children, classes, onClose, ...other } = props; + return ( + + {children} + {onClose ? ( + + + + ) : null} + + ); +}); + +type StadokDetailsProps = { site: StadokSite; siteId: number; onClose(): void }; + +const StadokDetailsPopup: FC = (props) => { + + const [currentTab, setCurrentTab] = useState('devices'); + const [orders, setOrders] = useState(null); + const [displayReport, setDisplayReport] = useState(false); + + // TODO: change and use stadok site report once api is changed + const reportUrl = `${SITEDOC_URL}/site/${props.site.id}/files/${props.siteId}-report.xml`; + const ordersUrl = `${SITEDOC_URL}/site/${props.site.id}/orders`; + + const classes = useStyles(); + + useEffect(() => { + + requestRest(ordersUrl, { method: 'GET' }).then(result =>{ + if (result) { + const orderList = result.map(order =>{ + return OrderToDisplay.parse(order); + }); + setOrders(orderList); + + } else { + setOrders([]); + } + }); + + requestRest(reportUrl).then(res =>{ + if (res) { + setDisplayReport(true); + } + }); + + }, []); + + + const getContacts = (site: StadokSite) =>{ + const contacts = []; + + if (site.createdBy) { + contacts.push({ h: 'Site Creator', col1: site.createdBy.firstName, col2: site.createdBy.lastName, col3: site.createdBy.email, col4: site.createdBy.telephoneNumber }); + } + + if (site.contacts?.manager) { + contacts.push({ h: 'Manager', col1: site.contacts.manager.firstName, col2: site.contacts.manager.lastName, col3: site.contacts.manager.email, col4: site.contacts.manager.telephoneNumber }); + } + + if (site.contacts?.owner) { + contacts.push({ h: 'Owner', col1: site.contacts.owner.firstName, col2: site.contacts.owner.lastName, col3: site.contacts.owner.email, col4: site.contacts.owner.telephoneNumber }); + } + return contacts; + }; + + const onClose = () =>{ + // setOpen(false); + props.onClose(); + }; + + //todo: use a set 'panelId' -> which values are allowed + const handleTabChange = (event: SyntheticEvent, newValue: string) => { + setCurrentTab(newValue); + }; + const contacts = getContacts(props.site); + + const createOrderInfo = () => { + + if (orders == null) { + return (
+ + Loading orders + +
); + } else if (orders.length === 0) { + return (
+ + No orders available + +
); + } else { + return ; + } + }; + + const displayImages = () => { + if (props.site.images.length === 1) { + return stadokImage(props.site.id, props.site.images[0], classes.largeImage); + } else { + return <> + { + stadokImage(props.site.id, props.site.images[0], classes.largeImage) + } +
+ + { + props.site.images.length <= 5 ? + props.site.images.slice(1, props.site.images.length).map(image => + stadokImage(props.site.id, image, classes.smallImage), + ) + : + <> + { + props.site.images.slice(1, 5).map(image => + stadokImage(props.site.id, image, classes.smallImage), + ) + } + + } +
+ ; + } + }; + + return ( + + + {props.site.id} + + +
+
+ + + { + props.site.type != null && props.site.type.length > 0 && + + } + + + + + + + + + + + + + + + { + currentTab == 'devices' && (props.site.devices?.length > 0 ? + + : +
+ + No devices available + +
) + } + { + currentTab == 'contacts' && (contacts.length > 0 ? + + : +
+ + No contacts available + +
) + } + { + currentTab == 'safetyInfo' && (props.site?.safetyNotices?.length > 0 + ? ( + + ) + : ( +
+ + No safety notices applicable + +
+ )) + } + { + currentTab == 'logs' && (props.site?.logs?.length > 0 + ? ( + + ) + : ( +
+ + No activity log available + +
+ ) + ) + } + + { + currentTab === 'orders' && createOrderInfo() + } +
+
+ { + props.site?.images?.length > 0 + ? displayImages() + : ( + + No images available + + ) + } + { + displayReport && +
+ + TSS Report +
+ } + +
+
+
+
+ ); + +}; + +export default StadokDetailsPopup; \ No newline at end of file diff --git a/features/sdnr/odlux/odlux/apps/networkMapApp/src/config.ts b/features/sdnr/odlux/odlux/apps/networkMapApp/src/config.ts new file mode 100644 index 0000000..6961c1b --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/networkMapApp/src/config.ts @@ -0,0 +1,89 @@ +/** + * ============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 const URL_API = '/topology/network'; +export const SITEDOC_URL = '/sitedoc'; +export const URL_TILE_API = '/tiles'; +export const URL_BASEPATH = 'network'; + +export const TERRAIN_URL = '/terrain'; //http://10.20.11.249:5200 maybe? /terrain +export const TILE_URL = '/tiles'; //http://tile.openstreetmap.org /tiles + +export const ELECTROMAGNETC_FIELD = '/electromagnetic-field/'; +export const ANTENNA_MAP = '/electromagnetic-field/'; + +export const OSM_STYLE = { + 'version': 8, + 'sources': { + 'raster-tiles': { + 'type': 'raster', + 'tiles': [ + TILE_URL + '/{z}/{x}/{y}.png', + ], + 'tileSize': 256, + 'attribution': '© OpenStreetMap contributors', + }, + 'signal-level': { + 'type': 'raster', + 'tiles': [ + ELECTROMAGNETC_FIELD + '?bbox={bbox-epsg-3857}&format=image/png&service=WMS&version=1.1.1&request=GetMap&srs=EPSG:3857&transparent=true&width=256&height=256&layers=layers=campusos%3Abest_server_test', + ], + 'tileSize': 256, + }, + 'antenna-map': { + 'type': 'raster', + 'tiles': [ + ELECTROMAGNETC_FIELD + '?bbox={bbox-epsg-3857}&format=image/png&service=WMS&version=1.1.1&request=GetMap&srs=EPSG:3857&transparent=true&width=256&height=256&layers=layers=campusos%3Asector_markers', + ], + 'tileSize': 256, + }, + }, + 'layers': [ + { + 'id': 'osm-map', + 'type': 'raster', + 'source': 'raster-tiles', + 'minZoom': 0, + 'maxZoom': 18, + }, + { + 'id': 'signal level', + 'type': 'raster', + 'source': 'signal-level', + 'minZoom': 0, + 'maxZoom': 18, + 'background-opacity': 1, + 'layout': { + 'visibility': 'none', + }, + }, + { + 'id': 'antenna map', + 'type': 'raster', + 'source': 'antenna-map', + 'minZoom': 0, + 'maxZoom': 18, + 'background-opacity': 1, + 'layout': { + 'visibility': 'none', + }, + }, + ], +}; + + diff --git a/features/sdnr/odlux/odlux/apps/networkMapApp/src/handlers/connectivityHandler.ts b/features/sdnr/odlux/odlux/apps/networkMapApp/src/handlers/connectivityHandler.ts new file mode 100644 index 0000000..6a0c530 --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/networkMapApp/src/handlers/connectivityHandler.ts @@ -0,0 +1,45 @@ +/** + * ============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 { IActionHandler } from '../../../../framework/src/flux/action'; +import { IsTopologyServerReachableAction, IsTileServerReachableAction, IsBusyCheckingConnectivityAction } from '../actions/connectivityAction'; + +export type connectivityState = { + isTopologyServerAvailable: boolean; + isTileServerAvailable: boolean; + isBusy: boolean; +}; + +const initialState: connectivityState = { + isTopologyServerAvailable: true, + isTileServerAvailable: true, + isBusy: true, +}; + +export const ConnectivityHandler: IActionHandler = (state = initialState, action) => { + + if (action instanceof IsTopologyServerReachableAction) { + state = { ...state, isTopologyServerAvailable: action.reachable }; + } else if (action instanceof IsTileServerReachableAction) { + state = { ...state, isTileServerAvailable: action.reachable }; + } else if (action instanceof IsBusyCheckingConnectivityAction) { + state = { ...state, isBusy: action.isBusy }; + } + + return state; +}; \ No newline at end of file diff --git a/features/sdnr/odlux/odlux/apps/networkMapApp/src/handlers/detailsHandler.ts b/features/sdnr/odlux/odlux/apps/networkMapApp/src/handlers/detailsHandler.ts new file mode 100644 index 0000000..fa5b301 --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/networkMapApp/src/handlers/detailsHandler.ts @@ -0,0 +1,74 @@ +/** + * ============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 { IActionHandler } from '../../../../framework/src/flux/action'; + +import { HistoryEntry } from '../model/historyEntry'; +import { Link, Site, Device, Service } from '../model/topologyTypes'; + +import { + AddToHistoryAction, + ClearHistoryAction, + IsBusyCheckingDeviceListAction, + FinishedLoadingDeviceListAction, + ClearLoadedDevicesAction, + ClearDetailsAction, + InitializeLoadedDevicesAction, + IsSitedocReachableAction, + SelectElementAction, +} from '../actions/detailsAction'; + +export type DetailsStoreState = { + data: Site | Link | Service | null; + history: HistoryEntry[]; + isBusyCheckingDeviceList: boolean; + checkedDevices: Device[]; + isSitedocReachable: boolean; +}; + +const initialState: DetailsStoreState = { + data: null, + history: [], + isBusyCheckingDeviceList: false, + checkedDevices: [], + isSitedocReachable: false, +}; + +export const DetailsHandler: IActionHandler = (state = initialState, action) => { + if (action instanceof SelectElementAction) { + state = { ...state, data: action.data }; + } else if (action instanceof ClearDetailsAction) { + state = { ...state, data: null }; + } else if (action instanceof AddToHistoryAction) { + state = { ...state, history: [...state.history, action.entry] }; + } else if (action instanceof ClearHistoryAction) { + state = { ...state, history: [] }; + } else if (action instanceof IsBusyCheckingDeviceListAction) { + state = { ...state, isBusyCheckingDeviceList: action.isBusy }; + } else if (action instanceof FinishedLoadingDeviceListAction) { + state = { ...state, checkedDevices: action.devices }; + } else if (action instanceof ClearLoadedDevicesAction) { + state = { ...state, checkedDevices: [] }; + } else if (action instanceof InitializeLoadedDevicesAction) { + state = { ...state, checkedDevices: action.devices }; + } else if (action instanceof IsSitedocReachableAction) { + state = { ...state, isSitedocReachable: action.isReachable }; + } + return state; +}; + diff --git a/features/sdnr/odlux/odlux/apps/networkMapApp/src/handlers/filterHandler.ts b/features/sdnr/odlux/odlux/apps/networkMapApp/src/handlers/filterHandler.ts new file mode 100644 index 0000000..6b196ed --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/networkMapApp/src/handlers/filterHandler.ts @@ -0,0 +1,41 @@ +/** + * ============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 { IActionHandler } from '../../../../framework/src/flux/action'; +import { SetFilterValueAction } from '../actions/filterActions'; + +export type FilterState = { + value: string; +}; + +const initialState: FilterState = { + value: '', +}; + +export const FilterHandler: IActionHandler = (state = initialState, action) => { + + if (action instanceof SetFilterValueAction) { + state = { + ...state, + value: action.value, + }; + } + + return state; +}; + diff --git a/features/sdnr/odlux/odlux/apps/networkMapApp/src/handlers/mapHandler.ts b/features/sdnr/odlux/odlux/apps/networkMapApp/src/handlers/mapHandler.ts new file mode 100644 index 0000000..e81e5d4 --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/networkMapApp/src/handlers/mapHandler.ts @@ -0,0 +1,326 @@ +/** + * ============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 { createHashHistory } from 'history'; +import { Dispatch } from '../../../../framework/src/flux/store'; +import { MiddlewareArg } from '../../../../framework/src/flux/middleware'; +import { Action, IActionHandler } from '../../../../framework/src/flux/action'; +import { IApplicationStoreState } from '../../../../framework/src/store/applicationStore'; +import { SelectElementAction } from '../actions/detailsAction'; + +import { + HighlightLinkAction, + HighlightSiteAction, + ZoomToSearchResultAction, + AddAlarmAction, + SetCoordinatesAction, + SetStatistics, + SetIconSwitchAction, + RemoveHighlightingAction, + ZoomToFinishedAction, + LayerChangedAction, + LayersLoadedAction, + HighlightServiceAction, + OpenLayersAction, + OpenStatisticsAction, + UpdateLayersVisibilityAction, +} from '../actions/mapActions'; + +import { Coordinate } from '../model/coordinates'; +import { Site, Link, Service, Feature, DetailsTypes, isService, isSite } from '../model/topologyTypes'; + +import { URL_BASEPATH, OSM_STYLE } from '../config'; + +const LogLevel = +(localStorage.getItem('log.odlux.networkMap.mapHandler') || 0); + +export type LayerItem = { name: string; displayed: boolean; base?: true }; + +export type MapState = { + selectedLink: Feature | null; + selectedSite: Feature | null; + selectedService: Feature | null; + zoomToElement: { + center: Coordinate | null; + start: Coordinate | undefined; + end: Coordinate | undefined; + zoom: number | undefined; + } | null; + alarmElement: Feature | null; + coordinates: { + lat: string; + lon: string; + zoom: string; + }; + statistics: { + links: string; + sites: string; + services: string; + isOpen: boolean; + }; + allowIconSwitch: boolean; + layersContainer: { + isOpen: boolean; + areFurtherLayersAvailable: boolean; + elements: LayerItem[]; + + }; +}; + +const initialState: MapState = { + selectedLink: null, + selectedSite: null, + selectedService: null, + zoomToElement: null, + alarmElement: null, + coordinates: { + lat: '52.5095', + lon: '13.3290', + zoom: '10', + }, + statistics: { + links: 'Not counted yet.', + sites: 'Not counted yet.', + services: 'Not counted yet.', + isOpen: true, + }, + allowIconSwitch: true, + layersContainer: { + isOpen: false, + areFurtherLayersAvailable: false, + elements: [ + ...OSM_STYLE.layers.map(layer => ({ name: layer.id, displayed: !layer?.layout?.visibility || layer?.layout?.visibility !== 'none', base: true }) as LayerItem), + { name: 'Sites', displayed: true, base: true }, + { name: 'Links', displayed: true, base: true }, + { name: 'Services', displayed: true, base: true }, + ], + }, +}; + +export const MapHandler: IActionHandler = (state = initialState, action: any) => { + if (action instanceof HighlightLinkAction) { + state = { + ...state, + selectedSite: null, + selectedService: null, + selectedLink: action.link.feature, + }; + } else if (action instanceof HighlightSiteAction) { + state = { + ...state, + selectedLink: null, + selectedService: null, + selectedSite: action.site.feature, + }; + } else if (action instanceof HighlightServiceAction) { + state = { + ...state, + selectedLink: null, + selectedSite: null, + selectedService: action.service.feature, + }; + } else if (action instanceof ZoomToSearchResultAction) { + state = { + ...state, + zoomToElement: { + center: action.center, + start: action.start, + end: action.end, + zoom: action.zoom, + }, + }; + } else if (action instanceof ZoomToFinishedAction) { + state = { + ...state, + zoomToElement: null, + }; + } else if (action instanceof AddAlarmAction) { + state = { + ...state, + alarmElement: action.site.feature, + }; + } else if (action instanceof SetCoordinatesAction) { + state = { + ...state, + coordinates: { + lat: action.lat, + lon: action.lon, + zoom: action.zoom, + }, + }; + } else if (action instanceof SetStatistics) { + state = { + ...state, + statistics: { + sites: action.siteCount, + links: action.linkCount, + services: action.serviceCount, + isOpen: state.statistics.isOpen, + }, + }; + } else if (action instanceof SetIconSwitchAction) { + state = { + ...state, + allowIconSwitch: action.enable, + }; + } else if (action instanceof RemoveHighlightingAction) { + state = { + ...state, + selectedLink: null, + selectedSite: null, + selectedService: null, + }; + } else if (action instanceof UpdateLayersVisibilityAction) { + const newData = state.layersContainer.elements.map(el => (el.name in action.layerVisibility ? { ...el, displayed: action.layerVisibility[el.name] } : el)); + state = { + ...state, + layersContainer: { + ...state.layersContainer, + elements: newData, + }, + }; + } else if (action instanceof LayerChangedAction) { + const newData = state.layersContainer.elements.map(el => (el.name === action.layerName ? { ...el, displayed: action.displayed } : el)); + state = { + ...state, + layersContainer: { + ...state.layersContainer, + elements: newData, + }, + }; + } else if (action instanceof LayersLoadedAction) { + const data = state.layersContainer; + const els: LayerItem[] = action.layers.map((e) => { return { name: e, displayed: false }; }); + data.elements.push(...els); + if (els.length > 0) { + data.areFurtherLayersAvailable = true; + } + state = { + ...state, + layersContainer: data, + }; + } else if (action instanceof OpenLayersAction) { + const data = state.layersContainer; + data.isOpen = action.open; + state = { + ...state, + layersContainer: data, + }; + } else if (action instanceof OpenStatisticsAction) { + const data = state.statistics; + data.isOpen = action.open; + state = { + ...state, + statistics: data, + }; + } + + return state; +}; + +const history = createHashHistory(); + +const getDetailIdentifier = (data: Link | Site | Service) => { + + if (isService(data)) { + return DetailsTypes.service; + } else if (isSite(data)) { + return DetailsTypes.site; + } else { + return DetailsTypes.link; + } +}; + +const getDetailsParameter = (data: Link | Site | Service | null) => { + let details = ''; + if (data) { + switch (getDetailIdentifier(data)) { + case DetailsTypes.link: + details = `linkId=${data.id}`; + break; + case DetailsTypes.site: + details = `siteId=${data.id}`; + break; + case DetailsTypes.service: + details = `serviceId=${data.id}`; + break; + } + } + return details; +}; + +const getCoordinatesParameter = (lat: string, lon: string, zoom: string) => { + const centerParam = lat && lat ? `center=${lat},${lon}` : ''; + const zoomParam = zoom ? `zoom=${zoom}` : ''; + return [centerParam, zoomParam].filter(e => e).join('&'); +}; + +export const MapMiddleware = (store: MiddlewareArg) => (next: Dispatch) => (action: A) => { + if (action instanceof SetCoordinatesAction) { + + const { + framework: { + navigationState, + }, + network: { + details: { + data, + }, + }, + } = store.getState(); + + const detailsParam = getDetailsParameter(data); + const coordinatesParam = getCoordinatesParameter(action.lat, action.lon, action.zoom); + const url = `/${URL_BASEPATH}?${[coordinatesParam, detailsParam].filter(e => e).join('&')}`; + if (navigationState.pathname !== url) { + history.replace(url); + } + + if (LogLevel > 3) { + console.log(`MapMiddleware::SetCoordinatesAction - lat: ${action.lat} lon: ${action.lon} zoom: ${action.zoom} data: ${data}`); + } + + } else if (action instanceof SelectElementAction) { + const { + framework: { + navigationState, + }, + network: { + map: { + coordinates, + }, + }, + } = store.getState(); + + + const detailsParam = getDetailsParameter(action.data); + const coordinatesParam = getCoordinatesParameter(coordinates.lat, coordinates.lon, coordinates.zoom); + const url = `/${URL_BASEPATH}?${[coordinatesParam, detailsParam].filter(e => e).join('&')}`; + + if (navigationState.pathname !== url) { + window.setTimeout(() => history.replace(url)); + } + + if (LogLevel > 3) { + console.log(`MapMiddleware::SelectElementAction - ne: ${action.data?.feature?.properties?.layer || 'side'} id: ${action.data.id} name: ${action.data.name} coordinates: ${coordinates.lat} ${coordinates.lon} ${coordinates.zoom}`); + } + } + // let all actions pass + return next(action); +}; + + diff --git a/features/sdnr/odlux/odlux/apps/networkMapApp/src/handlers/rootHandler.ts b/features/sdnr/odlux/odlux/apps/networkMapApp/src/handlers/rootHandler.ts new file mode 100644 index 0000000..a68d4b5 --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/networkMapApp/src/handlers/rootHandler.ts @@ -0,0 +1,56 @@ +/** + * ============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 { combineActionHandler } from '../../../../framework/src/flux/middleware'; + +import { DetailsHandler, DetailsStoreState } from './detailsHandler'; +import { MapHandler, MapState } from './mapHandler'; +import { SearchHandler, searchState } from './searchHandler'; +import { connectivityState, ConnectivityHandler } from './connectivityHandler'; +import { SettingsHandler, SettingsState } from './settingsHandler'; +import { ManagementHandler, ManagementState } from './sitedocManagementHandler'; +import { FilterHandler, FilterState } from './filterHandler'; + +export type INetworkAppStoreState = { + details: DetailsStoreState; + map: MapState; + search: searchState; + filter: FilterState; + connectivity: connectivityState; + settings: SettingsState; + sitedocManagement: ManagementState; +}; + +declare module '../../../../framework/src/store/applicationStore' { + interface IApplicationStoreState { + network: INetworkAppStoreState; + } +} + +const appHandler = { + details: DetailsHandler, + map: MapHandler, + search: SearchHandler, + filter: FilterHandler, + connectivity: ConnectivityHandler, + settings: SettingsHandler, + sitedocManagement: ManagementHandler, +}; + +export const networkmapRootHandler = combineActionHandler(appHandler); +export default networkmapRootHandler; \ No newline at end of file diff --git a/features/sdnr/odlux/odlux/apps/networkMapApp/src/handlers/searchHandler.ts b/features/sdnr/odlux/odlux/apps/networkMapApp/src/handlers/searchHandler.ts new file mode 100644 index 0000000..141ddf2 --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/networkMapApp/src/handlers/searchHandler.ts @@ -0,0 +1,37 @@ +/** + * ============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 { IActionHandler } from '../../../../framework/src/flux/action'; +import { SetSearchValueAction } from '../actions/searchAction'; + +export type searchState = { + value: string; +}; + +const initialState: searchState = { + value: '', +}; + +export const SearchHandler: IActionHandler = (state = initialState, action) => { + + if (action instanceof SetSearchValueAction) { + state = { ...state, value: action.value }; + } + + return state; +}; \ No newline at end of file diff --git a/features/sdnr/odlux/odlux/apps/networkMapApp/src/handlers/settingsHandler.ts b/features/sdnr/odlux/odlux/apps/networkMapApp/src/handlers/settingsHandler.ts new file mode 100644 index 0000000..b870fd1 --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/networkMapApp/src/handlers/settingsHandler.ts @@ -0,0 +1,73 @@ +/** + * ============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 { NetworkMapSettings, ThemeElement } from '../model/settings'; +import { SetBusyLoadingAction, SetMapSettingsAction, SetThemeSettingsAction } from '../actions/settingsAction'; + +export type SettingsState = { + mapSettings: NetworkMapSettings | null; + themes: ThemeElement[]; + isLoadingData: boolean; +}; + +const defaultThemes: ThemeElement[] = [ + { + key: 'light', + site: '#11b4da', + selectedSite: '#116bda', + fiberLink: '#1154d9', + microwaveLink: '#039903', + furtherLayers: {}, + }, + { + key: 'dark', + site: '#000000', + selectedSite: '#6e6e6e', + fiberLink: '#0a2a6b', + microwaveLink: '#005200', + furtherLayers: {}, + }, +]; + +const initialState: SettingsState = { + mapSettings: null, + themes: defaultThemes, + isLoadingData: true, +}; + +export const SettingsHandler: IActionHandler = (state = initialState, action) => { + + if (action instanceof SetMapSettingsAction) { + state = { + ...state, + mapSettings: action.settings, + }; + } else if (action instanceof SetThemeSettingsAction) { + state = { + ...state, + themes: action.settings.themes, + }; + } else if (action instanceof SetBusyLoadingAction) { + state = { + ...state, + isLoadingData: action.busy, + }; + } + return state; +}; \ No newline at end of file diff --git a/features/sdnr/odlux/odlux/apps/networkMapApp/src/handlers/sitedocManagementHandler.ts b/features/sdnr/odlux/odlux/apps/networkMapApp/src/handlers/sitedocManagementHandler.ts new file mode 100644 index 0000000..9d82b50 --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/networkMapApp/src/handlers/sitedocManagementHandler.ts @@ -0,0 +1,60 @@ +/** + * ============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 { ResetAction, SelectUserAction, SetAllUsersAction, SetTSSRAction, UpdateNoteAction, UpdateStateAction, UpdateTasks } from '../actions/sitedocManagementAction'; +import { IActionHandler } from '../../../../framework/src/flux/action'; +import { SitedocOrderTask, UserListItem } from '../model/siteDocTypes'; + +const emptyTask: SitedocOrderTask = { type: '', description: '', status: false }; + +export type ManagementState = { + users: UserListItem[]; + selectedUser: string; + note: string; + tasks: SitedocOrderTask[]; + isTSSR: boolean; + state: string; +}; + +const initialState: ManagementState = { + users:[], + selectedUser: '', + state: 'OPEN', + note: '', + tasks: [emptyTask], + isTSSR: false, +}; + +export const ManagementHandler: IActionHandler = (state = initialState, action) => { + if (action instanceof SetAllUsersAction) { + state = { ...state, users:action.users }; + } else if (action instanceof SelectUserAction) { + state = { ...state, selectedUser: action.user }; + } else if (action instanceof SetTSSRAction) { + state = { ...state, isTSSR: action.isTSSR }; + } else if (action instanceof UpdateNoteAction) { + state = { ...state, note:action.note }; + } else if (action instanceof UpdateStateAction) { + state = { ...state, state: action.state }; + } else if (action instanceof UpdateTasks) { + state = { ...state, tasks: action.tasks }; + } else if ( action instanceof ResetAction) { + state = { ...state, tasks: [emptyTask], selectedUser:'', note: '', isTSSR:false }; + } + return state; +}; \ No newline at end of file diff --git a/features/sdnr/odlux/odlux/apps/networkMapApp/src/index.html b/features/sdnr/odlux/odlux/apps/networkMapApp/src/index.html new file mode 100644 index 0000000..f9c8ad7 --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/networkMapApp/src/index.html @@ -0,0 +1,30 @@ + + + + + + + + + Networkmap App + + + +
+ + + + + + + \ No newline at end of file diff --git a/features/sdnr/odlux/odlux/apps/networkMapApp/src/model/boundingBox.ts b/features/sdnr/odlux/odlux/apps/networkMapApp/src/model/boundingBox.ts new file mode 100644 index 0000000..2f978d5 --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/networkMapApp/src/model/boundingBox.ts @@ -0,0 +1,67 @@ +/* eslint-disable no-underscore-dangle */ +/** + * ============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========================================================================== + */ + +/*** + * Custom bounding box, holds a rectangular area spanned open by lat-lon coordinate pairs + */ +export class BoundingBox { + + private _south: number; + + private _west: number; + + private _north: number; + + private _east: number; + + get south(): number { + return this._south; + } + + get west():number { + return this._west; + } + + get north():number { + return this._north; + } + + get east():number { + return this._east; + } + + public static createFromBoundingBox = (mapboxBoundingBox: maplibregl.LngLatBounds) => { + const bbox = new BoundingBox(); + bbox._south = mapboxBoundingBox.getSouth(); + bbox._east = mapboxBoundingBox.getEast(); + bbox._north = mapboxBoundingBox.getNorth(); + bbox._west = mapboxBoundingBox.getWest(); + return bbox; + }; + + public static createFromNumbers = (west: number, south: number, east: number, north: number) => { + const bbox = new BoundingBox(); + bbox._south = south; + bbox._west = west; + bbox._north = north; + bbox._east = east; + return bbox; + }; + +} \ No newline at end of file diff --git a/features/sdnr/odlux/odlux/apps/networkMapApp/src/model/coordinates.ts b/features/sdnr/odlux/odlux/apps/networkMapApp/src/model/coordinates.ts new file mode 100644 index 0000000..4d8b770 --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/networkMapApp/src/model/coordinates.ts @@ -0,0 +1,28 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2022 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 Coordinate = { + lat: number; + lon: number; +}; + +export type MapCoordinate = { + lat: number; + lon: number; + zoom: number; +}; \ No newline at end of file diff --git a/features/sdnr/odlux/odlux/apps/networkMapApp/src/model/count.ts b/features/sdnr/odlux/odlux/apps/networkMapApp/src/model/count.ts new file mode 100644 index 0000000..1457374 --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/networkMapApp/src/model/count.ts @@ -0,0 +1,23 @@ +/** + * ============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 ElementCount = { + sites: string; + links: string; + services: string; +}; \ No newline at end of file diff --git a/features/sdnr/odlux/odlux/apps/networkMapApp/src/model/historyEntry.ts b/features/sdnr/odlux/odlux/apps/networkMapApp/src/model/historyEntry.ts new file mode 100644 index 0000000..8c64719 --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/networkMapApp/src/model/historyEntry.ts @@ -0,0 +1,24 @@ +/** + * ============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 { Link, Site } from './topologyTypes'; + +export type HistoryEntry = { + id: string; + data: Site | Link; +}; \ No newline at end of file diff --git a/features/sdnr/odlux/odlux/apps/networkMapApp/src/model/networkElementConnection.ts b/features/sdnr/odlux/odlux/apps/networkMapApp/src/model/networkElementConnection.ts new file mode 100644 index 0000000..a939f9b --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/networkMapApp/src/model/networkElementConnection.ts @@ -0,0 +1,30 @@ +/** + * ============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========================================================================== + */ + +export type NetworkElementConnection = { + id?: string; + nodeId: string; + isRequired: boolean; + host: string; + port: number; + username?: string; + password?: string; + tlsKey?: string; + status?: 'Connected' | 'mounted' | 'unmounted' | 'Connecting' | 'Disconnected' | 'idle'; + ['device-type']?: string; +}; \ No newline at end of file diff --git a/features/sdnr/odlux/odlux/apps/networkMapApp/src/model/searchResult.ts b/features/sdnr/odlux/odlux/apps/networkMapApp/src/model/searchResult.ts new file mode 100644 index 0000000..29bfc5b --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/networkMapApp/src/model/searchResult.ts @@ -0,0 +1,25 @@ +/** + * ============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 { Link, Site, Service } from './topologyTypes'; + +export type SearchResult = { + links: Link[]; + sites: Site[]; + services: Service[]; +}; \ No newline at end of file diff --git a/features/sdnr/odlux/odlux/apps/networkMapApp/src/model/settings.ts b/features/sdnr/odlux/odlux/apps/networkMapApp/src/model/settings.ts new file mode 100644 index 0000000..0aff1e5 --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/networkMapApp/src/model/settings.ts @@ -0,0 +1,53 @@ +/** + * ============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 { CirclePaintProps, LinePaintProps } from '../utils/mapUtils'; + +export type NetworkMapSettings = { + startupPosition: { + latitude?: string; + longitude?: string; + zoom?: string; + }; + tileOpacity: string; + areIconsEnabled: boolean; + styling: { + theme: string; + }; +}; + +export type ThemeElement = { + key: string; + site: string; + selectedSite: string; + microwaveLink: string; + fiberLink: string; + furtherLayers: { + [key: string]: { + site?: Partial; + link?: Partial; + service?: Partial; + }; + }; +}; + +export type NetworkMapThemes = { + themes: ThemeElement[]; +}; + +export type NetworkSettings = NetworkMapSettings & NetworkMapThemes; \ No newline at end of file diff --git a/features/sdnr/odlux/odlux/apps/networkMapApp/src/model/siteDocTypes.ts b/features/sdnr/odlux/odlux/apps/networkMapApp/src/model/siteDocTypes.ts new file mode 100644 index 0000000..03d5c1a --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/networkMapApp/src/model/siteDocTypes.ts @@ -0,0 +1,52 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2022 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 UserListItem = { + userName: string; + firstName: string; + lastName: string; +}; + +export type RegisterUser = { + + firstName: string; + familyName: string; + email: string; + username: string; + password: string; + telephoneNr: string; + role: 'ANDROID'; +}; + +export type SitedocOrder = { + siteId: string; + assignedUser: string; + date: string; //in UTC + state: SitedocOrderTypes; + tasks: SitedocOrderTask[]; + note: string; + isTssr: boolean; +}; + +export type SitedocOrderTypes = 'OPEN' | 'UPDATE' | 'DELETE'; + +export type SitedocOrderTask = { + type: string; + description: string; + status: false; +}; \ No newline at end of file diff --git a/features/sdnr/odlux/odlux/apps/networkMapApp/src/model/stadokOrder.ts b/features/sdnr/odlux/odlux/apps/networkMapApp/src/model/stadokOrder.ts new file mode 100644 index 0000000..7ecfc6d --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/networkMapApp/src/model/stadokOrder.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========================================================================== + */ + +export type StadokOrder = { + date: Date; + assignedUser: string; + state: string; //todo: type restrict + tasks: Task[]; +}; + +export class OrderToDisplay { + static parse = (stadokOrder: StadokOrder) =>{ + let order = new OrderToDisplay(); + order.assignedUser = stadokOrder.assignedUser; + order.state = stadokOrder.state; + + const firstOpenTask = stadokOrder.tasks.find(task => !task.status); + + if (firstOpenTask) { + order.currentTask = firstOpenTask.description; + } else { + order.currentTask = 'No task description available'; + } + + return order; + }; + + state: string; + + assignedUser: string; + + currentTask: string; + +} + +type Task = { + type: string; + description: string; + status: boolean; +}; \ No newline at end of file diff --git a/features/sdnr/odlux/odlux/apps/networkMapApp/src/model/stadokSite.ts b/features/sdnr/odlux/odlux/apps/networkMapApp/src/model/stadokSite.ts new file mode 100644 index 0000000..9a83e9d --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/networkMapApp/src/model/stadokSite.ts @@ -0,0 +1,52 @@ +/** + * ============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 { Address } from './topologyTypes'; + +type StadokSite = { + id: string; + createdBy: Contact; + updatedOn: Date; + location: { lat: number; lon: number }; + address: Address; + contacts: { manager: Contact; owner: Contact }; + safetyNotices: string[]; + images: string[]; + type: string; + devices: Device[]; + logs: Log[]; +}; + +type Contact = { + firstName: string; + lastName: string; + email: string; + telephoneNumber: string; +}; +type Log = { + date: Date; //string? + person: string; + entry: string; +}; + +type Device = { + 'device': string; + 'antenna': string; +}; + +export default StadokSite; \ No newline at end of file diff --git a/features/sdnr/odlux/odlux/apps/networkMapApp/src/model/topologyTypes.ts b/features/sdnr/odlux/odlux/apps/networkMapApp/src/model/topologyTypes.ts new file mode 100644 index 0000000..1d9879e --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/networkMapApp/src/model/topologyTypes.ts @@ -0,0 +1,144 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2022 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 { Coordinate } from './coordinates'; + +type Point = { + type: 'Point'; + coordinates: number[]; +}; + +type LineString = { + type: 'LineString'; + coordinates: number[][]; +}; + +export type Geometry = Point | LineString; + +export type Feature = { + type: 'Feature'; + properties: { + id: number; + subType?: string; + layer?: string; + labels?: string[]; + xPonder?: boolean; + polarization?: string; + }; + geometry: Geometry; +}; + +export type Address = { + streetAndNr: string; + city: string; + zipCode: string | null; + country: string; +}; + +export type Service = { + id: number; + name: string; + backupForServiceId: number | null; + lifecycleState: string; + administrativeState: string; + operationalState: string; + created: string; + modified: string; + route: Coordinate[]; + length: number; + feature: Feature; +}; + +export type Site = { + id: number; + uuid: string; + name: string; + address: Address; + heightAmslInMeters?: number; //AboveGroundLevel + antennaHeightAmslInMeters?: number; + operator: string; + location:{ lon: number; lat: number }; + devices: Device[]; + links: { + id: number; + name: string; + azimuth: number | null; + }[]; + furtherInformation: string; + feature: Feature; +}; + +export type Device = { + id: string; + type?: string; + name: string; + manufacturer: string; + owner: string; + status?: string; + port: number[]; +}; + +type Antenna = { + id: string; + name: string; + height: number; + gain: number; +}; + +type LinkDetailLocation = { + lon: number; + lat: number; + siteId: number; + siteName: string | null; + amsl: number | null; + azimuth: number | null; + antenna: Antenna; + radio: { + id: number; + name: string; + }; + waveguide: { + id: number; + name: string; + }; +}; + +export type Link = { + id: number; + uuid: string; + name: string; + operator: string; + length: number; + polarization: string; + frequency: number | null; + siteA: LinkDetailLocation; + siteB: LinkDetailLocation; + feature: Feature; +}; + +export enum DetailsTypes { + service = 'service', + site = 'site', + link = 'link', +} + +export const isSite = (data: Link | Site | Service): data is Site => data.feature.properties.layer === DetailsTypes.site; + +export const isLink = (data: Link | Site | Service): data is Link => data.feature.properties.layer === DetailsTypes.link; + +export const isService = (data: Link | Site | Service): data is Service => data.feature.properties.layer === DetailsTypes.service; diff --git a/features/sdnr/odlux/odlux/apps/networkMapApp/src/pluginTransport.tsx b/features/sdnr/odlux/odlux/apps/networkMapApp/src/pluginTransport.tsx new file mode 100644 index 0000000..f09ac8e --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/networkMapApp/src/pluginTransport.tsx @@ -0,0 +1,76 @@ +/** + * ============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========================================================================== + */ + +// app configuration and main entry point for the app + +import React from 'react'; + +import { useApplicationDispatch } from '../../../framework/src/flux/connect'; +import applicationManager from '../../../framework/src/services/applicationManager'; + +import { addMapMessageListener } from '../../../lib/broadcast/mapChannel'; +import { URL_BASEPATH } from './config'; + +import { CheckSitedocReachability } from './actions/detailsAction'; +import { SetFilterValueAction } from './actions/filterActions'; +import { getSettings } from './actions/settingsAction'; + +import { NetworkMapSetup } from './components/customize/networkMapSetup'; + +import { networkmapRootHandler } from './handlers/rootHandler'; +import { MapMiddleware } from './handlers/mapHandler'; + +import MainView from './app'; + +const appIcon = require('./assets/icons/networkMapAppIcon.svg'); // select app icon + +const NetworkMapApp = () => { + + const dispatch = useApplicationDispatch(); + const tryReachSitedocServer = () => dispatch(CheckSitedocReachability()); + + React.useLayoutEffect(() => { + tryReachSitedocServer(); + }, []); + + return ( + + ); +}; + +NetworkMapApp.displayName = 'NetworkMapApp'; + +export const register = async () => { + const appApi = applicationManager.registerApplication({ + name: URL_BASEPATH, // used as name of state as well + icon: appIcon, + middlewares: [MapMiddleware], + rootActionHandler: networkmapRootHandler, + rootComponent: NetworkMapApp, + settingsElement: NetworkMapSetup, + menuEntry: 'Network Map', + }); + + addMapMessageListener('setFilter', (filter) => { + const store = appApi && appApi.applicationStore; + store?.dispatch(new SetFilterValueAction(filter)); + }); + + await appApi.applicationStoreInitialized; + await appApi.applicationStore?.dispatch(getSettings()); +}; diff --git a/features/sdnr/odlux/odlux/apps/networkMapApp/src/services/dataService.ts b/features/sdnr/odlux/odlux/apps/networkMapApp/src/services/dataService.ts new file mode 100644 index 0000000..e32d0a7 --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/networkMapApp/src/services/dataService.ts @@ -0,0 +1,118 @@ +/** + * ============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 { requestRest, requestRestExt } from '../../../../framework/src/services/restService'; +import { Result } from '../../../../framework/src/models'; + +import { NetworkElementConnection } from '../model/networkElementConnection'; +import { URL_API, URL_TILE_API } from '../config'; +import { ElementCount } from '../model/count'; +import { SearchResult } from '../model/searchResult'; + +class DataService { + + tryReachTileServer = async () => { + + try { + + const tiles = await fetch(URL_TILE_API + '/10/0/0.png'); + if (tiles.status == 200) { + return true; + } else { + console.error(tiles); + return false; + } + } catch (error) { + console.error(error); + return false; + + } + }; + + tryReachTopologyServer = async () => { + const response = await requestRestExt(URL_API + '/info/count/all'); + + if (response.status == 200) { + return true; + } else { + console.error(response.message); + return false; + } + + }; + + getGeojsonData = async (url: string) => { + + const result = await requestRestExt(url); + return result; + + }; + + getDetailsData = async (type: string, id: string) => { + + const response = await requestRestExt(`${URL_API}/${type}s/${id}`); + if (response.status == 200) { + return response.data; + } else { + console.error(response.message); + return null; + } + }; + + search = (searchTerm: string) => { + const data = { searchTerm }; + return requestRest(`${URL_API}/search`, { method: 'POST', body: JSON.stringify(data) }); + }; + + /** + * Get status and type of devices, if possible + */ + getAdditionalInfoOnDevices = async (ids: string[]) => { + + const path = 'rests/operations/data-provider:read-network-element-connection-list'; + + const query = { + 'data-provider:input': { + 'filter': [{ + 'property': 'id', + 'filtervalues': ids, + }], + 'pagination': { + 'size': ids.length, + 'page': 1, + }, + }, + }; + + if (ids.length > 0) { + const result = await requestRest>(path, { method: 'POST', body: JSON.stringify(query) }); + const resultData = result && result['data-provider:output'] && result['data-provider:output'].data; + return resultData; + } else { + return null; + } + }; + + getLabels = () => requestRest(`${URL_API}/labels`); + + getStatistics = (bbWest: number, bbSouth: number, bbEast: number, bbNorth: number) => + requestRest(`${URL_API}/info/count/${bbWest},${bbSouth},${bbEast},${bbNorth}`); + +} + +export const dataService = new DataService(); \ No newline at end of file diff --git a/features/sdnr/odlux/odlux/apps/networkMapApp/src/services/mapImagesService.ts b/features/sdnr/odlux/odlux/apps/networkMapApp/src/services/mapImagesService.ts new file mode 100644 index 0000000..3f32353 --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/networkMapApp/src/services/mapImagesService.ts @@ -0,0 +1,56 @@ +/** + * ============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 maplibregl from 'maplibre-gl'; + +import apartment from '../../icons/apartment.png'; +import dataCenter from '../../icons/datacenter.png'; +import factory from '../../icons/factory.png'; +import lamp from '../../icons/lamp.png'; +import dataCenterRed from '../../icons/datacenterred.png'; +import factoryRed from '../../icons/factoryred.png'; +import lampRed from '../../icons/lampred.png'; + + +type ImagesLoaded = (allImagesLoaded: boolean) => void; + +export const ImagesMap: { name: string; url: string }[] = [ + { name: 'data-center', url: dataCenter }, + { name: 'house', url: apartment }, + { name: 'factory', url: factory }, + { name: 'lamp', url: lamp }, + { name: 'data-center-red', url: dataCenterRed }, + { name: 'factory-red', url: factoryRed }, + { name: 'lamp-red', url: lampRed }, +]; + +export const addImages = (map: maplibregl.Map, callback?: ImagesLoaded) => { + ImagesMap.forEach(image => { + map.loadImage( + image.url, + (error: any, img: any) => { + if (error) throw error; + map.addImage(image.name, img); + + // continue if all images are loaded + if (callback && ImagesMap.every(({ name }) => map.hasImage(name))) { + callback(true); + } + }); + }); +}; \ No newline at end of file diff --git a/features/sdnr/odlux/odlux/apps/networkMapApp/src/services/settingsService.ts b/features/sdnr/odlux/odlux/apps/networkMapApp/src/services/settingsService.ts new file mode 100644 index 0000000..823be41 --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/networkMapApp/src/services/settingsService.ts @@ -0,0 +1,40 @@ +/** + * ============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 { NetworkMapSettings, NetworkMapThemes } from '../model/settings'; +import { getUserData, saveUserData } from '../../../../framework/src/services/userdataService'; + + +const getMapSettings = () => getUserData<{ + networkMap?: NetworkMapSettings; + networkMapThemes?: NetworkMapThemes; + networkMapLayers?: { [key: string]: boolean }; +}>(); + +const getMapThemes = () => getUserData('/networkMapThemes'); + +const updateMapSettings = (newElement: NetworkMapSettings) => saveUserData('/networkMap', JSON.stringify(newElement)); + +const updateMapLayers = (layerVisibility: { [key: string]: boolean }) => saveUserData<{ [key: string]: boolean }>('/networkMapLayers', JSON.stringify(layerVisibility)); + +export { + getMapSettings, + getMapThemes, + updateMapSettings, + updateMapLayers, +}; \ No newline at end of file diff --git a/features/sdnr/odlux/odlux/apps/networkMapApp/src/services/sitedocDataService.ts b/features/sdnr/odlux/odlux/apps/networkMapApp/src/services/sitedocDataService.ts new file mode 100644 index 0000000..35f4cb2 --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/networkMapApp/src/services/sitedocDataService.ts @@ -0,0 +1,70 @@ +/** + * ============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 { requestRest, requestRestExt } from '../../../../framework/src/services/restService'; + +import { Site } from '../model/topologyTypes'; +import { RegisterUser, SitedocOrder, UserListItem } from '../model/siteDocTypes'; + +const BASE_URL = '/sitedoc'; + +type Message = { + message: string; +}; + +class SitedocService { + + createOrder = async (order: SitedocOrder) => { + + const result = await requestRestExt(BASE_URL + '/order/create', { method: 'POST', body: JSON.stringify(order) }); + + if (result.status === 200) { + return { message: result.data!.message, error: false }; + } else { + + const message = { message: '', error: true }; + + if (result.data) { + message.message = 'Creation failed. Reason: ' + result.data.message; + return message; + } else { + message.message = 'Something went wrong...'; + return message; + } + } + }; + + createUser = (user: RegisterUser) => requestRest(BASE_URL + '/user/register', { method: 'POST', body: JSON.stringify(user) }); + + getSiteIfExists = (siteId: string) => requestRest('/topology/network/sites/' + siteId); + + getAllUsers = async () => { + + const result = await requestRest(BASE_URL + '/users/android'); + + if (result) { + return result; + } else { + return []; + } + + }; +} + +const sitedocDataService = new SitedocService(); +export { sitedocDataService }; \ No newline at end of file diff --git a/features/sdnr/odlux/odlux/apps/networkMapApp/src/styles/index.css b/features/sdnr/odlux/odlux/apps/networkMapApp/src/styles/index.css new file mode 100644 index 0000000..ec2585e --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/networkMapApp/src/styles/index.css @@ -0,0 +1,13 @@ +body { + margin: 0; + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', + 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', + sans-serif; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +code { + font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', + monospace; +} diff --git a/features/sdnr/odlux/odlux/apps/networkMapApp/src/styles/mapbox-gl.css b/features/sdnr/odlux/odlux/apps/networkMapApp/src/styles/mapbox-gl.css new file mode 100644 index 0000000..03c479a --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/networkMapApp/src/styles/mapbox-gl.css @@ -0,0 +1 @@ +.mapboxgl-map{font:12px/20px Helvetica Neue,Arial,Helvetica,sans-serif;overflow:hidden;position:relative;-webkit-tap-highlight-color:rgba(0,0,0,0);text-align:left}.mapboxgl-map:-webkit-full-screen{width:100%;height:100%}.mapboxgl-canary{background-color:salmon}.mapboxgl-canvas-container.mapboxgl-interactive,.mapboxgl-ctrl-group button.mapboxgl-ctrl-compass{cursor:-webkit-grab;cursor:-moz-grab;cursor:grab;-moz-user-select:none;-webkit-user-select:none;-ms-user-select:none;user-select:none}.mapboxgl-canvas-container.mapboxgl-interactive.mapboxgl-track-pointer{cursor:pointer}.mapboxgl-canvas-container.mapboxgl-interactive:active,.mapboxgl-ctrl-group button.mapboxgl-ctrl-compass:active{cursor:-webkit-grabbing;cursor:-moz-grabbing;cursor:grabbing}.mapboxgl-canvas-container.mapboxgl-touch-zoom-rotate,.mapboxgl-canvas-container.mapboxgl-touch-zoom-rotate .mapboxgl-canvas{touch-action:pan-x pan-y}.mapboxgl-canvas-container.mapboxgl-touch-drag-pan,.mapboxgl-canvas-container.mapboxgl-touch-drag-pan .mapboxgl-canvas{touch-action:pinch-zoom}.mapboxgl-canvas-container.mapboxgl-touch-zoom-rotate.mapboxgl-touch-drag-pan,.mapboxgl-canvas-container.mapboxgl-touch-zoom-rotate.mapboxgl-touch-drag-pan .mapboxgl-canvas{touch-action:none}.mapboxgl-ctrl-bottom-left,.mapboxgl-ctrl-bottom-right,.mapboxgl-ctrl-top-left,.mapboxgl-ctrl-top-right{position:absolute;pointer-events:none;z-index:2}.mapboxgl-ctrl-top-left{top:0;left:0}.mapboxgl-ctrl-top-right{top:0;right:0}.mapboxgl-ctrl-bottom-left{bottom:0;left:0}.mapboxgl-ctrl-bottom-right{right:0;bottom:0}.mapboxgl-ctrl{clear:both;pointer-events:auto;transform:translate(0)}.mapboxgl-ctrl-top-left .mapboxgl-ctrl{margin:10px 0 0 10px;float:left}.mapboxgl-ctrl-top-right .mapboxgl-ctrl{margin:10px 10px 0 0;float:right}.mapboxgl-ctrl-bottom-left .mapboxgl-ctrl{margin:0 0 10px 10px;float:left}.mapboxgl-ctrl-bottom-right .mapboxgl-ctrl{margin:0 10px 10px 0;float:right}.mapboxgl-ctrl-group{border-radius:4px;background:#fff}.mapboxgl-ctrl-group:not(:empty){-moz-box-shadow:0 0 2px rgba(0,0,0,.1);-webkit-box-shadow:0 0 2px rgba(0,0,0,.1);box-shadow:0 0 0 2px rgba(0,0,0,.1)}@media (-ms-high-contrast:active){.mapboxgl-ctrl-group:not(:empty){box-shadow:0 0 0 2px ButtonText}}.mapboxgl-ctrl-group button{width:29px;height:29px;display:block;padding:0;outline:none;border:0;box-sizing:border-box;background-color:transparent;cursor:pointer}.mapboxgl-ctrl-group button+button{border-top:1px solid #ddd}.mapboxgl-ctrl button .mapboxgl-ctrl-icon{display:block;width:100%;height:100%;background-repeat:no-repeat;background-position:50%}@media (-ms-high-contrast:active){.mapboxgl-ctrl-icon{background-color:transparent}.mapboxgl-ctrl-group button+button{border-top:1px solid ButtonText}}.mapboxgl-ctrl button::-moz-focus-inner{border:0;padding:0}.mapboxgl-ctrl-group button:focus{box-shadow:0 0 2px 2px #0096ff}.mapboxgl-ctrl button:disabled{cursor:not-allowed}.mapboxgl-ctrl button:disabled .mapboxgl-ctrl-icon{opacity:.25}.mapboxgl-ctrl button:not(:disabled):hover{background-color:rgba(0,0,0,.05)}.mapboxgl-ctrl-group button:focus:focus-visible{box-shadow:0 0 2px 2px #0096ff}.mapboxgl-ctrl-group button:focus:not(:focus-visible){box-shadow:none}.mapboxgl-ctrl-group button:focus:first-child{border-radius:4px 4px 0 0}.mapboxgl-ctrl-group button:focus:last-child{border-radius:0 0 4px 4px}.mapboxgl-ctrl-group button:focus:only-child{border-radius:inherit}.mapboxgl-ctrl button.mapboxgl-ctrl-zoom-out .mapboxgl-ctrl-icon{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg width='29' height='29' viewBox='0 0 29 29' xmlns='http://www.w3.org/2000/svg' fill='%23333'%3E%3Cpath d='M10 13c-.75 0-1.5.75-1.5 1.5S9.25 16 10 16h9c.75 0 1.5-.75 1.5-1.5S19.75 13 19 13h-9z'/%3E%3C/svg%3E")}.mapboxgl-ctrl button.mapboxgl-ctrl-zoom-in .mapboxgl-ctrl-icon{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg width='29' height='29' viewBox='0 0 29 29' xmlns='http://www.w3.org/2000/svg' fill='%23333'%3E%3Cpath d='M14.5 8.5c-.75 0-1.5.75-1.5 1.5v3h-3c-.75 0-1.5.75-1.5 1.5S9.25 16 10 16h3v3c0 .75.75 1.5 1.5 1.5S16 19.75 16 19v-3h3c.75 0 1.5-.75 1.5-1.5S19.75 13 19 13h-3v-3c0-.75-.75-1.5-1.5-1.5z'/%3E%3C/svg%3E")}@media (-ms-high-contrast:active){.mapboxgl-ctrl button.mapboxgl-ctrl-zoom-out .mapboxgl-ctrl-icon{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg width='29' height='29' viewBox='0 0 29 29' xmlns='http://www.w3.org/2000/svg' fill='%23fff'%3E%3Cpath d='M10 13c-.75 0-1.5.75-1.5 1.5S9.25 16 10 16h9c.75 0 1.5-.75 1.5-1.5S19.75 13 19 13h-9z'/%3E%3C/svg%3E")}.mapboxgl-ctrl button.mapboxgl-ctrl-zoom-in .mapboxgl-ctrl-icon{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg width='29' height='29' viewBox='0 0 29 29' xmlns='http://www.w3.org/2000/svg' fill='%23fff'%3E%3Cpath d='M14.5 8.5c-.75 0-1.5.75-1.5 1.5v3h-3c-.75 0-1.5.75-1.5 1.5S9.25 16 10 16h3v3c0 .75.75 1.5 1.5 1.5S16 19.75 16 19v-3h3c.75 0 1.5-.75 1.5-1.5S19.75 13 19 13h-3v-3c0-.75-.75-1.5-1.5-1.5z'/%3E%3C/svg%3E")}}@media (-ms-high-contrast:black-on-white){.mapboxgl-ctrl button.mapboxgl-ctrl-zoom-out .mapboxgl-ctrl-icon{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg width='29' height='29' viewBox='0 0 29 29' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M10 13c-.75 0-1.5.75-1.5 1.5S9.25 16 10 16h9c.75 0 1.5-.75 1.5-1.5S19.75 13 19 13h-9z'/%3E%3C/svg%3E")}.mapboxgl-ctrl button.mapboxgl-ctrl-zoom-in .mapboxgl-ctrl-icon{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg width='29' height='29' viewBox='0 0 29 29' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M14.5 8.5c-.75 0-1.5.75-1.5 1.5v3h-3c-.75 0-1.5.75-1.5 1.5S9.25 16 10 16h3v3c0 .75.75 1.5 1.5 1.5S16 19.75 16 19v-3h3c.75 0 1.5-.75 1.5-1.5S19.75 13 19 13h-3v-3c0-.75-.75-1.5-1.5-1.5z'/%3E%3C/svg%3E")}}.mapboxgl-ctrl button.mapboxgl-ctrl-fullscreen .mapboxgl-ctrl-icon{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg width='29' height='29' viewBox='0 0 29 29' xmlns='http://www.w3.org/2000/svg' fill='%23333'%3E%3Cpath d='M24 16v5.5c0 1.75-.75 2.5-2.5 2.5H16v-1l3-1.5-4-5.5 1-1 5.5 4 1.5-3h1zM6 16l1.5 3 5.5-4 1 1-4 5.5 3 1.5v1H7.5C5.75 24 5 23.25 5 21.5V16h1zm7-11v1l-3 1.5 4 5.5-1 1-5.5-4L6 13H5V7.5C5 5.75 5.75 5 7.5 5H13zm11 2.5c0-1.75-.75-2.5-2.5-2.5H16v1l3 1.5-4 5.5 1 1 5.5-4 1.5 3h1V7.5z'/%3E%3C/svg%3E")}.mapboxgl-ctrl button.mapboxgl-ctrl-shrink .mapboxgl-ctrl-icon{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg width='29' height='29' viewBox='0 0 29 29' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M18.5 16c-1.75 0-2.5.75-2.5 2.5V24h1l1.5-3 5.5 4 1-1-4-5.5 3-1.5v-1h-5.5zM13 18.5c0-1.75-.75-2.5-2.5-2.5H5v1l3 1.5L4 24l1 1 5.5-4 1.5 3h1v-5.5zm3-8c0 1.75.75 2.5 2.5 2.5H24v-1l-3-1.5L25 5l-1-1-5.5 4L17 5h-1v5.5zM10.5 13c1.75 0 2.5-.75 2.5-2.5V5h-1l-1.5 3L5 4 4 5l4 5.5L5 12v1h5.5z'/%3E%3C/svg%3E")}@media (-ms-high-contrast:active){.mapboxgl-ctrl button.mapboxgl-ctrl-fullscreen .mapboxgl-ctrl-icon{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg width='29' height='29' viewBox='0 0 29 29' xmlns='http://www.w3.org/2000/svg' fill='%23fff'%3E%3Cpath d='M24 16v5.5c0 1.75-.75 2.5-2.5 2.5H16v-1l3-1.5-4-5.5 1-1 5.5 4 1.5-3h1zM6 16l1.5 3 5.5-4 1 1-4 5.5 3 1.5v1H7.5C5.75 24 5 23.25 5 21.5V16h1zm7-11v1l-3 1.5 4 5.5-1 1-5.5-4L6 13H5V7.5C5 5.75 5.75 5 7.5 5H13zm11 2.5c0-1.75-.75-2.5-2.5-2.5H16v1l3 1.5-4 5.5 1 1 5.5-4 1.5 3h1V7.5z'/%3E%3C/svg%3E")}.mapboxgl-ctrl button.mapboxgl-ctrl-shrink .mapboxgl-ctrl-icon{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg width='29' height='29' viewBox='0 0 29 29' xmlns='http://www.w3.org/2000/svg' fill='%23fff'%3E%3Cpath d='M18.5 16c-1.75 0-2.5.75-2.5 2.5V24h1l1.5-3 5.5 4 1-1-4-5.5 3-1.5v-1h-5.5zM13 18.5c0-1.75-.75-2.5-2.5-2.5H5v1l3 1.5L4 24l1 1 5.5-4 1.5 3h1v-5.5zm3-8c0 1.75.75 2.5 2.5 2.5H24v-1l-3-1.5L25 5l-1-1-5.5 4L17 5h-1v5.5zM10.5 13c1.75 0 2.5-.75 2.5-2.5V5h-1l-1.5 3L5 4 4 5l4 5.5L5 12v1h5.5z'/%3E%3C/svg%3E")}}@media (-ms-high-contrast:black-on-white){.mapboxgl-ctrl button.mapboxgl-ctrl-fullscreen .mapboxgl-ctrl-icon{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg width='29' height='29' viewBox='0 0 29 29' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M24 16v5.5c0 1.75-.75 2.5-2.5 2.5H16v-1l3-1.5-4-5.5 1-1 5.5 4 1.5-3h1zM6 16l1.5 3 5.5-4 1 1-4 5.5 3 1.5v1H7.5C5.75 24 5 23.25 5 21.5V16h1zm7-11v1l-3 1.5 4 5.5-1 1-5.5-4L6 13H5V7.5C5 5.75 5.75 5 7.5 5H13zm11 2.5c0-1.75-.75-2.5-2.5-2.5H16v1l3 1.5-4 5.5 1 1 5.5-4 1.5 3h1V7.5z'/%3E%3C/svg%3E")}.mapboxgl-ctrl button.mapboxgl-ctrl-shrink .mapboxgl-ctrl-icon{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg width='29' height='29' viewBox='0 0 29 29' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M18.5 16c-1.75 0-2.5.75-2.5 2.5V24h1l1.5-3 5.5 4 1-1-4-5.5 3-1.5v-1h-5.5zM13 18.5c0-1.75-.75-2.5-2.5-2.5H5v1l3 1.5L4 24l1 1 5.5-4 1.5 3h1v-5.5zm3-8c0 1.75.75 2.5 2.5 2.5H24v-1l-3-1.5L25 5l-1-1-5.5 4L17 5h-1v5.5zM10.5 13c1.75 0 2.5-.75 2.5-2.5V5h-1l-1.5 3L5 4 4 5l4 5.5L5 12v1h5.5z'/%3E%3C/svg%3E")}}.mapboxgl-ctrl button.mapboxgl-ctrl-compass .mapboxgl-ctrl-icon{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg width='29' height='29' viewBox='0 0 29 29' xmlns='http://www.w3.org/2000/svg' fill='%23333'%3E%3Cpath d='M10.5 14l4-8 4 8h-8z'/%3E%3Cpath d='M10.5 16l4 8 4-8h-8z' fill='%23ccc'/%3E%3C/svg%3E")}@media (-ms-high-contrast:active){.mapboxgl-ctrl button.mapboxgl-ctrl-compass .mapboxgl-ctrl-icon{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg width='29' height='29' viewBox='0 0 29 29' xmlns='http://www.w3.org/2000/svg' fill='%23fff'%3E%3Cpath d='M10.5 14l4-8 4 8h-8z'/%3E%3Cpath d='M10.5 16l4 8 4-8h-8z' fill='%23999'/%3E%3C/svg%3E")}}@media (-ms-high-contrast:black-on-white){.mapboxgl-ctrl button.mapboxgl-ctrl-compass .mapboxgl-ctrl-icon{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg width='29' height='29' viewBox='0 0 29 29' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M10.5 14l4-8 4 8h-8z'/%3E%3Cpath d='M10.5 16l4 8 4-8h-8z' fill='%23ccc'/%3E%3C/svg%3E")}}.mapboxgl-ctrl button.mapboxgl-ctrl-geolocate .mapboxgl-ctrl-icon{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg width='29' height='29' viewBox='0 0 20 20' xmlns='http://www.w3.org/2000/svg' fill='%23333'%3E%3Cpath d='M10 4C9 4 9 5 9 5v.1A5 5 0 005.1 9H5s-1 0-1 1 1 1 1 1h.1A5 5 0 009 14.9v.1s0 1 1 1 1-1 1-1v-.1a5 5 0 003.9-3.9h.1s1 0 1-1-1-1-1-1h-.1A5 5 0 0011 5.1V5s0-1-1-1zm0 2.5a3.5 3.5 0 110 7 3.5 3.5 0 110-7z'/%3E%3Ccircle cx='10' cy='10' r='2'/%3E%3C/svg%3E")}.mapboxgl-ctrl button.mapboxgl-ctrl-geolocate:disabled .mapboxgl-ctrl-icon{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg width='29' height='29' viewBox='0 0 20 20' xmlns='http://www.w3.org/2000/svg' fill='%23aaa'%3E%3Cpath d='M10 4C9 4 9 5 9 5v.1A5 5 0 005.1 9H5s-1 0-1 1 1 1 1 1h.1A5 5 0 009 14.9v.1s0 1 1 1 1-1 1-1v-.1a5 5 0 003.9-3.9h.1s1 0 1-1-1-1-1-1h-.1A5 5 0 0011 5.1V5s0-1-1-1zm0 2.5a3.5 3.5 0 110 7 3.5 3.5 0 110-7z'/%3E%3Ccircle cx='10' cy='10' r='2'/%3E%3Cpath d='M14 5l1 1-9 9-1-1 9-9z' fill='red'/%3E%3C/svg%3E")}.mapboxgl-ctrl button.mapboxgl-ctrl-geolocate.mapboxgl-ctrl-geolocate-active .mapboxgl-ctrl-icon{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg width='29' height='29' viewBox='0 0 20 20' xmlns='http://www.w3.org/2000/svg' fill='%2333b5e5'%3E%3Cpath d='M10 4C9 4 9 5 9 5v.1A5 5 0 005.1 9H5s-1 0-1 1 1 1 1 1h.1A5 5 0 009 14.9v.1s0 1 1 1 1-1 1-1v-.1a5 5 0 003.9-3.9h.1s1 0 1-1-1-1-1-1h-.1A5 5 0 0011 5.1V5s0-1-1-1zm0 2.5a3.5 3.5 0 110 7 3.5 3.5 0 110-7z'/%3E%3Ccircle cx='10' cy='10' r='2'/%3E%3C/svg%3E")}.mapboxgl-ctrl button.mapboxgl-ctrl-geolocate.mapboxgl-ctrl-geolocate-active-error .mapboxgl-ctrl-icon{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg width='29' height='29' viewBox='0 0 20 20' xmlns='http://www.w3.org/2000/svg' fill='%23e58978'%3E%3Cpath d='M10 4C9 4 9 5 9 5v.1A5 5 0 005.1 9H5s-1 0-1 1 1 1 1 1h.1A5 5 0 009 14.9v.1s0 1 1 1 1-1 1-1v-.1a5 5 0 003.9-3.9h.1s1 0 1-1-1-1-1-1h-.1A5 5 0 0011 5.1V5s0-1-1-1zm0 2.5a3.5 3.5 0 110 7 3.5 3.5 0 110-7z'/%3E%3Ccircle cx='10' cy='10' r='2'/%3E%3C/svg%3E")}.mapboxgl-ctrl button.mapboxgl-ctrl-geolocate.mapboxgl-ctrl-geolocate-background .mapboxgl-ctrl-icon{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg width='29' height='29' viewBox='0 0 20 20' xmlns='http://www.w3.org/2000/svg' fill='%2333b5e5'%3E%3Cpath d='M10 4C9 4 9 5 9 5v.1A5 5 0 005.1 9H5s-1 0-1 1 1 1 1 1h.1A5 5 0 009 14.9v.1s0 1 1 1 1-1 1-1v-.1a5 5 0 003.9-3.9h.1s1 0 1-1-1-1-1-1h-.1A5 5 0 0011 5.1V5s0-1-1-1zm0 2.5a3.5 3.5 0 110 7 3.5 3.5 0 110-7z'/%3E%3C/svg%3E")}.mapboxgl-ctrl button.mapboxgl-ctrl-geolocate.mapboxgl-ctrl-geolocate-background-error .mapboxgl-ctrl-icon{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg width='29' height='29' viewBox='0 0 20 20' xmlns='http://www.w3.org/2000/svg' fill='%23e54e33'%3E%3Cpath d='M10 4C9 4 9 5 9 5v.1A5 5 0 005.1 9H5s-1 0-1 1 1 1 1 1h.1A5 5 0 009 14.9v.1s0 1 1 1 1-1 1-1v-.1a5 5 0 003.9-3.9h.1s1 0 1-1-1-1-1-1h-.1A5 5 0 0011 5.1V5s0-1-1-1zm0 2.5a3.5 3.5 0 110 7 3.5 3.5 0 110-7z'/%3E%3C/svg%3E")}.mapboxgl-ctrl button.mapboxgl-ctrl-geolocate.mapboxgl-ctrl-geolocate-waiting .mapboxgl-ctrl-icon{-webkit-animation:mapboxgl-spin 2s linear infinite;-moz-animation:mapboxgl-spin 2s infinite linear;-o-animation:mapboxgl-spin 2s infinite linear;-ms-animation:mapboxgl-spin 2s infinite linear;animation:mapboxgl-spin 2s linear infinite}@media (-ms-high-contrast:active){.mapboxgl-ctrl button.mapboxgl-ctrl-geolocate .mapboxgl-ctrl-icon{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg width='29' height='29' viewBox='0 0 20 20' xmlns='http://www.w3.org/2000/svg' fill='%23fff'%3E%3Cpath d='M10 4C9 4 9 5 9 5v.1A5 5 0 005.1 9H5s-1 0-1 1 1 1 1 1h.1A5 5 0 009 14.9v.1s0 1 1 1 1-1 1-1v-.1a5 5 0 003.9-3.9h.1s1 0 1-1-1-1-1-1h-.1A5 5 0 0011 5.1V5s0-1-1-1zm0 2.5a3.5 3.5 0 110 7 3.5 3.5 0 110-7z'/%3E%3Ccircle cx='10' cy='10' r='2'/%3E%3C/svg%3E")}.mapboxgl-ctrl button.mapboxgl-ctrl-geolocate:disabled .mapboxgl-ctrl-icon{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg width='29' height='29' viewBox='0 0 20 20' xmlns='http://www.w3.org/2000/svg' fill='%23999'%3E%3Cpath d='M10 4C9 4 9 5 9 5v.1A5 5 0 005.1 9H5s-1 0-1 1 1 1 1 1h.1A5 5 0 009 14.9v.1s0 1 1 1 1-1 1-1v-.1a5 5 0 003.9-3.9h.1s1 0 1-1-1-1-1-1h-.1A5 5 0 0011 5.1V5s0-1-1-1zm0 2.5a3.5 3.5 0 110 7 3.5 3.5 0 110-7z'/%3E%3Ccircle cx='10' cy='10' r='2'/%3E%3Cpath d='M14 5l1 1-9 9-1-1 9-9z' fill='red'/%3E%3C/svg%3E")}.mapboxgl-ctrl button.mapboxgl-ctrl-geolocate.mapboxgl-ctrl-geolocate-active .mapboxgl-ctrl-icon{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg width='29' height='29' viewBox='0 0 20 20' xmlns='http://www.w3.org/2000/svg' fill='%2333b5e5'%3E%3Cpath d='M10 4C9 4 9 5 9 5v.1A5 5 0 005.1 9H5s-1 0-1 1 1 1 1 1h.1A5 5 0 009 14.9v.1s0 1 1 1 1-1 1-1v-.1a5 5 0 003.9-3.9h.1s1 0 1-1-1-1-1-1h-.1A5 5 0 0011 5.1V5s0-1-1-1zm0 2.5a3.5 3.5 0 110 7 3.5 3.5 0 110-7z'/%3E%3Ccircle cx='10' cy='10' r='2'/%3E%3C/svg%3E")}.mapboxgl-ctrl button.mapboxgl-ctrl-geolocate.mapboxgl-ctrl-geolocate-active-error .mapboxgl-ctrl-icon{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg width='29' height='29' viewBox='0 0 20 20' xmlns='http://www.w3.org/2000/svg' fill='%23e58978'%3E%3Cpath d='M10 4C9 4 9 5 9 5v.1A5 5 0 005.1 9H5s-1 0-1 1 1 1 1 1h.1A5 5 0 009 14.9v.1s0 1 1 1 1-1 1-1v-.1a5 5 0 003.9-3.9h.1s1 0 1-1-1-1-1-1h-.1A5 5 0 0011 5.1V5s0-1-1-1zm0 2.5a3.5 3.5 0 110 7 3.5 3.5 0 110-7z'/%3E%3Ccircle cx='10' cy='10' r='2'/%3E%3C/svg%3E")}.mapboxgl-ctrl button.mapboxgl-ctrl-geolocate.mapboxgl-ctrl-geolocate-background .mapboxgl-ctrl-icon{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg width='29' height='29' viewBox='0 0 20 20' xmlns='http://www.w3.org/2000/svg' fill='%2333b5e5'%3E%3Cpath d='M10 4C9 4 9 5 9 5v.1A5 5 0 005.1 9H5s-1 0-1 1 1 1 1 1h.1A5 5 0 009 14.9v.1s0 1 1 1 1-1 1-1v-.1a5 5 0 003.9-3.9h.1s1 0 1-1-1-1-1-1h-.1A5 5 0 0011 5.1V5s0-1-1-1zm0 2.5a3.5 3.5 0 110 7 3.5 3.5 0 110-7z'/%3E%3C/svg%3E")}.mapboxgl-ctrl button.mapboxgl-ctrl-geolocate.mapboxgl-ctrl-geolocate-background-error .mapboxgl-ctrl-icon{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg width='29' height='29' viewBox='0 0 20 20' xmlns='http://www.w3.org/2000/svg' fill='%23e54e33'%3E%3Cpath d='M10 4C9 4 9 5 9 5v.1A5 5 0 005.1 9H5s-1 0-1 1 1 1 1 1h.1A5 5 0 009 14.9v.1s0 1 1 1 1-1 1-1v-.1a5 5 0 003.9-3.9h.1s1 0 1-1-1-1-1-1h-.1A5 5 0 0011 5.1V5s0-1-1-1zm0 2.5a3.5 3.5 0 110 7 3.5 3.5 0 110-7z'/%3E%3C/svg%3E")}}@media (-ms-high-contrast:black-on-white){.mapboxgl-ctrl button.mapboxgl-ctrl-geolocate .mapboxgl-ctrl-icon{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg width='29' height='29' viewBox='0 0 20 20' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M10 4C9 4 9 5 9 5v.1A5 5 0 005.1 9H5s-1 0-1 1 1 1 1 1h.1A5 5 0 009 14.9v.1s0 1 1 1 1-1 1-1v-.1a5 5 0 003.9-3.9h.1s1 0 1-1-1-1-1-1h-.1A5 5 0 0011 5.1V5s0-1-1-1zm0 2.5a3.5 3.5 0 110 7 3.5 3.5 0 110-7z'/%3E%3Ccircle cx='10' cy='10' r='2'/%3E%3C/svg%3E")}.mapboxgl-ctrl button.mapboxgl-ctrl-geolocate:disabled .mapboxgl-ctrl-icon{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg width='29' height='29' viewBox='0 0 20 20' xmlns='http://www.w3.org/2000/svg' fill='%23666'%3E%3Cpath d='M10 4C9 4 9 5 9 5v.1A5 5 0 005.1 9H5s-1 0-1 1 1 1 1 1h.1A5 5 0 009 14.9v.1s0 1 1 1 1-1 1-1v-.1a5 5 0 003.9-3.9h.1s1 0 1-1-1-1-1-1h-.1A5 5 0 0011 5.1V5s0-1-1-1zm0 2.5a3.5 3.5 0 110 7 3.5 3.5 0 110-7z'/%3E%3Ccircle cx='10' cy='10' r='2'/%3E%3Cpath d='M14 5l1 1-9 9-1-1 9-9z' fill='red'/%3E%3C/svg%3E")}}@-webkit-keyframes mapboxgl-spin{0%{-webkit-transform:rotate(0deg)}to{-webkit-transform:rotate(1turn)}}@-moz-keyframes mapboxgl-spin{0%{-moz-transform:rotate(0deg)}to{-moz-transform:rotate(1turn)}}@-o-keyframes mapboxgl-spin{0%{-o-transform:rotate(0deg)}to{-o-transform:rotate(1turn)}}@-ms-keyframes mapboxgl-spin{0%{-ms-transform:rotate(0deg)}to{-ms-transform:rotate(1turn)}}@keyframes mapboxgl-spin{0%{transform:rotate(0deg)}to{transform:rotate(1turn)}}a.mapboxgl-ctrl-logo{width:88px;height:23px;margin:0 0 -4px -4px;display:block;background-repeat:no-repeat;cursor:pointer;overflow:hidden;background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg width='88' height='23' viewBox='0 0 88 23' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' fill-rule='evenodd'%3E%3Cdefs%3E%3Cpath id='a' d='M11.5 2.25c5.105 0 9.25 4.145 9.25 9.25s-4.145 9.25-9.25 9.25-9.25-4.145-9.25-9.25 4.145-9.25 9.25-9.25zM6.997 15.983c-.051-.338-.828-5.802 2.233-8.873a4.395 4.395 0 013.13-1.28c1.27 0 2.49.51 3.39 1.42.91.9 1.42 2.12 1.42 3.39 0 1.18-.449 2.301-1.28 3.13C12.72 16.93 7 16 7 16l-.003-.017zM15.3 10.5l-2 .8-.8 2-.8-2-2-.8 2-.8.8-2 .8 2 2 .8z'/%3E%3Cpath id='b' d='M50.63 8c.13 0 .23.1.23.23V9c.7-.76 1.7-1.18 2.73-1.18 2.17 0 3.95 1.85 3.95 4.17s-1.77 4.19-3.94 4.19c-1.04 0-2.03-.43-2.74-1.18v3.77c0 .13-.1.23-.23.23h-1.4c-.13 0-.23-.1-.23-.23V8.23c0-.12.1-.23.23-.23h1.4zm-3.86.01c.01 0 .01 0 .01-.01.13 0 .22.1.22.22v7.55c0 .12-.1.23-.23.23h-1.4c-.13 0-.23-.1-.23-.23V15c-.7.76-1.69 1.19-2.73 1.19-2.17 0-3.94-1.87-3.94-4.19 0-2.32 1.77-4.19 3.94-4.19 1.03 0 2.02.43 2.73 1.18v-.75c0-.12.1-.23.23-.23h1.4zm26.375-.19a4.24 4.24 0 00-4.16 3.29c-.13.59-.13 1.19 0 1.77a4.233 4.233 0 004.17 3.3c2.35 0 4.26-1.87 4.26-4.19 0-2.32-1.9-4.17-4.27-4.17zM60.63 5c.13 0 .23.1.23.23v3.76c.7-.76 1.7-1.18 2.73-1.18 1.88 0 3.45 1.4 3.84 3.28.13.59.13 1.2 0 1.8-.39 1.88-1.96 3.29-3.84 3.29-1.03 0-2.02-.43-2.73-1.18v.77c0 .12-.1.23-.23.23h-1.4c-.13 0-.23-.1-.23-.23V5.23c0-.12.1-.23.23-.23h1.4zm-34 11h-1.4c-.13 0-.23-.11-.23-.23V8.22c.01-.13.1-.22.23-.22h1.4c.13 0 .22.11.23.22v.68c.5-.68 1.3-1.09 2.16-1.1h.03c1.09 0 2.09.6 2.6 1.55.45-.95 1.4-1.55 2.44-1.56 1.62 0 2.93 1.25 2.9 2.78l.03 5.2c0 .13-.1.23-.23.23h-1.41c-.13 0-.23-.11-.23-.23v-4.59c0-.98-.74-1.71-1.62-1.71-.8 0-1.46.7-1.59 1.62l.01 4.68c0 .13-.11.23-.23.23h-1.41c-.13 0-.23-.11-.23-.23v-4.59c0-.98-.74-1.71-1.62-1.71-.85 0-1.54.79-1.6 1.8v4.5c0 .13-.1.23-.23.23zm53.615 0h-1.61c-.04 0-.08-.01-.12-.03-.09-.06-.13-.19-.06-.28l2.43-3.71-2.39-3.65a.213.213 0 01-.03-.12c0-.12.09-.21.21-.21h1.61c.13 0 .24.06.3.17l1.41 2.37 1.4-2.37a.34.34 0 01.3-.17h1.6c.04 0 .08.01.12.03.09.06.13.19.06.28l-2.37 3.65 2.43 3.7c0 .05.01.09.01.13 0 .12-.09.21-.21.21h-1.61c-.13 0-.24-.06-.3-.17l-1.44-2.42-1.44 2.42a.34.34 0 01-.3.17zm-7.12-1.49c-1.33 0-2.42-1.12-2.42-2.51 0-1.39 1.08-2.52 2.42-2.52 1.33 0 2.42 1.12 2.42 2.51 0 1.39-1.08 2.51-2.42 2.52zm-19.865 0c-1.32 0-2.39-1.11-2.42-2.48v-.07c.02-1.38 1.09-2.49 2.4-2.49 1.32 0 2.41 1.12 2.41 2.51 0 1.39-1.07 2.52-2.39 2.53zm-8.11-2.48c-.01 1.37-1.09 2.47-2.41 2.47s-2.42-1.12-2.42-2.51c0-1.39 1.08-2.52 2.4-2.52 1.33 0 2.39 1.11 2.41 2.48l.02.08zm18.12 2.47c-1.32 0-2.39-1.11-2.41-2.48v-.06c.02-1.38 1.09-2.48 2.41-2.48s2.42 1.12 2.42 2.51c0 1.39-1.09 2.51-2.42 2.51z'/%3E%3C/defs%3E%3Cmask id='c'%3E%3Crect width='100%25' height='100%25' fill='%23fff'/%3E%3Cuse xlink:href='%23a'/%3E%3Cuse xlink:href='%23b'/%3E%3C/mask%3E%3Cg opacity='.3' stroke='%23000' stroke-width='3'%3E%3Ccircle mask='url(%23c)' cx='11.5' cy='11.5' r='9.25'/%3E%3Cuse xlink:href='%23b' mask='url(%23c)'/%3E%3C/g%3E%3Cg opacity='.9' fill='%23fff'%3E%3Cuse xlink:href='%23a'/%3E%3Cuse xlink:href='%23b'/%3E%3C/g%3E%3C/svg%3E")}a.mapboxgl-ctrl-logo.mapboxgl-compact{width:23px}@media (-ms-high-contrast:active){a.mapboxgl-ctrl-logo{background-color:transparent;background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg width='88' height='23' viewBox='0 0 88 23' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' fill-rule='evenodd'%3E%3Cdefs%3E%3Cpath id='a' d='M11.5 2.25c5.105 0 9.25 4.145 9.25 9.25s-4.145 9.25-9.25 9.25-9.25-4.145-9.25-9.25 4.145-9.25 9.25-9.25zM6.997 15.983c-.051-.338-.828-5.802 2.233-8.873a4.395 4.395 0 013.13-1.28c1.27 0 2.49.51 3.39 1.42.91.9 1.42 2.12 1.42 3.39 0 1.18-.449 2.301-1.28 3.13C12.72 16.93 7 16 7 16l-.003-.017zM15.3 10.5l-2 .8-.8 2-.8-2-2-.8 2-.8.8-2 .8 2 2 .8z'/%3E%3Cpath id='b' d='M50.63 8c.13 0 .23.1.23.23V9c.7-.76 1.7-1.18 2.73-1.18 2.17 0 3.95 1.85 3.95 4.17s-1.77 4.19-3.94 4.19c-1.04 0-2.03-.43-2.74-1.18v3.77c0 .13-.1.23-.23.23h-1.4c-.13 0-.23-.1-.23-.23V8.23c0-.12.1-.23.23-.23h1.4zm-3.86.01c.01 0 .01 0 .01-.01.13 0 .22.1.22.22v7.55c0 .12-.1.23-.23.23h-1.4c-.13 0-.23-.1-.23-.23V15c-.7.76-1.69 1.19-2.73 1.19-2.17 0-3.94-1.87-3.94-4.19 0-2.32 1.77-4.19 3.94-4.19 1.03 0 2.02.43 2.73 1.18v-.75c0-.12.1-.23.23-.23h1.4zm26.375-.19a4.24 4.24 0 00-4.16 3.29c-.13.59-.13 1.19 0 1.77a4.233 4.233 0 004.17 3.3c2.35 0 4.26-1.87 4.26-4.19 0-2.32-1.9-4.17-4.27-4.17zM60.63 5c.13 0 .23.1.23.23v3.76c.7-.76 1.7-1.18 2.73-1.18 1.88 0 3.45 1.4 3.84 3.28.13.59.13 1.2 0 1.8-.39 1.88-1.96 3.29-3.84 3.29-1.03 0-2.02-.43-2.73-1.18v.77c0 .12-.1.23-.23.23h-1.4c-.13 0-.23-.1-.23-.23V5.23c0-.12.1-.23.23-.23h1.4zm-34 11h-1.4c-.13 0-.23-.11-.23-.23V8.22c.01-.13.1-.22.23-.22h1.4c.13 0 .22.11.23.22v.68c.5-.68 1.3-1.09 2.16-1.1h.03c1.09 0 2.09.6 2.6 1.55.45-.95 1.4-1.55 2.44-1.56 1.62 0 2.93 1.25 2.9 2.78l.03 5.2c0 .13-.1.23-.23.23h-1.41c-.13 0-.23-.11-.23-.23v-4.59c0-.98-.74-1.71-1.62-1.71-.8 0-1.46.7-1.59 1.62l.01 4.68c0 .13-.11.23-.23.23h-1.41c-.13 0-.23-.11-.23-.23v-4.59c0-.98-.74-1.71-1.62-1.71-.85 0-1.54.79-1.6 1.8v4.5c0 .13-.1.23-.23.23zm53.615 0h-1.61c-.04 0-.08-.01-.12-.03-.09-.06-.13-.19-.06-.28l2.43-3.71-2.39-3.65a.213.213 0 01-.03-.12c0-.12.09-.21.21-.21h1.61c.13 0 .24.06.3.17l1.41 2.37 1.4-2.37a.34.34 0 01.3-.17h1.6c.04 0 .08.01.12.03.09.06.13.19.06.28l-2.37 3.65 2.43 3.7c0 .05.01.09.01.13 0 .12-.09.21-.21.21h-1.61c-.13 0-.24-.06-.3-.17l-1.44-2.42-1.44 2.42a.34.34 0 01-.3.17zm-7.12-1.49c-1.33 0-2.42-1.12-2.42-2.51 0-1.39 1.08-2.52 2.42-2.52 1.33 0 2.42 1.12 2.42 2.51 0 1.39-1.08 2.51-2.42 2.52zm-19.865 0c-1.32 0-2.39-1.11-2.42-2.48v-.07c.02-1.38 1.09-2.49 2.4-2.49 1.32 0 2.41 1.12 2.41 2.51 0 1.39-1.07 2.52-2.39 2.53zm-8.11-2.48c-.01 1.37-1.09 2.47-2.41 2.47s-2.42-1.12-2.42-2.51c0-1.39 1.08-2.52 2.4-2.52 1.33 0 2.39 1.11 2.41 2.48l.02.08zm18.12 2.47c-1.32 0-2.39-1.11-2.41-2.48v-.06c.02-1.38 1.09-2.48 2.41-2.48s2.42 1.12 2.42 2.51c0 1.39-1.09 2.51-2.42 2.51z'/%3E%3C/defs%3E%3Cmask id='c'%3E%3Crect width='100%25' height='100%25' fill='%23fff'/%3E%3Cuse xlink:href='%23a'/%3E%3Cuse xlink:href='%23b'/%3E%3C/mask%3E%3Cg stroke='%23000' stroke-width='3'%3E%3Ccircle mask='url(%23c)' cx='11.5' cy='11.5' r='9.25'/%3E%3Cuse xlink:href='%23b' mask='url(%23c)'/%3E%3C/g%3E%3Cg fill='%23fff'%3E%3Cuse xlink:href='%23a'/%3E%3Cuse xlink:href='%23b'/%3E%3C/g%3E%3C/svg%3E")}}@media (-ms-high-contrast:black-on-white){a.mapboxgl-ctrl-logo{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg width='88' height='23' viewBox='0 0 88 23' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' fill-rule='evenodd'%3E%3Cdefs%3E%3Cpath id='a' d='M11.5 2.25c5.105 0 9.25 4.145 9.25 9.25s-4.145 9.25-9.25 9.25-9.25-4.145-9.25-9.25 4.145-9.25 9.25-9.25zM6.997 15.983c-.051-.338-.828-5.802 2.233-8.873a4.395 4.395 0 013.13-1.28c1.27 0 2.49.51 3.39 1.42.91.9 1.42 2.12 1.42 3.39 0 1.18-.449 2.301-1.28 3.13C12.72 16.93 7 16 7 16l-.003-.017zM15.3 10.5l-2 .8-.8 2-.8-2-2-.8 2-.8.8-2 .8 2 2 .8z'/%3E%3Cpath id='b' d='M50.63 8c.13 0 .23.1.23.23V9c.7-.76 1.7-1.18 2.73-1.18 2.17 0 3.95 1.85 3.95 4.17s-1.77 4.19-3.94 4.19c-1.04 0-2.03-.43-2.74-1.18v3.77c0 .13-.1.23-.23.23h-1.4c-.13 0-.23-.1-.23-.23V8.23c0-.12.1-.23.23-.23h1.4zm-3.86.01c.01 0 .01 0 .01-.01.13 0 .22.1.22.22v7.55c0 .12-.1.23-.23.23h-1.4c-.13 0-.23-.1-.23-.23V15c-.7.76-1.69 1.19-2.73 1.19-2.17 0-3.94-1.87-3.94-4.19 0-2.32 1.77-4.19 3.94-4.19 1.03 0 2.02.43 2.73 1.18v-.75c0-.12.1-.23.23-.23h1.4zm26.375-.19a4.24 4.24 0 00-4.16 3.29c-.13.59-.13 1.19 0 1.77a4.233 4.233 0 004.17 3.3c2.35 0 4.26-1.87 4.26-4.19 0-2.32-1.9-4.17-4.27-4.17zM60.63 5c.13 0 .23.1.23.23v3.76c.7-.76 1.7-1.18 2.73-1.18 1.88 0 3.45 1.4 3.84 3.28.13.59.13 1.2 0 1.8-.39 1.88-1.96 3.29-3.84 3.29-1.03 0-2.02-.43-2.73-1.18v.77c0 .12-.1.23-.23.23h-1.4c-.13 0-.23-.1-.23-.23V5.23c0-.12.1-.23.23-.23h1.4zm-34 11h-1.4c-.13 0-.23-.11-.23-.23V8.22c.01-.13.1-.22.23-.22h1.4c.13 0 .22.11.23.22v.68c.5-.68 1.3-1.09 2.16-1.1h.03c1.09 0 2.09.6 2.6 1.55.45-.95 1.4-1.55 2.44-1.56 1.62 0 2.93 1.25 2.9 2.78l.03 5.2c0 .13-.1.23-.23.23h-1.41c-.13 0-.23-.11-.23-.23v-4.59c0-.98-.74-1.71-1.62-1.71-.8 0-1.46.7-1.59 1.62l.01 4.68c0 .13-.11.23-.23.23h-1.41c-.13 0-.23-.11-.23-.23v-4.59c0-.98-.74-1.71-1.62-1.71-.85 0-1.54.79-1.6 1.8v4.5c0 .13-.1.23-.23.23zm53.615 0h-1.61c-.04 0-.08-.01-.12-.03-.09-.06-.13-.19-.06-.28l2.43-3.71-2.39-3.65a.213.213 0 01-.03-.12c0-.12.09-.21.21-.21h1.61c.13 0 .24.06.3.17l1.41 2.37 1.4-2.37a.34.34 0 01.3-.17h1.6c.04 0 .08.01.12.03.09.06.13.19.06.28l-2.37 3.65 2.43 3.7c0 .05.01.09.01.13 0 .12-.09.21-.21.21h-1.61c-.13 0-.24-.06-.3-.17l-1.44-2.42-1.44 2.42a.34.34 0 01-.3.17zm-7.12-1.49c-1.33 0-2.42-1.12-2.42-2.51 0-1.39 1.08-2.52 2.42-2.52 1.33 0 2.42 1.12 2.42 2.51 0 1.39-1.08 2.51-2.42 2.52zm-19.865 0c-1.32 0-2.39-1.11-2.42-2.48v-.07c.02-1.38 1.09-2.49 2.4-2.49 1.32 0 2.41 1.12 2.41 2.51 0 1.39-1.07 2.52-2.39 2.53zm-8.11-2.48c-.01 1.37-1.09 2.47-2.41 2.47s-2.42-1.12-2.42-2.51c0-1.39 1.08-2.52 2.4-2.52 1.33 0 2.39 1.11 2.41 2.48l.02.08zm18.12 2.47c-1.32 0-2.39-1.11-2.41-2.48v-.06c.02-1.38 1.09-2.48 2.41-2.48s2.42 1.12 2.42 2.51c0 1.39-1.09 2.51-2.42 2.51z'/%3E%3C/defs%3E%3Cmask id='c'%3E%3Crect width='100%25' height='100%25' fill='%23fff'/%3E%3Cuse xlink:href='%23a'/%3E%3Cuse xlink:href='%23b'/%3E%3C/mask%3E%3Cg stroke='%23fff' stroke-width='3' fill='%23fff'%3E%3Ccircle mask='url(%23c)' cx='11.5' cy='11.5' r='9.25'/%3E%3Cuse xlink:href='%23b' mask='url(%23c)'/%3E%3C/g%3E%3Cuse xlink:href='%23a'/%3E%3Cuse xlink:href='%23b'/%3E%3C/svg%3E")}}.mapboxgl-ctrl.mapboxgl-ctrl-attrib{padding:0 5px;background-color:hsla(0,0%,100%,.5);margin:0}@media screen{.mapboxgl-ctrl-attrib.mapboxgl-compact{min-height:20px;padding:0;margin:10px;position:relative;background-color:#fff;border-radius:3px 12px 12px 3px}.mapboxgl-ctrl-attrib.mapboxgl-compact:hover{padding:2px 24px 2px 4px;visibility:visible;margin-top:6px}.mapboxgl-ctrl-bottom-left>.mapboxgl-ctrl-attrib.mapboxgl-compact:hover,.mapboxgl-ctrl-top-left>.mapboxgl-ctrl-attrib.mapboxgl-compact:hover{padding:2px 4px 2px 24px;border-radius:12px 3px 3px 12px}.mapboxgl-ctrl-attrib.mapboxgl-compact .mapboxgl-ctrl-attrib-inner{display:none}.mapboxgl-ctrl-attrib.mapboxgl-compact:hover .mapboxgl-ctrl-attrib-inner{display:block}.mapboxgl-ctrl-attrib.mapboxgl-compact:after{content:"";cursor:pointer;position:absolute;background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg width='24' height='24' viewBox='0 0 20 20' xmlns='http://www.w3.org/2000/svg' fill-rule='evenodd'%3E%3Cpath d='M4 10a6 6 0 1012 0 6 6 0 10-12 0m5-3a1 1 0 102 0 1 1 0 10-2 0m0 3a1 1 0 112 0v3a1 1 0 11-2 0'/%3E%3C/svg%3E");background-color:hsla(0,0%,100%,.5);width:24px;height:24px;box-sizing:border-box;border-radius:12px}.mapboxgl-ctrl-bottom-right>.mapboxgl-ctrl-attrib.mapboxgl-compact:after{bottom:0;right:0}.mapboxgl-ctrl-top-right>.mapboxgl-ctrl-attrib.mapboxgl-compact:after{top:0;right:0}.mapboxgl-ctrl-top-left>.mapboxgl-ctrl-attrib.mapboxgl-compact:after{top:0;left:0}.mapboxgl-ctrl-bottom-left>.mapboxgl-ctrl-attrib.mapboxgl-compact:after{bottom:0;left:0}}@media screen and (-ms-high-contrast:active){.mapboxgl-ctrl-attrib.mapboxgl-compact:after{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg width='24' height='24' viewBox='0 0 20 20' xmlns='http://www.w3.org/2000/svg' fill-rule='evenodd' fill='%23fff'%3E%3Cpath d='M4 10a6 6 0 1012 0 6 6 0 10-12 0m5-3a1 1 0 102 0 1 1 0 10-2 0m0 3a1 1 0 112 0v3a1 1 0 11-2 0'/%3E%3C/svg%3E")}}@media screen and (-ms-high-contrast:black-on-white){.mapboxgl-ctrl-attrib.mapboxgl-compact:after{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg width='24' height='24' viewBox='0 0 20 20' xmlns='http://www.w3.org/2000/svg' fill-rule='evenodd'%3E%3Cpath d='M4 10a6 6 0 1012 0 6 6 0 10-12 0m5-3a1 1 0 102 0 1 1 0 10-2 0m0 3a1 1 0 112 0v3a1 1 0 11-2 0'/%3E%3C/svg%3E")}}.mapboxgl-ctrl-attrib a{color:rgba(0,0,0,.75);text-decoration:none}.mapboxgl-ctrl-attrib a:hover{color:inherit;text-decoration:underline}.mapboxgl-ctrl-attrib .mapbox-improve-map{font-weight:700;margin-left:2px}.mapboxgl-attrib-empty{display:none}.mapboxgl-ctrl-scale{background-color:hsla(0,0%,100%,.75);font-size:10px;border:2px solid #333;border-top:#333;padding:0 5px;color:#333;box-sizing:border-box}.mapboxgl-popup{position:absolute;top:0;left:0;display:-webkit-flex;display:flex;will-change:transform;pointer-events:none}.mapboxgl-popup-anchor-top,.mapboxgl-popup-anchor-top-left,.mapboxgl-popup-anchor-top-right{-webkit-flex-direction:column;flex-direction:column}.mapboxgl-popup-anchor-bottom,.mapboxgl-popup-anchor-bottom-left,.mapboxgl-popup-anchor-bottom-right{-webkit-flex-direction:column-reverse;flex-direction:column-reverse}.mapboxgl-popup-anchor-left{-webkit-flex-direction:row;flex-direction:row}.mapboxgl-popup-anchor-right{-webkit-flex-direction:row-reverse;flex-direction:row-reverse}.mapboxgl-popup-tip{width:0;height:0;border:10px solid transparent;z-index:1}.mapboxgl-popup-anchor-top .mapboxgl-popup-tip{-webkit-align-self:center;align-self:center;border-top:none;border-bottom-color:#fff}.mapboxgl-popup-anchor-top-left .mapboxgl-popup-tip{-webkit-align-self:flex-start;align-self:flex-start;border-top:none;border-left:none;border-bottom-color:#fff}.mapboxgl-popup-anchor-top-right .mapboxgl-popup-tip{-webkit-align-self:flex-end;align-self:flex-end;border-top:none;border-right:none;border-bottom-color:#fff}.mapboxgl-popup-anchor-bottom .mapboxgl-popup-tip{-webkit-align-self:center;align-self:center;border-bottom:none;border-top-color:#fff}.mapboxgl-popup-anchor-bottom-left .mapboxgl-popup-tip{-webkit-align-self:flex-start;align-self:flex-start;border-bottom:none;border-left:none;border-top-color:#fff}.mapboxgl-popup-anchor-bottom-right .mapboxgl-popup-tip{-webkit-align-self:flex-end;align-self:flex-end;border-bottom:none;border-right:none;border-top-color:#fff}.mapboxgl-popup-anchor-left .mapboxgl-popup-tip{-webkit-align-self:center;align-self:center;border-left:none;border-right-color:#fff}.mapboxgl-popup-anchor-right .mapboxgl-popup-tip{-webkit-align-self:center;align-self:center;border-right:none;border-left-color:#fff}.mapboxgl-popup-close-button{position:absolute;right:0;top:0;border:0;border-radius:0 3px 0 0;cursor:pointer;background-color:transparent}.mapboxgl-popup-close-button:hover{background-color:rgba(0,0,0,.05)}.mapboxgl-popup-content{position:relative;background:#fff;border-radius:3px;box-shadow:0 1px 2px rgba(0,0,0,.1);padding:10px 10px 15px;pointer-events:auto}.mapboxgl-popup-anchor-top-left .mapboxgl-popup-content{border-top-left-radius:0}.mapboxgl-popup-anchor-top-right .mapboxgl-popup-content{border-top-right-radius:0}.mapboxgl-popup-anchor-bottom-left .mapboxgl-popup-content{border-bottom-left-radius:0}.mapboxgl-popup-anchor-bottom-right .mapboxgl-popup-content{border-bottom-right-radius:0}.mapboxgl-popup-track-pointer{display:none}.mapboxgl-popup-track-pointer *{pointer-events:none;user-select:none}.mapboxgl-map:hover .mapboxgl-popup-track-pointer{display:flex}.mapboxgl-map:active .mapboxgl-popup-track-pointer{display:none}.mapboxgl-marker{position:absolute;top:0;left:0;will-change:transform}.mapboxgl-user-location-dot,.mapboxgl-user-location-dot:before{background-color:#1da1f2;width:15px;height:15px;border-radius:50%}.mapboxgl-user-location-dot:before{content:"";position:absolute;-webkit-animation:mapboxgl-user-location-dot-pulse 2s infinite;-moz-animation:mapboxgl-user-location-dot-pulse 2s infinite;-ms-animation:mapboxgl-user-location-dot-pulse 2s infinite;animation:mapboxgl-user-location-dot-pulse 2s infinite}.mapboxgl-user-location-dot:after{border-radius:50%;border:2px solid #fff;content:"";height:19px;left:-2px;position:absolute;top:-2px;width:19px;box-sizing:border-box;box-shadow:0 0 3px rgba(0,0,0,.35)}@-webkit-keyframes mapboxgl-user-location-dot-pulse{0%{-webkit-transform:scale(1);opacity:1}70%{-webkit-transform:scale(3);opacity:0}to{-webkit-transform:scale(1);opacity:0}}@-ms-keyframes mapboxgl-user-location-dot-pulse{0%{-ms-transform:scale(1);opacity:1}70%{-ms-transform:scale(3);opacity:0}to{-ms-transform:scale(1);opacity:0}}@keyframes mapboxgl-user-location-dot-pulse{0%{transform:scale(1);opacity:1}70%{transform:scale(3);opacity:0}to{transform:scale(1);opacity:0}}.mapboxgl-user-location-dot-stale{background-color:#aaa}.mapboxgl-user-location-dot-stale:after{display:none}.mapboxgl-user-location-accuracy-circle{background-color:rgba(29,161,242,.2);width:1px;height:1px;border-radius:100%}.mapboxgl-crosshair,.mapboxgl-crosshair .mapboxgl-interactive,.mapboxgl-crosshair .mapboxgl-interactive:active{cursor:crosshair}.mapboxgl-boxzoom{position:absolute;top:0;left:0;width:0;height:0;background:#fff;border:2px dotted #202020;opacity:.5}@media print{.mapbox-improve-map{display:none}} \ No newline at end of file diff --git a/features/sdnr/odlux/odlux/apps/networkMapApp/src/utils/detailsUtils.ts b/features/sdnr/odlux/odlux/apps/networkMapApp/src/utils/detailsUtils.ts new file mode 100644 index 0000000..b16aac0 --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/networkMapApp/src/utils/detailsUtils.ts @@ -0,0 +1,48 @@ +import { dataService } from '../services/dataService'; +import { DetailsTypes, isLink, isService, isSite } from '../model/topologyTypes'; + +type DetailsDataUrl = { + type: string; + id: string; +} | null; + +class DetailsUtils { + + errorCallback: any; + + public setLoadDataError = (callback: (data: any) => void) => { + this.errorCallback = callback; + }; + + public isLoadNeeded = (detailsData: DetailsDataUrl, currentId: number | undefined, currentData: any) => { + + if (!detailsData) + return false; + + return (currentId?.toString() !== detailsData.id || + detailsData.type == DetailsTypes.site && !isSite(currentData) || + detailsData.type == DetailsTypes.link && !isLink(currentData) || + detailsData.type == DetailsTypes.service && !isService(currentData) + ); + }; + + public isTypeAllowed = (type: string) => { + return type == DetailsTypes.site || type == DetailsTypes.link || type == DetailsTypes.service; + }; + + public loadData = (type: string, id: string, callback: (data: any) => void) => { + + dataService.getDetailsData(type, id) + .then(res => { + if (res !== null) + callback(res); + else + this.errorCallback(id); + }); + }; + +} + +const detailsUtils = new DetailsUtils(); + +export default detailsUtils; \ No newline at end of file diff --git a/features/sdnr/odlux/odlux/apps/networkMapApp/src/utils/mapLayers.ts b/features/sdnr/odlux/odlux/apps/networkMapApp/src/utils/mapLayers.ts new file mode 100644 index 0000000..e42a604 --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/networkMapApp/src/utils/mapLayers.ts @@ -0,0 +1,275 @@ +/** + * ============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 { Feature } from '../model/topologyTypes'; +import { ThemeElement } from '../model/settings'; +import { CirclePaintProps, LinePaintProps } from './mapUtils'; + +class MapLayerService { + + availableThemes: ThemeElement[]; + + selectedTheme: string | null = null; + + iconLayersActive = false; + + public addBaseSources = (map: maplibregl.Map, selectedPoint: Feature | null, selectedLine: Feature | null, selectedService: Feature | null) => { + + // make sure the sources don't already exist + // (if the networkmap app gets opened quickly within short time periods, the prior sources might not be fully removed) + + if (!map.getSource('lines')) { + map.addSource('lines', { + type: 'geojson', + data: { type: 'FeatureCollection', features: [] }, + }); + } + + if (!map.getSource('services')) { + + map.addSource('services', { + type: 'geojson', + data: { type: 'FeatureCollection', features: [] }, + }); + } + + if (!map.getSource('selectedLine')) { + + const selectedElement = selectedLine || selectedService; + const features = selectedElement !== null ? [selectedElement] : []; + map.addSource('selectedLine', { + type: 'geojson', + data: { type: 'FeatureCollection', features: features }, + }); + } + + if (!map.getSource('points')) { + map.addSource('points', { + type: 'geojson', + data: { type: 'FeatureCollection', features: [] }, + }); + } + + if (!map.getSource('selectedPoints')) { + const selectedPointFeature = selectedPoint !== null ? [selectedPoint] : []; + map.addSource('selectedPoints', { + type: 'geojson', + data: { type: 'FeatureCollection', features: selectedPointFeature }, + + }); + } + }; + + public addCircleLayer = (map: maplibregl.Map, id: string, source: string, paint: CirclePaintProps, filter: any) => { + + map.addLayer({ + id: id, + source, + type: 'circle', + paint, + filter, + }); + }; + + public addLineLayer = (map: maplibregl.Map, id: string, source: string, paint: LinePaintProps, filter: any, isDashed?: boolean) => { + + const layer: any = { + id: id, + type: 'line', + source, + layout: { + 'line-join': 'round', + 'line-cap': 'round', + }, + paint, + filter, + }; + + if (isDashed) { + layer.paint['line-dasharray'] = [2, 1]; + } + + map.addLayer(layer); + }; + + public addBaseLayers = (map: maplibregl.Map) => { + + this.addCommonLayers(map); + }; + + public addIconLayers = (map: maplibregl.Map) => { + + this.iconLayersActive = true; + this.addCommonLayers(map); + this.createIconLayers(map); + }; + + public removeBaseLayers = (map: maplibregl.Map) => { + + map.removeLayer('points'); + map.removeLayer('lines'); + map.removeLayer('services'); + map.removeLayer('selectedPoints'); + map.removeLayer('selectedLine'); + map.removeLayer('selectedServices'); + }; + + public showIconLayers = (map: maplibregl.Map, show: boolean) => { + + const zoom = map.getZoom(); + if (show) { + if (zoom > 11) { + if (!this.iconLayersActive) { + this.iconLayersActive = true; + if (map.getLayer('points') !== undefined && map.getLayer('point-lamps') === undefined) { + this.createIconLayers(map); + } + } + } else { + if (this.iconLayersActive) + this.swapLayersBack(map); + } + } else { + if (this.iconLayersActive) + this.swapLayersBack(map); + } + }; + + public swapLayersBack = (map: maplibregl.Map) => { + this.iconLayersActive = false; + this.removeIconLayers(map); + + if (map.getLayer('selectedPoints')) { + map.setFilter('selectedPoints', null); + } + + if (map.getLayer('points')) { + map.setFilter('points', null); + } + }; + + public changeMapOpacity = (map: maplibregl.Map, newValue: number) => { + const newOpacity = newValue / 100; + if (map) { + const tiles = map.getStyle().layers?.filter(el => el.id.includes('tiles')); + tiles?.forEach(layer => { + if (layer.type === 'symbol') { + map.setPaintProperty(layer.id, 'icon-opacity', newOpacity); + map.setPaintProperty(layer.id, 'text-opacity', newOpacity); + } else { + map.setPaintProperty(layer.id, `${layer.type}-opacity`, newOpacity); + } + }); + } + }; + + public changeTheme = (map: maplibregl.Map, themeName: string) => { + this.selectedTheme = themeName; + const theme = this.pickTheme(); + if (theme && map.loaded()) { + map.setPaintProperty('points', 'circle-color', theme.site); + map.setPaintProperty('selectedPoints', 'circle-color', theme.selectedSite); + map.setPaintProperty('microwave-lines', 'line-color', theme.microwaveLink); + map.setPaintProperty('fibre-lines', 'line-color', theme.fiberLink); + + } + }; + + public addLayersToMap(pMap: maplibregl.Map, furtherLayerNames: string[]) { + const theme = this.pickTheme(); + const furtherLayersTheme = theme?.furtherLayers || {}; + furtherLayerNames.forEach(el => { + const layerTheme = furtherLayersTheme[el] ; + const sitePaint: CirclePaintProps = { + 'circle-color': '#8F00FF', + 'circle-radius': 6, + ...layerTheme?.site ?? {}, + }; + + const linksPaint: LinePaintProps = { + 'line-color': '#8F00FF', + 'line-width': 2, + ...layerTheme?.link ?? {}, + }; + + const servicesPaint: LinePaintProps = { + 'line-color': '#8F00FF', + 'line-width': 2, + ...layerTheme?.service ?? {}, + }; + + this.addCircleLayer(pMap, el, 'points', sitePaint, ['in', el, ['get', 'labels']]); + this.addLineLayer(pMap, `${el}-links`, 'lines', linksPaint, ['in', el, ['get', 'labels']]); + this.addLineLayer(pMap, `${el}-services`, 'services', servicesPaint, ['in', el, ['get', 'labels']]); + }); + } + + private createIconLayers = (map: maplibregl.Map) => { + //set filter to it! + map.setFilter('points'); + map.setFilter('selectedPoints'); + }; + + private addCommonLayers = (map: maplibregl.Map, themeSettings?: ThemeElement) => { + + const theme = !themeSettings ? this.pickTheme() : themeSettings; + this.addLineLayer(map, 'microwave-lines', 'lines', { 'line-color': theme.microwaveLink, 'line-width': 2 }, ['all', ['==', 'layer', 'link'], ['==', 'subType', 'microwave']]); + this.addLineLayer(map, 'fibre-lines', 'lines', { 'line-color': theme.fiberLink, 'line-width': 2 }, ['all', ['==', 'layer', 'link'], ['==', 'subType', 'fibre']]); + + this.addLineLayer(map, 'services', 'services', { 'line-color': '#FF0000', 'line-width': 2 }, ['all', ['==', 'layer', 'service'], ['==', 'isBackup', false ]]); + this.addLineLayer(map, 'backup-services', 'services', { 'line-color': '#FF0000', 'line-width': 2 }, ['all', ['==', 'layer', 'service'], ['==', 'isBackup', true ]], true); + + this.addLineLayer(map, 'selectedLineMicrowave', 'selectedLine', { 'line-color': theme.microwaveLink, 'line-width': 4 }, ['all', ['==', 'layer', 'link'], ['==', 'subType', 'microwave']]); + this.addLineLayer(map, 'selectedLineFibre', 'selectedLine', { 'line-color': theme.fiberLink, 'line-width': 4 }, ['all', ['==', 'layer', 'link'], ['==', 'subType', 'fibre']]); + this.addLineLayer(map, 'selectedServices', 'selectedLine', { 'line-color': '#FF0000', 'line-width': 4 }, ['==', 'layer', 'service']); + + // this.addCircleLayer(map, 'points', 'points', { 'circle-color': theme.site, 'circle-radius': [ 'match', ['get', 'xPonder'], 'true', 9, 7 ], 'circle-stroke-width': 1, 'circle-stroke-color':'#fff' }, ['==', 'layer', 'site']); + this.addCircleLayer(map, 'points', 'points', { 'circle-color': theme.site, 'circle-radius': 7, 'circle-stroke-width': 1, 'circle-stroke-color':'#fff' }, ['==', 'layer', 'site']); + this.addCircleLayer(map, 'selectedPoints', 'selectedPoints', { 'circle-color': theme.selectedSite, 'circle-radius': 9, 'circle-stroke-width': 1, 'circle-stroke-color': '#fff' }, ['==', 'layer', 'size']); + }; + + /** + * Pick the correct theme based on user selection + */ + private pickTheme = () => { + if (this.selectedTheme !== null) { + const result = this.availableThemes.find(el => el.key === this.selectedTheme); + if (result) + return result; + } + + return this.availableThemes[0]; + }; + + private removeIconLayers = (map: maplibregl.Map) => { + + map.removeLayer('point-building'); + map.removeLayer('point-lamps'); + map.removeLayer('point-data-center'); + map.removeLayer('point-factory'); + map.removeLayer('select-point-data-center'); + map.removeLayer('select-point-buildings'); + map.removeLayer('select-point-lamps'); + map.removeLayer('select-point-factory'); + }; + +} + +const mapLayerService = new MapLayerService(); +export default mapLayerService; + diff --git a/features/sdnr/odlux/odlux/apps/networkMapApp/src/utils/mapUtils.ts b/features/sdnr/odlux/odlux/apps/networkMapApp/src/utils/mapUtils.ts new file mode 100644 index 0000000..f590fbe --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/networkMapApp/src/utils/mapUtils.ts @@ -0,0 +1,148 @@ +/** + * ============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 type { CircleLayerSpecification, LineLayerSpecification, Map, MapGeoJSONFeature } from 'maplibre-gl'; + +export type CirclePaintProps = CircleLayerSpecification['paint']; +export type CircleLayoutProps = CircleLayerSpecification['layout']; + +export type LinePaintProps = LineLayerSpecification['paint']; +export type LineLayoutProps = LineLayerSpecification['layout']; + +import { BoundingBox } from '../model/boundingBox'; + +const EARTHRADIUSM = 6378137; + +const degrees_to_radians = (degrees: number) => { + return degrees * (Math.PI / 180); +}; + +const checkLatitude = (lat: number) => { + + if (lat > 90) + return 90; + else if (lat < -90) + return -90; + else + return lat; + +}; + +const checkLongitude = (lon: number) => { + if (lon > 180) + return 180; + else if (lon < -180) + return -180; + else + return lon; +}; + +const radians_to_degrees = (radians: number) => { + var pi = Math.PI; + return radians * (180 / pi); +}; + +export const addDistance = (south: number, west: number, north: number, east: number, distanceKm: number): BoundingBox => { + + const distanceInM = distanceKm * 1000; + + const dLat = distanceInM / EARTHRADIUSM; + const dLon = distanceInM / (EARTHRADIUSM * Math.cos(Math.PI * (north + south) / 360)); + + const latOffset = dLat * 180 / Math.PI; + const lonOffset = dLon * 180 / Math.PI; + + const newEast = checkLongitude(east + lonOffset); + const newWest = checkLongitude(west - lonOffset); + const newNorth = checkLatitude(north + latOffset); + const newSouth = checkLatitude(south - latOffset); + + return BoundingBox.createFromNumbers(newWest, newSouth, newEast, newNorth); + +}; + +export const increaseBoundingBox = (map: Map) => { + + const bbox = map.getBounds(); + + const distance = map.getCenter().distanceTo(bbox.getNorthEast()); // radius of visible area (center -> corner) (in meters) + + //calculate new boundingBox + const increasedBoundingBox = addDistance(bbox.getSouth(), bbox.getWest(), bbox.getNorth(), bbox.getEast(), (distance / 1000) / 2); + return increasedBoundingBox; +}; + + +//taken from https://www.movable-type.co.uk/scripts/latlong.html +export const calculateMidPoint = (latStart: number, lonStart: number, latEnd: number, lonEnd: number) => { + + const dLon = degrees_to_radians(lonEnd - lonStart); + + //convert to radians + const lat1 = degrees_to_radians(latStart); + const lat2 = degrees_to_radians(latEnd); + const lon1 = degrees_to_radians(lonStart); + + const Bx = Math.cos(lat2) * Math.cos(dLon); + const By = Math.cos(lat2) * Math.sin(dLon); + const lat3 = Math.atan2(Math.sin(lat1) + Math.sin(lat2), Math.sqrt((Math.cos(lat1) + Bx) * (Math.cos(lat1) + Bx) + By * By)); + const lon3 = lon1 + Math.atan2(By, Math.cos(lat1) + Bx); + + const result = { lat: radians_to_degrees(lat3), lon: radians_to_degrees(lon3) }; + + return result; +}; + + +export const LatLonToDMS = (value: number, isLon: boolean = false) => { + const absoluteValue = Math.abs(value); + const d = Math.floor(absoluteValue); + const m = Math.floor((absoluteValue - d) * 60); + const s = (absoluteValue - d - m / 60) * 3600; + const dms = `${d}° ${m}' ${s.toFixed(2)}"`; + + const sign = Math.sign(value); + + if (isLon) { + return (sign === -1 || sign === -0) ? dms + ' W' : dms + ' E'; + } else { + return (sign === -1 || sign === -0) ? dms + ' S' : dms + ' N'; + } +}; + +// Because features come from tiled vector data, feature geometries may be split +// or duplicated across tile boundaries and, as a result, features may appear +// multiple times in query results. + +//taken from https://docs.mapbox.com/mapbox-gl-js/example/filter-features-within-map-view/ + +export const getUniqueFeatures = (array: MapGeoJSONFeature[], comparatorProperty: string) => { + var existingFeatureKeys: any = {}; + + var uniqueFeatures = array.filter(function (el) { + if (existingFeatureKeys[el.properties![comparatorProperty]]) { + return false; + } else { + existingFeatureKeys[el.properties![comparatorProperty]] = true; + return true; + } + }); + + return uniqueFeatures; +}; + diff --git a/features/sdnr/odlux/odlux/apps/networkMapApp/tsconfig.json b/features/sdnr/odlux/odlux/apps/networkMapApp/tsconfig.json new file mode 100644 index 0000000..18956db --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/networkMapApp/tsconfig.json @@ -0,0 +1,6 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": "./dist", + }, +} diff --git a/features/sdnr/odlux/odlux/apps/networkMapApp/webpack.config.js b/features/sdnr/odlux/odlux/apps/networkMapApp/webpack.config.js new file mode 100644 index 0000000..1f4db83 --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/networkMapApp/webpack.config.js @@ -0,0 +1,146 @@ +/** + * 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 __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: { + networkMapApp: ["./pluginTransport.tsx"] + }, + + devtool: env === "release" ? false : "source-map", + + resolve: { + extensions: [".ts", ".tsx", ".js", ".jsx"] + }, + + output: { + path: distPath, + filename: "[name].js", + library: "networkMapApp", + 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: /\.(png|gif|jpg|svg)$/, + use: [{ + loader: 'url-loader', + options: { + limit: 10000, + name: './icons/[hash].[ext]' + } + }] + }, + { + test: /\.css$/i, + use: ["style-loader", "css-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: proxyConf, + } + }]; +} diff --git a/features/sdnr/odlux/odlux/apps/performanceHistoryApp/.babelrc b/features/sdnr/odlux/odlux/apps/performanceHistoryApp/.babelrc new file mode 100644 index 0000000..3d8cd12 --- /dev/null +++ b/features/sdnr/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/features/sdnr/odlux/odlux/apps/performanceHistoryApp/package.json b/features/sdnr/odlux/odlux/apps/performanceHistoryApp/package.json new file mode 100644 index 0000000..9a08612 --- /dev/null +++ b/features/sdnr/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/features/sdnr/odlux/odlux/apps/performanceHistoryApp/pom.xml b/features/sdnr/odlux/odlux/apps/performanceHistoryApp/pom.xml new file mode 100644 index 0000000..9952ae6 --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/performanceHistoryApp/pom.xml @@ -0,0 +1,105 @@ + + + + + 4.0.0 + + org.o-ran-sc.oam-controller.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/features/sdnr/odlux/odlux/apps/performanceHistoryApp/src/actions/deviceListActions.ts b/features/sdnr/odlux/odlux/apps/performanceHistoryApp/src/actions/deviceListActions.ts new file mode 100644 index 0000000..fbbf2c5 --- /dev/null +++ b/features/sdnr/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/features/sdnr/odlux/odlux/apps/performanceHistoryApp/src/actions/ltpAction.ts b/features/sdnr/odlux/odlux/apps/performanceHistoryApp/src/actions/ltpAction.ts new file mode 100644 index 0000000..1c333ab --- /dev/null +++ b/features/sdnr/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/features/sdnr/odlux/odlux/apps/performanceHistoryApp/src/actions/panelChangeActions.ts b/features/sdnr/odlux/odlux/apps/performanceHistoryApp/src/actions/panelChangeActions.ts new file mode 100644 index 0000000..8b77cb3 --- /dev/null +++ b/features/sdnr/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/features/sdnr/odlux/odlux/apps/performanceHistoryApp/src/actions/reloadAction.ts b/features/sdnr/odlux/odlux/apps/performanceHistoryApp/src/actions/reloadAction.ts new file mode 100644 index 0000000..6bd54ce --- /dev/null +++ b/features/sdnr/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/features/sdnr/odlux/odlux/apps/performanceHistoryApp/src/actions/timeChangeAction.ts b/features/sdnr/odlux/odlux/apps/performanceHistoryApp/src/actions/timeChangeAction.ts new file mode 100644 index 0000000..13214b2 --- /dev/null +++ b/features/sdnr/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/features/sdnr/odlux/odlux/apps/performanceHistoryApp/src/actions/toggleActions.ts b/features/sdnr/odlux/odlux/apps/performanceHistoryApp/src/actions/toggleActions.ts new file mode 100644 index 0000000..7921ea5 --- /dev/null +++ b/features/sdnr/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/features/sdnr/odlux/odlux/apps/performanceHistoryApp/src/assets/icons/performanceHistoryAppIcon.svg b/features/sdnr/odlux/odlux/apps/performanceHistoryApp/src/assets/icons/performanceHistoryAppIcon.svg new file mode 100644 index 0000000..982f1ee --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/performanceHistoryApp/src/assets/icons/performanceHistoryAppIcon.svg @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/features/sdnr/odlux/odlux/apps/performanceHistoryApp/src/components/adaptiveModulation.tsx b/features/sdnr/odlux/odlux/apps/performanceHistoryApp/src/components/adaptiveModulation.tsx new file mode 100644 index 0000000..5dac0bc --- /dev/null +++ b/features/sdnr/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/features/sdnr/odlux/odlux/apps/performanceHistoryApp/src/components/chartFilter.tsx b/features/sdnr/odlux/odlux/apps/performanceHistoryApp/src/components/chartFilter.tsx new file mode 100644 index 0000000..021f74a --- /dev/null +++ b/features/sdnr/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/features/sdnr/odlux/odlux/apps/performanceHistoryApp/src/components/crossPolarDiscrimination.tsx b/features/sdnr/odlux/odlux/apps/performanceHistoryApp/src/components/crossPolarDiscrimination.tsx new file mode 100644 index 0000000..5f925a9 --- /dev/null +++ b/features/sdnr/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/features/sdnr/odlux/odlux/apps/performanceHistoryApp/src/components/ltpSelection.tsx b/features/sdnr/odlux/odlux/apps/performanceHistoryApp/src/components/ltpSelection.tsx new file mode 100644 index 0000000..bd6333b --- /dev/null +++ b/features/sdnr/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/features/sdnr/odlux/odlux/apps/performanceHistoryApp/src/components/performanceData.tsx b/features/sdnr/odlux/odlux/apps/performanceHistoryApp/src/components/performanceData.tsx new file mode 100644 index 0000000..fb608aa --- /dev/null +++ b/features/sdnr/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/features/sdnr/odlux/odlux/apps/performanceHistoryApp/src/components/receiveLevel.tsx b/features/sdnr/odlux/odlux/apps/performanceHistoryApp/src/components/receiveLevel.tsx new file mode 100644 index 0000000..125cc6e --- /dev/null +++ b/features/sdnr/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/features/sdnr/odlux/odlux/apps/performanceHistoryApp/src/components/signalToInterference.tsx b/features/sdnr/odlux/odlux/apps/performanceHistoryApp/src/components/signalToInterference.tsx new file mode 100644 index 0000000..85241fb --- /dev/null +++ b/features/sdnr/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/features/sdnr/odlux/odlux/apps/performanceHistoryApp/src/components/temperature.tsx b/features/sdnr/odlux/odlux/apps/performanceHistoryApp/src/components/temperature.tsx new file mode 100644 index 0000000..d4b8233 --- /dev/null +++ b/features/sdnr/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/features/sdnr/odlux/odlux/apps/performanceHistoryApp/src/components/toggleContainer.tsx b/features/sdnr/odlux/odlux/apps/performanceHistoryApp/src/components/toggleContainer.tsx new file mode 100644 index 0000000..e883aef --- /dev/null +++ b/features/sdnr/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/features/sdnr/odlux/odlux/apps/performanceHistoryApp/src/components/transmissionPower.tsx b/features/sdnr/odlux/odlux/apps/performanceHistoryApp/src/components/transmissionPower.tsx new file mode 100644 index 0000000..db9a7c0 --- /dev/null +++ b/features/sdnr/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/features/sdnr/odlux/odlux/apps/performanceHistoryApp/src/handlers/adaptiveModulationHandler.ts b/features/sdnr/odlux/odlux/apps/performanceHistoryApp/src/handlers/adaptiveModulationHandler.ts new file mode 100644 index 0000000..9baf545 --- /dev/null +++ b/features/sdnr/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/features/sdnr/odlux/odlux/apps/performanceHistoryApp/src/handlers/availableLtpsActionHandler.ts b/features/sdnr/odlux/odlux/apps/performanceHistoryApp/src/handlers/availableLtpsActionHandler.ts new file mode 100644 index 0000000..f943ef4 --- /dev/null +++ b/features/sdnr/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/features/sdnr/odlux/odlux/apps/performanceHistoryApp/src/handlers/crossPolarDiscriminationHandler.ts b/features/sdnr/odlux/odlux/apps/performanceHistoryApp/src/handlers/crossPolarDiscriminationHandler.ts new file mode 100644 index 0000000..96cabfa --- /dev/null +++ b/features/sdnr/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/features/sdnr/odlux/odlux/apps/performanceHistoryApp/src/handlers/deviceListActionHandler.ts b/features/sdnr/odlux/odlux/apps/performanceHistoryApp/src/handlers/deviceListActionHandler.ts new file mode 100644 index 0000000..11e380a --- /dev/null +++ b/features/sdnr/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/features/sdnr/odlux/odlux/apps/performanceHistoryApp/src/handlers/performanceDataHandler.ts b/features/sdnr/odlux/odlux/apps/performanceHistoryApp/src/handlers/performanceDataHandler.ts new file mode 100644 index 0000000..664c7cd --- /dev/null +++ b/features/sdnr/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/features/sdnr/odlux/odlux/apps/performanceHistoryApp/src/handlers/performanceHistoryRootHandler.ts b/features/sdnr/odlux/odlux/apps/performanceHistoryApp/src/handlers/performanceHistoryRootHandler.ts new file mode 100644 index 0000000..c0cee46 --- /dev/null +++ b/features/sdnr/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/features/sdnr/odlux/odlux/apps/performanceHistoryApp/src/handlers/receiveLevelHandler.ts b/features/sdnr/odlux/odlux/apps/performanceHistoryApp/src/handlers/receiveLevelHandler.ts new file mode 100644 index 0000000..78626eb --- /dev/null +++ b/features/sdnr/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/features/sdnr/odlux/odlux/apps/performanceHistoryApp/src/handlers/signalToInterferenceHandler.ts b/features/sdnr/odlux/odlux/apps/performanceHistoryApp/src/handlers/signalToInterferenceHandler.ts new file mode 100644 index 0000000..ab6efab --- /dev/null +++ b/features/sdnr/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/features/sdnr/odlux/odlux/apps/performanceHistoryApp/src/handlers/temperatureHandler.ts b/features/sdnr/odlux/odlux/apps/performanceHistoryApp/src/handlers/temperatureHandler.ts new file mode 100644 index 0000000..02bf69b --- /dev/null +++ b/features/sdnr/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/features/sdnr/odlux/odlux/apps/performanceHistoryApp/src/handlers/transmissionPowerHandler.ts b/features/sdnr/odlux/odlux/apps/performanceHistoryApp/src/handlers/transmissionPowerHandler.ts new file mode 100644 index 0000000..9cf70dc --- /dev/null +++ b/features/sdnr/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/features/sdnr/odlux/odlux/apps/performanceHistoryApp/src/index.html b/features/sdnr/odlux/odlux/apps/performanceHistoryApp/src/index.html new file mode 100644 index 0000000..8cb775b --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/performanceHistoryApp/src/index.html @@ -0,0 +1,26 @@ + + + + + + + + + PM History Application + + + +
+ + + + + + \ No newline at end of file diff --git a/features/sdnr/odlux/odlux/apps/performanceHistoryApp/src/models/adaptiveModulationDataType.ts b/features/sdnr/odlux/odlux/apps/performanceHistoryApp/src/models/adaptiveModulationDataType.ts new file mode 100644 index 0000000..adb0bcd --- /dev/null +++ b/features/sdnr/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/features/sdnr/odlux/odlux/apps/performanceHistoryApp/src/models/availableLtps.ts b/features/sdnr/odlux/odlux/apps/performanceHistoryApp/src/models/availableLtps.ts new file mode 100644 index 0000000..6006157 --- /dev/null +++ b/features/sdnr/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/features/sdnr/odlux/odlux/apps/performanceHistoryApp/src/models/chartTypes.ts b/features/sdnr/odlux/odlux/apps/performanceHistoryApp/src/models/chartTypes.ts new file mode 100644 index 0000000..969c0b3 --- /dev/null +++ b/features/sdnr/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/features/sdnr/odlux/odlux/apps/performanceHistoryApp/src/models/crossPolarDiscriminationDataType.ts b/features/sdnr/odlux/odlux/apps/performanceHistoryApp/src/models/crossPolarDiscriminationDataType.ts new file mode 100644 index 0000000..749624b --- /dev/null +++ b/features/sdnr/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/features/sdnr/odlux/odlux/apps/performanceHistoryApp/src/models/deviceListType.ts b/features/sdnr/odlux/odlux/apps/performanceHistoryApp/src/models/deviceListType.ts new file mode 100644 index 0000000..fbe3141 --- /dev/null +++ b/features/sdnr/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/features/sdnr/odlux/odlux/apps/performanceHistoryApp/src/models/panelId.ts b/features/sdnr/odlux/odlux/apps/performanceHistoryApp/src/models/panelId.ts new file mode 100644 index 0000000..08bf7f8 --- /dev/null +++ b/features/sdnr/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/features/sdnr/odlux/odlux/apps/performanceHistoryApp/src/models/performanceDataType.ts b/features/sdnr/odlux/odlux/apps/performanceHistoryApp/src/models/performanceDataType.ts new file mode 100644 index 0000000..f71e09d --- /dev/null +++ b/features/sdnr/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/features/sdnr/odlux/odlux/apps/performanceHistoryApp/src/models/receiveLevelDataType.ts b/features/sdnr/odlux/odlux/apps/performanceHistoryApp/src/models/receiveLevelDataType.ts new file mode 100644 index 0000000..2748a3d --- /dev/null +++ b/features/sdnr/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/features/sdnr/odlux/odlux/apps/performanceHistoryApp/src/models/signalToInteferenceDataType.ts b/features/sdnr/odlux/odlux/apps/performanceHistoryApp/src/models/signalToInteferenceDataType.ts new file mode 100644 index 0000000..5c675fe --- /dev/null +++ b/features/sdnr/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/features/sdnr/odlux/odlux/apps/performanceHistoryApp/src/models/temperatureDataType.ts b/features/sdnr/odlux/odlux/apps/performanceHistoryApp/src/models/temperatureDataType.ts new file mode 100644 index 0000000..5798d5c --- /dev/null +++ b/features/sdnr/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/features/sdnr/odlux/odlux/apps/performanceHistoryApp/src/models/toggleDataType.ts b/features/sdnr/odlux/odlux/apps/performanceHistoryApp/src/models/toggleDataType.ts new file mode 100644 index 0000000..0e71c94 --- /dev/null +++ b/features/sdnr/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/features/sdnr/odlux/odlux/apps/performanceHistoryApp/src/models/topologyNetconf.ts b/features/sdnr/odlux/odlux/apps/performanceHistoryApp/src/models/topologyNetconf.ts new file mode 100644 index 0000000..f52af97 --- /dev/null +++ b/features/sdnr/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/features/sdnr/odlux/odlux/apps/performanceHistoryApp/src/models/transmissionPowerDataType.ts b/features/sdnr/odlux/odlux/apps/performanceHistoryApp/src/models/transmissionPowerDataType.ts new file mode 100644 index 0000000..62c00bf --- /dev/null +++ b/features/sdnr/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/features/sdnr/odlux/odlux/apps/performanceHistoryApp/src/pluginPerformance.tsx b/features/sdnr/odlux/odlux/apps/performanceHistoryApp/src/pluginPerformance.tsx new file mode 100644 index 0000000..ef939fd --- /dev/null +++ b/features/sdnr/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/features/sdnr/odlux/odlux/apps/performanceHistoryApp/src/services/performanceHistoryService.ts b/features/sdnr/odlux/odlux/apps/performanceHistoryApp/src/services/performanceHistoryService.ts new file mode 100644 index 0000000..ef013f1 --- /dev/null +++ b/features/sdnr/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/features/sdnr/odlux/odlux/apps/performanceHistoryApp/src/utils/chartUtils.tsx b/features/sdnr/odlux/odlux/apps/performanceHistoryApp/src/utils/chartUtils.tsx new file mode 100644 index 0000000..38abb3e --- /dev/null +++ b/features/sdnr/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/features/sdnr/odlux/odlux/apps/performanceHistoryApp/src/utils/tableUtils.ts b/features/sdnr/odlux/odlux/apps/performanceHistoryApp/src/utils/tableUtils.ts new file mode 100644 index 0000000..37fe962 --- /dev/null +++ b/features/sdnr/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/features/sdnr/odlux/odlux/apps/performanceHistoryApp/src/views/performanceHistoryApplication.tsx b/features/sdnr/odlux/odlux/apps/performanceHistoryApp/src/views/performanceHistoryApplication.tsx new file mode 100644 index 0000000..a4b9686 --- /dev/null +++ b/features/sdnr/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/features/sdnr/odlux/odlux/apps/performanceHistoryApp/tsconfig.json b/features/sdnr/odlux/odlux/apps/performanceHistoryApp/tsconfig.json new file mode 100644 index 0000000..ca65092 --- /dev/null +++ b/features/sdnr/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/features/sdnr/odlux/odlux/apps/performanceHistoryApp/webpack.config.js b/features/sdnr/odlux/odlux/apps/performanceHistoryApp/webpack.config.js new file mode 100644 index 0000000..2f25d0d --- /dev/null +++ b/features/sdnr/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 + } + } + } + }]; +} diff --git a/features/sdnr/odlux/odlux/apps/siteManagerApp/.babelrc b/features/sdnr/odlux/odlux/apps/siteManagerApp/.babelrc new file mode 100644 index 0000000..3d8cd12 --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/siteManagerApp/.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/features/sdnr/odlux/odlux/apps/siteManagerApp/package.json b/features/sdnr/odlux/odlux/apps/siteManagerApp/package.json new file mode 100644 index 0000000..b3f8e6a --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/siteManagerApp/package.json @@ -0,0 +1,48 @@ +{ + "name": "@odlux/sitemanager-app", + "version": "0.1.0", + "description": "A react based modular UI to display network siteManager 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", + "@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/lab": "^5.0.0-alpha.58", + "@mui/material": "^5.2.2", + "@mui/styles": "^5.2.2", + "@odlux/framework": "*", + "clsx": "^1.2.1" + }, + "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/features/sdnr/odlux/odlux/apps/siteManagerApp/pom.xml b/features/sdnr/odlux/odlux/apps/siteManagerApp/pom.xml new file mode 100644 index 0000000..812823e --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/siteManagerApp/pom.xml @@ -0,0 +1,106 @@ + + + + + 4.0.0 + + org.o-ran-sc.oam-controller.features.sdnr.odlux + sdnr-odlux-app-siteManagerApp + 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/features/sdnr/odlux/odlux/apps/siteManagerApp/src/actions/detailsAction.ts b/features/sdnr/odlux/odlux/apps/siteManagerApp/src/actions/detailsAction.ts new file mode 100644 index 0000000..dbc7295 --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/siteManagerApp/src/actions/detailsAction.ts @@ -0,0 +1,126 @@ +/** + * ============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 { Dispatch } from '../../../../framework/src/flux/store'; +import { requestRest } from '../../../../framework/src/services/restService'; +import { IApplicationStoreState } from '../../../../framework/src/store/applicationStore'; + +import { SITEDOC_URL } from '../config'; +import { HistoryEntry } from '../models/historyEntry'; +import { link } from '../models/link'; +import { NetworkElementConnection } from '../models/networkElementConnection'; +import { Device, Site } from '../models/site'; +import { Service } from '../models/topologyTypes'; +import dataService from '../services/dataService'; + + +export class SelectElementAction extends Action { + constructor(public data: link | Site | Service) { + super(); + } +} + +export class ClearDetailsAction extends Action { } + +export class AddToHistoryAction extends Action { + constructor(public entry: HistoryEntry) { + super(); + } +} + +export class ClearHistoryAction extends Action { } + +export class IsBusyCheckingDeviceListAction extends Action { + constructor(public isBusy: boolean) { + super(); + } +} + +export class FinishedLoadingDeviceListAction extends Action { + constructor(public devices: Device[]) { + super(); + } +} + +export class ClearLoadedDevicesAction extends Action { } + +export class InitializeLoadedDevicesAction extends Action { + constructor(public devices: Device[]) { + super(); + } +} + +export class IsSitedocReachableAction extends Action { + constructor(public isReachable: boolean) { + super(); + } +} + +export const UpdateDetailsView = (nodeId: string) => (dispatcher: Dispatch, getState: () => IApplicationStoreState) => { + const { siteManager: { details: { checkedDevices } } } = getState(); + if (checkedDevices !== null) { + const index = checkedDevices.findIndex(item => item.name === nodeId); + if (index !== -1) + requestRest('/rests/operational/network-topology:network-topology/topology/topology-netconf/node/' + nodeId, { method: 'GET' }) + .then(result => { + if (result !== null) { + checkedDevices[index].status = result.node[0]['netconf-node-topology:connection-status']; + } else { + checkedDevices[index].status = 'Not connected'; + } + dispatcher(new FinishedLoadingDeviceListAction(checkedDevices)); + }); + } +}; + +export const CheckDeviceList = (list: Device[]) => async (dispatcher: Dispatch, getState: () => IApplicationStoreState) => { + const { siteManager: { details: { isBusyCheckingDeviceList } } } = getState(); + if (isBusyCheckingDeviceList) return; + dispatcher(new IsBusyCheckingDeviceListAction(true)); + const ids: string[] = list + .filter(el => el.name && el.name.length > 0) + .map((device) => { + return device.name; + }); + + const resultData = await dataService.getAdditionalInfoOnDevices(ids); + if (resultData) { + resultData.forEach((data: NetworkElementConnection) => { + const index = list.findIndex(el => { return el.name === data.id; }); + if (index !== -1) { + list[index].status = data.status; + list[index].type = data['device-type']; + } + }); + } + dispatcher(new FinishedLoadingDeviceListAction(list)); + dispatcher(new IsBusyCheckingDeviceListAction(false)); +}; + +export const checkSiteDockReachability = () => async (dispatcher: Dispatch) => { + console.log('searching for sitedoc server...'); + requestRest(SITEDOC_URL + '/app/versioninfo').then(response => { + console.log(response); + if (response) { + dispatcher(new IsSitedocReachableAction(true)); + } else { + dispatcher(new IsSitedocReachableAction(false)); + } + }); +}; \ No newline at end of file diff --git a/features/sdnr/odlux/odlux/apps/siteManagerApp/src/actions/panelActions.ts b/features/sdnr/odlux/odlux/apps/siteManagerApp/src/actions/panelActions.ts new file mode 100644 index 0000000..2e86084 --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/siteManagerApp/src/actions/panelActions.ts @@ -0,0 +1,32 @@ +/** + * ============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/features/sdnr/odlux/odlux/apps/siteManagerApp/src/actions/siteManagerSiteSearchAction.ts b/features/sdnr/odlux/odlux/apps/siteManagerApp/src/actions/siteManagerSiteSearchAction.ts new file mode 100644 index 0000000..74ed0ee --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/siteManagerApp/src/actions/siteManagerSiteSearchAction.ts @@ -0,0 +1,56 @@ +/** + * ============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 { Dispatch } from '../../../../framework/src/flux/store'; + +import { SearchSiteIdResult } from '../models/siteManager'; +import siteManagerService from '../services/siteManagerService'; + +export class SearchValueAction extends Action { + constructor(public siteId: string, public categoryName?: string) { + super(); + } +} + +export class SetBusyAction extends Action { + constructor(public busy: boolean) { + super(); + } +} + + +/** + * Get searchTreeBySiteIdOrName + */ +export class BaseAction extends Action { } + +export class LoadTreeSiteSearchBySiteIdOrNameAction extends BaseAction { } + +export class AllTreeSiteSearchBySiteIdOrNameLoadedAction extends BaseAction { + constructor(public searchResult: SearchSiteIdResult) { + super(); + } +} + +export const loadTreeSiteSearchBySiteIdOrNameAsync = (searchValue: string) => async (dispatch: Dispatch) => { + dispatch(new LoadTreeSiteSearchBySiteIdOrNameAction()); + const searchResult: SearchSiteIdResult = (await siteManagerService.getSearchSiteIDTrail(searchValue)); + dispatch(new AllTreeSiteSearchBySiteIdOrNameLoadedAction(searchResult)); + return searchResult; +}; \ No newline at end of file diff --git a/features/sdnr/odlux/odlux/apps/siteManagerApp/src/actions/siteManagerTreeActions.ts b/features/sdnr/odlux/odlux/apps/siteManagerApp/src/actions/siteManagerTreeActions.ts new file mode 100644 index 0000000..68aea35 --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/siteManagerApp/src/actions/siteManagerTreeActions.ts @@ -0,0 +1,214 @@ +/** + * ============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 { Dispatch } from '../../../../framework/src/flux/store'; + +import { Bands, SiteConfigurationFreqPlan, SiteManagerAreas, SiteManagerCategories, SiteManagerCategoryItems, Sites, SitesListResult } from '../models/siteManager'; +import siteManagerService from '../services/siteManagerService'; + +export class BaseAction extends Action { } + +export class SetBusyAction extends Action { + constructor(public busy: boolean) { + super(); + } +} + +/** + * Get countries + */ +export class LoadAllCountriesAction extends BaseAction { } + +export class AllCountriesLoadedAction extends BaseAction { + constructor(public countriesList: SiteManagerAreas[] | null) { + super(); + + } +} + +export const loadAllCountriesAsync = () => async (dispatch: Dispatch) => { + dispatch(new LoadAllCountriesAction()); + const countries: SiteManagerAreas[] = (await siteManagerService.getCountries()) || []; + dispatch(new AllCountriesLoadedAction(countries)); + return countries; +}; + +/** + * Get areaByAreaId + */ +export class LoadAllAreasByAreaIdAction extends BaseAction { } + +export class AllAreasByAreaIdLoadedAction extends BaseAction { + constructor(public areaList: SiteManagerAreas[] | null) { + super(); + + } +} + +export const loadAllAreasByAreaIdAsync = (areaId: string) => async (dispatch: Dispatch) => { + dispatch(new LoadAllAreasByAreaIdAction()); + const areas: SiteManagerAreas[] = (await siteManagerService.getAreasByAreaId(areaId)) || []; + dispatch(new AllAreasByAreaIdLoadedAction(areas)); + return areas; +}; + +/** + * Get sitesByAreaId + */ +export class LoadAllSitesByAreaIdAction extends BaseAction { } + +export class AllSitesByAreaIdLoadedAction extends BaseAction { + constructor(public sitesList: SitesListResult) { + super(); + + } +} + +export const loadAllSitesByAreaIdAsync = (areaId: string) => async (dispatch: Dispatch) => { + dispatch(new LoadAllSitesByAreaIdAction()); + const sites: SitesListResult = (await siteManagerService.getSitesByAreaId(areaId)); + dispatch(new AllSitesByAreaIdLoadedAction(sites)); + return sites; +}; + + +/** + * Get CategoriesBySiteId + */ +export class LoadAllCategoriesBySiteIdAction extends BaseAction { } + +export class AllCategoriesBySiteIdALoadedAction extends BaseAction { + constructor(public categoryList: SiteManagerCategories[] | null) { + super(); + + } +} + +export const loadAllCategoriesBySiteIdAsync = (areaId: string) => async (dispatch: Dispatch) => { + dispatch(new LoadAllCategoriesBySiteIdAction()); + const categories: SiteManagerCategories[] = (await siteManagerService.getCategoriesBySiteId(areaId)) || []; + dispatch(new AllCategoriesBySiteIdALoadedAction(categories)); + return categories; +}; + +/** + * Get SiteDetailsBySiteId + */ +export class LoadAllSiteBySiteIdAction extends BaseAction { } + +export class AllSiteBySiteIdALoadedAction extends BaseAction { + constructor(public siteDetails: Sites | null) { + super(); + } +} + +export const loadAllSiteBySiteIdAsync = (siteId: string) => async (dispatch: Dispatch) => { + dispatch(new LoadAllSiteBySiteIdAction()); + const siteDetails: Sites = (await siteManagerService.getSiteBySiteId(siteId)) || []; + dispatch(new AllSiteBySiteIdALoadedAction(siteDetails)); + return siteDetails; +}; + +/** + * Get CategoryItemsBySiteIdAndCategoryName + */ +export class LoadAllCategoryItemsBySiteIdAction extends BaseAction { } + +export class AllCategoryItemsBySiteIdALoadedAction extends BaseAction { + constructor(public categoryItemList: SiteManagerCategoryItems | null) { + super(); + } +} + +export const loadAllCategoryItemsBySiteIdAsync = (siteId: string, categoryName: string) => async (dispatch: Dispatch) => { + dispatch(new LoadAllCategoryItemsBySiteIdAction()); + const categoryItemList: SiteManagerCategoryItems = (await siteManagerService.getCategoryItemsBySiteIdAndCategoryName(siteId, categoryName)) || []; + dispatch(new AllCategoryItemsBySiteIdALoadedAction(categoryItemList)); + return categoryItemList; +}; + +/** + * Get Site frequency plan + */ +export class LoadAllFrequencyPlanBySiteIdAction extends BaseAction { } + +export class AllFrequencyPlanBySiteIdALoadedAction extends BaseAction { + constructor(public freqPlanList: SiteConfigurationFreqPlan[]) { + super(); + } +} + +export const loadAllFrequencyPlanBySiteIdAsync = (siteId: string) => async (dispatch: Dispatch) => { + dispatch(new LoadAllFrequencyPlanBySiteIdAction()); + try { + const freqPlan: SiteConfigurationFreqPlan[] = await siteManagerService.getSitesFrequencyPlan(siteId); + dispatch(new AllFrequencyPlanBySiteIdALoadedAction(freqPlan)); + return freqPlan; + } catch (error) { + console.error('Error fetching site frequency plan:', error); + return []; + } +}; + +/** + * Get Available Frequency bands + */ +export class LoadAllAvailableBandsAction extends BaseAction { } + +export class AllAvailableBandsALoadedAction extends BaseAction { + constructor(public bandsList: Bands[]) { + super(); + } +} + +export const loadAllAvailableBandsAsync = () => async (dispatch: Dispatch) => { + dispatch(new LoadAllAvailableBandsAction()); + try { + const bands: Bands[] = await siteManagerService.getAvailableBands(); + dispatch(new AllAvailableBandsALoadedAction(bands)); + return bands; + } catch (error) { + console.error('Error fetching bands:', error); + return []; + } +}; + +/** + * Get All Available Site Types + */ +export class LoadAllAvailableSiteTypesAction extends BaseAction { } + +export class AllAvailableSiteTypesLoadedAction extends BaseAction { + constructor(public siteTypesList: String[]) { + super(); + } +} + +export const loadAllAvailableSiteTypesAsync = () => async (dispatch: Dispatch) => { + dispatch(new LoadAllAvailableSiteTypesAction()); + try { + const siteTypes: String[] = await siteManagerService.getAvailableSiteTypes(); + dispatch(new AllAvailableSiteTypesLoadedAction(siteTypes)); + return siteTypes; + } catch (error) { + console.error('Error fetching site types:', error); + return []; + } +}; + diff --git a/features/sdnr/odlux/odlux/apps/siteManagerApp/src/actions/sitedocManagementAction.ts b/features/sdnr/odlux/odlux/apps/siteManagerApp/src/actions/sitedocManagementAction.ts new file mode 100644 index 0000000..fe0f798 --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/siteManagerApp/src/actions/sitedocManagementAction.ts @@ -0,0 +1,93 @@ +/** + * ============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 { SitedocOrderTask, UserListItem } from '../models/siteDocTypes'; +import sitedocDataService from '../services/sitedocDataService'; + + +export class SetAllUsersAction extends Action { + constructor(public users: UserListItem[]) { + super(); + } +} + +export class SetTSSRAction extends Action { + constructor(public isTSSR: boolean) { + super(); + } +} + +export class UpdateNoteAction extends Action { + constructor(public note: string) { + super(); + } +} + +export class UpdateStateAction extends Action { + constructor(public state: string) { + super(); + } +} + +export class UpdateTasks extends Action { + constructor(public tasks: SitedocOrderTask[]) { + super(); + } +} + +export class ResetAction extends Action { } + +export class SelectUserAction extends Action { + constructor(public user: string) { + super(); + } +} + +export class SetSiteExists extends Action { + constructor(public exists: boolean) { + super(); + } +} + +export const getUsersAction = async (dispatcher: Dispatch) => { + const users = await sitedocDataService.getAllUsers(); + dispatcher(new SetAllUsersAction(users)); +}; + + +export class BaseAction extends Action { } + +export class LoadAllSiteDetailsBySiteIdAction extends BaseAction { } + +export class AllSiteDetailsBySiteIdALoadedAction extends BaseAction { + constructor(public siteDetailsList: any | null) { + super(); + } +} + +export const loadAllSiteDetailsBySiteIdAsync = (siteId: string) => async (dispatch: Dispatch) => { + dispatch(new LoadAllSiteDetailsBySiteIdAction()); + const siteDetailsList: any = (await sitedocDataService.getSiteDetails(siteId)); + dispatch(new AllSiteDetailsBySiteIdALoadedAction(siteDetailsList)); + return siteDetailsList; +}; + + diff --git a/features/sdnr/odlux/odlux/apps/siteManagerApp/src/assets/icons/siteManagerAppIcon.svg b/features/sdnr/odlux/odlux/apps/siteManagerApp/src/assets/icons/siteManagerAppIcon.svg new file mode 100644 index 0000000..26856fe --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/siteManagerApp/src/assets/icons/siteManagerAppIcon.svg @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/features/sdnr/odlux/odlux/apps/siteManagerApp/src/components/createNewOrder.tsx b/features/sdnr/odlux/odlux/apps/siteManagerApp/src/components/createNewOrder.tsx new file mode 100644 index 0000000..39334f5 --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/siteManagerApp/src/components/createNewOrder.tsx @@ -0,0 +1,252 @@ +/** + * ============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 React from 'react'; + +import { Button, Checkbox, FormControl, FormControlLabel, FormGroup, FormHelperText, InputLabel, MenuItem, Select, TextField, Typography } from '@mui/material'; +import Dialog from '@mui/material/Dialog'; +import DialogActions from '@mui/material/DialogActions'; +import DialogContent from '@mui/material/DialogContent'; +import DialogTitle from '@mui/material/DialogTitle'; +import { RouteComponentProps, withRouter } from 'react-router-dom'; + +import { useApplicationDispatch, useSelectApplicationState } from '../../../../framework/src/flux/connect'; +import { IApplicationStoreState } from '../../../../framework/src/store/applicationStore'; + +import { ResetAction, SelectUserAction, SetTSSRAction, UpdateNoteAction, UpdateTasks } from '../actions/sitedocManagementAction'; +import { SitedocOrder, SitedocOrderTask } from '../models/siteDocTypes'; +import sitedocDataService from '../services/sitedocDataService'; +import OrderTask from './orderTask'; + +type orderProps = RouteComponentProps & { + siteId: string; + onClose: (siteId: string) => void; + onError: () => void; +}; + +const NewOrder = (props: orderProps) => { + const users = useSelectApplicationState((state: IApplicationStoreState) => state.siteManager.sitedocManagement.users); + const selectedUser = useSelectApplicationState((state: IApplicationStoreState) => state.siteManager.sitedocManagement.selectedUser); + const isTssr = useSelectApplicationState((state: IApplicationStoreState) => state.siteManager.sitedocManagement.isTSSR); + const tasks = useSelectApplicationState((state: IApplicationStoreState) => state.siteManager.sitedocManagement.tasks); + const note = useSelectApplicationState((state: IApplicationStoreState) => state.siteManager.sitedocManagement.note); + const site = useSelectApplicationState((state: IApplicationStoreState) => state.siteManager.details.data); + + const dispatch = useApplicationDispatch(); + const selectUserAction = (user: string) => dispatch(new SelectUserAction(user)); + const checkTSSR = (value: boolean) => dispatch(new SetTSSRAction(value)); + const updateNote = (noteUpdate: string) => dispatch(new UpdateNoteAction(noteUpdate)); + const setTasks = (addTasks: SitedocOrderTask[]) => dispatch(new UpdateTasks(addTasks)); + const reset = () => dispatch(new ResetAction()); + + const emptyTask: SitedocOrderTask = { type: '', description: '', completed: false }; + const [isUsernameEmpty, setUsernameEmpty] = React.useState(false); + const [areTasksEmpty, setTasksEmpty] = React.useState<{ error: boolean }[]>([]); + const [orderInfoMessage, setOrderInfoMessage] = React.useState<{ message: string; error: boolean }>({ message: '', error: false }); + const splitSiteIdSelected = props.siteId.toString(); + let siteIdSelectedToCreateOrder = splitSiteIdSelected.split('#')[0]; + + React.useEffect(() => { + const taskErrors = tasks.map(() => { return { error: false }; }); + setTasksEmpty(taskErrors); + }, []); + + const addTaskButtonClicked = (e: any) => { + e.preventDefault(); + setTasks([...tasks, emptyTask]); + setTasksEmpty([...areTasksEmpty, { error: false }]); + }; + + const update = (property: string, index: number, newValue: string) => { + let items = [...tasks]; + let item = { ...items[index] } as any; + item[property] = newValue; + items[index] = item; + setTasks(items); + //clean error + if (items[index].description.length > 0 && items[index].type.length > 0) { + let errors = [...areTasksEmpty]; + errors[index] = { error: false }; + setTasksEmpty(errors); + } + }; + + const updateTaskType = (index: number, value: string) => { + update('type', index, value); + }; + + const updateTaskDescription = (index: number, value: string) => { + update('description', index, value); + }; + + const onReset = () => { + reset(); + }; + + const checkTasks = (orderTasks: SitedocOrderTask[]) => { + let empty: number[] = []; + orderTasks.forEach((el, i) => { + if (el.description.length == 0 || el.type.length == 0) + empty.push(i); + }); + + return empty; + }; + + const createOrder = () => { + const emptyTasks = checkTasks(tasks); + let areParamsEmpty = false; + if (selectedUser.length == 0) { + setUsernameEmpty(true); + areParamsEmpty = true; + } + + if (emptyTasks.length > 0) { + let orderTasks = areTasksEmpty; + //clean errors + orderTasks = tasks.map(() => { return { error: false }; }); + //set errors + emptyTasks.forEach(el => { + orderTasks[el] = { error: true }; + }); + setTasksEmpty(orderTasks); + areParamsEmpty = true; + } + + if (!areParamsEmpty) { + setUsernameEmpty(false); + setTasksEmpty([]); + //remove possible milliseconds + const datetime = new Date().toJSON().split('.')[0] + 'Z'; + let newOrder: SitedocOrder = { + tasks: tasks, + state: 'OPEN', + assignedUser: selectedUser, + reportFile: '', + note: note, + date: datetime, + isTssr: isTssr, + }; + sitedocDataService.createOrder(newOrder, siteIdSelectedToCreateOrder) + .then((res) => { + //display message + if (!res.serverError) { + setOrderInfoMessage(res); + } + }); + } + }; + + const onClose = (siteId: string) => { + // eslint-disable-next-line @typescript-eslint/no-unused-expressions + props.onClose && props.onClose(siteId); + }; + + const onError = () => { + // eslint-disable-next-line @typescript-eslint/no-unused-expressions + props.onError && props.onError(); + }; + + const name = site?.name; + + return <> + + TSS-Report Order + +
+ Create order for site {siteIdSelectedToCreateOrder}{name && name.length > 0 && ' | ' + name} + + Assign to User + + { + isUsernameEmpty && User cannot be empty + } + + + checkTSSR(!isTssr)} />} label='Is TSSR' /> + + +
+ { + tasks.map((el, index) => { + return { updateTaskDescription(index, e); }} + onTypeUpdate={(e) => { updateTaskType(index, e); }} />; + }) + } + + + + { updateNote(e.target.value as string); }} + label='Add Note' > +
+
+ { + orderInfoMessage.message.length > 0 && (!orderInfoMessage.error) && + {orderInfoMessage.message} + } + { + orderInfoMessage.message.length > 0 && orderInfoMessage.error && + <> + + Error in Order Creation + {orderInfoMessage.message} + + + + + + + } +
+
+
+ + + + + +
+ ; +}; + +const CreateNewOrder = withRouter(NewOrder); + +export default CreateNewOrder; diff --git a/features/sdnr/odlux/odlux/apps/siteManagerApp/src/components/denseTable.tsx b/features/sdnr/odlux/odlux/apps/siteManagerApp/src/components/denseTable.tsx new file mode 100644 index 0000000..8998e45 --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/siteManagerApp/src/components/denseTable.tsx @@ -0,0 +1,125 @@ +/** + * ============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 React, { FC, MouseEvent } from 'react'; + +import Paper from '@mui/material/Paper'; +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 makeStyles from '@mui/styles/makeStyles'; + +const useStyles = makeStyles({ + denseTable: { + borderRadius: '0px', + }, + button: { + margin: 0, + padding: '6px 6px', + minWidth: 'unset', + }, +}); + +type DenseTableProps = { + actions?: boolean; + headers: string[]; + height: number; + hover: boolean; + ariaLabelRow: string; + ariaLabelColumn?: string[]; + verticalTable?: boolean; + onLinkClick?(id: string): void; data: any[]; + onClick?(id: string): void; +}; + +const DenseTable: FC = (props) => { + const { + ariaLabelRow, + data, + headers, + height, + hover, + ariaLabelColumn, + onClick = () => undefined, + verticalTable, + } = props; + + const styles = useStyles(); + + const handleClick = (event: MouseEvent, id: string) => { + event.preventDefault(); + onClick(id); + }; + + return ( + +
+ + + + { + headers.map((tableHeader) => ({tableHeader})) + } + + + + {data.map((row, index) => { + + const values = (typeof row === 'string' || row instanceof String) ? [row] : Object.keys(row).map(function (e) { return row[e]; }); + + return ( + handleClick(e, row.name)}> + { + values.map((value, i) => { + if (value !== undefined) { + + if (!verticalTable) { + const ariaLabel = ariaLabelColumn === undefined ? headers[i].toLowerCase() : ariaLabelColumn[i]; + if (ariaLabel.length > 0) { + return {value}; + } else { + return {value}; + } + } else { + // skip adding aria label to 'header' column + if (i === 0) { + return {value}; + } else { + const ariaLabel = props.ariaLabelColumn === undefined ? props.headers[index].toLowerCase() : props.ariaLabelColumn[index]; + return {value}; + } + } + } else + return null; + }) + } + ); + }) + } + +
+
+
+ ); +}; + +DenseTable.displayName = 'DenseTable'; + +export default DenseTable; \ No newline at end of file diff --git a/features/sdnr/odlux/odlux/apps/siteManagerApp/src/components/deviceTable.tsx b/features/sdnr/odlux/odlux/apps/siteManagerApp/src/components/deviceTable.tsx new file mode 100644 index 0000000..433051a --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/siteManagerApp/src/components/deviceTable.tsx @@ -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 React, { useEffect } from 'react'; +import { ColumnType, MaterialTable, MaterialTableCtorType } from '../../../../framework/src/components/material-table'; +import { useApplicationDispatch, useSelectApplicationState } from '../../../../framework/src/flux/connect'; +import { IApplicationStoreState } from '../../../../framework/src/store/applicationStore'; +import { createDeviceTableActions, createDeviceTablePreActions, createDeviceTableProperties } from '../handlers/deviceTableHandler'; + +const DeviceTable = MaterialTable as MaterialTableCtorType; + +type DeviceTableComponentProps = { + preFilterType: { + id: string; + }; +}; + +const DeviceTableComponent: React.FC = (props) => { + + const deviceTableProperties = useSelectApplicationState((state: IApplicationStoreState) => createDeviceTableProperties(state)); + + const dispatch = useApplicationDispatch(); + const deviceTableActions = createDeviceTableActions(dispatch); + const deviceTablePreActions = createDeviceTablePreActions(dispatch); + + useEffect(() => { + const tablePreFilter = { + 'siteId': props.preFilterType.id, + }; + deviceTableActions.onClearFilters(); + deviceTablePreActions.onPreFilterChanged(tablePreFilter); + }, [props.preFilterType.id]); + + + return ( + <> + + + + ); +}; + +export const DeviceTableView = DeviceTableComponent; +export default DeviceTableView; \ No newline at end of file diff --git a/features/sdnr/odlux/odlux/apps/siteManagerApp/src/components/linkTable.tsx b/features/sdnr/odlux/odlux/apps/siteManagerApp/src/components/linkTable.tsx new file mode 100644 index 0000000..16ad9ce --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/siteManagerApp/src/components/linkTable.tsx @@ -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 React, { useEffect } from 'react'; + +import { MaterialTable, MaterialTableCtorType, ColumnType } from '../../../../framework/src/components/material-table'; +import { useApplicationDispatch, useSelectApplicationState } from '../../../../framework/src/flux/connect'; +import { IApplicationStoreState } from '../../../../framework/src/store/applicationStore'; +import { createLinkTableActions, createLinkTablePreActions, createLinkTableProperties } from '../handlers/linkTableHandler'; + +const LinkTable = MaterialTable as MaterialTableCtorType; + +type LinkTableComponentProps = { + preFilterType: { + id: string; + type: string; + }; +}; + +const LinkTableView: React.FC = (props) => { + const linkTableProperties = useSelectApplicationState((state: IApplicationStoreState) => createLinkTableProperties(state)); + + const dispatch = useApplicationDispatch(); + const linkTableActions = createLinkTableActions(dispatch); + const linkTablePreActions = createLinkTablePreActions(dispatch); + + useEffect(() => { + const tablePreFilter = { + 'siteId': props.preFilterType.id, + type: props.preFilterType.type, + }; + linkTableActions.onClearFilters(); + linkTablePreActions.onPreFilterChanged(tablePreFilter); + }, [props.preFilterType.id, props.preFilterType.type]); + + return ( + <> + { + return ( +
+ {rowData.siteA.id} +
+ ); + }, + }, + { + property: 'siteB', title: 'SiteB', type: ColumnType.custom, customControl: ({ rowData }) => { + return ( +
+ {rowData.siteB.id} +
+ ); + }, + }, + { property: 'operationalState', title: 'operational State', type: ColumnType.text }, + { property: 'operatorId', title: 'Operator Id', type: ColumnType.text }, + { property: 'lifecycleState', title: 'life Cycle State', type: ColumnType.text }, + ]} idProperty='id' {...linkTableActions} {...linkTableProperties} > +
+ + ); +}; + +export default LinkTableView; diff --git a/features/sdnr/odlux/odlux/apps/siteManagerApp/src/components/messageDialog.tsx b/features/sdnr/odlux/odlux/apps/siteManagerApp/src/components/messageDialog.tsx new file mode 100644 index 0000000..5de2717 --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/siteManagerApp/src/components/messageDialog.tsx @@ -0,0 +1,53 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2022 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 { DialogContentText } from '@mui/material'; +import Button from '@mui/material/Button'; +import Dialog from '@mui/material/Dialog'; +import DialogActions from '@mui/material/DialogActions'; +import DialogContent from '@mui/material/DialogContent'; + +type MessageDialogComponentProps = { + dialogMessage: string; + isFromSearch: boolean; + onClose: (event: React.SyntheticEvent, isFromSearch: boolean) => void; + openDialog: boolean; + setOpenDialog: any; +}; + +export const MessageDialog = (props: MessageDialogComponentProps) => { + const handleDialogClose = (event: React.SyntheticEvent) => { + props.setOpenDialog(false); + props.onClose(event, props.isFromSearch); + }; + + return ( + + + + Server Error: {props.dialogMessage} + + + + + + + ); +}; diff --git a/features/sdnr/odlux/odlux/apps/siteManagerApp/src/components/orderTask.tsx b/features/sdnr/odlux/odlux/apps/siteManagerApp/src/components/orderTask.tsx new file mode 100644 index 0000000..34b6f85 --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/siteManagerApp/src/components/orderTask.tsx @@ -0,0 +1,60 @@ +/** + * ============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 * as React from 'react'; +import { FormControl, FormHelperText, InputLabel, MenuItem, Select, Stack, TextField } from '@mui/material'; + +import { SitedocOrderTask } from '../models/siteDocTypes'; + +type taskProps = { value: SitedocOrderTask; onDescUpdate(val: string): void; onTypeUpdate(val: string): void; error: boolean }; + +const Task = (props: taskProps) => { + const [orderTypes] = React.useState(['UPDATE', 'DELETE']); + + return <> + + + Task Type + + + { props.onDescUpdate(e.target.value); }}> + { + props.error && Cannot be empty + } + + ; +}; + +const OrderTask = Task; + +export default OrderTask; \ No newline at end of file diff --git a/features/sdnr/odlux/odlux/apps/siteManagerApp/src/components/picturesTable.tsx b/features/sdnr/odlux/odlux/apps/siteManagerApp/src/components/picturesTable.tsx new file mode 100644 index 0000000..6664318 --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/siteManagerApp/src/components/picturesTable.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, { useState } from 'react'; + +import { faDownload } from '@fortawesome/free-solid-svg-icons'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import IconButton from '@mui/material/IconButton'; +import makeStyles from '@mui/styles/makeStyles'; + +import { ColumnType, MaterialTable, MaterialTableCtorType } from '../../../../framework/src/components/material-table'; + +const useStyles = makeStyles({ + root: { + '&:hover': { + color: 'blue', + }, + }, + previewImage: { + cursor: 'pointer', + height: 120, + width: 120, + marginTop: '10px', + marginLeft: '10px', + }, +}); + +const Pictures = MaterialTable as MaterialTableCtorType; + +type PicturesComponentProps = { + item: { + name: string; + url: string; + 'last-update': string; + }[]; +}; + +type SiteManagerPicturesComponentProps = PicturesComponentProps; + +const PicturesViewComponent: React.FC = (props: SiteManagerPicturesComponentProps) => { + const [item] = useState(props.item); + + const classes = useStyles(); + + const downloadPicture = (url: string) => { + let fileName = url.substring( + url.lastIndexOf('/') + 1, + url.lastIndexOf('.'), + ); + fetch(url) + .then(response => { + response.arrayBuffer().then(function (buffer) { + const reportUrl = window.URL.createObjectURL(new Blob([buffer])); + const link = document.createElement('a'); + link.href = reportUrl; + link.setAttribute('download', fileName + '.jpg'); + document.body.appendChild(link); + link.click(); + }); + }) + .catch(() => alert('oh no! something went wrong')); + }; + + return ( + <> + { + if (rowData.url === '') { + return (<>{rowData.name} ); + } else { + return (
{rowData.name} ); + } + }, + }, + { + property: 'preview', title: 'Photo Preview', type: ColumnType.custom, customControl: ({ rowData }) => { + return Preview; + }, + }, + { + property: 'url', title: 'Action', type: ColumnType.custom, customControl: ({ rowData }) => { + if (rowData.url === '') { + return ( ); + } else { + return ( + { event.stopPropagation(); downloadPicture(rowData.url); }} /> + ); + } + }, + }, + ]} idProperty='id' rows={item} > + + + ); +}; + +export const PicturesView = PicturesViewComponent; +export default PicturesView; \ No newline at end of file diff --git a/features/sdnr/odlux/odlux/apps/siteManagerApp/src/components/refreshSiteTableDialog.tsx b/features/sdnr/odlux/odlux/apps/siteManagerApp/src/components/refreshSiteTableDialog.tsx new file mode 100644 index 0000000..89671a6 --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/siteManagerApp/src/components/refreshSiteTableDialog.tsx @@ -0,0 +1,104 @@ +/** + * ============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 { useApplicationDispatch } from '../../../../framework/src/flux/connect'; + +import { siteTableReloadAction } from '../handlers/siteTableHandler'; + +export enum RefreshSiteTableDialogMode { + None = 'none', + RefreshSiteTableTable = 'RefreshSiteTableTable', +} + +type DialogSettings = { + dialogTitle: string; + dialogDescription: string; + applyButtonText: string; + cancelButtonText: string; + enableMountIdEditor: boolean; + enableUsernameEditor: boolean; + enableExtendedEditor: boolean; +}; + +const settings: { [key: string]: DialogSettings } = { + [RefreshSiteTableDialogMode.None]: { + dialogTitle: '', + dialogDescription: '', + applyButtonText: '', + cancelButtonText: '', + enableMountIdEditor: false, + enableUsernameEditor: false, + enableExtendedEditor: false, + }, + [RefreshSiteTableDialogMode.RefreshSiteTableTable]: { + dialogTitle: 'Do you want to refresh the Site table?', + dialogDescription: '', + applyButtonText: 'Yes', + cancelButtonText: 'Cancel', + enableMountIdEditor: true, + enableUsernameEditor: true, + enableExtendedEditor: true, + }, +}; + +type RefreshSiteTableDialogComponentProps = { + mode: RefreshSiteTableDialogMode; + onClose: () => void; +}; + +const RefreshSiteTableDialogComponent: React.FC = (props) => { + + const dispatch = useApplicationDispatch(); + const refreshSiteTable = () => dispatch(siteTableReloadAction); + + const setting = settings[props.mode]; + const onRefresh = () => { + refreshSiteTable(); + props.onClose(); + }; + + const onCancel = () => { + props.onClose(); + }; + + return ( + + {setting.dialogTitle} + + + {setting.dialogDescription} + + + + + + + + ); +}; + +export const RefreshSiteTableDialog = RefreshSiteTableDialogComponent; +export default RefreshSiteTableDialog; diff --git a/features/sdnr/odlux/odlux/apps/siteManagerApp/src/components/siteAdditionalInformation.tsx b/features/sdnr/odlux/odlux/apps/siteManagerApp/src/components/siteAdditionalInformation.tsx new file mode 100644 index 0000000..a94d272 --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/siteManagerApp/src/components/siteAdditionalInformation.tsx @@ -0,0 +1,136 @@ +/** + * ============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 React, { FC, SyntheticEvent, useEffect, useState } from 'react'; + +import { AppBar, Tab, Tabs, Typography } from '@mui/material'; + +import StadokSite from '../models/stadokSite'; +import DenseTable from './denseTable'; + +type StadokDetailsProps = { siteId: string }; + +const SiteAdditionalInformation: FC = (props) => { + + const [currentTab, setCurrentTab] = useState('contacts'); + const [data, setData] = useState(null); + + useEffect(() => { + const fetchData = async (siteId: string) => { + const response = await fetch('/sitedoc/site/' + siteId); + const result = await response.json(); + setData(result); + }; + + fetchData(props.siteId); + }, [props.siteId]); + + + const getContacts = (site: StadokSite | null) => { + const contacts = []; + if (site?.createdBy) { + contacts.push({ + h: 'Site Creator', col1: site?.createdBy.firstName, col2: site?.createdBy.lastName, + col3: site?.createdBy.email, col4: site?.createdBy.telephoneNumber, + }); + } + if (site?.contacts?.manager) { + contacts.push({ + h: 'Manager', col1: site?.contacts.manager.firstName, col2: site?.contacts.manager.lastName, + col3: site?.contacts.manager.email, col4: site?.contacts.manager.telephoneNumber, + }); + } + if (site?.contacts?.owner) { + contacts.push({ + h: 'Owner', col1: site?.contacts.owner.firstName, col2: site?.contacts.owner.lastName, + col3: site?.contacts.owner.email, col4: site?.contacts.owner.telephoneNumber, + }); + } + return contacts; + }; + + const handleTabChange = (event: SyntheticEvent, newValue: string) => { + setCurrentTab(newValue); + }; + const contacts = getContacts(data); + + + return ( +
+
+ + + + + + + + { + currentTab == 'contacts' && (contacts.length > 0 ? + + : +
+ + No contacts available + +
) + } + { + currentTab == 'safetyInfo' && (data && data?.safetyNotices?.length > 0 + ? ( + + ) + : ( +
+ + No safety notices applicable + +
+ )) + } + { + currentTab == 'logs' && (data && data?.logs?.length > 0 + ? ( + + ) + : ( +
+ + No activity log available + +
+ ) + ) + } +
+
+ + + ); + +}; + +export default SiteAdditionalInformation; \ No newline at end of file diff --git a/features/sdnr/odlux/odlux/apps/siteManagerApp/src/components/siteConfiguration.tsx b/features/sdnr/odlux/odlux/apps/siteManagerApp/src/components/siteConfiguration.tsx new file mode 100644 index 0000000..a494a84 --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/siteManagerApp/src/components/siteConfiguration.tsx @@ -0,0 +1,357 @@ +/** + * ============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, { useEffect, useState } from 'react'; + +import AddIcon from '@mui/icons-material/Add'; +import { Button, Dialog, DialogActions, DialogContent, DialogContentText, FormControl, InputLabel, MenuItem, Select, TextField, Typography } from '@mui/material'; +import CircularProgress from '@mui/material/CircularProgress'; + +import { ColumnType, MaterialTable, MaterialTableCtorType } from '../../../../framework/src/components/material-table'; +import { useApplicationDispatch } from '../../../../framework/src/flux/connect'; + +import { loadAllAvailableBandsAsync, loadAllFrequencyPlanBySiteIdAsync } from '../actions/siteManagerTreeActions'; +import { addEditSiteConfig } from '../models/siteManager'; +import siteManagerService from '../services/siteManagerService'; + +const SiteConfiguration = MaterialTable as MaterialTableCtorType; + + +type SiteConfigurationProps = { + siteId: string; +}; + +interface SiteConfigTableData { + band: string; + bandId: number; + status: string; + configuration: string; + comment: string; +} + +interface AvailableBands { + keyId: number; + name: string; + duplexSpacingMhz: number; +} + +const SiteConfigurationComponent: React.FC = (props: SiteConfigurationProps) => { + + const dispatch = useApplicationDispatch(); + const getSiteFrequencyPlan = async (siteId: string) => dispatch(loadAllFrequencyPlanBySiteIdAsync(siteId)); + const getAllAvailableBands = async () => dispatch(loadAllAvailableBandsAsync()); + + const [siteConfigTableData, setSiteConfigTableData] = useState([]); + const [bandsTableData, setBandsTableData] = useState([]); + const [openEditConfigDialog, setOpenEditConfigDialog] = useState(false); + const [config, setConfig] = useState('HIGH'); + const [comment, setComment] = useState(''); + const [rowData, setRowData] = useState(); + const [refreshTable, setRefreshTable] = useState(false); + const [shouldRefreshTable, setShouldRefreshTable] = useState(false); + const [saveInfoMessage, setSaveInfoMessage] = useState<{ message: string; error: boolean }>({ message: '', error: false }); + const [isDialogClosed, setIsDialogClosed] = useState(false); + const [openAddConfigDialog, setOpenAddConfigDialog] = useState(false); + const [bandId, setBandId] = useState(undefined); + const [openDeleteConfigDialog, setOpenDeleteConfigDialog] = useState(false); + const [isLoading, setIsLoading] = useState(false); + + const fetchSiteFrequencyPlan = async () => { + setIsLoading(true); + try { + const data: any = await getSiteFrequencyPlan(props.siteId); + const tableData: SiteConfigTableData[] = data.map((row: any) => ({ + band: row.band.name, + bandId: row.band.keyId, + status: row.status, + configuration: row.configuration, + comment: row.comment || '', + })); + setSiteConfigTableData(tableData); + } catch (error) { + console.error('Error fetching site frequency plan:', error); + } finally { + setIsLoading(false); + } + }; + + const fetchAllAvailableBands = async () => { + try { + const data: any = await getAllAvailableBands(); + const tableData: AvailableBands[] = data.map((row: any) => ({ + keyId: row.keyId, + name: row.name, + duplexSpacingMhz: row.duplexSpacingMhz, + })); + setBandsTableData(tableData); + } catch (error) { + console.error('Error fetching available bands:', error); + } + }; + + useEffect(() => { + fetchSiteFrequencyPlan(); + fetchAllAvailableBands(); + setSaveInfoMessage({ message: '', error: false }); + if (shouldRefreshTable) { + setShouldRefreshTable(false); + setRefreshTable((prevValue) => !prevValue); + } + }, [props.siteId, shouldRefreshTable]); + + const onOpenAddSiteConfigDialog = () => { + setOpenAddConfigDialog(true); + setBandId(undefined); + setComment(''); + }; + + const onOpenEditSiteConfigDialog = (event: React.MouseEvent, element: SiteConfigTableData) => { + if (!openEditConfigDialog) { + setOpenEditConfigDialog(true); + setComment(''); + setConfig(element.configuration); + setRowData(element); + } + }; + const onOpenDeleteSiteConfigDialog = (event: React.MouseEvent, element: SiteConfigTableData) => { + if (!openDeleteConfigDialog) { + setOpenDeleteConfigDialog(true); + setRowData(element); + } + }; + + const saveEditConfig = (bandid: string, siteId: string) => { + let modifiedConfig: addEditSiteConfig = { + configuration: config, + comment: comment, + }; + siteManagerService.saveSiteConfiguration(modifiedConfig, bandid, siteId).then((result) => { + if (!result.error) { + setOpenEditConfigDialog(false); + setIsDialogClosed(true); + } else { + setSaveInfoMessage(result); + } + }); + }; + + const addConfig = (bandid: string, siteId: string) => { + let addedConfig: addEditSiteConfig = { + configuration: config, + comment: comment, + }; + if (bandId !== undefined) { + siteManagerService.createSiteConfiguration(addedConfig, bandid, siteId).then((result) => { + if (!result.error) { + setOpenAddConfigDialog(false); + setIsDialogClosed(true); + } else { + setSaveInfoMessage(result); + } + }); + } + }; + const deleteConfig = (bandid: string, siteId: string) => { + siteManagerService.deleteSiteConfiguration(bandid, siteId).then((result) => { + if (!result.error) { + setOpenDeleteConfigDialog(false); + setIsDialogClosed(true); + } else { + setSaveInfoMessage(result); + } + }); + }; + + const handleCloseEditDialog = () => { + setOpenEditConfigDialog(false); + setSaveInfoMessage({ message: '', error: false }); + }; + + const handleCloseAddDialog = () => { + setOpenAddConfigDialog(false); + setSaveInfoMessage({ message: '', error: false }); + }; + + const handleCloseDeleteDialog = () => { + setOpenDeleteConfigDialog(false); + setSaveInfoMessage({ message: '', error: false }); + }; + + const addSiteConfigurationAction = { + icon: AddIcon, + tooltip: 'Add Site Configuration', + ariaLabel: 'add-site-configuration', + onClick: onOpenAddSiteConfigDialog, + }; + + const getContextMenu = (row: SiteConfigTableData) => { + return [ + <> + onOpenEditSiteConfigDialog(event, row)}> + Edit Config + , + onOpenDeleteSiteConfigDialog(event, row)}> + Delete Config + , + ]; + }; + + useEffect(() => { + if (isDialogClosed) { + setShouldRefreshTable(true); + setIsDialogClosed(false); + } + }, [isDialogClosed]); + + return ( + <> + {isLoading && ( +
+ +
+ )} + { + return getContextMenu(selectedRowData); + }} + /> + + + + {'Edit Configuration for the band ' + rowData?.band} + + + Configuration + + { setComment(event.target.value); }} id='edit-comment' label='Comment' aria-label='edit-comment' + type='text' fullWidth /> + + + + + + + +
+ {saveInfoMessage.message.length > 0 && !saveInfoMessage.error && ( + {saveInfoMessage.message} + )} + {saveInfoMessage.message.length > 0 && saveInfoMessage.error && ( + {'Save Failed - ' + saveInfoMessage.message} + )} +
+
+ + + {'Add Configuration for the Site'} + + Band + + + + Configuration + + { setComment(event.target.value); }} id='addConfig-comment' label='Comment' aria-label='addConfig-comment' + type='text' fullWidth /> + + + + + + +
+ {saveInfoMessage.message.length > 0 && !saveInfoMessage.error && ( + {saveInfoMessage.message} + )} + {saveInfoMessage.message.length > 0 && saveInfoMessage.error && ( + {'Save Failed - ' + saveInfoMessage.message} + )} +
+
+ + + {'Delete Frequency Plan from the Site'} + {'Do you really want to remove this configuration for band:' + rowData?.band} + + + + + +
+ {saveInfoMessage.message.length > 0 && !saveInfoMessage.error && ( + {saveInfoMessage.message} + )} + {saveInfoMessage.message.length > 0 && saveInfoMessage.error && ( + {'Save Failed - ' + saveInfoMessage.message} + )} +
+
+ + ); +}; + +export default SiteConfigurationComponent; diff --git a/features/sdnr/odlux/odlux/apps/siteManagerApp/src/components/siteDetails.tsx b/features/sdnr/odlux/odlux/apps/siteManagerApp/src/components/siteDetails.tsx new file mode 100644 index 0000000..c078231 --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/siteManagerApp/src/components/siteDetails.tsx @@ -0,0 +1,216 @@ +import React, { useState } from 'react'; + +import CancelIcon from '@mui/icons-material/Cancel'; +import EditIcon from '@mui/icons-material/Edit'; +import SaveIcon from '@mui/icons-material/Save'; +import { FormControl, InputLabel, MenuItem, Select, Typography } from '@mui/material'; +import IconButton from '@mui/material/IconButton'; +import TextField from '@mui/material/TextField'; +import makeStyles from '@mui/styles/makeStyles'; + +import { useApplicationDispatch } from '../../../../framework/src/flux/connect'; + +import { loadAllAvailableSiteTypesAsync } from '../actions/siteManagerTreeActions'; +import { Sites } from '../models/siteManager'; +import siteManagerService from '../services/siteManagerService'; + +const useStyles = makeStyles({ + formContainer: { + position: 'relative', + paddingRight: '48px', + }, + iconContainer: { + position: 'absolute', + top: '8px', + right: '0', + display: 'flex', + zIndex: 1, + }, +}); + +type Address = { + streetAndNr: string; + city: string; + zipCode: string; + country: string; +}; + +type Location = { + lon: string; + lat: string; +}; + +interface AvailableSiteTypes { + keyId: number; + type: string; +} + +type SiteDetailsProps = { + siteDetails: { + id: string; + uuid: string; + name: string; + amslInMeters: string; + type: string; + 'area-id': string; + 'item-count': number; + address: Address; + operator: string; + location: Location; + [key: string]: any; + }; +}; + +const SiteDetailsAccordion: React.FC = (props: SiteDetailsProps) => { + const classes = useStyles(); + const dispatch = useApplicationDispatch(); + const getAllAvailableSiteTypes = async () => dispatch(loadAllAvailableSiteTypesAsync()); + + const [formState, setFormState] = useState(props.siteDetails); + const [siteTypesData, setSiteTypesData] = useState([]); + const [isEditing, setIsEditing] = useState(false); + const [saveInfoMessage, setSaveInfoMessage] = React.useState<{ message: string; error: boolean }>({ message: '', error: false }); + + const handleInputChange = (event: React.ChangeEvent) => { + const { name, value } = event.target; + + setFormState((prevFormData) => ({ + ...prevFormData, + [name]: value, + })); + }; + + const handleNestedInputChange = (event: React.ChangeEvent) => { + const { name, value } = event.target; + const [parent, key] = name.split('.'); + + setFormState((prevFormData) => ({ + ...prevFormData, + [parent]: { + ...(prevFormData[parent as keyof Sites] as object), + [key]: value, + }, + })); + + }; + + const handleEditClick = () => { + setIsEditing(true); + setSaveInfoMessage({ message: '', error: false }); + }; + + const handleCancelClick = () => { + setFormState(props.siteDetails); + setIsEditing(false); + }; + + const handleSubmit = () => { + const editedFields: Sites = Object.keys(formState).reduce((acc, key) => { + if (formState[key as keyof Sites] !== props.siteDetails[key as keyof Sites]) { + acc[key as keyof Sites] = formState[key as keyof Sites]; + } + return acc; + }, {}); + siteManagerService.saveModifiedSiteDetails(editedFields, formState.id).then((result) => { + setSaveInfoMessage(result); + }); + setIsEditing(false); + }; + + const fetchAllAvailableSiteTypes = async () => { + try { + const data: any = await getAllAvailableSiteTypes(); + const tableData: any[] = data.map((row: any) => ({ + type: row, + })); + setSiteTypesData(tableData); + } catch (error) { + console.error('Error fetching all Site types:', error); + } + }; + + React.useEffect(() => { + setFormState(props.siteDetails); + fetchAllAvailableSiteTypes(); + setSaveInfoMessage({ message: '', error: false }); + }, [props.siteDetails]); + + return ( + <> +
+
+
+
+
+
+
+
+
+
+
+ + type + + +
+ + {isEditing ? ( +
+ { e.preventDefault(); handleSubmit(); }}> + + + + + +
+ ) : ( + + + + )} + +
+ { + saveInfoMessage.message.length > 0 && (!saveInfoMessage.error) && + {saveInfoMessage.message} + } + { + saveInfoMessage.message.length > 0 && saveInfoMessage.error && + {'Save Failed - ' + saveInfoMessage.message} + } +
+
+ + ); +}; + +export default SiteDetailsAccordion; diff --git a/features/sdnr/odlux/odlux/apps/siteManagerApp/src/components/siteManagerSiteSearch.tsx b/features/sdnr/odlux/odlux/apps/siteManagerApp/src/components/siteManagerSiteSearch.tsx new file mode 100644 index 0000000..365dc04 --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/siteManagerApp/src/components/siteManagerSiteSearch.tsx @@ -0,0 +1,130 @@ +/** + * ============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 React, { useRef } from 'react'; + +import SearchIcon from '@mui/icons-material/Search'; +import SettingsBackupRestoreIcon from '@mui/icons-material/SettingsBackupRestore'; +import { Divider, IconButton, InputBase, Paper } from '@mui/material'; +import makeStyles from '@mui/styles/makeStyles'; + +import { NavigateToApplication } from '../../../../framework/src/actions/navigationActions'; +import { useApplicationDispatch, useSelectApplicationState } from '../../../../framework/src/flux/connect'; +import { IApplicationStoreState } from '../../../../framework/src/store/applicationStore'; + +import { SearchValueAction } from '../actions/siteManagerSiteSearchAction'; + +const styles = makeStyles({ + root: { + padding: '5px', + display: 'flex', + alignItems: 'center', + flexDirection: 'row', + width: '70%', + zIndex: 1, + }, + input: { + flex: 1, + marginLeft: 5, + }, + iconButton: { + padding: 10, + }, + divider: { + height: 28, + margin: 4, + }, +}); +interface SearchProps { + handleRefresh: (event: React.SyntheticEvent) => void; + handleSearch: (event: React.SyntheticEvent, searchValue: string, searchCategoryName?: string) => void; +} + +type SiteManagerSiteSearchProps = SearchProps; + +const SiteManagerSiteSearch: React.FunctionComponent = (props) => { + const classes = styles(); + const buttonRef = useRef(null); + const searchTerm = useSelectApplicationState((state: IApplicationStoreState) => state.siteManager.searchSite.siteId); + const searchCategoryName = useSelectApplicationState((state: IApplicationStoreState) => state.siteManager.searchSite.categoryName); + + const dispatch = useApplicationDispatch(); + const setSearchTerm = (siteId: string, categoryName?: string) => dispatch(new SearchValueAction(siteId, categoryName)); + const navigateToApplication = (applicationName: string, path?: string) => dispatch(new NavigateToApplication(applicationName, path)); + + + React.useEffect(() => { + if (buttonRef.current) { + buttonRef.current.click(); + } + }, []); + + + const handleClick = async (event: React.SyntheticEvent) => { + event.preventDefault(); + if (searchCategoryName && searchCategoryName.length > 0) { + navigateToApplication('siteManager', 'treeview/' + searchTerm + '/' + searchCategoryName); + if (searchTerm.length > 0) { + await props.handleSearch(event, searchTerm, searchCategoryName); + } + } else { + navigateToApplication('siteManager', 'treeview/' + searchTerm); + if (searchTerm.length > 0) { + await props.handleSearch(event, searchTerm); + } + } + }; + + const handleRefresh = async (event: React.SyntheticEvent) => { + setSearchTerm(''); + await props.handleRefresh(event); + event.preventDefault(); + }; + + return <> + + setSearchTerm(e.currentTarget.value)} + /> + + + + + + + + + + ; +}; + +export default SiteManagerSiteSearch; \ No newline at end of file diff --git a/features/sdnr/odlux/odlux/apps/siteManagerApp/src/components/siteManagerTreeview.tsx b/features/sdnr/odlux/odlux/apps/siteManagerApp/src/components/siteManagerTreeview.tsx new file mode 100644 index 0000000..c8214ab --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/siteManagerApp/src/components/siteManagerTreeview.tsx @@ -0,0 +1,1212 @@ +/** +* ============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 React, { useEffect, useRef, useState } from 'react'; + +import { ExpandMoreOutlined } from '@mui/icons-material'; +import { default as SiteMap } from '@mui/icons-material/Map'; +import TreeItem, { treeItemClasses, TreeItemProps } from '@mui/lab/TreeItem'; +import TreeView from '@mui/lab/TreeView'; +import { Accordion, AccordionDetails, AccordionSummary, Button, IconButton, Paper, Typography } from '@mui/material'; +import CircularProgress from '@mui/material/CircularProgress'; +import Collapse from '@mui/material/Collapse'; +import { alpha, styled } from '@mui/material/styles'; +import SvgIcon, { SvgIconProps } from '@mui/material/SvgIcon'; +import { TransitionProps } from '@mui/material/transitions'; +import { makeStyles } from '@mui/styles'; +import { useApplicationDispatch, useSelectApplicationState } from '../../../../framework/src/flux/connect'; +import { IApplicationStoreState } from '../../../../framework/src/store/applicationStore'; + +import { loadAllSiteDetailsBySiteIdAsync } from '../actions/sitedocManagementAction'; +import { loadTreeSiteSearchBySiteIdOrNameAsync } from '../actions/siteManagerSiteSearchAction'; +import { + loadAllAreasByAreaIdAsync, loadAllCategoriesBySiteIdAsync, loadAllCategoryItemsBySiteIdAsync, + loadAllCountriesAsync, loadAllSiteBySiteIdAsync, loadAllSitesByAreaIdAsync, +} from '../actions/siteManagerTreeActions'; +import SiteManagerSiteSearch from '../components/siteManagerSiteSearch'; +import { + ITreeViewItem, SiteManagerAreas, SiteManagerCategories, SiteManagerCategoryItems, + SiteManagerSiteOrderItemsDetails, Sites, +} from '../models/siteManager'; +import CreateOrderView from '../views/OrderCreation'; +import DeviceTableView from './deviceTable'; +import LinkTableView from './linkTable'; +import { MessageDialog } from './messageDialog'; +import PicturesView from './picturesTable'; +import SiteAdditionalInformation from './siteAdditionalInformation'; +import SiteConfigurationComponent from './siteConfiguration'; +import SiteDetailsAccordion from './siteDetails'; +import SiteOrdersView from './siteOrdersTable'; +import { CustomContent } from './treeItem'; +import TSSReportsView from './tssReportsTable'; + +const styles = makeStyles({ + root: { + flex: '1 0 0%', + display: 'flex', + flexDirection: 'row', + }, + iconButton: { + padding: 10, + }, + paperTreeView: { + width: '50%', + padding: '5px', + display: 'flex', + flexDirection: 'column', + zIndex: 1, + }, + paperDisplayView: { + position: 'relative', + width: '100%', + padding: '5px', + display: 'flex', + flexDirection: 'column', + zIndex: 1, + }, + searchParentDiv: { + padding: '5px', + display: 'flex', + flexDirection: 'row', + position: 'relative', + }, + emptyDiv: { + width: '30%', + }, + tree: { + height: '700px', + flexGrow: 1, + maxWidth: '600px', + overflowY: 'auto', + overflowX: 'auto', + }, + table: { + borderTopWidth: 3, + borderBottomWidth: 3, + borderColor: 'grey', + borderStyle: 'solid', + top: '15px', + width: '100%', + padding: '15px', + 'bordercollapse': 'collapse', + 'paddingbottom': '12px', + 'text-align': 'left', + 'color': 'black', + }, + tableDetails: { + border: '1px solid black', + padding: '5px', + }, + selectedSearchNode: { + backgroundColor: '#D0CDCD', + }, + normalNode: { + }, + accordionTitle: { + width: '33%', flexShrink: 0, + }, + iconContainer: { + position: 'absolute', + top: '50px', + right: '0', + display: 'flex', + zIndex: 1, + }, + loadingSpinnerDisplayView: { + position: 'absolute', + top: 0, + left: 0, + marginTop: '50px', + width: '100%', + height: '100%', + display: 'flex', + justifyContent: 'center', + alignItems: 'center', + backgroundColor: 'rgba(255, 255, 255, 0.7)', + zIndex: 9999, + }, + loadingSpinnerTreeView: { + position: 'absolute', + top: 0, + left: 0, + marginTop: '350px', + width: '100%', + height: '100%', + display: 'flex', + justifyContent: 'center', + alignItems: 'center', + backgroundColor: 'rgba(255, 255, 255, 0.7)', + zIndex: 9999, + }, +}); +interface CategoryIdMap { + [index: string]: ITreeViewItem; +} + +type SiteManagerTreeviewComponentState = { + nodes: ITreeViewItem[]; + expanded: string[]; + selected: string; + parentChildMap: Map; + selectedSite: Sites; + isSite: boolean; + isCategory: boolean; + isCategoryExpanded: boolean; + sitesList: string[]; + treeItemName: string; + siteManagerCategoryItems: SiteManagerCategoryItems; + siteManagerSiteOrderItems: SiteManagerSiteOrderItemsDetails; + categoryList: string[]; + categoryIdMap: CategoryIdMap; + selectedCategoryLinkType: string; + searchLeafParent: string[]; + selectedSearchItem: string; + siteId: string; + isDialogOpen: boolean; + openDialog: boolean; + dialogMessage: string; + isFromSearch: boolean; + isFocused: boolean; + isReady: boolean; + isLoadingTreeView: boolean; + isLoadingDisplayView: boolean; + searchSiteSelected: ITreeViewItem; +}; + +function MinusSquare(props: SvgIconProps) { + return ( + + + + ); +} + +function PlusSquare(props: SvgIconProps) { + return ( + + + + ); +} + +function CloseSquare(props: SvgIconProps) { + return ( + + + + ); +} + +function TransitionComponent(props: TransitionProps) { + return ( + + ); +} + +const StyledTreeItem = styled((props: TreeItemProps) => ( + +))(({ theme }) => ({ + [`& .${treeItemClasses.iconContainer}`]: { + '& .close': { + opacity: 0.3, + }, + }, + [`& .${treeItemClasses.group}`]: { + marginLeft: 15, + paddingLeft: 18, + borderLeft: `1px dashed ${alpha(theme.palette.text.primary, 0.4)}`, + }, + [`& .${treeItemClasses.selected}`]: { + backgroundColor: '#D0CDCD !important', + }, +})); + +const SiteManagerTreeViewComponent: React.FC = () => { + useSelectApplicationState((state: IApplicationStoreState) => state.siteManager); + const dispatch = useApplicationDispatch(); + const getCountryList = async () => dispatch(loadAllCountriesAsync()); + const getAreaList = async (areaId: string) => dispatch(loadAllAreasByAreaIdAsync(areaId)); + const getSiteList = async (areaId: string) => dispatch(loadAllSitesByAreaIdAsync(areaId)); + const getCategoryList = async (siteId: string) => dispatch(loadAllCategoriesBySiteIdAsync(siteId)); + const getSiteDetailsBySiteId = async (siteId: string) => dispatch(loadAllSiteBySiteIdAsync(siteId)); + const getCategoryItemsBySiteId = async (siteId: string, categoryName: string) => dispatch(loadAllCategoryItemsBySiteIdAsync(siteId, categoryName)); + const getSiteDetailsWithContactsBySiteId = async (siteId: string) => dispatch(loadAllSiteDetailsBySiteIdAsync(siteId)); + const searchSiteIdTrail = async (searchValue: string) => dispatch(loadTreeSiteSearchBySiteIdOrNameAsync(searchValue)); + + const [state, setState] = useState({ + nodes: [], + parentChildMap: new Map(), + selectedSite: { + id: '', + uuid: '', + name: '', + amslInMeters: '', + type: '', + 'area-id': '', + 'item-count': 0, + address: { + streetAndNr: '', + city: '', + zipCode: '', + country: '', + }, + operator: '', + location: { + lon: '', + lat: '', + }, + }, + expanded: [], + selected: '', + isSite: false, + isCategory: false, + isCategoryExpanded: false, + sitesList: [], + treeItemName: '', + siteManagerCategoryItems: [{ + name: '', + url: '', + 'last-update': '', + }], + siteManagerSiteOrderItems: [{ + assignedUser: '', + state: '', + note: '', + tasks: [{ + type: '', + description: '', + completed: false, + }], + }], + categoryList: [], + categoryIdMap: {}, + selectedCategoryLinkType: '', + searchLeafParent: [], + selectedSearchItem: '', + siteId: '', + isDialogOpen: false, + openDialog: false, + dialogMessage: '', + isFromSearch: false, + isFocused: false, + isReady: false, + isLoadingTreeView: false, + isLoadingDisplayView: false, + searchSiteSelected: { + id: '', + areaCount: 0, + isCategory: false, + isNodeSelected: false, + isSite: false, + name: '', + parentId: '', + siteCount: 0, + }, + }); + + const treeRef = useRef(null); + const categoriesWithDownloadOption = ['mtssr', 'order', 'Pictures']; + const categoriesForTables = ['microwave', 'node', 'fibre']; + const classes = styles(); + const MAX_SITES_COUNT = 100; + + const categoriesIdDisplayNameMap = new Map([ + ['order', 'Site Orders'], + ['mtssr', 'TSS-Reports'], + ['Pictures', 'Photos'], + ['node', 'Nodes'], + ['fibre', 'Optical Links'], + ['microwave', 'MW Links'], + ]); + + const categoriesDisplayNameIdMap = new Map([ + ['Site Orders', 'order'], + ['TSS-Reports', 'mtssr'], + ['Photos', 'Pictures'], + ['Nodes', 'node'], + ['Optical Links', 'fibre'], + ['MW Links', 'microwave'], + ]); + + /** + * Function to get the reverse trail of parents to help find the right trail + * to append or identify the parent for a child/the children + */ + const getTrail = (nodeId: string, trail: string[]) => { + trail.push(nodeId); + if (state.parentChildMap.has(nodeId.toString())) { + const superParentId = state.parentChildMap.get(nodeId.toString()) + ''; + getTrail(superParentId, trail); + } + }; + + /** + * Function to add the sites to a given area + */ + const addSiteToLeafArea = (nodes: ITreeViewItem[], site: ITreeViewItem) => { + for (let iter = 0; iter < nodes.length; iter++) { + const node = nodes[iter]; + // eslint-disable-next-line @typescript-eslint/no-unused-expressions + node.children ? node.children : node.children = []; + if (node.id === site.parentId) { + node.children.push(site); + state.parentChildMap.set(site.id.toString(), node.id.toString()); + break; + } else { + addSiteToLeafArea(node.children, site); + } + } + }; + + /** + * Add tree items of sites under area + */ + const addTreeItemSites = (parentId: string, sites: Sites[]) => { + const maxSize = sites.length <= MAX_SITES_COUNT ? sites.length : MAX_SITES_COUNT; + const siteCountTotal = sites.length; + if (sites.length > MAX_SITES_COUNT) { + window.alert(`There are ${siteCountTotal} sites in this area, showing just ${MAX_SITES_COUNT} sites`); + } + const nodes: ITreeViewItem[] = []; + for (var iter = 0; iter < maxSize; iter++) { + const site = sites[iter]; + let node: ITreeViewItem = { + id: site.id.toString(), + name: site.id, + isSite: true, + isCategory: false, + parentId: parentId, + siteCount: site['item-count'], + areaCount: 0, + isNodeSelected: false, + }; + state.sitesList.push(site.id.toString()); + state.parentChildMap.set(node.id.toString(), node.parentId.toString()); + nodes.push(node); + } + return nodes; + }; + + /** + * Add categories to site in the tree items + */ + const addTreeItemCategories = (siteId: string, categories: SiteManagerCategories[]) => { + const nodes: ITreeViewItem[] = categories.map(category => { + const categoryId: string = category.id; + const categoryName: string = categoriesIdDisplayNameMap.get(category.id) + ''; + let node: ITreeViewItem = { + id: siteId + '#' + categoryId, + name: categoryName, + isSite: false, + isCategory: true, + parentId: siteId, + siteCount: 0, + areaCount: 0, + isNodeSelected: false, + }; + state.categoryIdMap[node.id] = node; + state.categoryList.push(node.id); + state.parentChildMap.set(node.id.toString(), siteId.toString()); + return node; + }); + return nodes; + }; + + /** + * Add tree items of areas under area + */ + const addTreeItemAreas = (areas: SiteManagerAreas[], isSearch?: boolean, + selectedSite?: string, searchValue?: string): ITreeViewItem[] => { + const nodes: ITreeViewItem[] = areas.map(area => { + let node: ITreeViewItem = { + id: area.id.toString(), + name: area.name, + isSite: false, + isCategory: false, + treeLevel: area['tree-level'], + parentId: area['parent-id'] + '', + areaCount: area['area-count'] ? area['area-count'] : 0, + siteCount: area['site-count'] ? area['site-count'] : 0, + siteLevel: area['site-level'], + isNodeSelected: false, + }; + if (area['parent-id']) { + state.parentChildMap.set(node.id.toString(), node.parentId.toString()); + } + if (isSearch) { + state.expanded.push(area.id.toString()); + // eslint-disable-next-line no-param-reassign + selectedSite = area.id == searchValue ? area.id.toString() : ''; + if (area.areas) { + node.children = addTreeItemAreas(area.areas, isSearch, selectedSite, searchValue); + } else { + state.searchLeafParent.push(area.id.toString()); + } + } + return node; + }); + return nodes; + }; + + const setOpenDialog = async (openDialog: boolean, dialogMessage: string, isFromSearch: boolean) => { + setState({ + ...state, + openDialog: openDialog, + dialogMessage: dialogMessage, + isFromSearch: isFromSearch, + }); + }; + + /** + * Function to append children to the right parent node by navigating through the tree list + */ + const appendChildrenToParentObject = async (nodes: ITreeViewItem[], trail: string[], forceUpdateChildren: boolean) => { + const searchId = trail.pop(); + if (searchId === undefined) + return nodes; + for (let i = 0; i < nodes.length; i++) { + const node = nodes[i]; + if ((node.id.toString()) == searchId) { + if (node.isSite && forceUpdateChildren) { + node.children = []; + } + const children = node.children ? node.children : []; + if (children.length > 0) { + await appendChildrenToParentObject(children, trail, forceUpdateChildren); + } else { + if (node.siteLevel && node.areaCount == 0) { + await getSiteList(searchId).then((sitesList: any) => { + if (sitesList.isError) { + setOpenDialog(true, sitesList.errorMessage, false); + return; + } + const childNodes = addTreeItemSites(searchId, sitesList.sites); + node.children = childNodes; + }); + } else if (node.isSite) { + await getCategoryList(searchId).then((categories: any) => { + const childNodes = addTreeItemCategories(searchId, categories); + node.children = childNodes; + }); + } else if (!node.isCategory) { + await getAreaList(searchId).then((areas: any) => { + const childNodes = addTreeItemAreas(areas); + node.children = childNodes; + }); + } + } + break; + } + } + return nodes; + }; + + /** + * Function to expand or collapse tree item + * If child items are not loaded, it calls the async function to load the child items + */ + const toggleTreeItems = async (nodeId: string, expanded: boolean) => { + const strNodeId: string = nodeId.toString(); + const trail: string[] = []; + if (expanded) { + const expandedTree = state.expanded.filter(item => item !== strNodeId); + setState({ + ...state, + expanded: expandedTree, + isFocused: false, + }); + return; + } + getTrail(strNodeId, trail); + await appendChildrenToParentObject(state.nodes, trail, false).then((nodes) => { + setState({ + ...state, + nodes: nodes, + isFocused: false, + }); + state.expanded.push(strNodeId); + }); + }; + + /** + * Clear selected item during search + */ + const clearSelection = async (nodes: ITreeViewItem[], trail: string[], selectedItem: string) => { + const searchId = trail.pop(); + if (searchId === undefined) + return; + for (let i = 0; i < nodes.length; i++) { + const node = nodes[i]; + if (node.id === searchId) { + if (searchId == selectedItem) { + setState({ + ...state, + selectedSearchItem: '', + isFocused: false, + }); + node.isNodeSelected = false; + return; + } + const children = node.children ? node.children : []; + if (children.length > 0) { + clearSelection(children, trail, selectedItem); + } + return; + } + } + }; + + /** + * Function to retrieve the details of site or category on click of the tree item + */ + const retrieveDetails = async (siteId: string, isCallFromSearch: boolean) => { + const selectedSearchItem = state.selectedSearchItem + ''; + if (!isCallFromSearch && (selectedSearchItem.length > 0)) { + const trail: string[] = []; + getTrail(state.selectedSearchItem, trail); + await clearSelection(state.nodes, trail, state.selectedSearchItem); + } + const returnedSiteValue = state.sitesList.find(site => site.toString() == siteId.toString()); + if (returnedSiteValue && state.selectedSite.id != returnedSiteValue) { + const trail: string[] = []; + getTrail(siteId + '', trail); + await appendChildrenToParentObject(state.nodes, trail, true).then((nodes) => { + setState({ ...state, nodes: nodes }); + state.expanded.push(siteId + ''); + }); + await getSiteDetailsBySiteId(siteId).then((site: any) => { + setState({ + ...state, + selectedSite: site, + isSite: true, + isCategory: false, + isCategoryExpanded: false, + isLoadingDisplayView: false, + treeItemName: site.name, + selected: site.id, + }); + }); + } else if (state.selectedSite.id == returnedSiteValue) { + } else { + const returnedCategoryValue = state.categoryList.find((category) => category === siteId); + if (returnedCategoryValue) { + const ids = siteId.split('#'); + if (state.selectedSite.id != ids[0]) { + await getSiteDetailsBySiteId(ids[0]).then((site: any) => { + setState({ + ...state, + selectedSite: site, + isSite: true, + isLoadingDisplayView: false, + treeItemName: site.name, + }); + }); + const trail: string[] = []; + getTrail(siteId + '', trail); + appendChildrenToParentObject(state.nodes, trail, true).then((nodes) => { + setState({ ...state, nodes: nodes }); + state.expanded.push(siteId + ''); + }); + } + if (categoriesWithDownloadOption.includes(ids[1])) { + await getCategoryItemsBySiteId(ids[0], ids[1]).then(async (categoryItems: any) => { + const categoryName = categoriesIdDisplayNameMap.get(ids[1]) + ''; + if (categoryName.includes('Site Orders')) { + await getSiteDetailsWithContactsBySiteId(siteId).then((siteOrderItems: any) => { + setState({ + ...state, + isCategory: true, + isCategoryExpanded: true, + siteManagerCategoryItems: categoryItems, + siteManagerSiteOrderItems: siteOrderItems, + treeItemName: categoryName, + selected: siteId, + selectedCategoryLinkType: '', + }); + }); + } else { + setState({ + ...state, + isCategory: true, + isCategoryExpanded: true, + siteManagerCategoryItems: categoryItems, + treeItemName: categoryName, + selected: siteId, + selectedCategoryLinkType: '', + }); + } + }); + } else if (categoriesForTables.includes(ids[1])) { + // const selectedCategory = state.categoryIdMap[siteId]; + const categoryName = categoriesIdDisplayNameMap.get(ids[1]) + ''; + setState({ + ...state, + isCategory: true, + isCategoryExpanded: true, + treeItemName: categoryName, + selected: siteId, + selectedCategoryLinkType: ids[1] ? ids[1] : '', + }); + } + } else { + setState({ + ...state, + selectedSite: { + id: '', + uuid: '', + name: '', + amslInMeters: '', + type: '', + 'area-id': '', + 'item-count': 0, + address: { + streetAndNr: '', + city: '', + zipCode: '', + country: '', + }, + operator: '', + location: { + lon: '', + lat: '', + }, + }, + isSite: false, + isCategory: false, + isCategoryExpanded: false, + siteManagerCategoryItems: [{ + name: '', + url: '', + 'last-update': '', + }], + treeItemName: '', + selected: '', + selectedSearchItem: '', + selectedCategoryLinkType: '', + isFocused: false, + isReady: false, + isLoadingDisplayView: false, + }); + } + } + }; + + /** + * Function to render the expandable tree item or leaf tree item + */ + const renderChildren = (node: ITreeViewItem) => { + const strNodeId = node.id.toString(); + return node.isCategory || node.isSite && node.siteCount == 0 || node.siteLevel && node.siteCount == 0 || + !node.isSite && !node.siteLevel && node.areaCount == 0 ? + ( + } endIcon={} > + {Array.isArray(node.children) + ? node.children.map((child) => renderChildren(child)) + : null} + + ) + : ( + } endIcon={}> + {Array.isArray(node.children) + ? node.children.map((child) => renderChildren(child)) + : null} + + ); + }; + + /** + * Function to scroll and Focus on the searched item on click of search + */ + const scrollAndFocus = (searchValue: string) => { + if (state.isReady && searchValue && searchValue !== '') { + const matchingNode = treeRef.current?.querySelector(`[id="site-manager-treeview-${searchValue}"]`); + if (matchingNode) { + state.isFocused = true; + state.isReady = false; + matchingNode.scrollIntoView({ behavior: 'smooth', block: 'center' }); + } + } + }; + + /** + * Function to handle on click of search + */ + const handleSearch = async (event: React.SyntheticEvent, searchValue: string, searchCategoryName?: string) => { + event.preventDefault(); + setState((prevState) => ({ + ...prevState, + nodes: [], + parentChildMap: new Map(), + selectedSite: { + id: '', + uuid: '', + name: '', + amslInMeters: '', + type: '', + 'area-id': '', + 'item-count': 0, + address: { + streetAndNr: '', + city: '', + zipCode: '', + country: '', + }, + operator: '', + location: { + lon: '', + lat: '', + }, + }, + expanded: [], + selected: '', + isSite: false, + isCategory: false, + isCategoryExpanded: false, + searchLeafParent: [], + treeItemName: '', + categoryIdMap: {}, + selectedCategoryLinkType: '', + selectedSearchItem: '', + siteId: '', + isDialogOpen: false, + isFocused: false, + isReady: false, + isLoadingTreeView: true, + searchSiteSelected: { + id: '', + areaCount: 0, + isCategory: false, + isNodeSelected: false, + isSite: false, + name: '', + parentId: '', + siteCount: 0, + }, + })); + await searchSiteIdTrail(searchValue).then(async (searchResult: any) => { + if (searchResult.isError) { + // alert(searchResult.errorMessage); + setOpenDialog(true, searchResult.errorMessage, true); + return; + } + let selected: string = ''; + let selectedSearchItem = ''; + const areas = searchResult.areas ? searchResult.areas : []; + const nodes = await addTreeItemAreas(areas, true, selected, searchValue); + const sites = searchResult.sites ? searchResult.sites : []; + let searchSiteSelected = state.searchSiteSelected; + if (sites.length > 0) { + let sitesCount = sites.length; + if (sites.length > MAX_SITES_COUNT) { + window.alert('There are more than 100 Sites in this area of searched site, showing first 100'); + sitesCount = MAX_SITES_COUNT; + } + for (let iter = 0; iter < sitesCount; iter++) { + const siteItr = sites[iter]; + let isNodeSelected = false; + if (siteItr.id == searchValue) { + selected = siteItr.id; + selectedSearchItem = siteItr.id; + isNodeSelected = true; + } + if (siteItr.name) { + if (siteItr.name.toUpperCase() == searchValue.toUpperCase()) { + selected = siteItr.id; + selectedSearchItem = siteItr.id; + isNodeSelected = true; + } + } + const site: ITreeViewItem = { + id: siteItr.id, + name: siteItr.id, + isSite: true, + isCategory: false, + parentId: siteItr['area-id'], + siteCount: siteItr['item-count'], + areaCount: 0, + isNodeSelected: isNodeSelected, + }; + if (isNodeSelected) + searchSiteSelected = site; + state.sitesList.push(site.id); + addSiteToLeafArea(nodes, site); + } + } + state.nodes = nodes; + state.selected = selected; + state.selectedSearchItem = selectedSearchItem; + state.isReady = true; + state.isLoadingTreeView = false; + state.isLoadingDisplayView = true; + state.searchSiteSelected = searchSiteSelected; + }); + await retrieveDetails(state.selected, true); + if (searchCategoryName && categoriesDisplayNameIdMap.get(searchCategoryName)) { + const categoryId = state.selected + '#' + categoriesDisplayNameIdMap.get(searchCategoryName); + setState((prevState) => ({ + ...prevState, + selected: categoryId, + })); + await retrieveDetails(categoryId, true); + state.searchSiteSelected.isNodeSelected = false; + state.categoryIdMap[categoryId].isNodeSelected = true; + await scrollAndFocus(categoryId); + } + scrollAndFocus(state.selectedSearchItem); + }; + + /** + * Function to refresh the site manager view to its initial state + */ + const handleRefresh = async () => { + setState({ + ...state, + nodes: [], + parentChildMap: new Map(), + expanded: [], + selected: '', + isSite: false, + isCategory: false, + isCategoryExpanded: false, + sitesList: [], + categoryList: [], + categoryIdMap: {}, + selectedSite: { + id: '', + uuid: '', + name: '', + amslInMeters: '', + type: '', + 'area-id': '', + 'item-count': 0, + address: { + streetAndNr: '', + city: '', + zipCode: '', + country: '', + }, + operator: '', + location: { + lon: '', + lat: '', + }, + }, + siteManagerCategoryItems: [{ + name: '', + url: '', + 'last-update': '', + }], + searchLeafParent: [], + treeItemName: '', + selectedCategoryLinkType: '', + siteId: '', + isDialogOpen: false, + openDialog: false, + dialogMessage: '', + isFromSearch: false, + isFocused: false, + isReady: false, + }); + await getCountryList().then((countries: any) => { + const nodes = addTreeItemAreas(countries); + setState({ + ...state, + nodes: nodes, + isFocused: false, + }); + }); + }; + + /** + * Function to open the Create TSS report dialog on click of the button in Site Actions category + */ + const onOpenCreateTSSReportOrderDialog = () => { + setState({ + ...state, + isDialogOpen: true, + }); + }; + /** + * Function to open the Close Create TSS report dialog in Site Actions category + */ + const onCloseCreateTSSReportOrderDialog = async () => { + const siteIdItem = state.selected.toString(); + const selectedSiteId = siteIdItem.split('#')[0] + ''; + const trail: string[] = []; + getTrail(selectedSiteId, trail); + await appendChildrenToParentObject(state.nodes, trail, true).then((nodes) => { + setState({ ...state, nodes: nodes }); + state.expanded.push(selectedSiteId); + }); + setState({ + ...state, + siteId: '', + isDialogOpen: false, + }); + }; + + const handleOnCloseOfDialog = (event: React.SyntheticEvent, isFromSearch: boolean) => { + setOpenDialog(false, '', isFromSearch); + if (isFromSearch) + handleRefresh(); + }; + + const onError = () => { + setState({ + ...state, + siteId: '', + isDialogOpen: false, + }); + }; + + /** + * On initial load/mount of site manager application + */ + useEffect(() => { + const fetchData = async () => { + try { + const countries: any = await getCountryList(); + const nodes = addTreeItemAreas(countries); + setState(prevState => ({ + ...prevState, + nodes: nodes, + isFocused: false, + })); + } catch (error) { + } + }; + fetchData(); + }, []); + + return ( +
+
+ +
+
+ {state.isLoadingTreeView && ( +
+ +
+ )} + +
+
+ {state.selected.length > 0 ? + } + defaultExpandIcon={} + defaultEndIcon={} + defaultExpanded={state.expanded} + expanded={state.expanded} + > + {state.nodes.map((item) => renderChildren(item))} + + : } + defaultExpandIcon={} + defaultEndIcon={} + defaultExpanded={state.expanded} + expanded={state.expanded} + > + {state.nodes.map((item) => renderChildren(item))} + + } +
+
+ +
+ {state.isLoadingDisplayView ? ( +
+ +
+ ) : ( + <> + {state.isSite ? +
+ + } + aria-label="site-details-panel-header" + id="site-detail-panel-header" + > + Site Details + + +
+ { + const networkMapBaseUrl = window.location.pathname.split('#')[0]; + const siteId = state.selectedSite.id + ''; + const url = `${networkMapBaseUrl}#/network?siteId=${siteId}`; + window.open(url); + }} + > + + +
+ +
+
+ + } + aria-label="site-configuration-panel-header" + id="site-detail-panel-header" + > + Site Configuration + + + + + + + } + aria-label="site-action-panel-header" + id="site-detail-panel-header" + > + Site Actions + + +
+ +
+
+
+ + } + aria-label="site-additional-information-panel-header" + id="site-additional-information-panel-header" + > + Site Additional Information + + + + + +
+ : <>} + {state.isCategory ? + + } + aria-label="links-panel-header" + id="location-panel-header" + > + Category Details + + + + + + + + + + + +
{state.treeItemName}
+ <> + {state.treeItemName.includes('TSS') ? + + : state.treeItemName.includes('Site Orders') ? + + : state.treeItemName.includes('Photos') ? + + : state.treeItemName.includes('Links') ? + + : state.treeItemName === 'Nodes' ? + + : <> + } + +
+
+
+ : <>} + + )} +
+
+ {state.isDialogOpen ? ( + + ) : <>} + setState({ ...state, openDialog: value })} + dialogMessage={state.dialogMessage} + onClose={handleOnCloseOfDialog} + isFromSearch={state.isFromSearch} + /> +
+
+ ); +}; + +export const SiteManagerTreeView = SiteManagerTreeViewComponent; +export default SiteManagerTreeView; \ No newline at end of file diff --git a/features/sdnr/odlux/odlux/apps/siteManagerApp/src/components/siteOrdersTable.tsx b/features/sdnr/odlux/odlux/apps/siteManagerApp/src/components/siteOrdersTable.tsx new file mode 100644 index 0000000..0358862 --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/siteManagerApp/src/components/siteOrdersTable.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, { useEffect, useState } from 'react'; + +import { faDownload } from '@fortawesome/free-solid-svg-icons'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import IconButton from '@mui/material/IconButton'; +import makeStyles from '@mui/styles/makeStyles'; + +import { ColumnType, MaterialTable, MaterialTableCtorType } from '../../../../framework/src/components/material-table'; + +import { SiteManagerCategoryItems, SiteManagerSiteOrderItemsDetails } from '../models/siteManager'; + +const styles = makeStyles({ + root: { + '&:hover': { + color: 'blue', + }, + }, +}); + +const SiteOrders = MaterialTable as MaterialTableCtorType; + +type joinSiteOrdersDetailsResult = { + name: string | undefined; + url: string; + assignedUser: string; + state: string; + note: string; + type: string; + description: string; + completed: boolean; +}; + +type SiteOrdersComponentProps = { + siteOrderItems: SiteManagerCategoryItems; + siteOrderItemDetails: SiteManagerSiteOrderItemsDetails; +}; + +const SiteOrdersViewComponent: React.FC = (props) => { + const [siteOrdersTableData, setSiteOrdersTableData] = useState([]); + const [isLoaded, setIsLoaded] = useState(false); + const classes = styles(); + + const downloadSiteOrdersReport = (url: string) => { + let fileName = url.substring( + url.lastIndexOf('/') + 1, + url.lastIndexOf('.'), + ); + fetch(url) + .then(resp => resp.blob()) + .then(blob => { + const reportUrl = window.URL.createObjectURL(blob); + const a = document.createElement('a'); + a.style.display = 'none'; + a.href = reportUrl; + a.download = fileName; + document.body.appendChild(a); + a.click(); + window.URL.revokeObjectURL(reportUrl); + alert('Report has downloaded!'); + }) + .catch(() => alert('oh no! something went wrong')); + }; + + const joinColumns = () => { + let siteOrdersItemList = props.siteOrderItems; + let siteOrdersDetailsList = props.siteOrderItemDetails; + let updatedSiteOrdersTableData: joinSiteOrdersDetailsResult[] = []; + + siteOrdersItemList.forEach(siteOrder => { + const name = siteOrder.name; + const userName = name.substring(name.lastIndexOf('-') + 1); + let indexDetails = 0; + for (; indexDetails < siteOrdersDetailsList.length; indexDetails++) { + const detail = siteOrdersDetailsList[indexDetails]; + if (userName === detail.assignedUser) { + updatedSiteOrdersTableData.push({ + name: name, + url: siteOrder.url, + assignedUser: userName, + state: detail.state, + note: detail.note, + type: detail.tasks[0]?.type, + description: detail.tasks[0]?.description, + completed: detail.tasks[0]?.completed, + }); + break; + } + } + siteOrdersDetailsList.splice(indexDetails, 1); + }); + siteOrdersItemList.splice(0, siteOrdersItemList.length); + setSiteOrdersTableData(updatedSiteOrdersTableData); + setIsLoaded(true); + }; + + useEffect(() => { + if (!isLoaded) { + joinColumns(); + } + }, [props.siteOrderItems, props.siteOrderItemDetails, isLoaded]); + + return ( + <> + {isLoaded ? ( + { + if (rowData.url === '') { + return (<>{rowData.name}); + } else { + return ( {rowData.name} ); + } + }, + }, + { property: 'state', title: 'State', type: ColumnType.text }, + { property: 'description', title: 'Current Task', type: ColumnType.text }, + { + property: 'url', title: 'Action', type: ColumnType.custom, customControl: ({ rowData }) => { + if (rowData.url === '') { + return ( ); + } else { + return ( + { event.stopPropagation(); downloadSiteOrdersReport(rowData.url); }} /> + ); + } + }, + }, + ]} idProperty='id' rows={siteOrdersTableData} > + ) : ( +
Loading...
+ )} + + ); +}; + + +export const SiteOrdersView = SiteOrdersViewComponent; +export default SiteOrdersView; \ No newline at end of file diff --git a/features/sdnr/odlux/odlux/apps/siteManagerApp/src/components/siteTable.tsx b/features/sdnr/odlux/odlux/apps/siteManagerApp/src/components/siteTable.tsx new file mode 100644 index 0000000..0cf9eb6 --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/siteManagerApp/src/components/siteTable.tsx @@ -0,0 +1,122 @@ +/** + * ============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, { useEffect, useState } from 'react'; + +import Map from '@mui/icons-material/Map'; +import Refresh from '@mui/icons-material/Refresh'; +import { Divider, MenuItem, Typography } from '@mui/material'; + +import { NavigateToApplication } from '../../../../framework/src/actions/navigationActions'; +import { ColumnType, MaterialTable, MaterialTableCtorType } from '../../../../framework/src/components/material-table'; +import { useSelectApplicationState, useApplicationDispatch } from '../../../../framework/src/flux/connect'; +import { IApplicationStoreState } from '../../../../framework/src/store/applicationStore'; + +import { SearchValueAction } from '../actions/siteManagerSiteSearchAction'; +import { createSiteTableActions, createSiteTableProperties } from '../handlers/siteTableHandler'; +import { SiteDetails } from '../models/siteDetails'; +import RefreshSiteTableDialog, { RefreshSiteTableDialogMode } from './refreshSiteTableDialog'; + + +const SiteTable = MaterialTable as MaterialTableCtorType; + +let initialSorted = false; + +const SiteTableComponent: React.FC = () => { + const siteTableProperties = useSelectApplicationState((state: IApplicationStoreState) => createSiteTableProperties(state)); + + const dispatch = useApplicationDispatch(); + const siteTableActions = createSiteTableActions(dispatch); + const navigateToApplication = (applicationName: string, path?: string) => dispatch(new NavigateToApplication(applicationName, path)); + + const [refreshSiteTableEditorMode, setRefreshSiteTableEditorMode] = useState(RefreshSiteTableDialogMode.None); + + const setSearchTerm = (event: React.SyntheticEvent, value: string) => dispatch(new SearchValueAction(value)); + + const getContextMenu = (rowData: SiteDetails) => { + return [ + { + navigateToApplication('siteManager', '/treeview/' + rowData.id); + setSearchTerm(event, rowData.id + ''); + }}> + View in Treeview + , + , + { + const siteId = rowData.id; + const baseUrl = window.location.pathname.split('#')[0]; + const url = `${baseUrl}#/network?siteId=${siteId}`; + window.open(url); + }}> + Show on Map + , + ]; + }; + + useEffect(() => { + if (!initialSorted) { + initialSorted = true; + siteTableActions.onHandleExplicitRequestSort('id', 'asc'); + } else { + siteTableActions.onRefresh(); + } + }, []); + + const refreshSiteTableAction = [{ + icon: Refresh, tooltip: 'Refresh Site Table', ariaLabel: 'refresh', onClick: () => { + setRefreshSiteTableEditorMode(RefreshSiteTableDialogMode.RefreshSiteTableTable); + }, + }, + ...siteTableProperties.showFilter ? [{ + icon: Map, tooltip: 'Show on map', ariaLabel: 'showOnMap', onClick: async () => { + const siteId = siteTableProperties.rows[0].id; + const baseUrl = window.location.pathname.split('#')[0]; + const url = `${baseUrl}#/network?siteId=${siteId}`; + window.open(url); + }, + }] : [], + ]; + + return ( + <> + { + return getContextMenu(rowData); + }} > + + setRefreshSiteTableEditorMode(RefreshSiteTableDialogMode.None)} + /> + + ); +}; + +export const SiteTableView = SiteTableComponent; +export default SiteTableView; \ No newline at end of file diff --git a/features/sdnr/odlux/odlux/apps/siteManagerApp/src/components/treeItem.tsx b/features/sdnr/odlux/odlux/apps/siteManagerApp/src/components/treeItem.tsx new file mode 100644 index 0000000..49c1111 --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/siteManagerApp/src/components/treeItem.tsx @@ -0,0 +1,107 @@ +/* eslint-disable react/prop-types */ +/** + * ============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 { TreeItemContentProps, useTreeItem } from '@mui/lab/TreeItem'; +import Typography from '@mui/material/Typography'; +import clsx from 'clsx'; +import { useSelectApplicationState } from '../../../../framework/src/flux/connect'; +import { IApplicationStoreState } from '../../../../framework/src/store/applicationStore'; + +type TreeItemComponentProps = TreeItemContentProps; + +interface CustomContentProps extends TreeItemComponentProps { + toggleTreeItems: (nodeId: string, expanded: boolean) => Promise; + retrieveDetails: (siteId: string, isCallFromSearch: boolean) => void; + isNodeSelected: boolean; +} + +// Custom TreeItem content +export const CustomContent = React.forwardRef(function CustomContent( + props: CustomContentProps, + ref, +) { + const { + classes, + className, + label, + nodeId, + icon: iconProp, + expansionIcon, + displayIcon, + toggleTreeItems, + retrieveDetails, + isNodeSelected, + + } = props; + + const { + disabled, + expanded, + selected, + focused, + handleExpansion, + handleSelection, + preventSelection, + } = useTreeItem(nodeId); + useSelectApplicationState((state: IApplicationStoreState) => state.siteManager); + + const icon = iconProp || expansionIcon || displayIcon; + + const handleMouseDown = (event: React.MouseEvent) => { + preventSelection(event); + }; + + const handleExpansionClick = async (event: React.MouseEvent) => { + await toggleTreeItems(nodeId, expanded).then(() => { + handleExpansion(event); + }); + handleExpansion(event); + }; + + const handleSelectionClick = (event: React.MouseEvent) => { + retrieveDetails(nodeId, false); + handleSelection(event); + }; + + return ( +
} + > +
+ {icon} +
+ +
{label}
+
+
+ ); +}); + diff --git a/features/sdnr/odlux/odlux/apps/siteManagerApp/src/components/tssReportsTable.tsx b/features/sdnr/odlux/odlux/apps/siteManagerApp/src/components/tssReportsTable.tsx new file mode 100644 index 0000000..fb3d99f --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/siteManagerApp/src/components/tssReportsTable.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, { useState } from 'react'; + +import { faDownload } from '@fortawesome/free-solid-svg-icons'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import IconButton from '@mui/material/IconButton'; +import makeStyles from '@mui/styles/makeStyles'; + +import { ColumnType, MaterialTable, MaterialTableCtorType } from '../../../../framework/src/components/material-table'; + +const styles = makeStyles({ + root: { + '&:hover': { + color: 'blue', + }, + }, +}); + +const TSSReports = MaterialTable as MaterialTableCtorType; + +type TSSReportsComponentProps = { + item: { + name: string; + url: string; + 'last-update': string; + }[]; +}; + +const TSSReportsComponent: React.FC = (props) => { + const [item] = useState(props.item); + + const classes = styles(); + + const downloadTSSReport = (url: string) => { + let fileName = url.substring( + url.lastIndexOf('/') + 1, + ); + fetch(url) + .then(resp => resp.blob()) + .then(blob => { + const reportUrl = window.URL.createObjectURL(blob); + const link = document.createElement('a'); + link.style.display = 'none'; + link.href = reportUrl; + link.setAttribute('download', fileName); + document.body.appendChild(link); + link.click(); + window.URL.revokeObjectURL(reportUrl); + alert('Report has downloaded!'); + }) + .catch(() => alert('oh no! something went wrong')); + }; + + return ( + <> + { + if (rowData.url === '') { + return (<>{rowData.name}); + } else { + return ( {rowData.name} ); + } + }, + }, + { + property: 'url', title: 'Action', type: ColumnType.custom, customControl: ({ rowData }) => { + if (rowData.url === '') { + return ( ); + } else { + return ( + { event.stopPropagation(); downloadTSSReport(rowData.url); }} /> + ); + } + }, + }, + ]} idProperty='id' rows={item} > + + + ); +}; + +export const TSSReportsView = TSSReportsComponent; +export default TSSReportsView; diff --git a/features/sdnr/odlux/odlux/apps/siteManagerApp/src/config.ts b/features/sdnr/odlux/odlux/apps/siteManagerApp/src/config.ts new file mode 100644 index 0000000..4052e0f --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/siteManagerApp/src/config.ts @@ -0,0 +1,50 @@ +/** + * ============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 const URL_API = '/topology/network'; +export const SITEDOC_URL = '/sitedoc'; +export const URL_TILE_API = '/tiles'; // http://tile.openstreetmap.org can be used for local testing, never commit with tile url changed! /tiles + + +export const OSM_STYLE = { + 'version': 8, + 'sources': { + 'raster-tiles': { + 'type': 'raster', + 'tiles': [ + URL_TILE_API + '/{z}/{x}/{y}.png', + ], + 'tileSize': 256, + 'attribution': + '© OpenStreetMap contributors', + }, + }, + 'layers': [ + { + 'id': 'simple-tiles', + 'type': 'raster', + 'source': 'raster-tiles', + 'minZoom': 0, + 'maxZoom': 18, + }, + ], +}; + +export const URL_BASEPATH = 'network'; + + diff --git a/features/sdnr/odlux/odlux/apps/siteManagerApp/src/handlers/detailsReducer.ts b/features/sdnr/odlux/odlux/apps/siteManagerApp/src/handlers/detailsReducer.ts new file mode 100644 index 0000000..ef860eb --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/siteManagerApp/src/handlers/detailsReducer.ts @@ -0,0 +1,70 @@ +/* eslint-disable @typescript-eslint/default-param-last */ +/* eslint-disable no-param-reassign */ +/** + * ============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 { IActionHandler } from '../../../../framework/src/flux/action'; + +import { + AddToHistoryAction, ClearDetailsAction, ClearHistoryAction, ClearLoadedDevicesAction, FinishedLoadingDeviceListAction, InitializeLoadedDevicesAction, + IsBusyCheckingDeviceListAction, IsSitedocReachableAction, SelectElementAction, +} from '../actions/detailsAction'; +import { HistoryEntry } from '../models/historyEntry'; +import { link } from '../models/link'; +import { Device, Site } from '../models/site'; +import { Service } from '../models/topologyTypes'; + +export type DetailsStoreState = { + data: Site | link | Service | null; + history: HistoryEntry[]; + isBusyCheckingDeviceList: boolean; + checkedDevices: Device[]; + isSitedocReachable: boolean; +}; + +const initialState: DetailsStoreState = { + data: null, + history: [], + isBusyCheckingDeviceList: false, + checkedDevices: [], + isSitedocReachable: false, +}; + +export const DetailsReducer: IActionHandler = (state = initialState, action) => { + if (action instanceof SelectElementAction) { + state = Object.assign({}, state, { data: action.data }); + } else if (action instanceof ClearDetailsAction) { + state = Object.assign({}, state, { data: null }); + } else if (action instanceof AddToHistoryAction) { + state = Object.assign({}, state, { history: [...state.history, action.entry] }); + } else if (action instanceof ClearHistoryAction) { + state = Object.assign({}, state, { history: [] }); + } else if (action instanceof IsBusyCheckingDeviceListAction) { + state = Object.assign({}, state, { isBusyCheckingDeviceList: action.isBusy }); + } else if (action instanceof FinishedLoadingDeviceListAction) { + state = Object.assign({}, state, { checkedDevices: action.devices }); + } else if (action instanceof ClearLoadedDevicesAction) { + state = Object.assign({}, state, { checkedDevices: [] }); + } else if (action instanceof InitializeLoadedDevicesAction) { + state = Object.assign({}, state, { checkedDevices: action.devices }); + } else if (action instanceof IsSitedocReachableAction) { + state = Object.assign({}, state, { isSitedocReachable: action.isReachable }); + } + return state; +}; + diff --git a/features/sdnr/odlux/odlux/apps/siteManagerApp/src/handlers/deviceTableHandler.ts b/features/sdnr/odlux/odlux/apps/siteManagerApp/src/handlers/deviceTableHandler.ts new file mode 100644 index 0000000..628ec2a --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/siteManagerApp/src/handlers/deviceTableHandler.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 { DeviceDetails } from '../models/siteManager'; + +export interface IDeviceTableState extends IExternalTableState { } + +// create elastic search material data fetch handler +const deviceTableSearchHandler = createSearchDataHandler('device', true); + +export const { + actionHandler: deviceTableActionHandler, + createActions: createDeviceTableActions, + createProperties: createDeviceTableProperties, + createPreActions: createDeviceTablePreActions, + reloadAction: deviceTableReloadAction, + + // set value action, to change a value +} = createExternal(deviceTableSearchHandler, appState => appState.siteManager.deviceTable); + diff --git a/features/sdnr/odlux/odlux/apps/siteManagerApp/src/handlers/linkTableHandler.ts b/features/sdnr/odlux/odlux/apps/siteManagerApp/src/handlers/linkTableHandler.ts new file mode 100644 index 0000000..cf5312a --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/siteManagerApp/src/handlers/linkTableHandler.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 { LinkDetails } from '../models/siteManager'; + +export interface ILinkTableState extends IExternalTableState { } + +// create elastic search material data fetch handler +const linkTableSearchHandler = createSearchDataHandler('link', true); + +export const { + actionHandler: linkTableActionHandler, + createActions: createLinkTableActions, + createProperties: createLinkTableProperties, + createPreActions: createLinkTablePreActions, + reloadAction: linkTableReloadAction, + + // set value action, to change a value +} = createExternal(linkTableSearchHandler, appState => appState.siteManager.linkTable); + diff --git a/features/sdnr/odlux/odlux/apps/siteManagerApp/src/handlers/siteManagerAppRootHandler.ts b/features/sdnr/odlux/odlux/apps/siteManagerApp/src/handlers/siteManagerAppRootHandler.ts new file mode 100644 index 0000000..3bed706 --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/siteManagerApp/src/handlers/siteManagerAppRootHandler.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 { IActionHandler } from '../../../../framework/src/flux/action'; +import { combineActionHandler } from '../../../../framework/src/flux/middleware'; + +import { SetPanelAction } from '../actions/panelActions'; +import { SearchValueAction } from '../actions/siteManagerSiteSearchAction'; +import { PanelId } from '../models/panelId'; +import { DetailsReducer, DetailsStoreState } from './detailsReducer'; +import { deviceTableActionHandler, IDeviceTableState } from './deviceTableHandler'; +import { ILinkTableState, linkTableActionHandler } from './linkTableHandler'; +import { ManagementHandler, ManagementState } from './sitedocManagementHandler'; +import { siteManagerSiteSearchHandler } from './siteManagerSiteSearchHandler'; +import { areasActionHandler, IAreasState } from './siteManagerTreeHandler'; +import { ISiteTableState, siteTableActionHandler } from './siteTableHandler'; + +export interface ISiteManagerAppStoreState { + siteManagerTree: IAreasState; + searchSite: SearchValueAction; + sitedocManagement: ManagementState; + details: DetailsStoreState; + currentOpenPanel: PanelId; + siteTable: ISiteTableState; + linkTable: ILinkTableState; + deviceTable: IDeviceTableState; +} + +const currentOpenPanelHandler: IActionHandler = (state = null, action) => { + if (action instanceof SetPanelAction) { + state = action.panelId; + } + return state; +}; + + + +declare module '../../../../framework/src/store/applicationStore' { + interface IApplicationStoreState { + siteManager: ISiteManagerAppStoreState; + } +} + +const actionHandlers = { + siteManagerTree: areasActionHandler, + searchSite: siteManagerSiteSearchHandler, + sitedocManagement: ManagementHandler, + details: DetailsReducer, + currentOpenPanel: currentOpenPanelHandler, + siteTable: siteTableActionHandler, + linkTable: linkTableActionHandler, + deviceTable: deviceTableActionHandler, +}; + +export const siteManagerAppRootHandler = combineActionHandler(actionHandlers); +export default siteManagerAppRootHandler; + diff --git a/features/sdnr/odlux/odlux/apps/siteManagerApp/src/handlers/siteManagerSiteSearchHandler.ts b/features/sdnr/odlux/odlux/apps/siteManagerApp/src/handlers/siteManagerSiteSearchHandler.ts new file mode 100644 index 0000000..0c34b08 --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/siteManagerApp/src/handlers/siteManagerSiteSearchHandler.ts @@ -0,0 +1,74 @@ +/** + * ============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 { IActionHandler } from '../../../../framework/src/flux/action'; + +import { AllTreeSiteSearchBySiteIdOrNameLoadedAction, LoadTreeSiteSearchBySiteIdOrNameAction, SearchValueAction, SetBusyAction } + from '../actions/siteManagerSiteSearchAction'; +import { SearchSiteIdResult } from '../models/siteManager'; + +export type searchState = { + siteId: string; + categoryName: string; + searchValue: SearchSiteIdResult; + busy: boolean; + isLoadingData: boolean; +}; + +const initialState: searchState = { + siteId: '', + categoryName: '', + searchValue: { + areas: [], + sites: [], + isError: false, + errorMessage: '', + }, + busy: false, + isLoadingData: true, +}; + +export const siteManagerSiteSearchHandler: IActionHandler = (state = initialState, action) => { + if (action instanceof SearchValueAction) { + state = Object.assign({}, state, { siteId: action.siteId, categoryName: action.categoryName }); + } else if (action instanceof LoadTreeSiteSearchBySiteIdOrNameAction) { + state = { + ...state, + busy: true, + }; + } else if (action instanceof AllTreeSiteSearchBySiteIdOrNameLoadedAction) { + if (action.searchResult) { + state = { + ...state, + searchValue: { + areas: action.searchResult.areas, + sites: action.searchResult.sites, + isError: action.searchResult.isError, + errorMessage: action.searchResult.errorMessage, + }, + busy: false, + }; + } + } else if (action instanceof SetBusyAction) { + state = { + ...state, + isLoadingData: action.busy, + }; + } + return state; +}; \ No newline at end of file diff --git a/features/sdnr/odlux/odlux/apps/siteManagerApp/src/handlers/siteManagerTreeHandler.ts b/features/sdnr/odlux/odlux/apps/siteManagerApp/src/handlers/siteManagerTreeHandler.ts new file mode 100644 index 0000000..131f893 --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/siteManagerApp/src/handlers/siteManagerTreeHandler.ts @@ -0,0 +1,219 @@ +/** + * ============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 { + AllAreasByAreaIdLoadedAction, AllCategoriesBySiteIdALoadedAction, AllCategoryItemsBySiteIdALoadedAction, + AllCountriesLoadedAction, AllSiteBySiteIdALoadedAction, AllSitesByAreaIdLoadedAction, LoadAllAreasByAreaIdAction, + LoadAllCategoriesBySiteIdAction, LoadAllCategoryItemsBySiteIdAction, LoadAllCountriesAction, LoadAllSiteBySiteIdAction, + LoadAllSitesByAreaIdAction, SetBusyAction, LoadAllFrequencyPlanBySiteIdAction, AllFrequencyPlanBySiteIdALoadedAction, + LoadAllAvailableBandsAction, AllAvailableBandsALoadedAction, +} from '../actions/siteManagerTreeActions'; +import { LinkDetails, SiteManagerAreas, SiteManagerCategories, SiteManagerCategoryItems, SitesListResult } from '../models/siteManager'; +import { Site } from '../models/siteSearch'; + +export interface IAreasState { + countryList: SiteManagerAreas[]; + areaList: SiteManagerAreas[]; + sitesList: SitesListResult; + categoryList: SiteManagerCategories[]; + frequencyPlan: any[]; + bands: any[]; + siteDetails: Site; + categoryItems: SiteManagerCategoryItems; + linksList: LinkDetails[]; + busy: boolean; + isLoadingData: boolean; +} + +const areasStateInit: IAreasState = { + countryList: [], + areaList: [], + sitesList: { + sites: [], + isError: false, + errorMessage: '', + }, + categoryList: [], + frequencyPlan: [], + bands: [], + siteDetails: { + id: '', + uuid: '', + name: '', + 'area-id': '', + 'item-count': 0, + address: { + streetAndNr: '', + city: '', + zipCode: '', + country: '', + }, + operator: '', + location: { + lon: '', + lat: '', + }, + }, + categoryItems: [{ + name: '', + url: '', + 'last-update': '', + }], + linksList: [], + busy: false, + isLoadingData: true, +}; + +export const areasActionHandler: IActionHandler = (state = areasStateInit, action) => { + if (action instanceof LoadAllCountriesAction) { + state = { + ...state, + busy: true, + }; + } else if (action instanceof AllCountriesLoadedAction) { + if (action.countriesList) { + state = { + ...state, + countryList: action.countriesList, + busy: true, + }; + } + } else if (action instanceof LoadAllAreasByAreaIdAction) { + state = { + ...state, + busy: true, + }; + } else if (action instanceof AllAreasByAreaIdLoadedAction) { + if (action.areaList) { + state = { + ...state, + areaList: action.areaList, + busy: true, + }; + } + } else if (action instanceof LoadAllSitesByAreaIdAction) { + state = { + ...state, + busy: true, + }; + } else if (action instanceof AllSitesByAreaIdLoadedAction) { + if (action.sitesList) { + state = { + ...state, + sitesList: action.sitesList, + busy: true, + }; + } + } else if (action instanceof LoadAllCategoriesBySiteIdAction) { + state = { + ...state, + busy: true, + }; + } else if (action instanceof AllCategoriesBySiteIdALoadedAction) { + if (action.categoryList) { + state = { + ...state, + categoryList: action.categoryList, + busy: true, + }; + } + } else if (action instanceof LoadAllFrequencyPlanBySiteIdAction) { + state = { + ...state, + busy: true, + }; + } else if (action instanceof AllFrequencyPlanBySiteIdALoadedAction) { + if (action.freqPlanList) { + state = { + ...state, + frequencyPlan: action.freqPlanList, + busy: true, + }; + } + } else if (action instanceof LoadAllAvailableBandsAction) { + state = { + ...state, + busy: true, + }; + } else if (action instanceof AllAvailableBandsALoadedAction) { + if (action.bandsList) { + state = { + ...state, + bands: action.bandsList, + busy: true, + }; + } + } else if (action instanceof LoadAllSiteBySiteIdAction) { + state = { + ...state, + busy: true, + }; + } else if (action instanceof AllSiteBySiteIdALoadedAction) { + if (action.siteDetails) { + state = { + ...state, + siteDetails: { + id: action.siteDetails.id, + name: action.siteDetails.name, + uuid: action.siteDetails.uuid, + 'area-id': action.siteDetails['area-id'], + 'item-count': action.siteDetails['item-count'], + address: { + streetAndNr: action.siteDetails.address ? action.siteDetails.address.streetAndNr : '', + city: action.siteDetails.address ? action.siteDetails.address.city : '', + zipCode: action.siteDetails.address ? action.siteDetails.address.zipCode : '', + country: action.siteDetails.address ? action.siteDetails.address.country : '', + }, + operator: action.siteDetails.operator, + location: { + lon: action.siteDetails.location ? action.siteDetails.location.lon : '', + lat: action.siteDetails.location ? action.siteDetails.location.lat : '', + }, + }, + busy: true, + }; + } + } else if (action instanceof LoadAllCategoryItemsBySiteIdAction) { + state = { + ...state, + busy: true, + }; + } else if (action instanceof AllCategoryItemsBySiteIdALoadedAction) { + if (action.categoryItemList) { + state = { + ...state, + categoryItems: [{ + name: action.categoryItemList[0].name, + url: action.categoryItemList[0].url, + 'last-update': action.categoryItemList[0]['last-update'], + }], + busy: true, + }; + } + } else if (action instanceof SetBusyAction) { + state = { + ...state, + isLoadingData: action.busy, + }; + } + + return state; +}; \ No newline at end of file diff --git a/features/sdnr/odlux/odlux/apps/siteManagerApp/src/handlers/siteTableHandler.ts b/features/sdnr/odlux/odlux/apps/siteManagerApp/src/handlers/siteTableHandler.ts new file mode 100644 index 0000000..40a8b19 --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/siteManagerApp/src/handlers/siteTableHandler.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 { SiteDetails } from '../models/siteDetails'; + +export interface ISiteTableState extends IExternalTableState { } + +// create elastic search material data fetch handler +const siteTableSearchHandler = createSearchDataHandler('site', true); + +export const { + actionHandler: siteTableActionHandler, + createActions: createSiteTableActions, + createProperties: createSiteTableProperties, + reloadAction: siteTableReloadAction, + + // set value action, to change a value +} = createExternal(siteTableSearchHandler, appState => appState.siteManager.siteTable); + diff --git a/features/sdnr/odlux/odlux/apps/siteManagerApp/src/handlers/sitedocManagementHandler.ts b/features/sdnr/odlux/odlux/apps/siteManagerApp/src/handlers/sitedocManagementHandler.ts new file mode 100644 index 0000000..da123f0 --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/siteManagerApp/src/handlers/sitedocManagementHandler.ts @@ -0,0 +1,62 @@ +/** + * ============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 { ResetAction, SelectUserAction, SetAllUsersAction, SetTSSRAction, UpdateNoteAction, UpdateStateAction, UpdateTasks } from '../actions/sitedocManagementAction'; +import { SitedocOrderTask, UserListItem } from '../models/siteDocTypes'; + +const emptyTask: SitedocOrderTask = { type: '', description: '', completed: false }; + + +export type ManagementState = { + users: UserListItem[]; + selectedUser: string; + note: string; + tasks: SitedocOrderTask[]; + isTSSR: boolean; + state: string; +}; + +const initialState: ManagementState = { + users: [], + selectedUser: '', + state: 'OPEN', + note: '', + tasks: [emptyTask], + isTSSR: false, +}; + +export const ManagementHandler: IActionHandler = (state = initialState, action) => { + if (action instanceof SetAllUsersAction) { + state = { ...state, users: action.users }; + } else if (action instanceof SelectUserAction) { + state = { ...state, selectedUser: action.user }; + } else if (action instanceof SetTSSRAction) { + state = { ...state, isTSSR: action.isTSSR }; + } else if (action instanceof UpdateNoteAction) { + state = { ...state, note: action.note }; + } else if (action instanceof UpdateStateAction) { + state = { ...state, state: action.state }; + } else if (action instanceof UpdateTasks) { + state = { ...state, tasks: action.tasks }; + } else if (action instanceof ResetAction) { + state = { ...state, tasks: [emptyTask], selectedUser: '', note: '', isTSSR: false }; + } + return state; +}; \ No newline at end of file diff --git a/features/sdnr/odlux/odlux/apps/siteManagerApp/src/index.html b/features/sdnr/odlux/odlux/apps/siteManagerApp/src/index.html new file mode 100644 index 0000000..864e7f9 --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/siteManagerApp/src/index.html @@ -0,0 +1,30 @@ + + + + + + + + + SiteManager App + + + +
+ + + + + + + \ No newline at end of file diff --git a/features/sdnr/odlux/odlux/apps/siteManagerApp/src/models/count.ts b/features/sdnr/odlux/odlux/apps/siteManagerApp/src/models/count.ts new file mode 100644 index 0000000..8b9b1d8 --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/siteManagerApp/src/models/count.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 elementCount = { sites: string; links: string; services: string }; \ No newline at end of file diff --git a/features/sdnr/odlux/odlux/apps/siteManagerApp/src/models/historyEntry.ts b/features/sdnr/odlux/odlux/apps/siteManagerApp/src/models/historyEntry.ts new file mode 100644 index 0000000..e000e8d --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/siteManagerApp/src/models/historyEntry.ts @@ -0,0 +1,22 @@ +/** + * ============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 { link } from './link'; +import { Site } from './site'; + +export type HistoryEntry = { id: string; data: Site | link }; \ No newline at end of file diff --git a/features/sdnr/odlux/odlux/apps/siteManagerApp/src/models/link.ts b/features/sdnr/odlux/odlux/apps/siteManagerApp/src/models/link.ts new file mode 100644 index 0000000..004b46f --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/siteManagerApp/src/models/link.ts @@ -0,0 +1,49 @@ +/** + * ============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========================================================================== + */ + +type Antenna = { + id: string; + name: string; + height: number; + gain: number; +}; + +type LinkDetailLocation = { + lon: number; + lat: number; + siteId: number; + siteName: string | null; + amsl: number | null; + azimuth: number | null; + antenna: Antenna; + radio: { id: number; name: string }; + waveguide: { id: number; name: string }; +}; + +export type link = { + id: number; + uuid: string; + name: string; + operator: string; + length: number; + polarization: string; + type: string; + frequency: number | null; + siteA: LinkDetailLocation; + siteB: LinkDetailLocation; +}; \ No newline at end of file diff --git a/features/sdnr/odlux/odlux/apps/siteManagerApp/src/models/networkElementConnection.ts b/features/sdnr/odlux/odlux/apps/siteManagerApp/src/models/networkElementConnection.ts new file mode 100644 index 0000000..a939f9b --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/siteManagerApp/src/models/networkElementConnection.ts @@ -0,0 +1,30 @@ +/** + * ============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========================================================================== + */ + +export type NetworkElementConnection = { + id?: string; + nodeId: string; + isRequired: boolean; + host: string; + port: number; + username?: string; + password?: string; + tlsKey?: string; + status?: 'Connected' | 'mounted' | 'unmounted' | 'Connecting' | 'Disconnected' | 'idle'; + ['device-type']?: string; +}; \ No newline at end of file diff --git a/features/sdnr/odlux/odlux/apps/siteManagerApp/src/models/panelId.ts b/features/sdnr/odlux/odlux/apps/siteManagerApp/src/models/panelId.ts new file mode 100644 index 0000000..80ca44b --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/siteManagerApp/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 | 'TreeView' | 'SiteTable'; \ No newline at end of file diff --git a/features/sdnr/odlux/odlux/apps/siteManagerApp/src/models/site.ts b/features/sdnr/odlux/odlux/apps/siteManagerApp/src/models/site.ts new file mode 100644 index 0000000..e1bde59 --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/siteManagerApp/src/models/site.ts @@ -0,0 +1,49 @@ +/** + * ============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 Site = { + id: number; + uuid: string; + name: string; + address: Address; + heightAmslInMeters?: number; //AboveGroundLevel + antennaHeightAmslInMeters?: number; + type?: string; + operator: string; + location: { lon: number; lat: number }; + devices: Device[]; + links: { id: number; name: string; azimuth: number | null }[]; + furtherInformation: string; +}; + +export type Address = { + streetAndNr: string; + city: string; + zipCode: string | null; + country: string; +}; + +export type Device = { + id: string; + type?: string; + name: string; + manufacturer: string; + owner: string; + status?: string; + port: number[]; +} \ No newline at end of file diff --git a/features/sdnr/odlux/odlux/apps/siteManagerApp/src/models/siteDetails.ts b/features/sdnr/odlux/odlux/apps/siteManagerApp/src/models/siteDetails.ts new file mode 100644 index 0000000..ae1a114 --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/siteManagerApp/src/models/siteDetails.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========================================================================== + */ + +export type SiteDetails = { + administrativeState: string; + alarmState: string; + areaId: string; + areaName: string; + forecastEnabled: boolean; + id: number; + labels: []; + latitude: number; + lifecycleState: string; + longitude: number; + name: string; + operationalState: string; + operatorId: string; + uuid: string; +}; + diff --git a/features/sdnr/odlux/odlux/apps/siteManagerApp/src/models/siteDocTypes.ts b/features/sdnr/odlux/odlux/apps/siteManagerApp/src/models/siteDocTypes.ts new file mode 100644 index 0000000..d74037a --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/siteManagerApp/src/models/siteDocTypes.ts @@ -0,0 +1,51 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2022 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 UserListItem = { + userName: string; + firstName: string; + lastName: string; +}; + +export type RegisterUser = { + firstName: string; + familyName: string; + email: string; + username: string; + password: string; + telephoneNr: string; + role: 'ANDROID'; +}; + +export type SitedocOrder = { + reportFile: string; + assignedUser: string; + date: string; //in UTC + state: SitedocOrderTypes; + tasks: SitedocOrderTask[]; + note: string; + isTssr: boolean; +}; + +export type SitedocOrderTypes = 'OPEN' | 'UPDATE' | 'DELETE'; + +export type SitedocOrderTask = { + type: string; + description: string; + completed: false; +}; \ No newline at end of file diff --git a/features/sdnr/odlux/odlux/apps/siteManagerApp/src/models/siteManager.ts b/features/sdnr/odlux/odlux/apps/siteManagerApp/src/models/siteManager.ts new file mode 100644 index 0000000..fde2f2c --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/siteManagerApp/src/models/siteManager.ts @@ -0,0 +1,182 @@ +/** + * ============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 SiteManagerAreas = { + id: string; + name: string; + 'tree-level': number; + 'parent-id'?: string; + 'area-count'?: number; + 'site-count'?: number; + 'site-level'?: boolean; + 'areas'?: SiteManagerAreas[]; +}; + +export interface ITreeViewItem { + id: string; + name: string; + isSite: boolean; + isCategory: boolean; + parentId: string; + areaCount: number; + siteCount: number; + isNodeSelected: boolean; + uuid?: number; + treeLevel?: number; + siteLevel?: boolean; + url?: string; + linkType?: string; + children?: ITreeViewItem[]; +} + +export type Sites = { + id: string; + uuid: string; + name: string; + amslInMeters: string; + type: string; + 'area-id': string; + 'item-count': number; + address: { + streetAndNr: string; + city: string; + zipCode: string; + country: string; + }; + operator: string; + location: { + lon: string; + lat: string; + }; +}; + +export interface SearchSiteIdResult { + areas: SiteManagerAreas[]; + sites: Sites[]; + isError: boolean; + errorMessage: string; +} + +export interface SitesListResult { + sites: Sites[]; + isError: boolean; + errorMessage: string; +} + +export type SiteManagerCategories = { + id: string; + name: string; + url: string; + 'link-type': string; +}; + +export type SiteManagerCategoryItems = { + name: string; + url: string; + 'last-update': string; +}[]; + +export type SiteManagerSiteOrderItemsDetails = { + assignedUser: string; + state: string; + note: string; + tasks: { + type: string; + description: string; + completed: boolean; + }[]; +}[]; + +export type LinkDetails = { + administrativeState: string; + deviceA: { id: number; nodeId: string; uuid: string }; + deviceB: { id: number; nodeId: string; uuid: string }; + id: number; + labels: []; + lifecycleState: string; + name: string; + operationalState: string; + operatorId: string; + siteA: { + areaId: string; + areaName: string; + id: number; + latitude: number; + longitude: number; + name: string; + operatorId: string; + uuid: string; + }; + siteB: { + areaId: string; + areaName: string; + id: number; + latitude: number; + longitude: number; + name: string; + operatorId: string; + uuid: string; + }; + type: string; + uuid: string; +}; + +export type DeviceDetails = { + areaId: string; + areaName: string; + calculationParameters: string; + id: number; + manufacturerId: number; + manufacturerName: string; + modelId: number; + modelName: string; + nodeId: string; + operationalParameters: string; + siteId: number; + siteName: string; + uuid: string; +}; + +export type SiteConfigurationFreqPlan = [ + { + id: number; + siteId: number; + band: { + id: number; + name: string; + duplexspacingMhz: number; + }; + status: string; + configuration: string; + comment: string; + }, +]; + +export type addEditSiteConfig = { + configuration: string; + comment: string; +}; + + +export type Bands = [ + { + id: number; + name: string; + duplexspacingMhz: number; + }, +]; diff --git a/features/sdnr/odlux/odlux/apps/siteManagerApp/src/models/siteSearch.ts b/features/sdnr/odlux/odlux/apps/siteManagerApp/src/models/siteSearch.ts new file mode 100644 index 0000000..1b59057 --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/siteManagerApp/src/models/siteSearch.ts @@ -0,0 +1,41 @@ +/** + * ============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========================================================================== + */ + + +export type Site = { + id: string; + uuid: string; + name: string; + 'area-id': string; + 'item-count': number; + address: { + streetAndNr: string; + city: string; + zipCode: string; + country: string; + }; + operator: string; + location: { + lon: string; + lat: string; + }; +}; + +export type SearchResult = { + sites: Site[]; +}; \ No newline at end of file diff --git a/features/sdnr/odlux/odlux/apps/siteManagerApp/src/models/stadokSite.ts b/features/sdnr/odlux/odlux/apps/siteManagerApp/src/models/stadokSite.ts new file mode 100644 index 0000000..e5da7df --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/siteManagerApp/src/models/stadokSite.ts @@ -0,0 +1,57 @@ +/** + * ============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========================================================================== + */ + +export type Address = { + streetAndNr: string; + city: string; + zipCode: string | null; + country: string; +}; + +type StadokSite = { + id: string; + createdBy: Contact; + updatedOn: Date; + location: { lat: number; lon: number }; + address: Address; + contacts: { manager: Contact; owner: Contact }; + safetyNotices: string[]; + images: string[]; + type: string; + devices: Device[]; + logs: Log[]; +}; + +type Contact = { + firstName: string; + lastName: string; + email: string; + telephoneNumber: string; +}; +type Log = { + date: Date; //string? + person: string; + entry: string; +}; + +type Device = { + 'device': string; + 'antenna': string; +}; + +export default StadokSite; \ No newline at end of file diff --git a/features/sdnr/odlux/odlux/apps/siteManagerApp/src/models/topologyTypes.ts b/features/sdnr/odlux/odlux/apps/siteManagerApp/src/models/topologyTypes.ts new file mode 100644 index 0000000..e6cd361 --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/siteManagerApp/src/models/topologyTypes.ts @@ -0,0 +1,15 @@ +type Route = { lat: number; lon: number }; + +export type Service = { + 'id': number; + 'name': string; + 'type': string; + 'backupForServiceId': number | null; + 'lifecycleState': string; + 'administrativeState': string; + 'operationalState': string; + 'created': string; + 'modified': string; + 'route': Route[]; + 'length': number; +}; \ No newline at end of file diff --git a/features/sdnr/odlux/odlux/apps/siteManagerApp/src/pluginSiteManager.tsx b/features/sdnr/odlux/odlux/apps/siteManagerApp/src/pluginSiteManager.tsx new file mode 100644 index 0000000..87cb432 --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/siteManagerApp/src/pluginSiteManager.tsx @@ -0,0 +1,89 @@ +/** +* ============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 { useApplicationDispatch } from '../../../framework/src/flux/connect'; +import applicationManager from '../../../framework/src/services/applicationManager'; + +import { loadTreeSiteSearchBySiteIdOrNameAsync, SearchValueAction } from './actions/siteManagerSiteSearchAction'; +import siteManagerAppRootHandler from './handlers/siteManagerAppRootHandler'; +import SiteManager from './views/siteManager'; + +const appIcon = require('./assets/icons/siteManagerAppIcon.svg'); // select app icon + +let currentSearchSiteId: string | undefined = undefined; +let currentSearchCategoryName: string | undefined = undefined; + +interface SearchProps { + handleRefresh: (event: React.SyntheticEvent) => void; + handleSearch: (event: React.SyntheticEvent, searchValue: string) => void; +} + +const SiteManagerTableApplicationRouteAdapter = ((props: SearchProps & RouteComponentProps<{ + searchSiteId?: string; searchCategoryName?: string; +}>) => { + + const dispatch = useApplicationDispatch(); + const setSearchTerm = (siteId: string, categoryName?: string) => dispatch(new SearchValueAction(siteId, categoryName)); + const searchSiteIdTrail = async (searchValue: string) => dispatch(loadTreeSiteSearchBySiteIdOrNameAsync(searchValue)); + + if (currentSearchCategoryName !== props.match.params.searchCategoryName) { + currentSearchCategoryName = props.match.params.searchCategoryName || undefined; + if (currentSearchCategoryName && currentSearchSiteId !== props.match.params.searchSiteId) { + currentSearchSiteId = props.match.params.searchSiteId || undefined; + if (currentSearchSiteId) { + setSearchTerm(currentSearchSiteId, currentSearchCategoryName); + searchSiteIdTrail(currentSearchSiteId); + } + } + } else { + if (currentSearchSiteId !== props.match.params.searchSiteId) { + currentSearchSiteId = props.match.params.searchSiteId || undefined; + if (currentSearchSiteId) { + setSearchTerm(currentSearchSiteId); + searchSiteIdTrail(currentSearchSiteId); + } + } + } + return ( + + ); +}); + +const App = withRouter((props: RouteComponentProps) => ( + + + + + + + +)); + +export function register() { + applicationManager.registerApplication({ + name: 'siteManager', + icon: appIcon, + rootActionHandler: siteManagerAppRootHandler, + rootComponent: App, + menuEntry: 'Site Manager', + }); +} + diff --git a/features/sdnr/odlux/odlux/apps/siteManagerApp/src/services/dataService.ts b/features/sdnr/odlux/odlux/apps/siteManagerApp/src/services/dataService.ts new file mode 100644 index 0000000..8e0d032 --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/siteManagerApp/src/services/dataService.ts @@ -0,0 +1,52 @@ +/** + * ============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 { Result } from '../../../../framework/src/models'; +import { requestRest } from '../../../../framework/src/services/restService'; + +import { NetworkElementConnection } from '../models/networkElementConnection'; + +const dataService = { + getAdditionalInfoOnDevices: async (ids: string[]) => { + const path = 'rests/operations/data-provider:read-network-element-connection-list'; + const query = { + 'data-provider:input': { + 'filter': [{ + 'property': 'id', + 'filtervalues': ids, + }], + 'pagination': { + 'size': ids.length, + 'page': 1, + }, + }, + }; + if (ids.length > 0) { + const result = await requestRest>( + path, + { method: 'POST', body: JSON.stringify(query) }, + ); + const resultData = result && result['data-provider:output'] && result['data-provider:output'].data; + return resultData; + } else { + return null; + } + }, +}; + +export default dataService; \ No newline at end of file diff --git a/features/sdnr/odlux/odlux/apps/siteManagerApp/src/services/mapService.ts b/features/sdnr/odlux/odlux/apps/siteManagerApp/src/services/mapService.ts new file mode 100644 index 0000000..65f83c7 --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/siteManagerApp/src/services/mapService.ts @@ -0,0 +1,62 @@ +import { IExternalTableState } from '../../../../framework/src/components/material-table/utilities'; +import { requestRest } from '../../../../framework/src/services/restService'; +import { SiteDetails } from '../models/siteDetails'; + +const filterKeysUrl = '/topology/network/filters/keys'; + +type FilterKey = { + key: string; + reference: string; + type: string; + label: string | null; +}; + +let filterMapping: { [table: string]: { [column: string]: { key: string; type: string } } }; + +const getFieldMapping = async () => { + if (filterMapping) { + return filterMapping; + } + + const response = await requestRest(filterKeysUrl); + if (!response) { + throw new Error('No filter mapping found'); + } + + filterMapping = {}; + response.forEach(key => { + const parts = key.reference.split('.'); + const table = parts[0]; + const column = parts[1]; + + if (!filterMapping[table]) { + filterMapping[table] = {}; + } + filterMapping[table][column] = { key: key.key, type: key.type }; + }); + + return filterMapping; +}; + +const createMapFilter = async (tableName: string, tableState: IExternalTableState) => { + const mapping = await getFieldMapping(); + + return Object.keys(tableState.filter).map(key => { + const column = mapping[tableName] && mapping[tableName][key]; + const value = tableState.filter[key] && tableState.filter[key].trim(); + if (!column || !value) { + return null; + } + return value.match(/^[<>]/i) + ? `${column.key}${value}` + : column.type === 'string' + ? value.includes('\'') + ? `${column.key}='${value}'` + : `${column.key}='${value}'` + : `${column.key}=${value}`; + + }).filter(filter => filter !== null).join(' AND '); + +}; + +export { createMapFilter }; diff --git a/features/sdnr/odlux/odlux/apps/siteManagerApp/src/services/siteManagerService.ts b/features/sdnr/odlux/odlux/apps/siteManagerApp/src/services/siteManagerService.ts new file mode 100644 index 0000000..1943bee --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/siteManagerApp/src/services/siteManagerService.ts @@ -0,0 +1,274 @@ +/** + * ============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 { requestRestExt } from '../../../../framework/src/services/restService'; + +import { + SearchSiteIdResult, SiteConfigurationFreqPlan, SiteManagerAreas, SiteManagerCategories, SiteManagerCategoryItems, + Sites, SitesListResult, addEditSiteConfig, Bands, +} from '../models/siteManager'; + +/** + * Represents a web api accessor service for all entries related actions. + */ + +const URL_SITE_MANAGER = '/topology/site-manager'; + +const URL_MICROWAVE = '/topology/microwave'; + +const siteManagerService = { + getCountries: async (): Promise => { + return fetch(`${URL_SITE_MANAGER}/areas`) + .then(res => res.json()) + .then(result => { + return result; + }); + }, + + getAreasByAreaId: async (areaId: string): Promise => { + return fetch(`${URL_SITE_MANAGER}/areas/${areaId}/areas`) + .then(res => res.json()) + .then(result => { + return result; + }); + }, + + getSitesByAreaId: async (areaId: string): Promise => { + const requestHeaders: HeadersInit = new Headers(); + requestHeaders.set('Content-Type', 'application/json'); + requestHeaders.set('Accept', 'application/json'); + + return fetch(`${URL_SITE_MANAGER}/areas/${areaId}/sites`, { + method: 'GET', + headers: requestHeaders, + }) + .then(response => { + if (!response.ok) { + // eslint-disable-next-line @typescript-eslint/no-throw-literal + throw response; + } + return response.json(); + }) + .then(response => { + const returnResponse: SitesListResult = { + sites: response ? response : [], + isError: false, + errorMessage: '', + }; + return returnResponse; + }).catch(error => { + return error.json().then((errorMessage: any) => { + const returnResponse: SitesListResult = { + sites: [], + isError: true, + errorMessage: errorMessage.message, + }; + return returnResponse; + }); + }); + }, + + getSiteBySiteId: async (siteId: string): Promise => { + const requestHeaders: HeadersInit = new Headers(); + requestHeaders.set('Content-Type', 'application/json'); + requestHeaders.set('Accept', 'application/json'); + + return fetch(`${URL_SITE_MANAGER}/sites/${siteId}`, { + method: 'GET', + headers: requestHeaders, + }) + .then(res => res.json()) + .then(result => { + return result; + }).catch(error => { + console.log('Error: ', error); + }); + }, + + getCategoriesBySiteId: async (siteId: string): Promise => { + return fetch(`${URL_SITE_MANAGER}/sites/${siteId}/categories`) + .then(res => res.json()) + .then(result => { + return result; + }); + }, + + getCategoryItemsBySiteIdAndCategoryName: async (siteId: string, categoryName: string): Promise => { + return fetch(`${URL_SITE_MANAGER}/sites/${siteId}/categories/${categoryName}/items`) + .then(res => res.json()) + .then(result => { + return result; + }).catch(() => { + // TODO comment the below code - testing + const value: SiteManagerCategoryItems = [{ + name: categoryName, + url: 'No url found', + 'last-update': 'today', + }]; + return value; + }); + }, + + getSearchSiteIDTrail: async (siteId: string): Promise => { + const data = { query: siteId, showBesideItems: true }; + + const requestHeaders: HeadersInit = new Headers(); + requestHeaders.set('Content-Type', 'application/json'); + requestHeaders.set('Accept', 'application/json'); + + return fetch(`${URL_SITE_MANAGER}/search`, { + method: 'POST', + headers: requestHeaders, + body: JSON.stringify(data), + }) + .then(response => { + if (!response.ok) { + // eslint-disable-next-line @typescript-eslint/no-throw-literal + throw response; + } + return response.json(); + }) + .then(response => { + const returnResponse: SearchSiteIdResult = { + areas: response.areas ? response.areas : [], + sites: response.sites ? response.sites : [], + isError: false, + errorMessage: '', + }; + return returnResponse; + }).catch(error => { + return error.json().then((errorMessage: any) => { + const returnResponse: SearchSiteIdResult = { + areas: [], + sites: [], + isError: true, + errorMessage: errorMessage.message, + }; + return returnResponse; + }); + }); + }, + + saveModifiedSiteDetails: async (siteDetails: Sites, siteId: string) => { + type Message = { 'message': string }; + const response = await requestRestExt(URL_SITE_MANAGER + `/sites/${siteId}`, { + method: 'PUT', + body: JSON.stringify(siteDetails), + }); + if (response.status == 200) { + return { message: 'Save Successful', error: false, serverError: false }; + } else { + let message = { message: '', error: true, serverError: true }; + if (response.data?.message) { + message.serverError = (response.status == 400) ? true : false; + message.message = 'Save failed: ' + response.data ? response.data.message : 'unknown'; + return message; + } else { + message.serverError = true; + message.message = 'Something went wrong ' + response.message; + return message; + } + } + }, + + getSitesFrequencyPlan: async (siteId: string): Promise => { + const response = await fetch(`${URL_MICROWAVE}/network/sites/${siteId}/frequencyplan`); + const result = await response.json(); + return result as SiteConfigurationFreqPlan[]; + }, + + getAvailableBands: async (): Promise => { + const response = await fetch(`${URL_MICROWAVE}/bands`); + const result = await response.json(); + return result as Bands[]; + }, + + getAvailableSiteTypes: async (): Promise => { + const response = await fetch(`${URL_SITE_MANAGER}/site-types`); + const data = await response.json(); + const filteredData = data.filter((item: string | null) => item !== null && item !== ''); + return filteredData as string[]; + }, + + saveSiteConfiguration: async (modifiedConfig: addEditSiteConfig, bandId: string, siteId: string) => { + type Message = { 'message': string }; + const response = await requestRestExt(`${URL_MICROWAVE}/network/sites/${siteId}/frequencyplan/${bandId}`, { + method: 'PUT', + body: JSON.stringify(modifiedConfig), + }); + if (response.status == 200) { + return { message: 'Save Successful', error: false, serverError: false }; + } else { + let message = { message: '', error: true, serverError: true }; + if (response.data?.message) { + message.serverError = (response.status == 400) ? true : false; + message.message = 'Save failed: ' + response.data ? response.data.message : 'unknown'; + return message; + } else { + message.serverError = true; + message.message = 'Something went wrong ' + response.message; + return message; + } + } + }, + + createSiteConfiguration: async (newConfig: addEditSiteConfig, bandId: string, siteId: string) => { + type Message = { 'message': string }; + const response = await requestRestExt(`${URL_MICROWAVE}/network/sites/${siteId}/frequencyplan/${bandId}`, { + method: 'POST', + body: JSON.stringify(newConfig), + }); + if (response.status == 200) { + return { message: 'Save Successful', error: false, serverError: false }; + } else { + let message = { message: '', error: true, serverError: true }; + if (response.data?.message) { + message.serverError = (response.status == 400) ? true : false; + message.message = 'Save failed: ' + response.data ? response.data.message : 'unknown'; + return message; + } else { + message.serverError = true; + message.message = 'Something went wrong ' + response.message; + return message; + } + } + }, + + deleteSiteConfiguration: async (bandId: string, siteId: string) => { + type Message = { 'message': string }; + const response = await requestRestExt(`${URL_MICROWAVE}/network/sites/${siteId}/frequencyplan/${bandId}`, { + method: 'DELETE', + }); + if (response.status == 200) { + return { message: 'Delete Successful', error: false, serverError: false }; + } else { + let message = { message: '', error: true, serverError: true }; + if (response.data?.message) { + message.serverError = (response.status == 400) ? true : false; + message.message = 'Delete failed: ' + response.data ? response.data.message : 'unknown'; + return message; + } else { + message.serverError = true; + message.message = 'Something went wrong ' + response.message; + return message; + } + } + }, +}; + +export default siteManagerService; \ No newline at end of file diff --git a/features/sdnr/odlux/odlux/apps/siteManagerApp/src/services/sitedocDataService.ts b/features/sdnr/odlux/odlux/apps/siteManagerApp/src/services/sitedocDataService.ts new file mode 100644 index 0000000..ca2bf26 --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/siteManagerApp/src/services/sitedocDataService.ts @@ -0,0 +1,82 @@ +/** + * ============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 { requestRest, requestRestExt } from '../../../../framework/src/services/restService'; + +import { Site } from '../models/site'; +import { SitedocOrder, UserListItem } from '../models/siteDocTypes'; +import { SiteManagerSiteOrderItemsDetails } from '../models/siteManager'; + +const BASE_URL = '/sitedoc'; + +type Message = { 'message': string }; + +const sitedocDataService = { + createOrder: async (order: SitedocOrder, siteId: string) => { + const result = await requestRestExt(BASE_URL + `/site/${siteId}/order`, { method: 'POST', body: JSON.stringify(order) }); + if (result.status === 200) { + return { message: 'Order Created', error: false, serverError: false }; + } else { + let message = { message: '', error: true, serverError: false }; + if (result.data) { + message.message = 'Creation failed: ' + result.data ? result.data.message : 'unknown'; + return message; + } else { + message.serverError = (result.status === 403) ? true : false; + message.message = 'Something went wrong... ' + result.message; + return message; + } + } + }, + + getSiteIfExists: async (siteId: string) => { + return requestRest('/topology/network/sites/' + siteId); + }, + + getAllUsers: async () => { + const result = await requestRest(BASE_URL + '/users/android'); + if (result) { + return result; + } else { + return []; + } + }, + + getSiteDetails: async (siteId: string): Promise => { + const selectedSiteId = siteId.split('#')[0] + ''; + return fetch(`${BASE_URL}/site/${selectedSiteId}/orders`) + .then(res => res.json()) + .then(result => { + return result; + }).catch(() => { + const value: SiteManagerSiteOrderItemsDetails = [{ + assignedUser: '', + state: '', + note: '', + tasks: [{ + type: '', + description: '', + completed: false, + }], + }]; + return value; + }); + }, +}; + +export default sitedocDataService; diff --git a/features/sdnr/odlux/odlux/apps/siteManagerApp/src/views/OrderCreation.tsx b/features/sdnr/odlux/odlux/apps/siteManagerApp/src/views/OrderCreation.tsx new file mode 100644 index 0000000..84db1fb --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/siteManagerApp/src/views/OrderCreation.tsx @@ -0,0 +1,86 @@ +/* eslint-disable @typescript-eslint/no-unused-expressions */ +/** + * ============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 React from 'react'; +import { RouteComponentProps, withRouter } from 'react-router-dom'; + +import { useApplicationDispatch } from '../../../../framework/src/flux/connect'; + +import { SelectElementAction } from '../actions/detailsAction'; +import { getUsersAction } from '../actions/sitedocManagementAction'; +import CreateNewOrder from '../components/createNewOrder'; +import { Site } from '../models/site'; +import sitedocDataService from '../services/sitedocDataService'; + +type orderProps = RouteComponentProps & { + siteId: string; + onClose: (siteId: string) => void; + onError: () => void; +}; + +const OrderCreation = (props: orderProps) => { + const dispatch = useApplicationDispatch(); + const getUsersActions = () => dispatch(getUsersAction); + const selectSite = (site: Site) => dispatch(new SelectElementAction(site)); + + const [showForm, setShowForm] = React.useState(true); + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const [siteInfo, setSiteInfo] = React.useState(''); + //on mount + React.useEffect(() => { + getUsersActions(); + if (props.siteId) { + setShowForm(true); + } else { + setShowForm(false); + sitedocDataService.getSiteIfExists(props.siteId).then(res => { + if (res) { + selectSite(res); + setShowForm(true); + } else { + setSiteInfo('Site not found.'); + } + }); + } + }, []); + + const onCancel = (siteId: string) => { + props.onClose && props.onClose(siteId); + }; + + const onError = () => { + props.onError && props.onError(); + }; + + return <> + { + (props.siteId === null) ? +
Check if site exists...
+ : + !showForm + ?
+
Site does not exist
+
+ : + } + ; +}; + +const CreateOrderView = withRouter(OrderCreation); + +export default CreateOrderView; \ No newline at end of file diff --git a/features/sdnr/odlux/odlux/apps/siteManagerApp/src/views/siteManager.tsx b/features/sdnr/odlux/odlux/apps/siteManagerApp/src/views/siteManager.tsx new file mode 100644 index 0000000..3cb3dd3 --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/siteManagerApp/src/views/siteManager.tsx @@ -0,0 +1,86 @@ +/** + * ============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 { AppBar, Tab, Tabs } from '@mui/material'; +import { RouteComponentProps, withRouter } from 'react-router-dom'; + +import { NavigateToApplication } from '../../../../framework/src/actions/navigationActions'; +import { useApplicationDispatch } from '../../../../framework/src/flux/connect'; + +import { SearchValueAction } from '../actions/siteManagerSiteSearchAction'; +import { SiteManagerTreeView } from '../components/siteManagerTreeview'; +import { SiteTableView } from '../components/siteTable'; + + +type TabId = 'SiteTable' | 'TreeView'; + +type DashboardComponentProps = RouteComponentProps & { activePanel: string }; +const scrollbar = { overflow: 'auto' }; + +const SiteManagerComponent = (props: DashboardComponentProps) => { + const [activeTab, setActiveTab] = useState(props.activePanel); + const dispatch = useApplicationDispatch(); + const navigateToApplication = (applicationName: string, path?: string) => dispatch(new NavigateToApplication(applicationName, path, '')); + const setSearchTerm = (value: string) => dispatch(new SearchValueAction(value)); + + React.useEffect(() => { + if (activeTab == null) { + setActiveTab('SiteTable'); + } + }, []); + + const onHandleTabChange = (event: React.SyntheticEvent, value: TabId) => { + setActiveTab(value); + }; + + const siteTableTab = () => { + setSearchTerm(''); + return navigateToApplication('siteManager', ''); + }; + + const treeViewTab = () => { + return navigateToApplication('siteManager', 'treeview'); + }; + + return ( + <> + + + + + + + {activeTab === 'SiteTable' + ?
+ +
+ : activeTab === 'TreeView' + ?
+ +
+ : null} + + ); +}; + + +export const SiteManager = withRouter(SiteManagerComponent); +export default SiteManager; + diff --git a/features/sdnr/odlux/odlux/apps/siteManagerApp/tsconfig.json b/features/sdnr/odlux/odlux/apps/siteManagerApp/tsconfig.json new file mode 100644 index 0000000..ca65092 --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/siteManagerApp/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/features/sdnr/odlux/odlux/apps/siteManagerApp/webpack.config.js b/features/sdnr/odlux/odlux/apps/siteManagerApp/webpack.config.js new file mode 100644 index 0000000..1e5d230 --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/siteManagerApp/webpack.config.js @@ -0,0 +1,142 @@ +/** + * 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 __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: { + siteManagerApp: ["./pluginSiteManager.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: proxyConf, + } + }]; +} diff --git a/features/sdnr/odlux/odlux/apps/unmFaultManagementApp/.babelrc b/features/sdnr/odlux/odlux/apps/unmFaultManagementApp/.babelrc new file mode 100644 index 0000000..3d8cd12 --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/unmFaultManagementApp/.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/features/sdnr/odlux/odlux/apps/unmFaultManagementApp/package.json b/features/sdnr/odlux/odlux/apps/unmFaultManagementApp/package.json new file mode 100644 index 0000000..c95c071 --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/unmFaultManagementApp/package.json @@ -0,0 +1,46 @@ +{ + "name": "@odlux/unm-fault-management-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": "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": "*", + "@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/features/sdnr/odlux/odlux/apps/unmFaultManagementApp/pom.xml b/features/sdnr/odlux/odlux/apps/unmFaultManagementApp/pom.xml new file mode 100644 index 0000000..98146c9 --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/unmFaultManagementApp/pom.xml @@ -0,0 +1,105 @@ + + + + + 4.0.0 + + org.o-ran-sc.oam-controller.features.sdnr.odlux + sdnr-odlux-app-unmFaultManagementApp + 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/features/sdnr/odlux/odlux/apps/unmFaultManagementApp/src/actions/panelChangeActions.ts b/features/sdnr/odlux/odlux/apps/unmFaultManagementApp/src/actions/panelChangeActions.ts new file mode 100644 index 0000000..fb29e9c --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/unmFaultManagementApp/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/features/sdnr/odlux/odlux/apps/unmFaultManagementApp/src/actions/unmFaultManagementAlarmStatusActions.ts b/features/sdnr/odlux/odlux/apps/unmFaultManagementApp/src/actions/unmFaultManagementAlarmStatusActions.ts new file mode 100644 index 0000000..d53fdc3 --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/unmFaultManagementApp/src/actions/unmFaultManagementAlarmStatusActions.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 { Dispatch } from '../../../../framework/src/flux/store'; + +import { Action } from '../../../../framework/src/flux/action'; +import { getUnmFaultCountBySeverity } from '../services/unmFaultStatusService'; + +export class BaseAction extends Action { } + +export class SetUnmFaultStatusAction extends BaseAction { + constructor(public criticalFaults: number, public majorFaults: number, + public minorFaults: number, public warnings: number, public indeterminate: number) { + super(); + } +} + +export const loadAllUnmFaultStatusCountAsyncAction = async (dispatch: Dispatch) => { + const result = await getUnmFaultCountBySeverity().catch(_ => null); + if (result) { + const statusAction = new SetUnmFaultStatusAction( + result.Critical || 0, + result.Major || 0, + result.Minor || 0, + result.Warning || 0, + result.Indeterminate || 0, + ); + dispatch(statusAction); + return; + } else { + dispatch(new SetUnmFaultStatusAction(0, 0, 0, 0, 0)); + } +}; diff --git a/features/sdnr/odlux/odlux/apps/unmFaultManagementApp/src/assets/icons/unmFaultManagementAppIcon.svg b/features/sdnr/odlux/odlux/apps/unmFaultManagementApp/src/assets/icons/unmFaultManagementAppIcon.svg new file mode 100644 index 0000000..aabbf4c --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/unmFaultManagementApp/src/assets/icons/unmFaultManagementAppIcon.svg @@ -0,0 +1,19 @@ + + + + + + + + + + + + diff --git a/features/sdnr/odlux/odlux/apps/unmFaultManagementApp/src/components/refreshUnmFaultLogDialog.tsx b/features/sdnr/odlux/odlux/apps/unmFaultManagementApp/src/components/refreshUnmFaultLogDialog.tsx new file mode 100644 index 0000000..b6f3474 --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/unmFaultManagementApp/src/components/refreshUnmFaultLogDialog.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 { unmFaultLogEntriesReloadAction } from '../handlers/unmFaultLogEntriesHandler'; +import { FaultLog } from '../models/unmFault'; + +export enum RefreshFaultLogDialogMode { + None = 'none', + RefreshFaultLogTable = 'RefreshFaultLogTable', +} + +const mapDispatch = (dispatcher: IDispatcher) => ({ + refreshFaultLog: () => dispatcher.dispatch(unmFaultLogEntriesReloadAction), +}); + +type DialogSettings = { + dialogTitle: string; + dialogDescription: string; + applyButtonText: string; + cancelButtonText: string; + enableMountIdEditor: boolean; + enableUsernameEditor: boolean; + enableExtendedEditor: boolean; +}; + +const settings: { [key: string]: DialogSettings } = { + [RefreshFaultLogDialogMode.None]: { + dialogTitle: '', + dialogDescription: '', + applyButtonText: '', + cancelButtonText: '', + enableMountIdEditor: false, + enableUsernameEditor: false, + enableExtendedEditor: false, + }, + [RefreshFaultLogDialogMode.RefreshFaultLogTable]: { + dialogTitle: 'Do you want to refresh the Fault Log?', + dialogDescription: '', + applyButtonText: 'Yes', + cancelButtonText: 'Cancel', + enableMountIdEditor: true, + enableUsernameEditor: true, + enableExtendedEditor: true, + }, +}; + +type RefreshFaultLogDialogComponentProps = Connect & { + mode: RefreshFaultLogDialogMode; + onClose: () => void; +}; + +type RefreshFaultLogDialogComponentState = FaultLog & { isNameValid: boolean; isHostSet: boolean }; + +class RefreshFaultLogDialogComponent extends React.Component { + render(): JSX.Element { + const setting = settings[this.props.mode]; + return ( + + {setting.dialogTitle} + + + {setting.dialogDescription} + + + + + + + + ); + } + + private onRefresh = () => { + this.props.refreshFaultLog(); + this.props.onClose(); + }; + + private onCancel = () => { + this.props.onClose(); + }; +} + +export const RefreshFaultLogDialog = connect(undefined, mapDispatch)(RefreshFaultLogDialogComponent); +export default RefreshFaultLogDialog; \ No newline at end of file diff --git a/features/sdnr/odlux/odlux/apps/unmFaultManagementApp/src/components/unmFaultManagementAlarmDetailsDialog.tsx b/features/sdnr/odlux/odlux/apps/unmFaultManagementApp/src/components/unmFaultManagementAlarmDetailsDialog.tsx new file mode 100644 index 0000000..47999ed --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/unmFaultManagementApp/src/components/unmFaultManagementAlarmDetailsDialog.tsx @@ -0,0 +1,172 @@ +/** + * ============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 { TextField } from '@mui/material'; +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 } from '../../../../framework/src/flux/connect'; + +import { FaultLog } from '../models/unmFault'; + +export enum ViewAlarmDetailsDialogMode { + None = 'none', + ViewAlarmDetails = 'viewDetails', +} + +const mapDispatch = () => ({ +}); + +type DialogSettings = { + dialogTitle: string; + dialogDescription: string; + applyButtonText: string; + cancelButtonText: string; + enableMountIdEditor: boolean; + enableUsernameEditor: boolean; + enableExtendedEditor: boolean; +}; + +const settings: { [key: string]: DialogSettings } = { + [ViewAlarmDetailsDialogMode.None]: { + dialogTitle: '', + dialogDescription: '', + applyButtonText: '', + cancelButtonText: '', + enableMountIdEditor: false, + enableUsernameEditor: false, + enableExtendedEditor: false, + }, + [ViewAlarmDetailsDialogMode.ViewAlarmDetails]: { + dialogTitle: 'Alarm Details', + dialogDescription: 'Details', + applyButtonText: 'Save', + cancelButtonText: 'Cancel', + enableMountIdEditor: false, + enableUsernameEditor: true, + enableExtendedEditor: false, + }, +}; + +type ViewAlarmDetailsDialogComponentProps = Connect & { + initialAlarmDetails: FaultLog; + mode: ViewAlarmDetailsDialogMode; + onClose: () => void; +}; + +type ViewAlarmDetailsDialogComponentState = FaultLog & { +}; + +class UnmFaultManagementAlarmDetailsComponent extends React.Component { + constructor(props: ViewAlarmDetailsDialogComponentProps) { + super(props); + this.state = { + nodeId: this.props.initialAlarmDetails.nodeId, + counter: this.props.initialAlarmDetails.counter, + timestamp: this.props.initialAlarmDetails.timestamp, + objectId: this.props.initialAlarmDetails.objectId, + severity: this.props.initialAlarmDetails.severity, + problem: this.props.initialAlarmDetails.problem, + sourceType: this.props.initialAlarmDetails.sourceType, + }; + } + + render(): JSX.Element { + const setting = settings[this.props.mode]; + return ( + + {setting.dialogTitle} + + + {setting.dialogDescription} + + + + + + + + + + + + + + { this.setState({ comment: event.target.value }); }} /> + + + + + + + + ); + } + + public componentDidMount() { + } + + + private onApply = () => { + if (this.props.onClose) this.props.onClose(); + + switch (this.props.mode) { + case ViewAlarmDetailsDialogMode.ViewAlarmDetails: + break; + } + }; + + private onCancel = () => { + if (this.props.onClose) this.props.onClose(); + }; + + static getDerivedStateFromProps(props: ViewAlarmDetailsDialogComponentProps, state: ViewAlarmDetailsDialogComponentState & { initialAlarmDetails: FaultLog }): ViewAlarmDetailsDialogComponentState & { initialAlarmDetails: FaultLog } { + let returnState = state; + if (props.initialAlarmDetails !== state.initialAlarmDetails) { + returnState = { + ...state, + ...props.initialAlarmDetails, + initialAlarmDetails: props.initialAlarmDetails, + }; + } + return returnState; + } +} + +export const ViewAlarmDetailsDialog = connect(undefined, mapDispatch)(UnmFaultManagementAlarmDetailsComponent); +export default ViewAlarmDetailsDialog; \ No newline at end of file diff --git a/features/sdnr/odlux/odlux/apps/unmFaultManagementApp/src/components/unmFaultManagementDashboard.tsx b/features/sdnr/odlux/odlux/apps/unmFaultManagementApp/src/components/unmFaultManagementDashboard.tsx new file mode 100644 index 0000000..690aa7c --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/unmFaultManagementApp/src/components/unmFaultManagementDashboard.tsx @@ -0,0 +1,111 @@ +/** + * ============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 } from 'react-router-dom'; + +import { WithStyles, withStyles } from '@mui/styles'; +import createStyles from '@mui/styles/createStyles'; + +import { connect, Connect } from '../../../../framework/src/flux/connect'; +import { IApplicationStoreState } from '../../../../framework/src/store/applicationStore'; + +const styles = () => createStyles({ + pageWidthSettings: { + width: '50%', + float: 'left', + }, + root: { + borderTopWidth: 5, + borderBottomWidth: 5, + borderColor: '#eee', + borderStyle: 'solid', + width: '75%', + padding: '15px', + 'fontfamily': 'Arial, Helvetica, sans-serif', + 'borderCollapse': 'collapse', + 'paddingBottom': '12px', + 'paddingLeft': '25px', + 'text-align': 'left', + 'color': 'black', + }, + +}); + +const scrollbar = { overflow: 'auto', paddingRight: '20px' }; + + +const mapProps = (state: IApplicationStoreState) => ({ + unmAlarmCount: state.unmFaultManagement.unmFaultStatus, +}); + +const mapDispatch = () => ({ +}); + +type UnmFaultManagementComponentProps = RouteComponentProps & Connect & WithStyles; + +class UnmFaultManagementDashboard extends React.Component { + constructor(props: UnmFaultManagementComponentProps) { + super(props); + this.state = { + }; + } + + render(): JSX.Element { + const { classes, unmAlarmCount } = this.props; + return ( + <> +
+

Alarm Overview

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + +

Total number of Alarms

{unmAlarmCount.critical + unmAlarmCount.major + unmAlarmCount.minor + + unmAlarmCount.warning + unmAlarmCount.indeterminate}

Critical

{unmAlarmCount.critical}

Major

{unmAlarmCount.major}

Minor

{unmAlarmCount.minor}

Warning

{unmAlarmCount.warning}

Indeterminate

{unmAlarmCount.indeterminate}
+
+
+ + ); + } +} + +export default connect(mapProps, mapDispatch)(withStyles(styles)(UnmFaultManagementDashboard)); diff --git a/features/sdnr/odlux/odlux/apps/unmFaultManagementApp/src/components/unmFaultManagementFaultLog.tsx b/features/sdnr/odlux/odlux/apps/unmFaultManagementApp/src/components/unmFaultManagementFaultLog.tsx new file mode 100644 index 0000000..e26cd58 --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/unmFaultManagementApp/src/components/unmFaultManagementFaultLog.tsx @@ -0,0 +1,151 @@ +/** + * ============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 { MenuItem, Typography } 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 RefreshFaultLogDialog, { RefreshFaultLogDialogMode } from '../components/refreshUnmFaultLogDialog'; +import ViewAlarmDetailsDialog, { ViewAlarmDetailsDialogMode } from '../components/unmFaultManagementAlarmDetailsDialog'; +import { createUnmFaultLogEntriesActions, createUnmFaultLogEntriesProperties } from '../handlers/unmFaultLogEntriesHandler'; +import { FaultLog } from '../models/unmFault'; + +const mapProps = (state: IApplicationStoreState) => ({ + unmFaultLogEntriesProperties: createUnmFaultLogEntriesProperties(state), +}); + +const mapDispatch = (dispatcher: IDispatcher) => ({ + unmFaultLogEntriesActions: createUnmFaultLogEntriesActions(dispatcher.dispatch), +}); + +type UnmFaultManagementApplicationComponentProps = Connect; + +type UnmFaultManagementApplicationState = { + alarmToEdit: FaultLog; + refreshFaultLogEditorMode: RefreshFaultLogDialogMode; + viewAlarmDetailsMode: ViewAlarmDetailsDialogMode; +}; + +const emptyAlarmElement: FaultLog = +{ + id: '', + nodeId: '', + counter: 0, + timestamp: { value: '' }, + objectId: '', + severity: null, + problem: '', + sourceType: '', +}; + +const FaultLogTable = MaterialTable as MaterialTableCtorType; + +class UnmFaultManagementFaultLogComponent extends React.Component { + constructor(props: UnmFaultManagementApplicationComponentProps) { + super(props); + this.state = { + alarmToEdit: emptyAlarmElement, + refreshFaultLogEditorMode: RefreshFaultLogDialogMode.None, + viewAlarmDetailsMode: ViewAlarmDetailsDialogMode.None, + }; + } + + getContextMenu(rowData: FaultLog): JSX.Element[] { + const buttonArray = [ + this.onOpenViewAlarmDetailsDialog(event, rowData)}>View Details, + ]; + return buttonArray; + } + + + render(): JSX.Element { + const refreshFaultLogAction = { + icon: Refresh, tooltip: 'Refresh Fault log table', ariaLabel: 'refresh', onClick: () => { + this.setState({ + refreshFaultLogEditorMode: RefreshFaultLogDialogMode.RefreshFaultLogTable, + }); + }, + }; + + return ( + <> + { + return ( + rowData.timestamp.value + ); + }, + }, + { property: 'nodeId', title: 'Node Name' }, + { property: 'objectId', title: 'Object Id' }, + { property: 'problem', title: 'Problem' }, + { property: 'sourceType', title: 'Source', width: '140px' }, + ]} {...this.props.unmFaultLogEntriesProperties} {...this.props.unmFaultLogEntriesActions} createContextMenu={rowData => { + return this.getContextMenu(rowData); + }} /> + + + + ); + } + + private onCloseRefreshFaultLogDialog = () => { + this.setState({ + refreshFaultLogEditorMode: RefreshFaultLogDialogMode.None, + }); + }; + + private onOpenViewAlarmDetailsDialog = (event: React.MouseEvent, element: FaultLog) => { + this.setState({ + alarmToEdit: { + nodeId: element.nodeId, + counter: element.counter, + timestamp: element.timestamp, + objectId: element.objectId, + severity: element.severity, + problem: element.problem, + sourceType: element.sourceType, + }, + viewAlarmDetailsMode: ViewAlarmDetailsDialogMode.ViewAlarmDetails, + }); + }; + + private onCloseViewAlarmDetailsDialog = () => { + this.setState({ + viewAlarmDetailsMode: ViewAlarmDetailsDialogMode.None, + }); + }; +} + +export const UnmFaultManagementFaultLog = connect(mapProps, mapDispatch)(UnmFaultManagementFaultLogComponent); +export default UnmFaultManagementFaultLog; diff --git a/features/sdnr/odlux/odlux/apps/unmFaultManagementApp/src/handlers/unmFaultLogEntriesHandler.ts b/features/sdnr/odlux/odlux/apps/unmFaultManagementApp/src/handlers/unmFaultLogEntriesHandler.ts new file mode 100644 index 0000000..ccc36de --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/unmFaultManagementApp/src/handlers/unmFaultLogEntriesHandler.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 { FaultLog } from '../models/unmFault'; + +export interface IUnmFaultLogEntriesState extends IExternalTableState { } + +// create eleactic search data fetch handler +const unmFaultLogEntriesSearchHandler = createSearchDataHandler('faultlog'); + +export const { + actionHandler: unmFaultLogEntriesActionHandler, + createActions: createUnmFaultLogEntriesActions, + createProperties: createUnmFaultLogEntriesProperties, + reloadAction: unmFaultLogEntriesReloadAction, + + // set value action, to change a value +} = createExternal(unmFaultLogEntriesSearchHandler, appState => appState.unmFaultManagement.unmFaultLogEntries); + diff --git a/features/sdnr/odlux/odlux/apps/unmFaultManagementApp/src/handlers/unmFaultManagementAlarmStatusHandler.ts b/features/sdnr/odlux/odlux/apps/unmFaultManagementApp/src/handlers/unmFaultManagementAlarmStatusHandler.ts new file mode 100644 index 0000000..1e96168 --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/unmFaultManagementApp/src/handlers/unmFaultManagementAlarmStatusHandler.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 { IActionHandler } from '../../../../framework/src/flux/action'; + +import { SetUnmFaultStatusAction } from '../actions/unmFaultManagementAlarmStatusActions'; + +export interface IUnmFaultStatusCount { + critical: number; + major: number; + minor: number; + warning: number; + indeterminate: number; +} + +const unmFaultStatusInit: IUnmFaultStatusCount = { + critical: 0, + major: 0, + minor: 0, + warning: 0, + indeterminate: 0, +}; + +export const unmFaultStatusHandler: IActionHandler = (state = unmFaultStatusInit, action) => { + if (action instanceof SetUnmFaultStatusAction) { + state = { + critical: action.criticalFaults, + major: action.majorFaults, + minor: action.minorFaults, + warning: action.warnings, + indeterminate: action.indeterminate, + }; + } + + return state; +}; diff --git a/features/sdnr/odlux/odlux/apps/unmFaultManagementApp/src/handlers/unmFaultManagementAppRootHandler.ts b/features/sdnr/odlux/odlux/apps/unmFaultManagementApp/src/handlers/unmFaultManagementAppRootHandler.ts new file mode 100644 index 0000000..c376945 --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/unmFaultManagementApp/src/handlers/unmFaultManagementAppRootHandler.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========================================================================== + */ +// 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 { unmFaultStatusHandler, IUnmFaultStatusCount } from './unmFaultManagementAlarmStatusHandler'; +import { IUnmFaultLogEntriesState, unmFaultLogEntriesActionHandler } from './unmFaultLogEntriesHandler'; + +export interface IUnmFaultManagementAppStoreState { + unmFaultLogEntries: IUnmFaultLogEntriesState; + currentOpenPanel: PanelId | null; + unmFaultStatus: IUnmFaultStatusCount; +} + +const currentOpenPanelHandler: IActionHandler = (state = null, action) => { + if (action instanceof SetPanelAction) { + state = action.panelId; + } + return state; +}; + +declare module '../../../../framework/src/store/applicationStore' { + interface IApplicationStoreState { + unmFaultManagement: IUnmFaultManagementAppStoreState; + } +} + +const actionHandlers = { + unmFaultLogEntries: unmFaultLogEntriesActionHandler, + currentOpenPanel: currentOpenPanelHandler, + unmFaultStatus: unmFaultStatusHandler, +}; + +export const UnmFaultManagementAppRootHandler = combineActionHandler(actionHandlers); +export default UnmFaultManagementAppRootHandler; diff --git a/features/sdnr/odlux/odlux/apps/unmFaultManagementApp/src/index.html b/features/sdnr/odlux/odlux/apps/unmFaultManagementApp/src/index.html new file mode 100644 index 0000000..db570ca --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/unmFaultManagementApp/src/index.html @@ -0,0 +1,25 @@ + + + + + + + + + UNM Fault Management App + + + +
+ + + + + + \ No newline at end of file diff --git a/features/sdnr/odlux/odlux/apps/unmFaultManagementApp/src/models/panelId.ts b/features/sdnr/odlux/odlux/apps/unmFaultManagementApp/src/models/panelId.ts new file mode 100644 index 0000000..4fc789d --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/unmFaultManagementApp/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 = 'Dashboard' | 'FaultLog'; \ No newline at end of file diff --git a/features/sdnr/odlux/odlux/apps/unmFaultManagementApp/src/models/unmFault.ts b/features/sdnr/odlux/odlux/apps/unmFaultManagementApp/src/models/unmFault.ts new file mode 100644 index 0000000..d652101 --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/unmFaultManagementApp/src/models/unmFault.ts @@ -0,0 +1,52 @@ +/** + * ============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 FaultLog = { + id?: string; + nodeId: string; + counter: number; + objectId: string; + timestamp: { value: string }; + problem: string; + severity: null | 'Warning' | 'Minor' | 'Major' | 'Critical' | 'Indeterminate'; + sourceType?: string; + comment?: string; +}; + + +/** + * Fault status count return + */ +export type FaultsCountReturnType = { + criticals: number; + majors: number; + minors: number; + warnings: number; + indeterminate: number; +}; + +export type FaultType = { + Critical: number; + Major: number; + Minor: number; + Warning: number; + Indeterminate: number; +}; + +export type Faults = { + faults: FaultsCountReturnType; +}; diff --git a/features/sdnr/odlux/odlux/apps/unmFaultManagementApp/src/pluginUnmFaultManagement.tsx b/features/sdnr/odlux/odlux/apps/unmFaultManagementApp/src/pluginUnmFaultManagement.tsx new file mode 100644 index 0000000..b1854e0 --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/unmFaultManagementApp/src/pluginUnmFaultManagement.tsx @@ -0,0 +1,81 @@ +/** + * ============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 { SetPanelAction } from './actions/panelChangeActions'; +import UnmFaultManagementDashboard from './components/unmFaultManagementDashboard'; +import { UnmFaultManagementAppRootHandler } from './handlers/unmFaultManagementAppRootHandler'; +import { PanelId } from './models/panelId'; +import UnmFaultManagementApplication from './views/unmFaultManagementApplication'; + +const appIcon = require('./assets/icons/unmFaultManagementAppIcon.svg'); // select app icon + +let currentMountId: string | undefined = undefined; +let currentSeverity: string | undefined = undefined; + +const mapProps = () => ({ +}); + +const mapDispatch = (dispatcher: IDispatcher) => ({ + setCurrentPanel: (panelId: PanelId) => dispatcher.dispatch(new SetPanelAction(panelId)), +}); + +const UnmFaultManagementApplicationRouteAdapter = connect(mapProps, mapDispatch)((props: RouteComponentProps<{ mountId?: string }> & Connect) => { + if (currentMountId !== props.match.params.mountId) { + currentMountId = props.match.params.mountId || undefined; + } + ; + return ( + + ); +}); + +const UnmFaultManagementApplicationAlarmStatusRouteAdapter = connect(mapProps, mapDispatch)((props: RouteComponentProps<{ severity?: string }> & Connect) => { + if (currentSeverity !== props.match.params.severity) { + currentSeverity = props.match.params.severity || undefined; + } + return ( + + ); +}); + +const App = withRouter((props: RouteComponentProps) => ( + + + + + + +)); + +export function register() { + applicationManager.registerApplication({ + name: 'unmFaultManagement', + icon: appIcon, + rootComponent: App, + rootActionHandler: UnmFaultManagementAppRootHandler, + menuEntry: 'UNM Fault Management', + }); +} \ No newline at end of file diff --git a/features/sdnr/odlux/odlux/apps/unmFaultManagementApp/src/services/unmFaultStatusService.ts b/features/sdnr/odlux/odlux/apps/unmFaultManagementApp/src/services/unmFaultStatusService.ts new file mode 100644 index 0000000..9f61864 --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/unmFaultManagementApp/src/services/unmFaultStatusService.ts @@ -0,0 +1,62 @@ +/** + * ============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 { FaultType, Faults } from '../models/unmFault'; + + +export const getUnmFaultCountBySeverity = async (): Promise => { + const path = 'rests/operations/data-provider:read-status'; + const query = { + 'data-provider:input': { + 'filter': [], + 'sortorder': [{ + 'property': 'timestamp', + 'sortorder': 'descending', + }], + 'pagination': { + 'size': 20, + 'page': 1, + }, + }, + }; + const result = await requestRest>(path, { method: 'POST', body: JSON.stringify(query) }); + + let faultType: FaultType = { + Critical: 0, + Major: 0, + Minor: 0, + Warning: 0, + Indeterminate: 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, + Indeterminate: faults[0].faults.indeterminate, + }; + } + return faultType; +}; diff --git a/features/sdnr/odlux/odlux/apps/unmFaultManagementApp/src/views/unmFaultManagementApplication.tsx b/features/sdnr/odlux/odlux/apps/unmFaultManagementApp/src/views/unmFaultManagementApplication.tsx new file mode 100644 index 0000000..94b745e --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/unmFaultManagementApp/src/views/unmFaultManagementApplication.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 { AppBar, Tab, Tabs } from '@mui/material'; + +import { useApplicationDispatch, useSelectApplicationState } from '../../../../framework/src/flux/connect'; + +import { setPanelAction } from '../actions/panelChangeActions'; +import { loadAllUnmFaultStatusCountAsyncAction } from '../actions/unmFaultManagementAlarmStatusActions'; +import UnmFaultManagementDashboard from '../components/unmFaultManagementDashboard'; +import UnmFaultManagementFaultLog from '../components/unmFaultManagementFaultLog'; +import { unmFaultLogEntriesReloadAction } from '../handlers/unmFaultLogEntriesHandler'; +import { PanelId } from '../models/panelId'; + + +const UnmFaultManagementApplication: React.FC<{}> = () => { + + const panel = useSelectApplicationState(state => state.unmFaultManagement.currentOpenPanel); + + const dispatch = useApplicationDispatch(); + const onLoadFaultLogEntries = () => dispatch(unmFaultLogEntriesReloadAction); + const onLoadDashboard = () => dispatch(loadAllUnmFaultStatusCountAsyncAction); + const switchActivePanel = (panelId: PanelId) => dispatch(setPanelAction(panelId)); + + const onTogglePanel = (panelId: PanelId) => { + const nextActivePanel = panelId; + switchActivePanel(nextActivePanel); + + + switch (nextActivePanel) { + case 'Dashboard': + onLoadDashboard(); + break; + case 'FaultLog': + onLoadFaultLogEntries(); + 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); + onTogglePanel(newValue); + }; + + React.useEffect(() => { + if (panel === null) { + onTogglePanel('Dashboard'); + } + }, []); + + + return ( + <> + + + + + + + { + panel === 'Dashboard' + ? + : panel === 'FaultLog' + ? + : null + } + + ); +}; + +export default UnmFaultManagementApplication; \ No newline at end of file diff --git a/features/sdnr/odlux/odlux/apps/unmFaultManagementApp/tsconfig.json b/features/sdnr/odlux/odlux/apps/unmFaultManagementApp/tsconfig.json new file mode 100644 index 0000000..ca65092 --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/unmFaultManagementApp/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/features/sdnr/odlux/odlux/apps/unmFaultManagementApp/webpack.config.js b/features/sdnr/odlux/odlux/apps/unmFaultManagementApp/webpack.config.js new file mode 100644 index 0000000..8e40dea --- /dev/null +++ b/features/sdnr/odlux/odlux/apps/unmFaultManagementApp/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: { + unmFaultManagementApp: ["./pluginUnmFaultManagement.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://sdncweb:8080", + secure: false + }, + "/database/": { + target: "http://sdncweb:8080", + secure: false + }, + "/restconf/": { + target: "http://sdncweb:8080", + secure: false + }, + "/rests/": { + target: "http://sdncweb:8080", + secure: false + }, + "/help/": { + target: "http://sdncweb:8080", + secure: false + }, + "/websocket": { + target: "http://sdncweb:8080", + ws: true, + changeOrigin: true, + secure: false + } + } + } + }]; +} diff --git a/features/sdnr/odlux/odlux/framework/.babelrc b/features/sdnr/odlux/odlux/framework/.babelrc new file mode 100644 index 0000000..3d8cd12 --- /dev/null +++ b/features/sdnr/odlux/odlux/framework/.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/features/sdnr/odlux/odlux/framework/LICENSE b/features/sdnr/odlux/odlux/framework/LICENSE new file mode 100644 index 0000000..3556ffa --- /dev/null +++ b/features/sdnr/odlux/odlux/framework/LICENSE @@ -0,0 +1,13 @@ +/* + * ============LICENSE_START============================================================================================================= + * Copyright (c) 2018 highstreet-technolgies. + * =================================================================== + * 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=============================================================================================================== + * + */ \ No newline at end of file diff --git a/features/sdnr/odlux/odlux/framework/package.json b/features/sdnr/odlux/odlux/framework/package.json new file mode 100644 index 0000000..9973d3b --- /dev/null +++ b/features/sdnr/odlux/odlux/framework/package.json @@ -0,0 +1,59 @@ +{ + "name": "@odlux/framework", + "version": "0.1.4", + "description": "A react based modular UI framework", + "main": "index.js", + "scripts": { + "start": "webpack-dev-server --env debug", + "prebuild": "rimraf dist", + "build": "webpack --env release --config webpack.vendor.js && webpack --env release --config webpack.config.js && webpack --env release --config webpack.runner.js", + "build:run": "webpack --env release --config webpack.runner.js", + "build:dev": "webpack --env debug --config webpack.config.js", + "vendor:dev": "webpack --env debug --config webpack.vendor.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", + "peerDependencies": { + "@fortawesome/fontawesome-svg-core": "1.2.35", + "@fortawesome/free-solid-svg-icons": "5.6.3", + "@fortawesome/react-fontawesome": "0.1.14", + "@types/classnames": "2.2.6", + "@types/flux": "3.1.8", + "@types/history": "^4.7.9", + "@types/jquery": "3.3.10", + "@types/jsonwebtoken": "7.2.8", + "@types/node": "^12.0.0", + "@types/react": "17.0.37", + "@types/react-dom": "17.0.11", + "@types/react-router-dom": "5.1.7", + "jquery": "3.3.1", + "history": "^4.9.0", + "jsonwebtoken": "8.3.0", + "react": "17.0.2", + "react-dom": "17.0.2", + "react-router-dom": "5.2.0" + }, + "dependencies": { + "@babel/polyfill": "^7.0.0", + "@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", + "@mui/system": "^5.2.2", + "@types/x2js": "^3.1.0", + "chart.js": "^3.4.0", + "http-server": "^0.11.1", + "react-chartjs-2": "^3.0.3" + } +} diff --git a/features/sdnr/odlux/odlux/framework/pom.xml b/features/sdnr/odlux/odlux/framework/pom.xml new file mode 100644 index 0000000..3dd19fe --- /dev/null +++ b/features/sdnr/odlux/odlux/framework/pom.xml @@ -0,0 +1,262 @@ + + + + + 4.0.0 + + org.o-ran-sc.oam-controller.features.sdnr.odlux + sdnr-odlux-framework + 1.7.0-SNAPSHOT + jar + + SDNR ODLUX :: ${project.artifactId} + + + Apache License, Version 2.0 + http://www.apache.org/licenses/LICENSE-2.0 + + + + + ${maven.build.timestamp} + ONAP Frankfurt (Neon, mdsal ${odl.mdsal.version}) + 179.f46d0ef0(25/03/03) + ONAP SDN-R | ONF Wireless for ${distversion} - Build: ${buildtime} ${buildno} ${project.version} + + + + + + dist + odlux + + + src2/main/resources + 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 + + + + clear cache + + yarn + + initialize + + cache clean + ${project.basedir} + ${project.basedir}/../ + + + + install lerna + + yarn + + initialize + + add lerna@3.22.1 -W --exact + ${project.basedir} + ${project.basedir}/../ + + + + exec lerna bootstrap + + lerna + + initialize + + false + bootstrap + ${project.basedir} + ${project.basedir}/../ + + + + yarn build + + yarn + + + run build + + + + + + org.apache.maven.plugins + maven-jar-plugin + + + org.codehaus.mojo + properties-maven-plugin + 1.0.0 + + + initialize + + read-project-properties + + + + ${basedir}/../odlux.properties + + + + + + + com.google.code.maven-replacer-plugin + replacer + 1.5.2 + + + replace version + prepare-package + + replace + + + + + ${project.build.directory}/classes/odlux + + app.js + version.json + + + + ##odlux.version## + ${odlux.version} + + + ##buildno## + ${buildno} + + + ##build-timestamp## + ${buildtime} + + + ##odlux.framework.buildno## + ${odlux.framework.buildno} + + + ##odlux.apps.configurationApp.buildno## + ${odlux.apps.configurationApp.buildno} + + + ##odlux.apps.connectApp.buildno## + ${odlux.apps.connectApp.buildno} + + + ##odlux.apps.eventLogApp.buildno## + ${odlux.apps.eventLogApp.buildno} + + + ##odlux.apps.faultApp.buildno## + ${odlux.apps.faultApp.buildno} + + + ##odlux.apps.helpApp.buildno## + ${odlux.apps.helpApp.buildno} + + + ##odlux.apps.inventoryApp.buildno## + ${odlux.apps.inventoryApp.buildno} + + + ##odlux.apps.microwaveApp.buildno## + ${odlux.apps.microwaveApp.buildno} + + + ##odlux.apps.maintenanceApp.buildno## + ${odlux.apps.maintenanceApp.buildno} + + + ##odlux.apps.mediatorApp.buildno## + ${odlux.apps.mediatorApp.buildno} + + + ##odlux.apps.networkMapApp.buildno## + ${odlux.apps.networkMapApp.buildno} + + + ##odlux.apps.siteManagerApp.buildno## + ${odlux.apps.siteManagerApp.buildno} + + + ##odlux.apps.permanceHistoryApp.buildno## + ${odlux.apps.permanceHistoryApp.buildno} + + + + + + + diff --git a/features/sdnr/odlux/odlux/framework/src/actions/authentication.ts b/features/sdnr/odlux/odlux/framework/src/actions/authentication.ts new file mode 100644 index 0000000..1412495 --- /dev/null +++ b/features/sdnr/odlux/odlux/framework/src/actions/authentication.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 { Dispatch } from '../flux/store'; +import { Action } from '../flux/action'; +import { AuthPolicy, User } from '../models/authentication'; +import { Settings } from '../models/settings'; +import { saveInitialSettings, SetGeneralSettingsAction } from './settingsAction'; +import { endWebsocketSession } from '../services/notificationService'; +import { endUserSession, startUserSession } from '../services/userSessionService'; +import { IApplicationStoreState } from '../store/applicationStore'; + +export class UpdateUser extends Action { + + constructor(public user?: User) { + super(); + } +} + +export class UpdatePolicies extends Action { + + constructor(public authPolicies?: AuthPolicy[]) { + super(); + } +} + +export const logoutUser = () => (dispatcher: Dispatch, getState: () => IApplicationStoreState) =>{ + + const { framework:{ applicationState:{ authentication }, authenticationState: { user } } } = getState(); + + dispatcher(new UpdateUser(undefined)); + dispatcher(new SetGeneralSettingsAction(null)); + endWebsocketSession(); + endUserSession(); + localStorage.removeItem('userToken'); + + + //only call if a user is currently logged in + if (authentication === 'oauth' && user) { + + const url = window.location.origin; + window.location.href = `${url}/oauth/logout`; + } +}; + +/** + * Loads the user settings for the given user and dispatches a `saveInitialSettings` action with the result. + * @param user The user for which to load the settings. + * @param dispatcher The dispatcher function to use for dispatching the `saveInitialSettings` action. + */ +const loadUserSettings = (user: User | undefined, dispatcher: Dispatch) => { + + // fetch used, because state change for user login is not done when frameworks restRequest call is started (and is accordingly undefined -> /userdata call yields 401, unauthorized) and triggering an action from inside the handler / login event is impossible + // no timeout used, because it's bad practice to add a timeout to hopefully avoid a race condition + // hence, fetch used to simply use supplied user data for getting settings + + if (user && user.isValid) { + + fetch('/userdata', { + method: 'GET', + headers: { + 'Content-Type': 'application/json', + 'Accept': 'application/json', + 'Authorization': `${user.tokenType} ${user.token}`, + }, + }).then((res: Response)=>{ + if (res.status == 200) { + return res.json(); + } else { + return null; + } + }).then((result:Settings)=>{ + dispatcher(saveInitialSettings(result)); + }); + } +}; + +/** + * Dispatches an `UpdateUser` action with the given user and starts a user session if the user is defined. + * Also loads the user settings for the given user and dispatches a `saveInitialSettings` action with the result. + * Finally, saves the user token to local storage. + * @param user The user to be logged in. + * @param dispatcher The dispatcher function to use for dispatching the actions. + */ +export const loginUserAction = (user?: User) => (dispatcher: Dispatch) =>{ + + dispatcher(new UpdateUser(user)); + if (user) { + startUserSession(user); + loadUserSettings(user, dispatcher); + localStorage.setItem('userToken', user.toString()); + } +}; \ No newline at end of file diff --git a/features/sdnr/odlux/odlux/framework/src/actions/errorActions.ts b/features/sdnr/odlux/odlux/framework/src/actions/errorActions.ts new file mode 100644 index 0000000..ddb0d57 --- /dev/null +++ b/features/sdnr/odlux/odlux/framework/src/actions/errorActions.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========================================================================== + */ +import { Action } from '../flux/action'; + +import { ErrorInfo } from '../models/errorInfo'; +export { ErrorInfo } from '../models/errorInfo'; + +export class AddErrorInfoAction extends Action { + + constructor(public errorInfo: ErrorInfo) { + super(); + } +} + +export class RemoveErrorInfoAction extends Action { + + constructor(public errorInfo: ErrorInfo) { + super(); + } +} + +export class ClearErrorInfoAction extends Action { + + constructor() { + super(); + } +} \ No newline at end of file diff --git a/features/sdnr/odlux/odlux/framework/src/actions/loginProvider.ts b/features/sdnr/odlux/odlux/framework/src/actions/loginProvider.ts new file mode 100644 index 0000000..e646711 --- /dev/null +++ b/features/sdnr/odlux/odlux/framework/src/actions/loginProvider.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 { Action } from '../flux/action'; +import { Dispatch } from '../flux/store'; + +import { IApplicationStoreState } from '../store/applicationStore'; +import { ExternalLoginProvider } from '../models/externalLoginProvider'; + +import authenticationService from '../services/authenticationService'; + +export class SetExternalLoginProviderAction extends Action { + constructor(public externalLoginProvders: ExternalLoginProvider[] | null) { + super(); + } +} + +export const updateExternalLoginProviderAsyncActionCreator = () => async (dispatch: Dispatch, getState: () => IApplicationStoreState ) => { + const providers = await authenticationService.getAvaliableExteralProvider(); + dispatch(new SetExternalLoginProviderAction(providers || null)); +} \ No newline at end of file diff --git a/features/sdnr/odlux/odlux/framework/src/actions/menuAction.ts b/features/sdnr/odlux/odlux/framework/src/actions/menuAction.ts new file mode 100644 index 0000000..ec07965 --- /dev/null +++ b/features/sdnr/odlux/odlux/framework/src/actions/menuAction.ts @@ -0,0 +1,31 @@ +/** + * ============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 '../flux/action'; + +export class MenuAction extends Action { + constructor(public isOpen: boolean) { + super(); + } +} + +export class MenuClosedByUser extends Action { + constructor(public isClosed: boolean) { + super(); + } +} \ No newline at end of file diff --git a/features/sdnr/odlux/odlux/framework/src/actions/navigationActions.ts b/features/sdnr/odlux/odlux/framework/src/actions/navigationActions.ts new file mode 100644 index 0000000..6951b97 --- /dev/null +++ b/features/sdnr/odlux/odlux/framework/src/actions/navigationActions.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 { Action } from "../flux/action"; + +export abstract class NavigationAction extends Action { } + +export class NavigateToApplication extends NavigationAction { + + constructor(public applicationName: string, public href?: string, public state?: TState, public replace: boolean = false ) { + super(); + + } +} + +export class PushAction extends NavigationAction { + constructor(public href: string, public state?: TState) { + super(); + + } +} + +export class ReplaceAction extends NavigationAction { + constructor(public href: string, public state?: TState) { + super(); + + } +} + +export class GoAction extends NavigationAction { + constructor(public index: number) { + super(); + + } +} + +export class GoBackAction extends NavigationAction { + +} + +export class GoForwardeAction extends NavigationAction { + +} + +export class LocationChanged extends NavigationAction { + constructor(public pathname: string, public search: string, public hash: string ) { + super(); + + } +} \ No newline at end of file diff --git a/features/sdnr/odlux/odlux/framework/src/actions/settingsAction.ts b/features/sdnr/odlux/odlux/framework/src/actions/settingsAction.ts new file mode 100644 index 0000000..fa45f23 --- /dev/null +++ b/features/sdnr/odlux/odlux/framework/src/actions/settingsAction.ts @@ -0,0 +1,130 @@ +/** + * ============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 { Dispatch } from "../flux/store"; +import { Action } from "../flux/action"; +import { GeneralSettings, Settings, TableSettings, TableSettingsColumn } from "../models/settings"; +import { getUserData, saveUserData } from "../services/userdataService"; +import { startWebsocketSession, suspendWebsocketSession } from "../services/notificationService"; +import { IApplicationStoreState } from "../store/applicationStore"; + + +export class SetGeneralSettingsAction extends Action { + /** + * + */ + constructor(public areNoticationsActive: boolean | null) { + super(); + } +} + +export class SetTableSettings extends Action { + + constructor(public tableName: string, public updatedColumns: TableSettingsColumn[]) { + super(); + } +} + +export class LoadSettingsAction extends Action { + + constructor(public settings: Settings & { isInitialLoadDone: true }) { + super(); + } + +} + +export class SettingsDoneLoadingAction extends Action { + +} + +export const setGeneralSettingsAction = (value: boolean) => (dispatcher: Dispatch) => { + + dispatcher(new SetGeneralSettingsAction(value)); + + if (value) { + startWebsocketSession(); + } else { + suspendWebsocketSession(); + } +} + + +export const updateGeneralSettingsAction = (activateNotifications: boolean) => async (dispatcher: Dispatch) => { + + const value: GeneralSettings = { general: { areNotificationsEnabled: activateNotifications } }; + const result = await saveUserData("/general", JSON.stringify(value.general)); + dispatcher(setGeneralSettingsAction(activateNotifications)); + +} + +export const updateTableSettings = (tableName: string, columns: TableSettingsColumn[]) => async (dispatcher: Dispatch, getState: () => IApplicationStoreState) => { + + + //TODO: ask micha how to handle object with variable properties! + //fix for now: just safe everything! + + let {framework:{applicationState:{settings:{tables}}}} = getState(); + + tables[tableName] = { columns: columns }; + const json=JSON.stringify(tables); + + // would only save latest entry + //const json = JSON.stringify({ [tableName]: { columns: columns } }); + + const result = await saveUserData("/tables", json); + + dispatcher(new SetTableSettings(tableName, columns)); +} + +export const getGeneralSettingsAction = () => async (dispatcher: Dispatch) => { + + const result = await getUserData(); + + if (result && result.general) { + dispatcher(new SetGeneralSettingsAction(result.general.areNotificationsEnabled!)) + } +} + +export const saveInitialSettings = (settings: any) => async (dispatcher: Dispatch) => { + + const defaultSettings = {general:{ areNotificationsEnabled: false }, tables:{}}; + + const initialSettings = {...defaultSettings, ...settings}; + + if (initialSettings) { + if (initialSettings.general) { + const settingsActive = initialSettings.general.areNotificationsEnabled; + + if (settingsActive) { + startWebsocketSession(); + } else { + suspendWebsocketSession(); + } + } + + dispatcher(new LoadSettingsAction(initialSettings)); + } + else { + dispatcher(new SettingsDoneLoadingAction()); + + } + + + + +} \ No newline at end of file diff --git a/features/sdnr/odlux/odlux/framework/src/actions/snackbarActions.ts b/features/sdnr/odlux/odlux/framework/src/actions/snackbarActions.ts new file mode 100644 index 0000000..ad4d606 --- /dev/null +++ b/features/sdnr/odlux/odlux/framework/src/actions/snackbarActions.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 '../flux/action'; +import { SnackbarItem } from '../models/snackbarItem'; +import { DistributiveOmit } from '@mui/types'; + +export class AddSnackbarNotification extends Action { + + constructor(notification: DistributiveOmit) { + super(); + + this.notification = { ...notification, key: (new Date().getTime() + Math.random()) } + } + + public notification: SnackbarItem +} + +export class RemoveSnackbarNotification extends Action { + constructor(public key: number) { + super(); + } +} \ No newline at end of file diff --git a/features/sdnr/odlux/odlux/framework/src/actions/titleActions.ts b/features/sdnr/odlux/odlux/framework/src/actions/titleActions.ts new file mode 100644 index 0000000..4bcfe29 --- /dev/null +++ b/features/sdnr/odlux/odlux/framework/src/actions/titleActions.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========================================================================== + */ +import { Action } from '../flux/action'; + +import { IconType } from '../models/iconDefinition'; + +export class SetTitleAction extends Action { + + constructor(public title: string, public icon?: IconType, public appId?: string) { + super(); + } +} \ No newline at end of file diff --git a/features/sdnr/odlux/odlux/framework/src/actions/websocketAction.ts b/features/sdnr/odlux/odlux/framework/src/actions/websocketAction.ts new file mode 100644 index 0000000..0b45f7a --- /dev/null +++ b/features/sdnr/odlux/odlux/framework/src/actions/websocketAction.ts @@ -0,0 +1,26 @@ +/** + * ============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 "../flux/action"; + + +export class SetWebsocketAction extends Action { + constructor(public isConnected: boolean|null) { + super(); + } +} \ No newline at end of file diff --git a/features/sdnr/odlux/odlux/framework/src/app.css b/features/sdnr/odlux/odlux/framework/src/app.css new file mode 100644 index 0000000..9b653b3 --- /dev/null +++ b/features/sdnr/odlux/odlux/framework/src/app.css @@ -0,0 +1,17 @@ +html, body, #app { + height: 100%; + min-width: 1000px; + padding: 0px; + margin: 0px; + font-family: 'Open Sans', 'Helvetica Neue', Helvetica, Arial, sans-serif; +} +.about-table td{ + padding:0.5rem 1rem; + border-bottom: 1px solid #DDD; +} +.about-table pre { + background:#FFF; + border:1px solid #CCC; + padding:1rem; + margin: 1rem 0; +} \ No newline at end of file diff --git a/features/sdnr/odlux/odlux/framework/src/app.tsx b/features/sdnr/odlux/odlux/framework/src/app.tsx new file mode 100644 index 0000000..bbe1f9e --- /dev/null +++ b/features/sdnr/odlux/odlux/framework/src/app.tsx @@ -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 * as React from 'react'; +import * as ReactDOM from 'react-dom'; + +import { ThemeProvider, Theme, StyledEngineProvider } from '@mui/material/styles'; + +import { Frame } from './views/frame'; + +import { User } from './models/authentication'; + +import { AddErrorInfoAction } from './actions/errorActions'; +import { loginUserAction } from './actions/authentication'; + +import { applicationStoreCreator } from './store/applicationStore'; +import { ApplicationStoreProvider } from './flux/connect'; + +import { startHistoryListener } from './middleware/navigation'; +import { startSoreService } from './services/storeService'; + +import { startUserSessionService } from './services/userSessionService'; +import { startNotificationService } from './services/notificationService'; + +import { startBroadcastChannel } from './services/broadcastService'; + +import theme from './design/default'; +import '!style-loader!css-loader!./app.css'; + +declare module '@mui/material/styles' { + + interface IDesign { + id: string, + name: string, + url: string, // image url of a company logo, which will be presented in the ui header + height: number, // image height [px] as delivered by the url + width: number, // image width [px] as delivered by the url + logoHeight: number // height in [px] of the logo (see url) within the ui header + } + + interface Theme { + design?: IDesign + } + interface DeprecatedThemeOptions { + design?: IDesign + } +} + + +declare module '@mui/styles/defaultTheme' { + // eslint-disable-next-line @typescript-eslint/no-empty-interface (remove this line if you don't have the rule enabled) + interface DefaultTheme extends Theme {} +} + +export { configureApplication } from "./handlers/applicationStateHandler"; + +export const transportPCEUrl = "transportPCEUrl"; + +export const runApplication = () => { + + const initialToken = localStorage.getItem("userToken"); + const applicationStore = applicationStoreCreator(); + + startBroadcastChannel(applicationStore); + startUserSessionService(applicationStore); + + if (initialToken) { + applicationStore.dispatch(loginUserAction(User.fromString(initialToken) || undefined)); + } + + window.onerror = function (msg: string, url: string, line: number, col: number, error: Error) { + // Note that col & error are new to the HTML 5 spec and may not be + // supported in every browser. It worked for me in Chrome. + var extra = !col ? '' : '\ncolumn: ' + col; + extra += !error ? '' : '\nerror: ' + error; + + // You can view the information in an alert to see things working like this: + applicationStore.dispatch(new AddErrorInfoAction({ error, message: msg, url, line, col, info: { extra } })); + + var suppressErrorAlert = true; + // If you return true, then error alerts (like in older versions of + // Internet Explorer) will be suppressed. + return suppressErrorAlert; + }; + + + startSoreService(applicationStore); + startHistoryListener(applicationStore); + startNotificationService(applicationStore); + + const App = (): JSX.Element => ( + + + + + + + + ); + + ReactDOM.render(, document.getElementById('app')); + + + +}; diff --git a/features/sdnr/odlux/odlux/framework/src/assets/icons/About.svg b/features/sdnr/odlux/odlux/framework/src/assets/icons/About.svg new file mode 100644 index 0000000..156e36e --- /dev/null +++ b/features/sdnr/odlux/odlux/framework/src/assets/icons/About.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/features/sdnr/odlux/odlux/framework/src/assets/icons/Home.svg b/features/sdnr/odlux/odlux/framework/src/assets/icons/Home.svg new file mode 100644 index 0000000..0836714 --- /dev/null +++ b/features/sdnr/odlux/odlux/framework/src/assets/icons/Home.svg @@ -0,0 +1,28 @@ + + + + + + + + + + + + diff --git a/features/sdnr/odlux/odlux/framework/src/assets/icons/Menu.svg b/features/sdnr/odlux/odlux/framework/src/assets/icons/Menu.svg new file mode 100644 index 0000000..ea03128 --- /dev/null +++ b/features/sdnr/odlux/odlux/framework/src/assets/icons/Menu.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/features/sdnr/odlux/odlux/framework/src/assets/icons/Tools.svg b/features/sdnr/odlux/odlux/framework/src/assets/icons/Tools.svg new file mode 100644 index 0000000..1595cdc --- /dev/null +++ b/features/sdnr/odlux/odlux/framework/src/assets/icons/Tools.svg @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + diff --git a/features/sdnr/odlux/odlux/framework/src/assets/icons/User.svg b/features/sdnr/odlux/odlux/framework/src/assets/icons/User.svg new file mode 100644 index 0000000..99618cf --- /dev/null +++ b/features/sdnr/odlux/odlux/framework/src/assets/icons/User.svg @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/features/sdnr/odlux/odlux/framework/src/assets/icons/ht.Connect.png b/features/sdnr/odlux/odlux/framework/src/assets/icons/ht.Connect.png new file mode 100644 index 0000000000000000000000000000000000000000..412390c791bfb86075c7741e50916a2c55ab630e GIT binary patch literal 14989 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 Ms7hCTFbV#D0Cz^9x&QzG literal 0 HcmV?d00001 diff --git a/features/sdnr/odlux/odlux/framework/src/assets/icons/ht.Connect.svg b/features/sdnr/odlux/odlux/framework/src/assets/icons/ht.Connect.svg new file mode 100644 index 0000000..c1d74bc --- /dev/null +++ b/features/sdnr/odlux/odlux/framework/src/assets/icons/ht.Connect.svg @@ -0,0 +1,81 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/features/sdnr/odlux/odlux/framework/src/assets/images/defaultLogo.svg b/features/sdnr/odlux/odlux/framework/src/assets/images/defaultLogo.svg new file mode 100644 index 0000000..bd9ddf5 --- /dev/null +++ b/features/sdnr/odlux/odlux/framework/src/assets/images/defaultLogo.svg @@ -0,0 +1,179 @@ + +image/svg+xml \ No newline at end of file diff --git a/features/sdnr/odlux/odlux/framework/src/assets/images/defaultLogo.svg.d.ts b/features/sdnr/odlux/odlux/framework/src/assets/images/defaultLogo.svg.d.ts new file mode 100644 index 0000000..60e4856 --- /dev/null +++ b/features/sdnr/odlux/odlux/framework/src/assets/images/defaultLogo.svg.d.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========================================================================== + */ +declare const path: string; +export default path; \ No newline at end of file diff --git a/features/sdnr/odlux/odlux/framework/src/assets/images/home.svg b/features/sdnr/odlux/odlux/framework/src/assets/images/home.svg new file mode 100644 index 0000000..080d050 --- /dev/null +++ b/features/sdnr/odlux/odlux/framework/src/assets/images/home.svg @@ -0,0 +1,20 @@ + + + + + + + + + + diff --git a/features/sdnr/odlux/odlux/framework/src/assets/images/home.svg.d.ts b/features/sdnr/odlux/odlux/framework/src/assets/images/home.svg.d.ts new file mode 100644 index 0000000..7098d79 --- /dev/null +++ b/features/sdnr/odlux/odlux/framework/src/assets/images/home.svg.d.ts @@ -0,0 +1,20 @@ +/** + * ============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========================================================================== + */ + + declare const home: string; + export default home; \ No newline at end of file diff --git a/features/sdnr/odlux/odlux/framework/src/assets/images/odluxLogo.gif b/features/sdnr/odlux/odlux/framework/src/assets/images/odluxLogo.gif new file mode 100644 index 0000000000000000000000000000000000000000..ad188a8d3e1b0bd8259b510b11eeb046c6e560d0 GIT binary patch literal 5709 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 literal 0 HcmV?d00001 diff --git a/features/sdnr/odlux/odlux/framework/src/assets/images/odluxLogo.gif.d.ts b/features/sdnr/odlux/odlux/framework/src/assets/images/odluxLogo.gif.d.ts new file mode 100644 index 0000000..3613183 --- /dev/null +++ b/features/sdnr/odlux/odlux/framework/src/assets/images/odluxLogo.gif.d.ts @@ -0,0 +1,20 @@ +/** + * ============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========================================================================== + */ + +declare const odluxLogo: string; +export default odluxLogo; \ No newline at end of file diff --git a/features/sdnr/odlux/odlux/framework/src/assets/images/onapLogo.gif b/features/sdnr/odlux/odlux/framework/src/assets/images/onapLogo.gif new file mode 100644 index 0000000000000000000000000000000000000000..cd7eb8f3c6579875e6e4d8245aa161f78900bd22 GIT binary patch literal 9249 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 { + + /** + * Creates a new instance of the Event class. + */ + constructor() { + this.eventHandlers = new Array<(arg: TEventArg) => void>(); + } + + /** + * Adds an event handler to this event, so that when the event is fired the given event handler function is called. + * + * @param eventHandler The event handler function to add to this event. + * @throws {Error} Thrown if the given event handler function has already been added to this event. + */ + public addHandler = (eventHandler: (arg: TEventArg) => void): void => { + if (this.eventHandlers.indexOf(eventHandler) > -1) { + throw new Error("The given event handler is already added to this event."); + } + + this.eventHandlers.push(eventHandler); + } + + /** + * Removes an event handler from this event, so that the given event handler function will not be called anymore when the event is fired. + * + * @param eventHandler: The event handler function to remove. + * @throws {Error} Thrown if the given event handler function has not been added to this event before. + */ + public removeHandler = (eventHandler: (arg: TEventArg) => void): void => { + const index = this.eventHandlers.indexOf(eventHandler); + if (!(index > -1)) { + throw new Error("The given event handler has not been added to this event yet."); + } + + this.eventHandlers.splice(index, 1); + } + + /** + * Invokes the event and calls all event handler functions currently registered on the event. + * + * @param argument The argument for the event. The argument will be passed to all registered event handler functions. + */ + public invoke = (argument?: TEventArg): void => { + this.eventHandlers.forEach((eventHandler: (arg?: TEventArg) => void, index: number, array: Array<(arg: TEventArg) => void>): void => { + eventHandler(argument); + }); + } + + private eventHandlers: Array<(arg?: TEventArg) => void>; + +} diff --git a/features/sdnr/odlux/odlux/framework/src/components/errorDisplay.tsx b/features/sdnr/odlux/odlux/framework/src/components/errorDisplay.tsx new file mode 100644 index 0000000..d41d826 --- /dev/null +++ b/features/sdnr/odlux/odlux/framework/src/components/errorDisplay.tsx @@ -0,0 +1,131 @@ +/** + * ============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 } from '@mui/material/styles'; + +import { WithStyles } from '@mui/styles'; +import withStyles from '@mui/styles/withStyles'; +import createStyles from '@mui/styles/createStyles'; + +import Modal from '@mui/material/Modal'; +import Button from '@mui/material/Button'; +import Card from '@mui/material/Card'; +import CardActions from '@mui/material/CardActions'; +import CardContent from '@mui/material/CardContent'; +import Typography from '@mui/material/Typography'; + +import { ClearErrorInfoAction, RemoveErrorInfoAction } from '../actions/errorActions'; + +import { connect, Connect } from '../flux/connect'; + +const styles = (theme: Theme) => createStyles({ + modal: { + display: "flex", + alignItems: "center", + justifyContent: "center", + }, + paper: { + width: theme.spacing(50), + backgroundColor: theme.palette.background.paper, + boxShadow: theme.shadows[5], + padding: theme.spacing(4), + }, + card: { + minWidth: 275, + }, + bullet: { + display: 'inline-block', + margin: '0 2px', + transform: 'scale(0.8)', + }, + title: { + marginBottom: 16, + fontSize: 14, + }, + pos: { + marginBottom: 12, + }, +}); + +type ErrorDisplayProps = WithStyles & Connect; + +// function getModalStyle() { +// const top = 50 + rand(); +// const left = 50 + rand(); + +// return { +// top: `${ top }%`, +// left: `${ left }%`, +// transform: `translate(-${ top }%, -${ left }%)`, +// }; +// } + +/** + * Represents a component for formatting and displaying errors. + */ +class ErrorDisplayComponent extends React.Component { + constructor(props: ErrorDisplayProps) { + super(props); + } + + render(): JSX.Element { + const { classes, state } = this.props; + const errorInfo = state.framework.applicationState.errors.length && state.framework.applicationState.errors[state.framework.applicationState.errors.length - 1]; + + return ( + 0} + onClose={() => this.props.dispatch(new ClearErrorInfoAction())} + > + {errorInfo && +
+ + + + {errorInfo.title != null ? errorInfo.title : "Something went wrong."} + + + {errorInfo.error && errorInfo.error.toString()} + + + {errorInfo.message && errorInfo.message.toString()} + + + {errorInfo.info && errorInfo.info.componentStack && errorInfo.info.componentStack.split('\n').map(line => { + return [line,
]; + })} + {errorInfo.info && errorInfo.info.extra && errorInfo.info.extra.split('\n').map(line => { + return [line,
]; + })} +
+
+ + + +
+
||
+ } +
+ ); + } +} + +export const ErrorDisplay = withStyles(styles)(connect()(ErrorDisplayComponent)); +export default ErrorDisplay; \ No newline at end of file diff --git a/features/sdnr/odlux/odlux/framework/src/components/icons/menuIcon.tsx b/features/sdnr/odlux/odlux/framework/src/components/icons/menuIcon.tsx new file mode 100644 index 0000000..0d7d734 --- /dev/null +++ b/features/sdnr/odlux/odlux/framework/src/components/icons/menuIcon.tsx @@ -0,0 +1,29 @@ +import React from 'react'; + +type MenuIconPropsBase = { + className?: string; + size?: number | string; +}; + +type MenuIconPropsWithColor = MenuIconPropsBase & { + color: string; +}; + +type MenuIconProps = MenuIconPropsBase | MenuIconPropsWithColor; + +const MenuIcon = (props: MenuIconProps) => { + const { className, size = '30px' } = props; + const color = 'color' in props ? props.color : '#36A9E1'; + + return ( + + + + + + ); +}; + +MenuIcon.defaultName = 'MenuIcon'; + +export default MenuIcon; \ No newline at end of file diff --git a/features/sdnr/odlux/odlux/framework/src/components/logo.tsx b/features/sdnr/odlux/odlux/framework/src/components/logo.tsx new file mode 100644 index 0000000..f2bb1f5 --- /dev/null +++ b/features/sdnr/odlux/odlux/framework/src/components/logo.tsx @@ -0,0 +1,103 @@ +/** + * ============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========================================================================== + */ +/****************************************************************************** + * Copyright 2018 highstreet technologies GmbH + * + * 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. + *****************************************************************************/ + +import * as React from 'react'; +import { withRouter, RouteComponentProps } from 'react-router-dom'; + +import { Theme } from '@mui/material/styles'; // infra for styling + + +import { WithStyles } from '@mui/styles'; +import withStyles from '@mui/styles/withStyles'; +import createStyles from '@mui/styles/createStyles'; + + +const defaultLogo = require('../assets/icons/ht.Connect.svg'); + +const styles = (theme: Theme) => createStyles({ + headerLogo: { + backgroundImage: "url(" + (theme.design && theme.design.url || defaultLogo) + ")", + backgroundColor: theme.palette.primary.main, + backgroundRepeat: "no-repeat", + backgroundSize: "auto " + (theme.design && theme.design.logoHeight || 70) + "px", + height: theme.design && theme.design.logoHeight || 70, + width: theme.design ? theme.design.width / theme.design.height * theme.design.logoHeight : 220 + } +}); + +type LogoProps = RouteComponentProps<{ id: string }> & WithStyles; +interface ILogoState { + windowWidth: number +} + +class LogoComponent extends React.Component { + + private hideLogoWhenWindowWidthIsLower: number = 800; + + constructor(props: LogoProps) { + super(props); + this.state = { + windowWidth: 0 + }; + this.updateWindowDimensions = this.updateWindowDimensions.bind(this); + } + + componentDidMount(): void { + this.updateWindowDimensions(); + window.addEventListener('resize', this.updateWindowDimensions); + }; + componentWillUnmount(): void { + window.removeEventListener('resize', this.updateWindowDimensions); + }; + updateWindowDimensions(): void { + this.setState({ windowWidth: window.innerWidth }); + } + + render(): JSX.Element { + let div: JSX.Element =
; + if (this.state.windowWidth >= this.hideLogoWhenWindowWidthIsLower) { + div =
; + } else { + console.info([ + "Logo hidden, because browser window width (", + this.state.windowWidth, + "px) is lower threshold (", + this.hideLogoWhenWindowWidthIsLower, + "px)."].join('')); + } + return div; + } +} + +export const Logo = withStyles(styles)(withRouter(LogoComponent)); +export default Logo; diff --git a/features/sdnr/odlux/odlux/framework/src/components/material-table/columnModel.ts b/features/sdnr/odlux/odlux/framework/src/components/material-table/columnModel.ts new file mode 100644 index 0000000..3ed3134 --- /dev/null +++ b/features/sdnr/odlux/odlux/framework/src/components/material-table/columnModel.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 * as React from 'react'; + +export enum ColumnType { + text, + numeric, + boolean, + date, + custom +} + +type CustomControl = { + className?: string; + style?: React.CSSProperties; + rowData: TData; +} + +export type ColumnModel = { + title?: string; + disablePadding?: boolean; + width?: string | number ; + className?: string; + hide?: boolean; + style?: React.CSSProperties; + align?: 'inherit' | 'left' | 'center' | 'right' | 'justify'; + disableSorting?: boolean; + disableFilter?: boolean; +} & ({ + property: string; + type: ColumnType.custom; + customControl: React.ComponentType>; +} | { + property: keyof TData; + type: ColumnType.boolean; + labels?: { "true": string, "false": string }; +} | { + property: keyof TData; + type?: ColumnType.numeric | ColumnType.text | ColumnType.date; +}); \ No newline at end of file diff --git a/features/sdnr/odlux/odlux/framework/src/components/material-table/index.tsx b/features/sdnr/odlux/odlux/framework/src/components/material-table/index.tsx new file mode 100644 index 0000000..c1a5005 --- /dev/null +++ b/features/sdnr/odlux/odlux/framework/src/components/material-table/index.tsx @@ -0,0 +1,707 @@ +/** + * ============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 } from '@mui/material/styles'; + +import { WithStyles } from '@mui/styles'; +import withStyles from '@mui/styles/withStyles'; +import createStyles from '@mui/styles/createStyles'; + +import Table from '@mui/material/Table'; +import TableBody from '@mui/material/TableBody'; +import TableCell from '@mui/material/TableCell'; +import TableContainer from '@mui/material/TableContainer'; +import TablePagination from '@mui/material/TablePagination'; +import TableRow from '@mui/material/TableRow'; +import Paper from '@mui/material/Paper'; +import Checkbox from '@mui/material/Checkbox'; + +import { TableToolbar } from './tableToolbar'; +import { EnhancedTableHead } from './tableHead'; +import { EnhancedTableFilter } from './tableFilter'; + +import { ColumnModel, ColumnType } from './columnModel'; +import { Menu, Typography } from '@mui/material'; +import { DistributiveOmit } from '@mui/types'; + +import makeStyles from '@mui/styles/makeStyles'; + +import { SvgIconProps } from '@mui/material/SvgIcon'; + +import { DividerTypeMap } from '@mui/material/Divider'; +import { MenuItemProps } from '@mui/material/MenuItem'; +import { flexbox } from '@mui/system'; +import { RowDisabled } from './utilities'; +import { toAriaLabel } from '../../utilities/yangHelper'; +export { ColumnModel, ColumnType } from './columnModel'; + +type propType = string | number | null | undefined | (string | number)[]; +type dataType = { [prop: string]: propType }; +type resultType = { page: number, total: number, rows: TData[] }; + +export type DataCallback = (page?: number, rowsPerPage?: number, orderBy?: string | null, order?: 'asc' | 'desc' | null, filter?: { [property: string]: string }) => resultType | Promise>; + +function regExpEscape(s: string) { + return s.replace(/[|\\{}()[\]^$+*?.]/g, '\\$&'); +} + +function wildcardCheck(input: string, pattern: string) { + if (!pattern) return true; + const regex = new RegExp( + (!pattern.startsWith('*') ? '^' : '') + + pattern.split(/\*+/).map(p => p.split(/\?+/).map(regExpEscape).join('.')).join('.*') + + (!pattern.endsWith('*') ? '$' : '') + ); + return input.match(regex) !== null && input.match(regex)!.length >= 1; +} + +function desc(a: dataType, b: dataType, orderBy: string) { + if ((b[orderBy] || "") < (a[orderBy] || "")) { + return -1; + } + if ((b[orderBy] || "") > (a[orderBy] || "")) { + return 1; + } + return 0; +} + +function stableSort(array: dataType[], cmp: (a: dataType, b: dataType) => number) { + const stabilizedThis = array.map((el, index) => [el, index]) as [dataType, number][]; + stabilizedThis.sort((a, b) => { + const order = cmp(a[0], b[0]); + if (order !== 0) return order; + return a[1] - b[1]; + }); + return stabilizedThis.map(el => el[0]); +} + +function getSorting(order: 'asc' | 'desc' | null, orderBy: string) { + return order === 'desc' ? (a: dataType, b: dataType) => desc(a, b, orderBy) : (a: dataType, b: dataType) => -desc(a, b, orderBy); +} + +const styles = (theme: Theme) => createStyles({ + root: { + width: '100%', + overflow: "hidden", + marginTop: theme.spacing(3), + position: "relative", + boxSizing: "border-box", + display: "flex", + flexDirection: "column", + }, + container: { + flex: "1 1 100%" + }, + pagination: { + overflow: "hidden", + minHeight: "52px" + } +}); + +const useTableRowExtStyles = makeStyles((theme: Theme) => createStyles({ + disabled: { + color: "rgba(180, 180, 180, 0.7)", + }, +})); + +type GetStatelessComponentProps = T extends (props: infer P & { children?: React.ReactNode }) => any ? P : any; +type TableRowExtProps = GetStatelessComponentProps & { disabled: boolean }; +const TableRowExt : React.FC = (props) => { + const [disabled, setDisabled] = React.useState(true); + const classes = useTableRowExtStyles(); + + const onMouseDown = (ev: React.MouseEvent) => { + if (ev.button ===1){ + setDisabled(!disabled); + ev.preventDefault(); + ev.stopPropagation(); + } else if (props.disabled && disabled) { + ev.preventDefault(); + ev.stopPropagation(); + } + }; + + return ( + + ); +}; + +export type MaterialTableComponentState = { + order: 'asc' | 'desc'; + orderBy: string | null; + selected: any[] | null; + rows: TData[]; + total: number; + page: number; + rowsPerPage: number; + loading: boolean; + showFilter: boolean; + hiddenColumns: string[]; + filter: { [property: string]: string }; +}; + +export type TableApi = { forceRefresh?: () => Promise }; + +type MaterialTableComponentBaseProps = WithStyles & { + className?: string; + columns: ColumnModel[]; + idProperty: keyof TData | ((data: TData) => React.Key); + + //Note: used to save settings as well. Must be unique across apps. Null tableIds will not get saved to the settings + tableId: string | null; + isPopup?: boolean; + title?: string; + stickyHeader?: boolean; + allowHtmlHeader?: boolean; + defaultSortOrder?: 'asc' | 'desc'; + defaultSortColumn?: keyof TData; + enableSelection?: boolean; + disableSorting?: boolean; + disableFilter?: boolean; + customActionButtons?: { icon: React.ComponentType, tooltip?: string, ariaLabel: string, onClick: () => void, disabled?: boolean }[]; + onHandleClick?(event: React.MouseEvent, rowData: TData): void; + createContextMenu?: (row: TData) => React.ReactElement, React.ComponentType>>[]; +}; + +type MaterialTableComponentPropsWithRows = MaterialTableComponentBaseProps & { rows: TData[]; asynchronus?: boolean; }; +type MaterialTableComponentPropsWithRequestData = MaterialTableComponentBaseProps & { onRequestData: DataCallback; tableApi?: TableApi; }; +type MaterialTableComponentPropsWithExternalState = MaterialTableComponentBaseProps & MaterialTableComponentState & { + onToggleFilter: () => void; + onFilterChanged: (property: string, filterTerm: string) => void; + onHandleChangePage: (page: number) => void; + onHandleChangeRowsPerPage: (rowsPerPage: number | null) => void; + onHandleRequestSort: (property: string) => void; + onHideColumns : (columnNames: string[]) => void + onShowColumns: (columnNames: string[]) => void +}; + +type MaterialTableComponentProps = + MaterialTableComponentPropsWithRows | + MaterialTableComponentPropsWithRequestData | + MaterialTableComponentPropsWithExternalState; + +function isMaterialTableComponentPropsWithRows(props: MaterialTableComponentProps): props is MaterialTableComponentPropsWithRows { + return (props as MaterialTableComponentPropsWithRows).rows !== undefined && (props as MaterialTableComponentPropsWithRows).rows instanceof Array; +} + +function isMaterialTableComponentPropsWithRequestData(props: MaterialTableComponentProps): props is MaterialTableComponentPropsWithRequestData { + return (props as MaterialTableComponentPropsWithRequestData).onRequestData !== undefined && (props as MaterialTableComponentPropsWithRequestData).onRequestData instanceof Function; +} + +function isMaterialTableComponentPropsWithRowsAndRequestData(props: MaterialTableComponentProps): props is MaterialTableComponentPropsWithExternalState { + const propsWithExternalState = (props as MaterialTableComponentPropsWithExternalState) + return propsWithExternalState.onFilterChanged instanceof Function || + propsWithExternalState.onHandleChangePage instanceof Function || + propsWithExternalState.onHandleChangeRowsPerPage instanceof Function || + propsWithExternalState.onToggleFilter instanceof Function || + propsWithExternalState.onHideColumns instanceof Function || + propsWithExternalState.onHandleRequestSort instanceof Function +} + +// get settings in here! + + +class MaterialTableComponent extends React.Component { + + constructor(props: MaterialTableComponentProps) { + super(props); + + + const page = isMaterialTableComponentPropsWithRowsAndRequestData(this.props) ? this.props.page : 0; + const rowsPerPage = isMaterialTableComponentPropsWithRowsAndRequestData(this.props) ? this.props.rowsPerPage || 10 : 10; + + this.state = { + contextMenuInfo: { index: -1 }, + filter: isMaterialTableComponentPropsWithRowsAndRequestData(this.props) ? this.props.filter || {} : {}, + showFilter: isMaterialTableComponentPropsWithRowsAndRequestData(this.props) ? this.props.showFilter : false, + loading: isMaterialTableComponentPropsWithRowsAndRequestData(this.props) ? this.props.loading : false, + order: isMaterialTableComponentPropsWithRowsAndRequestData(this.props) ? this.props.order : this.props.defaultSortOrder || 'asc', + orderBy: isMaterialTableComponentPropsWithRowsAndRequestData(this.props) ? this.props.orderBy : this.props.defaultSortColumn || null, + selected: isMaterialTableComponentPropsWithRowsAndRequestData(this.props) ? this.props.selected : null, + rows: isMaterialTableComponentPropsWithRows(this.props) && this.props.rows.slice(page * rowsPerPage, page * rowsPerPage + rowsPerPage) || [], + total: isMaterialTableComponentPropsWithRows(this.props) && this.props.rows.length || 0, + hiddenColumns: isMaterialTableComponentPropsWithRowsAndRequestData(this.props) && this.props.hiddenColumns || [], + page, + rowsPerPage, + }; + + if (isMaterialTableComponentPropsWithRequestData(this.props)) { + this.update(); + + if (this.props.tableApi) { + this.props.tableApi.forceRefresh = () => this.update(); + } + } + } + render(): JSX.Element { + const { classes, columns, allowHtmlHeader } = this.props; + const { rows, total: rowCount, order, orderBy, selected, rowsPerPage, page, showFilter, filter } = this.state; + const emptyRows = rowsPerPage - Math.min(rowsPerPage, rowCount - page * rowsPerPage); + const getId = typeof this.props.idProperty !== "function" ? (data: TData) => ((data as { [key: string]: any })[this.props.idProperty as any as string] as string | number) : this.props.idProperty; + const toggleFilter = isMaterialTableComponentPropsWithRowsAndRequestData(this.props) ? this.props.onToggleFilter : () => { !this.props.disableFilter && this.setState({ showFilter: !showFilter }, this.update) } + + const hideColumns = isMaterialTableComponentPropsWithRowsAndRequestData(this.props) ? this.props.onHideColumns : (data: string[]) => { const newArray = [...new Set([...this.state.hiddenColumns, ...data])]; this.setState({hiddenColumns:newArray}); } + const showColumns = isMaterialTableComponentPropsWithRowsAndRequestData(this.props) ? this.props.onShowColumns : (data: string[]) => { const newArray = this.state.hiddenColumns.filter(el=> !data.includes(el)); this.setState({hiddenColumns:newArray}); } + + const allColumnsHidden = this.props.columns.length === this.state.hiddenColumns.length; + return ( + + + + + + + {showFilter && || null} + + {allColumnsHidden ? All columns of this table are hidden. : + + rows // may need ordering here + .map((entry: TData & { [RowDisabled]?: boolean, [kex: string]: any }, index) => { + const entryId = getId(entry); + const contextMenu = (this.props.createContextMenu && this.state.contextMenuInfo.index === index && this.props.createContextMenu(entry)) || null; + const isSelected = this.isSelected(entryId) || this.state.contextMenuInfo.index === index; + return ( + { + if (this.props.createContextMenu) { + this.setState({ + contextMenuInfo: { + index: -1 + } + }); + } + this.handleClick(event, entry, entryId); + }} + onContextMenu={event => { + if (this.props.createContextMenu) { + event.preventDefault(); + event.stopPropagation(); + this.setState({ contextMenuInfo: { index, mouseX: event.clientX - 2, mouseY: event.clientY - 4 } }); + } + }} + role="checkbox" + aria-checked={isSelected} + aria-label="table-row" + tabIndex={-1} + key={entryId} + selected={isSelected} + disabled={entry[RowDisabled] || false} + > + {this.props.enableSelection + ? + + + : null + } + { + + this.props.columns.map( + col => { + const style = col.width ? { width: col.width } : {}; + const tableCell = ( + + + {col.type === ColumnType.custom && col.customControl + ? + : col.type === ColumnType.boolean + ? {col.labels ? col.labels[entry[col.property] ? "true" : "false"] : String(entry[col.property])} + : {String(entry[col.property])} + } + + ); + + //show column if... + const showColumn = !this.state.hiddenColumns.includes(col.property); + return showColumn && tableCell + } + ) + } + { this.setState({ contextMenuInfo: { index: -1 } })} anchorReference="anchorPosition" keepMounted + anchorPosition={this.state.contextMenuInfo.mouseY != null && this.state.contextMenuInfo.mouseX != null ? { top: this.state.contextMenuInfo.mouseY, left: this.state.contextMenuInfo.mouseX } : undefined}> + {contextMenu} + || null} + + ); + })} + {emptyRows > 0 && ( + + + + )} + +
+
+ +
+ ); + } + + static getDerivedStateFromProps(props: MaterialTableComponentProps, state: MaterialTableComponentState & { _rawRows: {}[] }): MaterialTableComponentState & { _rawRows: {}[] } { + + if (isMaterialTableComponentPropsWithRowsAndRequestData(props)) { + return { + ...state, + rows: props.rows, + total: props.total, + orderBy: props.orderBy, + order: props.order, + filter: props.filter, + loading: props.loading, + showFilter: props.showFilter, + page: props.page, + hiddenColumns: props.hiddenColumns, + rowsPerPage: props.rowsPerPage + } + } else if (isMaterialTableComponentPropsWithRows(props) && props.asynchronus && state._rawRows !== props.rows) { + const newState = MaterialTableComponent.updateRows(props, state); + return { + ...state, + ...newState, + _rawRows: props.rows || [] + }; + } + return state; + } + + private static updateRows(props: MaterialTableComponentPropsWithRows, state: MaterialTableComponentState): { rows: {}[], total: number, page: number } { + + let data = [...(props.rows as dataType[] || [])]; + const columns = props.columns; + + const { page, rowsPerPage, order, orderBy, filter } = state; + + try { + if (state.showFilter) { + Object.keys(filter).forEach(prop => { + const column = columns.find(c => c.property === prop); + const filterExpression = filter[prop]; + + if (!column) throw new Error("Filter for not existing column found."); + + if (filterExpression != null) { + data = data.filter((val) => { + const dataValue = val[prop]; + + if (dataValue != null) { + + if (column.type === ColumnType.boolean) { + + const boolDataValue = JSON.parse(String(dataValue).toLowerCase()); + const boolFilterExpression = JSON.parse(String(filterExpression).toLowerCase()); + return boolDataValue == boolFilterExpression; + + } else if (column.type === ColumnType.text) { + + const valueAsString = String(dataValue); + const filterExpressionAsString = String(filterExpression).trim(); + if (filterExpressionAsString.length === 0) return true; + return wildcardCheck(valueAsString, filterExpressionAsString); + + } else if (column.type === ColumnType.numeric){ + + const valueAsNumber = Number(dataValue); + const filterExpressionAsString = String(filterExpression).trim(); + if (filterExpressionAsString.length === 0 || isNaN(valueAsNumber)) return true; + + if (filterExpressionAsString.startsWith('>=')) { + return valueAsNumber >= Number(filterExpressionAsString.substring(2).trim()); + } else if (filterExpressionAsString.startsWith('<=')) { + return valueAsNumber <= Number(filterExpressionAsString.substring(2).trim()); + } else if (filterExpressionAsString.startsWith('>')) { + return valueAsNumber > Number(filterExpressionAsString.substring(1).trim()); + } else if (filterExpressionAsString.startsWith('<')) { + return valueAsNumber < Number(filterExpressionAsString.substring(1).trim()); + } + } else if (column.type === ColumnType.date){ + const valueAsString = String(dataValue); + + const convertToDate = (valueAsString: string) => { + // time value needs to be padded + const hasTimeValue = /T\d{2,2}/.test(valueAsString); + const indexCollon = valueAsString.indexOf(':'); + if (hasTimeValue && (indexCollon === -1 || indexCollon >= valueAsString.length-2)) { + valueAsString = indexCollon === -1 + ? valueAsString + ":00" + : indexCollon === valueAsString.length-1 + ? valueAsString + "00" + : valueAsString += "0" + } + return new Date(Date.parse(valueAsString)); + }; + + // @ts-ignore + const valueAsDate = new Date(Date.parse(dataValue)); + const filterExpressionAsString = String(filterExpression).trim(); + + if (filterExpressionAsString.startsWith('>=')) { + return valueAsDate >= convertToDate(filterExpressionAsString.substring(2).trim()); + } else if (filterExpressionAsString.startsWith('<=')) { + return valueAsDate <= convertToDate(filterExpressionAsString.substring(2).trim()); + } else if (filterExpressionAsString.startsWith('>')) { + return valueAsDate > convertToDate(filterExpressionAsString.substring(1).trim()); + } else if (filterExpressionAsString.startsWith('<')) { + return valueAsDate < convertToDate(filterExpressionAsString.substring(1).trim()); + } + + + if (filterExpressionAsString.length === 0) return true; + return wildcardCheck(valueAsString, filterExpressionAsString); + + } + } + + return (dataValue == filterExpression) + }); + }; + }); + } + + const rowCount = data.length; + + if (page > 0 && rowsPerPage * page > rowCount) { //if result is smaller than the currently shown page, new search and repaginate + let newPage = Math.floor(rowCount / rowsPerPage); + return { + rows: data, + total: rowCount, + page: newPage + }; + } else { + data = (orderBy && order + ? stableSort(data, getSorting(order, orderBy)) + : data).slice(page * rowsPerPage, page * rowsPerPage + rowsPerPage); + + return { + rows: data, + total: rowCount, + page: page + }; + } + + + } catch (e) { + console.error(e); + return { + rows: [], + total: 0, + page: page + } + } + } + + private async update() { + if (isMaterialTableComponentPropsWithRequestData(this.props)) { + const response = await Promise.resolve( + this.props.onRequestData( + this.state.page, this.state.rowsPerPage, this.state.orderBy, this.state.order, this.state.showFilter && this.state.filter || {}) + ); + this.setState(response); + } else { + let updateResult = MaterialTableComponent.updateRows(this.props, this.state); + this.setState(updateResult); + } + } + + private onFilterChanged = (property: string, filterTerm: string) => { + if (isMaterialTableComponentPropsWithRowsAndRequestData(this.props)) { + this.props.onFilterChanged(property, filterTerm); + return; + } + if (this.props.disableFilter) return; + const colDefinition = this.props.columns && this.props.columns.find(col => col.property === property); + if (colDefinition && colDefinition.disableFilter) return; + + const filter = { ...this.state.filter, [property]: filterTerm }; + this.setState({ + filter + }, this.update); + }; + + private onHandleRequestSort = (event: React.SyntheticEvent, property: string) => { + if (isMaterialTableComponentPropsWithRowsAndRequestData(this.props)) { + this.props.onHandleRequestSort(property); + return; + } + if (this.props.disableSorting) return; + const colDefinition = this.props.columns && this.props.columns.find(col => col.property === property); + if (colDefinition && colDefinition.disableSorting) return; + + const orderBy = this.state.orderBy === property && this.state.order === 'desc' ? null : property; + const order = this.state.orderBy === property && this.state.order === 'asc' ? 'desc' : 'asc'; + this.setState({ + order, + orderBy + }, this.update); + }; + + handleSelectAllClick: () => {}; + + private onHandleChangePage = (event: any | null, page: number) => { + if (isMaterialTableComponentPropsWithRowsAndRequestData(this.props)) { + this.props.onHandleChangePage(page); + return; + } + this.setState({ + page + }, this.update); + }; + + private onHandleChangeRowsPerPage = (event: React.ChangeEvent) => { + if (isMaterialTableComponentPropsWithRowsAndRequestData(this.props)) { + this.props.onHandleChangeRowsPerPage(+(event && event.target.value)); + return; + } + const rowsPerPage = +(event && event.target.value); + if (rowsPerPage && rowsPerPage > 0) { + this.setState({ + rowsPerPage + }, this.update); + } + }; + + private isSelected(id: string | number): boolean { + let selected = this.state.selected || []; + const selectedIndex = selected.indexOf(id); + return (selectedIndex > -1); + } + + private handleClick(event: any, rowData: TData, id: string | number): void { + if (this.props.onHandleClick instanceof Function) { + this.props.onHandleClick(event, rowData); + return; + } + if (!this.props.enableSelection) { + return; + } + let selected = this.state.selected || []; + const selectedIndex = selected.indexOf(id); + if (selectedIndex > -1) { + selected = [ + ...selected.slice(0, selectedIndex), + ...selected.slice(selectedIndex + 1) + ]; + } else { + selected = [ + ...selected, + id + ]; + } + this.setState({ + selected + }); + } + + + private exportToCsv = async () => { + let file; + let data: dataType[] | null = null; + let csv: string[] = []; + + if (isMaterialTableComponentPropsWithRequestData(this.props)) { + // table with extra request handler + this.setState({ loading: true }); + const result = await Promise.resolve( + this.props.onRequestData(0, 1000, this.state.orderBy, this.state.order, this.state.showFilter && this.state.filter || {}) + ); + data = result.rows; + this.setState({ loading: true }); + } else if (isMaterialTableComponentPropsWithRowsAndRequestData(this.props)) { + // table with generated handlers note: exports data shown on current page + data = this.props.rows; + } + else { + // table with local data + data = MaterialTableComponent.updateRows(this.props, this.state).rows; + } + + if (data && data.length > 0) { + csv.push(this.props.columns.map(col => col.title || col.property).join(',') + "\r\n"); + this.state.rows && this.state.rows.forEach((row: any) => { + csv.push(this.props.columns.map(col => row[col.property]).join(',') + "\r\n"); + }); + const properties = { type: "text/csv;charset=utf-8" }; // Specify the file's mime-type. + try { + // Specify the filename using the File constructor, but ... + file = new File(csv, "export.csv", properties); + } catch (e) { + // ... fall back to the Blob constructor if that isn't supported. + file = new Blob(csv, properties); + } + } + if (!file) return; + var reader = new FileReader(); + reader.onload = function (e) { + const dataUri = reader.result as any; + const link = document.createElement("a"); + if (typeof link.download === 'string') { + link.href = dataUri; + link.download = "export.csv"; + + //Firefox requires the link to be in the body + document.body.appendChild(link); + + //simulate click + link.click(); + + //remove the link when done + document.body.removeChild(link); + } else { + window.open(dataUri); + } + } + reader.readAsDataURL(file); + + // const url = URL.createObjectURL(file); + // window.location.replace(url); + } +} + +export type MaterialTableCtorType = new () => React.Component, 'classes'>>; + +export const MaterialTable = withStyles(styles)(MaterialTableComponent); +export default MaterialTable; \ No newline at end of file diff --git a/features/sdnr/odlux/odlux/framework/src/components/material-table/showColumnDialog.tsx b/features/sdnr/odlux/odlux/framework/src/components/material-table/showColumnDialog.tsx new file mode 100644 index 0000000..ab0d465 --- /dev/null +++ b/features/sdnr/odlux/odlux/framework/src/components/material-table/showColumnDialog.tsx @@ -0,0 +1,188 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2022 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, FormControlLabel, Popover, Switch, Typography } from '@mui/material'; +import { connect, Connect, IDispatcher } from '../../flux/connect'; + +import { ColumnModel } from './columnModel'; +import { IApplicationStoreState } from '../../store/applicationStore'; +import { TableSettingsColumn } from '../../models/settings'; +import { updateTableSettings } from '../../actions/settingsAction'; + +const mapStateToProps = (state: IApplicationStoreState) => ({ + settings: state.framework.applicationState.settings, + settingsDoneLoading: state.framework.applicationState.settings.isInitialLoadDone +}); + +const mapDispatchToProps = (dispatcher: IDispatcher) => ({ + saveToSettings: (tableName: string, columns: TableSettingsColumn[]) => dispatcher.dispatch(updateTableSettings(tableName, columns)) +}) + +type DialogProps = { + columns: ColumnModel<{}>[], + settingsName: string | null, + anchorEl: HTMLElement | null; + hideColumns: (columnNames: string[]) => void + showColumns: (columnNames: string[]) => void + onClose(): void + +} & Connect; + + //TODO: figure out why everything gets triggered twice... + +const ShowColumnsDialog: React.FunctionComponent = (props) => { + + const savedSettings = props.settingsName && props.settings.tables[props.settingsName]; + + const [checkedColumns, setCheckedColumns] = React.useState<{ property: string, display: boolean, title: string | undefined }[]>([]); + + const open = Boolean(props.anchorEl); + const allColumnNames = props.columns.map(e => e.property); + + React.useEffect(() => { + + createHideShowSelection(); + + }, []); + + React.useEffect(() => { + + createHideShowSelection(); + + }, [props.settings.isInitialLoadDone]); + + + const createHideShowSelection = () => { + let columns = props.columns.map(e => { return { property: e.property, display: !Boolean(e.hide), title: e.title } }); + + + if (savedSettings) { + + if (columns.length !== savedSettings.columns.length) { + console.error("saved column length does not match current column length. Maybe a settings entry got wrongly overridden?") + } + + //overwrite column data with settings + savedSettings?.columns.forEach(el => { + let foundIndex = columns.findIndex(e => e.property == el.property); + if (columns[foundIndex] !== undefined) + columns[foundIndex].display = el.displayed; + }); + + } else { + console.warn("No settingsName set, changes will not be saved.") + } + + setCheckedColumns(columns); + + const hideColumns = columns.filter(el => !el.display).map(e => e.property); + props.hideColumns(hideColumns); + } + + + const handleChange = (propertyName: string, checked: boolean) => { + if (!checked) { + props.hideColumns([propertyName]); + } else { + props.showColumns([propertyName]) + + } + + let updatedList = checkedColumns.map(item => { + if (item.property == propertyName) { + return { ...item, display: checked }; + } + return item; + }); + + setCheckedColumns(updatedList); + }; + + const onHideAll = () => { + + switchCheckedColumns(false); + props.hideColumns(allColumnNames); + } + + const onShowAll = () => { + + switchCheckedColumns(true); + props.showColumns(allColumnNames); + } + + const onClose = () => { + + const tableColumns: TableSettingsColumn[] = checkedColumns.map(el => { + return { + property: el.property, + displayed: el.display + } + }); + + if (props.settingsName) { + props.saveToSettings(props.settingsName, tableColumns); + } + props.onClose(); + + } + + const switchCheckedColumns = (changeToValue: boolean) => { + let updatedList = checkedColumns.map(item => { + return { ...item, display: changeToValue }; + }); + + setCheckedColumns(updatedList); + + } + + return ( +
+ Hide / Show Columns +
+
+ { + checkedColumns?.map((el, i) => { + + return <> + + handleChange(el.property, e.target.checked)} />} + label={el.title || el.property} + labelPlacement="end" + /> + + }) + } +
+ + +
+
+
) +} + +export default connect(mapStateToProps, mapDispatchToProps)(ShowColumnsDialog); diff --git a/features/sdnr/odlux/odlux/framework/src/components/material-table/tableFilter.tsx b/features/sdnr/odlux/odlux/framework/src/components/material-table/tableFilter.tsx new file mode 100644 index 0000000..1b91368 --- /dev/null +++ b/features/sdnr/odlux/odlux/framework/src/components/material-table/tableFilter.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 { ColumnModel, ColumnType } from './columnModel'; +import { Theme } from '@mui/material/styles'; + + +import { WithStyles } from '@mui/styles'; +import withStyles from '@mui/styles/withStyles'; +import createStyles from '@mui/styles/createStyles'; + + +import TableCell from '@mui/material/TableCell'; +import TableRow from '@mui/material/TableRow'; +import Input from '@mui/material/Input'; +import { Select, FormControl, InputLabel, MenuItem, SelectChangeEvent } from '@mui/material'; +import { toAriaLabel } from '../../utilities/yangHelper'; + + +const styles = (theme: Theme) => createStyles({ + container: { + display: 'flex', + flexWrap: 'wrap', + }, + input: { + margin: theme.spacing(1), + }, + numberInput: { + float: "right" + } +}); + +interface IEnhancedTableFilterComponentProps extends WithStyles { + onFilterChanged: (property: string, filterTerm: string) => void; + filter: { [property: string]: string }; + columns: ColumnModel<{}>[]; + hiddenColumns: string[]; + enableSelection?: boolean; +} + +class EnhancedTableFilterComponent extends React.Component { + createSelectFilterHandler = (property: string) => (event: SelectChangeEvent) => { + this.props.onFilterChanged && this.props.onFilterChanged(property, event.target.value as string); + }; + createInputFilterHandler = (property: string) => (event: React.ChangeEvent) => { + this.props.onFilterChanged && this.props.onFilterChanged(property, event.currentTarget.value); + }; + + + render() { + const { columns, filter, classes } = this.props; + return ( + + {this.props.enableSelection + ? + + : null + } + {columns.map((col, ind) => { + const style = col.width ? { width: col.width } : {}; + const tableCell = ( + + {col.disableFilter || (col.type === ColumnType.custom) + ? null + : (col.type === ColumnType.boolean) + ? + : } + + ); + + const showColumn = !this.props.hiddenColumns.includes(col.property); + + return showColumn && tableCell; + }, this)} + + ); + } +} + +export const EnhancedTableFilter = withStyles(styles)(EnhancedTableFilterComponent); \ No newline at end of file diff --git a/features/sdnr/odlux/odlux/framework/src/components/material-table/tableHead.tsx b/features/sdnr/odlux/odlux/framework/src/components/material-table/tableHead.tsx new file mode 100644 index 0000000..d6f7b7d --- /dev/null +++ b/features/sdnr/odlux/odlux/framework/src/components/material-table/tableHead.tsx @@ -0,0 +1,127 @@ +/** + * ============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 { ColumnModel, ColumnType } from './columnModel'; +import { Theme } from '@mui/material/styles'; + +import { WithStyles } from '@mui/styles'; +import withStyles from '@mui/styles/withStyles'; +import createStyles from '@mui/styles/createStyles'; + +import TableSortLabel from '@mui/material/TableSortLabel'; +import TableCell from '@mui/material/TableCell'; +import TableHead from '@mui/material/TableHead'; +import TableRow from '@mui/material/TableRow'; +import Checkbox from '@mui/material/Checkbox'; +import Tooltip from '@mui/material/Tooltip'; + +const styles = (theme: Theme) => createStyles({ + header: { + backgroundColor: "#fafafa", + position: "sticky", + top: 0 + } +}); + + +type styles_header = WithStyles; + +interface IEnhancedTableHeadComponentProps extends styles_header { + numSelected: number | null; + onRequestSort: (event: React.SyntheticEvent, property: string) => void; + onSelectAllClick: () => void; + order: 'asc' | 'desc'; + orderBy: string | null; + rowCount: number; + columns: ColumnModel<{}>[]; + hiddenColumns: string[]; + enableSelection?: boolean; + allowHtmlHeader?: boolean; +} + +class EnhancedTableHeadComponent extends React.Component { + createSortHandler = (property: string) => (event: React.SyntheticEvent) => { + this.props.onRequestSort(event, property); + }; + + render() { + const { onSelectAllClick, order, orderBy, numSelected, rowCount, columns } = this.props; + const {classes} = this.props; + + return ( + + + { this.props.enableSelection + ? + 0 && numSelected < rowCount || undefined } + checked={ numSelected === rowCount } + onChange={ onSelectAllClick } + /> + + : null + } + { columns.map(col => { + const style = col.width ? { width: col.width } : {}; + const tableCell = ( + + { col.disableSorting || (col.type === ColumnType.custom) + ? + { col.title || col.property } + + : + + { + this.props.allowHtmlHeader ?
+ : (col.title || col.property ) + } +
+
} +
+ ); + + //show column if... + const showColumn = !this.props.hiddenColumns.includes(col.property); + + return showColumn && tableCell; + }, this) } +
+
+ ); + } +} + +export const EnhancedTableHead = withStyles(styles)(EnhancedTableHeadComponent); \ No newline at end of file diff --git a/features/sdnr/odlux/odlux/framework/src/components/material-table/tableToolbar.tsx b/features/sdnr/odlux/odlux/framework/src/components/material-table/tableToolbar.tsx new file mode 100644 index 0000000..143b802 --- /dev/null +++ b/features/sdnr/odlux/odlux/framework/src/components/material-table/tableToolbar.tsx @@ -0,0 +1,191 @@ +/** + * ============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, lighten } from '@mui/material/styles'; + +import { WithStyles } from '@mui/styles'; +import withStyles from '@mui/styles/withStyles'; +import createStyles from '@mui/styles/createStyles'; + +import IconButton from '@mui/material/IconButton'; +import Tooltip from '@mui/material/Tooltip'; +import Toolbar from '@mui/material/Toolbar'; +import Typography from '@mui/material/Typography'; +import DeleteIcon from '@mui/icons-material/Delete'; +import MoreIcon from '@mui/icons-material/MoreVert'; +import FilterListIcon from '@mui/icons-material/FilterList'; +import MenuItem from '@mui/material/MenuItem'; +import Menu from '@mui/material/Menu'; +import { SvgIconProps } from '@mui/material/SvgIcon'; +import { Button } from '@mui/material'; +import { ColumnModel } from './columnModel'; +import ShowColumnsDialog from './showColumnDialog' + +const styles = (theme: Theme) => createStyles({ + root: { + paddingRight: theme.spacing(1), + }, + highlight: + theme.palette.mode === 'light' + ? { + color: theme.palette.secondary.main, + backgroundColor: lighten(theme.palette.secondary.light, 0.85), + } + : { + color: theme.palette.text.primary, + backgroundColor: theme.palette.secondary.dark, + }, + spacer: { + flex: '1 1 100%', + }, + actions: { + color: theme.palette.text.secondary, + display: "flex", + flex: "auto", + flexDirection: "row" + }, + title: { + flex: '0 0 auto', + }, + menuButton: { + marginLeft: -12, + marginRight: 20, + }, +}); + +interface ITableToolbarComponentProps extends WithStyles { + numSelected: number | null; + title?: string; + tableId: string | null; + customActionButtons?: { icon: React.ComponentType, tooltip?: string, ariaLabel: string, onClick: () => void, disabled?: boolean }[]; + columns: ColumnModel<{}>[]; + onHideColumns: (columnNames: string[]) => void + onShowColumns: (columnNames: string[]) => void + onToggleFilter: () => void; + onExportToCsv: () => void; +} + +class TableToolbarComponent extends React.Component { + + + constructor(props: ITableToolbarComponentProps) { + super(props); + + this.state = { + anchorEl: null, + anchorElDialog: null + }; + } + + private handleMenu = (event: React.MouseEvent) => { + this.setState({ anchorEl: event.currentTarget }); + }; + + private handleClose = () => { + this.setState({ anchorEl: null }); + }; + + private showColumnsDialog = (event: React.MouseEvent) =>{ + this.setState({ anchorElDialog: this.state.anchorEl }); + } + + private onCloseDialog = () =>{ + this.setState({ anchorElDialog: null }); + + } + + render() { + const { numSelected, classes } = this.props; + const open = !!this.state.anchorEl; + const buttonPrefix = this.props.tableId !== null ? this.props.tableId : 'table'; + return ( + <> + 0 ? classes.highlight : ''} `} > +
+ {numSelected && numSelected > 0 ? ( + + {numSelected} selected + + ) : ( + + {this.props.title || null} + + )} +
+
+
+ {this.props.customActionButtons + ? this.props.customActionButtons.map((action, ind) => ( + + action.onClick()} + size="large"> + + + + )) + : null} + {numSelected && numSelected > 0 ? ( + + + + + + ) : ( + + { this.props.onToggleFilter && this.props.onToggleFilter() }} + size="large"> + + + + )} + + + + + + + { this.props.onExportToCsv(); this.handleClose()}}>Export as CSV + { this.showColumnsDialog(e); this.handleClose()}}>Hide/show columns + +
+ + + + ); + } +} + +export const TableToolbar = withStyles(styles)(TableToolbarComponent); \ No newline at end of file diff --git a/features/sdnr/odlux/odlux/framework/src/components/material-table/utilities.ts b/features/sdnr/odlux/odlux/framework/src/components/material-table/utilities.ts new file mode 100644 index 0000000..e2fda76 --- /dev/null +++ b/features/sdnr/odlux/odlux/framework/src/components/material-table/utilities.ts @@ -0,0 +1,357 @@ +/** + * ============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, IActionHandler } from '../../flux/action'; +import { Dispatch } from '../../flux/store'; + +import { AddErrorInfoAction } from '../../actions/errorActions'; +import { IApplicationStoreState } from '../../store/applicationStore'; + +export const RowDisabled = Symbol("RowDisabled"); +import { DataCallback } from "."; + +export interface IExternalTableState { + order: 'asc' | 'desc'; + orderBy: string | null; + selected: any[] | null; + hiddenColumns: string[] + rows: (TData & { [RowDisabled]?: boolean })[]; + total: number; + page: number; + rowsPerPage: number; + loading: boolean; + showFilter: boolean; + filter: { [property: string]: string }; + preFilter: { [property: string]: string }; +} + +export type ExternalMethodes = { + reloadAction: (dispatch: Dispatch, getAppState: () => IApplicationStoreState) => Promise; + createActions: (dispatch: Dispatch, skipRefresh?: boolean) => { + onRefresh: () => void; + onHandleRequestSort: (orderBy: string) => void; + onHandleExplicitRequestSort: (property: string, sortOrder: "asc" | "desc") => void; + onToggleFilter: (refresh?: boolean | undefined) => void; + onFilterChanged: (property: string, filterTerm: string) => void; + onHandleChangePage: (page: number) => void; + onHandleChangeRowsPerPage: (rowsPerPage: number | null) => void; + onHideColumns: (columnName: string[]) => void; + onShowColumns: (columnName: string[]) => void; + onClearFilters: () => void; + }, + createPreActions: (dispatch: Dispatch, skipRefresh?: boolean) => { + onPreFilterChanged: (preFilter: { + [key: string]: string; + }) => void; + }; + createProperties: (state: IApplicationStoreState) => IExternalTableState; + actionHandler: IActionHandler, Action>; +} + + +/** Create an actionHandler and actions for external table states. */ +export function createExternal(callback: DataCallback, selectState: (appState: IApplicationStoreState) => IExternalTableState) : ExternalMethodes ; +export function createExternal(callback: DataCallback, selectState: (appState: IApplicationStoreState) => IExternalTableState, disableRow: (data: TData) => boolean) : ExternalMethodes; +export function createExternal(callback: DataCallback, selectState: (appState: IApplicationStoreState) => IExternalTableState, disableRow?: (data: TData) => boolean) : ExternalMethodes { + + //#region Actions + abstract class TableAction extends Action { } + + + class RequestSortAction extends TableAction { + constructor(public orderBy: string) { + super(); + } + } + + class RequestExplicitSortAction extends TableAction { + constructor(public propertyName: string, public sortOrder: "asc" | "desc") { + super(); + } + } + + class SetSelectedAction extends TableAction { + constructor(public selected: TData[] | null) { + super(); + } + } + + class SetPageAction extends TableAction { + constructor(public page: number) { + super(); + } + } + + class SetRowsPerPageAction extends TableAction { + constructor(public rowsPerPage: number) { + super(); + } + } + + class SetPreFilterChangedAction extends TableAction { + constructor(public preFilter: { [key: string]: string }) { + super(); + } + } + + class SetFilterChangedAction extends TableAction { + constructor(public filter: { [key: string]: string }) { + super(); + } + } + + class SetShowFilterAction extends TableAction { + constructor(public show: boolean) { + super(); + } + } + + class RefreshAction extends TableAction { + constructor() { + super(); + } + } + + class SetResultAction extends TableAction { + constructor(public result: { page: number, total: number, rows: TData[] }) { + super(); + } + } + + class HideColumnsAction extends TableAction{ + constructor(public property: string[]){ + super(); + } + } + + class ShowColumnsAction extends TableAction{ + constructor(public property: string[]){ + super(); + } + } + + // #endregion + + //#region Action Handler + const externalTableStateInit: IExternalTableState = { + order: 'asc', + orderBy: null, + selected: null, + hiddenColumns:[], + rows: [], + total: 0, + page: 0, + rowsPerPage: 10, + loading: false, + showFilter: false, + filter: {}, + preFilter: {} + }; + + const externalTableStateActionHandler: IActionHandler> = (state = externalTableStateInit, action) => { + if (!(action instanceof TableAction)) return state; + if (action instanceof RefreshAction) { + state = { + ...state, + loading: true + } + } else if (action instanceof SetResultAction) { + state = { + ...state, + loading: false, + rows: disableRow + ? action.result.rows.map((row: TData) => ({...row, [RowDisabled]: disableRow(row) })) + : action.result.rows, + total: action.result.total, + page: action.result.page, + } + } else if (action instanceof RequestSortAction) { + state = { + ...state, + loading: true, + orderBy: state.orderBy === action.orderBy && state.order === 'desc' ? null : action.orderBy, + order: state.orderBy === action.orderBy && state.order === 'asc' ? 'desc' : 'asc', + } + } else if (action instanceof RequestExplicitSortAction) { + state = { + ...state, + loading: true, + orderBy: action.propertyName, + order: action.sortOrder + } + } + else if (action instanceof SetShowFilterAction) { + state = { + ...state, + loading: true, + showFilter: action.show + } + } else if (action instanceof SetPreFilterChangedAction) { + state = { + ...state, + loading: true, + preFilter: action.preFilter + } + } else if (action instanceof SetFilterChangedAction) { + state = { + ...state, + loading: true, + filter: action.filter + } + } else if (action instanceof SetPageAction) { + state = { + ...state, + loading: true, + page: action.page + } + } else if (action instanceof SetRowsPerPageAction) { + state = { + ...state, + loading: true, + rowsPerPage: action.rowsPerPage + } + } + else if (action instanceof HideColumnsAction){ + + //merge arrays, remove duplicates + const newArray = [...new Set([...state.hiddenColumns, ...action.property])] + state = {...state, hiddenColumns: newArray}; + } + else if(action instanceof ShowColumnsAction){ + + const newArray = state.hiddenColumns.filter(el=> !action.property.includes(el)); + state = {...state, hiddenColumns: newArray}; + } + + return state; + } + + //const createTableAction(tableAction) + + //#endregion + const reloadAction = (dispatch: Dispatch, getAppState: () => IApplicationStoreState) => { + dispatch(new RefreshAction()); + const ownState = selectState(getAppState()); + const filter = { ...ownState.preFilter, ...(ownState.showFilter && ownState.filter || {}) }; + return Promise.resolve(callback(ownState.page, ownState.rowsPerPage, ownState.orderBy, ownState.order, filter)).then(result => { + + if (ownState.page > 0 && ownState.rowsPerPage * ownState.page > result.total) { //if result is smaller than the currently shown page, new search and repaginate + + let newPage = Math.floor(result.total / ownState.rowsPerPage); + + Promise.resolve(callback(newPage, ownState.rowsPerPage, ownState.orderBy, ownState.order, filter)).then(result1 => { + dispatch(new SetResultAction(result1)); + }); + + + } else { + dispatch(new SetResultAction(result)); + } + + + }).catch(error => dispatch(new AddErrorInfoAction(error))); + }; + + const createPreActions = (dispatch: Dispatch, skipRefresh: boolean = false) => { + return { + onPreFilterChanged: (preFilter: { [key: string]: string }) => { + dispatch(new SetPreFilterChangedAction(preFilter)); + (!skipRefresh) && dispatch(reloadAction); + } + }; + } + + const createActions = (dispatch: Dispatch, skipRefresh: boolean = false) => { + return { + onRefresh: () => { + dispatch(reloadAction); + }, + onHandleRequestSort: (orderBy: string) => { + dispatch((dispatch: Dispatch) => { + dispatch(new RequestSortAction(orderBy)); + (!skipRefresh) && dispatch(reloadAction); + }); + }, + onHandleExplicitRequestSort: (property: string, sortOrder: "asc" | "desc") => { + dispatch((dispatch: Dispatch) => { + dispatch(new RequestExplicitSortAction(property, sortOrder)); + (!skipRefresh) && dispatch(reloadAction); + }); + }, + onToggleFilter: (refresh?: boolean) => { + dispatch((dispatch: Dispatch, getAppState: () => IApplicationStoreState) => { + const { showFilter } = selectState(getAppState()); + dispatch(new SetShowFilterAction(!showFilter)); + if (!skipRefresh && (refresh === undefined || refresh)) + dispatch(reloadAction); + }); + }, + onFilterChanged: (property: string, filterTerm: string) => { + dispatch((dispatch: Dispatch, getAppState: () => IApplicationStoreState) => { + let { filter } = selectState(getAppState()); + filter = { ...filter, [property]: filterTerm }; + dispatch(new SetFilterChangedAction(filter)); + (!skipRefresh) && dispatch(reloadAction); + }); + }, + onHandleChangePage: (page: number) => { + dispatch((dispatch: Dispatch) => { + dispatch(new SetPageAction(page)); + (!skipRefresh) && dispatch(reloadAction); + }); + }, + onHandleChangeRowsPerPage: (rowsPerPage: number | null) => { + dispatch((dispatch: Dispatch) => { + dispatch(new SetRowsPerPageAction(rowsPerPage || 10)); + (!skipRefresh) && dispatch(reloadAction); + }); + }, + onHideColumns: (columnName: string[]) =>{ + dispatch((dispatch: Dispatch) => { + dispatch(new HideColumnsAction(columnName)); + }) + }, + onShowColumns: (columnName: string[]) =>{ + dispatch((dispatch: Dispatch) => { + dispatch(new ShowColumnsAction(columnName)); + }) + }, + onClearFilters: () => { + dispatch((dispatch: Dispatch) => { + let filter = { }; + dispatch(new SetFilterChangedAction(filter)); + }); + }, + // selected: + }; + }; + + const createProperties = (state: IApplicationStoreState) => { + return { + ...selectState(state) + } + } + + return { + reloadAction: reloadAction, + createActions: createActions, + createProperties: createProperties, + createPreActions: createPreActions, + actionHandler: externalTableStateActionHandler, + } +} + diff --git a/features/sdnr/odlux/odlux/framework/src/components/material-ui/index.ts b/features/sdnr/odlux/odlux/framework/src/components/material-ui/index.ts new file mode 100644 index 0000000..096e443 --- /dev/null +++ b/features/sdnr/odlux/odlux/framework/src/components/material-ui/index.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 { ListItemLink } from './listItemLink'; +export { Panel } from './panel'; +export { ToggleButton, ToggleButtonClassKey } from './toggleButton'; +export { TreeView, TreeItem, TreeViewCtorType} from './treeView'; +export { Loader } from './loader'; diff --git a/features/sdnr/odlux/odlux/framework/src/components/material-ui/listItemLink.tsx b/features/sdnr/odlux/odlux/framework/src/components/material-ui/listItemLink.tsx new file mode 100644 index 0000000..626cb89 --- /dev/null +++ b/features/sdnr/odlux/odlux/framework/src/components/material-ui/listItemLink.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 * as React from 'react'; +import { NavLink, Link, Route } from 'react-router-dom'; + +import ListItem from '@mui/material/ListItem'; +import ListItemIcon from '@mui/material/ListItemIcon'; +import ListItemText from '@mui/material/ListItemText'; + +import { Theme } from '@mui/material/styles'; +import { WithStyles } from '@mui/styles'; +import withStyles from '@mui/styles/withStyles'; +import createStyles from '@mui/styles/createStyles'; +import { toAriaLabel } from '../../utilities/yangHelper'; +import { IconType } from '../../models/iconDefinition'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; + +const styles = (theme: Theme) => createStyles({ + active: { + backgroundColor: theme.palette.action.selected + } +}); + +export interface IListItemLinkProps extends WithStyles { + icon: IconType | null; + primary: string | React.ComponentType; + secondary?: React.ComponentType; + to: string; + exact?: boolean; + external?: boolean; +} + +export const ListItemLink = withStyles(styles)((props: IListItemLinkProps) => { + const { icon, primary: Primary, secondary: Secondary, classes, to, exact = false, external=false } = props; + const renderLink = (itemProps: any): JSX.Element => ( + props.external ? : + ); + + const customIconHeight = 22; + const ariaLabel = typeof Primary === 'string' ? toAriaLabel("link-to-"+Primary) : toAriaLabel("link-to-"+Primary.displayName); + + //create menu icon, either using an faIcon or a link to a custom svg icon + //moved to one place for easier usage + const listItemIcon = icon && ( typeof icon === 'string' ? : ); + + return ( + <> + + { icon + ? { listItemIcon } + : null + } + { typeof Primary === 'string' + ? + : + } + + { Secondary + ? + : null + } + + ); + } +); + +export default ListItemLink; + diff --git a/features/sdnr/odlux/odlux/framework/src/components/material-ui/loader.tsx b/features/sdnr/odlux/odlux/framework/src/components/material-ui/loader.tsx new file mode 100644 index 0000000..bd523e1 --- /dev/null +++ b/features/sdnr/odlux/odlux/framework/src/components/material-ui/loader.tsx @@ -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========================================================================== + */ + + +import * as React from "react"; + +import { Theme } from '@mui/material/styles'; + +import { WithStyles } from '@mui/styles'; +import withStyles from '@mui/styles/withStyles'; +import createStyles from '@mui/styles/createStyles'; + +const styles = (theme: Theme) => createStyles({ + "@keyframes spin": { + "0%": { transform: "rotate(0deg)" }, + "100%": { transform: "rotate(360deg)" }, + }, + loader: { + border: `16px solid ${theme.palette.grey.A200}`, + borderTop: `16px solid ${theme.palette.secondary.main}`, + borderRadius: "50%", + width: "120px", + height: "120px", + animation: "$spin 2s linear infinite", + } +}); + +const LoaderComponent: React.FC> = (props) => { + return ( +
+ ); +}; + +export const Loader = withStyles(styles)(LoaderComponent); diff --git a/features/sdnr/odlux/odlux/framework/src/components/material-ui/panel.tsx b/features/sdnr/odlux/odlux/framework/src/components/material-ui/panel.tsx new file mode 100644 index 0000000..6d192d2 --- /dev/null +++ b/features/sdnr/odlux/odlux/framework/src/components/material-ui/panel.tsx @@ -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========================================================================== + */ +import * as React from 'react'; + +import { Theme } from '@mui/material/styles'; + +import { WithStyles } from '@mui/styles'; +import withStyles from '@mui/styles/withStyles'; +import createStyles from '@mui/styles/createStyles'; + +import { Accordion, AccordionSummary, AccordionDetails, Typography, AccordionActions } from '@mui/material'; + +import ExpandMoreIcon from '@mui/icons-material/ExpandMore'; +import { SvgIconProps } from '@mui/material/SvgIcon'; + +const styles = (theme: Theme) => createStyles({ + accordion: { + // background: theme.palette.secondary.dark, + // color: theme.palette.primary.contrastText + }, + detail: { + // background: theme.palette.background.paper, + // color: theme.palette.text.primary, + position: "relative", + flexDirection: 'column' + }, + text: { + // color: theme.palette.common.white, + // fontSize: "1rem" + }, +}); + +type PanalProps = WithStyles & { + activePanel: string | null, + panelId: string, + title: string, + customActionButtons?: JSX.Element[]; + onToggle: (panelId: string | null) => void; +} + +const PanelComponent: React.SFC = (props) => { + const { classes, activePanel, onToggle } = props; + return ( + onToggle(props.panelId)} > + }> + {props.title} + + + {props.children} + + {props.customActionButtons + ? + {props.customActionButtons} + + : null} + + ); +}; + +export const Panel = withStyles(styles)(PanelComponent); +export default Panel; \ No newline at end of file diff --git a/features/sdnr/odlux/odlux/framework/src/components/material-ui/snackDisplay.tsx b/features/sdnr/odlux/odlux/framework/src/components/material-ui/snackDisplay.tsx new file mode 100644 index 0000000..437784c --- /dev/null +++ b/features/sdnr/odlux/odlux/framework/src/components/material-ui/snackDisplay.tsx @@ -0,0 +1,74 @@ +/** + * ============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 { IApplicationStoreState } from '../../store/applicationStore'; +import { Connect, connect, IDispatcher } from '../../flux/connect'; +import { RemoveSnackbarNotification } from '../../actions/snackbarActions'; + +import { WithSnackbarProps, withSnackbar } from 'notistack'; + +const mapProps = (state: IApplicationStoreState) => ({ + notifications: state.framework.applicationState.snackBars +}); + +const mapDispatch = (dispatcher: IDispatcher) => ({ + removeSnackbar: (key: number) => { + dispatcher.dispatch(new RemoveSnackbarNotification(key)); + } +}); + +type DisplaySnackbarsComponentProps = Connect & WithSnackbarProps; + +class DisplaySnackbarsComponent extends React.Component { + private displayed: number[] = []; + + private storeDisplayed = (id: number) => { + this.displayed = [...this.displayed, id]; + }; + + public shouldComponentUpdate({ notifications: newSnacks = [] }: DisplaySnackbarsComponentProps) { + + const { notifications: currentSnacks } = this.props; + let notExists = false; + for (let i = 0; i < newSnacks.length; i++) { + if (notExists) continue; + notExists = notExists || !currentSnacks.filter(({ key }) => newSnacks[i].key === key).length; + } + return notExists; + } + + componentDidUpdate() { + const { notifications = [] } = this.props; + + notifications.forEach(notification => { + if (this.displayed.includes(notification.key)) return; + const options = notification.options || {}; + this.props.enqueueSnackbar(notification.message, options); + this.storeDisplayed(notification.key); + this.props.removeSnackbar(notification.key); + }); + } + + render() { + return null; + } +} + +const DisplayStackbars = withSnackbar(connect(mapProps, mapDispatch)(DisplaySnackbarsComponent)); +export default DisplayStackbars; \ No newline at end of file diff --git a/features/sdnr/odlux/odlux/framework/src/components/material-ui/toggleButton.tsx b/features/sdnr/odlux/odlux/framework/src/components/material-ui/toggleButton.tsx new file mode 100644 index 0000000..54f14a7 --- /dev/null +++ b/features/sdnr/odlux/odlux/framework/src/components/material-ui/toggleButton.tsx @@ -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========================================================================== + */ + +import * as React from 'react'; +import classNames from 'classnames'; +import { Theme, alpha } from '@mui/material/styles'; +import { WithStyles } from '@mui/styles'; +import withStyles from '@mui/styles/withStyles'; +import createStyles from '@mui/styles/createStyles'; +import ButtonBase from '@mui/material/ButtonBase'; + + +export const styles = (theme: Theme) => createStyles({ + /* Styles applied to the root element. */ + root: { + ...theme.typography.button, + height: 32, + minWidth: 48, + margin: 0, + padding: `${theme.spacing(1 - 4)} ${theme.spacing(1.5)}`, + borderRadius: 2, + willChange: 'opacity', + color: alpha(theme.palette.action.active, 0.38), + '&:hover': { + textDecoration: 'none', + // Reset on mouse devices + backgroundColor: alpha(theme.palette.text.primary, 0.12), + '@media (hover: none)': { + backgroundColor: 'transparent', + }, + '&.Mui-disabled': { + backgroundColor: 'transparent', + }, + }, + '&:not(:first-child)': { + borderTopLeftRadius: 0, + borderBottomLeftRadius: 0, + }, + '&:not(:last-child)': { + borderTopRightRadius: 0, + borderBottomRightRadius: 0, + }, + }, + /* Styles applied to the root element if `disabled={true}`. */ + disabled: { + color: alpha(theme.palette.action.disabled, 0.12), + }, + /* Styles applied to the root element if `selected={true}`. */ + selected: { + color: theme.palette.action.active, + '&:after': { + content: '""', + display: 'block', + position: 'absolute', + overflow: 'hidden', + borderRadius: 'inherit', + width: '100%', + height: '100%', + left: 0, + top: 0, + pointerEvents: 'none', + zIndex: 0, + backgroundColor: 'currentColor', + opacity: 0.38, + }, + '& + &:before': { + content: '""', + display: 'block', + position: 'absolute', + overflow: 'hidden', + width: 1, + height: '100%', + left: 0, + top: 0, + pointerEvents: 'none', + zIndex: 0, + backgroundColor: 'currentColor', + opacity: 0.12, + }, + }, + /* Styles applied to the `label` wrapper element. */ + label: { + width: '100%', + display: 'inherit', + alignItems: 'inherit', + justifyContent: 'inherit', + }, +}); + +export type ToggleButtonClassKey = 'disabled' | 'root' | 'label' | 'selected'; + +interface IToggleButtonProps extends WithStyles { + className?: string; + component?: React.ReactType; + disabled?: boolean; + disableFocusRipple?: boolean; + disableRipple?: boolean; + selected?: boolean; + type?: string; + value?: any; + onClick?: (event: React.FormEvent, value?: any) => void; + onChange?: (event: React.FormEvent, value?: any) => void; +} + +class ToggleButtonComponent extends React.Component { + handleChange = (event: React.FormEvent) => { + const { onChange, onClick, value } = this.props; + + event.stopPropagation(); + if (onClick) { + onClick(event, value); + if (event.isDefaultPrevented()) { + return; + } + } + + if (onChange) { + onChange(event, value); + } + event.preventDefault(); + }; + + render() { + const { + children, + className: classNameProp, + classes, + disableFocusRipple, + disabled, + selected, + ...other + } = this.props; + + const className = classNames( + classes.root, + { + [classes.disabled]: disabled, + [classes.selected]: selected, + }, + classNameProp, + ); + + return ( + + {children} + + ); + } + public static defaultProps = { + disabled: false, + disableFocusRipple: false, + disableRipple: false, + }; + + public static muiName = 'ToggleButton'; +} + +export const ToggleButton = withStyles(styles, { name: 'MuiToggleButton' })(ToggleButtonComponent); +export default ToggleButton; \ No newline at end of file diff --git a/features/sdnr/odlux/odlux/framework/src/components/material-ui/toggleButtonGroup.tsx b/features/sdnr/odlux/odlux/framework/src/components/material-ui/toggleButtonGroup.tsx new file mode 100644 index 0000000..bdabe0d --- /dev/null +++ b/features/sdnr/odlux/odlux/framework/src/components/material-ui/toggleButtonGroup.tsx @@ -0,0 +1,40 @@ +/** + * ============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 classNames from 'classnames'; +import { Theme } from '@mui/material/styles'; + +import { WithStyles } from '@mui/styles'; +import withStyles from '@mui/styles/withStyles'; +import createStyles from '@mui/styles/createStyles'; + +export const styles = (theme: Theme) => createStyles({ + /* Styles applied to the root element. */ + root: { + transition: theme.transitions.create('background,box-shadow'), + background: 'transparent', + borderRadius: 2, + overflow: 'hidden', + }, + /* Styles applied to the root element if `selected={true}` or `selected="auto" and `value` set. */ + selected: { + background: theme.palette.background.paper, + boxShadow: theme.shadows[2], + }, +}); + diff --git a/features/sdnr/odlux/odlux/framework/src/components/material-ui/treeView.tsx b/features/sdnr/odlux/odlux/framework/src/components/material-ui/treeView.tsx new file mode 100644 index 0000000..5c23909 --- /dev/null +++ b/features/sdnr/odlux/odlux/framework/src/components/material-ui/treeView.tsx @@ -0,0 +1,380 @@ +/** + * ============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 } from '@mui/material/styles'; + +import { makeStyles, WithStyles, WithTheme } from '@mui/styles'; +import withStyles from '@mui/styles/withStyles'; +import createStyles from '@mui/styles/createStyles'; + +import { List, ListItem, TextField, ListItemText, ListItemIcon, Typography } from '@mui/material'; +import { DistributiveOmit } from '@mui/types'; + +import withTheme from '@mui/styles/withTheme'; + +import { SvgIconProps } from '@mui/material/SvgIcon'; +import FileIcon from '@mui/icons-material/InsertDriveFile'; +import CloseIcon from '@mui/icons-material/ExpandLess'; +import OpenIcon from '@mui/icons-material/ExpandMore'; +import FolderIcon from '@mui/icons-material/Folder'; + +declare module '@mui/styles/defaultTheme' { + // eslint-disable-next-line @typescript-eslint/no-empty-interface (remove this line if you don't have the rule enabled) + interface DefaultTheme extends Theme {} +} + +const styles = (theme: Theme) => createStyles({ + root: { + padding: 0, + paddingBottom: 8, + paddingTop: 8, + }, + search: { + padding: `0px ${theme.spacing(1)}` + } +}); + +export enum SearchMode { + OnKeyDown = 1, + OnEnter =2 +} + +export type TreeItem = { + disabled?: boolean; + icon?: React.ComponentType; + iconClass?: string; + content: string; + contentClass?: string; + children?: TreeItem[]; + value?: TData; +} + +export type ExternalTreeItem = TreeItem & { + isMatch?: boolean; +} + + +type TreeViewComponentState = { + /** All indices of all expanded Items */ + expandedItems: ExternalTreeItem[]; + /** The index of the active iten or undefined if no item is active. */ + activeItem?: ExternalTreeItem; + /** The search term or undefined if search is currently not active. */ + searchTerm?: string; + searchTermValue?: string; +} + +type TreeViewComponentBaseProps = WithTheme & WithStyles & { + className?: string; + items: TreeItem[]; + useFolderIcons?: boolean; + enableSearchBar?: boolean; + autoExpandFolder?: boolean; + style?: React.CSSProperties; + itemHeight?: number; + depthOffset?: number; + searchMode?: SearchMode; + +} + +type TreeViewComponentWithInternalStateProps = TreeViewComponentBaseProps & { + initialSearchTerm? : string; + onItemClick?: (item: TreeItem) => void; + onFolderClick?: (item: TreeItem) => void; +} + +type TreeViewComponentWithExternalSearchProps = TreeViewComponentBaseProps & { + items: ExternalTreeItem[]; + initialSearchTerm? : string; + searchTerm: string; + onSearch: (searchTerm: string) => void; + onItemClick?: (item: TreeItem) => void; + onFolderClick?: (item: TreeItem) => void; +} + +type TreeViewComponentWithExternalStateProps = TreeViewComponentBaseProps & TreeViewComponentState & { + items: ExternalTreeItem[]; + initialSearchTerm? : string; + searchTerm: string; + onSearch: (searchTerm: string) => void; + onItemClick: (item: TreeItem) => void; + onFolderClick: (item: TreeItem) => void; +} + +type TreeViewComponentProps = + | TreeViewComponentWithInternalStateProps + | TreeViewComponentWithExternalSearchProps + | TreeViewComponentWithExternalStateProps; + +function isTreeViewComponentWithExternalSearchProps(props: TreeViewComponentProps): props is TreeViewComponentWithExternalSearchProps { + const propsWithExternalState = (props as TreeViewComponentWithExternalStateProps) + return ( + propsWithExternalState.onSearch instanceof Function && + propsWithExternalState.onFolderClick === undefined && + propsWithExternalState.expandedItems === undefined && + propsWithExternalState.searchTerm !== undefined + ); +} + +function isTreeViewComponentWithExternalStateProps(props: TreeViewComponentProps): props is TreeViewComponentWithExternalStateProps { + const propsWithExternalState = (props as TreeViewComponentWithExternalStateProps) + return ( + propsWithExternalState.onSearch instanceof Function && + propsWithExternalState.onFolderClick instanceof Function && + propsWithExternalState.expandedItems !== undefined && + propsWithExternalState.searchTerm !== undefined + ); +} + +class TreeViewComponent extends React.Component, TreeViewComponentState> { + + /** + * Initializes a new instance. + */ + constructor(props: TreeViewComponentProps) { + super(props); + + this.state = { + expandedItems: [], + activeItem: undefined, + searchTerm: undefined, + searchTermValue: props.initialSearchTerm + }; + } + + render(): JSX.Element { + this.itemIndex = 0; + const { searchTerm , searchTermValue} = this.state; + const { children, items, enableSearchBar } = this.props; + + return ( +
+ {children} + {enableSearchBar && || null} + {enableSearchBar && (searchTerm === undefined || searchTerm.length===0 )&& Please search for an inventory identifier or use *.} + + {this.renderItems(items, searchTerm && searchTerm.toLowerCase())} + +
+ ); + } + + private itemIndex: number = 0; + private renderItems = (items: TreeItem[], searchTerm: string | undefined, depth: number = 1, forceRender: boolean = true) => { + + return items.reduce((acc, item) => { + + const children = item.children; // this.props.childrenProperty && ((item as any)[this.props.childrenProperty] as TData[]); + const childrenJsx = children && this.renderItems(children, searchTerm, depth + 1, this.state.expandedItems.indexOf(item) > -1); + + const expanded = !isTreeViewComponentWithExternalStateProps(this.props) && searchTerm + ? childrenJsx && childrenJsx.length > 0 + : !children + ? false + : this.state.expandedItems.indexOf(item) > -1; + const isFolder = children !== undefined; + + const itemJsx = this.renderItem(item, searchTerm, depth, isFolder, expanded || false, forceRender); + itemJsx && acc.push(itemJsx); + + if (isFolder && expanded && childrenJsx) { + acc.push(...childrenJsx); + } + return acc; + + }, [] as JSX.Element[]); + } + private renderItem = (item: ExternalTreeItem , searchTerm: string | undefined, depth: number, isFolder: boolean, expanded: boolean, forceRender: boolean): JSX.Element | null => { + const styles = { + item: { + paddingLeft: (((this.props.depthOffset || 0) + depth) * Number(this.props.theme.spacing(3).replace("px", ''))), + backgroundColor: this.state.activeItem === item ? this.props.theme.palette.action.selected : undefined, + height: this.props.itemHeight || undefined, + cursor: item.disabled ? 'not-allowed' : 'pointer', + color: item.disabled ? this.props.theme.palette.text.disabled : this.props.theme.palette.text.primary, + overflow: 'hidden', + transform: 'translateZ(0)', + } + }; + + const text = item.content || ''; // need to keep track of search + const search_array = searchTerm?.split("*"); + const index = search_array?.findIndex(function (_str: String) { + return _str.length > 0; + }) || 0; + const firstSearchSubString = search_array ? search_array[index] : ""; + const matchIndex = firstSearchSubString ? text.toLowerCase().indexOf(firstSearchSubString) : -1; + + const hasStarInSearch = search_array ? search_array.length > 1 : false; + const isSearchStringWithStar = hasStarInSearch && firstSearchSubString?.length > 0 || false; + + const searchTermLength = firstSearchSubString && firstSearchSubString.length || 0; + + const handleClickCreator = (isIcon: boolean) => (event: React.SyntheticEvent) => { + if (item.disabled) return; + event.preventDefault(); + event.stopPropagation(); + if (isFolder && (this.props.autoExpandFolder || isIcon)) { + this.props.onFolderClick ? this.props.onFolderClick(item) : this.onFolderClick(item); + } else { + this.props.onItemClick ? this.props.onItemClick(item) : this.onItemClick(item); + } + }; + + return ((searchTerm && (matchIndex > -1 || expanded || (!isTreeViewComponentWithExternalStateProps(this.props) && item.isMatch || depth === 1)) || !searchTerm || forceRender) + ? ( + + + { // display the left icon + (this.props.useFolderIcons && {isFolder ? : }) || + (item.icon && ())} + + + { // highlight search result + isSearchStringWithStar && matchIndex > -1 + ? + {text} + )} /> + : matchIndex > -1 + ? + {text.substring(0, matchIndex)} + + {text.substring(matchIndex, matchIndex + searchTermLength)} + + {text.substring(matchIndex + searchTermLength)} + )} /> + : + {text} + )} /> + } + + { // display the right icon, depending on the state + !isFolder ? null : expanded ? () : ()} + + ) + : null + ); + } + + private onFolderClick = (item: TreeItem) => { + // toggle items with children + if (this.state.searchTerm) return; + const indexOfItemToToggle = this.state.expandedItems.indexOf(item); + if (indexOfItemToToggle === -1) { + this.setState({ + expandedItems: [...this.state.expandedItems, item], + }); + } else { + this.setState({ + expandedItems: [ + ...this.state.expandedItems.slice(0, indexOfItemToToggle), + ...this.state.expandedItems.slice(indexOfItemToToggle + 1), + ] + }); + } + }; + + private onItemClick = (item: TreeItem) => { + // activate items without children + this.setState({ + activeItem: item, + }); + }; + + private onSearchKeyDown = (event: React.KeyboardEvent) => { + const enterMode = this.props.searchMode === SearchMode.OnEnter; + + if (enterMode && event.keyCode === 13) { + event.preventDefault(); + event.stopPropagation(); + + enterMode && this.setState({ + searchTerm: this.state.searchTermValue + }); + + if (isTreeViewComponentWithExternalSearchProps(this.props) || isTreeViewComponentWithExternalStateProps(this.props)) { + this.props.onSearch(this.state.searchTermValue || ""); + } + } + } + + private onChangeSearchText = (event: React.ChangeEvent) => { + event.preventDefault(); + event.stopPropagation(); + + const keyDownMode = (!this.props.searchMode || this.props.searchMode === SearchMode.OnKeyDown); + + this.setState(keyDownMode + ? { + searchTerm: event.target.value, + searchTermValue: event.target.value, + } as any : { + searchTermValue: event.target.value, + }) as any; + + if ((isTreeViewComponentWithExternalSearchProps(this.props) || isTreeViewComponentWithExternalStateProps(this.props)) && keyDownMode) { + this.props.onSearch(event.target.value); + } + }; + + static getDerivedStateFromProps(props: TreeViewComponentProps, state: TreeViewComponentState): TreeViewComponentState { + if (isTreeViewComponentWithExternalStateProps(props)) { + return { + ...state, + expandedItems: props.expandedItems || [], + activeItem: props.activeItem, + searchTerm: props.searchTerm + }; + } else if (isTreeViewComponentWithExternalSearchProps(props)) { + return { + ...state, + searchTerm: props.searchTerm, + }; + } + return state; + } + + public static defaultProps = { + useFolderIcons: false, + enableSearchBar: false, + autoExpandFolder: false, + depthOffset: 0 + } +} + +export type TreeViewCtorType = new () => React.Component, 'theme'|'classes'>>; + +export const TreeView = withTheme(withStyles(styles)(TreeViewComponent)); +export default TreeView; \ No newline at end of file diff --git a/features/sdnr/odlux/odlux/framework/src/components/navigationMenu.tsx b/features/sdnr/odlux/odlux/framework/src/components/navigationMenu.tsx new file mode 100644 index 0000000..bcc191a --- /dev/null +++ b/features/sdnr/odlux/odlux/framework/src/components/navigationMenu.tsx @@ -0,0 +1,218 @@ +/** + * ============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 } from '@mui/material/styles'; +import classNames from 'classnames'; + +import { WithStyles } from '@mui/styles'; +import withStyles from '@mui/styles/withStyles'; +import createStyles from '@mui/styles/createStyles'; + +import Drawer from '@mui/material/Drawer'; +import List from '@mui/material/List'; + +import Divider from '@mui/material/Divider'; + +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { faProjectDiagram } from '@fortawesome/free-solid-svg-icons'; + +import ListItemLink from '../components/material-ui/listItemLink'; + +import { connect, Connect } from '../flux/connect'; +import { MenuAction } from '../actions/menuAction'; +import { transportPCEUrl } from '../app'; + +const aboutIcon = require('../assets/icons/About.svg'); +const homeIcon = require('../assets/icons/Home.svg'); +const loginIcon = require('../assets/icons/User.svg'); +const settingsIcon = require('../assets/icons/Tools.svg'); + +const drawerWidth = 240; + +const extraLinks = (window as any)._odluxExtraLinks as [string, string][]; + +const styles = (theme: Theme) => createStyles({ + drawerPaper: { + position: 'relative', + width: drawerWidth, + }, + toolbar: theme.mixins.toolbar as any, + + drawerOpen: { + width: drawerWidth, + transition: theme.transitions.create('width', { + easing: theme.transitions.easing.sharp, + duration: theme.transitions.duration.enteringScreen, + }), + }, + drawerClose: { + transition: theme.transitions.create('width', { + easing: theme.transitions.easing.sharp, + duration: theme.transitions.duration.leavingScreen, + }), + overflowX: 'hidden', + width: theme.spacing(7), + [theme.breakpoints.up('sm')]: { + width: theme.spacing(9), + }, + }, + drawer: { + + }, + menu: { + flex: "1 0 0%", + }, + optLinks: { + borderTop: "2px solid #cfcfcf", + display: "flex", + flexDirection: "row", + flexWrap: "wrap", + justifyContent: "space-around" + }, + link: { + margin: theme.spacing(1)+1, + fontSize: theme.typography.fontSize-2, + }, +}); + +const tabletWidthBreakpoint = 768; + +export const NavigationMenu = withStyles(styles)(connect()(({ classes, state, dispatch }: WithStyles & Connect & Connect) => { + const { user } = state.framework.authenticationState; + const isOpen = state.framework.applicationState.isMenuOpen; + const closedByUser = state.framework.applicationState.isMenuClosedByUser; + const transportUrl = state.framework.applicationState.transportpceUrl; + + const [responsive, setResponsive] = React.useState(false); + + //collapse menu on mount if necessary + React.useEffect(()=>{ + + if(isOpen && window.innerWidth <= tabletWidthBreakpoint){ + + setResponsive(true); + dispatch(new MenuAction(false)); + } + + },[]); + + React.useEffect(() => { + + function handleResize() { + if (user && user.isValid) { + if (window.innerWidth < tabletWidthBreakpoint && !responsive) { + setResponsive(true); + if (!closedByUser) { + dispatch(new MenuAction(false)); + } + + } else if (window.innerWidth > tabletWidthBreakpoint && responsive) { + setResponsive(false); + if (!closedByUser) { + dispatch(new MenuAction(true)); + } + + } + } + } + window.addEventListener("resize", handleResize); + + + return () => { + window.removeEventListener("resize", handleResize); + } + }) + + React.useEffect(()=>{ + // trigger a resize if menu changed in case elements have to re-arrange + window.dispatchEvent(new Event('menu-resized')); + }, [isOpen]) + + let menuItems = state.framework.applicationRegistration && Object.keys(state.framework.applicationRegistration).map(key => { + const reg = state.framework.applicationRegistration[key]; + return reg && ( + + ) || null; + }) || null; + + if(transportUrl.length>0){ + + const transportPCELink = ; + + const linkFound = menuItems.find(obj => obj.key === "microwave"); + + if (linkFound) { + const index = menuItems.indexOf(linkFound); + menuItems.splice(index + 1, 0, transportPCELink); + } else { + menuItems.push(transportPCELink); + } + } + + + return ( + + {user && user.isValid && <> +
+ { /* https://fiffty.github.io/react-treeview-mui/ */} + + + + { + menuItems + } + + + {(false && process.env.NODE_ENV === "development") + ? <> + + + + : null + } + + {isOpen && extraLinks &&
+ {extraLinks.map(linkInfo => ({linkInfo[0]}))} +
|| null} + || null + } + ) +})); + +export default NavigationMenu; \ No newline at end of file diff --git a/features/sdnr/odlux/odlux/framework/src/components/objectDump/index.tsx b/features/sdnr/odlux/odlux/framework/src/components/objectDump/index.tsx new file mode 100644 index 0000000..10a0547 --- /dev/null +++ b/features/sdnr/odlux/odlux/framework/src/components/objectDump/index.tsx @@ -0,0 +1,205 @@ +/** + * ============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 makeStyles from '@mui/styles/makeStyles'; + +export const getTypeName = (obj: any): string => { + if (obj == null) { + return obj === undefined ? "Undefined" : "Null"; + } + return Object.prototype.toString.call(obj).slice(8, -1); +}; + +const isObjectLike = (obj: any) => { + return typeof obj === "object" && obj !== null; +}; + +const isBoolean = (obj: any) => { + return obj === true || obj === false || + (isObjectLike(obj) && getTypeName(obj) === "Boolean"); +}; + +const isNumber = (obj: any) => { + return typeof obj === "number" || + (isObjectLike(obj) && getTypeName(obj) === "Number"); +}; + +const isString = (obj: any) => { + return typeof obj === "string" || + (isObjectLike(obj) && getTypeName(obj) === "String"); +}; + +const isNull = (obj: any) => { + return obj === null; +}; + +const isDate = (obj: any): boolean => { + return isObjectLike(obj) && (obj instanceof Date); +}; + +const useSimpleTableStyles = makeStyles({ + root: { + }, + table: { + fontFamily: "verdana, arial, helvetica, sans-serif", + borderSpacing: "3px", + borderCollapse: "separate", + marginLeft: "30px" + }, + label: { + cursor: "pointer", + }, + th: { + textAlign: "left", + color: "white", + padding: "5px", + backgroundColor: "#cccccc", + + }, + td: { + verticalAlign: "top", + padding: "0.5rem 1rem", + border: "2px solid #DDD" + }, + object: { + }, + objectTh: { + backgroundColor: "#cccccc", + }, + objectTd: { + padding: "0.5rem 1rem", + border: "2px solid #DDD" + }, + chevron: { + '&:before': { + borderStyle: 'solid', + borderWidth: '0.25em 0.25em 0 0', + content: '\'\'', + display: 'inline-block', + height: '0.45em', + left: '0.15em', + position: 'relative', + top: '0.15em', + transform: 'rotate(-45deg)', + transition: 'all 0.3s', + verticalAlign: 'top', + width: '0.45em', + } + + }, + right: { + '&:before': { + left: '0', + transform: 'rotate(45deg)', + } + }, + bottom: { + '&:before': { + left: '0', + transform: 'rotate(135deg)', + } + }, +}); + + +type SimpleTableProps = { + classNameTh?: string; + label?: JSX.Element | string | null; + cols?: number; + expand?: boolean; + ariaLabel?: string; +} + +const SimpleTable: React.FC = (props) => { + const { label = '', cols = 2, expand = true, classNameTh, children } = props; + const [isExpanded, setIsExpanded] = React.useState(expand); + + const classes = useSimpleTableStyles(); + + React.useEffect(() => { + setIsExpanded(expand); + }, [expand]); + + const handleClick = () => { + setIsExpanded(!isExpanded); + }; + + return ( + + {label && ( + + + + ) || null + } + {isExpanded && {children} || null} +
+ { label } +
+ ); +}; + + +type ObjectRendererProps = { + className?: string; + label?: JSX.Element | string | null; + expand?: boolean; + object: { [key: string]: any }; + ariaLabel?: string; +}; + +const ObjectRenderer: React.FC = (props) => { + const { object, className, label = 'Object', expand = true } = props; + const classes = useSimpleTableStyles(); + + return ( + + { + Object.keys(object).map(key => { + return ( + + {String(key)} + {renderObject(object[key], "sub-element")} + + ); + }) + } + + ); +}; + + +type ArrayRendererProps = { + label?: JSX.Element | string | null; + extraRenderer?: { [label: string]: React.ComponentType<{ label?: JSX.Element | string | null; object: any; }> }; + description?: string; + object: any; +}; + +const ArrayRenderer: React.FC = (props) => { + + return null; +}; + +export const renderObject = (object: any, ariaLabel?: string): JSX.Element | string => { + if (isString(object) || isNumber(object) || isBoolean(object)) { + return String(object); + } + return ; +}; diff --git a/features/sdnr/odlux/odlux/framework/src/components/routing/appFrame.tsx b/features/sdnr/odlux/odlux/framework/src/components/routing/appFrame.tsx new file mode 100644 index 0000000..aa22f17 --- /dev/null +++ b/features/sdnr/odlux/odlux/framework/src/components/routing/appFrame.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 } from '../../flux/connect'; + +import { SetTitleAction } from '../../actions/titleActions'; +import { AddErrorInfoAction } from '../../actions/errorActions'; + +import { IconType } from '../../models/iconDefinition'; + +export interface IAppFrameProps { + title: string; + icon?: IconType; + appId?: string +} + +/** + * Represents a component to wich will embed each single app providing the + * functionality to update the title and implement an exeprion border. + */ +export class AppFrame extends React.Component { + + public render(): JSX.Element { + return ( +
+ { this.props.children } +
+ ) + } + + public componentDidMount() { + this.props.dispatch(new SetTitleAction(this.props.title, this.props.icon, this.props.appId)); + } + public componentDidCatch(error: Error | null, info: object) { + this.props.dispatch(new AddErrorInfoAction({ error, info })); + } +} + +export default connect()(AppFrame); \ No newline at end of file diff --git a/features/sdnr/odlux/odlux/framework/src/components/settings/general.tsx b/features/sdnr/odlux/odlux/framework/src/components/settings/general.tsx new file mode 100644 index 0000000..ffd516b --- /dev/null +++ b/features/sdnr/odlux/odlux/framework/src/components/settings/general.tsx @@ -0,0 +1,110 @@ +/** + * ============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 React from 'react'; +import { Button, FormControlLabel, Switch, Typography } from '@mui/material'; +import makeStyles from '@mui/styles/makeStyles'; +import { SettingsComponentProps } from '../../models/settings'; +import { connect, Connect, IDispatcher } from '../../flux/connect'; +import { IApplicationStoreState } from '../../store/applicationStore'; +import { getGeneralSettingsAction, SetGeneralSettingsAction, updateGeneralSettingsAction } from '../../actions/settingsAction'; +import { sendMessage, SettingsMessage } from '../../services/broadcastService'; + + +type props = Connect & SettingsComponentProps; + +const mapProps = (state: IApplicationStoreState) => ({ + settings: state.framework.applicationState.settings, + user: state.framework.authenticationState.user?.user + +}); + +const mapDispatch = (dispatcher: IDispatcher) => ({ + + updateSettings :(activateNotifications: boolean) => dispatcher.dispatch(updateGeneralSettingsAction(activateNotifications)), + getSettings: () =>dispatcher.dispatch(getGeneralSettingsAction()), + }); + +const styles = makeStyles({ + sectionMargin: { + marginTop: "30px", + marginBottom: "15px" + }, + elementMargin: { + marginLeft: "10px" + }, + buttonPosition:{ + position: "absolute", + right: "32%" + } + }); + +const General : React.FunctionComponent = (props) =>{ + +const classes = styles(); + +const [areWebsocketsEnabled, setWebsocketsEnabled] = React.useState(props.settings.general.areNotificationsEnabled || false); + +React.useEffect(()=>{ + props.getSettings(); +},[]); + +React.useEffect(()=>{ + if(props.settings.general.areNotificationsEnabled!==null) + setWebsocketsEnabled(props.settings.general.areNotificationsEnabled) +},[props.settings]); + +const onWebsocketsChange = (event: React.ChangeEvent, newValue: boolean) =>{ + setWebsocketsEnabled(newValue); + } + +const onSave = (e: React.MouseEvent) =>{ + + e.preventDefault(); + const message: SettingsMessage = {key: 'general', enableNotifications: areWebsocketsEnabled, user: props.user!}; + sendMessage(message, "odlux_settings"); + props.updateSettings(areWebsocketsEnabled); + props.onClose(); +} + +const onCancel = (e: React.MouseEvent) =>{ + e.preventDefault(); + props.onClose(); + +} + + + return
+ + Enable Notifications + + } + label="Enable Notifications" + labelPlacement="end" + /> +
+ + +
+
+} + +export const GeneralUserSettings = connect(mapProps, mapDispatch)(General); +export default GeneralUserSettings; \ No newline at end of file diff --git a/features/sdnr/odlux/odlux/framework/src/components/titleBar.tsx b/features/sdnr/odlux/odlux/framework/src/components/titleBar.tsx new file mode 100644 index 0000000..40c0fc7 --- /dev/null +++ b/features/sdnr/odlux/odlux/framework/src/components/titleBar.tsx @@ -0,0 +1,233 @@ +/** + * ============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 { Theme } from '@mui/material/styles'; +import { WithStyles } from '@mui/styles'; +import withStyles from '@mui/styles/withStyles'; +import createStyles from '@mui/styles/createStyles'; +import AppBar from '@mui/material/AppBar'; +import Toolbar from '@mui/material/Toolbar'; +import Typography from '@mui/material/Typography'; +import Button from '@mui/material/Button'; +import IconButton from '@mui/material/IconButton'; +import AccountCircle from '@mui/icons-material/AccountCircle'; +import MenuItem from '@mui/material/MenuItem'; +import Menu from '@mui/material/Menu'; + +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { faBan } from '@fortawesome/free-solid-svg-icons'; +import { faDotCircle } from '@fortawesome/free-solid-svg-icons'; + +import { logoutUser } from '../actions/authentication'; +import { PushAction, ReplaceAction } from '../actions/navigationActions'; + +import { connect, Connect, IDispatcher } from '../flux/connect'; +import { MenuAction, MenuClosedByUser } from '../actions/menuAction'; + +import MenuIcon from './icons/menuIcon'; +import Logo from './logo'; + +const styles = (theme: Theme) => createStyles({ + appBar: { + zIndex: theme.zIndex.drawer + 1, + }, + grow: { + flexGrow: 1, + }, + menuButton: { + marginLeft: -12, + marginRight: 20, + }, + icon: { + marginLeft: 16, + marginRight: 8, + marginBottom: -2, + }, + connected: { + color: "green" + }, + notConnected: { + color: "red" + }, + notificationInfo: { + marginLeft: 5 + } +}); + +const mapDispatch = (dispatcher: IDispatcher) => { + return { + logout: () => { + dispatcher.dispatch(logoutUser()); + dispatcher.dispatch(new ReplaceAction("/login")); + }, + openSettings : () =>{ + dispatcher.dispatch(new PushAction("/settings")); + }, + toggleMainMenu: (value: boolean, value2: boolean) => { + dispatcher.dispatch(new MenuAction(value)); + dispatcher.dispatch(new MenuClosedByUser(value2)) + } + } +}; + +type TitleBarProps = RouteComponentProps<{}> & WithStyles & Connect + +class TitleBarComponent extends React.Component { + + constructor(props: TitleBarProps) { + super(props); + this.state = { + anchorEl: null + } + + } + render(): JSX.Element { + const { classes, state, history, location } = this.props; + const open = !!this.state.anchorEl; + let toolbarElements: Array; + toolbarElements = []; + + // create notificationInfo element + const notificationInfo = state.framework.applicationState.isWebsocketAvailable != undefined ? + (state.framework.applicationState.isWebsocketAvailable ? + Notifications | : Notifications |) + : Notifications N/A |; + + + // add notificationInfo element before help + if (state.framework.applicationRegistration) { + let isNotificationInfoAdded = false; + Object.keys(state.framework.applicationRegistration).map(key => { + const reg = state.framework.applicationRegistration[key]; + if (reg && reg.statusBarElement) { + if (key === "help") { + isNotificationInfoAdded = true; + toolbarElements.push(notificationInfo); + } + toolbarElements.push(); + } + }); + + // add notificationInfo in case help wasn't found + if (!isNotificationInfoAdded) { + toolbarElements.push(notificationInfo); + } + } + + const stateIcon = state.framework.applicationState.icon; + const customIconHeight = 22; + const icon = !stateIcon + ? null + : (typeof stateIcon === 'string' + ? + : ) + + + return ( + + + + + + + + {icon} + {state.framework.applicationState.title} + +
+ { + // render toolbar + toolbarElements.map((item) => { + return item + }) + } + + {state.framework.authenticationState.user + ? (
+ + + {/* Profile */} + { + this.props.openSettings(); + this.closeMenu(); }}>Settings + { + this.props.logout(); + this.closeMenu(); + }}>Logout + +
) + : ()} +
+
+ ); + }; + + private toggleMainMenu = (event: React.MouseEvent) => { + console.log(this.props); + if (this.props.state.framework.authenticationState.user && this.props.state.framework.authenticationState.user.isValid) { + const isMainMenuOpen = this.props.state.framework.applicationState.isMenuOpen + const isClosedByUser = this.props.state.framework.applicationState.isMenuClosedByUser + this.props.toggleMainMenu(!isMainMenuOpen, !isClosedByUser); + } + } + + private openMenu = (event: React.MouseEvent) => { + this.setState({ anchorEl: event.currentTarget }); + }; + + private closeMenu = () => { + this.setState({ anchorEl: null }); + }; +} + +//todo: ggf. https://github.com/acdlite/recompose verwenden zur Vereinfachung + +export const TitleBar = withStyles(styles)(withRouter(connect(undefined, mapDispatch)(TitleBarComponent))); +export default TitleBar; \ No newline at end of file diff --git a/features/sdnr/odlux/odlux/framework/src/design/default.ts b/features/sdnr/odlux/odlux/framework/src/design/default.ts new file mode 100644 index 0000000..c4a8118 --- /dev/null +++ b/features/sdnr/odlux/odlux/framework/src/design/default.ts @@ -0,0 +1,81 @@ +/** + * ============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========================================================================== + */ +/****************************************************************************** + * Copyright 2018 highstreet technologies GmbH + * + * 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. + *****************************************************************************/ + +import { createTheme, adaptV4Theme } from '@mui/material/styles'; +import onapLogo from '../assets/images/onapLogo.gif' + +const theme = createTheme(adaptV4Theme({ + design: { + id: "onap", + name: "Open Networking Automation Plattform (ONAP)", + url: onapLogo, + height: 49, + width: 229, + logoHeight: 32, + }, + palette: { + primary: { + light: "#eeeeee", + main: "#ffffff", + dark: "#e0e0e0", + contrastText: "#07819B" + }, + secondary: { + light: "rgba(7, 129, 155, 94)", + main: "rgba(7, 129, 155, 201)", + dark: "#07819B", + contrastText: "#ffffff" + }, + }, + overrides: { //temp fix for labels turning white after material new version (palette primary color) + MuiFormLabel: { + root: { + "&.Mui-focused": { + color: "rgba(143,143,143,1)" + } + }, + + focused: {} + }, + MuiInput: { + underline: { + + "&:after": { + borderBottom: "2px solid #444444" + } + } + } + }, +})); + +export default theme; \ No newline at end of file diff --git a/features/sdnr/odlux/odlux/framework/src/favicon.ico b/features/sdnr/odlux/odlux/framework/src/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..a8a5d31ca2127272bd1b49c61e1e2bfe9cd881d4 GIT binary patch literal 1150 zcmbu9Jxjwe5XPgnYLQ}T@nv*ya8a~_V6~G7zAfEcN*4#k!PRziD1HN%Iyve`=;l(W zUqR5p)fXHri1ufm9wCa8nm@_iJ$FxX$t8d1`uvy4I6qFgy@Ye)&bb-iJ>(I?y>QO^ zvLn`ewjc2A_!y+A4-7zmf3yQ0eDX_QTZ4xOXf|VP@QFV^59KoK?!xvq1#Um{`p z!2#^-m_9gYvQU6lOIca#c1=D%L%j~ERLpOE9X2+Mr<^p`$A`rSDNA#_zZ-sa1=*}| zHaB5w%k+F6IvtBqtoVw(xPbe6!-HR#*3;U7FHB9`*HM9Hm!_$-D8f!J<>YTz^WioJg_dRBD5sF2ao{qToIyYzhP>*EN?6b2NcYY2_OTXhc z8bA5Ue`p9EAEWQ+=x6?;qi}m0W2#k9{{KC{!2e7ARR90nAFX|OXnWNe=uRvzL%VJJ zR4vriUS8nj#O`mXi|+5u4NOcJH{g;B3s9-R{=U7Ru`!F$`w8#!^t3lM*VlHIT3_7Y Tm;JSeyEHiop_bu4@K^Z>N1EU- literal 0 HcmV?d00001 diff --git a/features/sdnr/odlux/odlux/framework/src/flux/action.ts b/features/sdnr/odlux/odlux/framework/src/flux/action.ts new file mode 100644 index 0000000..7689025 --- /dev/null +++ b/features/sdnr/odlux/odlux/framework/src/flux/action.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========================================================================== + */ + +/** + * Represents an action in the odlux flux architecture. + */ +export abstract class Action { } + +export interface IActionHandler { + (state: TState | undefined, action: TAction): TState; +} \ No newline at end of file diff --git a/features/sdnr/odlux/odlux/framework/src/flux/connect.tsx b/features/sdnr/odlux/odlux/framework/src/flux/connect.tsx new file mode 100644 index 0000000..09d30da --- /dev/null +++ b/features/sdnr/odlux/odlux/framework/src/flux/connect.tsx @@ -0,0 +1,213 @@ +/** + * ============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, useContext, createContext, useState, useEffect, useRef } from 'react'; + +import { Dispatch } from './store'; + +import { ApplicationStore, IApplicationStoreState } from '../store/applicationStore'; + +const LogLevel = +(localStorage.getItem('log.odlux.framework.flux.connect') || 0); + +interface IApplicationStoreContext { + applicationStore: ApplicationStore; +} + +export interface IDispatcher { + dispatch: Dispatch; +} + +interface IApplicationStoreProps { + state: IApplicationStoreState; +} + +interface IDispatchProps { + dispatch: Dispatch; +} + +type ComponentDecoratorInfer = { + (wrappedComponent: React.ComponentType): React.ComponentClass>; +}; + +const ApplicationStoreContext = createContext(undefined); + +export type Connect any) | undefined = undefined, TMapDispatch extends ((...args: any) => any) | undefined = undefined> = + (TMapProps extends ((...args: any) => any) ? ReturnType : IApplicationStoreProps) & + (TMapDispatch extends ((...args: any) => any) ? ReturnType : IDispatchProps); + +export function connect(): ComponentDecoratorInfer; + +export function connect( + mapStateToProps: (state: IApplicationStoreState) => TStateProps +): ComponentDecoratorInfer; + +export function connect( + mapStateToProps: (state: IApplicationStoreState) => TStateProps, + mapDispatchToProps: (dispatcher: IDispatcher) => TDispatchProps +): ComponentDecoratorInfer; + + +export function connect( + mapStateToProps: undefined, + mapDispatchToProps: (dispatcher: IDispatcher) => TDispatchProps +): ComponentDecoratorInfer; + + +export function connect( + mapStateToProps?: ((state: IApplicationStoreState) => TStateProps), + mapDispatchToProps?: ((dispatcher: IDispatcher) => TDispatchProps) +): + ((WrappedComponent: React.ComponentType) => React.ComponentType) { + + const injectApplicationStore = (WrappedComponent: React.ComponentType): React.ComponentType => { + + class StoreAdapter extends React.Component { + + render(): JSX.Element { + + if (isWrappedComponentIsVersion1(WrappedComponent)) { + const element = React.createElement(WrappedComponent, { ...(this.props as any), state: this.store.state, dispatch: this.store.dispatch.bind(this.store) }); + return element; + } else if (mapStateToProps && isWrappedComponentIsVersion2(WrappedComponent)) { + const element = React.createElement(WrappedComponent, { ...(this.props as any), ...(mapStateToProps(this.store.state) as any), dispatch: this.store.dispatch.bind(this.store) }); + return element; + } else if (mapStateToProps && mapDispatchToProps && isWrappedComponentIsVersion3(WrappedComponent)) { + const element = React.createElement(WrappedComponent, { ...(this.props as any), ...(mapStateToProps(this.store.state) as any), ...(mapDispatchToProps({ dispatch: this.store.dispatch.bind(this.store) }) as any) }); + return element; + } else if (!mapStateToProps && mapDispatchToProps && isWrappedComponentIsVersion4(WrappedComponent)) { + const element = React.createElement(WrappedComponent, { ...(this.props as any), state: this.store.state, ...(mapDispatchToProps({ dispatch: this.store.dispatch.bind(this.store) }) as any) }); + return element; + } + throw new Error("Invalid arguments in connect."); + } + + componentDidMount(): void { + this.store && this.store.changed.addHandler(this.handleStoreChanged); + } + + componentWillUnmount(): void { + this.store && this.store.changed.removeHandler(this.handleStoreChanged); + } + + private get store(): ApplicationStore { + return this.context.applicationStore; + } + + private handleStoreChanged = () => { + this.forceUpdate(); + } + } + StoreAdapter.contextType = ApplicationStoreContext; + return StoreAdapter; + } + + + return injectApplicationStore; + + /* inline methods */ + + function isWrappedComponentIsVersion1(wrappedComponent: any): wrappedComponent is React.ComponentType { + return !mapStateToProps && !mapDispatchToProps; + } + + function isWrappedComponentIsVersion2(wrappedComponent: any): wrappedComponent is React.ComponentType { + return !!mapStateToProps && !mapDispatchToProps; + } + + function isWrappedComponentIsVersion3(wrappedComponent: any): wrappedComponent is React.ComponentType { + return !!mapStateToProps && !!mapDispatchToProps; + } + + function isWrappedComponentIsVersion4(wrappedComponent: any): wrappedComponent is React.ComponentType { + return !mapStateToProps && !!mapDispatchToProps; + } +} + +type ApplicationStoreProviderProps = { + applicationStore: ApplicationStore; +} + +export const ApplicationStoreProvider: FC = (props) => { + const { applicationStore, children } = props; + + return ( + + {children} + + ); +}; + +export const useApplicationStore = (): ApplicationStore => { + const context = useContext(ApplicationStoreContext); + if (context == null || context.applicationStore == null) { + throw new Error("Requires application store provider!") + } + return context.applicationStore +}; + +export const useSelectApplicationState = ( selector: (state: IApplicationStoreState) => TProp, eqFunc = (a: TProp, b: TProp) => a === b ): TProp => { + const context = useContext(ApplicationStoreContext); + if (context == null || context.applicationStore == null) { + throw new Error("Requires application store provider!") + } + + const [propState, setPropState] = useState(selector(context.applicationStore.state)); + + const selectorRef = useRef(selector); + selectorRef.current = selector; + + const propStateRef = useRef({propState}); + propStateRef.current.propState = propState; + + useEffect(() => { + if (context == null || context.applicationStore == null) { + throw new Error("Requires application store provider!") + } + + const changedHandler = () => { + const newState = selectorRef.current(context.applicationStore.state); + if (!eqFunc(newState, propStateRef.current.propState)) { + setPropState(newState); + } + }; + + if (LogLevel > 3) { + console.log("useSelectApplicationState: adding handler", changedHandler); + } + + context.applicationStore.changed.addHandler(changedHandler); + + return () => { + if (LogLevel > 3) { + console.log("useSelectApplicationState: removing handler", changedHandler); + } + + context.applicationStore.changed.removeHandler(changedHandler); + } + }, [context]); + + return propState; + +}; + +export const useApplicationDispatch = (): Dispatch => { + const context = useContext(ApplicationStoreContext); + if (context == null || context.applicationStore == null) { + throw new Error("Requires application store provider!") + } + return context.applicationStore.dispatch; +}; diff --git a/features/sdnr/odlux/odlux/framework/src/flux/middleware.ts b/features/sdnr/odlux/odlux/framework/src/flux/middleware.ts new file mode 100644 index 0000000..de6505c --- /dev/null +++ b/features/sdnr/odlux/odlux/framework/src/flux/middleware.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 { Action, IActionHandler } from './action'; +import { Store, Dispatch, Enhancer } from './store'; + +export interface MiddlewareArg { + dispatch: Dispatch; + getState: () => T; +} + +export interface Middleware { + (obj: MiddlewareArg): Function; +} + +class InitialisationAction extends Action { }; +const initialisationAction = new InitialisationAction(); + +export type ActionHandlerMapObject = { + [K in keyof S]: IActionHandler +} + +export const combineActionHandler = (actionHandlers: ActionHandlerMapObject) : IActionHandler => { + const finalActionHandlers = {} as { [key: string]: any }; // https://github.com/microsoft/TypeScript/issues/31808 + Object.keys(actionHandlers).forEach(actionHandlerKey => { + const handler = actionHandlers[actionHandlerKey]; + if (typeof handler === 'function') { + finalActionHandlers[actionHandlerKey] = handler; + } + }); + + // ensure initialisation + Object.keys(finalActionHandlers).forEach(key => { + const actionHandler = finalActionHandlers[key]; + const initialState = actionHandler(undefined, initialisationAction); + if (typeof initialState === 'undefined') { + const errorMessage = `Action handler ${ key } returned undefiend during initialization.`; + throw new Error(errorMessage); + } + }); + + return function combination(state: TState = ({} as TState), action: TAction) { + let hasChanged = false; + const nextState = {} as { [key: string]: any }; // https://github.com/microsoft/TypeScript/issues/31808 + Object.keys(finalActionHandlers).forEach(key => { + const actionHandler = finalActionHandlers[key]; + const previousState = state[key]; + const nextStateKey = actionHandler(previousState, action); + if (typeof nextStateKey === 'undefined') { + const errorMessage = `Given ${ action.constructor } and action handler ${ key } returned undefiend.`; + throw new Error(errorMessage); + } + nextState[key] = nextStateKey; + hasChanged = hasChanged || nextStateKey !== previousState; + }); + return (hasChanged ? nextState : state) as TState; + }; +}; + +export const chainMiddleware = (...middlewares: Middleware[]): Enhancer => { + return (store: Store) => { + const middlewareAPI = { + getState() { return store.state }, + dispatch: (action: TAction) => store.dispatch(action) // we want to use the combinded dispatch + // we should NOT use the flux dispatcher here, since the action would affect ALL stores + }; + const chain = middlewares.map(middleware => middleware(middlewareAPI)); + return compose(...chain)(store.dispatch) as Dispatch; + } +}; + +/** + * Composes single-argument functions from right to left. The rightmost + * function can take multiple arguments as it provides the signature for + * the resulting composite function. + * + * @param {...Function} funcs The functions to compose. + * @returns {Function} A function obtained by composing the argument functions + * from right to left. For example, compose(f, g, h) is identical to doing + * (...args) => f(g(h(...args))). + */ +const compose = (...funcs: Function[]) => { + if (funcs.length === 0) { + return (arg: any) => arg + } + + if (funcs.length === 1) { + return funcs[0] + } + + return funcs.reduce((a, b) => (...args: any[]) => a(b(...args))); +}; + diff --git a/features/sdnr/odlux/odlux/framework/src/flux/store.ts b/features/sdnr/odlux/odlux/framework/src/flux/store.ts new file mode 100644 index 0000000..347d295 --- /dev/null +++ b/features/sdnr/odlux/odlux/framework/src/flux/store.ts @@ -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 { Event } from "../common/event" + +import { Action } from './action'; +import { IActionHandler } from './action'; + +const LogLevel = +(localStorage.getItem('log.odlux.framework.flux.store') || 0); + +export interface Dispatch { + (action: TAction): TAction; +} + +export interface Enhancer { + (store: Store): Dispatch; +} + +class InitializationAction extends Action { }; +const initializationAction = new InitializationAction(); + +export class Store { + + constructor(actionHandler: IActionHandler, enhancer?: Enhancer) + constructor(actionHandler: IActionHandler, initialState: TStoreState, enhancer?: Enhancer) + constructor(actionHandler: IActionHandler, initialState?: TStoreState | Enhancer, enhancer?: Enhancer) { + if (typeof initialState === 'function') { + enhancer = initialState as Enhancer; + initialState = undefined; + } + + this._isDispatching = false; + + this.changed = new Event(); + + this._actionHandler = actionHandler; + + this._state = initialState as TStoreState; + if (enhancer) this._dispatch = enhancer(this); + + this._dispatch(initializationAction); + } + + public changed: Event; + + private _dispatch: Dispatch = (payload: TAction): TAction => { + if (LogLevel > 2) { + console.log('Store::Dispatch - ', payload); + } + if (payload == null || !(payload instanceof Action)) { + throw new Error( + 'Actions must inherit from type Action. ' + + 'Use a custom middleware for async actions.' + ); + } + + if (this._isDispatching) { + throw new Error('ActionHandler may not dispatch actions.'); + } + + const oldState = this._state; + try { + this._isDispatching = true; + this._state = this._actionHandler(oldState, payload); + } finally { + this._isDispatching = false; + } + + if (this._state !== oldState) { + if (LogLevel > 3) { + console.log('Store::Dispatch - state has changed', this._state); + } + this.changed.invoke(); + } + + return payload; + } + + public get dispatch(): Dispatch { + return this._dispatch; + } + + public get state() { + return this._state + } + + private _state: TStoreState; + private _isDispatching: boolean; + private _actionHandler: IActionHandler; + +} + diff --git a/features/sdnr/odlux/odlux/framework/src/handlers/applicationRegistryHandler.ts b/features/sdnr/odlux/odlux/framework/src/handlers/applicationRegistryHandler.ts new file mode 100644 index 0000000..ec7b0a0 --- /dev/null +++ b/features/sdnr/odlux/odlux/framework/src/handlers/applicationRegistryHandler.ts @@ -0,0 +1,31 @@ +/** + * ============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 '../flux/action'; + +import { ApplicationInfo } from '../models/applicationInfo'; +import { applicationManager } from '../services/applicationManager'; + +export interface IApplicationRegistration { + [name: string]: ApplicationInfo; +} + +const applicationRegistrationInit: IApplicationRegistration = applicationManager.applications; + +export const applicationRegistryHandler: IActionHandler = (state = applicationRegistrationInit, action) => { + return state; +}; diff --git a/features/sdnr/odlux/odlux/framework/src/handlers/applicationStateHandler.ts b/features/sdnr/odlux/odlux/framework/src/handlers/applicationStateHandler.ts new file mode 100644 index 0000000..d0a0724 --- /dev/null +++ b/features/sdnr/odlux/odlux/framework/src/handlers/applicationStateHandler.ts @@ -0,0 +1,176 @@ +/** + * ============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 '../flux/action'; + +import { SetTitleAction } from '../actions/titleActions'; +import { SetExternalLoginProviderAction } from '../actions/loginProvider'; +import { AddSnackbarNotification, RemoveSnackbarNotification } from '../actions/snackbarActions'; +import { AddErrorInfoAction, RemoveErrorInfoAction, ClearErrorInfoAction } from '../actions/errorActions'; +import { MenuAction, MenuClosedByUser } from '../actions/menuAction' +import { SetWebsocketAction } from '../actions/websocketAction'; + +import { IconType } from '../models/iconDefinition'; +import { ErrorInfo } from '../models/errorInfo'; +import { SnackbarItem } from '../models/snackbarItem'; +import { ExternalLoginProvider } from '../models/externalLoginProvider'; +import { ApplicationConfig } from '../models/applicationConfig'; +import { IConnectAppStoreState } from '../../../apps/connectApp/src/handlers/connectAppRootHandler'; +import { IFaultAppStoreState } from '../../../apps/faultApp/src/handlers/faultAppRootHandler'; +import { GeneralSettings, Settings } from '../models/settings'; +import { LoadSettingsAction, SetGeneralSettingsAction, SetTableSettings, SettingsDoneLoadingAction } from '../actions/settingsAction'; +import { startWebsocketSession, suspendWebsocketSession } from '../services/notificationService'; + + +declare module '../store/applicationStore' { + interface IApplicationStoreState { + connect: IConnectAppStoreState; + fault: IFaultAppStoreState + } +} +export interface IApplicationState { + title: string; + appId?: string; + icon?: IconType; + isMenuOpen: boolean; + isMenuClosedByUser: boolean; + errors: ErrorInfo[]; + snackBars: SnackbarItem[]; + isWebsocketAvailable: boolean | null; + externalLoginProviders: ExternalLoginProvider[] | null; + authentication: "basic"|"oauth", // basic + enablePolicy: boolean, // false + transportpceUrl : string, + settings: Settings & { isInitialLoadDone: boolean } +} + +const applicationStateInit: IApplicationState = { + title: "Loading ...", + errors: [], + snackBars: [], + isMenuOpen: true, + isMenuClosedByUser: false, + isWebsocketAvailable: null, + externalLoginProviders: null, + authentication: "basic", + enablePolicy: false, + transportpceUrl: "", + settings:{ + general: { areNotificationsEnabled: null }, + tables: {}, + isInitialLoadDone: false + } +}; + +export const configureApplication = (config: ApplicationConfig) => { + applicationStateInit.authentication = config.authentication === "oauth" ? "oauth" : "basic"; + applicationStateInit.enablePolicy = config.enablePolicy ? true : false; + applicationStateInit.transportpceUrl=config.transportpceUrl == undefined ? "" : config.transportpceUrl; +} + +export const applicationStateHandler: IActionHandler = (state = applicationStateInit, action) => { + if (action instanceof SetTitleAction) { + state = { + ...state, + title: action.title, + icon: action.icon, + appId: action.appId, + }; + } else if (action instanceof AddErrorInfoAction) { + state = { + ...state, + errors: [ + ...state.errors, + action.errorInfo, + ] + }; + } else if (action instanceof RemoveErrorInfoAction) { + const index = state.errors.indexOf(action.errorInfo); + if (index > -1) { + state = { + ...state, + errors: [ + ...state.errors.slice(0, index), + ...state.errors.slice(index + 1), + ] + }; + } + } else if (action instanceof ClearErrorInfoAction) { + if (state.errors && state.errors.length) { + state = { + ...state, + errors: [], + }; + } + } else if (action instanceof AddSnackbarNotification) { + state = { + ...state, + snackBars: [ + ...state.snackBars, + action.notification, + ] + }; + } else if (action instanceof RemoveSnackbarNotification) { + state = { + ...state, + snackBars: state.snackBars.filter(s => s.key !== action.key), + }; + } else if (action instanceof MenuAction) { + state = { + ...state, + isMenuOpen: action.isOpen, + } + } else if (action instanceof MenuClosedByUser) { + state = { + ...state, + isMenuClosedByUser: action.isClosed, + } + } + else if (action instanceof SetWebsocketAction) { + state = { + ...state, + isWebsocketAvailable: action.isConnected, + } + } else if (action instanceof SetExternalLoginProviderAction){ + state = { + ...state, + externalLoginProviders: action.externalLoginProvders, + } + }else if(action instanceof SetGeneralSettingsAction){ + + state = { + ...state, + settings:{tables: state.settings.tables, isInitialLoadDone:state.settings.isInitialLoadDone, general:{areNotificationsEnabled: action.areNoticationsActive}} + } + } + else if(action instanceof SetTableSettings){ + + let tableUpdate = state.settings.tables; + tableUpdate[action.tableName] = {columns: action.updatedColumns}; + + state = {...state, settings:{general: state.settings.general, isInitialLoadDone:state.settings.isInitialLoadDone, tables: tableUpdate}} + + }else if(action instanceof LoadSettingsAction){ + + state = {...state, settings:action.settings} + } + else if(action instanceof SettingsDoneLoadingAction){ + state= {...state, settings: {...state.settings, isInitialLoadDone: true}} + } + + return state; +}; diff --git a/features/sdnr/odlux/odlux/framework/src/handlers/authenticationHandler.ts b/features/sdnr/odlux/odlux/framework/src/handlers/authenticationHandler.ts new file mode 100644 index 0000000..3a4891c --- /dev/null +++ b/features/sdnr/odlux/odlux/framework/src/handlers/authenticationHandler.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 { IActionHandler } from '../flux/action'; +import { UpdatePolicies, UpdateUser } from '../actions/authentication'; + +import { AuthPolicy, User } from '../models/authentication'; + +import { onLogin, onLogout } from '../services/applicationApi'; +import { startWebsocketSession, endWebsocketSession } from '../services/notificationService'; +import { startUserSession, endUserSession } from '../services/userSessionService'; +import { getUserData } from '../services/userdataService'; + +export interface IAuthenticationState { + user?: User; + policies?: AuthPolicy[]; +} + +const authenticationStateInit: IAuthenticationState = { + user: undefined +}; + +export const authenticationStateHandler: IActionHandler = (state = authenticationStateInit, action) => { + if (action instanceof UpdateUser) { + const {user} = action; + + if (user) { + onLogin(); + } else { + onLogout(); + } + + state = { + ...state, + user, + }; + } else if (action instanceof UpdatePolicies) { + state = { + ...state, + policies: action.authPolicies, + }; + } + return state; +}; + diff --git a/features/sdnr/odlux/odlux/framework/src/handlers/navigationStateHandler.ts b/features/sdnr/odlux/odlux/framework/src/handlers/navigationStateHandler.ts new file mode 100644 index 0000000..6ecdf2f --- /dev/null +++ b/features/sdnr/odlux/odlux/framework/src/handlers/navigationStateHandler.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 { IActionHandler } from '../flux/action'; +import { LocationChanged } from '../actions/navigationActions'; + + +export interface INavigationState { + pathname: string; + search: string; + hash: string; +} + +const navigationStateInit: INavigationState = { + pathname: '/', + search: '', + hash: '', +}; + + +export const navigationStateHandler: IActionHandler = (state = navigationStateInit, action) => { + if (action instanceof LocationChanged) { + state = { + ...state, + pathname: action.pathname, + search: action.search, + hash: action.hash + } + } + return state; +} \ No newline at end of file diff --git a/features/sdnr/odlux/odlux/framework/src/index.dev.html b/features/sdnr/odlux/odlux/framework/src/index.dev.html new file mode 100644 index 0000000..69c5f06 --- /dev/null +++ b/features/sdnr/odlux/odlux/framework/src/index.dev.html @@ -0,0 +1,35 @@ + + + + + + + + + O D L UX + + + +
+ + + + + + + \ No newline at end of file diff --git a/features/sdnr/odlux/odlux/framework/src/index.html b/features/sdnr/odlux/odlux/framework/src/index.html new file mode 100644 index 0000000..7196b5c --- /dev/null +++ b/features/sdnr/odlux/odlux/framework/src/index.html @@ -0,0 +1,27 @@ + + + + + + + + + O D L UX + + + +
+ + + + + + \ No newline at end of file diff --git a/features/sdnr/odlux/odlux/framework/src/middleware/api.ts b/features/sdnr/odlux/odlux/framework/src/middleware/api.ts new file mode 100644 index 0000000..8e9bc64 --- /dev/null +++ b/features/sdnr/odlux/odlux/framework/src/middleware/api.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 { Action, IActionHandler } from '../flux/action'; +import { MiddlewareArg } from '../flux/middleware'; +import { Dispatch } from '../flux/store'; + +import { IApplicationStoreState } from '../store/applicationStore'; +import { AddErrorInfoAction, ErrorInfo } from '../actions/errorActions'; + +const baseUrl = `${ window.location.origin }${ window.location.pathname }`; + +export class ApiAction extends Action { + constructor(public endpoint: string, public successAction: { new(result: TResult): TSuccessAction }, public authenticate: boolean = false) { + super(); + } +} + +export const apiMiddleware = (store: MiddlewareArg) => (next: Dispatch) => (action: A) => { + + // So the middleware doesn't get applied to every single action + if (action instanceof ApiAction) { + const user = store && store.getState().framework.authenticationState.user; + const token = user && user.token || null; + let config = { headers: {} }; + + if (action.authenticate) { + if (token) { + config = { + ...config, + headers: { + ...config.headers, + // 'Authorization': `Bearer ${ token }` + authorization: "Basic YWRtaW46YWRtaW4=" + } + } + } else { + return next(new AddErrorInfoAction({ message: 'Please login to continue.' })); + } + } + + fetch(baseUrl + action.endpoint.replace(/\/{2,}/, '/'), config) + .then(response => + response.json().then(data => ({ data, response })) + ) + .then(result => { + next(new action.successAction(result.data)); + }) + .catch((error: any) => { + next(new AddErrorInfoAction((error instanceof Error) ? { error: error } : { message: error.toString() })); + }); + } + + // let all actions pass + return next(action); +} + +export default apiMiddleware; \ No newline at end of file diff --git a/features/sdnr/odlux/odlux/framework/src/middleware/logger.ts b/features/sdnr/odlux/odlux/framework/src/middleware/logger.ts new file mode 100644 index 0000000..fb0874f --- /dev/null +++ b/features/sdnr/odlux/odlux/framework/src/middleware/logger.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 { Dispatch } from '../flux/store'; +import { MiddlewareApi } from '../store/applicationStore'; + +const LogLevel = +(localStorage.getItem('log.odlux.framework.middleware.logger') || 0); + +function createLoggerMiddleware() { + return function logger({ getState }: MiddlewareApi) { + return (next: Dispatch): Dispatch => action => { + if (LogLevel > 2) console.log('will dispatch', action); + const returnValue = next(action); + if (LogLevel > 2) console.log('state after dispatch', getState()); + return returnValue; + }; + }; +} + +export const logger = createLoggerMiddleware(); +export default logger; \ No newline at end of file diff --git a/features/sdnr/odlux/odlux/framework/src/middleware/navigation.ts b/features/sdnr/odlux/odlux/framework/src/middleware/navigation.ts new file mode 100644 index 0000000..14ff09f --- /dev/null +++ b/features/sdnr/odlux/odlux/framework/src/middleware/navigation.ts @@ -0,0 +1,96 @@ +/** + * ============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 jwt from 'jsonwebtoken'; +import { History, createHashHistory } from 'history'; + +import { User } from '../models/authentication'; + +import { LocationChanged, NavigateToApplication } from '../actions/navigationActions'; +import { PushAction, ReplaceAction, GoAction, GoBackAction, GoForwardeAction } from '../actions/navigationActions'; + +import { applicationManager } from '../services/applicationManager'; +import { loginUserAction, logoutUser } from '../actions/authentication'; + +import { ApplicationStore } from '../store/applicationStore'; +import { Dispatch } from '../flux/store'; + +export const history = createHashHistory(); +let applicationStore: ApplicationStore | null = null; + +const routerMiddlewareCreator = (historyParam: History) => () => (next: Dispatch): Dispatch => (action) => { + + if (action instanceof NavigateToApplication) { + const application = applicationManager.applications && applicationManager.applications[action.applicationName]; + if (application) { + const href = `/${application.path || application.name}${action.href ? '/' + action.href : ''}`.replace(/\/{2,}/i, '/'); + if (action.replace) { + historyParam.replace(href, action.state); + } else { + historyParam.push(href, action.state); + } + } + } else if (action instanceof PushAction) { + historyParam.push(action.href, action.state); + } else if (action instanceof ReplaceAction) { + historyParam.replace(action.href, action.state); + } else if (action instanceof GoAction) { + historyParam.go(action.index); + } else if (action instanceof GoBackAction) { + historyParam.goBack(); + } else if (action instanceof GoForwardeAction) { + historyParam.goForward(); + } else if (action instanceof LocationChanged) { + // ensure user is logged in and token is valid + if (action.pathname.startsWith('/oauth') && (action.search.startsWith('?token='))) { + const ind = action.search.lastIndexOf('token='); + const tokenStr = ind > -1 ? action.search.substring(ind + 6) : null; + const token = tokenStr && jwt.decode(tokenStr); + if (tokenStr && token) { + // @ts-ignore + const user = new User({ username: token.name, access_token: tokenStr, token_type: 'Bearer', expires: token.exp, issued: token.iat }) || undefined; + applicationStore?.dispatch(loginUserAction(user)); + } + } if (!action.pathname.startsWith('/login') && applicationStore && (!applicationStore.state.framework.authenticationState.user || !applicationStore.state.framework.authenticationState.user.isValid)) { + historyParam.replace(`/login?returnTo=${action.pathname}`); + applicationStore.dispatch(logoutUser()); + + } else if (action.pathname.startsWith('/login') && applicationStore && (applicationStore.state.framework.authenticationState.user && applicationStore.state.framework.authenticationState.user.isValid)) { + historyParam.replace('/'); + } else { + return next(action); + } + } else { + return next(action); + } + return action; +}; + +const startListener = (historyParam: History, store: ApplicationStore) => { + store.dispatch(new LocationChanged(historyParam.location.pathname, historyParam.location.search, historyParam.location.hash)); + historyParam.listen((location) => { + store.dispatch(new LocationChanged(location.pathname, location.search, location.hash)); + }); +}; + +export const startHistoryListener = (store: ApplicationStore) => { + applicationStore = store; + startListener(history, store); +}; + +export const routerMiddleware = routerMiddlewareCreator(history); +export default routerMiddleware; diff --git a/features/sdnr/odlux/odlux/framework/src/middleware/policies.ts b/features/sdnr/odlux/odlux/framework/src/middleware/policies.ts new file mode 100644 index 0000000..662ecdd --- /dev/null +++ b/features/sdnr/odlux/odlux/framework/src/middleware/policies.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 authenticationService from '../services/authenticationService'; + +import { UpdateUser, UpdatePolicies } from '../actions/authentication'; +import { Dispatch } from '../flux/store'; +import { MiddlewareApi } from '../store/applicationStore'; + +function updatePoliciesMiddleware() { + return ({ dispatch, getState }: MiddlewareApi) => + (next : Dispatch) : Dispatch => + action => { + const { framework: { applicationState: { enablePolicy } } } = getState() || { framework: { applicationState: { } } }; + if (enablePolicy && action instanceof UpdateUser) { + next(action); + authenticationService.getAccessPolicies().then((policies) => dispatch(new UpdatePolicies(policies||undefined))); + return action; + } + if (enablePolicy === false) next(new UpdatePolicies()); + return next(action); + }; +} + +export const updatePolicies = updatePoliciesMiddleware(); +export default updatePolicies; \ No newline at end of file diff --git a/features/sdnr/odlux/odlux/framework/src/middleware/thunk.ts b/features/sdnr/odlux/odlux/framework/src/middleware/thunk.ts new file mode 100644 index 0000000..18f40c4 --- /dev/null +++ b/features/sdnr/odlux/odlux/framework/src/middleware/thunk.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 { Dispatch } from '../flux/store'; +import { MiddlewareApi } from '../store/applicationStore'; + +function createThunkMiddleware() { + return ({ dispatch, getState }: MiddlewareApi) => + (next : Dispatch) : Dispatch => + action => { + if (typeof action === 'function') { + return action(dispatch, getState); + } + + return next(action); + }; +} + +export const thunk = createThunkMiddleware(); +export default thunk; \ No newline at end of file diff --git a/features/sdnr/odlux/odlux/framework/src/models/applicationConfig.ts b/features/sdnr/odlux/odlux/framework/src/models/applicationConfig.ts new file mode 100644 index 0000000..0ae9c26 --- /dev/null +++ b/features/sdnr/odlux/odlux/framework/src/models/applicationConfig.ts @@ -0,0 +1,5 @@ +export type ApplicationConfig = { + authentication: "basic"|"oauth", // basic + enablePolicy: false, // false + transportpceUrl? : string +}; \ No newline at end of file diff --git a/features/sdnr/odlux/odlux/framework/src/models/applicationInfo.ts b/features/sdnr/odlux/odlux/framework/src/models/applicationInfo.ts new file mode 100644 index 0000000..ff07b7d --- /dev/null +++ b/features/sdnr/odlux/odlux/framework/src/models/applicationInfo.ts @@ -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 { ComponentType } from 'react'; +import { IconType } from './iconDefinition'; + +import { IActionHandler } from '../flux/action'; +import { Middleware } from '../flux/middleware'; +import { SettingsComponentProps } from './settings'; + +/** Represents the information needed about an application to integrate. */ +export class ApplicationInfo { + /** The name of the application. */ + name: string; + /** Optional: The title of the application, if null ot undefined the name will be used. */ + title?: string; + /** Optional: The icon of the application for the navigation and title bar. */ + icon?: IconType; + /** Optional: The description of the application. */ + description?: string; + /** The root component of the application. */ + rootComponent: ComponentType; + /** Optional: The root action handler of the application. */ + rootActionHandler?: IActionHandler<{ [key: string]: any }>; + /** Optional: Application speciffic middlewares. */ + middlewares?: Middleware<{ [key: string]: any }>[]; + /** Optional: A mapping object with the exported components. */ + exportedComponents?: { [key: string]: ComponentType } + /** Optional: The entry to be shown in the menu. If undefiened the name will be used. */ + menuEntry?: string | React.ComponentType; + /** Optional: A component to be shown in the menu when this app is active below the main entry. If undefiened the name will be used. */ + subMenuEntry?: React.ComponentType; + /** Optional: A component to be shown in the applications status bar. If undefiened the name will be used. */ + statusBarElement?: React.ComponentType; + /** Optional: A component to be shown in the dashboardview. If undefiened the name will be used. */ + dashbaordElement?: React.ComponentType; + /** Optional: A component shown in the settings view */ + settingsElement?: React.ComponentType; + /** Optional: The pasth for this application. If undefined the name will be use as path. */ + path?: string; +} diff --git a/features/sdnr/odlux/odlux/framework/src/models/authentication.ts b/features/sdnr/odlux/odlux/framework/src/models/authentication.ts new file mode 100644 index 0000000..f565381 --- /dev/null +++ b/features/sdnr/odlux/odlux/framework/src/models/authentication.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========================================================================== + */ + +export type AuthToken = { + username: string; + access_token: string; + token_type: string; + /*** + * datetime the token should expire in unix timestamp + * + * must be in seconds + */ + expires: number; + /*** + * time the token was issued in unix timestamp + * + * must be in seconds + * + */ + issued: number; +} + +export type AuthPolicy = { + path: string; + methods: { + get?: boolean; + post?: boolean; + put?: boolean; + patch?: boolean; + delete?: boolean; + } +} + +export class User { + + constructor (private _bearerToken: AuthToken) { + + } + + public get user(): string | null { + return this._bearerToken && this._bearerToken.username; + }; + + public get token(): string | null { + return this._bearerToken && this._bearerToken.access_token; + } + + public get tokenType(): string | null { + return this._bearerToken && this._bearerToken.token_type; + } + + /*** + * Time the user should be logged out, in unix timestamp in seconds + */ + public get logoutAt(): number{ + return this._bearerToken && this._bearerToken.expires; + } + + /*** + * Time the user logged in, in unix timestamp in seconds + */ + public get loginAt(): number{ + return this._bearerToken && this._bearerToken.issued; + } + + public get isValid(): boolean { + return (this._bearerToken && (new Date().valueOf()) < this._bearerToken.expires*1000) || false; + } + + public toString() { + return JSON.stringify(this._bearerToken); + } + + public static fromString(data: string) { + return new User(JSON.parse(data)); + } + + +} diff --git a/features/sdnr/odlux/odlux/framework/src/models/elasticSearch.ts b/features/sdnr/odlux/odlux/framework/src/models/elasticSearch.ts new file mode 100644 index 0000000..fc43836 --- /dev/null +++ b/features/sdnr/odlux/odlux/framework/src/models/elasticSearch.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========================================================================== + */ +export type Result = { + "data-provider:output": { + pagination?: { + size: number; + page: number; + total: number; + }, + data: TSource[]; + } +} + +export type SingeResult = { + "data-provider:output": TSource; +} + + +export type ResultTopology = { + "output": { + pagination?: { + size: number; + page: number; + total: number; + }, + data: TSource[]; + } +} + +export type HitEntry = { + _index: string; + _type: string; + _id: string; + _score: number; + _source: TSource; +} + +type ActionResponse ={ + _index: string; + _type: string; + _id: string; + _shards: { + total: number, + successful: number, + failed: number + }, + +} + +export type PostResponse = ActionResponse & { + created: boolean +} + +export type DeleteResponse = ActionResponse & { + found: boolean +} + diff --git a/features/sdnr/odlux/odlux/framework/src/models/errorInfo.ts b/features/sdnr/odlux/odlux/framework/src/models/errorInfo.ts new file mode 100644 index 0000000..21217a1 --- /dev/null +++ b/features/sdnr/odlux/odlux/framework/src/models/errorInfo.ts @@ -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========================================================================== + */ +export type ErrorInfo = { + title?: string, + error?: Error | null, + url?: string, + line?: number, + col?: number, + info?: { + extra?: string, + componentStack?: string + }, + message?: string +} \ No newline at end of file diff --git a/features/sdnr/odlux/odlux/framework/src/models/externalLoginProvider.ts b/features/sdnr/odlux/odlux/framework/src/models/externalLoginProvider.ts new file mode 100644 index 0000000..357feed --- /dev/null +++ b/features/sdnr/odlux/odlux/framework/src/models/externalLoginProvider.ts @@ -0,0 +1,23 @@ +/** + * ============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 ExternalLoginProvider = { + id: string; + title: string; + loginUrl: string; + } \ No newline at end of file diff --git a/features/sdnr/odlux/odlux/framework/src/models/iconDefinition.ts b/features/sdnr/odlux/odlux/framework/src/models/iconDefinition.ts new file mode 100644 index 0000000..ff50aa7 --- /dev/null +++ b/features/sdnr/odlux/odlux/framework/src/models/iconDefinition.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========================================================================== + */ + +import { IconDefinition } from '@fortawesome/free-solid-svg-icons'; + +export type IconType = IconDefinition | string; \ No newline at end of file diff --git a/features/sdnr/odlux/odlux/framework/src/models/index.ts b/features/sdnr/odlux/odlux/framework/src/models/index.ts new file mode 100644 index 0000000..4b8dc20 --- /dev/null +++ b/features/sdnr/odlux/odlux/framework/src/models/index.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 * from './elasticSearch'; \ No newline at end of file diff --git a/features/sdnr/odlux/odlux/framework/src/models/restService.ts b/features/sdnr/odlux/odlux/framework/src/models/restService.ts new file mode 100644 index 0000000..03f580b --- /dev/null +++ b/features/sdnr/odlux/odlux/framework/src/models/restService.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========================================================================== + */ + +/** + * The PlainObject type is a JavaScript object containing zero or more key-value pairs. + */ +export interface PlainObject { + [key: string]: T; +} + +export interface AjaxParameter { + /** + * The HTTP method to use for the request (e.g. "POST", "GET", "PUT"). + */ + method?: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'OPTIONS' | 'PATCH'; + /** + * An object of additional header key/value pairs to send along with requests using the XMLHttpRequest + * transport. The header X-Requested-With: XMLHttpRequest is always added, but its default + * XMLHttpRequest value can be changed here. Values in the headers setting can also be overwritten from + * within the beforeSend function. + */ + headers?: PlainObject; + /** + * Data to be sent to the server. It is converted to a query string, if not already a string. It's + * appended to the url for GET-requests. See processData option to prevent this automatic processing. + * Object must be Key/Value pairs. If value is an Array, jQuery serializes multiple values with same + * key based on the value of the traditional setting (described below). + */ + data?: PlainObject | string; +} + + + diff --git a/features/sdnr/odlux/odlux/framework/src/models/settings.ts b/features/sdnr/odlux/odlux/framework/src/models/settings.ts new file mode 100644 index 0000000..1752d6a --- /dev/null +++ b/features/sdnr/odlux/odlux/framework/src/models/settings.ts @@ -0,0 +1,48 @@ +/** + * ============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========================================================================== + */ + +export type TableSettingsColumn = { + property: string; + displayed: boolean; +}; + +export type TableSettings = { + tables:{ + [key: string]: { + columns: TableSettingsColumn[]; + + //match prop names, hide them + //via property name! -> only those which are hidden! + //all others default false, oh yeah + //or maybe the other way around, gotta think about that + + }; + }; +}; + +export type GeneralSettings = { + general:{ + areNotificationsEnabled: boolean | null; + }; +}; + +export type Settings = TableSettings & GeneralSettings; + +export type SettingsComponentProps = { + onClose(): void; +}; \ No newline at end of file diff --git a/features/sdnr/odlux/odlux/framework/src/models/snackbarItem.ts b/features/sdnr/odlux/odlux/framework/src/models/snackbarItem.ts new file mode 100644 index 0000000..c10d7ef --- /dev/null +++ b/features/sdnr/odlux/odlux/framework/src/models/snackbarItem.ts @@ -0,0 +1,20 @@ +/** + * ============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 { OptionsObject } from "notistack"; + +export type SnackbarItem = { key: number, message: string, options?: OptionsObject }; \ No newline at end of file diff --git a/features/sdnr/odlux/odlux/framework/src/run.ts b/features/sdnr/odlux/odlux/framework/src/run.ts new file mode 100644 index 0000000..991b749 --- /dev/null +++ b/features/sdnr/odlux/odlux/framework/src/run.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 { configureApplication } from './handlers/applicationStateHandler'; +export { runApplication } from './app'; diff --git a/features/sdnr/odlux/odlux/framework/src/services/applicationApi.ts b/features/sdnr/odlux/odlux/framework/src/services/applicationApi.ts new file mode 100644 index 0000000..faa9984 --- /dev/null +++ b/features/sdnr/odlux/odlux/framework/src/services/applicationApi.ts @@ -0,0 +1,74 @@ +/** + * ============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 { Event } from '../common/event'; +import { ApplicationStore } from '../store/applicationStore'; +import { AuthMessage, sendMessage } from './broadcastService'; + +let resolveApplicationStoreInitialized: (store: ApplicationStore) => void; +let applicationStore: ApplicationStore | null = null; +const applicationStoreInitialized: Promise = new Promise((resolve) => resolveApplicationStoreInitialized = resolve); + +const loginEvent = new Event(); +const logoutEvent = new Event(); + +const authChannelName = 'odlux_auth'; + +export const onLogin = () => { + + const message : AuthMessage = { key: 'login', data: {} }; + sendMessage(message, authChannelName); + loginEvent.invoke(); + +}; + +export const onLogout = () => { + + document.cookie = 'JSESSIONID=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;'; + + const message : AuthMessage = { key: 'logout', data: {} }; + sendMessage(message, authChannelName); + logoutEvent.invoke(); +}; + +export const setApplicationStore = (store: ApplicationStore) => { + if (!applicationStore && store) { + applicationStore = store; + resolveApplicationStoreInitialized(store); + } +}; + +export const applicationApi = { + get applicationStore(): ApplicationStore | null { + return applicationStore; + }, + + get applicationStoreInitialized(): Promise { + return applicationStoreInitialized; + }, + + get loginEvent() { + return loginEvent; + }, + + get logoutEvent() { + return logoutEvent; + }, +}; + +export default applicationApi; \ No newline at end of file diff --git a/features/sdnr/odlux/odlux/framework/src/services/applicationManager.ts b/features/sdnr/odlux/odlux/framework/src/services/applicationManager.ts new file mode 100644 index 0000000..bd620a0 --- /dev/null +++ b/features/sdnr/odlux/odlux/framework/src/services/applicationManager.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========================================================================== + */ +import { ApplicationInfo } from '../models/applicationInfo'; +import { Event } from '../common/event'; + +import { applicationApi } from './applicationApi'; + +/** Represents registry to manage all applications. */ +class ApplicationManager { + + /** Stores all registered applications. */ + private _applications: { [key: string]: ApplicationInfo }; + + /** Initializes a new instance of this class. */ + constructor() { + this._applications = {}; + this.changed = new Event(); + } + + /** The changed event will fire if the registration has changed. */ + public changed: Event; + + /** Registers a new application. */ + public registerApplication(applicationInfo: ApplicationInfo) { + this._applications[applicationInfo.name] = applicationInfo; + this.changed.invoke(); + return applicationApi; + } + + /** Gets all registered applications. */ + public get applications() { + return this._applications; + } +} + +/** A singleton instance of the application manager. */ +export const applicationManager = new ApplicationManager(); +export default applicationManager; \ No newline at end of file diff --git a/features/sdnr/odlux/odlux/framework/src/services/authenticationService.ts b/features/sdnr/odlux/odlux/framework/src/services/authenticationService.ts new file mode 100644 index 0000000..b9c1a5a --- /dev/null +++ b/features/sdnr/odlux/odlux/framework/src/services/authenticationService.ts @@ -0,0 +1,96 @@ +/** + * ============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 { AuthPolicy, AuthToken } from "../models/authentication"; +import { ExternalLoginProvider } from "../models/externalLoginProvider"; + +import { requestRest, formEncode, requestRestExt } from "./restService"; + +type AuthTokenResponse = { + access_token: string; + token_type: string; + expires_at: number; + issued_at: number; +} + +class AuthenticationService { + public async getAvaliableExteralProvider() { + const result = await requestRest(`oauth/providers`, { + method: "GET", + headers: { + 'Content-Type': 'application/x-www-form-urlencoded' + }, + }, false); + return result; + } + + public async authenticateUserOAuth(email: string, password: string, scope: string): Promise { + const result = await requestRest(`oauth/login`, { + method: "POST", + headers: { + 'Content-Type': 'application/x-www-form-urlencoded' + }, + body: formEncode({ + grant_type: "password", + username: email, + password: password, + scope: scope + }) + }, false); + + + return result && { + username: email, + access_token: result.access_token, + token_type: result.token_type, + expires: result.expires_at, + issued: result.issued_at + } || null; + } + + public async authenticateUserBasicAuth(email: string, password: string, scope: string): Promise { + const result = await requestRest(`rests/data/network-topology:network-topology/topology=topology-netconf?fields=node(node-id)`, { + method: "GET", + headers: { + 'Authorization': "Basic " + btoa(email + ":" + password) + }, + }, false); + + if (result) { + return { + username: email, + access_token: btoa(email + ":" + password), + token_type: "Basic", + expires: (new Date()).valueOf() / 1000 + 86400, // 1 day + issued: (new Date()).valueOf() / 1000 + } + } + return null; + } + + public async getAccessPolicies(){ + return await requestRest(`oauth/policies`, { method: "GET" }, true); + } + + public async getServerReadyState(){ + const result = await requestRestExt(`/ready`, { method: "GET" }, false); + return result.status == (200 || 304) ? true : false; + } +} + +export const authenticationService = new AuthenticationService(); +export default authenticationService; \ No newline at end of file diff --git a/features/sdnr/odlux/odlux/framework/src/services/broadcastService.ts b/features/sdnr/odlux/odlux/framework/src/services/broadcastService.ts new file mode 100644 index 0000000..202bf55 --- /dev/null +++ b/features/sdnr/odlux/odlux/framework/src/services/broadcastService.ts @@ -0,0 +1,115 @@ +/** + * ============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 { setGeneralSettingsAction } from '../actions/settingsAction'; +import { loginUserAction, logoutUser } from '../actions/authentication'; +import { ReplaceAction } from '../actions/navigationActions'; +import { User } from '../models/authentication'; +import { ApplicationStore } from '../store/applicationStore'; + +type Broadcaster = { + channel: BroadcastChannel; + key: String; +}; + +type AuthTypes = 'login' | 'logout'; +export type AuthMessage = { + key: AuthTypes; + data: any; +}; + +type SettingsType = 'general'; +export type SettingsMessage = { + key: SettingsType; + enableNotifications: boolean; + user: string; +}; + +const channels: Broadcaster[] = []; +let store: ApplicationStore | null = null; + +export const saveChannel = (channel: BroadcastChannel, channelName: string) => { + channels.push({ channel: channel, key: channelName }); +}; + +const createSettingsBroadcastChannel = () => { + + const name = 'odlux_settings'; + const bc: BroadcastChannel = new BroadcastChannel(name); + channels.push({ channel: bc, key: name }); + + bc.onmessage = (eventMessage: MessageEvent) => { + console.log(eventMessage); + + if (eventMessage.data.key === 'general') { + + if (store?.state.framework.authenticationState.user) { + const data = eventMessage.data; + if (store.state.framework.authenticationState.user.user === data.user) { + store?.dispatch(setGeneralSettingsAction(data.enableNotifications)); + } + } + } + }; +}; + +const createAuthBroadcastChannel = () => { + const name = 'odlux_auth'; + const bc: BroadcastChannel = new BroadcastChannel(name); + channels.push({ channel: bc, key: name }); + + bc.onmessage = (eventMessage: MessageEvent) => { + console.log(eventMessage); + + if (eventMessage.data.key === 'login') { + if (!store?.state.framework.authenticationState.user) { + const initialToken = localStorage.getItem('userToken'); + if (initialToken) { + store?.dispatch(loginUserAction(User.fromString(initialToken))); + store?.dispatch(new ReplaceAction('/')); + } + } + } else if (eventMessage.data.key === 'logout') { + + if (store?.state.framework.authenticationState.user) { + store?.dispatch(logoutUser()); + store?.dispatch(new ReplaceAction('/login')); + } + } + }; +}; + +export const startBroadcastChannel = (applicationStore: ApplicationStore) => { + store = applicationStore; + + // might decide to use one general broadcast channel with more keys in the future + createAuthBroadcastChannel(); + createSettingsBroadcastChannel(); +}; + +export const getBroadcastChannel = (channelName: string) => { + const foundChannel = channels.find(s => s.key === channelName); + return foundChannel?.channel; +}; + +export const sendMessage = (data: any, channel: string) => { + const foundChannel = channels.find(s => s.key === channel); + if (foundChannel) { + foundChannel.channel.postMessage(data); + } +}; diff --git a/features/sdnr/odlux/odlux/framework/src/services/index.ts b/features/sdnr/odlux/odlux/framework/src/services/index.ts new file mode 100644 index 0000000..2f64ba0 --- /dev/null +++ b/features/sdnr/odlux/odlux/framework/src/services/index.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 { applicationManager } from './applicationManager'; +export { subscribe, unsubscribe } from './notificationService'; +export { requestRest } from './restService'; +export { saveUserData as saveUserdata, getUserData as getUserdata } from './userdataService'; + diff --git a/features/sdnr/odlux/odlux/framework/src/services/notificationService.ts b/features/sdnr/odlux/odlux/framework/src/services/notificationService.ts new file mode 100644 index 0000000..b2880b9 --- /dev/null +++ b/features/sdnr/odlux/odlux/framework/src/services/notificationService.ts @@ -0,0 +1,220 @@ +/** + * ============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 { ApplicationStore } from '../store/applicationStore'; +import { SetWebsocketAction } from '../actions/websocketAction'; + +const socketUrl = [location.protocol === 'https:' ? 'wss://' : 'ws://', location.hostname, ':', location.port, '/websocket'].join(''); +const subscriptions: { [scope: string]: SubscriptionCallback[] } = {}; +let socketReady: Promise; +let wasWebsocketConnectionEstablished: undefined | boolean; +let applicationStore: ApplicationStore | null; + +let areWebsocketsStoppedViaSettings = false; + + +export interface IFormatedMessage { + "event-time": string, + "data": { + "counter": number, + "attribute-name": string, + "time-stamp": string, + "object-id-ref": string, + "new-value": string + }, + "node-id": string, + "type": { + "namespace": string, + "revision": string, + "type": string + } +} + +export type SubscriptionCallback = (msg: TMessage) => void; + +function setCurrentSubscriptions(notificationSocket: WebSocket) { + const scopesToSubscribe = Object.keys(subscriptions); + if (notificationSocket.readyState === notificationSocket.OPEN) { + const data = { + 'data': 'scopes', + 'scopes':[{ + "schema":{ + "namespace":"urn:opendaylight:params:xml:ns:yang:devicemanager", + "revision":"*", + "notification": scopesToSubscribe + } + }] + }; + notificationSocket.send(JSON.stringify(data)); + return true; + }; + return false; +} + +function addScope(scope: string | string[], callback: SubscriptionCallback) { + const scopes = scope instanceof Array ? scope : [scope]; + + // send all new scopes to subscribe + const newScopesToSubscribe: string[] = scopes.reduce((acc: string[], cur: string) => { + const currentCallbacks = subscriptions[cur]; + if (currentCallbacks) { + if (!currentCallbacks.some(c => c === callback)) { + currentCallbacks.push(callback); + } + } else { + subscriptions[cur] = [callback]; + acc.push(cur); + } + return acc; + }, []); + + if (newScopesToSubscribe.length === 0) { + return true; + } + return false; +} + +function removeScope(scope: string | string[], callback: SubscriptionCallback) { + const scopes = scope instanceof Array ? scope : [scope]; + scopes.forEach(s => { + const callbacks = subscriptions[s]; + const index = callbacks && callbacks.indexOf(callback); + if (index > -1) { + callbacks.splice(index, 1); + } + if (callbacks.length === 0) { + subscriptions[s] === undefined; + } + }); +} + +export function subscribe(scope: string | string[], callback: SubscriptionCallback): Promise { + addScope(scope, callback) + return socketReady && socketReady.then((notificationSocket) => { + // send a subscription to all active scopes + return setCurrentSubscriptions(notificationSocket); + }) || true; +} + +export function unsubscribe(scope: string | string[], callback: SubscriptionCallback): Promise { + removeScope(scope, callback); + return socketReady && socketReady.then((notificationSocket) => { + // send a subscription to all active scopes + return setCurrentSubscriptions(notificationSocket); + }) || true; +} + +export const startNotificationService = (store: ApplicationStore) => { + applicationStore = store; +} + +const connect = (): Promise => { + return new Promise((resolve, reject) => { + const notificationSocket = new WebSocket(socketUrl); + + notificationSocket.onmessage = (event: MessageEvent) => { + // process received event + + if (event.data && typeof event.data === "string" ) { + const msg = JSON.parse(event.data) as IFormatedMessage; + const callbacks = msg?.type?.type && subscriptions[msg.type.type]; + if (callbacks) { + callbacks.forEach(cb => { + // ensure all callbacks will be called + try { + return cb(msg); + } catch (reason) { + console.error(reason); + } + }); + } + } + + }; + + notificationSocket.onerror = function (error) { + console.log("Socket error:"); + console.log(error); + reject("Socket error: " + error); + if (applicationStore) { + applicationStore.dispatch(new SetWebsocketAction(false)); + } + }; + + notificationSocket.onopen = function (event) { + if (applicationStore) { + applicationStore.dispatch(new SetWebsocketAction(true)); + } + console.log("Socket connection opened."); + resolve(notificationSocket); + + // send a subscription to all active scopes + setCurrentSubscriptions(notificationSocket); + }; + + notificationSocket.onclose = function (event) { + console.log("socket connection closed"); + dispatchSocketClose(); + + const isUserLoggedIn = applicationStore?.state.framework.authenticationState.user && applicationStore?.state.framework.authenticationState.user?.isValid; + + if (isUserLoggedIn && !areWebsocketsStoppedViaSettings) { + socketReady = connect(); + } + }; + }); +} + + +export const startWebsocketSession = () => { + socketReady = connect(); + areWebsocketsStoppedViaSettings = false; +} + +export const suspendWebsocketSession = () =>{ + areWebsocketsStoppedViaSettings = true; + closeSocket(); +} + +export const endWebsocketSession = () => { + closeSocket(); +} + +const closeSocket = () =>{ + + if (socketReady) { + socketReady.then(websocket => { + websocket.close(); + }); + }else{ + dispatchSocketClose(); + } +} + +const dispatchSocketClose = () =>{ + const isUserLoggedIn = applicationStore?.state.framework.authenticationState.user && applicationStore?.state.framework.authenticationState.user?.isValid; + + if(isUserLoggedIn){ + applicationStore?.dispatch(new SetWebsocketAction(false)); + }else{ + applicationStore?.dispatch(new SetWebsocketAction(null)); + } +} + + + + diff --git a/features/sdnr/odlux/odlux/framework/src/services/restAccessorService.ts b/features/sdnr/odlux/odlux/framework/src/services/restAccessorService.ts new file mode 100644 index 0000000..5ed4d7b --- /dev/null +++ b/features/sdnr/odlux/odlux/framework/src/services/restAccessorService.ts @@ -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 * as $ from 'jquery'; +import { Action, IActionHandler } from '../flux/action'; +import { MiddlewareArg } from '../flux/middleware'; +import { Dispatch } from '../flux/store'; + +import { IApplicationStoreState } from '../store/applicationStore'; +import { AddErrorInfoAction, ErrorInfo } from '../actions/errorActions'; +import { PlainObject, AjaxParameter } from 'models/restService'; + +export const absoluteUri = /^(https?:\/\/|blob:)/i; +export const baseUrl = `${ window.location.origin }${ window.location.pathname }`; + +class RestBaseAction extends Action { } + +export const createRestApiAccessor = (urlOrPath: string, initialValue: TResult) => { + const isLocalRequest = !absoluteUri.test(urlOrPath); + const uri = isLocalRequest ? `${ baseUrl }/${ urlOrPath }`.replace(/\/{2,}/, '/') : urlOrPath ; + + class RestRequestAction extends RestBaseAction { constructor(public settings?: AjaxParameter) { super(); } } + + class RestResponseAction extends RestBaseAction { constructor(public result: TResult) { super(); } } + + class RestErrorAction extends RestBaseAction { constructor(public error?: Error | string) { super(); } } + + type RestAction = RestRequestAction | RestResponseAction | RestErrorAction; + + /** Represents our middleware to handle rest backend requests */ + const restMiddleware = (api: MiddlewareArg) => + (next: Dispatch) => (action: RestAction): RestAction => { + + // pass all actions through by default + next(action); + // handle the RestRequestAction + if (action instanceof RestRequestAction) { + const state = api.getState(); + const authHeader = isLocalRequest && state && state.framework.authenticationState.user && state.framework.authenticationState.user.token + ? { "Authentication": "Bearer " + state.framework.authenticationState.user.token } : { }; + $.ajax({ + url: uri, + method: (action.settings && action.settings.method) || "GET", + headers: { ...authHeader, ...(action.settings && action.settings.headers ? action.settings.headers : { }) }, + }).then((data: TResult) => { + next(new RestResponseAction(data)); + }).catch((err: any) => { + next(new RestErrorAction()); + next(new AddErrorInfoAction((err instanceof Error) ? { error: err } : { message: err.toString() })); + }); + } + // allways return action + return action; + }; + + /** Represents our action handler to handle our actions */ + const restActionHandler: IActionHandler = (state = initialValue, action) => { + if (action instanceof RestRequestAction) { + return { + ...(state as any), + busy: true + }; + } else if (action instanceof RestResponseAction) { + return action.result; + } else if (action instanceof RestErrorAction) { + return initialValue; + } + return state; + }; + + return { + requestAction: RestRequestAction, + actionHandler: restActionHandler, + middleware: restMiddleware, + }; +} + + + diff --git a/features/sdnr/odlux/odlux/framework/src/services/restService.ts b/features/sdnr/odlux/odlux/framework/src/services/restService.ts new file mode 100644 index 0000000..0be3dca --- /dev/null +++ b/features/sdnr/odlux/odlux/framework/src/services/restService.ts @@ -0,0 +1,168 @@ +/** + * ============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 { ReplaceAction } from '../actions/navigationActions'; +import { AddErrorInfoAction } from '../actions/errorActions'; + +import { storeService } from './storeService'; + +const baseUri = `${ window.location.origin }`; +const absUrlPattern = /^https?:\/\//; + +export const formEncode = (params: { [key: string]: string | number }) => Object.keys(params).map((key) => { + return encodeURIComponent(key) + '=' + encodeURIComponent(params[key].toString()); +}).join('&'); + +const wildcardToRegexp = (pattern: string) => { + return new RegExp('^' + pattern.split(/\*\*/).map((p) => p.split(/\*+/).map((i) => i.replace(/[|\\{}()[\]^$+*?.]/g, '\\$&')).join('^[/]')).join('.*') + '$'); +}; + +export const getAccessPolicyByUrl = (url: string) => { + const result = { + GET : false, + POST: false, + PUT: false, + PATCH: false, + DELETE: false, + }; + + if (!storeService.applicationStore) return result; + + const { state: { framework: { applicationState: { enablePolicy }, authenticationState: { policies } } } } = storeService.applicationStore!; + + result.GET = true; + result.POST = true; + result.PUT = true; + result.PATCH = true; + result.DELETE = true; + + if (!enablePolicy || !policies || policies.length === 0) return result; + + policies.forEach(p => { + const re = wildcardToRegexp(p.path); + if (re.test(url)) { + result.GET = p.methods.get != null ? p.methods.get : result.GET ; + result.POST = p.methods.post != null ? p.methods.post : result.POST ; + result.PUT = p.methods.put != null ? p.methods.put : result.PUT ; + result.PATCH = p.methods.patch != null ? p.methods.patch : result.PATCH ; + result.DELETE = p.methods.delete != null ? p.methods.delete : result.DELETE ; + } + }); + + return result; + +}; + +/** Sends a rest request to the given path and reports the server state. + * @returns An object with the server state, a message and the data or undefined in case of a json parse error. + */ +export async function requestRestExt(path: string = '', initParam: RequestInit = {}, authenticate: boolean = true, isResource: boolean = false): Promise<{ status: number; message?: string; data: TData | null | undefined }> { + const result: { status: number; message?: string; data: TData | null } = { + status: -1, + data: null, + }; + const isAbsUrl = absUrlPattern.test(path); + const uri = isAbsUrl ? path : isResource ? path.replace(/\/{2,}/i, '/') : (baseUri) + ('/' + path).replace(/\/{2,}/i, '/'); + const init = { + 'method': 'GET', + ...initParam, + headers: { + 'Content-Type': 'application/json', + 'Accept': 'application/json', + ...initParam.headers, + } as HeadersInit, + }; + if (!isAbsUrl && authenticate && storeService.applicationStore) { + const { state: { framework: { authenticationState: { user } } } } = storeService.applicationStore; + // do not request if the user is not valid + + if (!user || !user.isValid || !user.token || !user.tokenType) { + return { + ...result, + message: 'User is not valid or not logged in.', + }; + } + (init.headers = { + ...init.headers, + 'Authorization': `${user.tokenType} ${user.token}`, + //'Authorization': 'Basic YWRtaW46YWRtaW4=' + }); + } + + const fetchResult = await fetch(uri, init); + if (fetchResult.status === 309) { + const redirectUrl = fetchResult.headers.get('Location'); + if (! redirectUrl) { + throw new Error('Status code 309 requires header "Location"'); + } + localStorage.removeItem('userToken'); + window.location.href = redirectUrl; + return { + ...result, + status: fetchResult.status, + message: 'Redirecting to new URL.', + }; + } else if (fetchResult.status === 403) { + if (storeService.applicationStore) { + storeService.applicationStore.dispatch(new AddErrorInfoAction({ title: 'Forbidden', message:'Status: [403], access denied.' })); + } + return { + ...result, + status: 403, + message: 'Forbidden.', + }; + } else if (fetchResult.status === 401) { + if (storeService.applicationStore) { + storeService.applicationStore.dispatch(new ReplaceAction(`/login?returnTo=${storeService.applicationStore.state.framework.navigationState.pathname}`)); + } + return { + ...result, + status: 401, + message: 'Authentication requested by server.', + }; + } + const contentType = fetchResult.headers.get('Content-Type') || fetchResult.headers.get('content-type'); + const isJson = contentType && (contentType.toLowerCase().startsWith('application/json') || contentType.toLowerCase().startsWith('application/yang-data+json')); + try { + const data = (isJson ? await fetchResult.json() : await fetchResult.text()) as TData; + return { + ...result, + status: fetchResult.status, + message: fetchResult.statusText, + data: data, + }; + } catch (error) { + return { + ...result, + status: fetchResult.status, + message: error && error.message || String(error), + data: undefined, + }; + } +} + +/** Sends a rest request to the given path. + * @returns The data, or null it there was any error + */ +export async function requestRest(path: string = '', init: RequestInit = {}, authenticate: boolean = true, isResource: boolean = false): Promise { + const res = await requestRestExt(path, init, authenticate, isResource); + if (res && res.status >= 200 && res.status < 300) { + return res.data; + } + return null; +} \ No newline at end of file diff --git a/features/sdnr/odlux/odlux/framework/src/services/snackbarService.ts b/features/sdnr/odlux/odlux/framework/src/services/snackbarService.ts new file mode 100644 index 0000000..50e279c --- /dev/null +++ b/features/sdnr/odlux/odlux/framework/src/services/snackbarService.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========================================================================== + */ +import { OptionsObject } from "notistack"; + +export const snackbarService = { + enqueueSnackbar: (message: string, options?: OptionsObject) =>{ } +} \ No newline at end of file diff --git a/features/sdnr/odlux/odlux/framework/src/services/storeService.ts b/features/sdnr/odlux/odlux/framework/src/services/storeService.ts new file mode 100644 index 0000000..cbb5987 --- /dev/null +++ b/features/sdnr/odlux/odlux/framework/src/services/storeService.ts @@ -0,0 +1,11 @@ +import { ApplicationStore } from "../store/applicationStore"; + +let applicationStore: ApplicationStore | null = null; + +export const startSoreService = (store: ApplicationStore) => { + applicationStore = store; +}; + +export const storeService = { + get applicationStore() { return applicationStore; }, + }; \ No newline at end of file diff --git a/features/sdnr/odlux/odlux/framework/src/services/userSessionService.ts b/features/sdnr/odlux/odlux/framework/src/services/userSessionService.ts new file mode 100644 index 0000000..8d899c4 --- /dev/null +++ b/features/sdnr/odlux/odlux/framework/src/services/userSessionService.ts @@ -0,0 +1,80 @@ +/** + * ============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 { ApplicationStore } from "../store/applicationStore"; +import { logoutUser } from "../actions/authentication"; +import { ReplaceAction } from "../actions/navigationActions"; +import { AuthMessage, getBroadcastChannel } from "./broadcastService"; +import { User } from "../models/authentication"; + +let currentUser: User | null; +let applicationStore: ApplicationStore | null = null; +let timer : null | ReturnType = null; + +export const startUserSessionService = (store: ApplicationStore) =>{ + applicationStore=store; +} + +export const startUserSession = (user: User) => { + console.log("user session started...") + + const currentTime = new Date(); + //get time differnce between login time and now (eg after user refreshes page) + const timeDiffernce =(currentTime.valueOf()/1000 - user.loginAt); + + currentUser = user; + + if (process.env.NODE_ENV === "development") { + //console.warn("logout timer not started in development mode"); + + const expiresIn = (user.logoutAt - user.loginAt) - timeDiffernce; + console.log("user should be logged out in: "+expiresIn/60 +"minutes") + createForceLogoutInterval(expiresIn); + } else { + const expiresIn = (user.logoutAt - user.loginAt) - timeDiffernce; + console.log("user should be logged out in: "+expiresIn/60 +"minutes") + createForceLogoutInterval(expiresIn); + } +}; + +const createForceLogoutInterval = (intervalInSec: number) => { + console.log("logout timer running..."); + + if(timer!==null){ + console.error("an old session was available"); + clearTimeout(timer); + } + + timer = setTimeout(function () { + if (currentUser && applicationStore) { + + applicationStore.dispatch(logoutUser()); + applicationStore.dispatch(new ReplaceAction("/login")); + + } + + }, intervalInSec * 1000) +} + +export const endUserSession = ()=>{ + + if(timer!==null){ + clearTimeout(timer); + timer=null; + } +} \ No newline at end of file diff --git a/features/sdnr/odlux/odlux/framework/src/services/userdataService.ts b/features/sdnr/odlux/odlux/framework/src/services/userdataService.ts new file mode 100644 index 0000000..53de8e1 --- /dev/null +++ b/features/sdnr/odlux/odlux/framework/src/services/userdataService.ts @@ -0,0 +1,28 @@ +/** + * ============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 { requestRest } from './restService'; + +const settingsPath = '/userdata'; + +export const getUserData = (partialPath?: string) => requestRest(partialPath ? settingsPath + partialPath : settingsPath, { method: 'GET' }); + +export const saveUserData = (partialPath: string, data: string) => requestRest(settingsPath + partialPath, { method: 'PUT', body: data }); + + + diff --git a/features/sdnr/odlux/odlux/framework/src/store/applicationStore.ts b/features/sdnr/odlux/odlux/framework/src/store/applicationStore.ts new file mode 100644 index 0000000..cbe8c20 --- /dev/null +++ b/features/sdnr/odlux/odlux/framework/src/store/applicationStore.ts @@ -0,0 +1,74 @@ +/** + * ============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 { Store } from '../flux/store'; +import { combineActionHandler, MiddlewareArg, Middleware, chainMiddleware } from '../flux/middleware'; + +import applicationService from '../services/applicationManager'; + +import { applicationRegistryHandler, IApplicationRegistration } from '../handlers/applicationRegistryHandler'; +import { authenticationStateHandler, IAuthenticationState } from '../handlers/authenticationHandler'; +import { applicationStateHandler, IApplicationState } from '../handlers/applicationStateHandler'; +import { navigationStateHandler, INavigationState } from '../handlers/navigationStateHandler'; + +import { setApplicationStore } from '../services/applicationApi'; + +import apiMiddleware from '../middleware/api'; +import thunkMiddleware from '../middleware/thunk'; +import loggerMiddleware from '../middleware/logger'; +import routerMiddleware from '../middleware/navigation'; +import { updatePolicies } from '../middleware/policies'; + +export type MiddlewareApi = MiddlewareArg; + +export interface IFrameworkStoreState { + applicationRegistration: IApplicationRegistration; + applicationState: IApplicationState; + authenticationState: IAuthenticationState; + navigationState: INavigationState; +} + +export interface IApplicationStoreState { + framework: IFrameworkStoreState; +} + +const frameworkHandlers = combineActionHandler({ + applicationRegistration: applicationRegistryHandler, + applicationState: applicationStateHandler, + authenticationState: authenticationStateHandler, + navigationState: navigationStateHandler +}); + +export class ApplicationStore extends Store { } + +/** This function will create the application store considering the currently registered application ans their middlewares. */ +export const applicationStoreCreator = (): ApplicationStore => { + const middlewares: Middleware[] = []; + const actionHandlers = Object.keys(applicationService.applications).reduce((acc, cur) => { + const reg = applicationService.applications[cur]; + reg && typeof reg.rootActionHandler === 'function' && (acc[cur] = reg.rootActionHandler); + reg && reg.middlewares && Array.isArray(reg.middlewares) && middlewares.push(...(reg.middlewares as Middleware[])); + return acc; + }, { framework: frameworkHandlers } as any); + + const applicationStore = new ApplicationStore(combineActionHandler(actionHandlers), chainMiddleware(loggerMiddleware, thunkMiddleware, routerMiddleware, apiMiddleware, updatePolicies, ...middlewares)); + setApplicationStore(applicationStore); + return applicationStore; +} + +export default applicationStoreCreator; \ No newline at end of file diff --git a/features/sdnr/odlux/odlux/framework/src/styles/att.ts b/features/sdnr/odlux/odlux/framework/src/styles/att.ts new file mode 100644 index 0000000..2d54590 --- /dev/null +++ b/features/sdnr/odlux/odlux/framework/src/styles/att.ts @@ -0,0 +1,46 @@ +/** + * ============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 { createTheme, adaptV4Theme } from '@mui/material/styles'; + +const theme = createTheme(adaptV4Theme({ + design: { + id: "att", + name: "AT&T", + url: "https://pmcvariety.files.wordpress.com/2016/04/att_logo.jpg?w=1000&h=563&crop=1", + height: 70, + width: 150, + logoHeight: 60, + }, + palette: { + primary: { + light: "#f2f2f29c", + main: "#f2f2f2", + dark: "#d5d5d5", + contrastText: "#0094d3" + }, + secondary: { + light: "#f2f2f2", + main: "rgba(51, 171, 226, 1)", + dark: "rgba(41, 159, 213, 1)", + contrastText: "#0094d3" + } + }, +})); + +export default theme; diff --git a/features/sdnr/odlux/odlux/framework/src/utilities/elasticSearch.ts b/features/sdnr/odlux/odlux/framework/src/utilities/elasticSearch.ts new file mode 100644 index 0000000..e1d3752 --- /dev/null +++ b/features/sdnr/odlux/odlux/framework/src/utilities/elasticSearch.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 { Result, ResultTopology } from '../models'; +import { DataCallback } from '../components/material-table'; + +import { requestRest } from '../services/restService'; + +import { convertPropertyNames, convertPropertyValues, replaceUpperCase, replaceHyphen } from './yangHelper'; + +type propType = string | number | null | undefined | (string | number)[]; +type dataType = { [prop: string]: propType }; + +/** Represents a fabric for the searchDataHandler used by the internal data api. + * @param typeName The name of the entry type to create a searchDataHandler for. + * @param additionalFilters Filterproperties and their values to add permanently. + * @returns The searchDataHandler callback to be used with the material table. +*/ +export function createSearchDataHandler(typeName: (() => string) | string, connectToTopologyServer?: boolean, additionalFilters?: {} | null | undefined): DataCallback<(TResult)> { + const fetchData: DataCallback<(TResult)> = async (pageIndex, rowsPerPage, orderBy, order, filter) => { + + const topologyUrl = `/topology/network/read-${typeof typeName === "function" ? typeName() : typeName}-list`; + const dataProviderUrl = `/rests/operations/data-provider:read-${typeof typeName === "function" ? typeName() : typeName}-list`; + + const url = connectToTopologyServer ? topologyUrl : dataProviderUrl; + + filter = { ...filter, ...additionalFilters }; + + const filterKeys = filter && Object.keys(filter) || []; + + const input = { + filter: filterKeys.filter(f => filter![f] != null && filter![f] !== "").map(property => ({ property, filtervalue: filter![property] })), + sortorder: orderBy ? [{ property: orderBy, sortorder: order === "desc" ? "descending" : "ascending" }] : [], + pagination: { size: rowsPerPage, page: (pageIndex != null && pageIndex > 0 && pageIndex || 0) + 1 } + } + + if (url.includes('data-provider')) { + const query = { + "data-provider:input": input + }; + + const result = await requestRest>(url, { + method: "POST", // *GET, POST, PUT, DELETE, etc. + mode: "same-origin", // no-cors, cors, *same-origin + cache: "no-cache", // *default, no-cache, reload, force-cache, only-if-cached + headers: { + "Content-Type": "application/json", + // "Content-Type": "application/x-www-form-urlencoded", + }, + body: JSON.stringify(convertPropertyValues(query, replaceUpperCase)), // body data type must match "Content-Type" header + }); + if (result) { + let rows: TResult[] = []; + + if (result && result["data-provider:output"] && result["data-provider:output"].data) { + rows = result["data-provider:output"].data.map(obj => convertPropertyNames(obj, replaceHyphen)) || [] + } + + const data = { + page: +(result["data-provider:output"].pagination && result["data-provider:output"].pagination.page != null && result["data-provider:output"].pagination.page - 1 || 0), total: +(result["data-provider:output"].pagination && result["data-provider:output"].pagination.total || 0), rows: rows + }; + return data; + } + } else if (url.includes('topology')) { + + const queryTopology = { + "input": input + }; + + const resultTopology = await requestRest>(url, { + method: "POST", // *GET, POST, PUT, DELETE, etc. + mode: "same-origin", // no-cors, cors, *same-origin + cache: "no-cache", // *default, no-cache, reload, force-cache, only-if-cached + headers: { + "Content-Type": "application/json", + // "Content-Type": "application/x-www-form-urlencoded", + }, + body: JSON.stringify(queryTopology), // body data type must match "Content-Type" header + }); + if (resultTopology) { + let rows: TResult[] = []; + + if (resultTopology && resultTopology.output && resultTopology.output.data) { + rows = resultTopology.output.data.map(obj => obj) || [] + } + + const data = { + page: +(resultTopology.output.pagination && resultTopology.output.pagination.page != null && resultTopology.output.pagination.page - 1 || 0), total: +(resultTopology.output.pagination && resultTopology.output.pagination.total || 0), rows: rows + }; + return data; + } + } + + return { page: 1, total: 0, rows: [] }; + }; + + return fetchData; +} + diff --git a/features/sdnr/odlux/odlux/framework/src/utilities/logLevel.ts b/features/sdnr/odlux/odlux/framework/src/utilities/logLevel.ts new file mode 100644 index 0000000..a198d98 --- /dev/null +++ b/features/sdnr/odlux/odlux/framework/src/utilities/logLevel.ts @@ -0,0 +1,8 @@ +export enum LogLevel { + Always = 0, + Error = 1, + Warning = 2, + Info = 3, + Debug = 4, + Trace = 5, +} diff --git a/features/sdnr/odlux/odlux/framework/src/utilities/withComponents.ts b/features/sdnr/odlux/odlux/framework/src/utilities/withComponents.ts new file mode 100644 index 0000000..8e460ad --- /dev/null +++ b/features/sdnr/odlux/odlux/framework/src/utilities/withComponents.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 * as React from 'react'; +import applicationService from '../services/applicationManager'; +export type WithComponents = { + components: { [prop in keyof T]: React.ComponentType } +}; + +export function withComponents(mapping: TMap) { + return (component: React.ComponentType>): React.ComponentType => { + const components = {} as any; + Object.keys(mapping).forEach(name => { + const [appKey, componentKey] = mapping[name].split('.'); + const reg = applicationService.applications[appKey]; + components[name] = reg && reg.exportedComponents && reg.exportedComponents[componentKey] || (() => null); + }); + return (props: TProps) => ( + React.createElement(component, Object.assign({ components }, props)) + ); + } +} +export default withComponents; \ No newline at end of file diff --git a/features/sdnr/odlux/odlux/framework/src/utilities/yangHelper.ts b/features/sdnr/odlux/odlux/framework/src/utilities/yangHelper.ts new file mode 100644 index 0000000..7e77c05 --- /dev/null +++ b/features/sdnr/odlux/odlux/framework/src/utilities/yangHelper.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 const replaceHyphen = (name: string) => name.replace(/-([a-z])/g, (g) => (g[1].toUpperCase())); +export const replaceUpperCase = (name: string) => name.replace(/([a-z][A-Z])/g, (g) => g[0] + '-' + g[1].toLowerCase()); + +/*** + * Replaces whitespace with '-' and cast everything to lowercase + */ +export const toAriaLabel = (value: string) => value.replace(/\s/g, "-").toLowerCase(); + +export const convertPropertyNames = (obj: T, conv: (name: string) => string): T => { + return Object.keys(obj).reduce<{ [prop: string]: any }>((acc, cur) => { + acc[conv(cur)] = typeof obj[cur] === "object" ? convertPropertyNames(obj[cur], conv) : obj[cur]; + return acc; + }, obj instanceof Array ? [] : {}) as T; +} + +export const convertPropertyValues = (obj: T, conv: (name: string) => string): T => { + return Object.keys(obj).reduce<{ [prop: string]: any }>((acc, cur) => { + acc[cur] = typeof obj[cur] === "object" + ? convertPropertyValues(obj[cur], conv) + : cur === "property" + ? conv(obj[cur]) + : obj[cur]; + return acc; + }, obj instanceof Array ? [] : {}) as T; +} \ No newline at end of file diff --git a/features/sdnr/odlux/odlux/framework/src/views/about.tsx b/features/sdnr/odlux/odlux/framework/src/views/about.tsx new file mode 100644 index 0000000..eb64809 --- /dev/null +++ b/features/sdnr/odlux/odlux/framework/src/views/about.tsx @@ -0,0 +1,198 @@ +/** + * ============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, useEffect, useState } from 'react'; +import * as marked from 'marked'; +import * as hljs from 'highlight.js'; +import { requestRestExt } from '../services/restService'; +import { Button, Typography } from '@mui/material'; + +const defaultRenderer = new marked.Renderer(); +defaultRenderer.link = (href, title, text) => ( + `${text}` +); + +type OdluxVersion= {version:string,build:string, framework: string, + applications:{ + configurationApp: string, + connectApp: string, + eventLogApp: string, + faultApp: string, + helpApp: string, + inventoryApp: string, + microwaveApp: string, + maintenanceApp: string, + mediatorApp: string, + networkMapApp: string, + permanceHistoryApp: string, + siteManagerApp: string, + unmFaultManagementApp: string; + }}; + +type TopologyVersion = {version: string, buildTimestamp: string}; + +const AboutComponent: FC = (props) => { + + const textareaRef = React.createRef(); + const [content, setContent] = useState(null); + const [isCopiedSuccessfully, setCopySuccess] = useState(false); + const [isContetLoaded, setContentLoaded] = useState(false); + + useEffect(()=>{ + loadAboutContent(); + },[]); + + const getMarkOdluxVersionMarkdownTable = (data:OdluxVersion|null|undefined):string => { + if(!data) { + return ""; + }else{ + let applicationVersions= ''; + if(data.applications){ + + applicationVersions = `| Framework | ${data.framework}|\n `+ + `| ConnectApp | ${data.applications.connectApp}|\n `+ + `| FaultApp | ${data.applications.faultApp}|\n `+ + `| MaintenanceApp | ${data.applications.maintenanceApp}|\n `+ + `| ConfigurationApp | ${data.applications.configurationApp}|\n `+ + `| PerformanceHistoryApp | ${data.applications.permanceHistoryApp}|\n `+ + `| InventoryApp | ${data.applications.inventoryApp}|\n `+ + `| EventLogApp | ${data.applications.eventLogApp}|\n `+ + `| MediatorApp | ${data.applications.mediatorApp}|\n `+ + `| NetworkMapApp | ${data.applications.networkMapApp}|\n `+ + `| MicrowaveApp | ${data.applications.microwaveApp}|\n `+ + `| SiteManagerApp | ${data.applications.siteManagerApp}|\n `+ + `| HelpApp | ${data.applications.helpApp}|\n `; + `| UnmFaultManagementApp | ${data.applications.unmFaultManagementApp}|\n `; + } + + return `| | |\n| --- | --- |\n| Version | ${data.version} |\n| Build timestamp | ${data.build}|\n`+ + applicationVersions; + } + } + + const getTopologyVersionMarkdownTable = (data: TopologyVersion|null|undefined) => { + if(!data){ + return "No version"; + } + else + { + const topologyInfo = `| | |\n| --- | --- |\n| Version | ${data.version} |\n` + + `| Build timestamp | ${data.buildTimestamp} |\n`; + return topologyInfo; + } + } + + const loadAboutContent = (): void => { + const baseUri = window.location.pathname.substring(0,window.location.pathname.lastIndexOf("/")+1); + const init = { + 'method': 'GET', + headers: { + 'Content-Type': 'application/json', + 'Accept': 'text/markdown', + } + }; + const p1 = requestRestExt('/about',init); + const p2 = requestRestExt(`${baseUri}version.json`); + const p3 = requestRestExt(`/topology/info/version`); + + Promise.all([p1,p2, p3]).then((responses) => { + const response = responses[0]; + const response2 = responses[1]; + const response3 = responses[2]; + const content = response.status == 200 ? response.data : `${response.status} ${response.message}` || "Server error"; + const content2 = `\n## ODLUX Version Info\n`+(response2.status == 200 ? getMarkOdluxVersionMarkdownTable(response2.data) : `${response2.message}` || "ODLUX Server error"); + const content3 = `\n## Topology API Version Info\n`+(response3.status == 200 ? getTopologyVersionMarkdownTable(response3.data): `Topology API not available`); + const loadedSucessfully = response.status == 200 ? true : false; + setContent((content + content2 + content3 ) || null); + setContentLoaded(loadedSucessfully); + }).catch((error) => { + setContent(error); + }); + } + + const copyToClipboard = (e: React.MouseEvent) =>{ + e.preventDefault(); + + if(textareaRef.current!==null){ + textareaRef.current.select(); + document.execCommand('copy'); + if(e.currentTarget != null){ // refocus on button, otherwhise the textarea would be focused + e.currentTarget.focus(); + } + setCopySuccess(true); + window.setTimeout(()=>{ setCopySuccess(false);},2000); + } + } + + const markedOptions: marked.MarkedOptions = { + gfm: true, + breaks: false, + pedantic: false, + sanitize: true, + smartLists: true, + smartypants: false, + langPrefix: 'hljs ', + ...({}), + highlight: (code, lang) => { + if (!!(lang && hljs.getLanguage(lang))) { + return hljs.highlight(lang, code).value; + } + return code; + } + }; + + + const className = "about-table" + const style: React.CSSProperties = {}; + const containerStyle = { overflow: "auto", paddingRight: "20px" } + + const html = (marked(content || 'loading', { renderer: markedOptions && markedOptions.renderer || defaultRenderer })); + + return ( +
+ { isContetLoaded && +
+ + { + isCopiedSuccessfully && + + copied successfully + + } +
+ } + +
+
+