From 05ef023752abdb4f1e072332496dc7c6eaff8965 Mon Sep 17 00:00:00 2001 From: herbert Date: Sat, 1 Feb 2020 16:00:00 +0100 Subject: SDN-R add updated odlux Updates all odlux framework and app components. Issue-ID: SDNC-1032 Signed-off-by: herbert Change-Id: I13c520489fd40d05b7fd5215f5941af6238e9cae --- sdnr/wt/odlux/README.md | 205 ++-------- sdnr/wt/odlux/apps/apiDemo/pom.xml | 10 +- sdnr/wt/odlux/apps/app-feature/pom.xml | 2 +- sdnr/wt/odlux/apps/configurationApp/pom.xml | 10 +- .../configurationApp/src/actions/deviceActions.ts | 2 +- .../configurationApp/src/components/baseProps.ts | 21 ++ .../src/components/ifWhenTextInput.tsx | 61 +++ .../src/components/uiElementBoolean.tsx | 58 +++ .../src/components/uiElementNumber.tsx | 77 ++++ .../src/components/uiElementReference.tsx | 48 +++ .../src/components/uiElementSelection.tsx | 59 +++ .../src/components/uiElementString.tsx | 84 +++++ .../src/components/uiElementUnion.tsx | 94 +++++ .../configurationApp/src/components/verifyer.ts | 280 ++++++++++++++ .../src/handlers/configurationAppRootHandler.ts | 18 + .../handlers/connectedNetworkElementsHandler.ts | 19 +- .../src/handlers/deviceDescriptionHandler.ts | 20 +- .../src/handlers/valueSelectorHandler.ts | 20 +- .../src/handlers/viewDescriptionHandler.ts | 18 + .../src/models/networkElementConnection.ts | 18 + .../apps/configurationApp/src/models/uiModels.ts | 68 +++- .../odlux/apps/configurationApp/src/models/yang.ts | 20 +- .../configurationApp/src/pluginConfiguration.tsx | 26 +- .../configurationApp/src/services/restServices.ts | 20 +- .../configurationApp/src/services/yangService.ts | 18 + .../src/views/configurationApplication.tsx | 412 ++++++++++++++------- .../src/views/networkElementSelector.tsx | 20 +- .../apps/configurationApp/src/yang/yangParser.ts | 388 ++++++++++++++----- sdnr/wt/odlux/apps/configurationApp/tsconfig.json | 2 +- sdnr/wt/odlux/apps/connectApp/pom.xml | 10 +- .../src/actions/commonNetworkElementsActions.ts | 69 ++-- .../src/actions/networkElementsActions.ts | 4 +- .../src/components/editNetworkElementDialog.tsx | 10 +- .../src/handlers/connectAppRootHandler.ts | 31 +- .../src/handlers/networkElementsHandler.ts | 4 +- .../apps/connectApp/src/models/guiCutTrough.ts | 20 +- .../src/models/networkElementConnection.ts | 18 + .../wt/odlux/apps/connectApp/src/models/panelId.ts | 18 + .../apps/connectApp/src/services/connectService.ts | 10 +- .../apps/connectApp/src/views/connectView.tsx | 12 +- sdnr/wt/odlux/apps/connectApp/webpack.config.js | 10 +- sdnr/wt/odlux/apps/demoApp/pom.xml | 10 +- sdnr/wt/odlux/apps/eventLogApp/pom.xml | 10 +- sdnr/wt/odlux/apps/faultApp/pom.xml | 10 +- sdnr/wt/odlux/apps/helpApp/pom.xml | 10 +- sdnr/wt/odlux/apps/inventoryApp/pom.xml | 10 +- sdnr/wt/odlux/apps/maintenanceApp/pom.xml | 10 +- sdnr/wt/odlux/apps/mediatorApp/pom.xml | 10 +- sdnr/wt/odlux/apps/minimumApp/pom.xml | 10 +- sdnr/wt/odlux/apps/performanceHistoryApp/pom.xml | 10 +- sdnr/wt/odlux/core/features/pom.xml | 2 +- sdnr/wt/odlux/core/installer/pom.xml | 182 ++++----- sdnr/wt/odlux/core/pom.xml | 54 +-- sdnr/wt/odlux/core/provider/pom.xml | 204 +++++----- sdnr/wt/odlux/framework/pom.xml | 314 ++++++++-------- sdnr/wt/odlux/framework/src/app.css | 10 + .../src/components/material-table/index.tsx | 5 +- .../src/components/material-table/tableFilter.tsx | 6 +- .../src/components/material-table/utilities.ts | 26 +- sdnr/wt/odlux/framework/src/views/about.tsx | 77 +++- sdnr/wt/odlux/framework/webpack.config.js | 6 +- sdnr/wt/odlux/pom.xml | 52 +-- sdnr/wt/odlux/yarn.lock | 30 +- 63 files changed, 2397 insertions(+), 975 deletions(-) create mode 100644 sdnr/wt/odlux/apps/configurationApp/src/components/baseProps.ts create mode 100644 sdnr/wt/odlux/apps/configurationApp/src/components/ifWhenTextInput.tsx create mode 100644 sdnr/wt/odlux/apps/configurationApp/src/components/uiElementBoolean.tsx create mode 100644 sdnr/wt/odlux/apps/configurationApp/src/components/uiElementNumber.tsx create mode 100644 sdnr/wt/odlux/apps/configurationApp/src/components/uiElementReference.tsx create mode 100644 sdnr/wt/odlux/apps/configurationApp/src/components/uiElementSelection.tsx create mode 100644 sdnr/wt/odlux/apps/configurationApp/src/components/uiElementString.tsx create mode 100644 sdnr/wt/odlux/apps/configurationApp/src/components/uiElementUnion.tsx create mode 100644 sdnr/wt/odlux/apps/configurationApp/src/components/verifyer.ts diff --git a/sdnr/wt/odlux/README.md b/sdnr/wt/odlux/README.md index 6e02db0e0..6e5d5965b 100644 --- a/sdnr/wt/odlux/README.md +++ b/sdnr/wt/odlux/README.md @@ -1,15 +1,22 @@ # 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 -* Node: 8.10 or higher -* Yarn: 1.12.3 or higher -* Lerna: 3.10.7 or higher +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 or higher +* Java: 8 ## Dev-Environment Installation @@ -25,7 +32,7 @@ You can install these globally or let it be installed by maven due "mvn clean in |-framework ``` - * go to features/sdnr/wt/odlux/apps and create your app: + * 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 \ @@ -40,7 +47,7 @@ You can install these globally or let it be installed by maven due "mvn clean in * 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 your app for development version you can use ```yarn run build``` or ```yarn run build:dev``` * build for karaf with ```mvn clean install``` @@ -63,19 +70,19 @@ You can install these globally or let it be installed by maven due "mvn clean in ### 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 +0 Connect +10 Fault +20 Maintenance +30 Configuration +40 Protection +50 Performance +60 Security +70 Inventory +80 Topology +90 Mediator +100 Help ``` ### blueprint.xml @@ -99,162 +106,6 @@ You can install these globally or let it be installed by maven due "mvn clean in ### pom.xml -``` - - - - - org.onap.ccsdk.parent - odlparent - 1.2.1-SNAPSHOT - - - 4.0.0 - org.onap.ccsdk.features.sdnr.wt - sdnr-wt-odlux-app-demoApp - 0.4.1-SNAPSHOT - bundle - sdnr-wt-odlux-app-demoApp - - - Apache License, Version 2.0 - http://www.apache.org/licenses/LICENSE-2.0 - - - - - ${project.groupId} - sdnr-wt-odlux-core-model - ${project.version} - - - ${project.groupId} - sdnr-wt-odlux-core-provider - ${project.version} - test - - - junit - junit - test - - - - src2/main/java - - - maven-clean-plugin - - - - dist - false - - - node - false - - - node_modules - false - - - ../node_modules - false - - - - - - org.codehaus.mojo - build-helper-maven-plugin - - - add-test-source - generate-test-sources - - add-test-source - - - - src2/test/java - - - - - - - com.github.eirslett - frontend-maven-plugin - 1.8-SNAPSHOT - - - install node and yarn - - install-node-and-yarn - - - initialize - - v8.10.0 - v1.12.3 - - - - yarn build - - yarn - - - run build - - - - - - org.apache.maven.plugins - maven-jar-plugin - - - org.apache.felix - maven-bundle-plugin - true - - - org.onap.ccsdk.features.sdnr.wt.odlux.model.*,com.opensymphony.* - - - - - - - - dist - odlux - - - src2/main/resources - - - src2/test/resources - - - - - - highstreet repo - https://cloud-highstreet-technologies.com/mvn/ - - true - always - - - - -your -``` - -* a 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. - + * 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/apps/apiDemo/pom.xml b/sdnr/wt/odlux/apps/apiDemo/pom.xml index c59a84232..5b6cfa389 100644 --- a/sdnr/wt/odlux/apps/apiDemo/pom.xml +++ b/sdnr/wt/odlux/apps/apiDemo/pom.xml @@ -14,7 +14,7 @@ 0.7.1-SNAPSHOT bundle - ccsdk-features :: ${project.artifactId} + sdnr-wt-odlux-app-apiDemo Apache License, Version 2.0 @@ -111,10 +111,10 @@ - de.jacks-it-lab - frontend-maven-plugin - 1.7.2 - + de.jacks-it-lab + frontend-maven-plugin + 1.7.2 + install node and yarn diff --git a/sdnr/wt/odlux/apps/app-feature/pom.xml b/sdnr/wt/odlux/apps/app-feature/pom.xml index 8888a7590..d95101681 100644 --- a/sdnr/wt/odlux/apps/app-feature/pom.xml +++ b/sdnr/wt/odlux/apps/app-feature/pom.xml @@ -26,7 +26,7 @@ 0.7.1-SNAPSHOT feature - ccsdk-features :: ${project.artifactId} + ccsdk-features :: ${project.artifactId} :: feature Apache License, Version 2.0 diff --git a/sdnr/wt/odlux/apps/configurationApp/pom.xml b/sdnr/wt/odlux/apps/configurationApp/pom.xml index 305630d4d..9681555db 100644 --- a/sdnr/wt/odlux/apps/configurationApp/pom.xml +++ b/sdnr/wt/odlux/apps/configurationApp/pom.xml @@ -14,7 +14,7 @@ 0.7.1-SNAPSHOT bundle - ccsdk-features :: ${project.artifactId} + sdnr-wt-odlux-app-configurationApp Apache License, Version 2.0 @@ -103,10 +103,10 @@ - de.jacks-it-lab - frontend-maven-plugin - 1.7.2 - + de.jacks-it-lab + frontend-maven-plugin + 1.7.2 + install node and yarn diff --git a/sdnr/wt/odlux/apps/configurationApp/src/actions/deviceActions.ts b/sdnr/wt/odlux/apps/configurationApp/src/actions/deviceActions.ts index fc0665325..5b4498df8 100644 --- a/sdnr/wt/odlux/apps/configurationApp/src/actions/deviceActions.ts +++ b/sdnr/wt/odlux/apps/configurationApp/src/actions/deviceActions.ts @@ -202,7 +202,7 @@ export const updateViewActionAsyncCreator = (vPath: string) => async (dispatch: if (ind === 0) { defaultNS = namespace }; - viewElement = viewSpecification.elements[property]; + viewElement = viewSpecification.elements[property] || viewSpecification.elements[`${namespace}:${property}`]; if (!viewElement) throw Error("Property [" + property + "] does not exist."); if (viewElement.isList && !key) { diff --git a/sdnr/wt/odlux/apps/configurationApp/src/components/baseProps.ts b/sdnr/wt/odlux/apps/configurationApp/src/components/baseProps.ts new file mode 100644 index 000000000..ec49191ce --- /dev/null +++ b/sdnr/wt/odlux/apps/configurationApp/src/components/baseProps.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 { ViewElement } from "../models/uiModels"; + +export type baseProps = { value: ViewElement, inputValue: string, readOnly: boolean, disabled: boolean, onChange(newValue: string): void }; \ No newline at end of file diff --git a/sdnr/wt/odlux/apps/configurationApp/src/components/ifWhenTextInput.tsx b/sdnr/wt/odlux/apps/configurationApp/src/components/ifWhenTextInput.tsx new file mode 100644 index 000000000..67358885c --- /dev/null +++ b/sdnr/wt/odlux/apps/configurationApp/src/components/ifWhenTextInput.tsx @@ -0,0 +1,61 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + * ============LICENSE_END========================================================================== + */ + +import { ViewElementBase } from "models/uiModels"; +import { TextField, InputAdornment, Input, Tooltip, Divider, IconButton, InputBase, Paper, makeStyles, Theme, createStyles, FormControl, InputLabel, FormHelperText } from "@material-ui/core"; +import * as React from 'react'; +import { faAdjust } from "@fortawesome/free-solid-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { InputProps } from "@material-ui/core/Input"; + +const useStyles = makeStyles((theme: Theme) => + createStyles({ + iconDark: { + color: '#ff8800' + }, + iconLight: { + color: 'orange' + }, + padding: { + paddingLeft: 10, + paddingRight: 10 + }, + }), +); + +type ifwhenProps = { element: ViewElementBase, id: any, label: any, style: any, helperText: string, error: boolean, toogleTooltip(value: boolean): void, [x: string]: any }; + +export const IfWhenTextInput = (props: ifwhenProps) => { + + const { element, toogleTooltip, id, label, helperText: errorText, error, style, ...otherProps } = props; + const classes = useStyles(); + + + const ifFeature = element.ifFeature ? props.toogleTooltip(false)} onMouseOut={e => props.toogleTooltip(true)} title={element.ifFeature}> : null; + const whenFeature = element.when ? ( props.toogleTooltip(false)} onMouseOut={e => props.toogleTooltip(true)} title={element.when}> + ) : null; + + return ( + + {label} + {ifFeature}{whenFeature}} /> + {errorText} + + + ); +} \ No newline at end of file diff --git a/sdnr/wt/odlux/apps/configurationApp/src/components/uiElementBoolean.tsx b/sdnr/wt/odlux/apps/configurationApp/src/components/uiElementBoolean.tsx new file mode 100644 index 000000000..cc141ee35 --- /dev/null +++ b/sdnr/wt/odlux/apps/configurationApp/src/components/uiElementBoolean.tsx @@ -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 { ViewElementBoolean } from "../models/uiModels"; +import * as React from "react" +import { MenuItem, FormHelperText, Select, FormControl, InputLabel } from "@material-ui/core"; +import { baseProps } from "./baseProps"; + +type booleanInputProps = baseProps; + +export const UiElementBoolean = (props: booleanInputProps) => { + + const element = props.value as ViewElementBoolean; + + let error = ""; + const value = String(props.inputValue).toLowerCase(); + if (element.mandatory && value !== "true" && value !== "false") { + error = "Error"; + } + return (!props.readOnly || element.id != null + ? ( + {element.label} + + {error} + ) + : null + ); +} \ No newline at end of file diff --git a/sdnr/wt/odlux/apps/configurationApp/src/components/uiElementNumber.tsx b/sdnr/wt/odlux/apps/configurationApp/src/components/uiElementNumber.tsx new file mode 100644 index 000000000..cf462052e --- /dev/null +++ b/sdnr/wt/odlux/apps/configurationApp/src/components/uiElementNumber.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 { ViewElementNumber } from "models/uiModels"; +import { Tooltip, InputAdornment } from "@material-ui/core"; +import * as React from 'react'; +import { baseProps } from "./baseProps"; +import { IfWhenTextInput } from "./ifWhenTextInput"; +import { checkRange } from "./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) => { + + if (data.trim().length > 0) { + 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."); + } + } else { + setError(false); + setHelperText(""); + } + + props.onChange(data); + } + + return ( + + setTooltipVisibility(val)} + spellCheck={false} autoFocus margin="dense" + id={element.id} label={element.label} type="text" value={props.inputValue} + style={{ width: 485, marginLeft: 20, marginRight: 20 }} + onChange={(e: any) => { 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/apps/configurationApp/src/components/uiElementReference.tsx b/sdnr/wt/odlux/apps/configurationApp/src/components/uiElementReference.tsx new file mode 100644 index 000000000..2760eee50 --- /dev/null +++ b/sdnr/wt/odlux/apps/configurationApp/src/components/uiElementReference.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========================================================================== + */ + +import React from 'react'; +import { Tooltip, Button, FormControl, Theme, createStyles, makeStyles } from '@material-ui/core'; + +import { ViewElement } from '../models/uiModels'; + +const useStyles = makeStyles((theme: Theme) => createStyles({ + button: { + "justifyContent": "left" + }, +})); + +type UIElementReferenceProps = { + element: ViewElement; + disabled: boolean; + onOpenReference(element: ViewElement): void; +}; + +export const UIElementReference: React.FC = (props) => { + const classes = useStyles(); + const { element } = props; + return ( + + + + + + ); +} \ No newline at end of file diff --git a/sdnr/wt/odlux/apps/configurationApp/src/components/uiElementSelection.tsx b/sdnr/wt/odlux/apps/configurationApp/src/components/uiElementSelection.tsx new file mode 100644 index 000000000..d46076338 --- /dev/null +++ b/sdnr/wt/odlux/apps/configurationApp/src/components/uiElementSelection.tsx @@ -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 * as React from 'react'; +import { baseProps } from './baseProps'; +import { ViewElementSelection } from '../models/uiModels' +import { FormControl, InputLabel, Select, FormHelperText, MenuItem } from '@material-ui/core'; + + + +type selectionProps = baseProps; + +export const UiElementSelection = (props: selectionProps) => { + + const element = props.value as ViewElementSelection; + + let error = ""; + const value = String(props.inputValue).toLowerCase(); + if (element.mandatory && !!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/apps/configurationApp/src/components/uiElementString.tsx b/sdnr/wt/odlux/apps/configurationApp/src/components/uiElementString.tsx new file mode 100644 index 000000000..43c60b502 --- /dev/null +++ b/sdnr/wt/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 { Tooltip, TextField } from "@material-ui/core"; +import { ViewElementString } from "../models/uiModels"; +import * as React from "react" +import { baseProps } from "./baseProps"; +import { IfWhenTextInput } from "./ifWhenTextInput"; +import { checkRange, checkPattern } from "./verifyer"; + +type stringEntryProps = baseProps & { isKey: boolean }; + +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 ( + + setTooltipVisibility(val)} + spellCheck={false} autoFocus margin="dense" + id={element.id} label={props.isKey ? "🔑 " + element.label : element.label} type="text" value={props.inputValue} + style={{ width: 485, marginLeft: 20, marginRight: 20 }} + onChange={(e: any) => { 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/apps/configurationApp/src/components/uiElementUnion.tsx b/sdnr/wt/odlux/apps/configurationApp/src/components/uiElementUnion.tsx new file mode 100644 index 000000000..dc158c05a --- /dev/null +++ b/sdnr/wt/odlux/apps/configurationApp/src/components/uiElementUnion.tsx @@ -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 * as React from 'react' +import { baseProps } from './baseProps'; +import { Tooltip } from '@material-ui/core'; +import { IfWhenTextInput } from './ifWhenTextInput'; +import { ViewElementUnion, isViewElementString, isViewElementNumber, isViewElementObject, ViewElementNumber } from '../models/uiModels'; +import { checkRange, checkPattern } from './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) => { + + debugger; + 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 + setTooltipVisibility(val)} + spellCheck={false} autoFocus margin="dense" + id={element.id} label={props.isKey ? "🔑 " + element.label : element.label} type="text" value={props.inputValue} + onChange={(e: any) => { 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/apps/configurationApp/src/components/verifyer.ts b/sdnr/wt/odlux/apps/configurationApp/src/components/verifyer.ts new file mode 100644 index 000000000..0a95cd8ca --- /dev/null +++ b/sdnr/wt/odlux/apps/configurationApp/src/components/verifyer.ts @@ -0,0 +1,280 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + * ============LICENSE_END========================================================================== + */ + +import { Expression, 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 = ""; + + +export function checkRange(element: ViewElementNumber | ViewElementString, data: number): string { + + //let test1: Operator = { operation: "AND", arguments: [{ operation: "OR", arguments: [{ operation: "AND", arguments: [new RegExp("^z", "g"), new RegExp("z$", "g")] }, new RegExp("^abc", "g"), new RegExp("^123", "g")] }, new RegExp("^def", "g"), new RegExp("^ppp", "g"), new RegExp("^aaa", "g")] }; + //let test1: Operator = { operation: "AND", arguments: [{ operation: "OR", arguments: [{ operation: "AND", arguments: [{ min: -5, max: 10 }, { min: -30, max: -20 }] }, { min: 8, max: 15 }] }] }; + //let test1: Operator = { operation: "OR", arguments: [{ operation: "OR", arguments: [{ min: -50, max: -40 }] }, { min: -30, max: -20 }, { min: 8, max: 15 }] }; + //let test1: Operator = { operation: "AND", arguments: [{ operation: "OR", arguments: [{ min: -5, max: 10 }, { min: 17, max: 23 }] }] }; + + const number = data; + + var 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 ""; +} + +function isYangRange(val: YangRange | Operator): val is YangRange { + return (val as YangRange).min !== undefined; +} + +function isYangOperator(val: YangRange | Operator): val is Operator { + return (val as Operator).operation !== undefined; +} + +function getRangeErrorMessagesRecursively(value: Operator, data: number): string[] { + let currentItteration: string[] = []; + console.log(value); + + // itterate 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) { + currentItteration.push(`${value.operation.toLocaleLowerCase()} ${errorMessageMiddleMinPart} ${min}`); + } else if (max != undefined) { + currentItteration.push(`${value.operation.toLocaleLowerCase()} ${errorMessageMiddleMaxPart} ${max}`); + + } + + } else if (isYangOperator(element)) { + + //get errormessages from expression + const result = getRangeErrorMessagesRecursively(element, data); + if (result.length === 0) { + isNumberCorrect = true; + } + currentItteration = currentItteration.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") { + + currentItteration.splice(0, currentItteration.length); + break; + } + } + + return currentItteration; +} + +function getRangeErrorMessages(value: Operator, data: number): string { + + const currentItteration = getRangeErrorMessagesRecursively(value, data); + + // build complete error message from found parts + let errormessage = ""; + if (currentItteration.length > 1) { + + currentItteration.forEach((element, index) => { + if (index === 0) { + errormessage = createStartMessage(element); + } else if (index === currentItteration.length - 1) { + errormessage += ` ${element}${errorMessageEnd}`; + } else { + errormessage += `, ${element}` + } + }); + } else if (currentItteration.length == 1) { + errormessage = `${createStartMessage(currentItteration[0])}${errorMessageEnd}`; + } + + return errormessage; +} + +function 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}`; +} + +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 } +} + +function 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; +} + +function isPatternValid(value: Operator, data: string): boolean { + + + // get all regex + const result = getRegexRecursively(value, data); + console.log(value); + + + 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; + + } +} + +function isRegExp(val: RegExp | Operator): val is RegExp { + return (val as RegExp).source !== undefined; +} + +function isRegExpOperator(val: RegExp | Operator): val is Operator { + return (val as Operator).operation !== undefined; +} \ No newline at end of file diff --git a/sdnr/wt/odlux/apps/configurationApp/src/handlers/configurationAppRootHandler.ts b/sdnr/wt/odlux/apps/configurationApp/src/handlers/configurationAppRootHandler.ts index 04b63d39b..1af699a6b 100644 --- a/sdnr/wt/odlux/apps/configurationApp/src/handlers/configurationAppRootHandler.ts +++ b/sdnr/wt/odlux/apps/configurationApp/src/handlers/configurationAppRootHandler.ts @@ -1,3 +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 { combineActionHandler } from '../../../../framework/src/flux/middleware'; import { IConnectedNetworkElementsState, connectedNetworkElementsActionHandler } from './connectedNetworkElementsHandler'; diff --git a/sdnr/wt/odlux/apps/configurationApp/src/handlers/connectedNetworkElementsHandler.ts b/sdnr/wt/odlux/apps/configurationApp/src/handlers/connectedNetworkElementsHandler.ts index 6a68242eb..e6b808b80 100644 --- a/sdnr/wt/odlux/apps/configurationApp/src/handlers/connectedNetworkElementsHandler.ts +++ b/sdnr/wt/odlux/apps/configurationApp/src/handlers/connectedNetworkElementsHandler.ts @@ -1,3 +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 { createExternal, IExternalTableState } from '../../../../framework/src/components/material-table/utilities'; import { createSearchDataHandler } from '../../../../framework/src/utilities/elasticSearch'; @@ -16,4 +34,3 @@ export const { // set value action, to change a value } = createExternal(connectedNetworkElementsSearchHandler, appState => appState.configuration.connectedNetworkElements); - \ No newline at end of file diff --git a/sdnr/wt/odlux/apps/configurationApp/src/handlers/deviceDescriptionHandler.ts b/sdnr/wt/odlux/apps/configurationApp/src/handlers/deviceDescriptionHandler.ts index 3cc27aa95..408399da4 100644 --- a/sdnr/wt/odlux/apps/configurationApp/src/handlers/deviceDescriptionHandler.ts +++ b/sdnr/wt/odlux/apps/configurationApp/src/handlers/deviceDescriptionHandler.ts @@ -1,3 +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 { Module } from "../models/yang"; import { ViewSpecification } from "../models/uiModels"; import { IActionHandler } from "../../../../framework/src/flux/action"; @@ -25,6 +43,6 @@ export const deviceDescriptionHandler: IActionHandler = modules: action.modules, views: action.views }; - } + } return state; }; diff --git a/sdnr/wt/odlux/apps/configurationApp/src/handlers/valueSelectorHandler.ts b/sdnr/wt/odlux/apps/configurationApp/src/handlers/valueSelectorHandler.ts index c9e7dd2cc..9af640f16 100644 --- a/sdnr/wt/odlux/apps/configurationApp/src/handlers/valueSelectorHandler.ts +++ b/sdnr/wt/odlux/apps/configurationApp/src/handlers/valueSelectorHandler.ts @@ -1,3 +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 { IActionHandler } from "../../../../framework/src/flux/action"; import { ViewSpecification } from "../models/uiModels"; import { EnableValueSelector, SetSelectedValue, UpdateDeviceDescription, SetCollectingSelectionData, UpdatViewDescription } from "../actions/deviceActions"; @@ -23,7 +41,7 @@ export const valueSelectorHandler: IActionHandler = (state if (action instanceof SetCollectingSelectionData) { state = { ...state, - collectingData: action.busy, + collectingData: action.busy, }; } else if (action instanceof EnableValueSelector) { state = { diff --git a/sdnr/wt/odlux/apps/configurationApp/src/handlers/viewDescriptionHandler.ts b/sdnr/wt/odlux/apps/configurationApp/src/handlers/viewDescriptionHandler.ts index 48155ee1e..c1b3350b2 100644 --- a/sdnr/wt/odlux/apps/configurationApp/src/handlers/viewDescriptionHandler.ts +++ b/sdnr/wt/odlux/apps/configurationApp/src/handlers/viewDescriptionHandler.ts @@ -1,3 +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 { IActionHandler } from "../../../../framework/src/flux/action"; import { UpdatViewDescription } from "../actions/deviceActions"; diff --git a/sdnr/wt/odlux/apps/configurationApp/src/models/networkElementConnection.ts b/sdnr/wt/odlux/apps/configurationApp/src/models/networkElementConnection.ts index 2575500a3..88f70181c 100644 --- a/sdnr/wt/odlux/apps/configurationApp/src/models/networkElementConnection.ts +++ b/sdnr/wt/odlux/apps/configurationApp/src/models/networkElementConnection.ts @@ -1,3 +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 NetworkElementConnection = { id?: string; nodeId: string; diff --git a/sdnr/wt/odlux/apps/configurationApp/src/models/uiModels.ts b/sdnr/wt/odlux/apps/configurationApp/src/models/uiModels.ts index 441d1281d..7b41c3845 100644 --- a/sdnr/wt/odlux/apps/configurationApp/src/models/uiModels.ts +++ b/sdnr/wt/odlux/apps/configurationApp/src/models/uiModels.ts @@ -1,3 +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 ViewElementBase = { "id": string; "label": string; @@ -15,7 +33,7 @@ export type ViewElementBase = { // https://tools.ietf.org/html/rfc7950#section-9.8 export type ViewElementBinary = ViewElementBase & { "uiType": "binary"; - "length"?: number; // number of octets + "length"?: Expression; // number of octets } // https://tools.ietf.org/html/rfc7950#section-9.7.4 @@ -29,16 +47,17 @@ export type ViewElementBits = ViewElementBase & { // https://tools.ietf.org/html/rfc7950#section-9 export type ViewElementString = ViewElementBase & { "uiType": "string"; - "pattern"?: string[]; - "length"?: string; + "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; + "min": number; + "max": number; + "range"?: Expression; "units"?: string; "format"?: string; "fDigits"?: number; @@ -85,6 +104,16 @@ export type ViewElementReference = ViewElementBase & { "ref": (currentPath: string) => ViewElement | null; } +export type ViewElementUnion = ViewElementBase & { + "uiType": "union"; + "elements": ViewElement[]; +} + +export type ViewElementChoise = ViewElementBase & { + "uiType": "choise"; + "cases": { [name: string]: { id: string, label: string, description?: string, elements: { [name: string]: ViewElement } } }; +} + export type ViewElement = | ViewElementBits | ViewElementBinary @@ -94,14 +123,16 @@ export type ViewElement = | ViewElementObject | ViewElementList | ViewElementSelection - | ViewElementReference; + | ViewElementReference + | ViewElementUnion + | ViewElementChoise; export const isViewElementString = (viewElement: ViewElement): viewElement is ViewElementString => { return viewElement && viewElement.uiType === "string"; } export const isViewElementNumber = (viewElement: ViewElement): viewElement is ViewElementNumber => { - return viewElement && viewElement.uiType === "number" ; + return viewElement && viewElement.uiType === "number"; } export const isViewElementBoolean = (viewElement: ViewElement): viewElement is ViewElementBoolean => { @@ -128,6 +159,15 @@ export const isViewElementReference = (viewElement: ViewElement): viewElement is return viewElement && viewElement.uiType === "reference"; } +export const isViewElementUnion = (viewElement: ViewElement): viewElement is ViewElementUnion => { + return viewElement && viewElement.uiType === "union"; +} + +export const isViewElementChoise = (viewElement: ViewElement): viewElement is ViewElementChoise => { + return viewElement && viewElement.uiType === "choise"; +} + + export type ViewSpecification = { "id": string; "name": string; @@ -140,3 +180,17 @@ export type ViewSpecification = { "elements": { [name: string]: ViewElement }; 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/apps/configurationApp/src/models/yang.ts b/sdnr/wt/odlux/apps/configurationApp/src/models/yang.ts index 57edf803f..11eb44d92 100644 --- a/sdnr/wt/odlux/apps/configurationApp/src/models/yang.ts +++ b/sdnr/wt/odlux/apps/configurationApp/src/models/yang.ts @@ -1,3 +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 { ViewElement, ViewSpecification } from "./uiModels"; export type Token = { @@ -33,7 +51,7 @@ export type Module = { namespace?: string; prefix?: string; identities: { [name: string]: Identity }; - revisions: { [version: string]: Revision } ; + revisions: { [version: string]: Revision }; imports: { [prefix: string]: string }; features: { [feature: string]: { description?: string } }; typedefs: { [type: string]: ViewElement }; diff --git a/sdnr/wt/odlux/apps/configurationApp/src/pluginConfiguration.tsx b/sdnr/wt/odlux/apps/configurationApp/src/pluginConfiguration.tsx index 7fd3a97f9..0cab7b793 100644 --- a/sdnr/wt/odlux/apps/configurationApp/src/pluginConfiguration.tsx +++ b/sdnr/wt/odlux/apps/configurationApp/src/pluginConfiguration.tsx @@ -1,3 +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 * as React from "react"; import { withRouter, RouteComponentProps, Route, Switch, Redirect } from 'react-router-dom'; @@ -32,7 +50,7 @@ const ConfigurationApplicationRouteAdapter = connect(mapProps, mapDisp)((props: currentNodeId = undefined; currentVirtualPath = undefined; } - },[]); + }, []); if (props.location.pathname !== lastUrl) { // ensure the asynchronus update will only be called once per path lastUrl = props.location.pathname; @@ -59,9 +77,9 @@ const ConfigurationApplicationRouteAdapter = connect(mapProps, mapDisp)((props: const App = withRouter((props: RouteComponentProps) => ( - - - + + + )); diff --git a/sdnr/wt/odlux/apps/configurationApp/src/services/restServices.ts b/sdnr/wt/odlux/apps/configurationApp/src/services/restServices.ts index 061be05ec..d0ed03a8d 100644 --- a/sdnr/wt/odlux/apps/configurationApp/src/services/restServices.ts +++ b/sdnr/wt/odlux/apps/configurationApp/src/services/restServices.ts @@ -1,3 +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 { requestRest, requestRestExt } from "../../../../framework/src/services/restService"; import { convertPropertyNames, replaceHyphen } from "../../../../framework/src/utilities/yangHelper"; @@ -32,7 +50,7 @@ class RestService { public setConfigData(path: string, data: any) { return requestRestExt<{ [key: string]: any }>(path, { method: "PUT", body: JSON.stringify(data) }); } - } +} export const restService = new RestService(); export default restService; \ No newline at end of file diff --git a/sdnr/wt/odlux/apps/configurationApp/src/services/yangService.ts b/sdnr/wt/odlux/apps/configurationApp/src/services/yangService.ts index 17a4e43a7..3dc3a8634 100644 --- a/sdnr/wt/odlux/apps/configurationApp/src/services/yangService.ts +++ b/sdnr/wt/odlux/apps/configurationApp/src/services/yangService.ts @@ -1,3 +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========================================================================== + */ + type YangInfo = [string, (string | null | undefined)]; const cache: { [path: string]: string } = { diff --git a/sdnr/wt/odlux/apps/configurationApp/src/views/configurationApplication.tsx b/sdnr/wt/odlux/apps/configurationApp/src/views/configurationApplication.tsx index 24a4af8b2..9c92ceb6b 100644 --- a/sdnr/wt/odlux/apps/configurationApp/src/views/configurationApplication.tsx +++ b/sdnr/wt/odlux/apps/configurationApp/src/views/configurationApplication.tsx @@ -16,7 +16,7 @@ * ============LICENSE_END========================================================================== */ -import * as React from 'react'; +import React from 'react'; import { RouteComponentProps, withRouter } from 'react-router-dom'; import { WithStyles, withStyles, createStyles, Theme } from '@material-ui/core/styles'; @@ -26,11 +26,12 @@ import { IApplicationStoreState } from "../../../../framework/src/store/applicat import MaterialTable, { ColumnModel, ColumnType, MaterialTableCtorType } from "../../../../framework/src/components/material-table"; import { Loader } from "../../../../framework/src/components/material-ui/loader"; -import { SetSelectedValue, splitVPath, updateDataActionAsyncCreator } from "../actions/deviceActions"; -import { ViewSpecification, isViewElementString, isViewElementNumber, isViewElementBoolean, isViewElementObjectOrList, isViewElementSelection } from "../models/uiModels"; +import { SetSelectedValue, splitVPath, updateDataActionAsyncCreator, updateViewActionAsyncCreator } from "../actions/deviceActions"; +import { ViewSpecification, isViewElementString, isViewElementNumber, isViewElementBoolean, isViewElementObjectOrList, isViewElementSelection, isViewElementChoise, ViewElement, ViewElementChoise, isViewElementUnion } from "../models/uiModels"; import Fab from '@material-ui/core/Fab'; import AddIcon from '@material-ui/icons/Add'; +import ArrowBack from '@material-ui/icons/ArrowBack'; import RemoveIcon from '@material-ui/icons/RemoveCircleOutline'; import SaveIcon from '@material-ui/icons/Save'; import EditIcon from '@material-ui/icons/Edit'; @@ -38,7 +39,7 @@ import Tooltip from "@material-ui/core/Tooltip"; import TextField from "@material-ui/core/TextField"; import FormControl from "@material-ui/core/FormControl"; import IconButton from "@material-ui/core/IconButton"; -import Button from "@material-ui/core/Button"; + import InputAdornment from "@material-ui/core/InputAdornment"; import InputLabel from "@material-ui/core/InputLabel"; import Select from "@material-ui/core/Select"; @@ -47,6 +48,13 @@ import Breadcrumbs from "@material-ui/core/Breadcrumbs"; import Link from "@material-ui/core/Link"; import FormHelperText from '@material-ui/core/FormHelperText'; +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'; + const styles = (theme: Theme) => createStyles({ header: { "display": "flex", @@ -97,6 +105,16 @@ const styles = (theme: Theme) => createStyles({ }, }, }, + section: { + padding: "15px", + borderBottom: `2px solid ${theme.palette.divider}`, + }, + viewElements: { + width: 485, marginLeft: 20, marginRight: 20 + }, + verificationElements: { + width: 485, marginLeft: 20, marginRight: 20 + } }); const mapProps = (state: IApplicationStoreState) => ({ @@ -115,6 +133,7 @@ const mapProps = (state: IApplicationStoreState) => ({ 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)), }); const SelectElementTable = MaterialTable as MaterialTableCtorType<{ [key: string]: any }>; @@ -126,6 +145,7 @@ type ConfigurationApplicationComponentState = { editMode: boolean; canEdit: boolean; viewData: { [key: string]: any } | null; + choises: { [path: string]: { selectedCase: string, data: { [property: string]: any } } }; } const OldProps = Symbol("OldProps"); @@ -134,27 +154,61 @@ class ConfigurationApplicationComponent extends React.Component { + const elm = nextProps.viewSpecification.elements[cur]; + if (isViewElementChoise(elm)) { + const caseKeys = Object.keys(elm.cases); + + // find the right case for this choise, 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 nextProps.viewData[caseElmElm.label] != null || nextProps.viewData[caseElmElm.id] != null; + }); + }) || 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 (nextProps.viewData[dataElm.label] !== undefined) { + dataAcc[dataElm.label] = nextProps.viewData[dataElm.label]; + } else if (nextProps.viewData[dataElm.id] !== undefined) { + dataAcc[dataElm.id] = nextProps.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 } } }) || {} } return state; } @@ -174,123 +228,203 @@ class ConfigurationApplicationComponent extends React.Component { - const elements = viewSpecification.elements; - return ( - Object.keys(elements).sort((a, b) => { - const vsA = elements[a]; - const vsB = elements[b]; - if (keyProperty) { - // if (vsA.label === vsB.label) return 0; - if (vsA.label === keyProperty) return -1; - if (vsB.label === keyProperty) return +1; - } + 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)); + if (isViewElementSelection(uiElement)) { + + return { this.changeValueFor(uiElement.id, e) }} + /> + + } else if (isViewElementBoolean(uiElement)) { + return { this.changeValueFor(uiElement.id, e) }} /> + + } else if (isViewElementString(uiElement)) { + return { this.changeValueFor(uiElement.id, e) }} /> + + } else if (isViewElementNumber(uiElement)) { + return { this.changeValueFor(uiElement.id, e) }} /> + } else if (isViewElementUnion(uiElement)) { + return { this.changeValueFor(uiElement.id, e) }} /> + } + else { + if (process.env.NODE_ENV !== "production") { + console.error(`Unknown element type - ${(uiElement as any).uiType} in ${(uiElement as any).id}.`) + } + return null; + } + }; - if (vsA.uiType === vsB.uiType) return 0; - if (vsA.uiType !== "object" && vsB.uiType !== "object") return 0; - if (vsA.uiType === "object") return +1; - return -1; - }).map(key => { - const uiElement = elements[key]; - const isKey = (uiElement.label === keyProperty); - const canEdit = editMode && (isNew || (uiElement.config && !isKey)); - if (isViewElementSelection(uiElement)) { - let error = "" - const value = String(viewData[uiElement.id]).toLowerCase(); - if (uiElement.mandatory && !!value) { - error = "Error"; - } - return (canEdit || viewData[uiElement.id] != null - ? ( - {uiElement.label} - - {error} - ) - : null - ); - } else if (isViewElementBoolean(uiElement)) { - let error = "" - const value = String(viewData[uiElement.id]).toLowerCase(); - if (uiElement.mandatory && value !== "true" && value !== "false") { - error = "Error"; - } - return (canEdit || viewData[uiElement.id] != null - ? ( - {uiElement.label} - - {error} - ) - : null - ); - } else if (isViewElementString(uiElement)) { - return ( - - { this.changeValueFor(uiElement.id, e.target.value) }} - /> - - ); - } else if (isViewElementNumber(uiElement)) { - return ( - - {uiElement.units} : undefined }} spellCheck={false} autoFocus margin="dense" - id={uiElement.id} label={uiElement.label} type="text" value={viewData[uiElement.id] == null ? '' : viewData[uiElement.id]} - style={{ width: 485, marginLeft: 20, marginRight: 20 }} - onChange={(e) => { this.changeValueFor(uiElement.id, e.target.value) }} - /> - - ); - } else if (isViewElementObjectOrList(uiElement)) { - return ( - - - - - - ); - } else { - if (process.env.NODE_ENV !== "production") { - console.error(`Unknown type - ${(uiElement as any).uiType} in ${(uiElement as any).id}.`) + // 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 renderUIChoise = (uiElement: ViewElementChoise, viewData: { [key: string]: any }, keyProperty: string | undefined, editMode: boolean, isNew: boolean) => { + const isKey = (uiElement.label === keyProperty); + + const currentChoise = this.state.choises[uiElement.id]; + const currentCase = currentChoise && uiElement.cases[currentChoise.selectedCase]; + + const canEdit = editMode && (isNew || (uiElement.config && !isKey)); + if (isViewElementChoise(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 Choise

} - return null; + + ); + } 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, 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 (isViewElementChoise(elm)) { + acc.choises.push(elm); + } else { + acc.elements.push(elm); + } + return acc; + }, { elements: [] as ViewElement[], references: [] as ViewElement[], choises: [] as ViewElementChoise[] }); + + 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.choises.length > 0 + ? ( +
+ {sections.choises.map(element => this.renderUIChoise(element, viewData, keyProperty, editMode, isNew))} +
+ ) : null + } + ); }; - private renderUIElementList(listSpecification: ViewSpecification, listKeyProperty: string, listData: { [key: string]: any }[]) { + private renderUIViewList(listSpecification: ViewSpecification, listKeyProperty: string, listData: { [key: string]: any }[]) { const listElements = listSpecification.elements; const navigate = (path: string) => { @@ -381,11 +515,41 @@ class ConfigurationApplicationComponent extends React.Component
+ {this.state.editMode && ( + { + 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 */ !displayAsList && viewSpecification.canEdit && (
{ if (this.state.editMode) { - this.props.onUpdateData(this.props.vPath!, this.state.viewData); + + // ensure only active choises will be contained + const choiseKeys = Object.keys(viewSpecification.elements).filter(elmKey => isViewElementChoise(viewSpecification.elements[elmKey])); + const elementsToRemove = choiseKeys.reduce((acc, cur) => { + const choise = viewSpecification.elements[cur] as ViewElementChoise; + const selectedCase = this.state.choises[cur].selectedCase; + Object.keys(choise.cases).forEach(caseKey => { + if (caseKey === selectedCase) return; + const caseElements = choise.cases[caseKey].elements; + Object.keys(caseElements).forEach(caseElementKey => { + acc.push(caseElements[caseElementKey]); + }); + }); + return acc; + }, [] as ViewElement[]); + + const viewData = this.state.viewData; + const resultingViewData = 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 }); + + this.props.onUpdateData(this.props.vPath!, resultingViewData); } this.setState({ editMode: !editMode }); }}> @@ -431,10 +595,10 @@ class ConfigurationApplicationComponent extends React.Component - { this.renderBreadCrumps() } - { displayAsList && viewData instanceof Array - ? this.renderUIElementList(viewSpecification, keyProperty!, viewData) - : this.renderUIElement(viewSpecification, viewData!, keyProperty, editMode, isNew) + {this.renderBreadCrumps()} + {displayAsList && viewData instanceof Array + ? this.renderUIViewList(viewSpecification, keyProperty!, viewData) + : this.renderUIView(viewSpecification, viewData!, keyProperty, editMode, isNew) }
); diff --git a/sdnr/wt/odlux/apps/configurationApp/src/views/networkElementSelector.tsx b/sdnr/wt/odlux/apps/configurationApp/src/views/networkElementSelector.tsx index 6fd5c8cf0..8155becbb 100644 --- a/sdnr/wt/odlux/apps/configurationApp/src/views/networkElementSelector.tsx +++ b/sdnr/wt/odlux/apps/configurationApp/src/views/networkElementSelector.tsx @@ -1,3 +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 * as React from 'react'; import { RouteComponentProps, withRouter } from 'react-router-dom'; @@ -29,7 +47,7 @@ class NetworkElementSelectorComponent extends React.Component { this.props.history.push(`${ this.props.match.path }/${row.nodeId}`) }} columns={[ + { this.props.history.push(`${this.props.match.path}/${row.nodeId}`) }} columns={[ { property: "nodeId", title: "Name", type: ColumnType.text }, { property: "isRequired", title: "Required ?", type: ColumnType.boolean }, { property: "host", title: "Host", type: ColumnType.text }, diff --git a/sdnr/wt/odlux/apps/configurationApp/src/yang/yangParser.ts b/sdnr/wt/odlux/apps/configurationApp/src/yang/yangParser.ts index c7ab5e4a3..49c2b9be7 100644 --- a/sdnr/wt/odlux/apps/configurationApp/src/yang/yangParser.ts +++ b/sdnr/wt/odlux/apps/configurationApp/src/yang/yangParser.ts @@ -1,6 +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========================================================================== + */ + import { Token, Statement, Module, Identity } from "../models/yang"; -import { ViewSpecification, ViewElement, isViewElementObjectOrList, ViewElementBase, isViewElementReference } from "../models/uiModels"; +import { ViewSpecification, ViewElement, isViewElementObjectOrList, ViewElementBase, isViewElementReference, ViewElementChoise, ViewElementBinary, ViewElementString, isViewElementString, isViewElementNumber, ViewElementNumber, Expression, YangRange, ViewElementUnion } from "../models/uiModels"; import { yangService } from "../services/yangService"; export const splitVPath = (vPath: string, vPathParser: RegExp): RegExpMatchArray[] => { @@ -238,6 +256,8 @@ class YangLexer { export class YangParser { private _groupingsToResolve: (() => void)[] = []; private _identityToResolve: (() => void)[] = []; + private _unionsToResolve: (() => void)[] = []; + private _modules: { [name: string]: Module } = {}; private _views: ViewSpecification[] = [{ @@ -248,7 +268,9 @@ export class YangParser { parentView: "0", title: "root", elements: {}, - }]; + }]; + + public static ResolveStack = Symbol("ResolveStack"); constructor() { @@ -311,12 +333,12 @@ export class YangParser { const revisions = this.extractNodes(rootStatement, "revision"); module.revisions = { ...module.revisions, - ...revisions.reduce<{ [version: string]: { }}>((acc, version) => { + ...revisions.reduce<{ [version: string]: {} }>((acc, version) => { if (!version.arg) { throw new Error(`Module [${module.name}] has a version w/o version number.`); } const description = this.extractValue(version, "description"); - const reference = this.extractValue(version,"reference"); + const reference = this.extractValue(version, "reference"); acc[version.arg] = { description, reference, @@ -345,10 +367,10 @@ export class YangParser { const imports = this.extractNodes(rootStatement, "import"); module.imports = { ...module.imports, - ...imports.reduce < { [key: string]: string }>((acc, imp) => { + ...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.`); + 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; @@ -389,8 +411,15 @@ export class YangParser { } public postProcess() { - // process all groupings + // execute all post processes like resolving in propper order + this._unionsToResolve.forEach(cb => { + try { cb(); } catch (error) { + console.warn(error.message); + } + }); + + // process all groupings this._groupingsToResolve.forEach(cb => { try { cb(); } catch (error) { console.warn(`Error resolving: [${error.message}]`); @@ -418,7 +447,7 @@ export class YangParser { }); // process Identities - const traverseIdentity = (identities : Identity[]) => { + const traverseIdentity = (identities: Identity[]) => { const result: Identity[] = []; for (let identity of identities) { if (identity.children && identity.children.length > 0) { @@ -438,7 +467,7 @@ export class YangParser { const identity = module.identities[idKey]; if (identity.base != null) { const base = this.resolveIdentity(identity.base, module); - base.children?.push(identity); + base.children ?.push(identity); } else { baseIdentites.push(identity); } @@ -455,7 +484,6 @@ export class YangParser { }); }; - private _nextId = 1; private get nextId() { return this._nextId++; @@ -478,7 +506,7 @@ export class YangParser { private extractTypeDefinitions(statement: Statement, module: Module, currentPath: string): void { const typedefs = this.extractNodes(statement, "typedef"); typedefs && typedefs.forEach(def => { - if (! def.arg) { + if (!def.arg) { throw new Error(`Module: [${module.name}]. Found typefed without name.`); } module.typedefs[def.arg] = this.getViewElement(def, module, 0, currentPath, false); @@ -562,13 +590,14 @@ export class YangParser { // extract conditions const ifFeature = this.extractValue(statement, "if-feature"); const whenCondition = this.extractValue(statement, "when"); + if (whenCondition) console.warn("Found in [" + module.name + "]" + currentPath + " when: " + whenCondition); // 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: [${module.name}]. Found container without name.`); + throw new Error(`Module: [${module.name}]${currentPath}. Found container without name.`); } const [currentView, subViews] = this.extractSubViews(cur, currentId, module, `${currentPath}/${module.name}:${cur.arg}`); elements.push({ @@ -589,11 +618,11 @@ export class YangParser { if (lists && lists.length > 0) { subViews.push(...lists.reduce((acc, cur) => { if (!cur.arg) { - throw new Error(`Module: [${module.name}]. Found list without name.`); + throw new Error(`Module: [${module.name}]${currentPath}. Found list without name.`); } const key = this.extractValue(cur, "key") || undefined; if (config && !key) { - throw new Error(`Module: [${module.name}]. Found configurable list without key.`); + throw new Error(`Module: [${module.name}]${currentPath}. Found configurable list without key.`); } const [currentView, subViews] = this.extractSubViews(cur, currentId, module, `${currentPath}/${module.name}:${cur.arg}`); elements.push({ @@ -635,10 +664,70 @@ export class YangParser { const choiceStms = this.extractNodes(statement, "choice"); if (choiceStms && choiceStms.length > 0) { - for (let i = 0; i < choiceStms.length; ++i) { - const cases = this.extractNodes(choiceStms[i], "case"); - console.warn(`Choice found ${choiceStms[i].arg}::${cases.map(c => c.arg).join(";")}`, choiceStms[i]); - } + elements.push(...choiceStms.reduce((accChoise, curChoise) => { + if (!curChoise.arg) { + throw new Error(`Module: [${module.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(curChoise, "case"); + if (caseStms && caseStms.length > 0) { + cases.push(...caseStms.reduce((accCase, curCase) => { + if (!curCase.arg) { + throw new Error(`Module: [${module.name}]${currentPath}/${curChoise.arg}. Found case without name.`); + } + const description = this.extractValue(curCase, "description") || undefined; + const [caseView, caseSubViews] = this.extractSubViews(curCase, parentId, module, `${currentPath}/${module.name}:${curChoise.arg}`); + subViews.push(caseView, ...caseSubViews); + + const caseDef: { id: string, label: string, description?: string, elements: { [name: string]: ViewElement } } = { + id: parentId === 0 ? `${module.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 [choiseView, choiseSubViews] = this.extractSubViews(curChoise, parentId, module, `${currentPath}/${module.name}:${curChoise.arg}`); + subViews.push(choiseView, ...choiseSubViews); + cases.push(...Object.keys(choiseView.elements).reduce((accElm, curElm) => { + const elm = choiseView.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 description = this.extractValue(curChoise, "description") || undefined; + const configValue = this.extractValue(curChoise, "config"); + const config = configValue == null ? true : configValue.toLocaleLowerCase() !== "false"; + + const mandatory = this.extractValue(curChoise, "mandatory") === "true" || false; + + const element: ViewElementChoise = { + uiType: "choise", + id: parentId === 0 ? `${module.name}:${curChoise.arg}` : curChoise.arg, + label: curChoise.arg, + config: config, + mandatory: mandatory, + description: description, + cases: cases.reduce((acc, cur) => { + acc[cur.id] = cur; + return acc; + }, {} as { [name: string]: { id: string, label: string, description?: string, elements: { [name: string]: ViewElement } } }) + }; + + accChoise.push(element); + return accChoise; + }, [])); } const rpcs = this.extractNodes(statement, "rpc"); @@ -707,17 +796,74 @@ export class YangParser { 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 rangeMatch = this.extractValue(cur, "range", /^(\d+)\.\.(\d+)/) || 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 => { + const [minStr, maxStr] = r.split('..'); + const minValue = Number(minStr); + const maxValue = Number(maxStr); + + 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 => { + const pattern = this.extractNodes(this.extractNodes(cur, "type")[0]!, "pattern").map(p => p.arg!).filter(p => !!p).map(p => `^${p}$`); + 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) { @@ -729,7 +875,7 @@ export class YangParser { } const element: ViewElementBase = { - id: parentId === 0 ? `${module.name}:${cur.arg}`: cur.arg, + id: parentId === 0 ? `${module.name}:${cur.arg}` : cur.arg, label: cur.arg, config: config, mandatory: mandatory, @@ -739,10 +885,12 @@ export class YangParser { }; if (type === "string") { + const length = extractRange(0, +18446744073709551615, "length"); return ({ ...element, uiType: "string", - pattern: this.extractNodes(this.extractNodes(cur, "type")[0]!, "pattern").map(p => p.arg!).filter(p => !!p), + length: length.expression, + pattern: extractPattern(), }); } else if (type === "boolean") { return ({ @@ -750,106 +898,109 @@ export class YangParser { uiType: "boolean" }); } else if (type === "uint8") { + const range = extractRange(0, +255); return ({ ...element, uiType: "number", - min: rangeMatch ? Number(rangeMatch[0]) : 0, - max: rangeMatch ? Number(rangeMatch[1]) : +255, + 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", - min: rangeMatch ? Number(rangeMatch[0]) : 0, - max: rangeMatch ? Number(rangeMatch[1]) : +65535, + 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", - min: rangeMatch ? Number(rangeMatch[0]) : 0, - max: rangeMatch ? Number(rangeMatch[1]) : +4294967295, + 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", - min: rangeMatch ? Number(rangeMatch[0]) : 0, - max: rangeMatch ? Number(rangeMatch[1]) : +18446744073709551615, + 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", - min: rangeMatch ? Number(rangeMatch[0]) : -128, - max: rangeMatch ? Number(rangeMatch[1]) : +127, + 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", - min: rangeMatch ? Number(rangeMatch[0]) : -32768, - max: rangeMatch ? Number(rangeMatch[1]) : +32767, + 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", - min: rangeMatch ? Number(rangeMatch[0]) : -2147483648, - max: rangeMatch ? Number(rangeMatch[1]) : +2147483647, + 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", - min: rangeMatch ? Number(rangeMatch[0]) : 0, - max: rangeMatch ? Number(rangeMatch[1]) : +18446744073709551615, - units: this.extractValue(cur, "units") || undefined, - format: this.extractValue(cur, "format") || undefined, - }); - } else if (type === "decimal16") { - return ({ - ...element, - uiType: "number", - min: rangeMatch ? Number(rangeMatch[0]) : 0, - max: rangeMatch ? Number(rangeMatch[1]) : +18446744073709551615, + range: range.expression, + min: range.min, + max: range.max, units: this.extractValue(cur, "units") || undefined, format: this.extractValue(cur, "format") || undefined, - fDigits: Number(this.extractValue(this.extractNodes(cur, "type")[0]!, "fraction-digits")) || -1 - }); - } else if (type === "decimal32") { - return ({ - ...element, - uiType: "number", - min: rangeMatch ? Number(rangeMatch[0]) : 0, - max: rangeMatch ? Number(rangeMatch[1]) : +18446744073709551615, - units: this.extractValue(cur, "units") || undefined, - format: this.extractValue(cur, "format") || undefined, - fDigits: Number(this.extractValue(this.extractNodes(cur, "type")[0]!, "fraction-digits")) || -1 }); } 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", - min: rangeMatch ? Number(rangeMatch[0]) : 0, - max: rangeMatch ? Number(rangeMatch[1]) : +18446744073709551615, + fDigits: fDigits, + range: range.expression, + min: range.min, + max: range.max, units: this.extractValue(cur, "units") || undefined, format: this.extractValue(cur, "format") || undefined, - fDigits: Number(this.extractValue(this.extractNodes(cur, "type")[0]!, "fraction-digits")) || -1 }); } else if (type === "enumeration") { const typeNode = this.extractNodes(cur, "type")[0]!; @@ -881,7 +1032,7 @@ export class YangParser { } const refPath = this.resolveReferencePath(vPath, module); const resolve = this.resolveReference.bind(this); - const res : ViewElement = { + const res: ViewElement = { ...element, uiType: "reference", referencePath: refPath, @@ -912,7 +1063,7 @@ export class YangParser { options: [] }; this._identityToResolve.push(() => { - const identity : Identity = this.resolveIdentity(base, module); + const identity: Identity = this.resolveIdentity(base, module); if (!identity) { throw new Error(`Module: [${module.name}][${currentPath}][${cur.arg}]. Could not resolve identity [${base}].`); } @@ -925,7 +1076,7 @@ export class YangParser { description: val.description })); }); - return res ; + return res; } else if (type === "empty") { // todo: ❗ handle empty ⚡ /* 9.11. The empty Built-In Type @@ -939,18 +1090,41 @@ export class YangParser { } else if (type === "union") { // todo: ❗ handle union ⚡ /* 9.12. The union Built-In Type */ - console.warn(`found type: union in [${module.name}][${currentPath}][${element.label}]`); - return { + const typeNode = this.extractNodes(cur, "type")[0]!; + const typeNodes = this.extractNodes(typeNode, "type"); + + const resultingElement = { ...element, - uiType: "string", + 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) => { + 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.`); } @@ -961,25 +1135,60 @@ export class YangParser { }, {}) }; } else if (type === "binary") { - const typeNode = this.extractNodes(cur, "type")[0]!; - const length = Number(this.extractValue(typeNode, "length")); return { ...element, uiType: "binary", - length: length === length ? length : undefined + length: extractRange(0, +18446744073709551615, "length"), }; } else { // not a build in type, have to resolve type - const typeRef = this.resolveType(type, module); + 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)); + } + return ({ ...typeRef, ...element, - description: description + description: description, }) as ViewElement; } } + 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) => { @@ -988,10 +1197,9 @@ export class YangParser { }); } - private resolveReference(vPath: string, currentPath: string) { const vPathParser = /(?:(?:([^\/\[\]\:]+):)?([^\/\[\]]+)(\[[^\]]+\])?)/g // 1 = opt: namespace / 2 = property / 3 = opt: indexPath - let element : ViewElement | null = null; + let element: ViewElement | null = null; let moduleName = ""; const vPathParts = splitVPath(vPath, vPathParser).map(p => ({ ns: p[1], property: p[2], ind: p[3] })); @@ -999,7 +1207,7 @@ export class YangParser { ? splitVPath(currentPath, vPathParser).map(p => ({ ns: p[1], property: p[2], ind: p[3] })) : []; - for (let i = 0; i < vPathParts.length; ++i){ + for (let i = 0; i < vPathParts.length; ++i) { const vPathPart = vPathParts[i]; if (vPathPart.property === "..") { resultPathParts.pop(); @@ -1009,26 +1217,26 @@ export class YangParser { } // resolve element by path - for (let j = 0; j < resultPathParts.length;++j){ + for (let j = 0; j < resultPathParts.length; ++j) { const pathPart = resultPathParts[j]; - if (j===0) { + 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; - 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[`${moduleName}:${pathPart.property}`]; - } else { - element = view.elements[pathPart.property] || view.elements[`${moduleName}:${pathPart.property}`]; - } + element = view.elements[`${moduleName}:${pathPart.property}`]; } else { - throw new Error("Could not resolve reference.\r\n" + vPath); + element = view.elements[pathPart.property] || view.elements[`${moduleName}:${pathPart.property}`]; } - if (!element) throw new Error("Could not resolve path [" + pathPart.property + "] in ["+ currentPath +"] \r\n" + vPath); + } 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); + } return element; } diff --git a/sdnr/wt/odlux/apps/configurationApp/tsconfig.json b/sdnr/wt/odlux/apps/configurationApp/tsconfig.json index b0c9b424d..c95005660 100644 --- a/sdnr/wt/odlux/apps/configurationApp/tsconfig.json +++ b/sdnr/wt/odlux/apps/configurationApp/tsconfig.json @@ -4,7 +4,7 @@ "outDir": "./dist", "sourceMap": true, "forceConsistentCasingInFileNames": true, - "allowSyntheticDefaultImports": false, + "allowSyntheticDefaultImports": true, "allowUnreachableCode": false, "allowUnusedLabels": false, "noFallthroughCasesInSwitch": true, diff --git a/sdnr/wt/odlux/apps/connectApp/pom.xml b/sdnr/wt/odlux/apps/connectApp/pom.xml index cfc7720c8..619c02a2d 100644 --- a/sdnr/wt/odlux/apps/connectApp/pom.xml +++ b/sdnr/wt/odlux/apps/connectApp/pom.xml @@ -14,7 +14,7 @@ 0.7.1-SNAPSHOT bundle - ccsdk-features :: ${project.artifactId} + sdnr-wt-odlux-app-connectApp Apache License, Version 2.0 @@ -103,10 +103,10 @@
- de.jacks-it-lab - frontend-maven-plugin - 1.7.2 - + de.jacks-it-lab + frontend-maven-plugin + 1.7.2 + install node and yarn diff --git a/sdnr/wt/odlux/apps/connectApp/src/actions/commonNetworkElementsActions.ts b/sdnr/wt/odlux/apps/connectApp/src/actions/commonNetworkElementsActions.ts index bf4778b5b..a6d81c10c 100644 --- a/sdnr/wt/odlux/apps/connectApp/src/actions/commonNetworkElementsActions.ts +++ b/sdnr/wt/odlux/apps/connectApp/src/actions/commonNetworkElementsActions.ts @@ -23,7 +23,7 @@ import { Action } from '../../../../framework/src/flux/action'; import { Dispatch } from '../../../../framework/src/flux/store'; import { IApplicationStoreState } from '../../../../framework/src/store/applicationStore'; -import { networkElementsReloadAction } from '../handlers/networkElementsHandler'; +import { networkElementsReloadAction, networkElementsReloadActionAsync } from '../handlers/networkElementsHandler'; import { connectionStatusLogReloadAction } from '../handlers/connectionStatusLogHandler'; import { PanelId } from '../models/panelId'; @@ -38,7 +38,7 @@ export class SetPanelAction extends Action { } export class AddWebUriList extends Action { - constructor(public element: guiCutThrough[], public knownElements: string[]) { + constructor(public searchedElements: guiCutThrough[], public notSearchedElements: string[], public newlySearchedElements?: string[]) { super(); } } @@ -53,58 +53,65 @@ 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 = (dispatcher: Dispatch) => (networkElements: NetworkElementConnection[], knownElements: string[]) => { +export const findWebUrisForGuiCutThroughAsyncAction = (networkElements: NetworkElementConnection[]) => 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 nodeIds = networkElements.map(element => { return element.id as string }); + const { connect: { guiCutThrough } } = getState(); - if (knownElements.length > 0) { + let notConnectedElements: string[] = []; + let elementsToSearch: string[] = []; + let prevFoundElements: string[] = []; - let elementsToSearch: string[] = []; - nodeIds.forEach(element => { - // find index of nodeId - const index = knownElements.indexOf(element); + networkElements.forEach(item => { + const id = item.id as string; + if (item.status === "Connected") { - // if element dosen't exist, add it to list - if (index === -1) { - elementsToSearch.push(element) + // element is connected and is added to search list, if it doesn't exist already + const exists = guiCutThrough.searchedElements.filter(element => element.nodeId === id).length > 0; + if (!exists) { + elementsToSearch.push(id); + + //element was found previously, but not searched for a weburi + 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 (!guiCutThrough.notSearchedElements.includes(id)) { + notConnectedElements.push(item.id as string); } - }); - - // if new elements were found, search for weburi - if (elementsToSearch.length > 0) { - const foundWebUris = connectService.getAllWebUriExtensionsForNetworkElementListAsync(elementsToSearch); - foundWebUris.then(result => { - dispatcher(new AddWebUriList(result, elementsToSearch)); - isBusy = false; - }) - - } else { - isBusy = false; } + }); - } else { - connectService.getAllWebUriExtensionsForNetworkElementListAsync(nodeIds).then(result => { - dispatcher(new AddWebUriList(result, nodeIds)); - isBusy = false; - }) + if (elementsToSearch.length > 0 || notConnectedElements.length > 0) { + const result = await connectService.getAllWebUriExtensionsForNetworkElementListAsync(elementsToSearch); + dispatcher(new AddWebUriList(result, notConnectedElements, prevFoundElements)); } + isBusy = false; } export const setPanelAction = (panelId: PanelId) => { return new SetPanelAction(panelId); } -export const updateCurrentViewAsyncAction = () => (dispatch: Dispatch, getState: () => IApplicationStoreState) => { +export const updateCurrentViewAsyncAction = () => async (dispatch: Dispatch, getState: () => IApplicationStoreState) => { const { connect: { currentOpenPanel } } = getState(); if (currentOpenPanel === "NetworkElements") { - return dispatch(networkElementsReloadAction); + return await dispatch(networkElementsReloadActionAsync); } else { return dispatch(connectionStatusLogReloadAction); diff --git a/sdnr/wt/odlux/apps/connectApp/src/actions/networkElementsActions.ts b/sdnr/wt/odlux/apps/connectApp/src/actions/networkElementsActions.ts index 1a86f94b1..1e569a196 100644 --- a/sdnr/wt/odlux/apps/connectApp/src/actions/networkElementsActions.ts +++ b/sdnr/wt/odlux/apps/connectApp/src/actions/networkElementsActions.ts @@ -43,7 +43,7 @@ export const editNetworkElementAsyncActionCreator = (element: UpdateNetworkEleme const res = await connectService.deleteNetworkElement(element); } else { - const res = await connectService.updateNetworkElement(element); + const res = await connectService.updateNetworkElement(element); } dispatch(updateCurrentViewAsyncAction()); dispatch(new AddSnackbarNotification({ message: `Successfully modified [${element.id}]`, options: { variant: 'success' } })); @@ -54,7 +54,7 @@ export const editNetworkElementAsyncActionCreator = (element: UpdateNetworkEleme export const removeNetworkElementAsyncActionCreator = (element: UpdateNetworkElement) => async (dispatch: Dispatch) => { const res = await connectService.deleteNetworkElement(element); await dispatch(unmountNetworkElementAsyncActionCreator(element && element.id)); - dispatch(updateCurrentViewAsyncAction()); + await dispatch(updateCurrentViewAsyncAction()); }; diff --git a/sdnr/wt/odlux/apps/connectApp/src/components/editNetworkElementDialog.tsx b/sdnr/wt/odlux/apps/connectApp/src/components/editNetworkElementDialog.tsx index 1e1f11523..ce7f48cc9 100644 --- a/sdnr/wt/odlux/apps/connectApp/src/components/editNetworkElementDialog.tsx +++ b/sdnr/wt/odlux/apps/connectApp/src/components/editNetworkElementDialog.tsx @@ -60,8 +60,8 @@ const mapDispatch = (dispatcher: IDispatcher) => ({ await dispatcher.dispatch(editNetworkElementAsyncActionCreator(element)); await dispatcher.dispatch(mountNetworkElementAsyncActionCreator(mountElement)); }, - removeNetworkElement: (element: UpdateNetworkElement) => { - dispatcher.dispatch(removeNetworkElementAsyncActionCreator(element)); + removeNetworkElement: async (element: UpdateNetworkElement) => { + await dispatcher.dispatch(removeNetworkElementAsyncActionCreator(element)); dispatcher.dispatch(removeWebUriAction(element.id)); } }); @@ -158,7 +158,7 @@ class EditNetworkElementDialogComponent extends React.Component - {setting.dialogTitle} + {setting.dialogTitle} {setting.dialogDescription} @@ -179,7 +179,7 @@ class EditNetworkElementDialogComponent extends React.Component - -