aboutsummaryrefslogtreecommitdiffstats
path: root/sdnr/wt/odlux/apps/configurationApp/src
diff options
context:
space:
mode:
Diffstat (limited to 'sdnr/wt/odlux/apps/configurationApp/src')
-rw-r--r--sdnr/wt/odlux/apps/configurationApp/src/actions/deviceActions.ts2
-rw-r--r--sdnr/wt/odlux/apps/configurationApp/src/components/baseProps.ts21
-rw-r--r--sdnr/wt/odlux/apps/configurationApp/src/components/ifWhenTextInput.tsx61
-rw-r--r--sdnr/wt/odlux/apps/configurationApp/src/components/uiElementBoolean.tsx58
-rw-r--r--sdnr/wt/odlux/apps/configurationApp/src/components/uiElementNumber.tsx77
-rw-r--r--sdnr/wt/odlux/apps/configurationApp/src/components/uiElementReference.tsx48
-rw-r--r--sdnr/wt/odlux/apps/configurationApp/src/components/uiElementSelection.tsx59
-rw-r--r--sdnr/wt/odlux/apps/configurationApp/src/components/uiElementString.tsx84
-rw-r--r--sdnr/wt/odlux/apps/configurationApp/src/components/uiElementUnion.tsx94
-rw-r--r--sdnr/wt/odlux/apps/configurationApp/src/components/verifyer.ts280
-rw-r--r--sdnr/wt/odlux/apps/configurationApp/src/handlers/configurationAppRootHandler.ts18
-rw-r--r--sdnr/wt/odlux/apps/configurationApp/src/handlers/connectedNetworkElementsHandler.ts19
-rw-r--r--sdnr/wt/odlux/apps/configurationApp/src/handlers/deviceDescriptionHandler.ts20
-rw-r--r--sdnr/wt/odlux/apps/configurationApp/src/handlers/valueSelectorHandler.ts20
-rw-r--r--sdnr/wt/odlux/apps/configurationApp/src/handlers/viewDescriptionHandler.ts18
-rw-r--r--sdnr/wt/odlux/apps/configurationApp/src/models/networkElementConnection.ts18
-rw-r--r--sdnr/wt/odlux/apps/configurationApp/src/models/uiModels.ts68
-rw-r--r--sdnr/wt/odlux/apps/configurationApp/src/models/yang.ts20
-rw-r--r--sdnr/wt/odlux/apps/configurationApp/src/pluginConfiguration.tsx26
-rw-r--r--sdnr/wt/odlux/apps/configurationApp/src/services/restServices.ts20
-rw-r--r--sdnr/wt/odlux/apps/configurationApp/src/services/yangService.ts18
-rw-r--r--sdnr/wt/odlux/apps/configurationApp/src/views/configurationApplication.tsx412
-rw-r--r--sdnr/wt/odlux/apps/configurationApp/src/views/networkElementSelector.tsx20
-rw-r--r--sdnr/wt/odlux/apps/configurationApp/src/yang/yangParser.ts388
24 files changed, 1637 insertions, 232 deletions
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 ? <Tooltip onMouseMove={e => props.toogleTooltip(false)} onMouseOut={e => props.toogleTooltip(true)} title={element.ifFeature}><InputAdornment position="start"><FontAwesomeIcon icon={faAdjust} className={classes.iconDark}></FontAwesomeIcon></InputAdornment></Tooltip> : null;
+ const whenFeature = element.when ? (<Tooltip className={classes.padding} onMouseMove={e => props.toogleTooltip(false)} onMouseOut={e => props.toogleTooltip(true)} title={element.when}>
+ <InputAdornment className={classes.padding} position="end"><FontAwesomeIcon icon={faAdjust} className={classes.iconLight}></FontAwesomeIcon></InputAdornment></Tooltip>) : null;
+
+ return (
+ <FormControl error={error} style={style}>
+ <InputLabel htmlFor={id} >{label}</InputLabel>
+ <Input {...otherProps} id={id} endAdornment={<div>{ifFeature}{whenFeature}</div>} />
+ <FormHelperText>{errorText}</FormHelperText>
+ </FormControl>
+
+ );
+} \ 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
+ ? (<FormControl style={{ width: 485, marginLeft: 20, marginRight: 20 }}>
+ <InputLabel htmlFor={`select-${element.id}`} >{element.label}</InputLabel>
+ <Select
+ required={!!element.mandatory}
+ error={!!error}
+ onChange={(e) => { props.onChange(e.target.value as any) }}
+ readOnly={props.readOnly}
+ disabled={props.disabled}
+ value={value}
+ inputProps={{
+ name: element.id,
+ id: `select-${element.id}`,
+ }}
+ >
+ <MenuItem value={'true'}>{element.trueValue || 'True'}</MenuItem>
+ <MenuItem value={'false'}>{element.falseValue || 'False'}</MenuItem>
+
+ </Select>
+ <FormHelperText>{error}</FormHelperText>
+ </FormControl>)
+ : 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 (
+ <Tooltip title={isTooltipVisible ? element.description || '' : ''}>
+ <IfWhenTextInput element={element} toogleTooltip={(val: boolean) => 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 ? <InputAdornment position="start">{element.units}</InputAdornment> : undefined}
+ />
+ </Tooltip>
+ );
+} \ 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<UIElementReferenceProps> = (props) => {
+ const classes = useStyles();
+ const { element } = props;
+ return (
+ <FormControl key={element.id} style={{ width: 485, marginLeft: 20, marginRight: 20 }}>
+ <Tooltip title={element.description || ''}>
+ <Button className={classes.button} color="secondary" disabled={props.disabled} onClick={() => {
+ props.onOpenReference(element);
+ }}>{element.label}</Button>
+ </Tooltip>
+ </FormControl>
+ );
+} \ 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
+ ? (<FormControl style={{ width: 485, marginLeft: 20, marginRight: 20 }}>
+ <InputLabel htmlFor={`select-${element.id}`} >{element.label}</InputLabel>
+ <Select
+ required={!!element.mandatory}
+ error={!!error}
+ onChange={(e) => { props.onChange(e.target.value as string) }}
+ readOnly={props.readOnly}
+ disabled={props.disabled}
+ value={value.toString().toLowerCase()}
+ inputProps={{
+ name: element.id,
+ id: `select-${element.id}`,
+ }}
+ >
+ {element.options.map(option => (<MenuItem key={option.key} title={option.description} value={option.key}>{option.key}</MenuItem>))}
+ </Select>
+ <FormHelperText>{error}</FormHelperText>
+ </FormControl>)
+ : 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 (
+ <Tooltip title={isTooltipVisible ? element.description || '' : ''}>
+ <IfWhenTextInput element={element} toogleTooltip={(val: boolean) => 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}
+ />
+ </Tooltip>
+ );
+} \ 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 <Tooltip title={isTooltipVisible ? element.description || '' : ''}>
+ <IfWhenTextInput element={element} toogleTooltip={(val: boolean) => 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}
+ />
+ </Tooltip>;
+} \ 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<YangRange> = { 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<YangRange> = { operation: "AND", arguments: [{ operation: "OR", arguments: [{ operation: "AND", arguments: [{ min: -5, max: 10 }, { min: -30, max: -20 }] }, { min: 8, max: 15 }] }] };
+ //let test1: Operator<YangRange> = { operation: "OR", arguments: [{ operation: "OR", arguments: [{ min: -50, max: -40 }] }, { min: -30, max: -20 }, { min: 8, max: 15 }] };
+ //let test1: Operator<YangRange> = { 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<YangRange>): val is YangRange {
+ return (val as YangRange).min !== undefined;
+}
+
+function isYangOperator(val: YangRange | Operator<YangRange>): val is Operator<YangRange> {
+ return (val as Operator<YangRange>).operation !== undefined;
+}
+
+function getRangeErrorMessagesRecursively(value: Operator<YangRange>, 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<YangRange>, 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<RegExp> | 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<RegExp>, 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<RegExp>, 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<RegExp>): val is RegExp {
+ return (val as RegExp).source !== undefined;
+}
+
+function isRegExpOperator(val: RegExp | Operator<RegExp>): val is Operator<RegExp> {
+ return (val as Operator<RegExp>).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<NetworkElementConnection>(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<IDeviceDescriptionState> =
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<IValueSelectorState> = (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<YangRange>; // 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<RegExp>;
+ "length"?: Expression<YangRange>;
"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<YangRange>;
"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> =
+ | T
+ | Operator<T>;
+
+export type Operator<T> = {
+ operation: "AND" | "OR";
+ arguments: Expression<T>[];
+} \ 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) => (
<Switch>
- <Route path={`${props.match.url}/:nodeId/*`} component={ ConfigurationApplicationRouteAdapter } />
- <Route path={`${props.match.url}/:nodeId`} component={ ConfigurationApplicationRouteAdapter } />
- <Route path={`${props.match.url}`} component={ NetworkElementSelector } />
+ <Route path={`${props.match.url}/:nodeId/*`} component={ConfigurationApplicationRouteAdapter} />
+ <Route path={`${props.match.url}/:nodeId`} component={ConfigurationApplicationRouteAdapter} />
+ <Route path={`${props.match.url}`} component={NetworkElementSelector} />
<Redirect to={`${props.match.url}`} />
</Switch>
));
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<ConfigurationApp
/**
*
*/
- constructor (props: ConfigurationApplicationComponentProps) {
+ constructor(props: ConfigurationApplicationComponentProps) {
super(props);
this.state = {
isNew: false,
canEdit: false,
editMode: false,
- viewData: null
+ viewData: null,
+ choises: {},
}
}
- static getDerivedStateFromProps(nextProps: ConfigurationApplicationComponentProps, prevState: ConfigurationApplicationComponentState & { [OldProps]: ConfigurationApplicationComponentProps }) {
+ static getDerivedStateFromProps(nextProps: ConfigurationApplicationComponentProps, prevState: ConfigurationApplicationComponentState & { [OldProps]: ConfigurationApplicationComponentProps }) {
if (!prevState || !prevState[OldProps] || (prevState[OldProps].viewData !== nextProps.viewData)) {
- const isNew: boolean = nextProps.vPath?.endsWith("[]") || false;
+ const isNew: boolean = nextProps.vPath ?.endsWith("[]") || false;
const state = {
...prevState,
isNew: isNew,
editMode: isNew,
viewData: nextProps.viewData || null,
[OldProps]: nextProps,
+ choises: nextProps.viewSpecification && Object.keys(nextProps.viewSpecification.elements).reduce((acc, cur) => {
+ 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<ConfigurationApp
});
}
- private renderUIElement = (viewSpecification: ViewSpecification, viewData: { [key: string]: any }, keyProperty: string | undefined, editMode: boolean, isNew: boolean) => {
- 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 <UiElementSelection
+ key={uiElement.id}
+ inputValue={viewData[uiElement.id] || ''}
+ value={uiElement}
+ readOnly={!canEdit}
+ disabled={editMode && !canEdit}
+ onChange={(e) => { this.changeValueFor(uiElement.id, e) }}
+ />
+
+ } else if (isViewElementBoolean(uiElement)) {
+ return <UiElementBoolean
+ key={uiElement.id}
+ inputValue={viewData[uiElement.id] || ''}
+ value={uiElement}
+ readOnly={!canEdit}
+ disabled={editMode && !canEdit}
+ onChange={(e) => { this.changeValueFor(uiElement.id, e) }} />
+
+ } else if (isViewElementString(uiElement)) {
+ return <UiElementString
+ key={uiElement.id}
+ inputValue={viewData[uiElement.id] || ''}
+ value={uiElement}
+ isKey={isKey}
+ readOnly={!canEdit}
+ disabled={editMode && !canEdit}
+ onChange={(e) => { this.changeValueFor(uiElement.id, e) }} />
+
+ } else if (isViewElementNumber(uiElement)) {
+ return <UiElementNumber
+ key={uiElement.id}
+ value={uiElement}
+ inputValue={viewData[uiElement.id] == null ? '' : viewData[uiElement.id]}
+ readOnly={!canEdit}
+ disabled={editMode && !canEdit}
+ onChange={(e) => { this.changeValueFor(uiElement.id, e) }} />
+ } else if (isViewElementUnion(uiElement)) {
+ return <UIElementUnion
+ key={uiElement.id}
+ isKey={false}
+ value={uiElement}
+ inputValue={viewData[uiElement.id] == null ? '' : viewData[uiElement.id]}
+ readOnly={!canEdit}
+ disabled={editMode && !canEdit}
+ onChange={(e) => { 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
- ? (<FormControl key={uiElement.id} style={{ width: 485, marginLeft: 20, marginRight: 20 }}>
- <InputLabel htmlFor={`select-${uiElement.id}`} >{uiElement.label}</InputLabel>
- <Select
- required={!!uiElement.mandatory}
- error={!!error}
- onChange={(e) => { this.changeValueFor(uiElement.id, e.target.value) }}
- readOnly={!canEdit}
- disabled={editMode && !canEdit}
- value={(viewData[uiElement.id] || '').toString().toLowerCase()}
- inputProps={{
- name: uiElement.id,
- id: `select-${uiElement.id}`,
- }}
- >
- {uiElement.options.map(option => (<MenuItem key={option.key} title={option.description} value={option.value}>{option.key}</MenuItem>))}
- </Select>
- <FormHelperText>{error}</FormHelperText>
- </FormControl>)
- : 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
- ? (<FormControl key={uiElement.id} style={{ width: 485, marginLeft: 20, marginRight: 20 }}>
- <InputLabel htmlFor={`select-${uiElement.id}`} >{uiElement.label}</InputLabel>
- <Select
- required={!!uiElement.mandatory}
- error={!!error}
- onChange={(e) => { this.changeValueFor(uiElement.id, e.target.value) }}
- readOnly={!canEdit}
- disabled={editMode && !canEdit}
- value={value}
- inputProps={{
- name: uiElement.id,
- id: `select-${uiElement.id}`,
- }}
- >
- <MenuItem value={'true'}>{uiElement.trueValue || 'True'}</MenuItem>
- <MenuItem value={'false'}>{uiElement.falseValue || 'False'}</MenuItem>
-
- </Select>
- <FormHelperText>{error}</FormHelperText>
- </FormControl>)
- : null
- );
- } else if (isViewElementString(uiElement)) {
- return (
- <Tooltip key={uiElement.id} title={uiElement.description || ''}>
- <TextField InputProps={{ readOnly: !canEdit, disabled: editMode && !canEdit }} spellCheck={false} autoFocus margin="dense"
- id={uiElement.id} label={isKey ? "🔑 " + uiElement.label : uiElement.label} type="text" value={viewData[uiElement.id] || ''}
- style={{ width: 485, marginLeft: 20, marginRight: 20 }}
- onChange={(e) => { this.changeValueFor(uiElement.id, e.target.value) }}
- />
- </Tooltip>
- );
- } else if (isViewElementNumber(uiElement)) {
- return (
- <Tooltip key={uiElement.id} title={uiElement.description || ''}>
- <TextField InputProps={{ readOnly: !canEdit, disabled: editMode && !canEdit, startAdornment: uiElement.units != null ? <InputAdornment position="start">{uiElement.units}</InputAdornment> : 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) }}
- />
- </Tooltip>
- );
- } else if (isViewElementObjectOrList(uiElement)) {
- return (
- <FormControl key={uiElement.id} style={{ width: 485, marginLeft: 20, marginRight: 20 }}>
- <Tooltip title={uiElement.description || ''}>
- <Button className={this.props.classes.leftButton} color="secondary" disabled={this.state.editMode} onClick={() => {
- this.navigate(`/${uiElement.id}`);
- }}>{uiElement.label}</Button>
- </Tooltip>
- </FormControl>
- );
- } 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 (
+ // <FormControl key={uiElement.id} style={{ width: 485, marginLeft: 20, marginRight: 20 }}>
+ // <Tooltip title={uiElement.description || ''}>
+ // <Button className={this.props.classes.leftButton} color="secondary" disabled={this.state.editMode} onClick={() => {
+ // this.navigate(`/${uiElement.id}`);
+ // }}>{uiElement.label}</Button>
+ // </Tooltip>
+ // </FormControl>
+ // );
+ // } 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 (
+ <>
+ <FormControl key={uiElement.id} style={{ width: 485, marginLeft: 20, marginRight: 20 }}>
+ <InputLabel htmlFor={`select-${uiElement.id}`} >{uiElement.label}</InputLabel>
+ <Select
+ required={!!uiElement.mandatory}
+ onChange={(e) => {
+ if (currentChoise.selectedCase === e.target.value) {
+ return; // nothing changed
+ }
+ this.setState({ choises: { ...this.state.choises, [uiElement.id]: { ...this.state.choises[uiElement.id], selectedCase: e.target.value as string } } });
+ }}
+ readOnly={!canEdit}
+ disabled={editMode && !canEdit}
+ value={this.state.choises[uiElement.id].selectedCase}
+ inputProps={{
+ name: uiElement.id,
+ id: `select-${uiElement.id}`,
+ }}
+ >
+ {
+ Object.keys(uiElement.cases).map(caseKey => {
+ const caseElm = uiElement.cases[caseKey];
+ return (
+ <MenuItem key={caseElm.id} title={caseElm.description} value={caseKey}>{caseElm.label}</MenuItem>
+ );
+ })
+ }
+ </Select>
+ </FormControl>
+ {subElements
+ ? Object.keys(subElements).map(elmKey => {
+ const elm = subElements[elmKey];
+ return this.renderUIElement(elm, viewData, keyProperty, editMode, isNew);
+ })
+ : <h3>Invalid Choise</h3>
}
- 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 (
+ <>
+ <div className={classes.section} />
+ {sections.elements.length > 0
+ ? (
+ <div className={classes.section}>
+ {sections.elements.map(element => this.renderUIElement(element, viewData, keyProperty, editMode, isNew))}
+ </div>
+ ) : null
+ }
+ {sections.references.length > 0
+ ? (
+ <div className={classes.section}>
+ {sections.references.map(element => (
+ <UIElementReference key={element.id} element={element} disabled={editMode} onOpenReference={(elm) => { this.navigate(`/${elm.id}`) }} />
+ ))}
+ </div>
+ ) : null
}
- })
+ {sections.choises.length > 0
+ ? (
+ <div className={classes.section}>
+ {sections.choises.map(element => this.renderUIChoise(element, viewData, keyProperty, editMode, isNew))}
+ </div>
+ ) : 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<ConfigurationApp
}
</Breadcrumbs>
</div>
+ {this.state.editMode && (
+ <Fab color="secondary" aria-label="edit" className={this.props.classes.fab} onClick={async () => {
+ this.props.vPath && await this.props.reloadView(this.props.vPath);
+ this.setState({ editMode: false });
+ }} ><ArrowBack /></Fab>
+ ) || null}
{ /* do not show edit if this is a list or it can't be edited */
!displayAsList && viewSpecification.canEdit && (<div>
<Fab color="secondary" aria-label="edit" className={this.props.classes.fab} onClick={() => {
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<ConfigurationApp
return (
<div>
- { 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)
}
</div >
);
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<NetworkElementSele
render() {
return (
- <ConnectedElementTable onHandleClick={(e, row) => { this.props.history.push(`${ this.props.match.path }/${row.nodeId}`) }} columns={[
+ <ConnectedElementTable onHandleClick={(e, row) => { 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<ViewSpecification[]>((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<ViewSpecification[]>((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<ViewElementChoise[]>((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<YangRange> | 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<RegExp> | 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<RegExp> | undefined, length: { expression: Expression<YangRange> | 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<YangRange> | 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;
}