aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAijana Schumann <aijana.schumann@highstreet-technologies.com>2020-12-04 17:40:42 +0100
committerAijana S <aijana.schumann@highstreet-technologies.com>2020-12-07 14:52:03 +0000
commite3ad1d3884cb4c801679e3390088ce17c997f9d1 (patch)
treef9327486345ca79303916c30a8e79f696cbfdd80
parent57c041563b4419b9cb3868507a508e24c5eee04d (diff)
Update ConfigurationApp
Add grouping per yang module Issue-ID: CCSDK-3023 Signed-off-by: Aijana Schumann <aijana.schumann@highstreet-technologies.com> Change-Id: I7fd15d0a7dc982c6d824e679b5a0d1eeaaa2e7a8
-rw-r--r--sdnr/wt/odlux/apps/configurationApp/src/actions/deviceActions.ts56
-rw-r--r--sdnr/wt/odlux/apps/configurationApp/src/components/baseProps.ts2
-rw-r--r--sdnr/wt/odlux/apps/configurationApp/src/components/uiElementReference.tsx2
-rw-r--r--sdnr/wt/odlux/apps/configurationApp/src/components/uiElementString.tsx4
-rw-r--r--sdnr/wt/odlux/apps/configurationApp/src/models/uiModels.ts17
-rw-r--r--sdnr/wt/odlux/apps/configurationApp/src/models/yang.ts8
-rw-r--r--sdnr/wt/odlux/apps/configurationApp/src/services/restServices.ts46
-rw-r--r--sdnr/wt/odlux/apps/configurationApp/src/views/configurationApplication.tsx1339
-rw-r--r--sdnr/wt/odlux/apps/configurationApp/src/yang/yangParser.ts113
-rw-r--r--sdnr/wt/odlux/apps/configurationApp/webpack.config.js26
-rw-r--r--sdnr/wt/odlux/framework/pom.xml2
-rw-r--r--sdnr/wt/odlux/framework/src/assets/version.json5
12 files changed, 935 insertions, 685 deletions
diff --git a/sdnr/wt/odlux/apps/configurationApp/src/actions/deviceActions.ts b/sdnr/wt/odlux/apps/configurationApp/src/actions/deviceActions.ts
index 83134fc92..d7babc156 100644
--- a/sdnr/wt/odlux/apps/configurationApp/src/actions/deviceActions.ts
+++ b/sdnr/wt/odlux/apps/configurationApp/src/actions/deviceActions.ts
@@ -8,8 +8,7 @@ import { DisplayModeType, DisplaySpecification } from '../handlers/viewDescripti
import { restService } from "../services/restServices";
import { YangParser } from "../yang/yangParser";
import { Module } from "../models/yang";
-import { ViewSpecification, ViewElement, isViewElementReference, isViewElementList, isViewElementObjectOrList, isViewElementRpc, isViewElementChoise, ViewElementChoiseCase } from "../models/uiModels";
-import { element } from 'prop-types';
+import { ViewSpecification, ViewElement, isViewElementReference, isViewElementList, isViewElementObjectOrList, isViewElementRpc, isViewElementChoise, ViewElementChoiseCase, ViewElementString } from "../models/uiModels";
export class EnableValueSelector extends Action {
constructor(public listSpecification: ViewSpecification, public listData: any[], public keyProperty: string, public onValueSelected : (value: any) => void ) {
@@ -52,22 +51,26 @@ export const updateNodeIdAsyncActionCreator = (nodeId: string) => async (dispatc
dispatch(new UpdateDeviceDescription("", {}, []));
dispatch(new SetCollectingSelectionData(true));
- const availableCapabilities = await restService.getCapabilitiesByMoutId(nodeId);
+ const { avaliableCapabilities, unavaliableCapabilities } = await restService.getCapabilitiesByMoutId(nodeId);
- if (!availableCapabilities || availableCapabilities.length <= 0) {
+ if (!avaliableCapabilities || avaliableCapabilities.length <= 0) {
throw new Error(`NetworkElement : [${nodeId}] has no capabilities.`);
}
-
- const parser = new YangParser();
-
+
const capParser = /^\(.*\?revision=(\d{4}-\d{2}-\d{2})\)(\S+)$/i;
- for (let i = 0; i < availableCapabilities.length; ++i){
- const capRaw = availableCapabilities[i];
+
+ const parser = new YangParser(unavaliableCapabilities?.map(cap => {
+ const capMatch = cap && capParser.exec(cap.capability);
+ return { capability:capMatch && capMatch[2] || '', failureReason: cap.failureReason };
+ }) || undefined);
+
+ for (let i = 0; i < avaliableCapabilities.length; ++i){
+ const capRaw = avaliableCapabilities[i];
const capMatch = capRaw && capParser.exec(capRaw.capability);
try {
capMatch && await parser.addCapability(capMatch[2], capMatch[1]);
} catch (err) {
- console.error(err);
+ console.error(`Error in ${capMatch && capMatch[2]} ${capMatch && capMatch[1]}`, err);
}
}
@@ -75,6 +78,10 @@ export const updateNodeIdAsyncActionCreator = (nodeId: string) => async (dispatc
dispatch(new SetCollectingSelectionData(false));
+ if (process.env.NODE_ENV === "development" ) {
+ console.log(parser, parser.modules, parser.views);
+ }
+
return dispatch(new UpdateDeviceDescription(nodeId, parser.modules, parser.views));
}
@@ -317,8 +324,33 @@ export const updateViewActionAsyncCreator = (vPath: string) => async (dispatch:
window.setTimeout(() => dispatch(new PushAction(`${vPath}[${refKey.replace(/\//ig, "%2F")}]`)));
}));
} else {
+ // Found a list at root level of a module w/o a refenrece key.
+ dataPath += `?content=config&fields=${encodeURIComponent(viewElement.id)}(${encodeURIComponent(viewElement.key || '')})`;
+ const restResult = (await restService.getConfigData(dataPath));
+ if (restResult && restResult.status === 200 && restResult.data && restResult.data[viewElement.id] ){
+ // spoof the not existing view here
+ const refData = restResult.data[viewElement.id];
+ const refView : ViewSpecification = {
+ id: "-1",
+ canEdit: false,
+ language: "en-US",
+ elements: {
+ [viewElement.key!] : {
+ uiType: "string",
+ config: false,
+ id: viewElement.key,
+ label: viewElement.key,
+ isList: true,
+ } as ViewElementString
+ }
+ };
+ dispatch(new EnableValueSelector(refView, refData, viewElement.key!, (refKey) => {
+ window.setTimeout(() => dispatch(new PushAction(`${vPath}[${refKey.replace(/\//ig, "%2F")}]`)));
+ }));
+ } else {
+ throw new Error("Found a list at root level of a module and could not determine the keys.");
+ }
dispatch(new SetCollectingSelectionData(false));
- throw new Error("Found a list at root level of a module w/o a refenrece key.");
}
return;
}
@@ -392,7 +424,7 @@ export const updateViewActionAsyncCreator = (vPath: string) => async (dispatch:
// extract the list -> key: list
data = extractList
- ? data[viewElement!.label] || [] // if the list is empty, it does not exist
+ ? data[viewElement!.id] || data[viewElement!.label] || [] // if the list is empty, it does not exist
: data;
} else if (viewElement! && viewElement!.uiType === "rpc") {
diff --git a/sdnr/wt/odlux/apps/configurationApp/src/components/baseProps.ts b/sdnr/wt/odlux/apps/configurationApp/src/components/baseProps.ts
index c08f5c9bc..26c3944c9 100644
--- a/sdnr/wt/odlux/apps/configurationApp/src/components/baseProps.ts
+++ b/sdnr/wt/odlux/apps/configurationApp/src/components/baseProps.ts
@@ -23,6 +23,6 @@ export type BaseProps<TValue = string> = {
inputValue: TValue,
readOnly: boolean,
disabled: boolean,
- onChange(newValue: TValue): void,
+ onChange(newValue: TValue): void;
isKey?: boolean
}; \ 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
index b95df1f51..223c4cb25 100644
--- a/sdnr/wt/odlux/apps/configurationApp/src/components/uiElementReference.tsx
+++ b/sdnr/wt/odlux/apps/configurationApp/src/components/uiElementReference.tsx
@@ -38,7 +38,7 @@ export const UIElementReference: React.FC<UIElementReferenceProps> = (props) =>
const { element } = props;
return (
<FormControl key={element.id} style={{ width: 485, marginLeft: 20, marginRight: 20 }}>
- <Tooltip title={element.description || ''}>
+ <Tooltip title={element.description || element.path || ''}>
<Button className={classes.button} aria-label={element.label+'-button'} color="secondary" disabled={props.disabled} onClick={() => {
props.onOpenReference(element);
}}>{`${element.label}`}</Button>
diff --git a/sdnr/wt/odlux/apps/configurationApp/src/components/uiElementString.tsx b/sdnr/wt/odlux/apps/configurationApp/src/components/uiElementString.tsx
index 122f7150a..f87b94f1d 100644
--- a/sdnr/wt/odlux/apps/configurationApp/src/components/uiElementString.tsx
+++ b/sdnr/wt/odlux/apps/configurationApp/src/components/uiElementString.tsx
@@ -23,7 +23,7 @@ import { BaseProps } from "./baseProps";
import { IfWhenTextInput } from "./ifWhenTextInput";
import { checkRange, checkPattern } from "./verifyer";
-type stringEntryProps = BaseProps;
+type stringEntryProps = BaseProps ;
export const UiElementString = (props: stringEntryProps) => {
@@ -81,4 +81,4 @@ export const UiElementString = (props: stringEntryProps) => {
/>
</Tooltip>
);
-}
+} \ No newline at end of file
diff --git a/sdnr/wt/odlux/apps/configurationApp/src/models/uiModels.ts b/sdnr/wt/odlux/apps/configurationApp/src/models/uiModels.ts
index a5a52fc2e..9c03bdf9b 100644
--- a/sdnr/wt/odlux/apps/configurationApp/src/models/uiModels.ts
+++ b/sdnr/wt/odlux/apps/configurationApp/src/models/uiModels.ts
@@ -19,6 +19,8 @@
export type ViewElementBase = {
"id": string;
"label": string;
+ "module": string;
+ "path": string;
"config": boolean;
"ifFeature"?: string;
"when"?: string;
@@ -52,6 +54,14 @@ export type ViewElementString = ViewElementBase & {
"invertMatch"?: true;
}
+// special case derived from
+export type ViewElementDate = ViewElementBase & {
+ "uiType": "date";
+ "pattern"?: Expression<RegExp>;
+ "length"?: Expression<YangRange>;
+ "invertMatch"?: true;
+}
+
// https://tools.ietf.org/html/rfc7950#section-9.3
export type ViewElementNumber = ViewElementBase & {
"uiType": "number";
@@ -134,6 +144,7 @@ export type ViewElement =
| ViewElementBits
| ViewElementBinary
| ViewElementString
+ | ViewElementDate
| ViewElementNumber
| ViewElementBoolean
| ViewElementObject
@@ -145,7 +156,11 @@ export type ViewElement =
| ViewElementRpc;
export const isViewElementString = (viewElement: ViewElement): viewElement is ViewElementString => {
- return viewElement && viewElement.uiType === "string";
+ return viewElement && (viewElement.uiType === "string" || viewElement.uiType === "date");
+}
+
+export const isViewElementDate = (viewElement: ViewElement): viewElement is ViewElementDate => {
+ return viewElement && (viewElement.uiType === "date");
}
export const isViewElementNumber = (viewElement: ViewElement): viewElement is ViewElementNumber => {
diff --git a/sdnr/wt/odlux/apps/configurationApp/src/models/yang.ts b/sdnr/wt/odlux/apps/configurationApp/src/models/yang.ts
index 11eb44d92..e4ab6f59f 100644
--- a/sdnr/wt/odlux/apps/configurationApp/src/models/yang.ts
+++ b/sdnr/wt/odlux/apps/configurationApp/src/models/yang.ts
@@ -17,6 +17,13 @@
*/
import { ViewElement, ViewSpecification } from "./uiModels";
+import { StepLabel } from "@material-ui/core";
+
+export enum ModuleState {
+ stable,
+ instable,
+ unabaliabe,
+}
export type Token = {
name: string;
@@ -50,6 +57,7 @@ export type Module = {
name: string;
namespace?: string;
prefix?: string;
+ state: ModuleState;
identities: { [name: string]: Identity };
revisions: { [version: string]: Revision };
imports: { [prefix: string]: string };
diff --git a/sdnr/wt/odlux/apps/configurationApp/src/services/restServices.ts b/sdnr/wt/odlux/apps/configurationApp/src/services/restServices.ts
index eb2c67c26..239a8e448 100644
--- a/sdnr/wt/odlux/apps/configurationApp/src/services/restServices.ts
+++ b/sdnr/wt/odlux/apps/configurationApp/src/services/restServices.ts
@@ -21,12 +21,50 @@ import { convertPropertyNames, replaceHyphen } from "../../../../framework/src/u
import { NetworkElementConnection } from "../models/networkElementConnection";
+type CapabilityResponse = {
+ "network-topology:node": {
+ "node-id": string,
+ "netconf-node-topology:available-capabilities": {
+ "available-capability": {
+ "capability-origin": string,
+ "capability": string,
+ }[]
+ },
+ "netconf-node-topology:unavailable-capabilities": {
+ "unavailable-capability": {
+ "capability": string,
+ "failure-reason": string,
+ }[]
+ }
+ }[]
+}
+
+type CapabilityAnswer = {
+ avaliableCapabilities: {
+ capabilityOrigin: string,
+ capability: string
+ }[] | null ,
+ unavaliableCapabilities: {
+ failureReason: string,
+ capability: string
+ }[] | null ,
+}
+
class RestService {
- public async getCapabilitiesByMoutId(nodeId: string): Promise<{ "capabilityOrigin": string, "capability": string }[] | null> {
+ public async getCapabilitiesByMoutId(nodeId: string): Promise<CapabilityAnswer> {
const path = `/rests/data/network-topology:network-topology/topology=topology-netconf/node=${nodeId}`;
- const capabilitiesResult = await requestRest<{"network-topology:node": {"node-id": string, "netconf-node-topology:available-capabilities": { "available-capability": { "capability-origin": string, "capability": string }[] }}[] }>(path, { method: "GET" });
- return capabilitiesResult && capabilitiesResult["network-topology:node"] && capabilitiesResult["network-topology:node"].length > 0 &&
- capabilitiesResult["network-topology:node"][0]["netconf-node-topology:available-capabilities"]["available-capability"].map<any>(obj => convertPropertyNames(obj, replaceHyphen)) || null;
+ const capabilitiesResult = await requestRest<CapabilityResponse>(path, { method: "GET" });
+ const avaliableCapabilities = capabilitiesResult && capabilitiesResult["network-topology:node"] && capabilitiesResult["network-topology:node"].length > 0 &&
+ capabilitiesResult["network-topology:node"][0]["netconf-node-topology:available-capabilities"] &&
+ capabilitiesResult["network-topology:node"][0]["netconf-node-topology:available-capabilities"]["available-capability"] &&
+ capabilitiesResult["network-topology:node"][0]["netconf-node-topology:available-capabilities"]["available-capability"].map<any>(obj => convertPropertyNames(obj, replaceHyphen)) || [];
+
+ const unavaliableCapabilities = capabilitiesResult && capabilitiesResult["network-topology:node"] && capabilitiesResult["network-topology:node"].length > 0 &&
+ capabilitiesResult["network-topology:node"][0]["netconf-node-topology:unavailable-capabilities"] &&
+ capabilitiesResult["network-topology:node"][0]["netconf-node-topology:unavailable-capabilities"]["unavailable-capability"] &&
+ capabilitiesResult["network-topology:node"][0]["netconf-node-topology:unavailable-capabilities"]["unavailable-capability"].map<any>(obj => convertPropertyNames(obj, replaceHyphen)) || []
+
+ return { avaliableCapabilities, unavaliableCapabilities };
}
public async getMountedNetworkElementByMountId(nodeId: string): Promise<NetworkElementConnection | null> {
diff --git a/sdnr/wt/odlux/apps/configurationApp/src/views/configurationApplication.tsx b/sdnr/wt/odlux/apps/configurationApp/src/views/configurationApplication.tsx
index 3b1df6f87..45b3081c2 100644
--- a/sdnr/wt/odlux/apps/configurationApp/src/views/configurationApplication.tsx
+++ b/sdnr/wt/odlux/apps/configurationApp/src/views/configurationApplication.tsx
@@ -29,7 +29,7 @@ import { renderObject } from '../../../../framework/src/components/objectDump';
import { DisplayModeType } from '../handlers/viewDescriptionHandler';
import { SetSelectedValue, splitVPath, updateDataActionAsyncCreator, updateViewActionAsyncCreator, removeElementActionAsyncCreator, executeRpcActionAsyncCreator } from "../actions/deviceActions";
-import { ViewSpecification, isViewElementString, isViewElementNumber, isViewElementBoolean, isViewElementObjectOrList, isViewElementSelection, isViewElementChoise, ViewElement, ViewElementChoise, isViewElementUnion, isViewElementRpc, ViewElementRpc, isViewElementEmpty } from "../models/uiModels";
+import { ViewSpecification, isViewElementString, isViewElementNumber, isViewElementBoolean, isViewElementObjectOrList, isViewElementSelection, isViewElementChoise, ViewElement, ViewElementChoise, isViewElementUnion, isViewElementRpc, ViewElementRpc, isViewElementEmpty, isViewElementDate } from "../models/uiModels";
import Fab from '@material-ui/core/Fab';
import AddIcon from '@material-ui/icons/Add';
@@ -45,8 +45,14 @@ import InputLabel from "@material-ui/core/InputLabel";
import Select from "@material-ui/core/Select";
import MenuItem from "@material-ui/core/MenuItem";
import Breadcrumbs from "@material-ui/core/Breadcrumbs";
-import { Button } from '@material-ui/core';
+import Button from '@material-ui/core/Button';
import Link from "@material-ui/core/Link";
+import Accordion from '@material-ui/core/Accordion';
+import AccordionSummary from '@material-ui/core/AccordionSummary';
+import AccordionDetails from '@material-ui/core/AccordionDetails';
+import Typography from '@material-ui/core/Typography';
+import ExpandMoreIcon from '@material-ui/icons/ExpandMore';
+
import { BaseProps } from '../components/baseProps';
import { UIElementReference } from '../components/uiElementReference';
@@ -60,93 +66,104 @@ import { UiElementLeafList } from '../components/uiElementLeafList';
import { useConfirm } from 'material-ui-confirm';
const styles = (theme: Theme) => createStyles({
- header: {
- "display": "flex",
- "justifyContent": "space-between",
- },
- leftButton: {
- "justifyContent": "left"
- },
- outer: {
- "flex": "1",
- "height": "100%",
- "display": "flex",
- "alignItems": "center",
- "justifyContent": "center",
- },
- inner: {
-
- },
- container: {
- "height": "100%",
- "display": "flex",
- "flexDirection": "column",
- },
- "icon": {
- "marginRight": theme.spacing(0.5),
- "width": 20,
- "height": 20,
- },
- "fab": {
- "margin": theme.spacing(1),
- },
- button: {
- margin: 0,
- padding: "6px 6px",
- minWidth: 'unset'
- },
- readOnly: {
- '& label.Mui-focused': {
- color: 'green',
+ header: {
+ "display": "flex",
+ "justifyContent": "space-between",
+ },
+ leftButton: {
+ "justifyContent": "left"
+ },
+ outer: {
+ "flex": "1",
+ "height": "100%",
+ "display": "flex",
+ "alignItems": "center",
+ "justifyContent": "center",
+ },
+ inner: {
+
+ },
+ container: {
+ "height": "100%",
+ "display": "flex",
+ "flexDirection": "column",
+ },
+ "icon": {
+ "marginRight": theme.spacing(0.5),
+ "width": 20,
+ "height": 20,
+ },
+ "fab": {
+ "margin": theme.spacing(1),
+ },
+ button: {
+ margin: 0,
+ padding: "6px 6px",
+ minWidth: 'unset'
},
- '& .MuiInput-underline:after': {
- borderBottomColor: 'green',
+ readOnly: {
+ '& label.Mui-focused': {
+ color: 'green',
+ },
+ '& .MuiInput-underline:after': {
+ borderBottomColor: 'green',
+ },
+ '& .MuiOutlinedInput-root': {
+ '& fieldset': {
+ borderColor: 'red',
+ },
+ '&:hover fieldset': {
+ borderColor: 'yellow',
+ },
+ '&.Mui-focused fieldset': {
+ borderColor: 'green',
+ },
+ },
},
- '& .MuiOutlinedInput-root': {
- '& fieldset': {
- borderColor: 'red',
- },
- '&:hover fieldset': {
- borderColor: 'yellow',
- },
- '&.Mui-focused fieldset': {
- borderColor: 'green',
- },
+ uiView: {
+ overflowY: "auto",
},
- },
- uiView: {
- overflowY: "auto",
- },
- section: {
- padding: "15px",
- borderBottom: `2px solid ${theme.palette.divider}`,
- },
- viewElements: {
- width: 485, marginLeft: 20, marginRight: 20
- },
- verificationElements: {
- width: 485, marginLeft: 20, marginRight: 20
- }
+ section: {
+ padding: "15px",
+ borderBottom: `2px solid ${theme.palette.divider}`,
+ },
+ viewElements: {
+ width: 485, marginLeft: 20, marginRight: 20
+ },
+ verificationElements: {
+ width: 485, marginLeft: 20, marginRight: 20
+ },
+ heading: {
+ fontSize: theme.typography.pxToRem(15),
+ fontWeight: theme.typography.fontWeightRegular,
+ },
+ moduleCollection: {
+ marginTop: "16px",
+ overflow: "auto",
+ },
+ objectReult: {
+ overflow: "auto"
+ }
});
const mapProps = (state: IApplicationStoreState) => ({
- collectingData: state.configuration.valueSelector.collectingData,
- listKeyProperty: state.configuration.valueSelector.keyProperty,
- listSpecification: state.configuration.valueSelector.listSpecification,
- listData: state.configuration.valueSelector.listData,
- vPath: state.configuration.viewDescription.vPath,
- nodeId: state.configuration.deviceDescription.nodeId,
- viewData: state.configuration.viewDescription.viewData,
- outputData: state.configuration.viewDescription.outputData,
- displaySpecification: state.configuration.viewDescription.displaySpecification,
+ collectingData: state.configuration.valueSelector.collectingData,
+ listKeyProperty: state.configuration.valueSelector.keyProperty,
+ listSpecification: state.configuration.valueSelector.listSpecification,
+ listData: state.configuration.valueSelector.listData,
+ vPath: state.configuration.viewDescription.vPath,
+ nodeId: state.configuration.deviceDescription.nodeId,
+ viewData: state.configuration.viewDescription.viewData,
+ outputData: state.configuration.viewDescription.outputData,
+ displaySpecification: state.configuration.viewDescription.displaySpecification,
});
const mapDispatch = (dispatcher: IDispatcher) => ({
- onValueSelected: (value: any) => dispatcher.dispatch(new SetSelectedValue(value)),
- onUpdateData: (vPath: string, data: any) => dispatcher.dispatch(updateDataActionAsyncCreator(vPath, data)),
- reloadView: (vPath: string) => dispatcher.dispatch(updateViewActionAsyncCreator(vPath)),
- removeElement: (vPath: string) => dispatcher.dispatch(removeElementActionAsyncCreator(vPath)),
- executeRpc: (vPath: string, data: any) => dispatcher.dispatch(executeRpcActionAsyncCreator(vPath, data)),
+ onValueSelected: (value: any) => dispatcher.dispatch(new SetSelectedValue(value)),
+ onUpdateData: (vPath: string, data: any) => dispatcher.dispatch(updateDataActionAsyncCreator(vPath, data)),
+ reloadView: (vPath: string) => dispatcher.dispatch(updateViewActionAsyncCreator(vPath)),
+ removeElement: (vPath: string) => dispatcher.dispatch(removeElementActionAsyncCreator(vPath)),
+ executeRpc: (vPath: string, data: any) => dispatcher.dispatch(executeRpcActionAsyncCreator(vPath, data)),
});
const SelectElementTable = MaterialTable as MaterialTableCtorType<{ [key: string]: any }>;
@@ -154,599 +171,653 @@ const SelectElementTable = MaterialTable as MaterialTableCtorType<{ [key: string
type ConfigurationApplicationComponentProps = RouteComponentProps & Connect<typeof mapProps, typeof mapDispatch> & WithStyles<typeof styles>;
type ConfigurationApplicationComponentState = {
- isNew: boolean;
- editMode: boolean;
- canEdit: boolean;
- viewData: { [key: string]: any } | null;
- choises: { [path: string]: { selectedCase: string, data: { [property: string]: any } } };
+ isNew: boolean;
+ editMode: boolean;
+ canEdit: boolean;
+ viewData: { [key: string]: any } | null;
+ choises: { [path: string]: { selectedCase: string, data: { [property: string]: any } } };
}
const OldProps = Symbol("OldProps");
class ConfigurationApplicationComponent extends React.Component<ConfigurationApplicationComponentProps, ConfigurationApplicationComponentState> {
- /**
- *
- */
- constructor(props: ConfigurationApplicationComponentProps) {
- super(props);
-
- this.state = {
- isNew: false,
- canEdit: false,
- editMode: false,
- viewData: null,
- choises: {},
+ /**
+ *
+ */
+ constructor(props: ConfigurationApplicationComponentProps) {
+ super(props);
+
+ this.state = {
+ isNew: false,
+ canEdit: false,
+ editMode: false,
+ viewData: null,
+ choises: {},
+ }
}
- }
-
- private static getChoisesFromElements = (elements: { [name: string]: ViewElement }, viewData: any) => {
- return Object.keys(elements).reduce((acc, cur) => {
- const elm = 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 viewData[caseElmElm.label] !== undefined || viewData[caseElmElm.id] != undefined;
- });
- }) || caseKeys[0];
-
- // extract all data of the active case
- const caseElements = elm.cases[selectedCase].elements;
- const data = Object.keys(caseElements).reduce((dataAcc, dataCur) => {
- const dataElm = caseElements[dataCur];
- if (isViewElementEmpty(dataElm)) {
- dataAcc[dataElm.label] = null;
- } else if (viewData[dataElm.label] !== undefined) {
- dataAcc[dataElm.label] = viewData[dataElm.label];
- } else if (viewData[dataElm.id] !== undefined) {
- dataAcc[dataElm.id] = viewData[dataElm.id];
- }
- return dataAcc;
- }, {} as { [name: string]: any });
-
- acc[elm.id] = {
- selectedCase,
- data,
- };
- }
- return acc;
- }, {} as { [path: string]: { selectedCase: string, data: { [property: string]: any } } }) || {}
- }
-
- static getDerivedStateFromProps(nextProps: ConfigurationApplicationComponentProps, prevState: ConfigurationApplicationComponentState & { [OldProps]: ConfigurationApplicationComponentProps }) {
-
- if (!prevState || !prevState[OldProps] || (prevState[OldProps].viewData !== nextProps.viewData)) {
- const isNew: boolean = nextProps.vPath?.endsWith("[]") || false;
- const state = {
- ...prevState,
- isNew: isNew,
- editMode: isNew,
- viewData: nextProps.viewData || null,
- [OldProps]: nextProps,
- choises: nextProps.displaySpecification.displayMode === DisplayModeType.doNotDisplay
- ? null
- : nextProps.displaySpecification.displayMode === DisplayModeType.displayAsRPC
- ? nextProps.displaySpecification.inputViewSpecification && ConfigurationApplicationComponent.getChoisesFromElements(nextProps.displaySpecification.inputViewSpecification.elements, nextProps.viewData) || []
- : ConfigurationApplicationComponent.getChoisesFromElements(nextProps.displaySpecification.viewSpecification.elements, nextProps.viewData)
- }
- return state;
+
+ private static getChoisesFromElements = (elements: { [name: string]: ViewElement }, viewData: any) => {
+ return Object.keys(elements).reduce((acc, cur) => {
+ const elm = 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 viewData[caseElmElm.label] !== undefined || viewData[caseElmElm.id] != undefined;
+ });
+ }) || caseKeys[0];
+
+ // extract all data of the active case
+ const caseElements = elm.cases[selectedCase].elements;
+ const data = Object.keys(caseElements).reduce((dataAcc, dataCur) => {
+ const dataElm = caseElements[dataCur];
+ if (isViewElementEmpty(dataElm)) {
+ dataAcc[dataElm.label] = null;
+ } else if (viewData[dataElm.label] !== undefined) {
+ dataAcc[dataElm.label] = viewData[dataElm.label];
+ } else if (viewData[dataElm.id] !== undefined) {
+ dataAcc[dataElm.id] = viewData[dataElm.id];
+ }
+ return dataAcc;
+ }, {} as { [name: string]: any });
+
+ acc[elm.id] = {
+ selectedCase,
+ data,
+ };
+ }
+ return acc;
+ }, {} as { [path: string]: { selectedCase: string, data: { [property: string]: any } } }) || {}
}
- return null;
- }
-
- private navigate = (path: string) => {
- this.props.history.push(`${this.props.match.url}${path}`);
- }
-
- private changeValueFor = (property: string, value: any) => {
- this.setState({
- viewData: {
- ...this.state.viewData,
- [property]: value
- }
- });
- }
-
- private collectData = (elements: { [name: string]: ViewElement }) => {
- // ensure only active choises will be contained
- const viewData : { [key: string]: any }= { ...this.state.viewData };
- const choiseKeys = Object.keys(elements).filter(elmKey => isViewElementChoise(elements[elmKey]));
- const elementsToRemove = choiseKeys.reduce((acc, curChoiceKey) => {
- const currentChoice = elements[curChoiceKey] as ViewElementChoise;
- const selectedCase = this.state.choises[curChoiceKey].selectedCase;
- Object.keys(currentChoice.cases).forEach(caseKey => {
- const caseElements = currentChoice.cases[caseKey].elements;
- if (caseKey === selectedCase) {
- Object.keys(caseElements).forEach(caseElementKey => {
- const elm = caseElements[caseElementKey];
- if (isViewElementEmpty(elm)) {
- // insert null for all empty elements
- viewData[elm.id] = null;
+
+ static getDerivedStateFromProps(nextProps: ConfigurationApplicationComponentProps, prevState: ConfigurationApplicationComponentState & { [OldProps]: ConfigurationApplicationComponentProps }) {
+
+ if (!prevState || !prevState[OldProps] || (prevState[OldProps].viewData !== nextProps.viewData)) {
+ const isNew: boolean = nextProps.vPath?.endsWith("[]") || false;
+ const state = {
+ ...prevState,
+ isNew: isNew,
+ editMode: isNew,
+ viewData: nextProps.viewData || null,
+ [OldProps]: nextProps,
+ choises: nextProps.displaySpecification.displayMode === DisplayModeType.doNotDisplay
+ ? null
+ : nextProps.displaySpecification.displayMode === DisplayModeType.displayAsRPC
+ ? nextProps.displaySpecification.inputViewSpecification && ConfigurationApplicationComponent.getChoisesFromElements(nextProps.displaySpecification.inputViewSpecification.elements, nextProps.viewData) || []
+ : ConfigurationApplicationComponent.getChoisesFromElements(nextProps.displaySpecification.viewSpecification.elements, nextProps.viewData)
+ }
+ return state;
+ }
+ return null;
+ }
+
+ private navigate = (path: string) => {
+ this.props.history.push(`${this.props.match.url}${path}`);
+ }
+
+ private changeValueFor = (property: string, value: any) => {
+ this.setState({
+ viewData: {
+ ...this.state.viewData,
+ [property]: value
}
- });
- return;
- };
- Object.keys(caseElements).forEach(caseElementKey => {
- acc.push(caseElements[caseElementKey]);
});
- });
- return acc;
- }, [] as ViewElement[]);
-
- return viewData && Object.keys(viewData).reduce((acc, cur) => {
- if (!elementsToRemove.some(elm => elm.label === cur || elm.id === cur)) {
- acc[cur] = viewData[cur];
- }
- return acc;
- }, {} as { [key: string]: any });
- }
-
- private getEditorForViewElement = (uiElement: ViewElement) : (null | React.ComponentType<BaseProps<any>>) => {
- if (isViewElementEmpty(uiElement)) {
- return null;
- } else if (isViewElementSelection(uiElement)) {
- return UiElementSelection;
- } else if (isViewElementBoolean(uiElement)) {
- return UiElementBoolean;
- } else if (isViewElementString(uiElement)) {
- return UiElementString;
- } else if (isViewElementNumber(uiElement)) {
- return UiElementNumber;
- } else if (isViewElementUnion(uiElement)) {
- return UIElementUnion;
- } else {
- if (process.env.NODE_ENV !== "production") {
- console.error(`Unknown element type - ${(uiElement as any).uiType} in ${(uiElement as any).id}.`)
- }
- return null;
}
- }
-
- private renderUIElement = (uiElement: ViewElement, viewData: { [key: string]: any }, keyProperty: string | undefined, editMode: boolean, isNew: boolean) => {
- const isKey = (uiElement.label === keyProperty);
- const canEdit = editMode && (isNew || (uiElement.config && !isKey));
-
- // do not show elements w/o any value from the backend
- if (viewData[uiElement.id] == null && !editMode) {
- return null;
- } else if (isViewElementEmpty(uiElement)) {
- return null;
- } else if (uiElement.isList) {
- /* element is a leaf-list */
- return <UiElementLeafList
- key={uiElement.id}
- inputValue={viewData[uiElement.id] == null ? [] : viewData[uiElement.id]}
- value={uiElement}
- readOnly={!canEdit}
- disabled={editMode && !canEdit}
- onChange={(e) => { this.changeValueFor(uiElement.id, e) }}
- getEditorForViewElement = { this.getEditorForViewElement }
- />;
- } else {
- const Element = this.getEditorForViewElement(uiElement);
- return Element != null
- ? (
- <Element
+
+ private collectData = (elements: { [name: string]: ViewElement }) => {
+ // ensure only active choises will be contained
+ const viewData: { [key: string]: any } = { ...this.state.viewData };
+ const choiseKeys = Object.keys(elements).filter(elmKey => isViewElementChoise(elements[elmKey]));
+ const elementsToRemove = choiseKeys.reduce((acc, curChoiceKey) => {
+ const currentChoice = elements[curChoiceKey] as ViewElementChoise;
+ const selectedCase = this.state.choises[curChoiceKey].selectedCase;
+ Object.keys(currentChoice.cases).forEach(caseKey => {
+ const caseElements = currentChoice.cases[caseKey].elements;
+ if (caseKey === selectedCase) {
+ Object.keys(caseElements).forEach(caseElementKey => {
+ const elm = caseElements[caseElementKey];
+ if (isViewElementEmpty(elm)) {
+ // insert null for all empty elements
+ viewData[elm.id] = null;
+ }
+ });
+ return;
+ };
+ Object.keys(caseElements).forEach(caseElementKey => {
+ acc.push(caseElements[caseElementKey]);
+ });
+ });
+ return acc;
+ }, [] as ViewElement[]);
+
+ return viewData && Object.keys(viewData).reduce((acc, cur) => {
+ if (!elementsToRemove.some(elm => elm.label === cur || elm.id === cur)) {
+ acc[cur] = viewData[cur];
+ }
+ return acc;
+ }, {} as { [key: string]: any });
+ }
+
+ private getEditorForViewElement = (uiElement: ViewElement): (null | React.ComponentType<BaseProps<any>>) => {
+ if (isViewElementEmpty(uiElement)) {
+ return null;
+ } else if (isViewElementSelection(uiElement)) {
+ return UiElementSelection;
+ } else if (isViewElementBoolean(uiElement)) {
+ return UiElementBoolean;
+ } else if (isViewElementString(uiElement)) {
+ return UiElementString;
+ } else if (isViewElementDate(uiElement)) {
+ return UiElementString;
+ } else if (isViewElementNumber(uiElement)) {
+ return UiElementNumber;
+ } else if (isViewElementUnion(uiElement)) {
+ return UIElementUnion;
+ } else {
+ if (process.env.NODE_ENV !== "production") {
+ console.error(`Unknown element type - ${(uiElement as any).uiType} in ${(uiElement as any).id}.`)
+ }
+ return null;
+ }
+ }
+
+ private renderUIElement = (uiElement: ViewElement, viewData: { [key: string]: any }, keyProperty: string | undefined, editMode: boolean, isNew: boolean) => {
+ const isKey = (uiElement.label === keyProperty);
+ const canEdit = editMode && (isNew || (uiElement.config && !isKey));
+
+ // do not show elements w/o any value from the backend
+ if (viewData[uiElement.id] == null && !editMode) {
+ return null;
+ } else if (isViewElementEmpty(uiElement)) {
+ return null;
+ } else if (uiElement.isList) {
+ /* element is a leaf-list */
+ return <UiElementLeafList
key={uiElement.id}
- isKey={isKey}
- inputValue={viewData[uiElement.id] == null ? '' : viewData[uiElement.id]}
+ inputValue={viewData[uiElement.id] == null ? [] : viewData[uiElement.id]}
value={uiElement}
readOnly={!canEdit}
disabled={editMode && !canEdit}
onChange={(e) => { this.changeValueFor(uiElement.id, e) }}
- /> )
- : null ;
- }
- };
-
- // private renderUIReference = (uiElement: ViewElement, viewData: { [key: string]: any }, keyProperty: string | undefined, editMode: boolean, isNew: boolean) => {
- // const isKey = (uiElement.label === keyProperty);
- // const canEdit = editMode && (isNew || (uiElement.config && !isKey));
- // if (isViewElementObjectOrList(uiElement)) {
- // return (
- // <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} value={caseKey}><Tooltip title={caseElm.description || ''}><div style={{width:"100%"}}>{caseElm.label}</div></Tooltip></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>
- }
- </>
- );
- } 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;
+ getEditorForViewElement={this.getEditorForViewElement}
+ />;
+ } else {
+ const Element = this.getEditorForViewElement(uiElement);
+ return Element != null
+ ? (
+ <Element
+ key={uiElement.id}
+ isKey={isKey}
+ inputValue={viewData[uiElement.id] == null ? '' : viewData[uiElement.id]}
+ value={uiElement}
+ readOnly={!canEdit}
+ disabled={editMode && !canEdit}
+ onChange={(e) => { this.changeValueFor(uiElement.id, e) }}
+ />)
+ : null;
+ }
};
- 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 if (isViewElementRpc(elm)) {
- acc.rpcs.push(elm);
- } else {
- acc.elements.push(elm);
- }
- return acc;
- }, { elements: [] as ViewElement[], references: [] as ViewElement[], choises: [] as ViewElementChoise[], rpcs: [] as ViewElementRpc[] });
-
- sections.elements = sections.elements.sort(orderFunc);
-
- return (
- <div className={classes.uiView}>
- <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
- }
- {sections.rpcs.length > 0
- ? (
- <div className={classes.section}>
- {sections.rpcs.map(element => (
- <UIElementReference key={element.id} element={element} disabled={editMode} onOpenReference={(elm) => { this.navigate(`/${elm.id}`) }} />
- ))}
- </div>
- ) : null
+ // private renderUIReference = (uiElement: ViewElement, viewData: { [key: string]: any }, keyProperty: string | undefined, editMode: boolean, isNew: boolean) => {
+ // const isKey = (uiElement.label === keyProperty);
+ // const canEdit = editMode && (isNew || (uiElement.config && !isKey));
+ // if (isViewElementObjectOrList(uiElement)) {
+ // return (
+ // <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
+ aria-label={uiElement.label+'-selection'}
+ 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} value={caseKey} aria-label={caseKey}><Tooltip title={caseElm.description || ''}><div style={{ width: "100%" }}>{caseElm.label}</div></Tooltip></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>
+ }
+ </>
+ );
+ } else {
+ if (process.env.NODE_ENV !== "production") {
+ console.error(`Unknown type - ${(uiElement as any).uiType} in ${(uiElement as any).id}.`)
+ }
+ return null;
}
- </div>
- );
- };
+ };
+
+ 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 if (isViewElementRpc(elm)) {
+ acc.rpcs.push(elm);
+ } else {
+ acc.elements.push(elm);
+ }
+ return acc;
+ }, { elements: [] as ViewElement[], references: [] as ViewElement[], choises: [] as ViewElementChoise[], rpcs: [] as ViewElementRpc[] });
- private renderUIViewList(listSpecification: ViewSpecification, listKeyProperty: string, listData: { [key: string]: any }[]) {
- const listElements = listSpecification.elements;
+ sections.elements = sections.elements.sort(orderFunc);
- const navigate = (path: string) => {
- this.props.history.push(`${this.props.match.url}${path}`);
+ return (
+ <div className={classes.uiView}>
+ <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
+ }
+ {sections.rpcs.length > 0
+ ? (
+ <div className={classes.section}>
+ {sections.rpcs.map(element => (
+ <UIElementReference key={element.id} element={element} disabled={editMode} onOpenReference={(elm) => { this.navigate(`/${elm.id}`) }} />
+ ))}
+ </div>
+ ) : null
+ }
+ </div>
+ );
};
- const addNewElementAction = {
- icon: AddIcon, tooltip: 'Add', onClick: () => {
- navigate("[]"); // empty key means new element
- }
+ private renderUIViewSelector = (viewSpecification: ViewSpecification, viewData: { [key: string]: any }, keyProperty: string | undefined, editMode: boolean, isNew: boolean) => {
+ const { classes } = this.props;
+
+ // group by module name
+ const modules = Object.keys(viewSpecification.elements).reduce<{ [key: string]: ViewSpecification }>((acc, cur) => {
+ const elm = viewSpecification.elements[cur];
+ const moduleView = (acc[elm.module] = acc[elm.module] || { ...viewSpecification, elements: {} });
+ moduleView.elements[cur] = elm;
+ return acc;
+ }, {});
+
+ const moduleKeys = Object.keys(modules).sort();
+
+ return (
+ <div className={classes.moduleCollection}>
+ {
+ moduleKeys.map(key => {
+ const moduleView = modules[key];
+ return (
+ <Accordion key={key} defaultExpanded={ moduleKeys.length < 4 } aria-label={key+'-panel'} >
+ <AccordionSummary expandIcon={<ExpandMoreIcon />} aria-controls={`content-${key}`} id={`header-${key}`} >
+ <Typography className={classes.heading}>{key}</Typography>
+ </AccordionSummary>
+ <AccordionDetails>
+ {this.renderUIView(moduleView, viewData, keyProperty, editMode, isNew)}
+ </AccordionDetails>
+ </Accordion>
+ );
+ })
+ }
+ </div>
+ );
};
- const { classes, removeElement } = this.props;
+ private renderUIViewList(listSpecification: ViewSpecification, listKeyProperty: string, listData: { [key: string]: any }[]) {
+ const listElements = listSpecification.elements;
+
+ const navigate = (path: string) => {
+ this.props.history.push(`${this.props.match.url}${path}`);
+ };
+
+ const addNewElementAction = {
+ icon: AddIcon, tooltip: 'Add', onClick: () => {
+ navigate("[]"); // empty key means new element
+ }
+ };
+
+ const { classes, removeElement } = this.props;
- const DeleteIconWithConfirmation : React.FC<{rowData: {[key:string]:any}, onReload: () => void} > = (props) => {
- const confirm = useConfirm();
+ const DeleteIconWithConfirmation: React.FC<{ rowData: { [key: string]: any }, onReload: () => void }> = (props) => {
+ const confirm = useConfirm();
+
+ return (
+ <Tooltip title={"Remove"} >
+ <IconButton className={classes.button} aria-label="remove-element-button"
+ onClick={async (e) => {
+ e.stopPropagation();
+ e.preventDefault();
+ confirm({ title: "Do you really want to delete this element ?", description: "This action is permanent!", confirmationButtonProps: { color: "secondary" } })
+ .then(() => removeElement(`${this.props.vPath}[${props.rowData[listKeyProperty]}]`))
+ .then(props.onReload);
+ }} >
+ <RemoveIcon />
+ </IconButton>
+ </Tooltip>
+ );
+ }
return (
- <Tooltip title={"Remove"} >
- <IconButton className={classes.button} onClick={async (e) => {
- e.stopPropagation();
- e.preventDefault();
- confirm({title: "Do you really want to delete this element ?", description: "This action is permanent!", confirmationButtonProps: { color: "secondary" }})
- .then(() => removeElement(`${this.props.vPath}[${props.rowData[listKeyProperty]}]`))
- .then( props.onReload );
- }} >
- <RemoveIcon />
- </IconButton>
- </Tooltip>
+ <SelectElementTable stickyHeader idProperty={listKeyProperty} rows={listData} customActionButtons={[addNewElementAction]} columns={
+ Object.keys(listElements).reduce<ColumnModel<{ [key: string]: any }>[]>((acc, cur) => {
+ const elm = listElements[cur];
+ if (elm.uiType !== "object" && listData.every(entry => entry[elm.label] != null)) {
+ if (elm.label !== listKeyProperty) {
+ acc.push(elm.uiType === "boolean"
+ ? { property: elm.label, type: ColumnType.boolean }
+ : elm.uiType === "date"
+ ? { property: elm.label, type: ColumnType.date }
+ : { property: elm.label, type: elm.uiType === "number" ? ColumnType.numeric : ColumnType.text });
+ } else {
+ acc.unshift(elm.uiType === "boolean"
+ ? { property: elm.label, type: ColumnType.boolean }
+ : elm.uiType === "date"
+ ? { property: elm.label, type: ColumnType.date }
+ : { property: elm.label, type: elm.uiType === "number" ? ColumnType.numeric : ColumnType.text });
+ }
+ }
+ return acc;
+ }, []).concat([{
+ property: "Actions", disableFilter: true, disableSorting: true, type: ColumnType.custom, customControl: (({ rowData }) => {
+ return (
+ <DeleteIconWithConfirmation rowData={rowData} onReload={() => this.props.vPath && this.props.reloadView(this.props.vPath)} />
+ );
+ })
+ }])
+ } onHandleClick={(ev, row) => {
+ ev.preventDefault();
+ navigate(`[${encodeURIComponent(row[listKeyProperty])}]`);
+ }} ></SelectElementTable>
);
}
- return (
- <SelectElementTable stickyHeader idProperty={listKeyProperty} rows={listData} customActionButtons={[addNewElementAction]} columns={
- Object.keys(listElements).reduce<ColumnModel<{ [key: string]: any }>[]>((acc, cur) => {
- const elm = listElements[cur];
- if (elm.uiType !== "object" && listData.every(entry => entry[elm.label] != null)) {
- if (elm.label !== listKeyProperty) {
- acc.push(elm.uiType === "boolean" ? { property: elm.label, type: ColumnType.boolean } : { property: elm.label, type: elm.uiType === "number" ? ColumnType.numeric : ColumnType.text });
+ private renderUIViewRPC(inputViewSpecification: ViewSpecification | undefined, inputViewData: { [key: string]: any }, outputViewData: { [key: string]: any }, keyProperty: string | undefined, editMode: boolean, isNew: boolean) {
+ const { classes } = this.props;
+
+ const orderFunc = (vsA: ViewElement, vsB: ViewElement) => {
+ if (keyProperty) {
+ // if (vsA.label === vsB.label) return 0;
+ if (vsA.label === keyProperty) return -1;
+ if (vsB.label === keyProperty) return +1;
+ }
+
+ // if (vsA.uiType === vsB.uiType) return 0;
+ // if (vsA.uiType !== "object" && vsB.uiType !== "object") return 0;
+ // if (vsA.uiType === "object") return +1;
+ return -1;
+ };
+
+ const sections = inputViewSpecification && Object.keys(inputViewSpecification.elements).reduce((acc, cur) => {
+ const elm = inputViewSpecification.elements[cur];
+ if (isViewElementObjectOrList(elm)) {
+ console.error("Object should not appear in RPC view !");
+ } else if (isViewElementChoise(elm)) {
+ acc.choises.push(elm);
+ } else if (isViewElementRpc(elm)) {
+ console.error("RPC should not appear in RPC view !");
} else {
- acc.unshift(elm.uiType === "boolean" ? { property: elm.label, type: ColumnType.boolean } : { property: elm.label, type: elm.uiType === "number" ? ColumnType.numeric : ColumnType.text });
+ acc.elements.push(elm);
}
- }
- return acc;
- }, []).concat([{
- property: "Actions", disableFilter: true, disableSorting: true, type: ColumnType.custom, customControl: ( ({ rowData })=> {
- return (
- <DeleteIconWithConfirmation rowData={rowData} onReload={() => this.props.vPath && this.props.reloadView(this.props.vPath) } />
- );
- })
- }])
- } onHandleClick={(ev, row) => {
- ev.preventDefault();
- navigate(`[${row[listKeyProperty]}]`);
- }} ></SelectElementTable>
- );
- }
-
- private renderUIViewRPC(inputViewSpecification: ViewSpecification | undefined, inputViewData: { [key: string]: any }, outputViewData: { [key: string]: any }, keyProperty: string | undefined, editMode: boolean, isNew: boolean) {
- const { classes } = this.props;
-
- const orderFunc = (vsA: ViewElement, vsB: ViewElement) => {
- if (keyProperty) {
- // if (vsA.label === vsB.label) return 0;
- if (vsA.label === keyProperty) return -1;
- if (vsB.label === keyProperty) return +1;
- }
-
- // if (vsA.uiType === vsB.uiType) return 0;
- // if (vsA.uiType !== "object" && vsB.uiType !== "object") return 0;
- // if (vsA.uiType === "object") return +1;
- return -1;
+ return acc;
+ }, { elements: [] as ViewElement[], references: [] as ViewElement[], choises: [] as ViewElementChoise[], rpcs: [] as ViewElementRpc[] })
+ || { elements: [] as ViewElement[], references: [] as ViewElement[], choises: [] as ViewElementChoise[], rpcs: [] as ViewElementRpc[] };
+
+ 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, inputViewData, keyProperty, editMode, isNew))}
+ </div>
+ ) : null
+ }
+ { sections.choises.length > 0
+ ? (
+ <div className={classes.section}>
+ {sections.choises.map(element => this.renderUIChoise(element, inputViewData, keyProperty, editMode, isNew))}
+ </div>
+ ) : null
+ }
+ <Button onClick={() => {
+ const resultingViewData = inputViewSpecification && this.collectData(inputViewSpecification.elements);
+ this.props.executeRpc(this.props.vPath!, resultingViewData);
+ }} >Exec</Button>
+ <div className={classes.objectReult}>
+ { outputViewData !== undefined
+ ? renderObject(outputViewData)
+ : null
+ }
+ </div>
+ </>
+ );
};
- const sections = inputViewSpecification && Object.keys(inputViewSpecification.elements).reduce((acc, cur) => {
- const elm = inputViewSpecification.elements[cur];
- if (isViewElementObjectOrList(elm)) {
- console.error("Object should not appear in RPC view !");
- } else if (isViewElementChoise(elm)) {
- acc.choises.push(elm);
- } else if (isViewElementRpc(elm)) {
- console.error("RPC should not appear in RPC view !");
- } else {
- acc.elements.push(elm);
- }
- return acc;
- }, { elements: [] as ViewElement[], references: [] as ViewElement[], choises: [] as ViewElementChoise[], rpcs: [] as ViewElementRpc[] })
- || { elements: [] as ViewElement[], references: [] as ViewElement[], choises: [] as ViewElementChoise[], rpcs: [] as ViewElementRpc[] };
-
- 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, inputViewData, keyProperty, editMode, isNew))}
+ private renderBreadCrumps() {
+ const { editMode } = this.state;
+ const { displaySpecification, vPath, nodeId } = this.props;
+ const pathParts = splitVPath(vPath!, /(?:([^\/\["]+)(?:\[([^\]]*)\])?)/g); // 1 = property / 2 = optional key
+ let lastPath = `/configuration`;
+ let basePath = `/configuration/${nodeId}`;
+ return (
+ <div className={this.props.classes.header}>
+ <div>
+ <Breadcrumbs aria-label="breadcrumbs">
+ <Link color="inherit" href="#" aria-label="back-breadcrumb"
+ onClick={(ev: React.MouseEvent<HTMLElement>) => {
+ ev.preventDefault();
+ this.props.history.push(lastPath);
+ }}>Back</Link>
+ <Link color="inherit" href="#"
+ aria-label={nodeId+'-breadcrumb'}
+ onClick={(ev: React.MouseEvent<HTMLElement>) => {
+ ev.preventDefault();
+ this.props.history.push(`/configuration/${nodeId}`);
+ }}><span>{nodeId}</span></Link>
+ {
+ pathParts.map(([prop, key], ind) => {
+ const path = `${basePath}/${prop}`;
+ const keyPath = key && `${basePath}/${prop}[${key}]`;
+ const propTitle= prop.replace(/^[^:]+:/, "");
+ const ret = (
+ <span key={ind}>
+ <Link color="inherit" href="#"
+ aria-label={propTitle+'-breadcrumb'}
+ onClick={(ev: React.MouseEvent<HTMLElement>) => {
+ ev.preventDefault();
+ this.props.history.push(path);
+ }}><span>{propTitle}</span></Link>
+ {
+ keyPath && <Link color="inherit" href="#"
+ aria-label={key+'-breadcrumb'}
+ onClick={(ev: React.MouseEvent<HTMLElement>) => {
+ ev.preventDefault();
+ this.props.history.push(keyPath);
+ }}>{`[${key}]`}</Link> || null
+ }
+ </span>
+ );
+ lastPath = basePath;
+ basePath = keyPath || path;
+ return ret;
+ })
+ }
+ </Breadcrumbs>
+ </div>
+ {this.state.editMode && (
+ <Fab color="secondary" aria-label="back-button" 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 */
+ displaySpecification.displayMode === DisplayModeType.displayAsObject && displaySpecification.viewSpecification.canEdit && (<div>
+ <Fab color="secondary" aria-label={editMode ? 'save-button' : 'edit-button'} className={this.props.classes.fab} onClick={() => {
+ if (this.state.editMode) {
+ // ensure only active choises will be contained
+ const resultingViewData = this.collectData(displaySpecification.viewSpecification.elements);
+ this.props.onUpdateData(this.props.vPath!, resultingViewData);
+ }
+ this.setState({ editMode: !editMode });
+ }}>
+ {editMode
+ ? <SaveIcon />
+ : <EditIcon />
+ }
+ </Fab>
+ </div> || null)
+ }
</div>
- ) : null
+ );
+ }
+
+ private renderValueSelector() {
+ const { listKeyProperty, listSpecification, listData, onValueSelected } = this.props;
+ if (!listKeyProperty || !listSpecification) {
+ throw new Error("ListKex ot view not specified.");
}
- {sections.choises.length > 0
- ? (
- <div className={classes.section}>
- {sections.choises.map(element => this.renderUIChoise(element, inputViewData, keyProperty, editMode, isNew))}
+
+ return (
+ <div className={this.props.classes.container}>
+ <SelectElementTable stickyHeader idProperty={listKeyProperty} rows={listData} columns={
+ Object.keys(listSpecification.elements).reduce<ColumnModel<{ [key: string]: any }>[]>((acc, cur) => {
+ const elm = listSpecification.elements[cur];
+ if (elm.uiType !== "object" && listData.every(entry => entry[elm.label] != null)) {
+ if (elm.label !== listKeyProperty) {
+ acc.push({ property: elm.label, type: elm.uiType === "number" ? ColumnType.numeric : ColumnType.text });
+ } else {
+ acc.unshift({ property: elm.label, type: elm.uiType === "number" ? ColumnType.numeric : ColumnType.text });
+ }
+ }
+ return acc;
+ }, [])
+ } onHandleClick={(ev, row) => { ev.preventDefault(); onValueSelected(row); }} ></SelectElementTable>
</div>
- ) : null
- }
- <Button onClick={() => {
- const resultingViewData = inputViewSpecification && this.collectData(inputViewSpecification.elements);
- this.props.executeRpc(this.props.vPath!, resultingViewData);
- }} >Exec</Button>
- {outputViewData !== undefined
- ? (
- renderObject(outputViewData) )
- : null
- }
- </>
- );
- };
-
- private renderBreadCrumps() {
- const { editMode } = this.state;
- const { displaySpecification } = this.props;
- const { vPath, nodeId } = this.props;
- const pathParts = splitVPath(vPath!, /(?:([^\/\["]+)(?:\[([^\]]*)\])?)/g); // 1 = property / 2 = optional key
- let lastPath = `/configuration`;
- let basePath = `/configuration/${nodeId}`;
- return (
- <div className={this.props.classes.header}>
- <div>
- <Breadcrumbs aria-label="breadcrumb">
- <Link color="inherit" href="#" onClick={(ev: React.MouseEvent<HTMLElement>) => {
- ev.preventDefault();
- this.props.history.push(lastPath);
- }}>Back</Link>
- <Link color="inherit" href="#" onClick={(ev: React.MouseEvent<HTMLElement>) => {
- ev.preventDefault();
- this.props.history.push(`/configuration/${nodeId}`);
- }}><span>{nodeId}</span></Link>
- {
- pathParts.map(([prop, key], ind) => {
- const path = `${basePath}/${prop}`;
- const keyPath = key && `${basePath}/${prop}[${key}]`;
- const ret = (
- <span key={ind}>
- <Link color="inherit" href="#" onClick={(ev: React.MouseEvent<HTMLElement>) => {
- ev.preventDefault();
- this.props.history.push(path);
- }}><span>{prop.replace(/^[^:]+:/, "")}</span></Link>
- {
- keyPath && <Link color="inherit" href="#" onClick={(ev: React.MouseEvent<HTMLElement>) => {
- ev.preventDefault();
- this.props.history.push(keyPath);
- }}>{`[${key}]`}</Link> || null
- }
- </span>
- );
- lastPath = basePath;
- basePath = keyPath || path;
- return ret;
- })
- }
- </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 */
- displaySpecification.displayMode === DisplayModeType.displayAsObject && displaySpecification.viewSpecification.canEdit && (<div>
- <Fab color="secondary" aria-label="edit" className={this.props.classes.fab} onClick={() => {
- if (this.state.editMode) {
- // ensure only active choises will be contained
- const resultingViewData = this.collectData(displaySpecification.viewSpecification.elements);
- this.props.onUpdateData(this.props.vPath!, resultingViewData);
- }
- this.setState({ editMode: !editMode });
- }}>
- {editMode
- ? <SaveIcon />
- : <EditIcon />
- }
- </Fab>
- </div> || null)
- }
- </div>
- );
- }
-
- private renderValueSelector() {
- const { listKeyProperty, listSpecification, listData, onValueSelected } = this.props;
- if (!listKeyProperty || !listSpecification) {
- throw new Error("ListKex ot view not specified.");
+ );
}
- return (
- <div className={this.props.classes.container}>
- <SelectElementTable stickyHeader idProperty={listKeyProperty} rows={listData} columns={
- Object.keys(listSpecification.elements).reduce<ColumnModel<{ [key: string]: any }>[]>((acc, cur) => {
- const elm = listSpecification.elements[cur];
- if (elm.uiType !== "object" && listData.every(entry => entry[elm.label] != null)) {
- if (elm.label !== listKeyProperty) {
- acc.push({ property: elm.label, type: elm.uiType === "number" ? ColumnType.numeric : ColumnType.text });
- } else {
- acc.unshift({ property: elm.label, type: elm.uiType === "number" ? ColumnType.numeric : ColumnType.text });
- }
- }
- return acc;
- }, [])
- } onHandleClick={(ev, row) => { ev.preventDefault(); onValueSelected(row); }} ></SelectElementTable>
- </div>
- );
- }
-
- private renderValueEditor() {
- const { displaySpecification: ds, outputData } = this.props;
- const { viewData, editMode, isNew } = this.state;
-
- return (
- <div className={this.props.classes.container}>
- {this.renderBreadCrumps()}
- {ds.displayMode === DisplayModeType.doNotDisplay
- ? null
- : ds.displayMode === DisplayModeType.displayAsList && viewData instanceof Array
- ? this.renderUIViewList(ds.viewSpecification, ds.keyProperty!, viewData)
- : ds.displayMode === DisplayModeType.displayAsRPC
- ? this.renderUIViewRPC(ds.inputViewSpecification, viewData!, outputData, undefined, true, false)
- : this.renderUIView(ds.viewSpecification, viewData!, ds.keyProperty, editMode, isNew)
- }
- </div >
- );
- }
-
- private renderCollectingData() {
- return (
- <div className={this.props.classes.outer}>
- <div className={this.props.classes.inner}>
- <Loader />
- <h3>Processing ...</h3>
- </div>
- </div>
- );
- }
-
- render() {
- return this.props.collectingData || !this.state.viewData
- ? this.renderCollectingData()
- : this.props.listSpecification
- ? this.renderValueSelector()
- : this.renderValueEditor();
- }
+ private renderValueEditor() {
+ const { displaySpecification: ds, outputData } = this.props;
+ const { viewData, editMode, isNew } = this.state;
+
+ return (
+ <div className={this.props.classes.container}>
+ {this.renderBreadCrumps()}
+ {ds.displayMode === DisplayModeType.doNotDisplay
+ ? null
+ : ds.displayMode === DisplayModeType.displayAsList && viewData instanceof Array
+ ? this.renderUIViewList(ds.viewSpecification, ds.keyProperty!, viewData)
+ : ds.displayMode === DisplayModeType.displayAsRPC
+ ? this.renderUIViewRPC(ds.inputViewSpecification, viewData!, outputData, undefined, true, false)
+ : this.renderUIViewSelector(ds.viewSpecification, viewData!, ds.keyProperty, editMode, isNew)
+ }
+ </div >
+ );
+ }
+
+ private renderCollectingData() {
+ return (
+ <div className={this.props.classes.outer}>
+ <div className={this.props.classes.inner}>
+ <Loader />
+ <h3>Processing ...</h3>
+ </div>
+ </div>
+ );
+ }
+
+ render() {
+ return this.props.collectingData || !this.state.viewData
+ ? this.renderCollectingData()
+ : this.props.listSpecification
+ ? this.renderValueSelector()
+ : this.renderValueEditor();
+ }
}
export const ConfigurationApplication = withStyles(styles)(withRouter(connect(mapProps, mapDispatch)(ConfigurationApplicationComponent)));
-export default ConfigurationApplication;
+export default ConfigurationApplication; \ No newline at end of file
diff --git a/sdnr/wt/odlux/apps/configurationApp/src/yang/yangParser.ts b/sdnr/wt/odlux/apps/configurationApp/src/yang/yangParser.ts
index 0f74297df..c5cb8fb4c 100644
--- a/sdnr/wt/odlux/apps/configurationApp/src/yang/yangParser.ts
+++ b/sdnr/wt/odlux/apps/configurationApp/src/yang/yangParser.ts
@@ -15,14 +15,15 @@
* the License.
* ============LICENSE_END==========================================================================
*/
-import { Token, Statement, Module, Identity } from "../models/yang";
+import { Token, Statement, Module, Identity, ModuleState } from "../models/yang";
import {
ViewSpecification, ViewElement, isViewElementObjectOrList, ViewElementBase,
isViewElementReference, ViewElementChoise, ViewElementBinary, ViewElementString, isViewElementString,
- isViewElementNumber, ViewElementNumber, Expression, YangRange, ViewElementUnion, ViewElementRpc, isViewElementRpc, ResolveFunction
+ isViewElementNumber, ViewElementNumber, Expression, YangRange, ViewElementUnion, ViewElementRpc, isViewElementRpc, ResolveFunction, ViewElementDate
} from "../models/uiModels";
import { yangService } from "../services/yangService";
+
export const splitVPath = (vPath: string, vPathParser: RegExp): RegExpMatchArray[] => {
const pathParts: RegExpMatchArray[] = [];
let partMatch: RegExpExecArray | null;
@@ -284,8 +285,8 @@ export class YangParser {
public static ResolveStack = Symbol("ResolveStack");
- constructor() {
-
+ constructor(private _unavailableCapabilities: { failureReason: string; capability: string; }[] = []) {
+
}
public get modules() {
@@ -299,9 +300,16 @@ export class YangParser {
public async addCapability(capability: string, version?: string) {
// do not add twice
if (this._modules[capability]) {
+ // console.warn(`Skipped capability: ${capability} since allready contained.` );
return;
}
+ // // do not add unavaliabe capabilities
+ // if (this._unavailableCapabilities.some(c => c.capability === capability)) {
+ // // console.warn(`Skipped capability: ${capability} since it is marked as unavaliable.` );
+ // return;
+ // }
+
const data = await yangService.getCapability(capability, version);
if (!data) {
throw new Error(`Could not load yang file for ${capability}.`);
@@ -316,6 +324,8 @@ export class YangParser {
throw new Error(`Root element capability ${rootStatement.arg} does not requested ${capability}.`);
}
+ const isUnavaliabe = this._unavailableCapabilities.some(c => c.capability === capability);
+
const module = this._modules[capability] = {
name: rootStatement.arg,
revisions: {},
@@ -326,7 +336,10 @@ export class YangParser {
groupings: {},
typedefs: {},
views: {},
- elements: {}
+ elements: {},
+ state: isUnavaliabe
+ ? ModuleState.unabaliabe
+ : ModuleState.stable,
};
await this.handleModule(module, rootStatement, capability);
@@ -389,9 +402,14 @@ export class YangParser {
}, {})
};
- // import all required files
+ // import all required files and set module state
if (imports) for (let ind = 0; ind < imports.length; ++ind) {
- await this.addCapability(imports[ind].arg!);
+ const moduleName = imports[ind].arg!;
+ await this.addCapability(moduleName);
+ const importedModule = this._modules[imports[ind].arg!];
+ if (importedModule && importedModule.state > ModuleState.stable) {
+ module.state = Math.max(module.state, ModuleState.instable);
+ }
}
this.extractTypeDefinitions(rootStatement, module, "");
@@ -420,7 +438,9 @@ export class YangParser {
const viewIdIndex = Number(viewElement.viewId);
module.views[key] = this._views[viewIdIndex];
}
- this._views[0].elements[key] = module.elements[key];
+
+ // add only the UI View if the module is avliable
+ if (module.state !== ModuleState.unabaliabe) this._views[0].elements[key] = module.elements[key];
});
});
return module;
@@ -442,10 +462,30 @@ export class YangParser {
}
});
- // process all augmentations
+ // process all augmentations / sort by namespace changes to ensure propper order
Object.keys(this.modules).forEach(modKey => {
const module = this.modules[modKey];
- Object.keys(module.augments).forEach(augKey => {
+ const augmentKeysWithCounter = Object.keys(module.augments).map((key) => {
+ const pathParts = splitVPath(key, /(?:(?:([^\/\:]+):)?([^\/]+))/g); // 1 = opt: namespace / 2 = property
+ let nameSpaceChangeCounter = 0;
+ let currentNS = module.name; // init namespace
+ pathParts.forEach(([ns, _])=> {
+ if (ns === currentNS){
+ currentNS = ns;
+ nameSpaceChangeCounter++;
+ }
+ });
+ return {
+ key,
+ nameSpaceChangeCounter,
+ }
+ });
+
+ const augmentKeys = augmentKeysWithCounter
+ .sort((a,b) => a.nameSpaceChangeCounter > b.nameSpaceChangeCounter ? 1 : a.nameSpaceChangeCounter === b.nameSpaceChangeCounter ? 0 : -1 )
+ .map((a) => a.key);
+
+ augmentKeys.forEach(augKey => {
const augments = module.augments[augKey];
const viewSpec = this.resolveView(augKey);
if (!viewSpec) console.warn(`Could not find view to augment [${augKey}] in [${module.name}].`);
@@ -454,6 +494,7 @@ export class YangParser {
const elm = augment.elements[key];
viewSpec.elements[key] = {
...augment.elements[key],
+
when: elm.when ? `(${augment.when}) and (${elm.when})` : augment.when,
ifFeature: elm.ifFeature ? `(${augment.ifFeature}) and (${elm.ifFeature})` : augment.ifFeature,
};
@@ -546,7 +587,7 @@ export class YangParser {
const grouping = cur.arg;
// the default for config on module level is config = true;
- const [currentView, subViews] = this.extractSubViews(cur, parentId, module, currentPath);
+ const [currentView, subViews] = this.extractSubViews(cur, /* parentId */ -1, module, currentPath);
grouping && (module.groupings[grouping] = currentView);
acc.push(currentView, ...subViews);
return acc;
@@ -600,6 +641,7 @@ export class YangParser {
}, {});
}
+ // Hint: use 0 as parentId for rootElements and -1 for rootGroupings.
private extractSubViews(statement: Statement, parentId: number, module: Module, currentPath: string): [ViewSpecification, ViewSpecification[]] {
// used for scoped definitions
const context: Module = {
@@ -640,6 +682,8 @@ export class YangParser {
elements.push({
id: parentId === 0 ? `${context.name}:${cur.arg}` : cur.arg,
label: cur.arg,
+ path: currentPath,
+ module: context.name || module.name || '',
uiType: "object",
viewId: currentView.id,
config: config
@@ -667,6 +711,8 @@ export class YangParser {
elements.push({
id: parentId === 0 ? `${context.name}:${cur.arg}` : cur.arg,
label: cur.arg,
+ path: currentPath,
+ module: context.name || module.name || '',
isList: true,
uiType: "object",
viewId: currentView.id,
@@ -755,6 +801,8 @@ export class YangParser {
uiType: "choise",
id: parentId === 0 ? `${context.name}:${curChoise.arg}` : curChoise.arg,
label: curChoise.arg,
+ path: currentPath,
+ module: context.name || module.name || '',
config: config,
mandatory: mandatory,
description: description,
@@ -802,6 +850,8 @@ export class YangParser {
uiType: "rpc",
id: parentId === 0 ? `${context.name}:${curRpc.arg}` : curRpc.arg,
label: curRpc.arg,
+ path: currentPath,
+ module: context.name || module.name || '',
config: config,
description: description,
inputViewId: inputViewId,
@@ -869,7 +919,8 @@ export class YangParser {
Object.keys(groupingViewSpec.elements).forEach(key => {
const elm = groupingViewSpec.elements[key];
- viewSpec.elements[key] = {
+ // a useRef on root level need a namespace
+ viewSpec.elements[parentId === 0 ? `${module.name}:${key}` : key] = {
...groupingViewSpec.elements[key],
when: elm.when ? `(${groupingViewSpec.when}) and (${elm.when})` : groupingViewSpec.when,
ifFeature: elm.ifFeature ? `(${groupingViewSpec.ifFeature}) and (${elm.ifFeature})` : groupingViewSpec.ifFeature,
@@ -889,7 +940,9 @@ export class YangParser {
}
});
// console.log("Resolved "+currentElementPath, viewSpec);
- viewSpec?.uses![ResolveFunction] = undefined;
+ if (viewSpec?.uses) {
+ viewSpec.uses[ResolveFunction] = undefined;
+ }
}
this._groupingsToResolve.push(viewSpec);
@@ -934,9 +987,19 @@ export class YangParser {
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);
+ let minValue: number;
+ let maxValue: number;
+
+ if (r.indexOf('..') > -1) {
+ const [minStr, maxStr] = r.split('..');
+ minValue = Number(minStr);
+ maxValue = Number(maxStr);
+ } else if (!isNaN(maxValue = Number(r && r.trim() )) ) {
+ minValue = maxValue;
+ } else {
+ minValue = min,
+ maxValue = max;
+ }
if (minValue > min) min = minValue;
if (maxValue < max) max = maxValue;
@@ -978,7 +1041,9 @@ export class YangParser {
const element: ViewElementBase = {
id: parentId === 0 ? `${module.name}:${cur.arg}` : cur.arg,
- label: cur.arg,
+ label: cur.arg,
+ path: currentPath,
+ module: module.name || "",
config: config,
mandatory: mandatory,
isList: isList,
@@ -1254,17 +1319,27 @@ export class YangParser {
length: extractRange(0, +18446744073709551615, "length"),
};
} else {
- // not a build in type, have to resolve type
+ // not a build in type, need to resolve type
let typeRef = this.resolveType(type, module);
if (typeRef == null) console.error(new Error(`Could not resolve type ${type} in [${module.name}][${currentPath}].`));
+
if (isViewElementString(typeRef)) {
typeRef = this.resolveStringType(typeRef, extractPattern(), extractRange(0, +18446744073709551615));
-
} else if (isViewElementNumber(typeRef)) {
typeRef = this.resolveNumberType(typeRef, extractRange(typeRef.min, typeRef.max));
}
+ // spoof date type here from special string type
+ if ((type === 'date-and-time' || type.endsWith(':date-and-time') ) && typeRef.module === "ietf-yang-types") {
+ return {
+ ...typeRef,
+ ...element,
+ description: description,
+ uiType: "date",
+ };
+ }
+
return ({
...typeRef,
...element,
diff --git a/sdnr/wt/odlux/apps/configurationApp/webpack.config.js b/sdnr/wt/odlux/apps/configurationApp/webpack.config.js
index 329eb00f4..6349305ac 100644
--- a/sdnr/wt/odlux/apps/configurationApp/webpack.config.js
+++ b/sdnr/wt/odlux/apps/configurationApp/webpack.config.js
@@ -129,36 +129,46 @@ module.exports = (env) => {
colors: true
},
proxy: {
+ "/about": {
+ // target: "http://10.20.6.29:48181",
+ target: "http://localhost:8181",
+ secure: false
+ },
"/yang-schema/": {
- target: "http://10.20.6.29:48181",
+ target: "http://localhost:8181",
secure: false
},
"/oauth2/": {
- target: "http://10.20.6.29:48181",
+ // target: "https://10.20.35.188:30205",
+ target: "http://localhost:8181",
secure: false
},
"/database/": {
- target: "http://10.20.6.29:48181",
+ target: "http://localhost:8181",
secure: false
},
"/restconf/": {
- target: "http://10.20.6.29:48181",
+ target: "http://localhost:8181",
secure: false
},
"/rests/": {
- target: "http://10.20.6.29:48181",
+ target: "http://localhost:8181",
secure: false
},
"/help/": {
- target: "http://10.20.6.29:48181",
+ target: "http://localhost:8181",
+ secure: false
+ },
+ "/about/": {
+ target: "http://localhost:8181",
secure: false
},
"/tree/": {
- target: "http://10.20.6.29:48181",
+ target: "http://localhost:8181",
secure: false
},
"/websocket": {
- target: "http://10.20.6.29:48181",
+ target: "http://localhost:8181",
ws: true,
changeOrigin: true,
secure: false
diff --git a/sdnr/wt/odlux/framework/pom.xml b/sdnr/wt/odlux/framework/pom.xml
index 732b4fec5..c849c1968 100644
--- a/sdnr/wt/odlux/framework/pom.xml
+++ b/sdnr/wt/odlux/framework/pom.xml
@@ -46,7 +46,7 @@
<properties>
<buildtime>${maven.build.timestamp}</buildtime>
<distversion>ONAP Frankfurt (Neon, mdsal ${odl.mdsal.version})</distversion>
- <buildno>68.d7886ce(20/09/04)</buildno>
+ <buildno>86.51a94bd(20/11/17)</buildno>
<odlux.version>ONAP SDN-R | ONF Wireless for ${distversion} - Build: ${buildtime} ${buildno} ${project.version}</odlux.version>
</properties>
diff --git a/sdnr/wt/odlux/framework/src/assets/version.json b/sdnr/wt/odlux/framework/src/assets/version.json
index 6311e1094..7720cb969 100644
--- a/sdnr/wt/odlux/framework/src/assets/version.json
+++ b/sdnr/wt/odlux/framework/src/assets/version.json
@@ -1,4 +1,5 @@
{
- "version":"68.d7886ce(20/09/04)",
- "build":"2020-09-04T12:31:19Z"
+ "version":"86.51a94bd(20/11/17)",
+ "build":"2020-11-17T11:13:24Z"
+
} \ No newline at end of file