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

Invalid Choice

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

{renderMessage}

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

Processing ...

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

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

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

Welcome to ODLUX

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

Loading ...

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

Detail {props.match.params.id}

+

This are the information about {props.staticContext}.

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

Selected Network Element: {props.selectedNE}

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

Collecting Data ...

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

Data couldn't be loaded

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

Please select a LTP

+
+ : +
+

No performance data found

+
) + } + ); +}; + +export default LtpSelection; \ No newline at end of file diff --git a/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/components/performanceData.tsx b/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/components/performanceData.tsx new file mode 100644 index 000000000..fb608aac6 --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/components/performanceData.tsx @@ -0,0 +1,150 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + * ============LICENSE_END========================================================================== + */ +import React from 'react'; +import { RouteComponentProps, withRouter } from 'react-router-dom'; + +import { ColumnModel, ColumnType, MaterialTable, MaterialTableCtorType } from '../../../../framework/src/components/material-table'; +import { connect, Connect, IDispatcher } from '../../../../framework/src/flux/connect'; +import { IApplicationStoreState } from '../../../../framework/src/store/applicationStore'; + +import { SetFilterVisibility, SetSubViewAction } from '../actions/toggleActions'; +import { createPerformanceDataActions, createPerformanceDataProperties } from '../handlers/performanceDataHandler'; +import { IDataSet, IDataSetsObject } from '../models/chartTypes'; +import { PerformanceDatabaseDataType, PerformanceDataType } from '../models/performanceDataType'; +import { lineChart, sortDataByTimeStamp } from '../utils/chartUtils'; +import { addColumnLabels } from '../utils/tableUtils'; +import ToggleContainer from './toggleContainer'; + +const mapProps = (state: IApplicationStoreState) => ({ + performanceDataProperties: createPerformanceDataProperties(state), + currentView: state.performanceHistory.subViews.performanceData.subView, + isFilterVisible: state.performanceHistory.subViews.performanceData.isFilterVisible, + existingFilter: state.performanceHistory.performanceData.filter, +}); + +const mapDisp = (dispatcher: IDispatcher) => ({ + performanceDataActions: createPerformanceDataActions(dispatcher.dispatch), + setSubView: (value: 'chart' | 'table') => dispatcher.dispatch(new SetSubViewAction('performanceData', value)), + toggleFilterButton: (value: boolean) => { dispatcher.dispatch(new SetFilterVisibility('performanceData', value)); }, +}); + +type PerformanceDataComponentProps = RouteComponentProps & Connect & { + selectedTimePeriod: string; +}; + +const PerformanceDataTable = MaterialTable as MaterialTableCtorType; + +/** + * The Component which gets the performance data from the database based on the selected time period. + */ +class PerformanceDataComponent extends React.Component { + onToggleFilterButton = () => { + this.props.toggleFilterButton(!this.props.isFilterVisible); + }; + + onFilterChanged = (property: string, filterTerm: string) => { + this.props.performanceDataActions.onFilterChanged(property, filterTerm); + if (!this.props.performanceDataProperties.showFilter) + this.props.performanceDataActions.onToggleFilter(false); + }; + + render(): JSX.Element { + const properties = this.props.performanceDataProperties; + const actions = this.props.performanceDataActions; + + const chartPagedData = this.getChartDataValues(properties.rows); + const performanceColumns: ColumnModel[] = [ + { property: 'radioSignalId', title: 'Radio signal', type: ColumnType.text }, + { property: 'scannerId', title: 'Scanner ID', type: ColumnType.text }, + { property: 'timeStamp', title: 'End Time', type: ColumnType.text }, + { + property: 'suspectIntervalFlag', title: 'Suspect Interval', type: ColumnType.boolean, + }, + ]; + + chartPagedData.datasets.forEach(ds => { + performanceColumns.push(addColumnLabels(ds.name, ds.columnLabel)); + }); + return ( + <> + this.props.toggleFilterButton(!this.props.isFilterVisible)} + existingFilter={this.props.existingFilter} onFilterChanged={this.onFilterChanged} selectedValue={this.props.currentView} showFilter={this.props.isFilterVisible} onChange={this.props.setSubView}> + {lineChart(chartPagedData)} + + + + ); + } + + /** + * This function gets the performance values for PerformanceData according on the chartjs dataset structure + * which is to be sent to the chart. + */ + private getChartDataValues = (rows: PerformanceDataType[]): IDataSetsObject => { + const data_rows = [...rows]; + sortDataByTimeStamp(data_rows); + + const datasets: IDataSet[] = [{ + name: 'es', + label: 'es', + borderColor: '#0e17f3de', + bezierCurve: false, + lineTension: 0, + fill: false, + data: [], + columnLabel: 'ES', + }, { + name: 'ses', + label: 'ses', + borderColor: '#08edb6de', + bezierCurve: false, + lineTension: 0, + fill: false, + data: [], + columnLabel: 'SES', + }, { + name: 'unavailability', + label: 'unavailability', + borderColor: '#b308edde', + bezierCurve: false, + lineTension: 0, + fill: false, + data: [], + columnLabel: 'Unavailability', + }]; + + data_rows.forEach(row => { + row.es = row.performanceData.es; + row.ses = row.performanceData.ses; + row.unavailability = row.performanceData.unavailability; + datasets.forEach(ds => { + ds.data.push({ + x: row['timeStamp' as keyof PerformanceDataType] as string, + y: row.performanceData[ds.name as keyof PerformanceDatabaseDataType] as string, + }); + }); + }); + + return { + datasets: datasets, + }; + }; +} + +const PerformanceData = withRouter(connect(mapProps, mapDisp)(PerformanceDataComponent)); +export default PerformanceData; diff --git a/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/components/receiveLevel.tsx b/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/components/receiveLevel.tsx new file mode 100644 index 000000000..125cc6ee6 --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/components/receiveLevel.tsx @@ -0,0 +1,154 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + * ============LICENSE_END========================================================================== + */ +import React from 'react'; +import { RouteComponentProps, withRouter } from 'react-router-dom'; + +import { ColumnModel, ColumnType, MaterialTable, MaterialTableCtorType } from '../../../../framework/src/components/material-table'; +import { connect, Connect, IDispatcher } from '../../../../framework/src/flux/connect'; +import { IApplicationStoreState } from '../../../../framework/src/store/applicationStore'; + +import { SetFilterVisibility, SetSubViewAction } from '../actions/toggleActions'; +import { createReceiveLevelActions, createReceiveLevelProperties } from '../handlers/receiveLevelHandler'; +import { IDataSet, IDataSetsObject } from '../models/chartTypes'; +import { ReceiveLevelDatabaseDataType, ReceiveLevelDataType } from '../models/receiveLevelDataType'; +import { lineChart, sortDataByTimeStamp } from '../utils/chartUtils'; +import { addColumnLabels } from '../utils/tableUtils'; +import ToggleContainer from './toggleContainer'; + +const mapProps = (state: IApplicationStoreState) => ({ + receiveLevelProperties: createReceiveLevelProperties(state), + currentView: state.performanceHistory.subViews.receiveLevel.subView, + isFilterVisible: state.performanceHistory.subViews.receiveLevel.isFilterVisible, + existingFilter: state.performanceHistory.receiveLevel.filter, +}); + +const mapDisp = (dispatcher: IDispatcher) => ({ + receiveLevelActions: createReceiveLevelActions(dispatcher.dispatch), + setSubView: (value: 'chart' | 'table') => dispatcher.dispatch(new SetSubViewAction('receiveLevel', value)), + toggleFilterButton: (value: boolean) => { dispatcher.dispatch(new SetFilterVisibility('receiveLevel', value)); }, +}); + +type ReceiveLevelComponentProps = RouteComponentProps & Connect & { + selectedTimePeriod: string; +}; + +const ReceiveLevelTable = MaterialTable as MaterialTableCtorType; + +/** + * The Component which gets the receiveLevel data from the database based on the selected time period. + */ +class ReceiveLevelComponent extends React.Component { + onToggleFilterButton = () => { + this.props.toggleFilterButton(!this.props.isFilterVisible); + }; + + + onChange = (value: 'chart' | 'table') => { + this.props.setSubView(value); + }; + + onFilterChanged = (property: string, filterTerm: string) => { + this.props.receiveLevelActions.onFilterChanged(property, filterTerm); + if (!this.props.receiveLevelProperties.showFilter) + this.props.receiveLevelActions.onToggleFilter(false); + }; + + render(): JSX.Element { + const properties = this.props.receiveLevelProperties; + const actions = this.props.receiveLevelActions; + + const chartPagedData = this.getChartDataValues(properties.rows); + const receiveLevelColumns: ColumnModel[] = [ + { property: 'radioSignalId', title: 'Radio signal', type: ColumnType.text }, + { property: 'scannerId', title: 'Scanner ID', type: ColumnType.text }, + { property: 'timeStamp', title: 'End Time', type: ColumnType.text }, + { + property: 'suspectIntervalFlag', title: 'Suspect Interval', type: ColumnType.boolean, + }, + ]; + + chartPagedData.datasets.forEach(ds => { + receiveLevelColumns.push(addColumnLabels(ds.name, ds.columnLabel)); + }); + + return ( + <> + + {lineChart(chartPagedData)} + + + + ); + } + + /** + * This function gets the performance values for ReceiveLevel according on the chartjs dataset structure + * which is to be sent to the chart. + */ + private getChartDataValues = (rows: ReceiveLevelDataType[]): IDataSetsObject => { + const data_rows = [...rows]; + sortDataByTimeStamp(data_rows); + + const datasets: IDataSet[] = [{ + name: 'rxLevelMin', + label: 'rx-level-min', + borderColor: '#0e17f3de', + bezierCurve: false, + lineTension: 0, + fill: false, + data: [], + columnLabel: 'Rx min', + }, { + name: 'rxLevelAvg', + label: 'rx-level-avg', + borderColor: '#08edb6de', + bezierCurve: false, + lineTension: 0, + fill: false, + data: [], + columnLabel: 'Rx avg', + }, { + name: 'rxLevelMax', + label: 'rx-level-max', + borderColor: '#b308edde', + bezierCurve: false, + lineTension: 0, + fill: false, + data: [], + columnLabel: 'Rx max', + }]; + + data_rows.forEach(row => { + row.rxLevelMin = row.performanceData.rxLevelMin; + row.rxLevelAvg = row.performanceData.rxLevelAvg; + row.rxLevelMax = row.performanceData.rxLevelMax; + datasets.forEach(ds => { + ds.data.push({ + x: row['timeStamp' as keyof ReceiveLevelDataType] as string, + y: row.performanceData[ds.name as keyof ReceiveLevelDatabaseDataType] as string, + }); + }); + }); + return { + datasets: datasets, + }; + }; +} + +const ReceiveLevel = withRouter(connect(mapProps, mapDisp)(ReceiveLevelComponent)); +export default ReceiveLevel; diff --git a/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/components/signalToInterference.tsx b/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/components/signalToInterference.tsx new file mode 100644 index 000000000..85241fbb1 --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/components/signalToInterference.tsx @@ -0,0 +1,156 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + * ============LICENSE_END========================================================================== + */ +import React from 'react'; +import { RouteComponentProps, withRouter } from 'react-router-dom'; + +import { ColumnModel, ColumnType, MaterialTable, MaterialTableCtorType } from '../../../../framework/src/components/material-table'; +import { connect, Connect, IDispatcher } from '../../../../framework/src/flux/connect'; +import { IApplicationStoreState } from '../../../../framework/src/store/applicationStore'; + +import { SetFilterVisibility, SetSubViewAction } from '../actions/toggleActions'; +import { createSignalToInterferenceActions, createSignalToInterferenceProperties } from '../handlers/signalToInterferenceHandler'; +import { IDataSet, IDataSetsObject } from '../models/chartTypes'; +import { SignalToInterferenceDatabaseDataType, SignalToInterferenceDataType } from '../models/signalToInteferenceDataType'; +import { lineChart, sortDataByTimeStamp } from '../utils/chartUtils'; +import { addColumnLabels } from '../utils/tableUtils'; +import ToggleContainer from './toggleContainer'; + +const mapProps = (state: IApplicationStoreState) => ({ + signalToInterferenceProperties: createSignalToInterferenceProperties(state), + currentView: state.performanceHistory.subViews.SINR.subView, + isFilterVisible: state.performanceHistory.subViews.SINR.isFilterVisible, + existingFilter: state.performanceHistory.signalToInterference.filter, +}); + +const mapDisp = (dispatcher: IDispatcher) => ({ + signalToInterferenceActions: createSignalToInterferenceActions(dispatcher.dispatch), + setSubView: (value: 'chart' | 'table') => dispatcher.dispatch(new SetSubViewAction('SINR', value)), + toggleFilterButton: (value: boolean) => { dispatcher.dispatch(new SetFilterVisibility('SINR', value)); }, +}); + +type SignalToInterferenceComponentProps = RouteComponentProps & Connect & { + selectedTimePeriod: string; +}; + +const SignalToInterferenceTable = MaterialTable as MaterialTableCtorType; + +/** + * The Component which gets the signal to interference data from the database based on the selected time period. + */ +class SignalToInterferenceComponent extends React.Component { + onToggleFilterButton = () => { + this.props.toggleFilterButton(!this.props.isFilterVisible); + }; + + onChange = (value: 'chart' | 'table') => { + this.props.setSubView(value); + }; + + onFilterChanged = (property: string, filterTerm: string) => { + this.props.signalToInterferenceActions.onFilterChanged(property, filterTerm); + if (!this.props.signalToInterferenceProperties.showFilter) + this.props.signalToInterferenceActions.onToggleFilter(false); + }; + + render(): JSX.Element { + const properties = this.props.signalToInterferenceProperties; + const actions = this.props.signalToInterferenceActions; + + const chartPagedData = this.getChartDataValues(properties.rows); + + const sinrColumns: ColumnModel[] = [ + { property: 'radioSignalId', title: 'Radio signal', type: ColumnType.text }, + { property: 'scannerId', title: 'Scanner ID', type: ColumnType.text }, + { property: 'timeStamp', title: 'End Time', type: ColumnType.text }, + { + property: 'suspectIntervalFlag', title: 'Suspect Interval', type: ColumnType.boolean, + }, + ]; + + chartPagedData.datasets.forEach(ds => { + sinrColumns.push(addColumnLabels(ds.name, ds.columnLabel)); + }); + return ( + <> + + {lineChart(chartPagedData)} + + + + ); + } + + /** + * This function gets the performance values for SINR according on the chartjs dataset structure + * which is to be sent to the chart. + */ + + private getChartDataValues = (rows: SignalToInterferenceDataType[]): IDataSetsObject => { + const data_rows = [...rows]; + sortDataByTimeStamp(data_rows); + + const datasets: IDataSet[] = [{ + name: 'snirMin', + label: 'snir-min', + borderColor: '#0e17f3de', + bezierCurve: false, + lineTension: 0, + fill: false, + data: [], + columnLabel: 'SINR (min)[db]', + }, { + name: 'snirAvg', + label: 'snir-avg', + borderColor: '#08edb6de', + bezierCurve: false, + lineTension: 0, + fill: false, + data: [], + columnLabel: 'SINR (avg)[db]', + }, { + name: 'snirMax', + label: 'snir-max', + borderColor: '#b308edde', + bezierCurve: false, + lineTension: 0, + fill: false, + data: [], + columnLabel: 'SINR (max)[db]', + }]; + + data_rows.forEach(row => { + row.snirMin = row.performanceData.snirMin; + row.snirAvg = row.performanceData.snirAvg; + row.snirMax = row.performanceData.snirMax; + datasets.forEach(ds => { + ds.data.push({ + x: row['timeStamp' as keyof SignalToInterferenceDataType] as string, + y: row.performanceData[ds.name as keyof SignalToInterferenceDatabaseDataType] as string, + }); + }); + }); + return { + datasets: datasets, + }; + }; +} + +const SignalToInterference = withRouter(connect(mapProps, mapDisp)(SignalToInterferenceComponent)); +export default SignalToInterference; diff --git a/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/components/temperature.tsx b/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/components/temperature.tsx new file mode 100644 index 000000000..d4b823387 --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/components/temperature.tsx @@ -0,0 +1,156 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + * ============LICENSE_END========================================================================== + */ +import React from 'react'; +import { RouteComponentProps, withRouter } from 'react-router-dom'; + +import { ColumnModel, ColumnType, MaterialTable, MaterialTableCtorType } from '../../../../framework/src/components/material-table'; +import { connect, Connect, IDispatcher } from '../../../../framework/src/flux/connect'; +import { IApplicationStoreState } from '../../../../framework/src/store/applicationStore'; + +import { SetFilterVisibility, SetSubViewAction } from '../actions/toggleActions'; +import { createTemperatureActions, createTemperatureProperties } from '../handlers/temperatureHandler'; +import { IDataSet, IDataSetsObject } from '../models/chartTypes'; +import { TemperatureDatabaseDataType, TemperatureDataType } from '../models/temperatureDataType'; +import { lineChart, sortDataByTimeStamp } from '../utils/chartUtils'; +import { addColumnLabels } from '../utils/tableUtils'; +import ToggleContainer from './toggleContainer'; + +const mapProps = (state: IApplicationStoreState) => ({ + temperatureProperties: createTemperatureProperties(state), + currentView: state.performanceHistory.subViews.temperatur.subView, + isFilterVisible: state.performanceHistory.subViews.temperatur.isFilterVisible, + existingFilter: state.performanceHistory.temperature.filter, +}); + +const mapDisp = (dispatcher: IDispatcher) => ({ + temperatureActions: createTemperatureActions(dispatcher.dispatch), + setSubView: (value: 'chart' | 'table') => dispatcher.dispatch(new SetSubViewAction('Temp', value)), + toggleFilterButton: (value: boolean) => { dispatcher.dispatch(new SetFilterVisibility('Temp', value)); }, + +}); + +type TemperatureComponentProps = RouteComponentProps & Connect & { + selectedTimePeriod: string; +}; + +const TemperatureTable = MaterialTable as MaterialTableCtorType; + +/** + * The Component which gets the temperature data from the database based on the selected time period. + */ +class TemperatureComponent extends React.Component { + onToggleFilterButton = () => { + this.props.toggleFilterButton(!this.props.isFilterVisible); + }; + + + onChange = (value: 'chart' | 'table') => { + this.props.setSubView(value); + }; + + onFilterChanged = (property: string, filterTerm: string) => { + this.props.temperatureActions.onFilterChanged(property, filterTerm); + if (!this.props.temperatureProperties.showFilter) + this.props.temperatureActions.onToggleFilter(false); + }; + + render(): JSX.Element { + const properties = this.props.temperatureProperties; + const actions = this.props.temperatureActions; + + const chartPagedData = this.getChartDataValues(properties.rows); + const temperatureColumns: ColumnModel[] = [ + { property: 'radioSignalId', title: 'Radio signal', type: ColumnType.text }, + { property: 'scannerId', title: 'Scanner ID', type: ColumnType.text }, + { property: 'timeStamp', title: 'End Time', type: ColumnType.text }, + { + property: 'suspectIntervalFlag', title: 'Suspect Interval', type: ColumnType.boolean, + }, + ]; + + chartPagedData.datasets.forEach(ds => { + temperatureColumns.push(addColumnLabels(ds.name, ds.columnLabel)); + }); + return ( + <> + + + {lineChart(chartPagedData)} + + + + ); + } + + /** + * This function gets the performance values for Temperature according on the chartjs dataset structure + * which is to be sent to the chart. + */ + + private getChartDataValues = (rows: TemperatureDataType[]): IDataSetsObject => { + const data_rows = [...rows]; + sortDataByTimeStamp(data_rows); + + const datasets: IDataSet[] = [{ + name: 'rfTempMin', + label: 'rf-temp-min', + borderColor: '#0e17f3de', + bezierCurve: false, + lineTension: 0, + fill: false, + data: [], + columnLabel: 'Rf Temp Min[deg C]', + }, { + name: 'rfTempAvg', + label: 'rf-temp-avg', + borderColor: '#08edb6de', + bezierCurve: false, + lineTension: 0, + fill: false, + data: [], + columnLabel: 'Rf Temp Avg[deg C]', + }, { + name: 'rfTempMax', + label: 'rf-temp-max', + borderColor: '#b308edde', + bezierCurve: false, + lineTension: 0, + fill: false, + data: [], + columnLabel: 'Rf Temp Max[deg C]', + }]; + + data_rows.forEach(row => { + row.rfTempMin = row.performanceData.rfTempMin; + row.rfTempAvg = row.performanceData.rfTempAvg; + row.rfTempMax = row.performanceData.rfTempMax; + datasets.forEach(ds => { + ds.data.push({ + x: row['timeStamp' as keyof TemperatureDataType] as string, + y: row.performanceData[ds.name as keyof TemperatureDatabaseDataType] as string, + }); + }); + }); + return { + datasets: datasets, + }; + }; +} + +const Temperature = withRouter(connect(mapProps, mapDisp)(TemperatureComponent)); +export default Temperature; diff --git a/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/components/toggleContainer.tsx b/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/components/toggleContainer.tsx new file mode 100644 index 000000000..e883aef7f --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/components/toggleContainer.tsx @@ -0,0 +1,102 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2020 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + * ============LICENSE_END========================================================================== + */ + +import * as React from 'react'; + +import BarChartIcon from '@mui/icons-material/BarChart'; +import FilterListIcon from '@mui/icons-material/FilterList'; +import TableChartIcon from '@mui/icons-material/TableChart'; +import ToggleButton from '@mui/material/ToggleButton'; +import ToggleButtonGroup from '@mui/material/ToggleButtonGroup'; +import Tooltip from '@mui/material/Tooltip'; +import makeStyles from '@mui/styles/makeStyles'; + +import ChartFilter from './chartFilter'; + +const styles = makeStyles({ + toggleButtonContainer: { + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + padding: '10px', + }, + subViewGroup: { + padding: '10px', + }, + filterGroup: { + marginLeft: '10px', + }, +}); + +type toggleProps = { selectedValue: string; onChange(value: string): void; showFilter: boolean; onToggleFilterButton(): void; onFilterChanged: (property: string, filterTerm: string) => void; existingFilter: any }; + +const ToggleContainer: React.FunctionComponent = (props) => { + + const classes = styles(); + + const handleChange = (event: React.MouseEvent, newView: string) => { + if (newView !== null) { + props.onChange(newView); + } + }; + + const handleFilterChange = (_event: React.MouseEvent) => { + props.onToggleFilterButton(); + }; + + const children = React.Children.toArray(props.children); + + //hide filter if visible + table + //put current name into state, let container handle stuff itelf, register for togglestate, get right via set name + + return ( + <> +
+ + + + + + + + + + + + + + + + + + + + + + +
+ { + props.selectedValue === 'chart' && + + + } + {props.selectedValue === 'chart' ? children[0] : props.selectedValue === 'table' && children[1]} + ); +}; + +export default ToggleContainer; \ No newline at end of file diff --git a/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/components/transmissionPower.tsx b/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/components/transmissionPower.tsx new file mode 100644 index 000000000..db9a7c077 --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/components/transmissionPower.tsx @@ -0,0 +1,158 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + * ============LICENSE_END========================================================================== + */ +import React from 'react'; +import { RouteComponentProps, withRouter } from 'react-router-dom'; + +import { ColumnModel, ColumnType, MaterialTable, MaterialTableCtorType } from '../../../../framework/src/components/material-table'; +import { connect, Connect, IDispatcher } from '../../../../framework/src/flux/connect'; +import { IApplicationStoreState } from '../../../../framework/src/store/applicationStore'; + +import { SetFilterVisibility, SetSubViewAction } from '../actions/toggleActions'; +import { createTransmissionPowerActions, createTransmissionPowerProperties } from '../handlers/transmissionPowerHandler'; +import { IDataSet, IDataSetsObject } from '../models/chartTypes'; +import { TransmissionPowerDatabaseDataType, TransmissionPowerDataType } from '../models/transmissionPowerDataType'; +import { lineChart, sortDataByTimeStamp } from '../utils/chartUtils'; +import { addColumnLabels } from '../utils/tableUtils'; +import ToggleContainer from './toggleContainer'; + +const mapProps = (state: IApplicationStoreState) => ({ + transmissionPowerProperties: createTransmissionPowerProperties(state), + currentView: state.performanceHistory.subViews.transmissionPower.subView, + isFilterVisible: state.performanceHistory.subViews.transmissionPower.isFilterVisible, + existingFilter: state.performanceHistory.transmissionPower.filter, +}); + +const mapDisp = (dispatcher: IDispatcher) => ({ + transmissionPowerActions: createTransmissionPowerActions(dispatcher.dispatch), + setSubView: (value: 'chart' | 'table') => dispatcher.dispatch(new SetSubViewAction('transmissionPower', value)), + toggleFilterButton: (value: boolean) => { dispatcher.dispatch(new SetFilterVisibility('transmissionPower', value)); }, + + +}); + +type TransmissionPowerComponentProps = RouteComponentProps & Connect & { + selectedTimePeriod: string; +}; + +const TransmissionPowerTable = MaterialTable as MaterialTableCtorType; + +/** + * The Component which gets the transmission power data from the database based on the selected time period. + */ +class TransmissionPowerComponent extends React.Component { + onToggleFilterButton = () => { + this.props.toggleFilterButton(!this.props.isFilterVisible); + }; + + onChange = (value: 'chart' | 'table') => { + this.props.setSubView(value); + }; + + onFilterChanged = (property: string, filterTerm: string) => { + this.props.transmissionPowerActions.onFilterChanged(property, filterTerm); + if (!this.props.transmissionPowerProperties.showFilter) + this.props.transmissionPowerActions.onToggleFilter(false); + }; + + render(): JSX.Element { + const properties = this.props.transmissionPowerProperties; + const actions = this.props.transmissionPowerActions; + + const chartPagedData = this.getChartDataValues(properties.rows); + + const transmissionColumns: ColumnModel[] = [ + { property: 'radioSignalId', title: 'Radio signal', type: ColumnType.text }, + { property: 'scannerId', title: 'Scanner ID', type: ColumnType.text }, + { property: 'timeStamp', title: 'End Time', type: ColumnType.text }, + { + property: 'suspectIntervalFlag', title: 'Suspect Interval', type: ColumnType.boolean, + }, + ]; + + chartPagedData.datasets.forEach(ds => { + transmissionColumns.push(addColumnLabels(ds.name, ds.columnLabel)); + }); + + return ( + <> + + {lineChart(chartPagedData)} + + + + ); + } + + /** + * This function gets the performance values for TransmissionPower according on the chartjs dataset structure + * which is to be sent to the chart. + */ + + private getChartDataValues = (rows: TransmissionPowerDataType[]): IDataSetsObject => { + const data_rows = [...rows]; + sortDataByTimeStamp(data_rows); + + const datasets: IDataSet[] = [{ + name: 'txLevelMin', + label: 'tx-level-min', + borderColor: '#0e17f3de', + bezierCurve: false, + lineTension: 0, + fill: false, + data: [], + columnLabel: 'Tx min', + }, { + name: 'txLevelAvg', + label: 'tx-level-avg', + borderColor: '#08edb6de', + bezierCurve: false, + lineTension: 0, + fill: false, + data: [], + columnLabel: 'Tx avg', + }, { + name: 'txLevelMax', + label: 'tx-level-max', + borderColor: '#b308edde', + bezierCurve: false, + lineTension: 0, + fill: false, + data: [], + columnLabel: 'Tx max', + }]; + + data_rows.forEach(row => { + row.txLevelMin = row.performanceData.txLevelMin; + row.txLevelAvg = row.performanceData.txLevelAvg; + row.txLevelMax = row.performanceData.txLevelMax; + datasets.forEach(ds => { + ds.data.push({ + x: row['timeStamp' as keyof TransmissionPowerDataType] as string, + y: row.performanceData[ds.name as keyof TransmissionPowerDatabaseDataType] as string, + }); + }); + }); + return { + datasets: datasets, + }; + }; +} + +const TransmissionPower = withRouter(connect(mapProps, mapDisp)(TransmissionPowerComponent)); +export default TransmissionPower; diff --git a/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/handlers/adaptiveModulationHandler.ts b/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/handlers/adaptiveModulationHandler.ts new file mode 100644 index 000000000..9baf54529 --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/handlers/adaptiveModulationHandler.ts @@ -0,0 +1,37 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + * ============LICENSE_END========================================================================== + */ +import { createExternal, IExternalTableState } from '../../../../framework/src/components/material-table/utilities'; +import { createSearchDataHandler } from '../../../../framework/src/utilities/elasticSearch'; + +import { AdaptiveModulationDataType } from '../models/adaptiveModulationDataType'; +import { getFilter } from '../utils/tableUtils'; + +export interface IAdaptiveModulationState extends IExternalTableState { } + +/** + * Creates elastic search material data fetch handler for Adaptive modulation from historicalperformance database. + */ +const adaptiveModulationSearchHandler = createSearchDataHandler(getFilter, false, null); +export const { + actionHandler: adaptiveModulationActionHandler, + createActions: createAdaptiveModulationActions, + createProperties: createAdaptiveModulationProperties, + createPreActions: createAdaptiveModulationPreActions, + reloadAction: adaptiveModulationReloadAction, +} = createExternal(adaptiveModulationSearchHandler, appState => appState.performanceHistory.adaptiveModulation); + diff --git a/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/handlers/availableLtpsActionHandler.ts b/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/handlers/availableLtpsActionHandler.ts new file mode 100644 index 000000000..f943ef44c --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/handlers/availableLtpsActionHandler.ts @@ -0,0 +1,99 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + * ============LICENSE_END========================================================================== + */ +import { IActionHandler } from '../../../../framework/src/flux/action'; + +import { + AllAvailableLtpsLoadedAction, + LoadAllAvailableLtpsAction, + SetInitialLoadedAction, + NoLtpsFoundAction, + ResetLtpsAction, +} from '../actions/ltpAction'; + +import { LtpIds } from '../models/availableLtps'; + +export interface IAvailableLtpsState { + distinctLtps: LtpIds[]; + busy: boolean; + loadedOnce: boolean; + error: string | undefined; +} + +const ltpListStateInit: IAvailableLtpsState = { + distinctLtps: [], + busy: false, + loadedOnce: false, + error: undefined, +}; + +export const availableLtpsActionHandler: IActionHandler = (state = ltpListStateInit, action) => { + if (action instanceof LoadAllAvailableLtpsAction) { + + state = { + ...state, + busy: true, + }; + + } else if (action instanceof AllAvailableLtpsLoadedAction) { + if (!action.error && action.availableLtps) { + state = { + ...state, + distinctLtps: action.availableLtps, + busy: false, + error: undefined, + loadedOnce: true, + }; + } else if (action.error) { + state = { + ...state, + busy: false, + loadedOnce: true, + error: action.error, + }; + } + } else if (action instanceof SetInitialLoadedAction) { + + state = { + ...state, + loadedOnce: action.initialLoaded, + }; + } else if (action instanceof NoLtpsFoundAction) { + state = { + ...state, + busy: false, + error: undefined, + loadedOnce: true, + distinctLtps: [], + }; + } else if (action instanceof ResetLtpsAction) { + state = { + ...state, + busy: false, + error: undefined, + loadedOnce: false, + distinctLtps: [], + }; + } else { + state = { + ...state, + busy: false, + }; + } + + return state; +}; \ No newline at end of file diff --git a/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/handlers/crossPolarDiscriminationHandler.ts b/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/handlers/crossPolarDiscriminationHandler.ts new file mode 100644 index 000000000..96cabfa83 --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/handlers/crossPolarDiscriminationHandler.ts @@ -0,0 +1,38 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + * ============LICENSE_END========================================================================== + */ +import { createExternal, IExternalTableState } from '../../../../framework/src/components/material-table/utilities'; +import { createSearchDataHandler } from '../../../../framework/src/utilities/elasticSearch'; + +import { CrossPolarDiscriminationDataType } from '../models/crossPolarDiscriminationDataType'; +import { getFilter } from '../utils/tableUtils'; + +export interface ICrossPolarDiscriminationState extends IExternalTableState { } + +/** + * Creates elastic search material data fetch handler for CPD from historicalperformance database. + */ +const crossPolarDiscriminationSearchHandler = createSearchDataHandler(getFilter, false, null); + +export const { + actionHandler: crossPolarDiscriminationActionHandler, + createActions: createCrossPolarDiscriminationActions, + createProperties: createCrossPolarDiscriminationProperties, + createPreActions: createCrossPolarDiscriminationPreActions, + reloadAction: crossPolarDiscriminationReloadAction, +} = createExternal(crossPolarDiscriminationSearchHandler, appState => appState.performanceHistory.crossPolarDiscrimination); + diff --git a/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/handlers/deviceListActionHandler.ts b/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/handlers/deviceListActionHandler.ts new file mode 100644 index 000000000..11e380ad5 --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/handlers/deviceListActionHandler.ts @@ -0,0 +1,56 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + * ============LICENSE_END========================================================================== + */ +import { IActionHandler } from '../../../../framework/src/flux/action'; + +import { AllDeviceListLoadedAction, LoadAllDeviceListAction } from '../actions/deviceListActions'; +import { DeviceListType } from '../models/deviceListType'; + +export interface IDeviceListState { + deviceList: DeviceListType[]; + busy: boolean; +} + +const deviceListStateInit: IDeviceListState = { + deviceList: [], + busy: false, +}; + +export const deviceListActionHandler: IActionHandler = (state = deviceListStateInit, action) => { + if (action instanceof LoadAllDeviceListAction) { + + state = { + ...state, + busy: true, + }; + + } else if (action instanceof AllDeviceListLoadedAction) { + if (!action.error && action.deviceList) { + state = { + ...state, + deviceList: action.deviceList, + busy: false, + }; + } else { + state = { + ...state, + busy: false, + }; + } + } + return state; +}; \ No newline at end of file diff --git a/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/handlers/performanceDataHandler.ts b/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/handlers/performanceDataHandler.ts new file mode 100644 index 000000000..664c7cd6c --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/handlers/performanceDataHandler.ts @@ -0,0 +1,39 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + * ============LICENSE_END========================================================================== + */ + +import { createExternal, IExternalTableState } from '../../../../framework/src/components/material-table/utilities'; +import { createSearchDataHandler } from '../../../../framework/src/utilities/elasticSearch'; + +import { PerformanceDataType } from '../models/performanceDataType'; +import { getFilter } from '../utils/tableUtils'; + +export interface IPerformanceDataState extends IExternalTableState { } + +/** +* Creates elastic search material data fetch handler for performance data from historicalperformance15min database. +*/ +const performanceDataSearchHandler = createSearchDataHandler(getFilter, false, null); + +export const { + actionHandler: performanceDataActionHandler, + createActions: createPerformanceDataActions, + createProperties: createPerformanceDataProperties, + createPreActions: createPerformanceDataPreActions, + reloadAction: performanceDataReloadAction, +} = createExternal(performanceDataSearchHandler, appState => appState.performanceHistory.performanceData); + diff --git a/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/handlers/performanceHistoryRootHandler.ts b/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/handlers/performanceHistoryRootHandler.ts new file mode 100644 index 000000000..c0cee46ba --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/handlers/performanceHistoryRootHandler.ts @@ -0,0 +1,181 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + * ============LICENSE_END========================================================================== + */ +// main state handler + +import { IActionHandler } from '../../../../framework/src/flux/action'; +import { combineActionHandler } from '../../../../framework/src/flux/middleware'; +// ** do not remove ** +// eslint-disable-next-line @typescript-eslint/no-unused-vars +import { IApplicationStoreState } from '../../../../framework/src/store/applicationStore'; + +import { IConnectAppStoreState } from '../../../connectApp/src/handlers/connectAppRootHandler'; +import { UpdateMountId } from '../actions/deviceListActions'; +import { SetPanelAction } from '../actions/panelChangeActions'; +import { ReloadAction } from '../actions/reloadAction'; +import { TimeChangeAction } from '../actions/timeChangeAction'; +import { ResetAllSubViewsAction, SetFilterVisibility, SetSubViewAction } from '../actions/toggleActions'; +import { PmDataInterval } from '../models/performanceDataType'; +import { currentViewType, SubTabType } from '../models/toggleDataType'; +import { adaptiveModulationActionHandler, IAdaptiveModulationState } from './adaptiveModulationHandler'; +import { availableLtpsActionHandler, IAvailableLtpsState } from './availableLtpsActionHandler'; +import { crossPolarDiscriminationActionHandler, ICrossPolarDiscriminationState } from './crossPolarDiscriminationHandler'; +import { deviceListActionHandler, IDeviceListState } from './deviceListActionHandler'; +import { IPerformanceDataState, performanceDataActionHandler } from './performanceDataHandler'; +import { IReceiveLevelState, receiveLevelActionHandler } from './receiveLevelHandler'; +import { ISignalToInterferenceState, signalToInterferenceActionHandler } from './signalToInterferenceHandler'; +import { ITemperatureState, temperatureActionHandler } from './temperatureHandler'; +import { ITransmissionPowerState, transmissionPowerActionHandler } from './transmissionPowerHandler'; + +export interface IPerformanceHistoryStoreState { + nodeId: string; + networkElements: IDeviceListState; + ltps: IAvailableLtpsState; + performanceData: IPerformanceDataState; + receiveLevel: IReceiveLevelState; + transmissionPower: ITransmissionPowerState; + adaptiveModulation: IAdaptiveModulationState; + temperature: ITemperatureState; + signalToInterference: ISignalToInterferenceState; + crossPolarDiscrimination: ICrossPolarDiscriminationState; + currentOpenPanel: string | null; + pmDataIntervalType: PmDataInterval; + subViews: toggleViewDataType; + isReloadSchedueled: boolean; +} + +const mountIdHandler: IActionHandler = (state = '', action) => { + if (action instanceof UpdateMountId) { + state = ''; + if (action.nodeId) { + state = action.nodeId; + } + } + return state; +}; + +const reloadHandler: IActionHandler = (state = false, action) => { + + if (action instanceof ReloadAction) { + state = action.show; + } + return state; +}; + + +const currentOpenPanelHandler: IActionHandler = (state = null, action) => { + if (action instanceof SetPanelAction) { + state = action.panelId; + } + return state; +}; + +const currentPMDataIntervalHandler: IActionHandler = (state = PmDataInterval.pmInterval15Min, action) => { + if (action instanceof TimeChangeAction) { + state = action.time; + } + return state; +}; + +type filterableSubview = { subView: SubTabType; isFilterVisible: boolean }; +type toggleViewDataType = { + currentSubView: currentViewType; + performanceData: filterableSubview; + receiveLevel: filterableSubview; + transmissionPower: filterableSubview; + adaptiveModulation: filterableSubview; + temperatur: filterableSubview; + SINR: filterableSubview; + CPD: filterableSubview; +}; + + +const toogleViewDataHandler: IActionHandler = ( + state = { + currentSubView: 'performanceData', + performanceData: { subView: 'chart', isFilterVisible: true }, + receiveLevel: { subView: 'chart', isFilterVisible: true }, + adaptiveModulation: { subView: 'chart', isFilterVisible: true }, + transmissionPower: { subView: 'chart', isFilterVisible: true }, + temperatur: { subView: 'chart', isFilterVisible: true }, + SINR: { subView: 'chart', isFilterVisible: true }, + CPD: { subView: 'chart', isFilterVisible: true }, + }, action) => { + + if (action instanceof SetSubViewAction) { + switch (action.currentView) { + case 'performanceData': state = { ...state, performanceData: { ...state.performanceData, subView: action.selectedTab } }; break; + case 'adaptiveModulation': state = { ...state, adaptiveModulation: { ...state.adaptiveModulation, subView: action.selectedTab } }; break; + case 'receiveLevel': state = { ...state, receiveLevel: { ...state.receiveLevel, subView: action.selectedTab } }; break; + case 'transmissionPower': state = { ...state, transmissionPower: { ...state.transmissionPower, subView: action.selectedTab } }; break; + case 'Temp': state = { ...state, temperatur: { ...state.temperatur, subView: action.selectedTab } }; break; + case 'SINR': state = { ...state, SINR: { ...state.SINR, subView: action.selectedTab } }; break; + case 'CPD': state = { ...state, CPD: { ...state.CPD, subView: action.selectedTab } }; break; + } + } else if (action instanceof SetFilterVisibility) { + switch (action.currentView) { + case 'performanceData': state = { + ...state, performanceData: { ...state.performanceData, isFilterVisible: action.isVisible }, + }; break; + case 'adaptiveModulation': state = { ...state, adaptiveModulation: { ...state.performanceData, isFilterVisible: action.isVisible } }; break; + case 'receiveLevel': state = { ...state, receiveLevel: { ...state.receiveLevel, isFilterVisible: action.isVisible } }; break; + case 'transmissionPower': state = { ...state, transmissionPower: { ...state.transmissionPower, isFilterVisible: action.isVisible } }; break; + case 'Temp': state = { ...state, temperatur: { ...state.temperatur, isFilterVisible: action.isVisible } }; break; + case 'SINR': state = { ...state, SINR: { ...state.SINR, isFilterVisible: action.isVisible } }; break; + case 'CPD': state = { ...state, CPD: { ...state.CPD, isFilterVisible: action.isVisible } }; break; + } + } else if (action instanceof ResetAllSubViewsAction) { + state = { + ...state, performanceData: { ...state.performanceData, subView: 'chart' }, + adaptiveModulation: { ...state.adaptiveModulation, subView: 'chart' }, + receiveLevel: { ...state.receiveLevel, subView: 'chart' }, + transmissionPower: { ...state.transmissionPower, subView: 'chart' }, + temperatur: { ...state.temperatur, subView: 'chart' }, + SINR: { ...state.SINR, subView: 'chart' }, + CPD: { ...state.CPD, subView: 'chart' }, + }; + } + return state; +}; + +declare module '../../../../framework/src/store/applicationStore' { + interface IApplicationStoreState { + performanceHistory: IPerformanceHistoryStoreState; + connect: IConnectAppStoreState; + } +} + +const actionHandlers = { + nodeId: mountIdHandler, + networkElements: deviceListActionHandler, + ltps: availableLtpsActionHandler, + performanceData: performanceDataActionHandler, + receiveLevel: receiveLevelActionHandler, + transmissionPower: transmissionPowerActionHandler, + adaptiveModulation: adaptiveModulationActionHandler, + temperature: temperatureActionHandler, + signalToInterference: signalToInterferenceActionHandler, + crossPolarDiscrimination: crossPolarDiscriminationActionHandler, + currentOpenPanel: currentOpenPanelHandler, + pmDataIntervalType: currentPMDataIntervalHandler, + subViews: toogleViewDataHandler, + isReloadSchedueled: reloadHandler, +}; + +const performanceHistoryRootHandler = combineActionHandler(actionHandlers); +export default performanceHistoryRootHandler; + diff --git a/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/handlers/receiveLevelHandler.ts b/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/handlers/receiveLevelHandler.ts new file mode 100644 index 000000000..78626ebec --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/handlers/receiveLevelHandler.ts @@ -0,0 +1,38 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + * ============LICENSE_END========================================================================== + */ +import { createExternal, IExternalTableState } from '../../../../framework/src/components/material-table/utilities'; +import { createSearchDataHandler } from '../../../../framework/src/utilities/elasticSearch'; + +import { ReceiveLevelDataType } from '../models/receiveLevelDataType'; +import { getFilter } from '../utils/tableUtils'; + +export interface IReceiveLevelState extends IExternalTableState { } + +/** + * Creates elastic search material data fetch handler for receiveLevel from historicalperformance database. + */ +const receiveLevelSearchHandler = createSearchDataHandler(getFilter, false, null); + +export const { + actionHandler: receiveLevelActionHandler, + createActions: createReceiveLevelActions, + createProperties: createReceiveLevelProperties, + createPreActions: createReceiveLevelPreActions, + reloadAction: receiveLevelReloadAction, +} = createExternal(receiveLevelSearchHandler, appState => appState.performanceHistory.receiveLevel); + diff --git a/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/handlers/signalToInterferenceHandler.ts b/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/handlers/signalToInterferenceHandler.ts new file mode 100644 index 000000000..ab6efabae --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/handlers/signalToInterferenceHandler.ts @@ -0,0 +1,38 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + * ============LICENSE_END========================================================================== + */ +import { createExternal, IExternalTableState } from '../../../../framework/src/components/material-table/utilities'; +import { createSearchDataHandler } from '../../../../framework/src/utilities/elasticSearch'; + +import { SignalToInterferenceDataType } from '../models/signalToInteferenceDataType'; +import { getFilter } from '../utils/tableUtils'; + +export interface ISignalToInterferenceState extends IExternalTableState { } + +/** + * Creates elastic search material data fetch handler for SINR from historicalperformance database. + */ +const signalToInterferenceSearchHandler = createSearchDataHandler(getFilter, false, null); + +export const { + actionHandler: signalToInterferenceActionHandler, + createActions: createSignalToInterferenceActions, + createProperties: createSignalToInterferenceProperties, + createPreActions: createSignalToInterferencePreActions, + reloadAction: signalToInterferenceReloadAction, +} = createExternal(signalToInterferenceSearchHandler, appState => appState.performanceHistory.signalToInterference); + diff --git a/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/handlers/temperatureHandler.ts b/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/handlers/temperatureHandler.ts new file mode 100644 index 000000000..02bf69be7 --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/handlers/temperatureHandler.ts @@ -0,0 +1,38 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + * ============LICENSE_END========================================================================== + */ +import { createExternal, IExternalTableState } from '../../../../framework/src/components/material-table/utilities'; +import { createSearchDataHandler } from '../../../../framework/src/utilities/elasticSearch'; + +import { TemperatureDataType } from '../models/temperatureDataType'; +import { getFilter } from '../utils/tableUtils'; + +export interface ITemperatureState extends IExternalTableState { } + +/** + * Creates elastic search material data fetch handler for Temperature from historicalperformance database. + */ +const temperatureSearchHandler = createSearchDataHandler(getFilter, false, null); + +export const { + actionHandler: temperatureActionHandler, + createActions: createTemperatureActions, + createProperties: createTemperatureProperties, + createPreActions: createTemperaturePreActions, + reloadAction: temperatureReloadAction, +} = createExternal(temperatureSearchHandler, appState => appState.performanceHistory.temperature); + diff --git a/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/handlers/transmissionPowerHandler.ts b/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/handlers/transmissionPowerHandler.ts new file mode 100644 index 000000000..9cf70dc87 --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/handlers/transmissionPowerHandler.ts @@ -0,0 +1,38 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + * ============LICENSE_END========================================================================== + */ +import { createExternal, IExternalTableState } from '../../../../framework/src/components/material-table/utilities'; +import { createSearchDataHandler } from '../../../../framework/src/utilities/elasticSearch'; + +import { TransmissionPowerDataType } from '../models/transmissionPowerDataType'; +import { getFilter } from '../utils/tableUtils'; + +export interface ITransmissionPowerState extends IExternalTableState { } + +/** + * Creates elastic search material data fetch handler for Transmission power from historicalperformance database. + */ +const transmissionPowerSearchHandler = createSearchDataHandler(getFilter, false, null); + +export const { + actionHandler: transmissionPowerActionHandler, + createActions: createTransmissionPowerActions, + createProperties: createTransmissionPowerProperties, + createPreActions: createTransmissionPowerPreActions, + reloadAction: transmissionPowerReloadAction, +} = createExternal(transmissionPowerSearchHandler, appState => appState.performanceHistory.transmissionPower); + diff --git a/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/index.html b/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/index.html new file mode 100644 index 000000000..8cb775be2 --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/index.html @@ -0,0 +1,26 @@ + + + + + + + + + PM History Application + + + +
+ + + + + + \ No newline at end of file diff --git a/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/models/adaptiveModulationDataType.ts b/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/models/adaptiveModulationDataType.ts new file mode 100644 index 000000000..adb0bcd6f --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/models/adaptiveModulationDataType.ts @@ -0,0 +1,109 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * Licensed under the Apache License, Version 2.0 (the License); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an AS IS BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + * ============LICENSE_END========================================================================== + */ +export { HitEntry, Result } from '../../../../framework/src/models'; + +/** + * Represents Adaptive Modulation data fields of the performance history table. + */ +export type AdaptiveModulationDatabaseDataType = { + _id: string ; + time2StatesS: number; + time2States: number; + time2StatesL: number; + time4StatesS: number; + time4States: number; + time4StatesL: number; + time16StatesS: number; + time16States: number; + time16StatesL: number; + time32StatesS: number; + time32States: number; + time32StatesL: number; + time64StatesS: number; + time64States: number; + time64StatesL: number; + time128StatesS: number; + time128States: number; + time128StatesL: number; + time256StatesS: number; + time256States: number; + time256StatesL: number; + time512StatesS: number; + time512States: number; + time512StatesL: number; + time1024StatesS: number; + time1024States: number; + time1024StatesL: number; + time2048StatesS: number; + time2048States: number; + time2048StatesL: number; + time4096StatesS: number; + time4096States: number; + time4096StatesL: number; + time8192StatesS: number; + time8192States: number; + time8192StatesL: number; +}; + + +/** + * Internally used type to provide table and chart data + */ +export type AdaptiveModulationDataType = { + performanceData: AdaptiveModulationDatabaseDataType; + radioSignalId: string; + scannerId: string; + timeStamp: string; + suspectIntervalFlag: boolean; + time2StatesS: number; + time2States: number; + time2StatesL: number; + time4StatesS: number; + time4States: number; + time4StatesL: number; + time16StatesS: number; + time16States: number; + time16StatesL: number; + time32StatesS: number; + time32States: number; + time32StatesL: number; + time64StatesS: number; + time64States: number; + time64StatesL: number; + time128StatesS: number; + time128States: number; + time128StatesL: number; + time256StatesS: number; + time256States: number; + time256StatesL: number; + time512StatesS: number; + time512States: number; + time512StatesL: number; + time1024StatesS: number; + time1024States: number; + time1024StatesL: number; + time2048StatesS: number; + time2048States: number; + time2048StatesL: number; + time4096StatesS: number; + time4096States: number; + time4096StatesL: number; + time8192StatesS: number; + time8192States: number; + time8192StatesL: number; +} & { _id: string }; \ No newline at end of file diff --git a/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/models/availableLtps.ts b/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/models/availableLtps.ts new file mode 100644 index 000000000..60061571c --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/models/availableLtps.ts @@ -0,0 +1,21 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + * ============LICENSE_END========================================================================== + */ +export type LtpIds = { + key: string; +}; + diff --git a/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/models/chartTypes.ts b/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/models/chartTypes.ts new file mode 100644 index 000000000..969c0b399 --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/models/chartTypes.ts @@ -0,0 +1,49 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + * ============LICENSE_END========================================================================== + */ +export interface IData { + x: string; + y: string; +} + +/** + * Structure of chartjs dataset with the chart properties. + */ +export interface IDataSet { + name: string; + label: string; + lineTension: 0; + bezierCurve: boolean; + fill: boolean; + borderColor: string; + data: IData[]; + columnLabel: string; +} + +/** + * Structure of chartjs dataset which is sent to the chart. + */ +export interface IDataSetsObject { + datasets: IDataSet[]; +} + +/** + * Interface used by chart for sorting on time-stamp + */ +export interface ITimeStamp { + timeStamp: string; +} diff --git a/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/models/crossPolarDiscriminationDataType.ts b/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/models/crossPolarDiscriminationDataType.ts new file mode 100644 index 000000000..749624b9a --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/models/crossPolarDiscriminationDataType.ts @@ -0,0 +1,44 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + * ============LICENSE_END========================================================================== + */ +export { HitEntry, Result } from '../../../../framework/src/models'; + + +/** + * Represents Receive level data fields of the performance history table. + */ +export type CrossPolarDiscriminationDatabaseDataType = { + _id: string; + xpdMin: number; + xpdAvg: number; + xpdMax: number; +}; + +/** + * Internally used type to provide table and chart data + */ +export type CrossPolarDiscriminationDataType = { + performanceData: CrossPolarDiscriminationDatabaseDataType; + radioSignalId: string; + scannerId: string; + timeStamp: string; + suspectIntervalFlag: boolean; + xpdMin: number; + xpdAvg: number; + xpdMax: number; +} & { _id: string }; + diff --git a/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/models/deviceListType.ts b/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/models/deviceListType.ts new file mode 100644 index 000000000..fbe314162 --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/models/deviceListType.ts @@ -0,0 +1,25 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + * ============LICENSE_END========================================================================== + */ + +/** + * Represents all the distinct devices from the performance history data. + */ + +export type DeviceListType = { + nodeId: string; +}; diff --git a/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/models/panelId.ts b/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/models/panelId.ts new file mode 100644 index 000000000..08bf7f815 --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/models/panelId.ts @@ -0,0 +1,22 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + * ============LICENSE_END========================================================================== + */ + +/** + * Represents PanelIds for the available Expansional panels. + */ +export type PanelId = null | 'PerformanceData' | 'ReceiveLevel' | 'TransmissionPower' | 'AdaptiveModulation' | 'Temperature' | 'SINR' | 'CPD'; \ No newline at end of file diff --git a/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/models/performanceDataType.ts b/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/models/performanceDataType.ts new file mode 100644 index 000000000..f71e09de9 --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/models/performanceDataType.ts @@ -0,0 +1,53 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + * ============LICENSE_END========================================================================== + */ + +//export { HitEntry, Result } from '../../../../framework/src/models'; + +/** + * Represents performance data fields of the performance history table as used in the database + */ +export type PerformanceDatabaseDataType = { + _id: string; + es: number; + ses: number; + unavailability: number; +}; + +/** + * Internally used type to provide table and chart data + */ +export type PerformanceDataType = { + + performanceData: PerformanceDatabaseDataType; + radioSignalId: string; + scannerId: string; + timeStamp: string; + suspectIntervalFlag: boolean; + es: number; + ses: number; + unavailability: number; +} & { _id: string }; + + +/** + * Represents performance data time interval. + */ +export const enum PmDataInterval { + pmInterval15Min, + pmInterval24Hours, +} \ No newline at end of file diff --git a/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/models/receiveLevelDataType.ts b/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/models/receiveLevelDataType.ts new file mode 100644 index 000000000..2748a3d5d --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/models/receiveLevelDataType.ts @@ -0,0 +1,43 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + * ============LICENSE_END========================================================================== + */ +export { HitEntry, Result } from '../../../../framework/src/models'; + +/** + * Represents Receive level data fields of the performance history table. + */ +export type ReceiveLevelDatabaseDataType = { + _id: string; + rxLevelMin: number; + rxLevelAvg: number; + rxLevelMax: number; +}; + +/** + * Internally used type to provide table and chart data + */ +export type ReceiveLevelDataType = { + performanceData: ReceiveLevelDatabaseDataType; + radioSignalId: string; + scannerId: string; + timeStamp: string; + suspectIntervalFlag: boolean; + rxLevelMin: number; + rxLevelAvg: number; + rxLevelMax: number; +} & { _id: string }; + diff --git a/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/models/signalToInteferenceDataType.ts b/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/models/signalToInteferenceDataType.ts new file mode 100644 index 000000000..5c675feb8 --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/models/signalToInteferenceDataType.ts @@ -0,0 +1,44 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + * ============LICENSE_END========================================================================== + */ +export { HitEntry, Result } from '../../../../framework/src/models'; + + +/** + * Represents Receive level data fields of the performance history table. + */ +export type SignalToInterferenceDatabaseDataType = { + _id: string; + snirMin: number; + snirAvg: number; + snirMax: number; +}; + +/** + * Internally used type to provide table and chart data + */ +export type SignalToInterferenceDataType = { + performanceData: SignalToInterferenceDatabaseDataType; + radioSignalId: string; + scannerId: string; + timeStamp: string; + suspectIntervalFlag: boolean; + snirMin: number; + snirAvg: number; + snirMax: number; +} & { _id: string }; + diff --git a/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/models/temperatureDataType.ts b/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/models/temperatureDataType.ts new file mode 100644 index 000000000..5798d5c5d --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/models/temperatureDataType.ts @@ -0,0 +1,45 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + * ============LICENSE_END========================================================================== + */ +export { HitEntry, Result } from '../../../../framework/src/models'; + + +/** + * Represents Receive level data fields of the performance history table. + */ +export type TemperatureDatabaseDataType = { + _id: string; + rfTempMin: number; + rfTempAvg: number; + rfTempMax: number; +}; + +/** + * Internally used type to provide table and chart data + */ +export type TemperatureDataType = { + performanceData: TemperatureDatabaseDataType; + radioSignalId: string; + scannerId: string; + timeStamp: string; + suspectIntervalFlag: boolean; + rfTempMin: number; + rfTempAvg: number; + rfTempMax: number; +} & { _id: string }; + + diff --git a/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/models/toggleDataType.ts b/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/models/toggleDataType.ts new file mode 100644 index 000000000..0e71c9474 --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/models/toggleDataType.ts @@ -0,0 +1,25 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2020 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + * ============LICENSE_END========================================================================== + */ + +/** + * Specifies possible sub views + */ +export type SubTabType = 'chart' | 'table'; + +export type currentViewType = 'performanceData' | 'receiveLevel' | 'transmissionPower' | 'adaptiveModulation' | 'Temp' | 'SINR' | 'CPD'; + diff --git a/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/models/topologyNetconf.ts b/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/models/topologyNetconf.ts new file mode 100644 index 000000000..f52af9784 --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/models/topologyNetconf.ts @@ -0,0 +1,26 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + * ============LICENSE_END========================================================================== + */ + +export interface TopologyNode { + 'node-id': string; +} + +export interface Topology { + 'topology-id': string; + node: TopologyNode[]; +} diff --git a/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/models/transmissionPowerDataType.ts b/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/models/transmissionPowerDataType.ts new file mode 100644 index 000000000..62c00bfe4 --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/models/transmissionPowerDataType.ts @@ -0,0 +1,44 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + * ============LICENSE_END========================================================================== + */ +export { HitEntry, Result } from '../../../../framework/src/models'; + + +/** + * Represents Receive level data fields of the performance history table. + */ +export type TransmissionPowerDatabaseDataType = { + _id: string; + txLevelMin: number; + txLevelAvg: number; + txLevelMax: number; +}; + +/** + * Internally used type to provide table and chart data + */ +export type TransmissionPowerDataType = { + performanceData: TransmissionPowerDatabaseDataType; + radioSignalId: string; + scannerId: string; + timeStamp: string; + suspectIntervalFlag: boolean; + txLevelMin: number; + txLevelAvg: number; + txLevelMax: number; +} & { _id: string }; + diff --git a/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/pluginPerformance.tsx b/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/pluginPerformance.tsx new file mode 100644 index 000000000..ef939fdba --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/pluginPerformance.tsx @@ -0,0 +1,147 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + * ============LICENSE_END========================================================================== + */ + +import React from 'react'; +import { Redirect, Route, RouteComponentProps, Switch, withRouter } from 'react-router-dom'; + +import { connect, Connect, IDispatcher } from '../../../framework/src/flux/connect'; +import applicationManager from '../../../framework/src/services/applicationManager'; +// eslint-disable-next-line @typescript-eslint/no-unused-vars +import { IApplicationStoreState } from '../../../framework/src/store/applicationStore'; +import { ApplicationStore } from '../../../framework/src/store/applicationStore'; + +import { updateMountIdActionCreator } from './actions/deviceListActions'; +import { ResetLtpsAction } from './actions/ltpAction'; +import { ReloadAction } from './actions/reloadAction'; +import { ResetAllSubViewsAction } from './actions/toggleActions'; +import performanceHistoryRootHandler from './handlers/performanceHistoryRootHandler'; +import { PmDataInterval } from './models/performanceDataType'; +import PerformanceHistoryApplication from './views/performanceHistoryApplication'; + +const appIcon = require('./assets/icons/performanceHistoryAppIcon.svg'); // select app icon + +let api: { + readonly applicationStore: ApplicationStore | null; + readonly applicationStoreInitialized: Promise; +}; + +const mapProps = () => ({ +}); + +const mapDisp = (dispatcher: IDispatcher) => ({ + updateMountId: (mountId: string) => dispatcher.dispatch(updateMountIdActionCreator(mountId)), + resetLtps: () => dispatcher.dispatch(new ResetLtpsAction()), + resetSubViews: () => dispatcher.dispatch(new ResetAllSubViewsAction()), + setScheduleReload: (show: boolean) => dispatcher.dispatch(new ReloadAction(show)), +}); + +let currentMountId: string | null = null; +let lastUrl: string = '/performanceHistory'; +const PerformanceHistoryApplicationRouteAdapter = connect(mapProps, mapDisp)((props: RouteComponentProps<{ mountId?: string }> & Connect) => { + let mountId: string = ''; + + const getMountId = (last_url: string) => { + let index = last_url.lastIndexOf('performanceHistory/'); + if (index >= 0) { + mountId = last_url.substring(index + 19); + } else { + mountId = ''; + } + + return mountId; + }; + + const scheduleReload = (current_mount_id: string) => { + props.updateMountId(current_mount_id); + props.resetLtps(); + props.resetSubViews(); + props.setScheduleReload(true); + }; + + // called when component finished mounting + React.useEffect(() => { + + lastUrl = props.location.pathname; + mountId = getMountId(lastUrl); + + if (currentMountId !== mountId) { // new element is loaded + currentMountId = mountId; + scheduleReload(currentMountId); + } else + if (currentMountId !== '') { // same element is loaded again + scheduleReload(currentMountId); + } + }, []); + + // called when component gets updated + React.useEffect(() => { + + lastUrl = props.location.pathname; + mountId = getMountId(lastUrl); + + if (currentMountId !== mountId) { + currentMountId = mountId; + scheduleReload(currentMountId); + } + + }); + + return ( + + ); +}); + +const PerformanceHistoryRouterApp = withRouter((props: RouteComponentProps) => { + props.history.action = 'POP'; + return ( + + + + + + ); +}); + +export function register() { + api = applicationManager.registerApplication({ + name: 'performanceHistory', + icon: appIcon, + rootComponent: PerformanceHistoryRouterApp, + rootActionHandler: performanceHistoryRootHandler, + menuEntry: 'Performance', + }); +} + +export function setPmDataInterval(pmDataInterval: PmDataInterval): boolean { + let reload: boolean = true; + if (api && api.applicationStore) { + if (api.applicationStore.state.performanceHistory.pmDataIntervalType !== pmDataInterval) { + reload = true; + } + api.applicationStore.state.performanceHistory.pmDataIntervalType = pmDataInterval; + } + return reload; +} + + +export function getPmDataInterval(): PmDataInterval { + let result = api && api.applicationStore + ? api.applicationStore.state.performanceHistory.pmDataIntervalType + : PmDataInterval.pmInterval15Min; + return result ? result : PmDataInterval.pmInterval15Min; +} diff --git a/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/services/performanceHistoryService.ts b/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/services/performanceHistoryService.ts new file mode 100644 index 000000000..ef013f1cb --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/services/performanceHistoryService.ts @@ -0,0 +1,107 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + * ============LICENSE_END========================================================================== + */ +import { Result } from '../../../../framework/src/models/elasticSearch'; +import { requestRest } from '../../../../framework/src/services/restService'; + +import { convertPropertyNames, replaceUpperCase } from '../../../../framework/src/utilities/yangHelper'; +import { LtpIds } from '../models/availableLtps'; +import { DeviceListType } from '../models/deviceListType'; + +/** + * Represents a web api accessor service for Network elements actions. + */ +class PerformanceService { + + /** + * Get distinct ltps based on the selected network element and time period from the historicalperformance15min database table. + */ + public async getDistinctLtpsFromDatabase(networkElement: string, selectedTimePeriod: string): Promise { + let path; + const query = { + 'filter': [{ + 'property': 'node-name', + 'filtervalue': networkElement, + }], + 'sortorder': [], + 'pagination': { + 'size': 20, + 'page': 1, + }, + }; + + + if (selectedTimePeriod === '15min') { + path = '/rests/operations/data-provider:read-pmdata-15m-ltp-list'; + } else { + path = '/rests/operations/data-provider:read-pmdata-24h-ltp-list'; + } + + const result = await requestRest>(path, { method: 'POST', body: JSON.stringify(convertPropertyNames({ input: query }, replaceUpperCase)) }); + return result && result['data-provider:output'] && result['data-provider:output'].data && result['data-provider:output'].data.map(ne => ({ key: ne })) || null; + } + + + + /** + * Gets all devices from the performanceHistory 15min backend. + */ + public async getDeviceListfromPerf15minHistory(): Promise<(DeviceListType)[] | null> { + const path = '/rests/operations/data-provider:read-pmdata-15m-device-list'; + const query = { + 'data-provider:input': { + 'filter': [], + 'sortorder': [], + 'pagination': { + 'size': 20, + 'page': 1, + }, + }, + }; + + const result = await requestRest>(path, { method: 'POST', body: JSON.stringify(query) }); + return result && result['data-provider:output'] && result['data-provider:output'].data && result['data-provider:output'].data.map(ne => ({ + nodeId: ne, + })) || null; + } + + /** + * Gets all devices from the performanceHistory 24h backend. + */ + public async getDeviceListfromPerf24hHistory(): Promise<(DeviceListType)[] | null> { + const path = '/rests/operations/data-provider:read-pmdata-24h-device-list'; + const query = { + 'data-provider:input': { + 'filter': [], + 'sortorder': [], + 'pagination': { + 'size': 20, + 'page': 1, + }, + }, + }; + + const result = await requestRest>(path, { method: 'POST', body: JSON.stringify(query) }); + return result && result['data-provider:output'] && result['data-provider:output'].data && result['data-provider:output'].data.map(ne => ({ + nodeId: ne, + })) || null; + } +} + + +export const PerformanceHistoryService = new PerformanceService(); +export default PerformanceHistoryService; diff --git a/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/utils/chartUtils.tsx b/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/utils/chartUtils.tsx new file mode 100644 index 000000000..38abb3e79 --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/utils/chartUtils.tsx @@ -0,0 +1,77 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + * ============LICENSE_END========================================================================== + */ +import React from 'react'; +import moment from 'moment'; +import { Line } from 'react-chartjs-2'; + +import { IDataSetsObject } from '../models/chartTypes'; +import { ITimeStamp } from '../models/chartTypes'; + +const style: React.CSSProperties = { + height: '80%', +}; + +export const lineChart = (chartPagedData: IDataSetsObject) => { + return ( +
+ +
+ ); +}; + +export const sortDataByTimeStamp = (_rows: T[]): T[] => { + return (_rows.sort((a, b) => { + const result = Date.parse(a.timeStamp) - Date.parse(b.timeStamp); + return isNaN(result) ? 0 : result; + })); +}; \ No newline at end of file diff --git a/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/utils/tableUtils.ts b/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/utils/tableUtils.ts new file mode 100644 index 000000000..37fe962dc --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/utils/tableUtils.ts @@ -0,0 +1,36 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + * ============LICENSE_END========================================================================== + */ +import { ColumnModel, ColumnType } from '../../../../framework/src/components/material-table'; + +import { PmDataInterval } from '../models/performanceDataType'; +import { getPmDataInterval } from '../pluginPerformance'; + +export const addColumnLabels = (name: string, title: string, disableFilter = true, disableSorting = true): ColumnModel => { + return { property: name as keyof T, title: title, type: ColumnType.text, disableFilter: disableFilter, disableSorting: disableSorting }; +}; + +export function getFilter(): string { + switch (getPmDataInterval()) { + case PmDataInterval.pmInterval15Min: + return 'pmdata-15m'; + case PmDataInterval.pmInterval24Hours: + return 'pmdata-24h'; + default: + throw new Error('Unknown time intervall'); + } +} \ No newline at end of file diff --git a/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/views/performanceHistoryApplication.tsx b/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/views/performanceHistoryApplication.tsx new file mode 100644 index 000000000..a4b968622 --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/src/views/performanceHistoryApplication.tsx @@ -0,0 +1,456 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + * ============LICENSE_END========================================================================== + */ +import React from 'react'; +import { connect, Connect, IDispatcher } from '../../../../framework/src/flux/connect'; + +import { Theme } from '@mui/material/styles'; +import { WithStyles } from '@mui/styles'; +import createStyles from '@mui/styles/createStyles'; +import withStyles from '@mui/styles/withStyles'; + + +import { NavigateToApplication } from '../../../../framework/src/actions/navigationActions'; +import { Dispatch } from '../../../../framework/src/flux/store'; +import { IApplicationStoreState } from '../../../../framework/src/store/applicationStore'; + +import { loadAllDeviceListAsync } from '../actions/deviceListActions'; +import { loadDistinctLtpsbyNetworkElementAsync, ResetLtpsAction } from '../actions/ltpAction'; +import { SetPanelAction } from '../actions/panelChangeActions'; +import { TimeChangeAction } from '../actions/timeChangeAction'; +import AdaptiveModulation from '../components/adaptiveModulation'; +import CrossPolarDiscrimination from '../components/crossPolarDiscrimination'; +import PerformanceData from '../components/performanceData'; +import ReceiveLevel from '../components/receiveLevel'; +import SignalToInterference from '../components/signalToInterference'; +import Temperature from '../components/temperature'; +import TransmissionPower from '../components/transmissionPower'; +import { adaptiveModulationReloadAction, createAdaptiveModulationActions, createAdaptiveModulationPreActions } from '../handlers/adaptiveModulationHandler'; +import { createCrossPolarDiscriminationActions, createCrossPolarDiscriminationPreActions, crossPolarDiscriminationReloadAction } from '../handlers/crossPolarDiscriminationHandler'; +import { createPerformanceDataActions, createPerformanceDataPreActions, performanceDataReloadAction } from '../handlers/performanceDataHandler'; +import { createReceiveLevelActions, createReceiveLevelPreActions, receiveLevelReloadAction } from '../handlers/receiveLevelHandler'; +import { createSignalToInterferenceActions, createSignalToInterferencePreActions, signalToInterferenceReloadAction } from '../handlers/signalToInterferenceHandler'; +import { createTemperatureActions, createTemperaturePreActions, temperatureReloadAction } from '../handlers/temperatureHandler'; +import { createTransmissionPowerActions, createTransmissionPowerPreActions, transmissionPowerReloadAction } from '../handlers/transmissionPowerHandler'; +import { PanelId } from '../models/panelId'; +import { PmDataInterval } from '../models/performanceDataType'; + +import { AppBar, SelectChangeEvent, Tab, Tabs } from '@mui/material'; +import { MaterialTable, MaterialTableCtorType } from '../../../../framework/src/components/material-table'; +import { ReloadAction } from '../actions/reloadAction'; +import { ResetAllSubViewsAction } from '../actions/toggleActions'; +import LtpSelection from '../components/ltpSelection'; + +const PerformanceHistoryComponentStyles = (theme: Theme) => createStyles({ + root: { + display: 'flex', + flexWrap: 'wrap', + }, + margin: { + margin: theme.spacing(1), + }, +}); + +const mapProps = (state: IApplicationStoreState) => ({ + ...state.performanceHistory, + activePanel: state.performanceHistory.currentOpenPanel, + availableLtps: state.performanceHistory.ltps.distinctLtps, + networkElements: state.performanceHistory.networkElements.deviceList, + initialLoaded: state.performanceHistory.ltps.loadedOnce, + error: state.performanceHistory.ltps.error, + shouldReload: state.performanceHistory.isReloadSchedueled, +}); + +const mapDispatcher = (dispatcher: IDispatcher) => ({ + enableFilterPerformanceData: createPerformanceDataActions(dispatcher.dispatch), + enableFilterReceiveLevel: createReceiveLevelActions(dispatcher.dispatch), + enableFilterTransmissionPower: createTransmissionPowerActions(dispatcher.dispatch), + enableFilterAdaptiveModulation: createAdaptiveModulationActions(dispatcher.dispatch), + enableFilterTemperature: createTemperatureActions(dispatcher.dispatch), + enableFilterSinr: createSignalToInterferenceActions(dispatcher.dispatch), + enableFilterCpd: createCrossPolarDiscriminationActions(dispatcher.dispatch), + reloadPerformanceData: () => dispatcher.dispatch(performanceDataReloadAction), + reloadReceiveLevel: () => dispatcher.dispatch(receiveLevelReloadAction), + reloadTransmissionPower: () => dispatcher.dispatch(transmissionPowerReloadAction), + reloadAdaptiveModulation: () => dispatcher.dispatch(adaptiveModulationReloadAction), + reloadTemperature: () => dispatcher.dispatch(temperatureReloadAction), + reloadSignalToInterference: () => dispatcher.dispatch(signalToInterferenceReloadAction), + reloadCrossPolarDiscrimination: () => dispatcher.dispatch(crossPolarDiscriminationReloadAction), + performanceDataPreActions: createPerformanceDataPreActions(dispatcher.dispatch), + receiveLevelPreActions: createReceiveLevelPreActions(dispatcher.dispatch), + transmissionPowerPreActions: createTransmissionPowerPreActions(dispatcher.dispatch), + adaptiveModulationPreActions: createAdaptiveModulationPreActions(dispatcher.dispatch), + temperaturePreActions: createTemperaturePreActions(dispatcher.dispatch), + signalToInterferencePreActions: createSignalToInterferencePreActions(dispatcher.dispatch), + crossPolarDiscriminationPreActions: createCrossPolarDiscriminationPreActions(dispatcher.dispatch), + getAllDevicesPMdata: async () => { + await dispatcher.dispatch(loadAllDeviceListAsync); + }, + getDistinctLtpsIds: ( + selectedNetworkElement: string, + selectedTimePeriod: string, + selectedLtp: string, + selectFirstLtp?: Function, + resetLTP?: Function, + ) => dispatcher.dispatch(loadDistinctLtpsbyNetworkElementAsync(selectedNetworkElement, selectedTimePeriod, selectedLtp, selectFirstLtp, resetLTP)), + setCurrentPanel: (panelId: PanelId) => dispatcher.dispatch(new SetPanelAction(panelId)), + timeIntervalChange: (time: PmDataInterval) => dispatcher.dispatch(new TimeChangeAction(time)), + changeNode: (nodeId: string) => dispatcher.dispatch((dispatch: Dispatch) => { + dispatch(new NavigateToApplication('performanceHistory', nodeId)); + }), + resetLtps: () => dispatcher.dispatch((dispatch: Dispatch) => { dispatch(new ResetLtpsAction()); }), + resetSubViews: () => dispatcher.dispatch(new ResetAllSubViewsAction()), + setShouldReload: (show: boolean) => dispatcher.dispatch(new ReloadAction(show)), +}); + +export type NetworkElementType = { + nodeId: string; +}; + +const NetworkElementTable = MaterialTable as MaterialTableCtorType; + +type PerformanceHistoryComponentProps = Connect & WithStyles; + +type PerformanceHistoryComponentState = { + selectedNetworkElement: string; + selectedTimePeriod: string; + selectedLtp: string; + showNetworkElementsTable: boolean; + showLtps: boolean; + showPanels: boolean; + preFilter: + { + 'node-name': string; + 'uuid-interface': string; + } | {}; +}; + +/** +* Represents the component for Performance history application. +*/ +class PerformanceHistoryComponent extends React.Component { + /** + * Initialises this instance + */ + constructor(props: PerformanceHistoryComponentProps) { + super(props); + this.state = { + selectedNetworkElement: props.nodeId !== '' ? props.nodeId : '-1', + selectedTimePeriod: '15min', + selectedLtp: '-1', + showNetworkElementsTable: true, + showLtps: false, + showPanels: false, + preFilter: {}, + }; + } + + onChangeTabs = (event: React.SyntheticEvent, newValue: PanelId) => { + const nextActivePanel = newValue; + this.changeTabs(nextActivePanel); + }; + + changeTabs = (nextActivePanel: PanelId) => { + this.props.setCurrentPanel(nextActivePanel); + const preFilter = this.state.preFilter; + switch (nextActivePanel) { + case 'PerformanceData': + if (this.props.performanceData.preFilter !== {} && this.props.performanceData.preFilter === preFilter) { + this.props.reloadPerformanceData(); + } else { + this.props.performanceDataPreActions.onPreFilterChanged(preFilter); + } + break; + case 'ReceiveLevel': + if (this.props.receiveLevel.preFilter !== {} && this.props.receiveLevel.preFilter === preFilter) { + this.props.reloadReceiveLevel(); + } else { + this.props.receiveLevelPreActions.onPreFilterChanged(preFilter); + } + break; + case 'TransmissionPower': + if (this.props.transmissionPower.preFilter !== {} && this.props.transmissionPower.preFilter === preFilter) { + this.props.reloadTransmissionPower(); + } else { + this.props.transmissionPowerPreActions.onPreFilterChanged(preFilter); + } + break; + case 'AdaptiveModulation': + if (this.props.adaptiveModulation.preFilter !== {} && this.props.adaptiveModulation.preFilter === preFilter) { + this.props.reloadAdaptiveModulation(); + } else { + this.props.adaptiveModulationPreActions.onPreFilterChanged(preFilter); + } + break; + case 'Temperature': + if (this.props.temperature.preFilter !== {} && this.props.temperature.preFilter === preFilter) { + this.props.reloadTemperature(); + } else { + this.props.temperaturePreActions.onPreFilterChanged(preFilter); + } + break; + case 'SINR': + if (this.props.signalToInterference.preFilter !== {} && this.props.signalToInterference.preFilter === preFilter) { + this.props.reloadSignalToInterference(); + } else { + this.props.signalToInterferencePreActions.onPreFilterChanged(preFilter); + } + break; + case 'CPD': + if (this.props.crossPolarDiscrimination.preFilter !== {} && this.props.crossPolarDiscrimination.preFilter === preFilter) { + this.props.reloadCrossPolarDiscrimination(); + } else { + this.props.crossPolarDiscriminationPreActions.onPreFilterChanged(preFilter); + } + break; + default: + // do nothing if all panels are closed + break; + } + }; + + render(): JSX.Element { + const { activePanel, nodeId } = this.props; + if (nodeId === '') { + return ( + <> + { this.handleNetworkElementSelect(rowData.nodeId); }} columns={ + [{ property: 'nodeId', title: 'Node Name' }] + } /> + + ); + } else { + this.handleURLChange(nodeId); + return ( + <> + {this.state.showLtps && + + + } + {this.state.showPanels && + <> + + + + + + + + + + + + + { + activePanel === 'PerformanceData' && + + } + + { + activePanel === 'ReceiveLevel' && + + } + + { + activePanel === 'TransmissionPower' && + + } + + { + activePanel === 'AdaptiveModulation' && + + } + { + activePanel === 'Temperature' && + + } + + { + activePanel === 'SINR' && + + } + + { + activePanel === 'CPD' && + + } + + } + + ); + } + } + + + public componentDidMount() { + this.props.setCurrentPanel(null); + this.props.getAllDevicesPMdata(); + } + + /** + * Function which selects the first ltp returned from the database on selection of network element. + */ + private selectFirstLtp = (firstLtp: string) => { + this.setState({ + showPanels: true, + selectedLtp: firstLtp, + }); + this.preFilterChangeAndReload(this.state.selectedNetworkElement, this.state.selectedTimePeriod, firstLtp); + this.changeTabs('PerformanceData'); + }; + + /** + * A function which reloads the visible table, if available, based on prefilters defined by network element and ltp on selected time period. + */ + private preFilterChangeAndReload = (networkElement: string, timePeriod: string, ltp: string) => { + const newPreFilter = { + 'node-name': networkElement, + 'uuid-interface': ltp, + }; + + const activePanel = this.props.activePanel; + + if (this.props.activePanel !== null) { + // set prefilter and reload data if panel is open + + switch (activePanel) { + case 'PerformanceData': + this.props.performanceDataPreActions.onPreFilterChanged(newPreFilter); + break; + case 'ReceiveLevel': + this.props.receiveLevelPreActions.onPreFilterChanged(newPreFilter); + break; + case 'TransmissionPower': + this.props.transmissionPowerPreActions.onPreFilterChanged(newPreFilter); + break; + case 'AdaptiveModulation': + this.props.adaptiveModulationPreActions.onPreFilterChanged(newPreFilter); + break; + case 'Temperature': + this.props.temperaturePreActions.onPreFilterChanged(newPreFilter); + break; + case 'SINR': + this.props.signalToInterferencePreActions.onPreFilterChanged(newPreFilter); + break; + case 'CPD': + this.props.crossPolarDiscriminationPreActions.onPreFilterChanged(newPreFilter); + break; + default: + // do nothing if all panels are closed + break; + } + } + + // set prefilter + this.setState({ preFilter: newPreFilter }); + + }; + + /** + * Function which handles network element changes. + */ + private handleNetworkElementSelect = (selectedNetworkElement: string) => { + + this.setState({ + showLtps: true, + selectedNetworkElement: selectedNetworkElement, + showNetworkElementsTable: false, + showPanels: false, + selectedLtp: '-1', + }); + + this.props.resetSubViews(); + this.props.resetLtps(); + this.setState({ preFilter: {} }); + this.props.changeNode(selectedNetworkElement); + this.props.getDistinctLtpsIds(selectedNetworkElement, this.state.selectedTimePeriod, '-1', this.selectFirstLtp); + }; + + private handleURLChange = (selectedNetworkElement: string) => { + + if (this.props.shouldReload) { + + this.setState({ + showLtps: true, + selectedNetworkElement: selectedNetworkElement, + showPanels: false, + selectedLtp: '-1', + }); + this.props.getDistinctLtpsIds(selectedNetworkElement, this.state.selectedTimePeriod, '-1', this.selectFirstLtp); + this.props.setShouldReload(false); + } + }; + + /** + * Function which resets the ltps to "--select--" in the state if the passed parameter @ltpNotSelected is true. + * @param ltpNotSelected: true, if existing selected is not available in the given time period, else false + */ + private resetLtpDropdown = (ltpNotSelected: boolean) => { + if (ltpNotSelected) { + this.setState({ + selectedLtp: '-1', + showPanels: false, + }); + } + }; + + /** + * Function which handles the time period changes. + */ + private handleTimePeriodChange = (event: SelectChangeEvent) => { + + const selectedTimeInterval = event.target.value === '15min' + ? PmDataInterval.pmInterval15Min + : PmDataInterval.pmInterval24Hours; + + this.setState({ + selectedTimePeriod: event.target.value as string, + }); + + this.props.timeIntervalChange(selectedTimeInterval); + this.props.getDistinctLtpsIds(this.state.selectedNetworkElement, event.target.value as string, this.state.selectedLtp, undefined, this.resetLtpDropdown); + this.preFilterChangeAndReload(this.state.selectedNetworkElement, event.target.value as string, this.state.selectedLtp); + }; + + /** + * Function which handles the ltp changes. + */ + private handleLtpChange = (event:SelectChangeEvent ) => { + + if (event.target.value === '-1') { + this.setState({ + showPanels: false, + selectedLtp: event.target.value, + }); + + } else if (event.target.value !== this.state.selectedLtp) { + this.setState({ + showPanels: true, + selectedLtp: event.target.value as string, + }); + this.preFilterChangeAndReload(this.state.selectedNetworkElement, this.state.selectedTimePeriod, event.target.value as string); + + } + }; +} + +const PerformanceHistoryApplication = withStyles(PerformanceHistoryComponentStyles)(connect(mapProps, mapDispatcher)(PerformanceHistoryComponent)); +export default PerformanceHistoryApplication; diff --git a/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/tsconfig.json b/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/tsconfig.json new file mode 100644 index 000000000..ca65092e0 --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/tsconfig.json @@ -0,0 +1,37 @@ +{ + "compilerOptions": { + "baseUrl": "./src", + "outDir": "./dist", + "sourceMap": true, + "forceConsistentCasingInFileNames": true, + "allowSyntheticDefaultImports": true, + "allowUnreachableCode": false, + "allowUnusedLabels": false, + "noFallthroughCasesInSwitch": true, + "noImplicitAny": true, + "noImplicitReturns": true, + "noImplicitThis": true, + "strictNullChecks": true, + "pretty": true, + "newLine": "LF", + "module": "es2015", + "target": "es2016", + "moduleResolution": "node", + "experimentalDecorators": true, + "jsx": "preserve", + "lib": [ + "dom", + "es2015", + "es2016" + ], + "types": [ + "prop-types", + "react", + "react-dom" + ] + }, + "exclude": [ + "dist", + "node_modules" + ] +} diff --git a/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/webpack.config.js b/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/webpack.config.js new file mode 100644 index 000000000..2f25d0df1 --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/performanceHistoryApp/webpack.config.js @@ -0,0 +1,167 @@ +/** + * Webpack 4 configuration file + * see https://webpack.js.org/configuration/ + * see https://webpack.js.org/configuration/dev-server/ + */ + +"use strict"; + +const path = require("path"); +const webpack = require("webpack"); +const CopyWebpackPlugin = require("copy-webpack-plugin"); +const TerserPlugin = require('terser-webpack-plugin'); + +// const __dirname = (path => path.replace(/^([a-z]\:)/, c => c.toUpperCase()))(process.__dirname()); + +module.exports = (env) => { + const distPath = path.resolve(__dirname, env === "release" ? "." : "../..", "dist"); + const frameworkPath = path.resolve(__dirname, env === "release" ? "../../framework" : "../..", "dist"); + return [{ + name: "App", + + mode: "none", //disable default behavior + + target: "web", + + context: path.resolve(__dirname, "src"), + + entry: { + performanceHistoryApp: ["./pluginPerformance.tsx"] + }, + + devtool: env === "release" ? false : "source-map", + + resolve: { + extensions: [".ts", ".tsx", ".js", ".jsx"] + }, + + output: { + path: distPath, + filename: "[name].js", + library: "[name]", + libraryTarget: "umd2", + chunkFilename: "[name].js" + }, + module: { + rules: [{ + test: /\.tsx?$/, + exclude: /node_modules/, + use: [{ + loader: "babel-loader" + }, { + loader: "ts-loader" + }] + }, { + test: /\.jsx?$/, + exclude: /node_modules/, + use: [{ + loader: "babel-loader" + }] + },{ + //don't minify images + test: /\.(png|gif|jpg|svg)$/, + use: [{ + loader: 'url-loader', + options: { + limit: 10, + name: './images/[name].[ext]' + } + }] + }] + }, + + optimization: { + noEmitOnErrors: true, + namedModules: env !== "release", + minimize: env === "release", + minimizer: env !== "release" ? [] : [new TerserPlugin({ + terserOptions: { + warnings: false, // false, true, "verbose" + compress: { + drop_console: true, + drop_debugger: true, + } + } + })], + }, + plugins: [ + new webpack.DllReferencePlugin({ + context: path.resolve(__dirname, "../../framework/src"), + manifest: require(path.resolve(frameworkPath, "vendor-manifest.json")), + sourceType: "umd2" + }), + new webpack.DllReferencePlugin({ + context: path.resolve(__dirname, "../../framework/src"), + manifest: require(path.resolve(frameworkPath, "app-manifest.json")), + sourceType: "umd2" + }), + ...(env === "release" ? [ + new webpack.DefinePlugin({ + "process.env": { + NODE_ENV: "'production'", + VERSION: JSON.stringify(require("./package.json").version) + } + }), + ] : [ + new webpack.DefinePlugin({ + "process.env": { + NODE_ENV: "'development'", + VERSION: JSON.stringify(require("./package.json").version) + } + }), + new CopyWebpackPlugin([{ + from: 'index.html', + to: distPath + }]), + ]) + ], + + devServer: { + public: "http://localhost:3100", + contentBase: frameworkPath, + + compress: true, + headers: { + "Access-Control-Allow-Origin": "*" + }, + host: "0.0.0.0", + port: 3100, + disableHostCheck: true, + historyApiFallback: true, + inline: true, + hot: false, + quiet: false, + stats: { + colors: true + }, + proxy: { + "/oauth2/": { + target: "http://10.20.6.29:48181", + secure: false + }, + "/database/": { + target: "http://10.20.6.29:48181", + secure: false + }, + "/restconf/": { + target: "http://10.20.6.29:48181", + secure: false + }, + "/rests/": { + target: "http://10.20.6.29:48181", + secure: false + }, + "/help/": { + target: "http://10.20.6.29:48181", + secure: false + }, + "/websocket": { + target: "http://10.20.6.29:48181", + ws: true, + changeOrigin: true, + secure: false + } + } + } + }]; +} diff --git a/sdnr/wt-odlux/odlux/framework/.babelrc b/sdnr/wt-odlux/odlux/framework/.babelrc new file mode 100644 index 000000000..3d8cd1260 --- /dev/null +++ b/sdnr/wt-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/sdnr/wt-odlux/odlux/framework/LICENSE b/sdnr/wt-odlux/odlux/framework/LICENSE new file mode 100644 index 000000000..3556ffa71 --- /dev/null +++ b/sdnr/wt-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/sdnr/wt-odlux/odlux/framework/package.json b/sdnr/wt-odlux/odlux/framework/package.json new file mode 100644 index 000000000..9973d3b7c --- /dev/null +++ b/sdnr/wt-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/sdnr/wt-odlux/odlux/framework/pom.xml b/sdnr/wt-odlux/odlux/framework/pom.xml new file mode 100644 index 000000000..83e5cdd7e --- /dev/null +++ b/sdnr/wt-odlux/odlux/framework/pom.xml @@ -0,0 +1,262 @@ + + + + + 4.0.0 + + org.onap.ccsdk.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}) + 172.b89e2c2b(23/10/04) + 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/sdnr/wt-odlux/odlux/framework/src/actions/authentication.ts b/sdnr/wt-odlux/odlux/framework/src/actions/authentication.ts new file mode 100644 index 000000000..141249530 --- /dev/null +++ b/sdnr/wt-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/sdnr/wt-odlux/odlux/framework/src/actions/errorActions.ts b/sdnr/wt-odlux/odlux/framework/src/actions/errorActions.ts new file mode 100644 index 000000000..ddb0d5728 --- /dev/null +++ b/sdnr/wt-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/sdnr/wt-odlux/odlux/framework/src/actions/loginProvider.ts b/sdnr/wt-odlux/odlux/framework/src/actions/loginProvider.ts new file mode 100644 index 000000000..e6467111e --- /dev/null +++ b/sdnr/wt-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/sdnr/wt-odlux/odlux/framework/src/actions/menuAction.ts b/sdnr/wt-odlux/odlux/framework/src/actions/menuAction.ts new file mode 100644 index 000000000..ec0796587 --- /dev/null +++ b/sdnr/wt-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/sdnr/wt-odlux/odlux/framework/src/actions/navigationActions.ts b/sdnr/wt-odlux/odlux/framework/src/actions/navigationActions.ts new file mode 100644 index 000000000..6951b979f --- /dev/null +++ b/sdnr/wt-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/sdnr/wt-odlux/odlux/framework/src/actions/settingsAction.ts b/sdnr/wt-odlux/odlux/framework/src/actions/settingsAction.ts new file mode 100644 index 000000000..fa45f239c --- /dev/null +++ b/sdnr/wt-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/sdnr/wt-odlux/odlux/framework/src/actions/snackbarActions.ts b/sdnr/wt-odlux/odlux/framework/src/actions/snackbarActions.ts new file mode 100644 index 000000000..ad4d60609 --- /dev/null +++ b/sdnr/wt-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/sdnr/wt-odlux/odlux/framework/src/actions/titleActions.ts b/sdnr/wt-odlux/odlux/framework/src/actions/titleActions.ts new file mode 100644 index 000000000..4bcfe292e --- /dev/null +++ b/sdnr/wt-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/sdnr/wt-odlux/odlux/framework/src/actions/websocketAction.ts b/sdnr/wt-odlux/odlux/framework/src/actions/websocketAction.ts new file mode 100644 index 000000000..0b45f7ac7 --- /dev/null +++ b/sdnr/wt-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/sdnr/wt-odlux/odlux/framework/src/app.css b/sdnr/wt-odlux/odlux/framework/src/app.css new file mode 100644 index 000000000..9b653b3b5 --- /dev/null +++ b/sdnr/wt-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/sdnr/wt-odlux/odlux/framework/src/app.tsx b/sdnr/wt-odlux/odlux/framework/src/app.tsx new file mode 100644 index 000000000..bbe1f9ec8 --- /dev/null +++ b/sdnr/wt-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/sdnr/wt-odlux/odlux/framework/src/assets/icons/About.svg b/sdnr/wt-odlux/odlux/framework/src/assets/icons/About.svg new file mode 100644 index 000000000..156e36efe --- /dev/null +++ b/sdnr/wt-odlux/odlux/framework/src/assets/icons/About.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/sdnr/wt-odlux/odlux/framework/src/assets/icons/Home.svg b/sdnr/wt-odlux/odlux/framework/src/assets/icons/Home.svg new file mode 100644 index 000000000..0836714b4 --- /dev/null +++ b/sdnr/wt-odlux/odlux/framework/src/assets/icons/Home.svg @@ -0,0 +1,28 @@ + + + + + + + + + + + + diff --git a/sdnr/wt-odlux/odlux/framework/src/assets/icons/Menu.svg b/sdnr/wt-odlux/odlux/framework/src/assets/icons/Menu.svg new file mode 100644 index 000000000..ea0312802 --- /dev/null +++ b/sdnr/wt-odlux/odlux/framework/src/assets/icons/Menu.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/sdnr/wt-odlux/odlux/framework/src/assets/icons/Tools.svg b/sdnr/wt-odlux/odlux/framework/src/assets/icons/Tools.svg new file mode 100644 index 000000000..1595cdc1c --- /dev/null +++ b/sdnr/wt-odlux/odlux/framework/src/assets/icons/Tools.svg @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + diff --git a/sdnr/wt-odlux/odlux/framework/src/assets/icons/User.svg b/sdnr/wt-odlux/odlux/framework/src/assets/icons/User.svg new file mode 100644 index 000000000..99618cf57 --- /dev/null +++ b/sdnr/wt-odlux/odlux/framework/src/assets/icons/User.svg @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/sdnr/wt-odlux/odlux/framework/src/assets/icons/ht.Connect.png b/sdnr/wt-odlux/odlux/framework/src/assets/icons/ht.Connect.png new file mode 100644 index 000000000..412390c79 Binary files /dev/null and b/sdnr/wt-odlux/odlux/framework/src/assets/icons/ht.Connect.png differ diff --git a/sdnr/wt-odlux/odlux/framework/src/assets/icons/ht.Connect.svg b/sdnr/wt-odlux/odlux/framework/src/assets/icons/ht.Connect.svg new file mode 100644 index 000000000..c1d74bc96 --- /dev/null +++ b/sdnr/wt-odlux/odlux/framework/src/assets/icons/ht.Connect.svg @@ -0,0 +1,81 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/sdnr/wt-odlux/odlux/framework/src/assets/images/defaultLogo.svg b/sdnr/wt-odlux/odlux/framework/src/assets/images/defaultLogo.svg new file mode 100644 index 000000000..bd9ddf583 --- /dev/null +++ b/sdnr/wt-odlux/odlux/framework/src/assets/images/defaultLogo.svg @@ -0,0 +1,179 @@ + +image/svg+xml \ No newline at end of file diff --git a/sdnr/wt-odlux/odlux/framework/src/assets/images/defaultLogo.svg.d.ts b/sdnr/wt-odlux/odlux/framework/src/assets/images/defaultLogo.svg.d.ts new file mode 100644 index 000000000..60e4856dd --- /dev/null +++ b/sdnr/wt-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/sdnr/wt-odlux/odlux/framework/src/assets/images/home.svg b/sdnr/wt-odlux/odlux/framework/src/assets/images/home.svg new file mode 100644 index 000000000..080d0502e --- /dev/null +++ b/sdnr/wt-odlux/odlux/framework/src/assets/images/home.svg @@ -0,0 +1,20 @@ + + + + + + + + + + diff --git a/sdnr/wt-odlux/odlux/framework/src/assets/images/home.svg.d.ts b/sdnr/wt-odlux/odlux/framework/src/assets/images/home.svg.d.ts new file mode 100644 index 000000000..7098d79a2 --- /dev/null +++ b/sdnr/wt-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/sdnr/wt-odlux/odlux/framework/src/assets/images/odluxLogo.gif b/sdnr/wt-odlux/odlux/framework/src/assets/images/odluxLogo.gif new file mode 100644 index 000000000..ad188a8d3 Binary files /dev/null and b/sdnr/wt-odlux/odlux/framework/src/assets/images/odluxLogo.gif differ diff --git a/sdnr/wt-odlux/odlux/framework/src/assets/images/odluxLogo.gif.d.ts b/sdnr/wt-odlux/odlux/framework/src/assets/images/odluxLogo.gif.d.ts new file mode 100644 index 000000000..36131834c --- /dev/null +++ b/sdnr/wt-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/sdnr/wt-odlux/odlux/framework/src/assets/images/onapLogo.gif b/sdnr/wt-odlux/odlux/framework/src/assets/images/onapLogo.gif new file mode 100644 index 000000000..cd7eb8f3c Binary files /dev/null and b/sdnr/wt-odlux/odlux/framework/src/assets/images/onapLogo.gif differ diff --git a/sdnr/wt-odlux/odlux/framework/src/assets/images/onapLogo.gif.d.ts b/sdnr/wt-odlux/odlux/framework/src/assets/images/onapLogo.gif.d.ts new file mode 100644 index 000000000..5bfb37a84 --- /dev/null +++ b/sdnr/wt-odlux/odlux/framework/src/assets/images/onapLogo.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 onapLogo: string; + export default onapLogo; \ No newline at end of file diff --git a/sdnr/wt-odlux/odlux/framework/src/assets/version.json b/sdnr/wt-odlux/odlux/framework/src/assets/version.json new file mode 100644 index 000000000..6d4eb0cc6 --- /dev/null +++ b/sdnr/wt-odlux/odlux/framework/src/assets/version.json @@ -0,0 +1,4 @@ +{ + "version":"56.139cd6d(20/07/08)", + "build":"2020-07-16T06:06:24Z" +} \ No newline at end of file diff --git a/sdnr/wt-odlux/odlux/framework/src/common/event.ts b/sdnr/wt-odlux/odlux/framework/src/common/event.ts new file mode 100644 index 000000000..913487911 --- /dev/null +++ b/sdnr/wt-odlux/odlux/framework/src/common/event.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========================================================================== + */ + + +/** + * Represents an event. + * Events enable a class or object to notify other classes or objects when something of interest occurs. + * The class that sends (or invokes) the event is called the publisher and the classes that receive (or handle) the event are called subscribers. + * + * Objects can create an instances of an Events and offer that Events for other objects to attach to. + * Objects who want to be informed about an Event can attach a function (an event handler) to the event which is then called when the event is fired. + * + * @template TEventArg Type of the event argument. Use void if the event does not has an argument. + */ +export class Event { + + /** + * 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/sdnr/wt-odlux/odlux/framework/src/components/errorDisplay.tsx b/sdnr/wt-odlux/odlux/framework/src/components/errorDisplay.tsx new file mode 100644 index 000000000..d41d82687 --- /dev/null +++ b/sdnr/wt-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/sdnr/wt-odlux/odlux/framework/src/components/icons/menuIcon.tsx b/sdnr/wt-odlux/odlux/framework/src/components/icons/menuIcon.tsx new file mode 100644 index 000000000..0d7d734c9 --- /dev/null +++ b/sdnr/wt-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/sdnr/wt-odlux/odlux/framework/src/components/logo.tsx b/sdnr/wt-odlux/odlux/framework/src/components/logo.tsx new file mode 100644 index 000000000..f2bb1f575 --- /dev/null +++ b/sdnr/wt-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/sdnr/wt-odlux/odlux/framework/src/components/material-table/columnModel.ts b/sdnr/wt-odlux/odlux/framework/src/components/material-table/columnModel.ts new file mode 100644 index 000000000..3ed313497 --- /dev/null +++ b/sdnr/wt-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/sdnr/wt-odlux/odlux/framework/src/components/material-table/index.tsx b/sdnr/wt-odlux/odlux/framework/src/components/material-table/index.tsx new file mode 100644 index 000000000..c1a5005d4 --- /dev/null +++ b/sdnr/wt-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/sdnr/wt-odlux/odlux/framework/src/components/material-table/showColumnDialog.tsx b/sdnr/wt-odlux/odlux/framework/src/components/material-table/showColumnDialog.tsx new file mode 100644 index 000000000..ab0d465e7 --- /dev/null +++ b/sdnr/wt-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/sdnr/wt-odlux/odlux/framework/src/components/material-table/tableFilter.tsx b/sdnr/wt-odlux/odlux/framework/src/components/material-table/tableFilter.tsx new file mode 100644 index 000000000..1b9136844 --- /dev/null +++ b/sdnr/wt-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/sdnr/wt-odlux/odlux/framework/src/components/material-table/tableHead.tsx b/sdnr/wt-odlux/odlux/framework/src/components/material-table/tableHead.tsx new file mode 100644 index 000000000..d6f7b7def --- /dev/null +++ b/sdnr/wt-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/sdnr/wt-odlux/odlux/framework/src/components/material-table/tableToolbar.tsx b/sdnr/wt-odlux/odlux/framework/src/components/material-table/tableToolbar.tsx new file mode 100644 index 000000000..143b802a4 --- /dev/null +++ b/sdnr/wt-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/sdnr/wt-odlux/odlux/framework/src/components/material-table/utilities.ts b/sdnr/wt-odlux/odlux/framework/src/components/material-table/utilities.ts new file mode 100644 index 000000000..e2fda7647 --- /dev/null +++ b/sdnr/wt-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/sdnr/wt-odlux/odlux/framework/src/components/material-ui/index.ts b/sdnr/wt-odlux/odlux/framework/src/components/material-ui/index.ts new file mode 100644 index 000000000..096e4439d --- /dev/null +++ b/sdnr/wt-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/sdnr/wt-odlux/odlux/framework/src/components/material-ui/listItemLink.tsx b/sdnr/wt-odlux/odlux/framework/src/components/material-ui/listItemLink.tsx new file mode 100644 index 000000000..626cb8978 --- /dev/null +++ b/sdnr/wt-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/sdnr/wt-odlux/odlux/framework/src/components/material-ui/loader.tsx b/sdnr/wt-odlux/odlux/framework/src/components/material-ui/loader.tsx new file mode 100644 index 000000000..bd523e1f4 --- /dev/null +++ b/sdnr/wt-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/sdnr/wt-odlux/odlux/framework/src/components/material-ui/panel.tsx b/sdnr/wt-odlux/odlux/framework/src/components/material-ui/panel.tsx new file mode 100644 index 000000000..6d192d2f0 --- /dev/null +++ b/sdnr/wt-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/sdnr/wt-odlux/odlux/framework/src/components/material-ui/snackDisplay.tsx b/sdnr/wt-odlux/odlux/framework/src/components/material-ui/snackDisplay.tsx new file mode 100644 index 000000000..437784ce5 --- /dev/null +++ b/sdnr/wt-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/sdnr/wt-odlux/odlux/framework/src/components/material-ui/toggleButton.tsx b/sdnr/wt-odlux/odlux/framework/src/components/material-ui/toggleButton.tsx new file mode 100644 index 000000000..54f14a7e0 --- /dev/null +++ b/sdnr/wt-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/sdnr/wt-odlux/odlux/framework/src/components/material-ui/toggleButtonGroup.tsx b/sdnr/wt-odlux/odlux/framework/src/components/material-ui/toggleButtonGroup.tsx new file mode 100644 index 000000000..bdabe0d56 --- /dev/null +++ b/sdnr/wt-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/sdnr/wt-odlux/odlux/framework/src/components/material-ui/treeView.tsx b/sdnr/wt-odlux/odlux/framework/src/components/material-ui/treeView.tsx new file mode 100644 index 000000000..5c23909c4 --- /dev/null +++ b/sdnr/wt-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/sdnr/wt-odlux/odlux/framework/src/components/navigationMenu.tsx b/sdnr/wt-odlux/odlux/framework/src/components/navigationMenu.tsx new file mode 100644 index 000000000..bcc191a89 --- /dev/null +++ b/sdnr/wt-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/sdnr/wt-odlux/odlux/framework/src/components/objectDump/index.tsx b/sdnr/wt-odlux/odlux/framework/src/components/objectDump/index.tsx new file mode 100644 index 000000000..10a0547be --- /dev/null +++ b/sdnr/wt-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/sdnr/wt-odlux/odlux/framework/src/components/routing/appFrame.tsx b/sdnr/wt-odlux/odlux/framework/src/components/routing/appFrame.tsx new file mode 100644 index 000000000..aa22f17f4 --- /dev/null +++ b/sdnr/wt-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/sdnr/wt-odlux/odlux/framework/src/components/settings/general.tsx b/sdnr/wt-odlux/odlux/framework/src/components/settings/general.tsx new file mode 100644 index 000000000..ffd516ba1 --- /dev/null +++ b/sdnr/wt-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/sdnr/wt-odlux/odlux/framework/src/components/titleBar.tsx b/sdnr/wt-odlux/odlux/framework/src/components/titleBar.tsx new file mode 100644 index 000000000..40c0fc736 --- /dev/null +++ b/sdnr/wt-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/sdnr/wt-odlux/odlux/framework/src/design/default.ts b/sdnr/wt-odlux/odlux/framework/src/design/default.ts new file mode 100644 index 000000000..c4a8118c2 --- /dev/null +++ b/sdnr/wt-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/sdnr/wt-odlux/odlux/framework/src/favicon.ico b/sdnr/wt-odlux/odlux/framework/src/favicon.ico new file mode 100644 index 000000000..a8a5d31ca Binary files /dev/null and b/sdnr/wt-odlux/odlux/framework/src/favicon.ico differ diff --git a/sdnr/wt-odlux/odlux/framework/src/flux/action.ts b/sdnr/wt-odlux/odlux/framework/src/flux/action.ts new file mode 100644 index 000000000..76890257e --- /dev/null +++ b/sdnr/wt-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/sdnr/wt-odlux/odlux/framework/src/flux/connect.tsx b/sdnr/wt-odlux/odlux/framework/src/flux/connect.tsx new file mode 100644 index 000000000..09d30dae7 --- /dev/null +++ b/sdnr/wt-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/sdnr/wt-odlux/odlux/framework/src/flux/middleware.ts b/sdnr/wt-odlux/odlux/framework/src/flux/middleware.ts new file mode 100644 index 000000000..de6505c4f --- /dev/null +++ b/sdnr/wt-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/sdnr/wt-odlux/odlux/framework/src/flux/store.ts b/sdnr/wt-odlux/odlux/framework/src/flux/store.ts new file mode 100644 index 000000000..347d295e0 --- /dev/null +++ b/sdnr/wt-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/sdnr/wt-odlux/odlux/framework/src/handlers/applicationRegistryHandler.ts b/sdnr/wt-odlux/odlux/framework/src/handlers/applicationRegistryHandler.ts new file mode 100644 index 000000000..ec7b0a04a --- /dev/null +++ b/sdnr/wt-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/sdnr/wt-odlux/odlux/framework/src/handlers/applicationStateHandler.ts b/sdnr/wt-odlux/odlux/framework/src/handlers/applicationStateHandler.ts new file mode 100644 index 000000000..d0a07248d --- /dev/null +++ b/sdnr/wt-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/sdnr/wt-odlux/odlux/framework/src/handlers/authenticationHandler.ts b/sdnr/wt-odlux/odlux/framework/src/handlers/authenticationHandler.ts new file mode 100644 index 000000000..3a4891c98 --- /dev/null +++ b/sdnr/wt-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/sdnr/wt-odlux/odlux/framework/src/handlers/navigationStateHandler.ts b/sdnr/wt-odlux/odlux/framework/src/handlers/navigationStateHandler.ts new file mode 100644 index 000000000..6ecdf2f79 --- /dev/null +++ b/sdnr/wt-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/sdnr/wt-odlux/odlux/framework/src/index.dev.html b/sdnr/wt-odlux/odlux/framework/src/index.dev.html new file mode 100644 index 000000000..69c5f0607 --- /dev/null +++ b/sdnr/wt-odlux/odlux/framework/src/index.dev.html @@ -0,0 +1,35 @@ + + + + + + + + + O D L UX + + + +
+ + + + + + + \ No newline at end of file diff --git a/sdnr/wt-odlux/odlux/framework/src/index.html b/sdnr/wt-odlux/odlux/framework/src/index.html new file mode 100644 index 000000000..7196b5cfe --- /dev/null +++ b/sdnr/wt-odlux/odlux/framework/src/index.html @@ -0,0 +1,27 @@ + + + + + + + + + O D L UX + + + +
+ + + + + + \ No newline at end of file diff --git a/sdnr/wt-odlux/odlux/framework/src/middleware/api.ts b/sdnr/wt-odlux/odlux/framework/src/middleware/api.ts new file mode 100644 index 000000000..8e9bc6459 --- /dev/null +++ b/sdnr/wt-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/sdnr/wt-odlux/odlux/framework/src/middleware/logger.ts b/sdnr/wt-odlux/odlux/framework/src/middleware/logger.ts new file mode 100644 index 000000000..fb0874f3e --- /dev/null +++ b/sdnr/wt-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/sdnr/wt-odlux/odlux/framework/src/middleware/navigation.ts b/sdnr/wt-odlux/odlux/framework/src/middleware/navigation.ts new file mode 100644 index 000000000..14ff09f77 --- /dev/null +++ b/sdnr/wt-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/sdnr/wt-odlux/odlux/framework/src/middleware/policies.ts b/sdnr/wt-odlux/odlux/framework/src/middleware/policies.ts new file mode 100644 index 000000000..662ecddb3 --- /dev/null +++ b/sdnr/wt-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/sdnr/wt-odlux/odlux/framework/src/middleware/thunk.ts b/sdnr/wt-odlux/odlux/framework/src/middleware/thunk.ts new file mode 100644 index 000000000..18f40c4ca --- /dev/null +++ b/sdnr/wt-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/sdnr/wt-odlux/odlux/framework/src/models/applicationConfig.ts b/sdnr/wt-odlux/odlux/framework/src/models/applicationConfig.ts new file mode 100644 index 000000000..0ae9c266b --- /dev/null +++ b/sdnr/wt-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/sdnr/wt-odlux/odlux/framework/src/models/applicationInfo.ts b/sdnr/wt-odlux/odlux/framework/src/models/applicationInfo.ts new file mode 100644 index 000000000..ff07b7d7b --- /dev/null +++ b/sdnr/wt-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/sdnr/wt-odlux/odlux/framework/src/models/authentication.ts b/sdnr/wt-odlux/odlux/framework/src/models/authentication.ts new file mode 100644 index 000000000..f56538184 --- /dev/null +++ b/sdnr/wt-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/sdnr/wt-odlux/odlux/framework/src/models/elasticSearch.ts b/sdnr/wt-odlux/odlux/framework/src/models/elasticSearch.ts new file mode 100644 index 000000000..fc4383612 --- /dev/null +++ b/sdnr/wt-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/sdnr/wt-odlux/odlux/framework/src/models/errorInfo.ts b/sdnr/wt-odlux/odlux/framework/src/models/errorInfo.ts new file mode 100644 index 000000000..21217a108 --- /dev/null +++ b/sdnr/wt-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/sdnr/wt-odlux/odlux/framework/src/models/externalLoginProvider.ts b/sdnr/wt-odlux/odlux/framework/src/models/externalLoginProvider.ts new file mode 100644 index 000000000..357feed06 --- /dev/null +++ b/sdnr/wt-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/sdnr/wt-odlux/odlux/framework/src/models/iconDefinition.ts b/sdnr/wt-odlux/odlux/framework/src/models/iconDefinition.ts new file mode 100644 index 000000000..ff50aa73c --- /dev/null +++ b/sdnr/wt-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/sdnr/wt-odlux/odlux/framework/src/models/index.ts b/sdnr/wt-odlux/odlux/framework/src/models/index.ts new file mode 100644 index 000000000..4b8dc20aa --- /dev/null +++ b/sdnr/wt-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/sdnr/wt-odlux/odlux/framework/src/models/restService.ts b/sdnr/wt-odlux/odlux/framework/src/models/restService.ts new file mode 100644 index 000000000..03f580b01 --- /dev/null +++ b/sdnr/wt-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/sdnr/wt-odlux/odlux/framework/src/models/settings.ts b/sdnr/wt-odlux/odlux/framework/src/models/settings.ts new file mode 100644 index 000000000..1752d6a52 --- /dev/null +++ b/sdnr/wt-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/sdnr/wt-odlux/odlux/framework/src/models/snackbarItem.ts b/sdnr/wt-odlux/odlux/framework/src/models/snackbarItem.ts new file mode 100644 index 000000000..c10d7ef37 --- /dev/null +++ b/sdnr/wt-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/sdnr/wt-odlux/odlux/framework/src/run.ts b/sdnr/wt-odlux/odlux/framework/src/run.ts new file mode 100644 index 000000000..991b7494e --- /dev/null +++ b/sdnr/wt-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/sdnr/wt-odlux/odlux/framework/src/services/applicationApi.ts b/sdnr/wt-odlux/odlux/framework/src/services/applicationApi.ts new file mode 100644 index 000000000..faa998450 --- /dev/null +++ b/sdnr/wt-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/sdnr/wt-odlux/odlux/framework/src/services/applicationManager.ts b/sdnr/wt-odlux/odlux/framework/src/services/applicationManager.ts new file mode 100644 index 000000000..bd620a020 --- /dev/null +++ b/sdnr/wt-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/sdnr/wt-odlux/odlux/framework/src/services/authenticationService.ts b/sdnr/wt-odlux/odlux/framework/src/services/authenticationService.ts new file mode 100644 index 000000000..b9c1a5a94 --- /dev/null +++ b/sdnr/wt-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/sdnr/wt-odlux/odlux/framework/src/services/broadcastService.ts b/sdnr/wt-odlux/odlux/framework/src/services/broadcastService.ts new file mode 100644 index 000000000..202bf5563 --- /dev/null +++ b/sdnr/wt-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/sdnr/wt-odlux/odlux/framework/src/services/index.ts b/sdnr/wt-odlux/odlux/framework/src/services/index.ts new file mode 100644 index 000000000..2f64ba0ac --- /dev/null +++ b/sdnr/wt-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/sdnr/wt-odlux/odlux/framework/src/services/notificationService.ts b/sdnr/wt-odlux/odlux/framework/src/services/notificationService.ts new file mode 100644 index 000000000..b2880b9de --- /dev/null +++ b/sdnr/wt-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/sdnr/wt-odlux/odlux/framework/src/services/restAccessorService.ts b/sdnr/wt-odlux/odlux/framework/src/services/restAccessorService.ts new file mode 100644 index 000000000..5ed4d7b6e --- /dev/null +++ b/sdnr/wt-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/sdnr/wt-odlux/odlux/framework/src/services/restService.ts b/sdnr/wt-odlux/odlux/framework/src/services/restService.ts new file mode 100644 index 000000000..0be3dca07 --- /dev/null +++ b/sdnr/wt-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/sdnr/wt-odlux/odlux/framework/src/services/snackbarService.ts b/sdnr/wt-odlux/odlux/framework/src/services/snackbarService.ts new file mode 100644 index 000000000..50e279c67 --- /dev/null +++ b/sdnr/wt-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/sdnr/wt-odlux/odlux/framework/src/services/storeService.ts b/sdnr/wt-odlux/odlux/framework/src/services/storeService.ts new file mode 100644 index 000000000..cbb5987de --- /dev/null +++ b/sdnr/wt-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/sdnr/wt-odlux/odlux/framework/src/services/userSessionService.ts b/sdnr/wt-odlux/odlux/framework/src/services/userSessionService.ts new file mode 100644 index 000000000..8d899c4d6 --- /dev/null +++ b/sdnr/wt-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/sdnr/wt-odlux/odlux/framework/src/services/userdataService.ts b/sdnr/wt-odlux/odlux/framework/src/services/userdataService.ts new file mode 100644 index 000000000..53de8e1c3 --- /dev/null +++ b/sdnr/wt-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/sdnr/wt-odlux/odlux/framework/src/store/applicationStore.ts b/sdnr/wt-odlux/odlux/framework/src/store/applicationStore.ts new file mode 100644 index 000000000..cbe8c20da --- /dev/null +++ b/sdnr/wt-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/sdnr/wt-odlux/odlux/framework/src/styles/att.ts b/sdnr/wt-odlux/odlux/framework/src/styles/att.ts new file mode 100644 index 000000000..2d54590c9 --- /dev/null +++ b/sdnr/wt-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/sdnr/wt-odlux/odlux/framework/src/utilities/elasticSearch.ts b/sdnr/wt-odlux/odlux/framework/src/utilities/elasticSearch.ts new file mode 100644 index 000000000..e1d37522d --- /dev/null +++ b/sdnr/wt-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/sdnr/wt-odlux/odlux/framework/src/utilities/logLevel.ts b/sdnr/wt-odlux/odlux/framework/src/utilities/logLevel.ts new file mode 100644 index 000000000..a198d98a9 --- /dev/null +++ b/sdnr/wt-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/sdnr/wt-odlux/odlux/framework/src/utilities/withComponents.ts b/sdnr/wt-odlux/odlux/framework/src/utilities/withComponents.ts new file mode 100644 index 000000000..8e460ad4c --- /dev/null +++ b/sdnr/wt-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/sdnr/wt-odlux/odlux/framework/src/utilities/yangHelper.ts b/sdnr/wt-odlux/odlux/framework/src/utilities/yangHelper.ts new file mode 100644 index 000000000..7e77c055c --- /dev/null +++ b/sdnr/wt-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/sdnr/wt-odlux/odlux/framework/src/views/about.tsx b/sdnr/wt-odlux/odlux/framework/src/views/about.tsx new file mode 100644 index 000000000..72358bf84 --- /dev/null +++ b/sdnr/wt-odlux/odlux/framework/src/views/about.tsx @@ -0,0 +1,196 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS 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, + }}; + +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 `; + } + + 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 + + } +
+ } + +
+
+