aboutsummaryrefslogtreecommitdiffstats
path: root/sdnr/wt/odlux/apps
diff options
context:
space:
mode:
authorMichael Dürre <michael.duerre@highstreet-technologies.com>2020-07-16 05:55:07 +0200
committerMichael Dürre <michael.duerre@highstreet-technologies.com>2020-07-16 05:55:21 +0200
commit7dbe38ba0522b346a0fcd9851e797f0fd71ecd5e (patch)
treecc19db7e0637c8e392d40cdf3a53bb5e5f3e0d30 /sdnr/wt/odlux/apps
parent25b3759a0907d06e0d8e391f751c6fcf067087f5 (diff)
switch to rfc8040 restconf
change rest interface and some small code cleanups Issue-ID: CCSDK-2572 Signed-off-by: Michael Dürre <michael.duerre@highstreet-technologies.com> Change-Id: I3475bd2574b32950c4bf84fbd1c2a9dac9af208a
Diffstat (limited to 'sdnr/wt/odlux/apps')
-rw-r--r--sdnr/wt/odlux/apps/apiDemo/package.json4
-rw-r--r--sdnr/wt/odlux/apps/configurationApp/package.json4
-rw-r--r--sdnr/wt/odlux/apps/configurationApp/src/actions/deviceActions.ts321
-rw-r--r--sdnr/wt/odlux/apps/configurationApp/src/components/uiElementSelection.tsx5
-rw-r--r--sdnr/wt/odlux/apps/configurationApp/src/handlers/valueSelectorHandler.ts4
-rw-r--r--sdnr/wt/odlux/apps/configurationApp/src/handlers/viewDescriptionHandler.ts55
-rw-r--r--sdnr/wt/odlux/apps/configurationApp/src/models/uiModels.ts34
-rw-r--r--sdnr/wt/odlux/apps/configurationApp/src/services/restServices.ts12
-rw-r--r--sdnr/wt/odlux/apps/configurationApp/src/views/configurationApplication.tsx271
-rw-r--r--sdnr/wt/odlux/apps/configurationApp/src/yang/yangParser.ts109
-rw-r--r--sdnr/wt/odlux/apps/connectApp/package.json4
-rw-r--r--sdnr/wt/odlux/apps/connectApp/src/components/editNetworkElementDialog.tsx5
-rw-r--r--sdnr/wt/odlux/apps/demoApp/package.json4
-rw-r--r--sdnr/wt/odlux/apps/eventLogApp/package.json4
-rw-r--r--sdnr/wt/odlux/apps/faultApp/package.json4
-rw-r--r--sdnr/wt/odlux/apps/helpApp/package.json4
-rw-r--r--sdnr/wt/odlux/apps/inventoryApp/package.json4
-rw-r--r--sdnr/wt/odlux/apps/inventoryApp/src/actions/inventoryTreeActions.ts104
-rw-r--r--sdnr/wt/odlux/apps/inventoryApp/src/actions/panelActions.ts31
-rw-r--r--sdnr/wt/odlux/apps/inventoryApp/src/fakeData/index.ts38
-rw-r--r--sdnr/wt/odlux/apps/inventoryApp/src/handlers/connectedNetworkElementsHandler.ts36
-rw-r--r--sdnr/wt/odlux/apps/inventoryApp/src/handlers/inventoryAppRootHandler.ts16
-rw-r--r--sdnr/wt/odlux/apps/inventoryApp/src/handlers/inventoryElementsHandler.ts (renamed from sdnr/wt/odlux/apps/inventoryApp/src/handlers/inventoryElementsHandler.tsx)0
-rw-r--r--sdnr/wt/odlux/apps/inventoryApp/src/handlers/inventoryTreeHandler.ts68
-rw-r--r--sdnr/wt/odlux/apps/inventoryApp/src/handlers/panelHandler.ts11
-rw-r--r--sdnr/wt/odlux/apps/inventoryApp/src/index.html3
-rw-r--r--sdnr/wt/odlux/apps/inventoryApp/src/models/inventory.ts9
-rw-r--r--sdnr/wt/odlux/apps/inventoryApp/src/models/networkElementConnection.ts37
-rw-r--r--sdnr/wt/odlux/apps/inventoryApp/src/models/panelId.ts19
-rw-r--r--sdnr/wt/odlux/apps/inventoryApp/src/pluginInventory.tsx36
-rw-r--r--sdnr/wt/odlux/apps/inventoryApp/src/services/inventoryService.ts41
-rw-r--r--sdnr/wt/odlux/apps/inventoryApp/src/views/dashboard.tsx195
-rw-r--r--sdnr/wt/odlux/apps/inventoryApp/src/views/treeview.tsx132
-rw-r--r--sdnr/wt/odlux/apps/inventoryApp/webpack.config.js4
-rw-r--r--sdnr/wt/odlux/apps/maintenanceApp/package.json4
-rw-r--r--sdnr/wt/odlux/apps/mediatorApp/package.json4
-rw-r--r--sdnr/wt/odlux/apps/minimumApp/package.json4
-rw-r--r--sdnr/wt/odlux/apps/performanceHistoryApp/package.json4
-rw-r--r--sdnr/wt/odlux/apps/performanceHistoryApp/src/components/toggleContainer.tsx3
-rw-r--r--sdnr/wt/odlux/apps/performanceHistoryApp/webpack.config.js6
40 files changed, 1361 insertions, 292 deletions
diff --git a/sdnr/wt/odlux/apps/apiDemo/package.json b/sdnr/wt/odlux/apps/apiDemo/package.json
index 44dae6854..8118f92c4 100644
--- a/sdnr/wt/odlux/apps/apiDemo/package.json
+++ b/sdnr/wt/odlux/apps/apiDemo/package.json
@@ -24,8 +24,8 @@
"@odlux/framework": "*"
},
"peerDependencies": {
- "@types/react": "16.9.11",
- "@types/react-dom": "16.9.4",
+ "@types/react": "16.9.19",
+ "@types/react-dom": "16.9.5",
"@types/react-router-dom": "4.3.1",
"@material-ui/core": "4.9.0",
"@material-ui/icons": "4.5.1",
diff --git a/sdnr/wt/odlux/apps/configurationApp/package.json b/sdnr/wt/odlux/apps/configurationApp/package.json
index b18d1ce10..7b9dce688 100644
--- a/sdnr/wt/odlux/apps/configurationApp/package.json
+++ b/sdnr/wt/odlux/apps/configurationApp/package.json
@@ -24,8 +24,8 @@
"@odlux/framework": "*"
},
"peerDependencies": {
- "@types/react": "16.9.11",
- "@types/react-dom": "16.9.4",
+ "@types/react": "16.9.19",
+ "@types/react-dom": "16.9.5",
"@types/react-router-dom": "4.3.1",
"@material-ui/core": "4.9.0",
"@material-ui/icons": "4.5.1",
diff --git a/sdnr/wt/odlux/apps/configurationApp/src/actions/deviceActions.ts b/sdnr/wt/odlux/apps/configurationApp/src/actions/deviceActions.ts
index 45cdfe64d..dbe95e0d1 100644
--- a/sdnr/wt/odlux/apps/configurationApp/src/actions/deviceActions.ts
+++ b/sdnr/wt/odlux/apps/configurationApp/src/actions/deviceActions.ts
@@ -2,12 +2,14 @@ import { Action } from '../../../../framework/src/flux/action';
import { Dispatch } from '../../../../framework/src/flux/store';
import { IApplicationStoreState } from "../../../../framework/src/store/applicationStore";
import { PushAction, ReplaceAction } from "../../../../framework/src/actions/navigationActions";
+import { AddErrorInfoAction } from "../../../../framework/src/actions/errorActions";
+import { DisplayModeType, DisplaySpecification } from '../handlers/viewDescriptionHandler';
import { restService } from "../services/restServices";
import { YangParser } from "../yang/yangParser";
import { Module } from "../models/yang";
-import { ViewSpecification, ViewElement, isViewElementReference, isViewElementList, isViewElementObjectOrList } from "../models/uiModels";
-import { AddErrorInfoAction } from "../../../../framework/src/actions/errorActions";
+import { ViewSpecification, ViewElement, isViewElementReference, isViewElementList, isViewElementObjectOrList, isViewElementRpc, isViewElementChoise, ViewElementChoiseCase } from "../models/uiModels";
+import { element } from 'prop-types';
export class EnableValueSelector extends Action {
constructor(public listSpecification: ViewSpecification, public listData: any[], public keyProperty: string, public onValueSelected : (value: any) => void ) {
@@ -34,7 +36,13 @@ export class UpdateDeviceDescription extends Action {
}
export class UpdatViewDescription extends Action {
- constructor(public vPath: string, public view: ViewSpecification, public viewData: any, public displayAsList: boolean = false, public key?: string ) {
+ constructor (public vPath: string, public viewData: any, public displaySpecification: DisplaySpecification = { displayMode: DisplayModeType.doNotDisplay } ) {
+ super();
+ }
+}
+
+export class UpdatOutputData extends Action {
+ constructor (public outputData: any) {
super();
}
}
@@ -159,7 +167,7 @@ const getReferencedDataList = async (refPath: string, dataPath: string, modules:
return null;
}
-const resolveViewDescription = (defaultNS: string | null, vPath: string, view: ViewSpecification, viewData: any, displayAsList: boolean = false, key?: string): UpdatViewDescription =>{
+const resolveViewDescription = (defaultNS: string | null, vPath: string, view: ViewSpecification): ViewSpecification =>{
// check if-feature | when | and resolve all references.
view = { ...view };
@@ -173,28 +181,72 @@ const resolveViewDescription = (defaultNS: string | null, vPath: string, view: V
}
return acc;
}, {});
- return new UpdatViewDescription(vPath, view, viewData, displayAsList, key);
+ return view;
}
+const flatenViewElements = (defaultNS: string | null, parentPath: string, elements: { [name: string]: ViewElement }, views: ViewSpecification[], currentPath: string ): { [name: string]: ViewElement } => {
+ if (!elements) return {};
+ return Object.keys(elements).reduce<{ [name: string]: ViewElement }>((acc, cur) => {
+ const elm = elements[cur];
+
+ // remove the detault namespace, and only the default namespace, sine it seems that this is also not in the restconf response
+ const elmKey = defaultNS && elm.id.replace(new RegExp(`^${defaultNS}:`, "i"), "") || elm.id;
+ const key = parentPath ? `${parentPath}.${elmKey}` : elmKey;
+
+ if (isViewElementRpc(elm)) {
+ console.warn(`Flaten of RFC not supported ! [${currentPath}][${elm.label}]`);
+ return acc;
+ } else if (isViewElementObjectOrList(elm)) {
+ const view = views[+elm.viewId];
+ const inner = view && flatenViewElements(defaultNS, key, view.elements, views, `${currentPath}/${view.name}`);
+ inner && Object.keys(inner).forEach(k => (acc[k] = inner[k]));
+ } else if (isViewElementChoise(elm)) {
+ acc[key] = {
+ ...elm,
+ id: key,
+ cases: Object.keys(elm.cases).reduce<{ [name: string]: ViewElementChoiseCase }>((accCases, curCases) => {
+ const caseElement = elm.cases[curCases];
+ accCases[curCases] = {
+ ...caseElement,
+ // Hint: do not use key it contains elmKey, which shell be omitted for cases.
+ elements: flatenViewElements(defaultNS, /*key*/ parentPath, caseElement.elements, views, `${currentPath}/${elm.label}`)
+ };
+ return accCases;
+ }, {}),
+ };
+ } else {
+ acc[key] = {
+ ...elm,
+ id: key,
+ };
+ }
+ return acc;
+ }, {});
+};
+
export const updateViewActionAsyncCreator = (vPath: string) => async (dispatch: Dispatch, getState: () => IApplicationStoreState) => {
const pathParts = splitVPath(vPath, /(?:([^\/\["]+)(?:\[([^\]]*)\])?)/g); // 1 = property / 2 = optional key
const { configuration: { deviceDescription: { nodeId, modules, views } }, framework: { navigationState } } = getState();
let dataPath = `/restconf/config/network-topology:network-topology/topology/topology-netconf/node/${nodeId}/yang-ext:mount`;
+
+ let inputViewSpecification: ViewSpecification | undefined = undefined;
+ let outputViewSpecification: ViewSpecification | undefined = undefined;
+
let viewSpecification: ViewSpecification = views[0];
let viewElement: ViewElement;
let dataMember: string;
let extractList: boolean = false;
- let currentNS : string | null = null;
- let defaultNS : string | null = null;
+ let currentNS: string | null = null;
+ let defaultNS: string | null = null;
dispatch(new SetCollectingSelectionData(true));
try {
for (let ind = 0; ind < pathParts.length; ++ind) {
const [property, key] = pathParts[ind];
const namespaceInd = property && property.indexOf(":") || -1;
- const namespace : string | null = namespaceInd > -1 ? (currentNS = property.slice(0, namespaceInd)) : currentNS;
+ const namespace: string | null = namespaceInd > -1 ? (currentNS = property.slice(0, namespaceInd)) : currentNS;
if (ind === 0) { defaultNS = namespace };
@@ -206,6 +258,7 @@ export const updateViewActionAsyncCreator = (vPath: string) => async (dispatch:
dispatch(new SetCollectingSelectionData(false));
throw new Error("No key for list [" + property + "]");
} else if (vPath.endsWith("[]") && pathParts.length - 1 === ind) {
+
// empty key is used for new element
if (viewElement && "viewId" in viewElement) viewSpecification = views[+viewElement.viewId];
const data = Object.keys(viewSpecification.elements).reduce<{ [name: string]: any }>((acc, cur) => {
@@ -215,7 +268,16 @@ export const updateViewActionAsyncCreator = (vPath: string) => async (dispatch:
}
return acc;
}, {});
- return dispatch(resolveViewDescription(defaultNS, vPath, viewSpecification, data, false, isViewElementList(viewElement!) && viewElement.key || undefined));
+
+ // create display specification
+ const ds: DisplaySpecification = {
+ displayMode: DisplayModeType.displayAsObject,
+ viewSpecification: resolveViewDescription(defaultNS, vPath, viewSpecification),
+ keyProperty: isViewElementList(viewElement!) && viewElement.key || undefined
+ };
+
+ // update display specification
+ return dispatch(new UpdatViewDescription(vPath, data, ds));
}
if (viewElement && isViewElementList(viewElement) && viewSpecification.parentView === "0") {
// check if there is a reference as key
@@ -247,11 +309,27 @@ export const updateViewActionAsyncCreator = (vPath: string) => async (dispatch:
extractList = false;
}
- if (viewElement && "viewId" in viewElement) viewSpecification = views[+viewElement.viewId];
+ if (viewElement && "viewId" in viewElement) {
+ viewSpecification = views[+viewElement.viewId];
+ } else if (viewElement.uiType === "rpc") {
+ viewSpecification = views[+(viewElement.inputViewId || 0)];
+
+ // create new instance & flaten
+ inputViewSpecification = viewElement.inputViewId != null && {
+ ...views[+(viewElement.inputViewId || 0)],
+ elements: flatenViewElements(defaultNS, "", views[+(viewElement.inputViewId || 0)].elements, views, viewElement.label),
+ } || undefined;
+ outputViewSpecification = viewElement.outputViewId != null && {
+ ...views[+(viewElement.outputViewId || 0)],
+ elements: flatenViewElements(defaultNS, "", views[+(viewElement.outputViewId || 0)].elements, views, viewElement.label),
+ } || undefined;
+
+ }
}
let data: any = {};
- if (viewSpecification && viewSpecification.id !== "0") {
+ // do not get any data from netconf if there is no view specified || this is the root element [0] || this is an rpc
+ if (viewSpecification && !(viewSpecification.id === "0" || viewElement!.uiType === "rpc")) {
const restResult = (await restService.getConfigData(dataPath));
if (!restResult.data) {
// special case: if this is a list without any response
@@ -259,7 +337,15 @@ export const updateViewActionAsyncCreator = (vPath: string) => async (dispatch:
if (!isViewElementList(viewElement!)) {
throw new Error(`vPath: [${vPath}]. ViewElement has no key.`);
}
- return dispatch(resolveViewDescription(defaultNS, vPath, viewSpecification, [], extractList, viewElement.key));
+ // create display specification
+ const ds: DisplaySpecification = {
+ displayMode: extractList ? DisplayModeType.displayAsList : DisplayModeType.displayAsObject,
+ viewSpecification: resolveViewDescription(defaultNS, vPath, viewSpecification),
+ keyProperty: viewElement.key
+ };
+
+ // update display specification
+ return dispatch(new UpdatViewDescription(vPath, [], ds));
}
throw new Error(`Did not get response from Server. Status: [${restResult.status}]`);
} else if (restResult.status < 200 || restResult.status > 299) {
@@ -278,9 +364,33 @@ export const updateViewActionAsyncCreator = (vPath: string) => async (dispatch:
data = extractList
? data[viewElement!.label] || [] // if the list is empty, it does not exist
: data;
+
+ } else if (viewElement! && viewElement!.uiType === "rpc") {
+ // set data to defaults
+ data = {};
+ inputViewSpecification && Object.keys(inputViewSpecification.elements).forEach(key => {
+ const elm = inputViewSpecification && inputViewSpecification.elements[key];
+ if (elm && elm.default != undefined) {
+ data[elm.id] = elm.default;
+ }
+ });
}
- return dispatch(resolveViewDescription(defaultNS, vPath, viewSpecification, data, extractList, isViewElementList(viewElement!) && viewElement.key || undefined));
+ // create display specification
+ const ds: DisplaySpecification = viewElement! && viewElement!.uiType === "rpc"
+ ? {
+ displayMode: DisplayModeType.displayAsRPC,
+ inputViewSpecification: inputViewSpecification && resolveViewDescription(defaultNS, vPath, inputViewSpecification),
+ outputViewSpecification: outputViewSpecification && resolveViewDescription(defaultNS, vPath, outputViewSpecification),
+ }
+ : {
+ displayMode: extractList ? DisplayModeType.displayAsList : DisplayModeType.displayAsObject,
+ viewSpecification: resolveViewDescription(defaultNS, vPath, viewSpecification),
+ keyProperty: isViewElementList(viewElement!) && viewElement.key || undefined,
+ };
+
+ // update display specification
+ return dispatch(new UpdatViewDescription(vPath, data, ds));
// https://beta.just-run.it/#/configuration/Sim12600/core-model:network-element/ltp[LTP-MWPS-TTP-01]
// https://beta.just-run.it/#/configuration/Sim12600/core-model:network-element/ltp[LTP-MWPS-TTP-01]/lp
} catch (error) {
@@ -290,7 +400,7 @@ export const updateViewActionAsyncCreator = (vPath: string) => async (dispatch:
} finally {
return;
}
-}
+};
export const updateDataActionAsyncCreator = (vPath: string, data: any) => async (dispatch: Dispatch, getState: () => IApplicationStoreState) => {
const pathParts = splitVPath(vPath, /(?:([^\/\["]+)(?:\[([^\]]*)\])?)/g); // 1 = property / 2 = optional key
@@ -313,7 +423,7 @@ export const updateDataActionAsyncCreator = (vPath: string, data: any) => async
const namespace: string | null = namespaceInd > -1 ? (currentNS = property.slice(0, namespaceInd)) : currentNS;
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 (isViewElementList(viewElement) && !key) {
@@ -330,7 +440,7 @@ export const updateDataActionAsyncCreator = (vPath: string, data: any) => async
isNew = key;
if (!key) {
dispatch(new SetCollectingSelectionData(false));
- throw new Error("No value for key [" + viewElement.key +"] in list [" + property + "]");
+ throw new Error("No value for key [" + viewElement.key + "] in list [" + property + "]");
}
}
}
@@ -363,14 +473,183 @@ export const updateDataActionAsyncCreator = (vPath: string, data: any) => async
}
}
- return isNew
- ? dispatch(new ReplaceAction(`/configuration/${nodeId}/${vPath.replace(/\[\]$/i,`[${isNew}]`)}`)) // navigate to new element
- : dispatch(resolveViewDescription(defaultNS, vPath, viewSpecification, data, embedList, isViewElementList(viewElement!) && viewElement.key || undefined));
+ if (isNew) {
+ return dispatch(new ReplaceAction(`/configuration/${nodeId}/${vPath.replace(/\[\]$/i, `[${isNew}]`)}`)) // navigate to new element
+ }
+
+ // create display specification
+ const ds: DisplaySpecification = {
+ displayMode: embedList ? DisplayModeType.displayAsList : DisplayModeType.displayAsObject,
+ viewSpecification: resolveViewDescription(defaultNS, vPath, viewSpecification),
+ keyProperty: isViewElementList(viewElement!) && viewElement.key || undefined,
+ };
+
+ // update display specification
+ return dispatch(new UpdatViewDescription(vPath, data, ds));
} catch (error) {
history.back();
dispatch(new AddErrorInfoAction({ title: "Problem", message: error.message || `Could not change ${dataPath}` }));
- dispatch(new SetCollectingSelectionData(false));
+
} finally {
+ dispatch(new SetCollectingSelectionData(false));
return;
}
-} \ No newline at end of file
+};
+
+export const removeElementActionAsyncCreator = (vPath: string) => async (dispatch: Dispatch, getState: () => IApplicationStoreState) => {
+ const pathParts = splitVPath(vPath, /(?:([^\/\["]+)(?:\[([^\]]*)\])?)/g); // 1 = property / 2 = optional key
+ const { configuration: { deviceDescription: { nodeId, views } } } = getState();
+ let dataPath = `/restconf/config/network-topology:network-topology/topology/topology-netconf/node/${nodeId}/yang-ext:mount`;
+ let viewSpecification: ViewSpecification = views[0];
+ let viewElement: ViewElement;
+
+ let currentNS: string | null = null;
+ let defaultNS: string | null = null;
+
+ dispatch(new SetCollectingSelectionData(true));
+ try {
+ for (let ind = 0; ind < pathParts.length; ++ind) {
+ let [property, key] = pathParts[ind];
+ const namespaceInd = property && property.indexOf(":") || -1;
+ const namespace: string | null = namespaceInd > -1 ? (currentNS = property.slice(0, namespaceInd)) : currentNS;
+
+ if (ind === 0) { defaultNS = namespace };
+ viewElement = viewSpecification.elements[property] || viewSpecification.elements[`${namespace}:${property}`];
+ if (!viewElement) throw Error("Property [" + property + "] does not exist.");
+
+ if (isViewElementList(viewElement) && !key) {
+ if (viewElement && viewElement.isList && viewSpecification.parentView === "0") {
+ throw new Error("Found a list at root level of a module w/o a refenrece key.");
+ }
+ if (pathParts.length - 1 > ind) {
+ dispatch(new SetCollectingSelectionData(false));
+ throw new Error("No key for list [" + property + "]");
+ } else if (vPath.endsWith("[]") && pathParts.length - 1 === ind) {
+ // remove the whole table
+ }
+ }
+
+ dataPath += `/${property}${key ? `/${key.replace(/\//ig, "%2F")}` : ""}`;
+
+ if (viewElement && "viewId" in viewElement) {
+ viewSpecification = views[+viewElement.viewId];
+ } else if (viewElement.uiType === "rpc") {
+ viewSpecification = views[+(viewElement.inputViewId || 0)];
+ }
+ }
+
+ const updateResult = await restService.removeConfigElement(dataPath);
+ if (updateResult.status < 200 || updateResult.status > 299) {
+ const message = updateResult.data && updateResult.data.errors && updateResult.data.errors.error && updateResult.data.errors.error[0] && updateResult.data.errors.error[0]["error-message"] || "";
+ throw new Error(`Server Error. Status: [${updateResult.status}]\n${message || updateResult.message || ''}`);
+ }
+ } catch (error) {
+ dispatch(new AddErrorInfoAction({ title: "Problem", message: error.message || `Could not remove ${dataPath}` }));
+ } finally {
+ dispatch(new SetCollectingSelectionData(false));
+ }
+
+
+};
+
+export const executeRpcActionAsyncCreator = (vPath: string, data: any) => async (dispatch: Dispatch, getState: () => IApplicationStoreState) => {
+ const pathParts = splitVPath(vPath, /(?:([^\/\["]+)(?:\[([^\]]*)\])?)/g); // 1 = property / 2 = optional key
+ const { configuration: { deviceDescription: { nodeId, views }, viewDescription: oldViewDescription } } = getState();
+ let dataPath = `/restconf/operations/network-topology:network-topology/topology/topology-netconf/node/${nodeId}/yang-ext:mount`;
+ let viewSpecification: ViewSpecification = views[0];
+ let viewElement: ViewElement;
+ let dataMember: string;
+ let embedList: boolean = false;
+ let isNew: string | false = false;
+
+ let currentNS: string | null = null;
+ let defaultNS: string | null = null;
+
+ dispatch(new SetCollectingSelectionData(true));
+ try {
+ for (let ind = 0; ind < pathParts.length; ++ind) {
+ let [property, key] = pathParts[ind];
+ const namespaceInd = property && property.indexOf(":") || -1;
+ const namespace: string | null = namespaceInd > -1 ? (currentNS = property.slice(0, namespaceInd)) : currentNS;
+
+ if (ind === 0) { defaultNS = namespace };
+ viewElement = viewSpecification.elements[property] || viewSpecification.elements[`${namespace}:${property}`];
+ if (!viewElement) throw Error("Property [" + property + "] does not exist.");
+
+ if (isViewElementList(viewElement) && !key) {
+ embedList = true;
+ // if (viewElement && viewElement.isList && viewSpecification.parentView === "0") {
+ // throw new Error("Found a list at root level of a module w/o a refenrece key.");
+ // }
+ // if (pathParts.length - 1 > ind) {
+ // dispatch(new SetCollectingSelectionData(false));
+ // throw new Error("No key for list [" + property + "]");
+ // } else if (vPath.endsWith("[]") && pathParts.length - 1 === ind) {
+ // // handle new element
+ // key = viewElement.key && String(data[viewElement.key]) || "";
+ // isNew = key;
+ // if (!key) {
+ // dispatch(new SetCollectingSelectionData(false));
+ // throw new Error("No value for key [" + viewElement.key + "] in list [" + property + "]");
+ // }
+ // }
+ }
+
+ dataPath += `/${property}${key ? `/${key.replace(/\//ig, "%2F")}` : ""}`;
+ dataMember = viewElement.label;
+ embedList = false;
+
+ if (viewElement && "viewId" in viewElement) {
+ viewSpecification = views[+viewElement.viewId];
+ } else if (viewElement.uiType === "rpc") {
+ viewSpecification = views[+(viewElement.inputViewId || 0)];
+ }
+ }
+
+ // re-inflate formerly flatten rpc data
+ data = data && Object.keys(data).reduce < { [name: string ]: any }>((acc, cur) => {
+ const pathParts = cur.split(".");
+ let pos = 0;
+ const updatePath = (obj: any, key: string) => {
+ obj[key] = (pos >= pathParts.length)
+ ? data[cur]
+ : updatePath(obj[key] || {}, pathParts[pos++]);
+ return obj;
+ }
+ updatePath(acc, pathParts[pos++]);
+ return acc;
+ }, {}) || null;
+
+ // embed the list -> key: list
+ data = embedList
+ ? { [viewElement!.label]: data }
+ : data;
+
+ // embed the first element list[key]
+ data = isNew
+ ? [data]
+ : data;
+
+ // do not post root member (0)
+ if ((viewSpecification && viewSpecification.id !== "0") || (dataMember! && !data)) {
+ const updateResult = await restService.executeRpc(dataPath, { [`${defaultNS}:input`]: data || {} });
+ if (updateResult.status < 200 || updateResult.status > 299) {
+ const message = updateResult.data && updateResult.data.errors && updateResult.data.errors.error && updateResult.data.errors.error[0] && updateResult.data.errors.error[0]["error-message"] || "";
+ throw new Error(`Server Error. Status: [${updateResult.status}]\n${message || updateResult.message || ''}`);
+ }
+ const viewData = { ...oldViewDescription.viewData, output: updateResult.data || null};
+ dispatch(new UpdatOutputData(viewData));
+ } else {
+ throw new Error(`There is NO RPC specified.`);
+ }
+
+
+ // // update display specification
+ // return
+ } catch (error) {
+ dispatch(new AddErrorInfoAction({ title: "Problem", message: error.message || `Could not change ${dataPath}` }));
+
+ } finally {
+ dispatch(new SetCollectingSelectionData(false));
+ }
+};
diff --git a/sdnr/wt/odlux/apps/configurationApp/src/components/uiElementSelection.tsx b/sdnr/wt/odlux/apps/configurationApp/src/components/uiElementSelection.tsx
index d46076338..8b0b890d0 100644
--- a/sdnr/wt/odlux/apps/configurationApp/src/components/uiElementSelection.tsx
+++ b/sdnr/wt/odlux/apps/configurationApp/src/components/uiElementSelection.tsx
@@ -39,6 +39,7 @@ export const UiElementSelection = (props: selectionProps) => {
? (<FormControl style={{ width: 485, marginLeft: 20, marginRight: 20 }}>
<InputLabel htmlFor={`select-${element.id}`} >{element.label}</InputLabel>
<Select
+ title={(element.options.find(o => o.key === value.toString().toLowerCase()) || { description: undefined }).description}
required={!!element.mandatory}
error={!!error}
onChange={(e) => { props.onChange(e.target.value as string) }}
@@ -50,7 +51,9 @@ export const UiElementSelection = (props: selectionProps) => {
id: `select-${element.id}`,
}}
>
- {element.options.map(option => (<MenuItem key={option.key} title={option.description} value={option.key}>{option.key}</MenuItem>))}
+ {element.options.map(option => (
+ <MenuItem key={option.key} title={option.description} value={option.key}>{option.key}</MenuItem>
+ ))}
</Select>
<FormHelperText>{error}</FormHelperText>
</FormControl>)
diff --git a/sdnr/wt/odlux/apps/configurationApp/src/handlers/valueSelectorHandler.ts b/sdnr/wt/odlux/apps/configurationApp/src/handlers/valueSelectorHandler.ts
index 9af640f16..5b2d55ee2 100644
--- a/sdnr/wt/odlux/apps/configurationApp/src/handlers/valueSelectorHandler.ts
+++ b/sdnr/wt/odlux/apps/configurationApp/src/handlers/valueSelectorHandler.ts
@@ -18,7 +18,7 @@
import { IActionHandler } from "../../../../framework/src/flux/action";
import { ViewSpecification } from "../models/uiModels";
-import { EnableValueSelector, SetSelectedValue, UpdateDeviceDescription, SetCollectingSelectionData, UpdatViewDescription } from "../actions/deviceActions";
+import { EnableValueSelector, SetSelectedValue, UpdateDeviceDescription, SetCollectingSelectionData, UpdatViewDescription, UpdatOutputData } from "../actions/deviceActions";
export interface IValueSelectorState {
collectingData: boolean;
@@ -62,7 +62,7 @@ export const valueSelectorHandler: IActionHandler<IValueSelectorState> = (state
onValueSelected: nc,
listData: [],
};
- } else if (action instanceof UpdateDeviceDescription || action instanceof UpdatViewDescription) {
+ } else if (action instanceof UpdateDeviceDescription || action instanceof UpdatViewDescription || action instanceof UpdatOutputData) {
state = {
...state,
collectingData: false,
diff --git a/sdnr/wt/odlux/apps/configurationApp/src/handlers/viewDescriptionHandler.ts b/sdnr/wt/odlux/apps/configurationApp/src/handlers/viewDescriptionHandler.ts
index c1b3350b2..69710b9e9 100644
--- a/sdnr/wt/odlux/apps/configurationApp/src/handlers/viewDescriptionHandler.ts
+++ b/sdnr/wt/odlux/apps/configurationApp/src/handlers/viewDescriptionHandler.ts
@@ -18,31 +18,42 @@
import { IActionHandler } from "../../../../framework/src/flux/action";
-import { UpdatViewDescription } from "../actions/deviceActions";
+import { UpdatViewDescription, UpdatOutputData } from "../actions/deviceActions";
import { ViewSpecification } from "../models/uiModels";
+export enum DisplayModeType {
+ doNotDisplay = 0,
+ displayAsObject = 1,
+ displayAsList = 2,
+ displayAsRPC = 3
+};
+
+export type DisplaySpecification = {
+ displayMode: DisplayModeType.doNotDisplay;
+} | {
+ displayMode: DisplayModeType.displayAsObject | DisplayModeType.displayAsList ;
+ viewSpecification: ViewSpecification;
+ keyProperty: string | undefined;
+} | {
+ displayMode: DisplayModeType.displayAsRPC;
+ inputViewSpecification?: ViewSpecification;
+ outputViewSpecification?: ViewSpecification;
+}
+
export interface IViewDescriptionState {
vPath: string | null;
- keyProperty: string | undefined;
- displayAsList: boolean;
- viewSpecification: ViewSpecification;
- viewData: any
+ displaySpecification: DisplaySpecification;
+ viewData: any,
+ outputData?: any,
}
const viewDescriptionStateInit: IViewDescriptionState = {
vPath: null,
- keyProperty: undefined,
- displayAsList: false,
- viewSpecification: {
- id: "empty",
- canEdit: false,
- parentView: "",
- name: "emplty",
- language: "en-US",
- title: "empty",
- elements: {}
+ displaySpecification: {
+ displayMode: DisplayModeType.doNotDisplay,
},
- viewData: null
+ viewData: null,
+ outputData: undefined,
};
export const viewDescriptionHandler: IActionHandler<IViewDescriptionState> = (state = viewDescriptionStateInit, action) => {
@@ -50,11 +61,15 @@ export const viewDescriptionHandler: IActionHandler<IViewDescriptionState> = (st
state = {
...state,
vPath: action.vPath,
- keyProperty: action.key,
- displayAsList: action.displayAsList,
- viewSpecification: action.view,
viewData: action.viewData,
- }
+ outputData: undefined,
+ displaySpecification: action.displaySpecification,
+ };
+ } else if (action instanceof UpdatOutputData) {
+ state = {
+ ...state,
+ outputData: action.outputData,
+ };
}
return state;
};
diff --git a/sdnr/wt/odlux/apps/configurationApp/src/models/uiModels.ts b/sdnr/wt/odlux/apps/configurationApp/src/models/uiModels.ts
index 7b41c3845..f0391eebf 100644
--- a/sdnr/wt/odlux/apps/configurationApp/src/models/uiModels.ts
+++ b/sdnr/wt/odlux/apps/configurationApp/src/models/uiModels.ts
@@ -109,12 +109,28 @@ export type ViewElementUnion = ViewElementBase & {
"elements": ViewElement[];
}
+export type ViewElementChoiseCase = { id: string, label: string, description?: string, elements: { [name: string]: ViewElement } };
+
export type ViewElementChoise = ViewElementBase & {
"uiType": "choise";
- "cases": { [name: string]: { id: string, label: string, description?: string, elements: { [name: string]: ViewElement } } };
+ "cases": {
+ [name: string]: ViewElementChoiseCase;
+ }
+}
+
+// https://tools.ietf.org/html/rfc7950#section-7.14.1
+export type ViewElementRpc = ViewElementBase & {
+ "uiType": "rpc";
+ "inputViewId"?: string;
+ "outputViewId"?: string;
+}
+
+export type ViewElementEmpty = ViewElementBase & {
+ "uiType": "empty";
}
export type ViewElement =
+ | ViewElementEmpty
| ViewElementBits
| ViewElementBinary
| ViewElementString
@@ -125,7 +141,8 @@ export type ViewElement =
| ViewElementSelection
| ViewElementReference
| ViewElementUnion
- | ViewElementChoise;
+ | ViewElementChoise
+ | ViewElementRpc;
export const isViewElementString = (viewElement: ViewElement): viewElement is ViewElementString => {
return viewElement && viewElement.uiType === "string";
@@ -167,16 +184,25 @@ export const isViewElementChoise = (viewElement: ViewElement): viewElement is Vi
return viewElement && viewElement.uiType === "choise";
}
+export const isViewElementRpc = (viewElement: ViewElement): viewElement is ViewElementRpc => {
+ return viewElement && viewElement.uiType === "rpc";
+}
+
+export const isViewElementEmpty = (viewElement: ViewElement): viewElement is ViewElementRpc => {
+ return viewElement && viewElement.uiType === "empty";
+}
+
+export const ResolveFunction = Symbol("IsResolved");
export type ViewSpecification = {
"id": string;
- "name": string;
+ "name"?: string;
"title"?: string;
"parentView"?: string;
"language": string;
"ifFeature"?: string;
"when"?: string;
- "uses"?: string[];
+ "uses"?: (string[]) & { [ResolveFunction]?: () => void };
"elements": { [name: string]: ViewElement };
readonly "canEdit": boolean;
}
diff --git a/sdnr/wt/odlux/apps/configurationApp/src/services/restServices.ts b/sdnr/wt/odlux/apps/configurationApp/src/services/restServices.ts
index 0d28e6653..b260f1ffb 100644
--- a/sdnr/wt/odlux/apps/configurationApp/src/services/restServices.ts
+++ b/sdnr/wt/odlux/apps/configurationApp/src/services/restServices.ts
@@ -57,6 +57,18 @@ class RestService {
public setConfigData(path: string, data: any) {
return requestRestExt<{ [key: string]: any }>(path, { method: "PUT", body: JSON.stringify(data) });
}
+
+ public executeRpc(path: string, data: any) {
+ return requestRestExt<{ [key: string]: any }>(path, { method: "POST", body: JSON.stringify(data) });
+ }
+
+ /** Removes the element by restconf path.
+ * @param path The restconf path to identify the note to update.
+ * @returns The restconf result.
+ */
+ public removeConfigElement(path: string) {
+ return requestRestExt<{ [key: string]: any }>(path, { method: "DELETE" });
+ }
}
export const restService = new RestService();
diff --git a/sdnr/wt/odlux/apps/configurationApp/src/views/configurationApplication.tsx b/sdnr/wt/odlux/apps/configurationApp/src/views/configurationApplication.tsx
index 06de39b9d..385f3b52f 100644
--- a/sdnr/wt/odlux/apps/configurationApp/src/views/configurationApplication.tsx
+++ b/sdnr/wt/odlux/apps/configurationApp/src/views/configurationApplication.tsx
@@ -25,9 +25,11 @@ import connect, { IDispatcher, Connect } from "../../../../framework/src/flux/co
import { IApplicationStoreState } from "../../../../framework/src/store/applicationStore";
import MaterialTable, { ColumnModel, ColumnType, MaterialTableCtorType } from "../../../../framework/src/components/material-table";
import { Loader } from "../../../../framework/src/components/material-ui/loader";
+import { renderObject } from '../../../../framework/src/components/objectDump';
-import { SetSelectedValue, splitVPath, updateDataActionAsyncCreator, updateViewActionAsyncCreator } from "../actions/deviceActions";
-import { ViewSpecification, isViewElementString, isViewElementNumber, isViewElementBoolean, isViewElementObjectOrList, isViewElementSelection, isViewElementChoise, ViewElement, ViewElementChoise, isViewElementUnion } from "../models/uiModels";
+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 Fab from '@material-ui/core/Fab';
import AddIcon from '@material-ui/icons/Add';
@@ -36,17 +38,14 @@ import RemoveIcon from '@material-ui/icons/RemoveCircleOutline';
import SaveIcon from '@material-ui/icons/Save';
import EditIcon from '@material-ui/icons/Edit';
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 InputAdornment from "@material-ui/core/InputAdornment";
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 Link from "@material-ui/core/Link";
-import FormHelperText from '@material-ui/core/FormHelperText';
import { UIElementReference } from '../components/uiElementReference';
import { UiElementNumber } from '../components/uiElementNumber';
@@ -54,6 +53,7 @@ import { UiElementString } from '../components/uiElementString';
import { UiElementBoolean } from '../components/uiElementBoolean';
import { UiElementSelection } from '../components/uiElementSelection';
import { UIElementUnion } from '../components/uiElementUnion';
+import { Button } from '@material-ui/core';
const styles = (theme: Theme) => createStyles({
header: {
@@ -110,6 +110,9 @@ const styles = (theme: Theme) => createStyles({
},
},
},
+ uiView: {
+ overflowY: "auto",
+ },
section: {
padding: "15px",
borderBottom: `2px solid ${theme.palette.divider}`,
@@ -130,15 +133,16 @@ const mapProps = (state: IApplicationStoreState) => ({
vPath: state.configuration.viewDescription.vPath,
nodeId: state.configuration.deviceDescription.nodeId,
viewData: state.configuration.viewDescription.viewData,
- viewSpecification: state.configuration.viewDescription.viewSpecification,
- displayAsList: state.configuration.viewDescription.displayAsList,
- keyProperty: state.configuration.viewDescription.keyProperty,
+ outputData: state.configuration.viewDescription.outputData,
+ displaySpecification: state.configuration.viewDescription.displaySpecification,
});
const mapDispatch = (dispatcher: IDispatcher) => ({
onValueSelected: (value: any) => dispatcher.dispatch(new SetSelectedValue(value)),
onUpdateData: (vPath: string, data: any) => dispatcher.dispatch(updateDataActionAsyncCreator(vPath, data)),
reloadView: (vPath: string) => dispatcher.dispatch(updateViewActionAsyncCreator(vPath)),
+ removeElement: (vPath: string) => dispatcher.dispatch(removeElementActionAsyncCreator(vPath)),
+ executeRpc: (vPath: string, data: any) => dispatcher.dispatch(executeRpcActionAsyncCreator(vPath, data)),
});
const SelectElementTable = MaterialTable as MaterialTableCtorType<{ [key: string]: any }>;
@@ -171,49 +175,59 @@ class ConfigurationApplicationComponent extends React.Component<ConfigurationApp
}
}
+ 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 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 } } }) || {}
+ 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;
}
@@ -233,10 +247,46 @@ class ConfigurationApplicationComponent extends React.Component<ConfigurationApp
});
}
+ 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 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)) {
+ if (isViewElementEmpty(uiElement)) {
+ return null;
+ } else if (isViewElementSelection(uiElement)) {
return <UiElementSelection
key={uiElement.id}
@@ -283,8 +333,7 @@ class ConfigurationApplicationComponent extends React.Component<ConfigurationApp
readOnly={!canEdit}
disabled={editMode && !canEdit}
onChange={(e) => { this.changeValueFor(uiElement.id, e) }} />
- }
- else {
+ } else {
if (process.env.NODE_ENV !== "production") {
console.error(`Unknown element type - ${(uiElement as any).uiType} in ${(uiElement as any).id}.`)
}
@@ -391,16 +440,18 @@ class ConfigurationApplicationComponent extends React.Component<ConfigurationApp
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[] });
+ }, { 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
? (
@@ -425,7 +476,16 @@ class ConfigurationApplicationComponent extends React.Component<ConfigurationApp
</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>
);
};
@@ -442,7 +502,7 @@ class ConfigurationApplicationComponent extends React.Component<ConfigurationApp
}
};
- const { classes } = this.props;
+ const { classes, removeElement } = this.props;
return (
<SelectElementTable stickyHeader idProperty={listKeyProperty} rows={listData} customActionButtons={[addNewElementAction]} columns={
@@ -457,11 +517,13 @@ class ConfigurationApplicationComponent extends React.Component<ConfigurationApp
}
return acc;
}, []).concat([{
- property: "Actions", disableFilter: true, disableSorting: true, type: ColumnType.custom, customControl: (row => {
+ property: "Actions", disableFilter: true, disableSorting: true, type: ColumnType.custom, customControl: ( ({ rowData })=> {
return (
<Tooltip title={"Remove"} >
- <IconButton className={classes.button} onClick={event => {
-
+ <IconButton className={classes.button} onClick={(e) => {
+ e.stopPropagation();
+ e.preventDefault();
+ removeElement(`${this.props.vPath}[${rowData[listKeyProperty]}]`)
}} >
<RemoveIcon />
</IconButton>
@@ -476,10 +538,73 @@ class ConfigurationApplicationComponent extends React.Component<ConfigurationApp
);
}
+ 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.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))}
+ </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>
+ {outputViewData !== undefined
+ ? (
+ renderObject(outputViewData) )
+ : null
+ }
+ </>
+ );
+ };
+
private renderBreadCrumps() {
const { editMode } = this.state;
- const { viewSpecification, displayAsList } = this.props;
- const { vPath, match: { url, path }, nodeId } = this.props;
+ 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}`;
@@ -527,33 +652,11 @@ class ConfigurationApplicationComponent extends React.Component<ConfigurationApp
}} ><ArrowBack /></Fab>
) || null}
{ /* do not show edit if this is a list or it can't be edited */
- !displayAsList && viewSpecification.canEdit && (<div>
+ 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 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 });
-
+ const resultingViewData = this.collectData(displaySpecification.viewSpecification.elements);
this.props.onUpdateData(this.props.vPath!, resultingViewData);
}
this.setState({ editMode: !editMode });
@@ -595,15 +698,19 @@ class ConfigurationApplicationComponent extends React.Component<ConfigurationApp
}
private renderValueEditor() {
- const { keyProperty, displayAsList, viewSpecification } = this.props;
+ const { displaySpecification: ds, outputData } = this.props;
const { viewData, editMode, isNew } = this.state;
return (
<div className={this.props.classes.container}>
{this.renderBreadCrumps()}
- {displayAsList && viewData instanceof Array
- ? this.renderUIViewList(viewSpecification, keyProperty!, viewData)
- : this.renderUIView(viewSpecification, viewData!, keyProperty, editMode, isNew)
+ {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 >
);
diff --git a/sdnr/wt/odlux/apps/configurationApp/src/yang/yangParser.ts b/sdnr/wt/odlux/apps/configurationApp/src/yang/yangParser.ts
index 27859f7b6..b1c1e7430 100644
--- a/sdnr/wt/odlux/apps/configurationApp/src/yang/yangParser.ts
+++ b/sdnr/wt/odlux/apps/configurationApp/src/yang/yangParser.ts
@@ -15,10 +15,12 @@
* the License.
* ============LICENSE_END==========================================================================
*/
-
-
import { Token, Statement, Module, Identity } from "../models/yang";
-import { ViewSpecification, ViewElement, isViewElementObjectOrList, ViewElementBase, isViewElementReference, ViewElementChoise, ViewElementBinary, ViewElementString, isViewElementString, isViewElementNumber, ViewElementNumber, Expression, YangRange, ViewElementUnion } from "../models/uiModels";
+import {
+ ViewSpecification, ViewElement, isViewElementObjectOrList, ViewElementBase,
+ isViewElementReference, ViewElementChoise, ViewElementBinary, ViewElementString, isViewElementString,
+ isViewElementNumber, ViewElementNumber, Expression, YangRange, ViewElementUnion, ViewElementRpc, isViewElementRpc, ResolveFunction
+} from "../models/uiModels";
import { yangService } from "../services/yangService";
export const splitVPath = (vPath: string, vPathParser: RegExp): RegExpMatchArray[] => {
@@ -263,7 +265,8 @@ class YangLexer {
}
export class YangParser {
- private _groupingsToResolve: (() => void)[] = [];
+ private _groupingsToResolve: ViewSpecification[] = [];
+
private _identityToResolve: (() => void)[] = [];
private _unionsToResolve: (() => void)[] = [];
private _modulesToResolve: (() => void)[] = [];
@@ -410,11 +413,13 @@ export class YangParser {
this._modulesToResolve.push(() => {
Object.keys(module.elements).forEach(key => {
const viewElement = module.elements[key];
- if (!isViewElementObjectOrList(viewElement)) {
- throw new Error(`Module: [${module}]. Only List or Object allowed on root level.`);
+ if (!(isViewElementObjectOrList(viewElement) || isViewElementRpc(viewElement))) {
+ console.error(new Error(`Module: [${module}]. Only Object, List or RPC are allowed on root level.`));
+ }
+ if (isViewElementObjectOrList(viewElement)) {
+ const viewIdIndex = Number(viewElement.viewId);
+ module.views[key] = this._views[viewIdIndex];
}
- const viewIdIndex = Number(viewElement.viewId);
- module.views[key] = this._views[viewIdIndex];
this._views[0].elements[key] = module.elements[key];
});
});
@@ -431,8 +436,8 @@ export class YangParser {
});
// process all groupings
- this._groupingsToResolve.forEach(cb => {
- try { cb(); } catch (error) {
+ this._groupingsToResolve.filter(vs => vs.uses && vs.uses[ResolveFunction]).forEach(vs => {
+ try { vs.uses![ResolveFunction]!(); } catch (error) {
console.warn(`Error resolving: [${error.message}]`);
}
});
@@ -470,7 +475,6 @@ export class YangParser {
return result;
}
-
const baseIdentites: Identity[] = [];
Object.keys(this.modules).forEach(modKey => {
const module = this.modules[modKey];
@@ -478,7 +482,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);
}
@@ -765,20 +769,60 @@ export class YangParser {
}, []));
}
- const rpcs = this.extractNodes(statement, "rpc");
- if (rpcs && rpcs.length > 0) {
- // todo:
- }
+ const rpcStms = this.extractNodes(statement, "rpc");
+ if (rpcStms && rpcStms.length > 0) {
+ elements.push(...rpcStms.reduce<ViewElementRpc[]>((accRpc, curRpc) => {
+ if (!curRpc.arg) {
+ throw new Error(`Module: [${context.name}]${currentPath}. Found rpc without name.`);
+ }
+
+ const description = this.extractValue(curRpc, "description") || undefined;
+ const configValue = this.extractValue(curRpc, "config");
+ const config = configValue == null ? true : configValue.toLocaleLowerCase() !== "false";
+
+ let inputViewId: string | undefined = undefined;
+ let outputViewId: string | undefined = undefined;
+
+ const input = this.extractNodes(curRpc, "input") || undefined;
+ const output = this.extractNodes(curRpc, "output") || undefined;
+
+ if (input && input.length > 0) {
+ const [inputView, inputSubViews] = this.extractSubViews(input[0], parentId, context, `${currentPath}/${context.name}:${curRpc.arg}`);
+ subViews.push(inputView, ...inputSubViews);
+ inputViewId = inputView.id;
+ }
+
+ if (output && output.length > 0) {
+ const [outputView, outputSubViews] = this.extractSubViews(output[0], parentId, context, `${currentPath}/${context.name}:${curRpc.arg}`);
+ subViews.push(outputView, ...outputSubViews);
+ outputViewId = outputView.id;
+ }
- if (!statement.arg) {
- throw new Error(`Module: [${context.name}]. Found statement without name.`);
+ const element: ViewElementRpc = {
+ uiType: "rpc",
+ id: parentId === 0 ? `${context.name}:${curRpc.arg}` : curRpc.arg,
+ label: curRpc.arg,
+ config: config,
+ description: description,
+ inputViewId: inputViewId,
+ outputViewId: outputViewId,
+ };
+
+ accRpc.push(element);
+
+ return accRpc;
+ }, []));
}
+ // if (!statement.arg) {
+ // throw new Error(`Module: [${context.name}]. Found statement without name.`);
+ // }
+
const viewSpec: ViewSpecification = {
id: String(currentId),
parentView: String(parentId),
- name: statement.arg,
- title: statement.arg,
+ name: statement.arg != null ? statement.arg : undefined,
+ title: statement.arg != null ? statement.arg : undefined,
language: "en-us",
canEdit: false,
ifFeature: ifFeature,
@@ -804,6 +848,8 @@ export class YangParser {
if (usesRefs && usesRefs.length > 0) {
viewSpec.uses = (viewSpec.uses || []);
+ const resolveFunctions : (()=>void)[] = [];
+
for (let i = 0; i < usesRefs.length; ++i) {
const groupingName = usesRefs[i].arg;
if (!groupingName) {
@@ -812,9 +858,14 @@ export class YangParser {
viewSpec.uses.push(this.resolveReferencePath(groupingName, context));
- this._groupingsToResolve.push(() => {
+ resolveFunctions.push(() => {
const groupingViewSpec = this.resolveGrouping(groupingName, context);
if (groupingViewSpec) {
+
+ // resolve recursive
+ const resolveFunc = groupingViewSpec.uses && groupingViewSpec.uses[ResolveFunction];
+ resolveFunc && resolveFunc();
+
Object.keys(groupingViewSpec.elements).forEach(key => {
const elm = groupingViewSpec.elements[key];
viewSpec.elements[key] = {
@@ -826,6 +877,19 @@ export class YangParser {
}
});
}
+
+ viewSpec.uses[ResolveFunction] = () => {
+ resolveFunctions.forEach(res => {
+ try {
+ res();
+ } catch (error) {
+ console.error(error);
+ }
+ });
+ viewSpec?.uses![ResolveFunction] = undefined;
+ }
+
+ this._groupingsToResolve.push(viewSpec);
}
return [viewSpec, subViews];
@@ -1117,10 +1181,9 @@ export class YangParser {
/* 9.11. The empty Built-In Type
The empty built-in type represents a leaf that does not have any
value, it conveys information by its presence or absence. */
- console.warn(`found type: empty in [${module.name}][${currentPath}][${element.label}]`);
return {
...element,
- uiType: "string",
+ uiType: "empty",
};
} else if (type === "union") {
// todo: ❗ handle union ⚡
diff --git a/sdnr/wt/odlux/apps/connectApp/package.json b/sdnr/wt/odlux/apps/connectApp/package.json
index 774daba6b..1efd282c4 100644
--- a/sdnr/wt/odlux/apps/connectApp/package.json
+++ b/sdnr/wt/odlux/apps/connectApp/package.json
@@ -24,8 +24,8 @@
"@odlux/framework": "*"
},
"peerDependencies": {
- "@types/react": "16.9.11",
- "@types/react-dom": "16.9.4",
+ "@types/react": "16.9.19",
+ "@types/react-dom": "16.9.5",
"@types/react-router-dom": "4.3.1",
"@material-ui/core": "4.9.0",
"@material-ui/icons": "4.5.1",
diff --git a/sdnr/wt/odlux/apps/connectApp/src/components/editNetworkElementDialog.tsx b/sdnr/wt/odlux/apps/connectApp/src/components/editNetworkElementDialog.tsx
index e3b640120..3b4cf3bb5 100644
--- a/sdnr/wt/odlux/apps/connectApp/src/components/editNetworkElementDialog.tsx
+++ b/sdnr/wt/odlux/apps/connectApp/src/components/editNetworkElementDialog.tsx
@@ -61,16 +61,19 @@ const mapDispatch = (dispatcher: IDispatcher) => ({
editNetworkElement: async (element: UpdateNetworkElement, mountElement: NetworkElementConnection) => {
const values = Object.keys(element);
+ console.log("edit element");
+ console.log(values);
//make sure properties are there in case they get renamed
const idProperty = propertyOf<UpdateNetworkElement>("id");
const isRequiredProperty = propertyOf<UpdateNetworkElement>("isRequired");
+
if (values.length === 2 && values.includes(idProperty as string) && values.includes(isRequiredProperty as string)) {
// do not mount network element, if only isRequired is changed
await dispatcher.dispatch(editNetworkElementAsyncActionCreator(element));
- } else {
+ } else if(!(values.length===1 &&values.includes(idProperty as string))) { //do not edit or mount element, if only id was saved into object (no changes made!)
await dispatcher.dispatch(editNetworkElementAsyncActionCreator(element));
await dispatcher.dispatch(mountNetworkElementAsyncActionCreator(mountElement));
}
diff --git a/sdnr/wt/odlux/apps/demoApp/package.json b/sdnr/wt/odlux/apps/demoApp/package.json
index b5857099b..f30a922fe 100644
--- a/sdnr/wt/odlux/apps/demoApp/package.json
+++ b/sdnr/wt/odlux/apps/demoApp/package.json
@@ -24,8 +24,8 @@
"@odlux/framework": "*"
},
"peerDependencies": {
- "@types/react": "16.9.11",
- "@types/react-dom": "16.9.4",
+ "@types/react": "16.9.19",
+ "@types/react-dom": "16.9.5",
"@types/react-router-dom": "4.3.1",
"@material-ui/core": "4.9.0",
"@material-ui/icons": "4.5.1",
diff --git a/sdnr/wt/odlux/apps/eventLogApp/package.json b/sdnr/wt/odlux/apps/eventLogApp/package.json
index ce8b8859d..f182374cf 100644
--- a/sdnr/wt/odlux/apps/eventLogApp/package.json
+++ b/sdnr/wt/odlux/apps/eventLogApp/package.json
@@ -24,8 +24,8 @@
"@odlux/framework": "*"
},
"peerDependencies": {
- "@types/react": "16.9.11",
- "@types/react-dom": "16.9.4",
+ "@types/react": "16.9.19",
+ "@types/react-dom": "16.9.5",
"@types/react-router-dom": "4.3.1",
"@material-ui/core": "4.9.0",
"@material-ui/icons": "4.5.1",
diff --git a/sdnr/wt/odlux/apps/faultApp/package.json b/sdnr/wt/odlux/apps/faultApp/package.json
index b922df351..c42d28986 100644
--- a/sdnr/wt/odlux/apps/faultApp/package.json
+++ b/sdnr/wt/odlux/apps/faultApp/package.json
@@ -24,8 +24,8 @@
"@odlux/framework": "*"
},
"peerDependencies": {
- "@types/react": "16.9.11",
- "@types/react-dom": "16.9.4",
+ "@types/react": "16.9.19",
+ "@types/react-dom": "16.9.5",
"@types/react-router-dom": "4.3.1",
"@material-ui/core": "4.9.0",
"@material-ui/icons": "4.5.1",
diff --git a/sdnr/wt/odlux/apps/helpApp/package.json b/sdnr/wt/odlux/apps/helpApp/package.json
index 2a331f68a..1f516cec4 100644
--- a/sdnr/wt/odlux/apps/helpApp/package.json
+++ b/sdnr/wt/odlux/apps/helpApp/package.json
@@ -29,8 +29,8 @@
"github-markdown-css": "2.10.0"
},
"peerDependencies": {
- "@types/react": "16.9.11",
- "@types/react-dom": "16.9.4",
+ "@types/react": "16.9.19",
+ "@types/react-dom": "16.9.5",
"@types/react-router-dom": "4.3.1",
"@material-ui/core": "4.9.0",
"@material-ui/icons": "4.5.1",
diff --git a/sdnr/wt/odlux/apps/inventoryApp/package.json b/sdnr/wt/odlux/apps/inventoryApp/package.json
index 929ca0fb4..4b1f3f9f9 100644
--- a/sdnr/wt/odlux/apps/inventoryApp/package.json
+++ b/sdnr/wt/odlux/apps/inventoryApp/package.json
@@ -24,8 +24,8 @@
"@odlux/framework": "*"
},
"peerDependencies": {
- "@types/react": "16.9.11",
- "@types/react-dom": "16.9.4",
+ "@types/react": "16.9.19",
+ "@types/react-dom": "16.9.5",
"@types/react-router-dom": "4.3.1",
"@material-ui/core": "4.9.0",
"@material-ui/icons": "4.5.1",
diff --git a/sdnr/wt/odlux/apps/inventoryApp/src/actions/inventoryTreeActions.ts b/sdnr/wt/odlux/apps/inventoryApp/src/actions/inventoryTreeActions.ts
new file mode 100644
index 000000000..c09b669a1
--- /dev/null
+++ b/sdnr/wt/odlux/apps/inventoryApp/src/actions/inventoryTreeActions.ts
@@ -0,0 +1,104 @@
+/**
+ * ============LICENSE_START========================================================================
+ * ONAP : ccsdk feature sdnr wt odlux
+ * =================================================================================================
+ * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved.
+ * =================================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ * ============LICENSE_END==========================================================================
+ */
+
+import { Action } from '../../../../framework/src/flux/action';
+import { Dispatch } from '../../../../framework/src/flux/store';
+import { IApplicationStoreState } from '../../../../framework/src/store/applicationStore';
+
+import { InventoryType, InventoryTreeNode, TreeDemoItem } from '../models/inventory';
+import { inventoryService } from '../services/inventoryService';
+import { AddErrorInfoAction } from '../../../../framework/src/actions/errorActions';
+import { NavigateToApplication } from '../../../../framework/src/actions/navigationActions';
+
+/**
+ * Represents the base action.
+ */
+export class BaseAction extends Action { }
+
+export class SetBusyAction extends BaseAction {
+ constructor(public busy: boolean = true) {
+ super();
+
+ }
+}
+
+export class SetSearchTextAction extends BaseAction {
+ constructor(public searchTerm: string = "") {
+ super();
+
+ }
+}
+
+export class UpdateInventoryTreeAction extends BaseAction {
+ constructor(public rootNode: InventoryTreeNode) {
+ super();
+
+ }
+}
+
+export class UpdateSelectedNodeAction extends BaseAction {
+ constructor(public selectedNode?: InventoryType) {
+ super();
+
+ }
+}
+
+export class UpdateExpandedNodesAction extends BaseAction {
+ constructor(public expandedNodes?: TreeDemoItem[]) {
+ super();
+
+ }
+}
+
+export const setSearchTermAction = (searchTerm: string) => (dispatch: Dispatch, getState: () => IApplicationStoreState) =>{
+ dispatch(new SetSearchTextAction(searchTerm));
+}
+
+
+export const updateInventoryTreeAsyncAction = (mountId: string, searchTerm?: string) => async (dispatch: Dispatch, getState: () => IApplicationStoreState) => {
+ dispatch(new SetBusyAction(true));
+ dispatch(new SetSearchTextAction(searchTerm));
+ try {
+ const result = await inventoryService.getInventoryTree(mountId, searchTerm);
+ if (!result) {
+ dispatch(new AddErrorInfoAction({ title: "Error", message: `Could not load inventory tree for [${mountId}]. Please check you connection to the server and try later.` }));
+ dispatch(new NavigateToApplication("inventory"));
+ } else {
+ dispatch(new UpdateInventoryTreeAction(result));
+ }
+ } catch (err) {
+ throw new Error("Could not load inventory tree from server.");
+ }
+ finally {
+ dispatch(new SetBusyAction(false));
+ }
+};
+
+export const selectInventoryNodeAsyncAction = (nodeId: string) => async (dispatch: Dispatch, getState: () => IApplicationStoreState) => {
+ dispatch(new SetBusyAction(true));
+ try {
+ const result = await inventoryService.getInventoryEntry(nodeId);
+ if (!result) throw new Error("Could not load inventory tree from server.");
+ dispatch(new UpdateSelectedNodeAction(result));
+ } catch (err) {
+ throw new Error("Could not load inventory tree from server.");
+ }
+ finally {
+ dispatch(new SetBusyAction(false));
+ }
+};
diff --git a/sdnr/wt/odlux/apps/inventoryApp/src/actions/panelActions.ts b/sdnr/wt/odlux/apps/inventoryApp/src/actions/panelActions.ts
new file mode 100644
index 000000000..10fde8c1d
--- /dev/null
+++ b/sdnr/wt/odlux/apps/inventoryApp/src/actions/panelActions.ts
@@ -0,0 +1,31 @@
+/**
+ * ============LICENSE_START========================================================================
+ * ONAP : ccsdk feature sdnr wt odlux
+ * =================================================================================================
+ * Copyright (C) 2020 highstreet technologies GmbH Intellectual Property. All rights reserved.
+ * =================================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ * ============LICENSE_END==========================================================================
+ */
+
+import { Action } from "../../../../framework/src/flux/action";
+import { PanelId } from "models/panelId";
+
+
+export class SetPanelAction extends Action {
+ constructor(public panelId: PanelId) {
+ super();
+ }
+ }
+
+export const setPanelAction = (panelId: PanelId) => {
+ return new SetPanelAction(panelId);
+ } \ No newline at end of file
diff --git a/sdnr/wt/odlux/apps/inventoryApp/src/fakeData/index.ts b/sdnr/wt/odlux/apps/inventoryApp/src/fakeData/index.ts
index 692ea82c7..46827e842 100644
--- a/sdnr/wt/odlux/apps/inventoryApp/src/fakeData/index.ts
+++ b/sdnr/wt/odlux/apps/inventoryApp/src/fakeData/index.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 { InventoryTreeNode, InventoryType } from "models/inventory";
import { convertPropertyNames, replaceHyphen } from "../../../../framework/src/utilities/yangHelper";
@@ -30,31 +48,31 @@ const deleay = (time: number) => () => new Promise<number>(resolve => setTimeout
const getTreeElements = (searchTerm: string | null, treeLevel: number = 0, parentUUID: string | null = null): [InventoryTreeNode, boolean] => {
const elements = (data.filter(e => e["tree-level"] === treeLevel && (!parentUUID || e["parent-uuid"] === parentUUID)) || [])
let elementMatch = false;
- const treeeNode = elements.reduce<InventoryTreeNode>((acc, cur) => {
- const [children, childMatch] = getTreeElements(searchTerm, treeLevel + 1, cur["node-id"]);
- const isMatch = searchTerm ? Object.keys(cur).some(k => String((cur as any)[k]).indexOf(searchTerm)) : false;
+ const treeNode = elements.reduce<InventoryTreeNode>((acc, cur) => {
+ const [children, childMatch] = getTreeElements(searchTerm, treeLevel + 1, cur["uuid"]);
+ const isMatch = searchTerm ? Object.keys(cur).some(k => String((cur as any)[k]).indexOf(searchTerm) > -1) : false;
elementMatch = elementMatch || isMatch || childMatch;
if (!searchTerm || isMatch || childMatch) {
- acc[cur["node-id"]] = {
- label: cur["node-id"],
+ acc[cur["uuid"]] = {
+ label: cur["uuid"],
children: children,
- isMatch: false,
+ isMatch: isMatch,
};
}
return acc;
}, {});
- return [treeeNode, elementMatch]
+ return [treeNode, elementMatch]
};
-export const getTree = async (searchTerm: string | null = null) : Promise<InventoryTreeNode> => {
+export const getTree = async (searchTerm: string | null = null): Promise<InventoryTreeNode> => {
await deleay(600);
const [node] = getTreeElements(searchTerm);
return node;
};
-export const getElement = async (id: string ): Promise<InventoryType | undefined> => {
+export const getElement = async (id: string): Promise<InventoryType | undefined> => {
await deleay(600);
- const res = data.find(e => e.id === id);
+ const res = data.find(e => e.uuid === id);
return res && convertPropertyNames(res, replaceHyphen) as unknown as InventoryType;
};
diff --git a/sdnr/wt/odlux/apps/inventoryApp/src/handlers/connectedNetworkElementsHandler.ts b/sdnr/wt/odlux/apps/inventoryApp/src/handlers/connectedNetworkElementsHandler.ts
new file mode 100644
index 000000000..79c12d619
--- /dev/null
+++ b/sdnr/wt/odlux/apps/inventoryApp/src/handlers/connectedNetworkElementsHandler.ts
@@ -0,0 +1,36 @@
+/**
+ * ============LICENSE_START========================================================================
+ * ONAP : ccsdk feature sdnr wt odlux
+ * =================================================================================================
+ * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved.
+ * =================================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ * ============LICENSE_END==========================================================================
+ */
+
+import { createExternal, IExternalTableState } from '../../../../framework/src/components/material-table/utilities';
+import { createSearchDataHandler } from '../../../../framework/src/utilities/elasticSearch';
+
+import { NetworkElementConnection } from '../models/networkElementConnection';
+
+export interface IConnectedNetworkElementsState extends IExternalTableState<NetworkElementConnection> { }
+
+// create eleactic search material data fetch handler
+const connectedNetworkElementsSearchHandler = createSearchDataHandler<NetworkElementConnection>('network-element-connection', { status: "Connected" });
+
+export const {
+ actionHandler: connectedNetworkElementsActionHandler,
+ createActions: createConnectedNetworkElementsActions,
+ createProperties: createConnectedNetworkElementsProperties,
+ reloadAction: connectedNetworkElementsReloadAction,
+
+ // set value action, to change a value
+} = createExternal<NetworkElementConnection>(connectedNetworkElementsSearchHandler, appState => appState.inventory.connectedNetworkElements);
diff --git a/sdnr/wt/odlux/apps/inventoryApp/src/handlers/inventoryAppRootHandler.ts b/sdnr/wt/odlux/apps/inventoryApp/src/handlers/inventoryAppRootHandler.ts
index 786f6d0c5..0e857ffe9 100644
--- a/sdnr/wt/odlux/apps/inventoryApp/src/handlers/inventoryAppRootHandler.ts
+++ b/sdnr/wt/odlux/apps/inventoryApp/src/handlers/inventoryAppRootHandler.ts
@@ -22,14 +22,23 @@ import { combineActionHandler } from '../../../../framework/src/flux/middleware'
// ** do not remove **
import { IApplicationStoreState } from '../../../../framework/src/store/applicationStore';
import { IActionHandler } from '../../../../framework/src/flux/action';
-import { IInventoryElementsState, inventoryElementsActionHandler } from './inventoryElementsHandler';
+import { IInvenroryTree, inventoryTreeHandler } from './inventoryTreeHandler';
+import { IConnectedNetworkElementsState, connectedNetworkElementsActionHandler } from './connectedNetworkElementsHandler';
+import { PanelId } from '../models/panelId';
+import { currentOpenPanelHandler } from './panelHandler';
+import { inventoryElementsActionHandler, IInventoryElementsState } from './inventoryElementsHandler';
export interface IInventoryAppStateState {
- inventoryElements: IInventoryElementsState
+ inventoryTree: IInvenroryTree;
+ connectedNetworkElements: IConnectedNetworkElementsState; // used for ne selection
+ currentOpenPanel: PanelId;
+ inventoryElements: IInventoryElementsState;
}
+
+
declare module '../../../../framework/src/store/applicationStore' {
interface IApplicationStoreState {
inventory: IInventoryAppStateState;
@@ -37,6 +46,9 @@ declare module '../../../../framework/src/store/applicationStore' {
}
const actionHandlers = {
+ inventoryTree: inventoryTreeHandler,
+ connectedNetworkElements: connectedNetworkElementsActionHandler,
+ currentOpenPanel: currentOpenPanelHandler,
inventoryElements: inventoryElementsActionHandler
};
diff --git a/sdnr/wt/odlux/apps/inventoryApp/src/handlers/inventoryElementsHandler.tsx b/sdnr/wt/odlux/apps/inventoryApp/src/handlers/inventoryElementsHandler.ts
index a65319efa..a65319efa 100644
--- a/sdnr/wt/odlux/apps/inventoryApp/src/handlers/inventoryElementsHandler.tsx
+++ b/sdnr/wt/odlux/apps/inventoryApp/src/handlers/inventoryElementsHandler.ts
diff --git a/sdnr/wt/odlux/apps/inventoryApp/src/handlers/inventoryTreeHandler.ts b/sdnr/wt/odlux/apps/inventoryApp/src/handlers/inventoryTreeHandler.ts
new file mode 100644
index 000000000..9029a6719
--- /dev/null
+++ b/sdnr/wt/odlux/apps/inventoryApp/src/handlers/inventoryTreeHandler.ts
@@ -0,0 +1,68 @@
+/**
+ * ============LICENSE_START========================================================================
+ * ONAP : ccsdk feature sdnr wt odlux
+ * =================================================================================================
+ * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved.
+ * =================================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ * ============LICENSE_END==========================================================================
+ */
+
+import { IActionHandler } from "../../../../framework/src/flux/action";
+
+import { SetBusyAction, UpdateInventoryTreeAction, UpdateSelectedNodeAction, SetSearchTextAction, UpdateExpandedNodesAction } from "../actions/inventoryTreeActions";
+import { InventoryTreeNode, InventoryType, TreeDemoItem } from "../models/inventory";
+
+
+export interface IInvenroryTree {
+ isBusy: boolean;
+ rootNodes: TreeDemoItem[];
+ selectedNode?: InventoryType;
+ expandedItems: TreeDemoItem[];
+ searchTerm: string;
+}
+
+const initialState: IInvenroryTree = {
+ isBusy: false,
+ rootNodes: [],
+ searchTerm: "",
+ selectedNode: undefined,
+ expandedItems: [],
+}
+
+
+const getTreeDataFromInvetoryTreeNode = (node: InventoryTreeNode): TreeDemoItem[] => Object.keys(node).reduce<TreeDemoItem[]>((acc, key) => {
+ const cur = node[key];
+ acc.push({
+ isMatch: cur.isMatch,
+ content: cur.label || key,
+ value: key,
+ children: cur.children && getTreeDataFromInvetoryTreeNode(cur.children),
+ });
+ return acc;
+}, []);
+
+export const inventoryTreeHandler: IActionHandler<IInvenroryTree> = (state = initialState, action) => {
+ if (action instanceof SetBusyAction) {
+ state = { ...state, isBusy: action.busy };
+ } else if (action instanceof SetSearchTextAction) {
+ state = { ...state, searchTerm: action.searchTerm };
+ } else if (action instanceof UpdateInventoryTreeAction) {
+ const rootNodes = getTreeDataFromInvetoryTreeNode(action.rootNode);
+ state = { ...state, rootNodes: rootNodes, expandedItems: [], selectedNode: undefined };
+ } else if (action instanceof UpdateSelectedNodeAction) {
+ state = { ...state, selectedNode: action.selectedNode };
+ } else if (action instanceof UpdateExpandedNodesAction) {
+ state = { ...state, expandedItems: action.expandedNodes || []}
+ }
+
+ return state;
+} \ No newline at end of file
diff --git a/sdnr/wt/odlux/apps/inventoryApp/src/handlers/panelHandler.ts b/sdnr/wt/odlux/apps/inventoryApp/src/handlers/panelHandler.ts
new file mode 100644
index 000000000..761253112
--- /dev/null
+++ b/sdnr/wt/odlux/apps/inventoryApp/src/handlers/panelHandler.ts
@@ -0,0 +1,11 @@
+import { PanelId } from "../models/panelId";
+import { IActionHandler } from "../../../../framework/src/flux/action";
+import { SetPanelAction } from "../actions/panelActions";
+
+
+export const currentOpenPanelHandler: IActionHandler<PanelId> = (state = null, action) => {
+ if (action instanceof SetPanelAction) {
+ state = action.panelId;
+ }
+ return state;
+ } \ No newline at end of file
diff --git a/sdnr/wt/odlux/apps/inventoryApp/src/index.html b/sdnr/wt/odlux/apps/inventoryApp/src/index.html
index 0cdb9e93b..75531ec1b 100644
--- a/sdnr/wt/odlux/apps/inventoryApp/src/index.html
+++ b/sdnr/wt/odlux/apps/inventoryApp/src/index.html
@@ -15,8 +15,9 @@
<script type="text/javascript" src="./config.js"></script>
<script>
// run the application
- require(["app", "inventoryApp"], function (app, inventoryApp) {
+ require(["app", "inventoryApp", "connectApp"], function (app, inventoryApp, connectApp) {
inventoryApp.register();
+ connectApp.register();
app("./app.tsx").runApplication();
});
</script>
diff --git a/sdnr/wt/odlux/apps/inventoryApp/src/models/inventory.ts b/sdnr/wt/odlux/apps/inventoryApp/src/models/inventory.ts
index c6b6c91cb..a6c990529 100644
--- a/sdnr/wt/odlux/apps/inventoryApp/src/models/inventory.ts
+++ b/sdnr/wt/odlux/apps/inventoryApp/src/models/inventory.ts
@@ -15,6 +15,9 @@
* the License.
* ============LICENSE_END==========================================================================
*/
+
+import { ExternalTreeItem } from '../../../../framework/src/components/material-ui/treeView';
+
export { HitEntry, Result } from '../../../../framework/src/models';
export type InventoryType = {
@@ -23,7 +26,7 @@ export type InventoryType = {
nodeId: string;
uuid: string;
containedHolder?: (string)[] | null;
- manufacturerName?: string ;
+ manufacturerName?: string;
manufacturerIdentifier: string;
serial: string;
date: string;
@@ -42,4 +45,6 @@ export type InventoryTreeNode = {
ownSeverity?: string;
childrenSeveritySummary?: string;
}
-} \ No newline at end of file
+}
+
+export type TreeDemoItem = ExternalTreeItem<string>; \ No newline at end of file
diff --git a/sdnr/wt/odlux/apps/inventoryApp/src/models/networkElementConnection.ts b/sdnr/wt/odlux/apps/inventoryApp/src/models/networkElementConnection.ts
new file mode 100644
index 000000000..88f70181c
--- /dev/null
+++ b/sdnr/wt/odlux/apps/inventoryApp/src/models/networkElementConnection.ts
@@ -0,0 +1,37 @@
+/**
+ * ============LICENSE_START========================================================================
+ * ONAP : ccsdk feature sdnr wt odlux
+ * =================================================================================================
+ * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved.
+ * =================================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ * ============LICENSE_END==========================================================================
+ */
+
+export type NetworkElementConnection = {
+ id?: string;
+ nodeId: string;
+ host: string;
+ port: number;
+ username?: string;
+ password?: string;
+ isRequired?: boolean;
+ status?: "connected" | "mounted" | "unmounted" | "connecting" | "disconnected" | "idle";
+ coreModelCapability?: string;
+ deviceType?: string;
+ nodeDetails?: {
+ availableCapabilities: string[];
+ unavailableCapabilities: {
+ failureReason: string;
+ capability: string;
+ }[];
+ }
+}
diff --git a/sdnr/wt/odlux/apps/inventoryApp/src/models/panelId.ts b/sdnr/wt/odlux/apps/inventoryApp/src/models/panelId.ts
new file mode 100644
index 000000000..b05c1db64
--- /dev/null
+++ b/sdnr/wt/odlux/apps/inventoryApp/src/models/panelId.ts
@@ -0,0 +1,19 @@
+/**
+ * ============LICENSE_START========================================================================
+ * ONAP : ccsdk feature sdnr wt odlux
+ * =================================================================================================
+ * Copyright (C) 2020 highstreet technologies GmbH Intellectual Property. All rights reserved.
+ * =================================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ * ============LICENSE_END==========================================================================
+ */
+
+export type PanelId = null | "InventoryElementsTable" | "TreeviewTable"; \ No newline at end of file
diff --git a/sdnr/wt/odlux/apps/inventoryApp/src/pluginInventory.tsx b/sdnr/wt/odlux/apps/inventoryApp/src/pluginInventory.tsx
index ad53285cb..665e085e6 100644
--- a/sdnr/wt/odlux/apps/inventoryApp/src/pluginInventory.tsx
+++ b/sdnr/wt/odlux/apps/inventoryApp/src/pluginInventory.tsx
@@ -22,43 +22,15 @@ import { withRouter, RouteComponentProps, Route, Switch, Redirect } from 'react-
import { faShoppingBag } from '@fortawesome/free-solid-svg-icons'; // select app icon
import applicationManager from '../../../framework/src/services/applicationManager';
-import connect, { Connect, IDispatcher } from '../../../framework/src/flux/connect';
-import { IApplicationStoreState } from "../../../framework/src/store/applicationStore";
+import { InventoryTreeView } from './views/treeview';
+import Dashboard from "./views/dashboard";
-import { Dashboard } from './views/dashboard';
import inventoryAppRootHandler from './handlers/inventoryAppRootHandler';
-import { createInventoryElementsProperties, createInventoryElementsActions, inventoryElementsReloadAction } from "./handlers/inventoryElementsHandler";
-
-let currentMountId: string | undefined = undefined;
-
-const mapProps = (state: IApplicationStoreState) => ({
- inventoryProperties: createInventoryElementsProperties(state),
-});
-
-const mapDisp = (dispatcher: IDispatcher) => ({
- inventoryActions: createInventoryElementsActions(dispatcher.dispatch, true)
-});
-
-const InventoryApplicationRouteAdapter = connect(mapProps, mapDisp)((props: RouteComponentProps<{ mountId?: string }> & Connect<typeof mapProps, typeof mapDisp>) => {
- if (currentMountId !== props.match.params.mountId) {
- currentMountId = props.match.params.mountId || undefined;
- window.setTimeout(() => {
- if (currentMountId) {
- props.inventoryActions.onFilterChanged("nodeId", currentMountId);
- props.inventoryProperties.showFilter;
- props.inventoryActions.onRefresh();
- }
- });
- }
- return (
- <Dashboard />
- )
-});
-
const App = withRouter((props: RouteComponentProps) => (
<Switch>
- <Route path={`${props.match.path}/:mountId?`} component={InventoryApplicationRouteAdapter} />
+ <Route path={`${props.match.path}/:mountId`} component={InventoryTreeView} />
+ <Route path={`${props.match.path}`} component={Dashboard} />
<Redirect to={`${props.match.path}`} />
</Switch>
));
diff --git a/sdnr/wt/odlux/apps/inventoryApp/src/services/inventoryService.ts b/sdnr/wt/odlux/apps/inventoryApp/src/services/inventoryService.ts
index 252d6d425..b6025d4da 100644
--- a/sdnr/wt/odlux/apps/inventoryApp/src/services/inventoryService.ts
+++ b/sdnr/wt/odlux/apps/inventoryApp/src/services/inventoryService.ts
@@ -16,6 +16,7 @@
* ============LICENSE_END==========================================================================
*/
import { requestRest } from '../../../../framework/src/services/restService';
+import { convertPropertyNames, replaceHyphen } from '../../../../framework/src/utilities/yangHelper';
import { InventoryTreeNode, InventoryType } from '../models/inventory';
import { getTree, getElement } from '../fakeData';
@@ -24,12 +25,44 @@ import { getTree, getElement } from '../fakeData';
* Represents a web api accessor service for all maintenence entries related actions.
*/
class InventoryService {
- public async getInventoryTree(searchTerm?: string): Promise<InventoryTreeNode> {
- return await getTree(searchTerm);
+ public async getInventoryTree(mountId: string, searchTerm: string = ""): Promise<InventoryTreeNode | null> {
+ //return await getTree(searchTerm);
+ const path = `/tree/read-inventoryequipment-tree/${mountId}`;
+ const body = {
+ "query": searchTerm
+ };
+ const inventoryTree = await requestRest<InventoryTreeNode>(path, { method: "POST" , body: JSON.stringify(body)});
+ return inventoryTree && convertPropertyNames(inventoryTree, replaceHyphen) || null;
}
- public async getInventoryEntry(id: string): Promise<InventoryType| undefined> {
- return await getElement(id);
+ public async getInventoryEntry(id: string): Promise<InventoryType | undefined> {
+ const path = `/restconf/operations/data-provider:read-inventory-list`;
+ const body = {
+ "input": {
+ "filter": [
+ { property: "id", filtervalue: id },
+ ],
+ "sortorder": [],
+ "pagination": {
+ "size": 1,
+ "page": 1
+ }
+ }
+ };
+ const inventoryTreeElement = await requestRest<{
+ output: {
+ "pagination": {
+ "size": number,
+ "page": number,
+ "total": number
+ },
+ "data": InventoryType[]
+ }
+ }>(path, { method: "POST", body: JSON.stringify(body) });
+
+ return inventoryTreeElement && inventoryTreeElement.output && inventoryTreeElement.output.pagination && inventoryTreeElement.output.pagination.total >= 1 &&
+ inventoryTreeElement.output.data && convertPropertyNames(inventoryTreeElement.output.data[0], replaceHyphen) || undefined;
+ // return await getElement(id);
}
}
diff --git a/sdnr/wt/odlux/apps/inventoryApp/src/views/dashboard.tsx b/sdnr/wt/odlux/apps/inventoryApp/src/views/dashboard.tsx
index b63f628a3..14792df5b 100644
--- a/sdnr/wt/odlux/apps/inventoryApp/src/views/dashboard.tsx
+++ b/sdnr/wt/odlux/apps/inventoryApp/src/views/dashboard.tsx
@@ -15,88 +15,163 @@
* the License.
* ============LICENSE_END==========================================================================
*/
-import * as React from "react";
-import { withStyles, WithStyles, createStyles, Theme } from '@material-ui/core/styles';
-import { Connect, connect, IDispatcher } from '../../../../framework/src/flux/connect';
-import { MaterialTable, MaterialTableCtorType } from '../../../../framework/src/components/material-table';
-import { TreeView, TreeItem, TreeViewCtorType } from '../../../../framework/src/components/material-ui/treeView';
+import * as React from 'react';
+import { RouteComponentProps, withRouter } from 'react-router-dom';
-import { InventoryType } from '../models/inventory';
+import connect, { IDispatcher, Connect } from "../../../../framework/src/flux/connect";
import { IApplicationStoreState } from "../../../../framework/src/store/applicationStore";
-import { createInventoryElementsProperties, createInventoryElementsActions } from "../handlers/inventoryElementsHandler";
+import { MaterialTable, MaterialTableCtorType, ColumnType } from "../../../../framework/src/components/material-table";
+import { AppBar, Tabs, Tab, MenuItem, Typography } from "@material-ui/core";
+import { PanelId } from "../models/panelId";
+import { setPanelAction } from "../actions/panelActions";
-const styles = (theme: Theme) => createStyles({
- root: {
- flex: "1 0 0%",
- display: "flex",
- flexDirection: "row",
- },
- tree: {
- flex: "1 0 0%",
- minWidth: "250px",
- padding: `0px ${theme.spacing(1)}px`
- },
- details: {
- flex: "5 0 0%",
- padding: `0px ${theme.spacing(1)}px`
- }
-});
-const InventoryTable = MaterialTable as MaterialTableCtorType<InventoryType & {_id: string}>;
+import { createConnectedNetworkElementsProperties, createConnectedNetworkElementsActions } from "../handlers/connectedNetworkElementsHandler";
+
+import { NetworkElementConnection } from "../models/networkElementConnection";
+
+import { InventoryType } from '../models/inventory';
+
+import { createInventoryElementsProperties, createInventoryElementsActions } from "../handlers/inventoryElementsHandler";
+import { NavigateToApplication } from '../../../../framework/src/actions/navigationActions';
+import { updateInventoryTreeAsyncAction } from '../actions/inventoryTreeActions';
+
+const InventoryTable = MaterialTable as MaterialTableCtorType<InventoryType & { _id: string }>;
const mapProps = (state: IApplicationStoreState) => ({
+ connectedNetworkElementsProperties: createConnectedNetworkElementsProperties(state),
+ panelId: state.inventory.currentOpenPanel,
inventoryElementsProperties: createInventoryElementsProperties(state),
inventoryElements: state.inventory.inventoryElements
});
const mapDispatch = (dispatcher: IDispatcher) => ({
- inventoryElementsActions: createInventoryElementsActions(dispatcher.dispatch)
+ connectedNetworkElementsActions: createConnectedNetworkElementsActions(dispatcher.dispatch),
+ switchActivePanel: (panelId: PanelId) => {
+ dispatcher.dispatch(setPanelAction(panelId));
+ },
+ inventoryElementsActions: createInventoryElementsActions(dispatcher.dispatch),
+ navigateToApplication: (applicationName: string, path?: string) => dispatcher.dispatch(new NavigateToApplication(applicationName, path)),
+ updateInventoryTree: (mountId: string, seatchTerm?: string) => dispatcher.dispatch(updateInventoryTreeAsyncAction(mountId, seatchTerm)),
});
-const SampleTree = TreeView as any as TreeViewCtorType<string>;
+let treeViewInitialSorted = false;
+let inventoryInitialSorted = false;
+const ConnectedElementTable = MaterialTable as MaterialTableCtorType<NetworkElementConnection>;
-type TreeDemoItem = TreeItem<string>;
+type DashboardComponentProps = RouteComponentProps & Connect<typeof mapProps, typeof mapDispatch>;
-const treeData: TreeDemoItem[] = [
- {
- content: "Erste Ebene", children: [
- {
- content: "Zweite Ebene", children: [
- { content: "Dritte Ebene" },
- ]
- },
- { content: "Zweite Ebene 2" },
- ]
- },
- { content: "Erste Ebene 3" },
-];
+class DashboardSelectorComponent extends React.Component<DashboardComponentProps> {
+
+ private onHandleTabChange = (event: React.ChangeEvent<{}>, newValue: PanelId) => {
+ this.onTogglePanel(newValue);
+ }
+
+ private onTogglePanel = (panelId: PanelId) => {
+ const nextActivePanel = panelId;
+ this.props.switchActivePanel(nextActivePanel);
+
+ switch (nextActivePanel) {
+ case 'InventoryElementsTable':
+
+ if (!inventoryInitialSorted) {
+ this.props.inventoryElementsActions.onHandleExplicitRequestSort("nodeId", "asc");
+ inventoryInitialSorted = true;
+ } else {
+ this.props.inventoryElementsActions.onRefresh();
+
+ }
+ break;
+ case 'TreeviewTable':
+ if (!treeViewInitialSorted) {
+ this.props.connectedNetworkElementsActions.onHandleExplicitRequestSort("nodeId", "asc");
+ treeViewInitialSorted = true;
+ } else {
+ this.props.connectedNetworkElementsActions.onRefresh();
+ }
+ break;
+ case null:
+ // do nothing if all panels are closed
+ break;
+ default:
+ console.warn("Unknown nextActivePanel [" + nextActivePanel + "] in connectView");
+ break;
+ }
+
+ };
+
+ getContextMenu = (rowData: InventoryType) => {
+ return [
+ <MenuItem aria-label={"inventory-button"} onClick={event => { this.props.updateInventoryTree(rowData.nodeId, rowData.uuid); this.props.navigateToApplication("inventory", rowData.nodeId) }}><Typography>View in Treeview</Typography></MenuItem>,
+ ];
+
+ }
-class DashboardComponent extends React.Component<& WithStyles<typeof styles> & Connect<typeof mapProps, typeof mapDispatch>> {
render() {
- return <InventoryTable stickyHeader title="Inventory" idProperty="_id" columns={[
- { property: "nodeId", title: "Node Name" },
- { property: "manufacturerIdentifier", title: "Manufacturer" },
- { property: "parentUuid", title: "Parent" },
- { property: "uuid", title: "Name" },
- { property: "serial", title: "Serial" },
- { property: "version", title: "Version" },
- { property: "date", title: "Date" },
- { property: "description", title: "Description" },
- { property: "partTypeId", title: "Part Type Id" },
- { property: "modelIdentifier", title: "Model Identifier" },
- { property: "typeName", title: "Type" },
- { property: "treeLevel", title: "Containment Level" },
- ]} {...this.props.inventoryElementsActions} {...this.props.inventoryElementsProperties} >
- </InventoryTable>
+
+ const { panelId: activePanelId } = this.props;
+ return (
+ <>
+ <AppBar position="static">
+ <Tabs value={activePanelId} onChange={this.onHandleTabChange} aria-label="simple tabs example">
+ <Tab label="Table View" value="InventoryElementsTable" />
+ <Tab label="Tree view" value="TreeviewTable" />
+ </Tabs>
+ </AppBar>
+
+ {
+
+ activePanelId === "InventoryElementsTable" &&
+
+ <InventoryTable stickyHeader title="Inventory" idProperty="_id" columns={[
+ { property: "nodeId", title: "Node Name" },
+ { property: "manufacturerIdentifier", title: "Manufacturer" },
+ { property: "parentUuid", title: "Parent" },
+ { property: "uuid", title: "Name" },
+ { property: "serial", title: "Serial" },
+ { property: "version", title: "Version" },
+ { property: "date", title: "Date" },
+ { property: "description", title: "Description" },
+ { property: "partTypeId", title: "Part Type Id" },
+ { property: "modelIdentifier", title: "Model Identifier" },
+ { property: "typeName", title: "Type" },
+ { property: "treeLevel", title: "Containment Level" },
+ ]} {...this.props.inventoryElementsActions} {...this.props.inventoryElementsProperties}
+ createContextMenu={rowData => {
+
+ return this.getContextMenu(rowData);
+ }} >
+ </InventoryTable>
+
+ }
+ {
+ activePanelId === "TreeviewTable" &&
+
+ <ConnectedElementTable stickyHeader 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 },
+ { property: "port", title: "Port", type: ColumnType.numeric },
+ { property: "coreModelCapability", title: "Core Model", type: ColumnType.text },
+ { property: "deviceType", title: "Type", type: ColumnType.text },
+ ]} idProperty="id" {...this.props.connectedNetworkElementsActions} {...this.props.connectedNetworkElementsProperties} asynchronus >
+ </ConnectedElementTable>
+ }
+ </>
+ );
}
componentDidMount() {
- this.props.inventoryElementsActions.onToggleFilter();
- this.props.inventoryElementsActions.onHandleRequestSort("node-id");
+
+ if (this.props.panelId === null) { //set default tab if none is set
+ this.onTogglePanel("InventoryElementsTable");
+ }
+
}
}
-export const Dashboard = connect(mapProps, mapDispatch)(withStyles(styles)(DashboardComponent));
-export default Dashboard; \ No newline at end of file
+export const Dashboard = withRouter(connect(mapProps, mapDispatch)(DashboardSelectorComponent));
+export default Dashboard;
+
diff --git a/sdnr/wt/odlux/apps/inventoryApp/src/views/treeview.tsx b/sdnr/wt/odlux/apps/inventoryApp/src/views/treeview.tsx
new file mode 100644
index 000000000..5f2c61080
--- /dev/null
+++ b/sdnr/wt/odlux/apps/inventoryApp/src/views/treeview.tsx
@@ -0,0 +1,132 @@
+/**
+ * ============LICENSE_START========================================================================
+ * ONAP : ccsdk feature sdnr wt odlux
+ * =================================================================================================
+ * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved.
+ * =================================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS 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 { withStyles, WithStyles, createStyles, Theme } from '@material-ui/core/styles';
+
+import { renderObject } from '../../../../framework/src/components/objectDump';
+import { Connect, connect, IDispatcher } from '../../../../framework/src/flux/connect';
+import { TreeView, TreeViewCtorType, SearchMode } from '../../../../framework/src/components/material-ui/treeView';
+
+import { IApplicationStoreState } from "../../../../framework/src/store/applicationStore";
+
+import { updateInventoryTreeAsyncAction, selectInventoryNodeAsyncAction, UpdateSelectedNodeAction, UpdateExpandedNodesAction, setSearchTermAction} from "../actions/inventoryTreeActions";
+import { TreeDemoItem } from "../models/inventory";
+
+import { RouteComponentProps } from "react-router-dom";
+
+const styles = (theme: Theme) => createStyles({
+ root: {
+ flex: "1 0 0%",
+ display: "flex",
+ flexDirection: "row",
+ },
+ tree: {
+ flex: "1 0 0%",
+ minWidth: "250px",
+ padding: `0px ${theme.spacing(1)}px`
+ },
+ details: {
+ flex: "5 0 0%",
+ padding: `0px ${theme.spacing(1)}px`
+ }
+});
+
+const mapProps = (state: IApplicationStoreState) => ({
+ isBusy: state.inventory.inventoryTree.isBusy,
+ rootNodes: state.inventory.inventoryTree.rootNodes,
+ searchTerm: state.inventory.inventoryTree.searchTerm,
+ selectedNode: state.inventory.inventoryTree.selectedNode,
+ expendedItems: state.inventory.inventoryTree.expandedItems,
+});
+
+const mapDispatch = (dispatcher: IDispatcher) => ({
+ updateExpendedNodes: (expendedNodes: TreeDemoItem[]) => dispatcher.dispatch(new UpdateExpandedNodesAction(expendedNodes)),
+ updateInventoryTree: (mountId: string, seatchTerm?: string) => dispatcher.dispatch(updateInventoryTreeAsyncAction(mountId, seatchTerm)),
+ selectTreeNode: (nodeId?: string) => nodeId ? dispatcher.dispatch(selectInventoryNodeAsyncAction(nodeId)) : dispatcher.dispatch(new UpdateSelectedNodeAction(undefined)),
+ setSearchTerm: (searchTerm: string) => dispatcher.dispatch(setSearchTermAction(searchTerm)),
+});
+
+const propsChache = Symbol("PropsCache");
+const InventoryTree = TreeView as any as TreeViewCtorType<string>;
+
+
+
+type TreeviewComponentProps = RouteComponentProps<{ mountId: string}> & WithStyles<typeof styles> & Connect<typeof mapProps, typeof mapDispatch>
+
+type TreeviewComponentState = {
+ [propsChache]: {
+ rootNodes?: TreeDemoItem[];
+ };
+ rootNodes: TreeDemoItem[];
+}
+
+
+class DashboardComponent extends React.Component<TreeviewComponentProps, TreeviewComponentState> {
+
+ constructor (props: TreeviewComponentProps) {
+ super(props);
+
+ this.state = {
+ [propsChache]: {},
+ rootNodes: [],
+ };
+ }
+
+ static getDerivedStateFromProps(props: TreeviewComponentProps, state: TreeviewComponentState) {
+ if (state[propsChache].rootNodes != props.rootNodes) {
+ state = { ...state, rootNodes: props.rootNodes}
+ }
+ return state;
+ }
+
+ render() {
+ const { classes, updateInventoryTree, updateExpendedNodes, expendedItems, selectedNode, selectTreeNode, searchTerm, match: { params: { mountId }} } = this.props;
+ return (
+ <div className={classes.root}>
+ <InventoryTree className={classes.tree} items={this.state.rootNodes} enableSearchBar initialSearchTerm={searchTerm} searchMode={SearchMode.OnEnter} searchTerm={searchTerm}
+ onSearch={(searchTerm) => updateInventoryTree(mountId, searchTerm)} expandedItems={expendedItems} onFolderClick={(item) => {
+ const indexOfItemToToggle = expendedItems.indexOf(item);
+ if (indexOfItemToToggle === -1) {
+ updateExpendedNodes([...expendedItems, item]);
+ } else {
+ updateExpendedNodes([
+ ...expendedItems.slice(0, indexOfItemToToggle),
+ ...expendedItems.slice(indexOfItemToToggle + 1),
+ ]);
+ }
+ }}
+ onItemClick={(elm) => selectTreeNode(elm.value)} />
+ <div className={classes.details}>{
+ selectedNode && renderObject(selectedNode) || null
+ }</div>
+ </div>
+ );
+ }
+
+ componentDidMount() {
+ const { updateInventoryTree, searchTerm, match: { params: { mountId } }} = this.props;
+ updateInventoryTree(mountId, searchTerm);
+ }
+
+ componentWillUnmount(){
+ this.props.setSearchTerm("");
+ }
+}
+
+export const InventoryTreeView = connect(mapProps, mapDispatch)(withStyles(styles)(DashboardComponent));
+export default InventoryTreeView; \ No newline at end of file
diff --git a/sdnr/wt/odlux/apps/inventoryApp/webpack.config.js b/sdnr/wt/odlux/apps/inventoryApp/webpack.config.js
index 889bb4d8e..d81797c1e 100644
--- a/sdnr/wt/odlux/apps/inventoryApp/webpack.config.js
+++ b/sdnr/wt/odlux/apps/inventoryApp/webpack.config.js
@@ -146,6 +146,10 @@ module.exports = (env) => {
target: "http://localhost:48181",
secure: false
},
+ "/tree/": {
+ target: "http://localhost:48181",
+ secure: false
+ },
"/websocket": {
target: "http://localhost:48181",
ws: true,
diff --git a/sdnr/wt/odlux/apps/maintenanceApp/package.json b/sdnr/wt/odlux/apps/maintenanceApp/package.json
index edd582744..faaef753c 100644
--- a/sdnr/wt/odlux/apps/maintenanceApp/package.json
+++ b/sdnr/wt/odlux/apps/maintenanceApp/package.json
@@ -25,8 +25,8 @@
"@odlux/connect-app": "*"
},
"peerDependencies": {
- "@types/react": "16.9.11",
- "@types/react-dom": "16.9.4",
+ "@types/react": "16.9.19",
+ "@types/react-dom": "16.9.5",
"@types/react-router-dom": "4.3.1",
"@material-ui/core": "4.9.0",
"@material-ui/icons": "4.5.1",
diff --git a/sdnr/wt/odlux/apps/mediatorApp/package.json b/sdnr/wt/odlux/apps/mediatorApp/package.json
index cfd4bd9e9..1f1b7d578 100644
--- a/sdnr/wt/odlux/apps/mediatorApp/package.json
+++ b/sdnr/wt/odlux/apps/mediatorApp/package.json
@@ -25,8 +25,8 @@
},
"peerDependencies": {
"@fortawesome/free-solid-svg-icons": "5.6.3",
- "@types/react": "16.9.11",
- "@types/react-dom": "16.9.4",
+ "@types/react": "16.9.19",
+ "@types/react-dom": "16.9.5",
"@types/react-router-dom": "4.3.1",
"@material-ui/core": "4.9.0",
"@material-ui/icons": "4.5.1",
diff --git a/sdnr/wt/odlux/apps/minimumApp/package.json b/sdnr/wt/odlux/apps/minimumApp/package.json
index 5a8352e4b..d2b836979 100644
--- a/sdnr/wt/odlux/apps/minimumApp/package.json
+++ b/sdnr/wt/odlux/apps/minimumApp/package.json
@@ -24,8 +24,8 @@
"@odlux/framework": "*"
},
"peerDependencies": {
- "@types/react": "16.9.11",
- "@types/react-dom": "16.9.4",
+ "@types/react": "16.9.19",
+ "@types/react-dom": "16.9.5",
"@types/react-router-dom": "4.3.1",
"@material-ui/core": "4.9.0",
"@material-ui/icons": "4.5.1",
diff --git a/sdnr/wt/odlux/apps/performanceHistoryApp/package.json b/sdnr/wt/odlux/apps/performanceHistoryApp/package.json
index e1ca8013b..1a6415fbc 100644
--- a/sdnr/wt/odlux/apps/performanceHistoryApp/package.json
+++ b/sdnr/wt/odlux/apps/performanceHistoryApp/package.json
@@ -27,8 +27,8 @@
"chart.js": "2.8.0"
},
"peerDependencies": {
- "@types/react": "16.9.11",
- "@types/react-dom": "16.9.4",
+ "@types/react": "16.9.19",
+ "@types/react-dom": "16.9.5",
"@types/react-router-dom": "4.3.1",
"@material-ui/core": "4.9.0",
"@material-ui/icons": "4.5.1",
diff --git a/sdnr/wt/odlux/apps/performanceHistoryApp/src/components/toggleContainer.tsx b/sdnr/wt/odlux/apps/performanceHistoryApp/src/components/toggleContainer.tsx
index 6014b22e4..88dc9fd11 100644
--- a/sdnr/wt/odlux/apps/performanceHistoryApp/src/components/toggleContainer.tsx
+++ b/sdnr/wt/odlux/apps/performanceHistoryApp/src/components/toggleContainer.tsx
@@ -59,6 +59,9 @@ const ToggleContainer: React.FunctionComponent<toggleProps> = (props) => {
const children = React.Children.toArray(props.children);
+ //hide filter if visible + table
+ //put current name into state, let container handle stuff itelf, register for togglestate, get right via set name
+
return (
<>
<div className={classes.toggleButtonContainer} >
diff --git a/sdnr/wt/odlux/apps/performanceHistoryApp/webpack.config.js b/sdnr/wt/odlux/apps/performanceHistoryApp/webpack.config.js
index bbbc3e43e..6579db3ec 100644
--- a/sdnr/wt/odlux/apps/performanceHistoryApp/webpack.config.js
+++ b/sdnr/wt/odlux/apps/performanceHistoryApp/webpack.config.js
@@ -126,15 +126,15 @@ module.exports = (env) => {
},
proxy: {
"/oauth2/": {
- target: "http://localhost:48181",
+ target: "http://10.20.6.29:28181",
secure: false
},
"/restconf": {
- target: "http://localhost:48181",
+ target: "http://10.20.6.29:28181",
secure: false
},
"/database": {
- target: "http://localhost:48181",
+ target: "http://10.20.6.29:28181",
secure: false
}
}