From 7dbe38ba0522b346a0fcd9851e797f0fd71ecd5e Mon Sep 17 00:00:00 2001 From: Michael Dürre Date: Thu, 16 Jul 2020 05:55:07 +0200 Subject: switch to rfc8040 restconf MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit change rest interface and some small code cleanups Issue-ID: CCSDK-2572 Signed-off-by: Michael Dürre Change-Id: I3475bd2574b32950c4bf84fbd1c2a9dac9af208a --- sdnr/wt/odlux/apps/apiDemo/package.json | 4 +- sdnr/wt/odlux/apps/configurationApp/package.json | 4 +- .../configurationApp/src/actions/deviceActions.ts | 321 ++++++++++++- .../src/components/uiElementSelection.tsx | 5 +- .../src/handlers/valueSelectorHandler.ts | 4 +- .../src/handlers/viewDescriptionHandler.ts | 55 ++- .../apps/configurationApp/src/models/uiModels.ts | 34 +- .../configurationApp/src/services/restServices.ts | 12 + .../src/views/configurationApplication.tsx | 271 +++++++---- .../apps/configurationApp/src/yang/yangParser.ts | 109 ++++- sdnr/wt/odlux/apps/connectApp/package.json | 4 +- .../src/components/editNetworkElementDialog.tsx | 5 +- sdnr/wt/odlux/apps/demoApp/package.json | 4 +- sdnr/wt/odlux/apps/eventLogApp/package.json | 4 +- sdnr/wt/odlux/apps/faultApp/package.json | 4 +- sdnr/wt/odlux/apps/helpApp/package.json | 4 +- sdnr/wt/odlux/apps/inventoryApp/package.json | 4 +- .../src/actions/inventoryTreeActions.ts | 104 +++++ .../apps/inventoryApp/src/actions/panelActions.ts | 31 ++ .../odlux/apps/inventoryApp/src/fakeData/index.ts | 38 +- .../handlers/connectedNetworkElementsHandler.ts | 36 ++ .../src/handlers/inventoryAppRootHandler.ts | 16 +- .../src/handlers/inventoryElementsHandler.ts | 36 ++ .../src/handlers/inventoryElementsHandler.tsx | 36 -- .../src/handlers/inventoryTreeHandler.ts | 68 +++ .../apps/inventoryApp/src/handlers/panelHandler.ts | 11 + sdnr/wt/odlux/apps/inventoryApp/src/index.html | 3 +- .../apps/inventoryApp/src/models/inventory.ts | 9 +- .../src/models/networkElementConnection.ts | 37 ++ .../odlux/apps/inventoryApp/src/models/panelId.ts | 19 + .../apps/inventoryApp/src/pluginInventory.tsx | 36 +- .../inventoryApp/src/services/inventoryService.ts | 41 +- .../apps/inventoryApp/src/views/dashboard.tsx | 195 +++++--- .../odlux/apps/inventoryApp/src/views/treeview.tsx | 132 ++++++ sdnr/wt/odlux/apps/inventoryApp/webpack.config.js | 4 + sdnr/wt/odlux/apps/maintenanceApp/package.json | 4 +- sdnr/wt/odlux/apps/mediatorApp/package.json | 4 +- sdnr/wt/odlux/apps/minimumApp/package.json | 4 +- .../odlux/apps/performanceHistoryApp/package.json | 4 +- .../src/components/toggleContainer.tsx | 3 + .../apps/performanceHistoryApp/webpack.config.js | 6 +- sdnr/wt/odlux/core/features/pom.xml | 11 - .../opensymphony/xwork2/util/ClassLoaderUtil.java | 125 ++--- .../wt/odlux/model/bundles/ClassLoaderUtilExt.java | 8 +- .../sdnr/wt/odlux/model/bundles/OdluxBundle.java | 25 +- .../wt/odlux/model/bundles/OdluxBundleLoader.java | 2 +- .../model/bundles/OdluxBundleResourceAccess.java | 2 + .../features/sdnr/wt/odlux/IndexOdluxBundle.java | 35 +- .../ccsdk/features/sdnr/wt/odlux/IndexServlet.java | 65 ++- .../features/sdnr/wt/odlux/ResFilesServlet.java | 53 ++- .../sdnr/odlux/test/TestBundleLoaderImpl.java | 9 +- .../sdnr/odlux/test/TestLoadResources.java | 12 +- .../features/sdnr/odlux/test/TestRedirect.java | 48 +- .../sdnr/odlux/test/TestResFileServlet.java | 9 +- sdnr/wt/odlux/framework/package.json | 6 +- sdnr/wt/odlux/framework/pom.xml | 2 +- .../src/components/material-ui/treeView.tsx | 6 +- .../framework/src/components/objectDump/index.tsx | 173 +++++++ sdnr/wt/odlux/framework/src/index.html | 4 +- .../src/services/authenticationService.ts | 20 +- sdnr/wt/odlux/framework/src/views/about.tsx | 49 +- sdnr/wt/odlux/framework/src/views/login.tsx | 3 +- sdnr/wt/odlux/framework/webpack.config.js | 6 +- sdnr/wt/odlux/package.json | 5 +- sdnr/wt/odlux/yarn.lock | 514 ++++++++++++++++----- 65 files changed, 2246 insertions(+), 671 deletions(-) create mode 100644 sdnr/wt/odlux/apps/inventoryApp/src/actions/inventoryTreeActions.ts create mode 100644 sdnr/wt/odlux/apps/inventoryApp/src/actions/panelActions.ts create mode 100644 sdnr/wt/odlux/apps/inventoryApp/src/handlers/connectedNetworkElementsHandler.ts create mode 100644 sdnr/wt/odlux/apps/inventoryApp/src/handlers/inventoryElementsHandler.ts delete mode 100644 sdnr/wt/odlux/apps/inventoryApp/src/handlers/inventoryElementsHandler.tsx create mode 100644 sdnr/wt/odlux/apps/inventoryApp/src/handlers/inventoryTreeHandler.ts create mode 100644 sdnr/wt/odlux/apps/inventoryApp/src/handlers/panelHandler.ts create mode 100644 sdnr/wt/odlux/apps/inventoryApp/src/models/networkElementConnection.ts create mode 100644 sdnr/wt/odlux/apps/inventoryApp/src/models/panelId.ts create mode 100644 sdnr/wt/odlux/apps/inventoryApp/src/views/treeview.tsx create mode 100644 sdnr/wt/odlux/framework/src/components/objectDump/index.tsx (limited to 'sdnr/wt') 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) => { ? ( {element.label} {error} ) 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 = (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 = (state = viewDescriptionStateInit, action) => { @@ -50,11 +61,15 @@ export const viewDescriptionHandler: IActionHandler = (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 { + 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 { + // 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 { 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 +
{sections.elements.length > 0 ? ( @@ -425,7 +476,16 @@ class ConfigurationApplicationComponent extends React.Component ) : null } - + {sections.rpcs.length > 0 + ? ( +
+ {sections.rpcs.map(element => ( + { this.navigate(`/${elm.id}`) }} /> + ))} +
+ ) : null + } +
); }; @@ -442,7 +502,7 @@ class ConfigurationApplicationComponent extends React.Component { + property: "Actions", disableFilter: true, disableSorting: true, type: ColumnType.custom, customControl: ( ({ rowData })=> { return ( - { - + { + e.stopPropagation(); + e.preventDefault(); + removeElement(`${this.props.vPath}[${rowData[listKeyProperty]}]`) }} > @@ -476,10 +538,73 @@ class ConfigurationApplicationComponent extends React.Component { + 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 ( + <> +
+ {sections.elements.length > 0 + ? ( +
+ {sections.elements.map(element => this.renderUIElement(element, inputViewData, keyProperty, editMode, isNew))} +
+ ) : null + } + {sections.choises.length > 0 + ? ( +
+ {sections.choises.map(element => this.renderUIChoise(element, inputViewData, keyProperty, editMode, isNew))} +
+ ) : null + } + + {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 ) || null} { /* do not show edit if this is a list or it can't be edited */ - !displayAsList && viewSpecification.canEdit && (
+ displaySpecification.displayMode === DisplayModeType.displayAsObject && displaySpecification.viewSpecification.canEdit && (
{ 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 {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) }
); 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((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("id"); const isRequiredProperty = propertyOf("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(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((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((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 => { +export const getTree = async (searchTerm: string | null = null): Promise => { await deleay(600); const [node] = getTreeElements(searchTerm); return node; }; -export const getElement = async (id: string ): Promise => { +export const getElement = async (id: string): Promise => { 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 { } + +// create eleactic search material data fetch handler +const connectedNetworkElementsSearchHandler = createSearchDataHandler('network-element-connection', { status: "Connected" }); + +export const { + actionHandler: connectedNetworkElementsActionHandler, + createActions: createConnectedNetworkElementsActions, + createProperties: createConnectedNetworkElementsProperties, + reloadAction: connectedNetworkElementsReloadAction, + + // set value action, to change a value +} = createExternal(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.ts b/sdnr/wt/odlux/apps/inventoryApp/src/handlers/inventoryElementsHandler.ts new file mode 100644 index 000000000..a65319efa --- /dev/null +++ b/sdnr/wt/odlux/apps/inventoryApp/src/handlers/inventoryElementsHandler.ts @@ -0,0 +1,36 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + * ============LICENSE_END========================================================================== + */ +import { createExternal,IExternalTableState } from '../../../../framework/src/components/material-table/utilities'; +import { createSearchDataHandler } from '../../../../framework/src/utilities/elasticSearch'; + +import { InventoryType } from '../models/inventory'; + +export interface IInventoryElementsState extends IExternalTableState { } + +// create eleactic search material data fetch handler +const inventoryElementsSearchHandler = createSearchDataHandler("inventory"); + +export const { + actionHandler: inventoryElementsActionHandler, + createActions: createInventoryElementsActions, + createProperties: createInventoryElementsProperties, + reloadAction: inventoryElementsReloadAction, + + // set value action, to change a value +} = createExternal(inventoryElementsSearchHandler, appState => appState.inventory.inventoryElements); + diff --git a/sdnr/wt/odlux/apps/inventoryApp/src/handlers/inventoryElementsHandler.tsx b/sdnr/wt/odlux/apps/inventoryApp/src/handlers/inventoryElementsHandler.tsx deleted file mode 100644 index a65319efa..000000000 --- a/sdnr/wt/odlux/apps/inventoryApp/src/handlers/inventoryElementsHandler.tsx +++ /dev/null @@ -1,36 +0,0 @@ -/** - * ============LICENSE_START======================================================================== - * ONAP : ccsdk feature sdnr wt odlux - * ================================================================================================= - * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved. - * ================================================================================================= - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except - * in compliance with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License - * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express - * or implied. See the License for the specific language governing permissions and limitations under - * the License. - * ============LICENSE_END========================================================================== - */ -import { createExternal,IExternalTableState } from '../../../../framework/src/components/material-table/utilities'; -import { createSearchDataHandler } from '../../../../framework/src/utilities/elasticSearch'; - -import { InventoryType } from '../models/inventory'; - -export interface IInventoryElementsState extends IExternalTableState { } - -// create eleactic search material data fetch handler -const inventoryElementsSearchHandler = createSearchDataHandler("inventory"); - -export const { - actionHandler: inventoryElementsActionHandler, - createActions: createInventoryElementsActions, - createProperties: createInventoryElementsProperties, - reloadAction: inventoryElementsReloadAction, - - // set value action, to change a value -} = createExternal(inventoryElementsSearchHandler, appState => appState.inventory.inventoryElements); - diff --git a/sdnr/wt/odlux/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((acc, key) => { + const cur = node[key]; + acc.push({ + isMatch: cur.isMatch, + content: cur.label || key, + value: key, + children: cur.children && getTreeDataFromInvetoryTreeNode(cur.children), + }); + return acc; +}, []); + +export const inventoryTreeHandler: IActionHandler = (state = initialState, action) => { + if (action instanceof SetBusyAction) { + state = { ...state, isBusy: action.busy }; + } else if (action instanceof SetSearchTextAction) { + state = { ...state, searchTerm: action.searchTerm }; + } else if (action instanceof UpdateInventoryTreeAction) { + const rootNodes = getTreeDataFromInvetoryTreeNode(action.rootNode); + state = { ...state, rootNodes: rootNodes, expandedItems: [], selectedNode: undefined }; + } else if (action instanceof UpdateSelectedNodeAction) { + state = { ...state, selectedNode: action.selectedNode }; + } else if (action instanceof UpdateExpandedNodesAction) { + state = { ...state, expandedItems: action.expandedNodes || []} + } + + return state; +} \ No newline at end of file diff --git a/sdnr/wt/odlux/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 = (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 @@ 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; \ 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) => { - 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 ( - - ) -}); - const App = withRouter((props: RouteComponentProps) => ( - + + )); 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 { - return await getTree(searchTerm); + public async getInventoryTree(mountId: string, searchTerm: string = ""): Promise { + //return await getTree(searchTerm); + const path = `/tree/read-inventoryequipment-tree/${mountId}`; + const body = { + "query": searchTerm + }; + const inventoryTree = await requestRest(path, { method: "POST" , body: JSON.stringify(body)}); + return inventoryTree && convertPropertyNames(inventoryTree, replaceHyphen) || null; } - public async getInventoryEntry(id: string): Promise { - return await getElement(id); + public async getInventoryEntry(id: string): Promise { + 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; +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; 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; +let treeViewInitialSorted = false; +let inventoryInitialSorted = false; +const ConnectedElementTable = MaterialTable as MaterialTableCtorType; -type TreeDemoItem = TreeItem; +type DashboardComponentProps = RouteComponentProps & Connect; -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 { + + 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 [ + { this.props.updateInventoryTree(rowData.nodeId, rowData.uuid); this.props.navigateToApplication("inventory", rowData.nodeId) }}>View in Treeview, + ]; + + } -class DashboardComponent extends React.Component<& WithStyles & Connect> { render() { - return - + + const { panelId: activePanelId } = this.props; + return ( + <> + + + + + + + + { + + activePanelId === "InventoryElementsTable" && + + { + + return this.getContextMenu(rowData); + }} > + + + } + { + activePanelId === "TreeviewTable" && + + { 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 > + + } + + ); } 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; + + + +type TreeviewComponentProps = RouteComponentProps<{ mountId: string}> & WithStyles & Connect + +type TreeviewComponentState = { + [propsChache]: { + rootNodes?: TreeDemoItem[]; + }; + rootNodes: TreeDemoItem[]; +} + + +class DashboardComponent extends React.Component { + + constructor (props: TreeviewComponentProps) { + super(props); + + this.state = { + [propsChache]: {}, + rootNodes: [], + }; + } + + static getDerivedStateFromProps(props: TreeviewComponentProps, state: TreeviewComponentState) { + if (state[propsChache].rootNodes != props.rootNodes) { + state = { ...state, rootNodes: props.rootNodes} + } + return state; + } + + render() { + const { classes, updateInventoryTree, updateExpendedNodes, expendedItems, selectedNode, selectTreeNode, searchTerm, match: { params: { mountId }} } = this.props; + return ( +
+ updateInventoryTree(mountId, searchTerm)} expandedItems={expendedItems} onFolderClick={(item) => { + const indexOfItemToToggle = expendedItems.indexOf(item); + if (indexOfItemToToggle === -1) { + updateExpendedNodes([...expendedItems, item]); + } else { + updateExpendedNodes([ + ...expendedItems.slice(0, indexOfItemToToggle), + ...expendedItems.slice(indexOfItemToToggle + 1), + ]); + } + }} + onItemClick={(elm) => selectTreeNode(elm.value)} /> +
{ + selectedNode && renderObject(selectedNode) || null + }
+
+ ); + } + + 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 = (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 ( <>
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 } } diff --git a/sdnr/wt/odlux/core/features/pom.xml b/sdnr/wt/odlux/core/features/pom.xml index d63f8d892..2aa72b769 100644 --- a/sdnr/wt/odlux/core/features/pom.xml +++ b/sdnr/wt/odlux/core/features/pom.xml @@ -46,17 +46,6 @@ - - - - org.opendaylight.controller - mdsal-artifacts - ${odl.controller.mdsal.version} - pom - import - - - diff --git a/sdnr/wt/odlux/core/model/src/main/java/com/opensymphony/xwork2/util/ClassLoaderUtil.java b/sdnr/wt/odlux/core/model/src/main/java/com/opensymphony/xwork2/util/ClassLoaderUtil.java index 133b08494..636c9db29 100644 --- a/sdnr/wt/odlux/core/model/src/main/java/com/opensymphony/xwork2/util/ClassLoaderUtil.java +++ b/sdnr/wt/odlux/core/model/src/main/java/com/opensymphony/xwork2/util/ClassLoaderUtil.java @@ -22,11 +22,11 @@ import java.util.*; /** - * This class is extremely useful for loading resources and classes in a fault tolerant manner - * that works across different applications servers. + * This class is extremely useful for loading resources and classes in a fault tolerant manner that works across + * different applications servers. * - * It has come out of many months of frustrating use of multiple application servers at Atlassian, - * please don't change things unless you're sure they're not going to break in one server or another! + * It has come out of many months of frustrating use of multiple application servers at Atlassian, please don't change + * things unless you're sure they're not going to break in one server or another! * * It was brought in from oscore trunk revision 147. * @@ -37,58 +37,59 @@ public class ClassLoaderUtil { //~ Methods //////////////////////////////////////////////////////////////// /** - * Load all resources with a given name, potentially aggregating all results - * from the searched classloaders. If no results are found, the resource name - * is prepended by '/' and tried again. + * Load all resources with a given name, potentially aggregating all results from the searched classloaders. If no + * results are found, the resource name is prepended by '/' and tried again. * * This method will try to load the resources using the following methods (in order): *
    - *
  • From Thread.currentThread().getContextClassLoader() - *
  • From ClassLoaderUtil.class.getClassLoader() - *
  • callingClass.getClassLoader() + *
  • From Thread.currentThread().getContextClassLoader() + *
  • From ClassLoaderUtil.class.getClassLoader() + *
  • callingClass.getClassLoader() *
* * @param resourceName The name of the resources to load * @param callingClass The Class object of the calling object */ - public static Iterator getResources(String resourceName, Class callingClass, boolean aggregate) throws IOException { + public static Iterator getResources(String resourceName, Class callingClass, boolean aggregate) + throws IOException { - AggregateIterator iterator = new AggregateIterator<>(); + AggregateIterator iterator = new AggregateIterator<>(); - iterator.addEnumeration(Thread.currentThread().getContextClassLoader().getResources(resourceName)); + iterator.addEnumeration(Thread.currentThread().getContextClassLoader().getResources(resourceName)); - if (!iterator.hasNext() || aggregate) { - iterator.addEnumeration(ClassLoaderUtil.class.getClassLoader().getResources(resourceName)); - } + if (!iterator.hasNext() || aggregate) { + iterator.addEnumeration(ClassLoaderUtil.class.getClassLoader().getResources(resourceName)); + } - if (!iterator.hasNext() || aggregate) { - ClassLoader cl = callingClass.getClassLoader(); + if (!iterator.hasNext() || aggregate) { + ClassLoader cl = callingClass.getClassLoader(); - if (cl != null) { - iterator.addEnumeration(cl.getResources(resourceName)); - } - } + if (cl != null) { + iterator.addEnumeration(cl.getResources(resourceName)); + } + } - if (!iterator.hasNext() && resourceName != null && (resourceName.length() == 0 || resourceName.charAt(0) != '/')) { - return getResources('/' + resourceName, callingClass, aggregate); - } + if (!iterator.hasNext() && resourceName != null + && (resourceName.length() == 0 || resourceName.charAt(0) != '/')) { + return getResources('/' + resourceName, callingClass, aggregate); + } - return iterator; - } + return iterator; + } /** - * Load a given resource. - * - * This method will try to load the resource using the following methods (in order): - *
    - *
  • From Thread.currentThread().getContextClassLoader() - *
  • From ClassLoaderUtil.class.getClassLoader() - *
  • callingClass.getClassLoader() - *
- * - * @param resourceName The name IllegalStateException("Unable to call ")of the resource to load - * @param callingClass The Class object of the calling object - */ + * Load a given resource. + * + * This method will try to load the resource using the following methods (in order): + *
    + *
  • From Thread.currentThread().getContextClassLoader() + *
  • From ClassLoaderUtil.class.getClassLoader() + *
  • callingClass.getClassLoader() + *
+ * + * @param resourceName The name IllegalStateException("Unable to call ")of the resource to load + * @param callingClass The Class object of the calling object + */ public static URL getResource(String resourceName, Class callingClass) { URL url = Thread.currentThread().getContextClassLoader().getResource(resourceName); @@ -112,13 +113,13 @@ public class ClassLoaderUtil { } /** - * This is a convenience method to load a resource as a stream. - * - * The algorithm used to find the resource is given in getResource() - * - * @param resourceName The name of the resource to load - * @param callingClass The Class object of the calling object - */ + * This is a convenience method to load a resource as a stream. + * + * The algorithm used to find the resource is given in getResource() + * + * @param resourceName The name of the resource to load + * @param callingClass The Class object of the calling object + */ public static InputStream getResourceAsStream(String resourceName, Class callingClass) { URL url = getResource(resourceName, callingClass); @@ -130,20 +131,20 @@ public class ClassLoaderUtil { } /** - * Load a class with a given name. - * - * It will try to load the class in the following order: - *
    - *
  • From Thread.currentThread().getContextClassLoader() - *
  • Using the basic Class.forName() - *
  • From ClassLoaderUtil.class.getClassLoader() - *
  • From the callingClass.getClassLoader() - *
- * - * @param className The name of the class to load - * @param callingClass The Class object of the calling object - * @throws ClassNotFoundException If the class cannot be found anywhere. - */ + * Load a class with a given name. + * + * It will try to load the class in the following order: + *
    + *
  • From Thread.currentThread().getContextClassLoader() + *
  • Using the basic Class.forName() + *
  • From ClassLoaderUtil.class.getClassLoader() + *
  • From the callingClass.getClassLoader() + *
+ * + * @param className The name of the class to load + * @param callingClass The Class object of the calling object + * @throws ClassNotFoundException If the class cannot be found anywhere. + */ public static Class loadClass(String className, Class callingClass) throws ClassNotFoundException { try { return Thread.currentThread().getContextClassLoader().loadClass(className); @@ -161,8 +162,8 @@ public class ClassLoaderUtil { } /** - * Aggregates Enumeration instances into one iterator and filters out duplicates. Always keeps one - * ahead of the enumerator to protect against returning duplicates. + * Aggregates Enumeration instances into one iterator and filters out duplicates. Always keeps one ahead of the + * enumerator to protect against returning duplicates. */ static class AggregateIterator implements Iterator { diff --git a/sdnr/wt/odlux/core/model/src/main/java/org/onap/ccsdk/features/sdnr/wt/odlux/model/bundles/ClassLoaderUtilExt.java b/sdnr/wt/odlux/core/model/src/main/java/org/onap/ccsdk/features/sdnr/wt/odlux/model/bundles/ClassLoaderUtilExt.java index f6d0b55bf..a4a0e76c3 100644 --- a/sdnr/wt/odlux/core/model/src/main/java/org/onap/ccsdk/features/sdnr/wt/odlux/model/bundles/ClassLoaderUtilExt.java +++ b/sdnr/wt/odlux/core/model/src/main/java/org/onap/ccsdk/features/sdnr/wt/odlux/model/bundles/ClassLoaderUtilExt.java @@ -37,7 +37,7 @@ public class ClassLoaderUtilExt { try { urls = getResources(resourceName, callingClass, true); } catch (IOException e) { - LOG.debug("No resource {}",resourceName); + LOG.debug("No resource {}", resourceName); } if (urls != null) { while (urls.hasNext()) { @@ -50,11 +50,9 @@ public class ClassLoaderUtilExt { if (url == null) { LOG.debug("res currently not found. try to find with bundle"); Bundle b = FrameworkUtil.getBundle(callingClass); - if(b!=null) - { + if (b != null) { url = b.getEntry(resourceName); - } - else { + } else { ClassLoader loader = Thread.currentThread().getContextClassLoader(); url = loader.getResource(resourceName); } diff --git a/sdnr/wt/odlux/core/model/src/main/java/org/onap/ccsdk/features/sdnr/wt/odlux/model/bundles/OdluxBundle.java b/sdnr/wt/odlux/core/model/src/main/java/org/onap/ccsdk/features/sdnr/wt/odlux/model/bundles/OdluxBundle.java index e1e300a0d..ab69d63c8 100644 --- a/sdnr/wt/odlux/core/model/src/main/java/org/onap/ccsdk/features/sdnr/wt/odlux/model/bundles/OdluxBundle.java +++ b/sdnr/wt/odlux/core/model/src/main/java/org/onap/ccsdk/features/sdnr/wt/odlux/model/bundles/OdluxBundle.java @@ -25,8 +25,8 @@ import java.io.InputStreamReader; import java.net.URL; /** - * At startup of each karaf bundle, each UI module creates an instance of this class via blueprint. - * Initialize method gets called at loading of bundle. + * At startup of each karaf bundle, each UI module creates an instance of this class via blueprint. Initialize method + * gets called at loading of bundle. */ public class OdluxBundle { @@ -103,12 +103,12 @@ public class OdluxBundle { public String getResourceFileContent(String filename) { return this.loadFileContent(this.getResource(filename)); } - + protected URL getResource(String filename) { return ClassLoaderUtilExt.getResource(filename, this.getClass()); } - protected String loadFileContent(final URL url ) { + protected String loadFileContent(final URL url) { if (url == null) return null; LOG.debug("try to load res " + url.toString()); @@ -124,15 +124,14 @@ public class OdluxBundle { } catch (IOException e) { LOG.warn("could not load resfile " + url.toString() + ": " + e.getMessage()); return null; - } - finally { - if(in!=null) { - try { - in.close(); - } catch (IOException e) { - - } - } + } finally { + if (in != null) { + try { + in.close(); + } catch (IOException e) { + + } + } } return sb.toString(); diff --git a/sdnr/wt/odlux/core/model/src/main/java/org/onap/ccsdk/features/sdnr/wt/odlux/model/bundles/OdluxBundleLoader.java b/sdnr/wt/odlux/core/model/src/main/java/org/onap/ccsdk/features/sdnr/wt/odlux/model/bundles/OdluxBundleLoader.java index 455c83296..870011e85 100644 --- a/sdnr/wt/odlux/core/model/src/main/java/org/onap/ccsdk/features/sdnr/wt/odlux/model/bundles/OdluxBundleLoader.java +++ b/sdnr/wt/odlux/core/model/src/main/java/org/onap/ccsdk/features/sdnr/wt/odlux/model/bundles/OdluxBundleLoader.java @@ -25,6 +25,6 @@ public interface OdluxBundleLoader { public int getNumberOfBundles(); - public String getResourceContent(String fn, OdluxBundleResourceAccess indexBundle); + public String getResourceContent(String fn, OdluxBundleResourceAccess indexBundle); } diff --git a/sdnr/wt/odlux/core/model/src/main/java/org/onap/ccsdk/features/sdnr/wt/odlux/model/bundles/OdluxBundleResourceAccess.java b/sdnr/wt/odlux/core/model/src/main/java/org/onap/ccsdk/features/sdnr/wt/odlux/model/bundles/OdluxBundleResourceAccess.java index f9a842556..1abed468b 100644 --- a/sdnr/wt/odlux/core/model/src/main/java/org/onap/ccsdk/features/sdnr/wt/odlux/model/bundles/OdluxBundleResourceAccess.java +++ b/sdnr/wt/odlux/core/model/src/main/java/org/onap/ccsdk/features/sdnr/wt/odlux/model/bundles/OdluxBundleResourceAccess.java @@ -22,7 +22,9 @@ import java.util.List; public interface OdluxBundleResourceAccess { boolean hasResource(String fn); + String getResourceFileContent(String fn, List bundleNames); + String getBundleName(); } diff --git a/sdnr/wt/odlux/core/provider/src/main/java/org/onap/ccsdk/features/sdnr/wt/odlux/IndexOdluxBundle.java b/sdnr/wt/odlux/core/provider/src/main/java/org/onap/ccsdk/features/sdnr/wt/odlux/IndexOdluxBundle.java index 75497e08c..1ea27d71f 100644 --- a/sdnr/wt/odlux/core/provider/src/main/java/org/onap/ccsdk/features/sdnr/wt/odlux/IndexOdluxBundle.java +++ b/sdnr/wt/odlux/core/provider/src/main/java/org/onap/ccsdk/features/sdnr/wt/odlux/IndexOdluxBundle.java @@ -46,15 +46,15 @@ public class IndexOdluxBundle extends OdluxBundle implements OdluxBundleResource super(null, BUNDLENAME_APP); } + @Override - protected String loadFileContent(URL url) - { + protected String loadFileContent(URL url) { return loadFileContent(url, OdluxBundleLoaderImpl.getInstance().getLoadedBundles(this.getBundleName())); } @Override public String getResourceFileContent(String fn, List bundleNames) { - return loadFileContent(this.getResource(fn),bundleNames); + return loadFileContent(this.getResource(fn), bundleNames); } private static String loadFileContent(URL url, List bundlesNamesList) { @@ -64,7 +64,7 @@ public class IndexOdluxBundle extends OdluxBundle implements OdluxBundleResource LOG.debug("try to load res " + url.toString()); StringBuilder sb = new StringBuilder(); Matcher matcher; - BufferedReader in=null; + BufferedReader in = null; try { in = new BufferedReader(new InputStreamReader(url.openStream())); @@ -73,8 +73,8 @@ public class IndexOdluxBundle extends OdluxBundle implements OdluxBundleResource if (url.getFile().endsWith("index.html")) { matcher = patternRequire.matcher(inputLine); if (matcher.find()) { - inputLine = inputLine.substring(0, matcher.start(1)) + "\"" + String.join("\",\"", bundlesNamesList) - + "\"" + inputLine.substring(matcher.end(1)); + inputLine = inputLine.substring(0, matcher.start(1)) + "\"" + + String.join("\",\"", bundlesNamesList) + "\"" + inputLine.substring(matcher.end(1)); } matcher = patternFunction.matcher(inputLine); if (matcher.find()) { @@ -89,26 +89,25 @@ public class IndexOdluxBundle extends OdluxBundle implements OdluxBundleResource hlp += bundle + ".register();" + LR; } } - inputLine = inputLine.substring(0, matcher.start(1)) + hlp - + inputLine.substring(matcher.start(1)); + inputLine = + inputLine.substring(0, matcher.start(1)) + hlp + inputLine.substring(matcher.start(1)); } } sb.append(inputLine + LR); } - + } catch (IOException e) { LOG.warn("could not load resfile {} : {}", url, e.getMessage()); return null; - } - finally { - try { - if (in != null) { - in.close(); - } - } catch (IOException e) { + } finally { + try { + if (in != null) { + in.close(); + } + } catch (IOException e) { - } - } + } + } return sb.toString(); } diff --git a/sdnr/wt/odlux/core/provider/src/main/java/org/onap/ccsdk/features/sdnr/wt/odlux/IndexServlet.java b/sdnr/wt/odlux/core/provider/src/main/java/org/onap/ccsdk/features/sdnr/wt/odlux/IndexServlet.java index db709b17d..a7cc2ac82 100644 --- a/sdnr/wt/odlux/core/provider/src/main/java/org/onap/ccsdk/features/sdnr/wt/odlux/IndexServlet.java +++ b/sdnr/wt/odlux/core/provider/src/main/java/org/onap/ccsdk/features/sdnr/wt/odlux/IndexServlet.java @@ -22,7 +22,6 @@ import java.io.IOException; import java.io.OutputStream; import java.net.HttpURLConnection; import java.nio.file.Files; - import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; @@ -32,40 +31,40 @@ import org.slf4j.LoggerFactory; public class IndexServlet extends HttpServlet { - private static final long serialVersionUID = 3039669437157215355L; - private static Logger LOG = LoggerFactory.getLogger(IndexServlet.class); + private static final long serialVersionUID = 3039669437157215355L; + private static Logger LOG = LoggerFactory.getLogger(IndexServlet.class); - @Override - protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { - if (req.getRequestURI() != null && req.getRequestURI().contains("favicon.ico")) { - this.sendFile(resp, "etc/favicon.ico","image/x-icon"); - } else { + @Override + protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { + if (req.getRequestURI() != null && req.getRequestURI().contains("favicon.ico")) { + this.sendFile(resp, "etc/favicon.ico", "image/x-icon"); + } else { - LOG.debug("redirect to odlux/index.html"); - resp.setStatus(HttpServletResponse.SC_MOVED_PERMANENTLY); - resp.setHeader("Location", "odlux/index.html"); - } - } + LOG.debug("redirect to odlux/index.html"); + resp.setStatus(HttpServletResponse.SC_MOVED_PERMANENTLY); + resp.setHeader("Location", "odlux/index.html"); + } + } - private void sendFile(HttpServletResponse response, String filename, String mimeType) { - File f = new File(filename); - if (f.exists()) { - try { - byte[] bytes = Files.readAllBytes(f.toPath()); - response.setContentType(mimeType); - response.setContentLength(bytes.length); - response.setStatus(HttpURLConnection.HTTP_OK); - OutputStream os = response.getOutputStream(); - os.write(bytes); - os.flush(); - os.close(); - } catch (IOException e) { - LOG.debug("problem sending {}: {}", filename, e); - } - } else { - LOG.debug("file not found: {}",filename); - response.setStatus(HttpURLConnection.HTTP_NOT_FOUND); - } - } + private void sendFile(HttpServletResponse response, String filename, String mimeType) { + File f = new File(filename); + if (f.exists()) { + try { + byte[] bytes = Files.readAllBytes(f.toPath()); + response.setContentType(mimeType); + response.setContentLength(bytes.length); + response.setStatus(HttpURLConnection.HTTP_OK); + OutputStream os = response.getOutputStream(); + os.write(bytes); + os.flush(); + os.close(); + } catch (IOException e) { + LOG.debug("problem sending {}: {}", filename, e); + } + } else { + LOG.debug("file not found: {}", filename); + response.setStatus(HttpURLConnection.HTTP_NOT_FOUND); + } + } } diff --git a/sdnr/wt/odlux/core/provider/src/main/java/org/onap/ccsdk/features/sdnr/wt/odlux/ResFilesServlet.java b/sdnr/wt/odlux/core/provider/src/main/java/org/onap/ccsdk/features/sdnr/wt/odlux/ResFilesServlet.java index 321924e52..b480d89eb 100644 --- a/sdnr/wt/odlux/core/provider/src/main/java/org/onap/ccsdk/features/sdnr/wt/odlux/ResFilesServlet.java +++ b/sdnr/wt/odlux/core/provider/src/main/java/org/onap/ccsdk/features/sdnr/wt/odlux/ResFilesServlet.java @@ -20,12 +20,10 @@ package org.onap.ccsdk.features.sdnr.wt.odlux; import java.io.IOException; import java.io.OutputStream; import java.net.HttpURLConnection; - import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; - import org.onap.ccsdk.features.sdnr.wt.odlux.model.bundles.OdluxBundleLoader; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -46,34 +44,35 @@ public class ResFilesServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { - final String fn = req.getRequestURI(); + final String fn = req.getRequestURI(); LOG.debug("Get request with for URI: {}", fn); OdluxBundleLoader odluxBundleLoader = OdluxBundleLoaderImpl.getInstance(); - if (odluxBundleLoader != null) { - String fileContent = odluxBundleLoader.getResourceContent(fn, indexBundle); - if (fileContent != null) { - //Store header info - String mimeType = getMimeType(fn); - byte[] byteContent = fileContent.getBytes(java.nio.charset.StandardCharsets.UTF_8); - int length = byteContent.length; + if (odluxBundleLoader != null) { + String fileContent = odluxBundleLoader.getResourceContent(fn, indexBundle); + if (fileContent != null) { + //Store header info + String mimeType = getMimeType(fn); + byte[] byteContent = fileContent.getBytes(java.nio.charset.StandardCharsets.UTF_8); + int length = byteContent.length; - LOG.debug("Found file in resources. Name {} mimetype {} length {} and write to output stream", fn, mimeType, length); - resp.setContentType(mimeType); - resp.setContentLength(length); - resp.setStatus(HttpURLConnection.HTTP_OK); - OutputStream os = resp.getOutputStream(); - os.write(byteContent); - os.flush(); - os.close(); - } else { - LOG.debug("File {} not found in res.", fn); - resp.setStatus(HttpURLConnection.HTTP_NOT_FOUND); - } - } else { - LOG.debug("BundleLoaderInstance to found.", fn); - resp.setStatus(HttpURLConnection.HTTP_NOT_FOUND); - } + LOG.debug("Found file in resources. Name {} mimetype {} length {} and write to output stream", fn, + mimeType, length); + resp.setContentType(mimeType); + resp.setContentLength(length); + resp.setStatus(HttpURLConnection.HTTP_OK); + OutputStream os = resp.getOutputStream(); + os.write(byteContent); + os.flush(); + os.close(); + } else { + LOG.debug("File {} not found in res.", fn); + resp.setStatus(HttpURLConnection.HTTP_NOT_FOUND); + } + } else { + LOG.debug("BundleLoaderInstance to found.", fn); + resp.setStatus(HttpURLConnection.HTTP_NOT_FOUND); + } } public String loadFileContent(String filename) { @@ -82,7 +81,7 @@ public class ResFilesServlet extends HttpServlet { //Provide own function that can be overloaded for test public String getMimeType(String fileName) { - return getServletContext().getMimeType(fileName); + return getServletContext().getMimeType(fileName); } } diff --git a/sdnr/wt/odlux/core/provider/src/test/java/org/onap/ccsdk/features/sdnr/odlux/test/TestBundleLoaderImpl.java b/sdnr/wt/odlux/core/provider/src/test/java/org/onap/ccsdk/features/sdnr/odlux/test/TestBundleLoaderImpl.java index c973d530b..3d817a2d2 100644 --- a/sdnr/wt/odlux/core/provider/src/test/java/org/onap/ccsdk/features/sdnr/odlux/test/TestBundleLoaderImpl.java +++ b/sdnr/wt/odlux/core/provider/src/test/java/org/onap/ccsdk/features/sdnr/odlux/test/TestBundleLoaderImpl.java @@ -20,7 +20,6 @@ package org.onap.ccsdk.features.sdnr.odlux.test; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; - import org.junit.Test; import org.onap.ccsdk.features.sdnr.wt.odlux.OdluxBundleLoaderImpl; import org.onap.ccsdk.features.sdnr.wt.odlux.model.bundles.OdluxBundle; @@ -45,10 +44,10 @@ public class TestBundleLoaderImpl { bundle2.initialize(); assertNotNull(bundle1.getResourceFileContent("index.html")); assertNotNull(bundle2.getResourceFileContent("index2.html")); - assertEquals(loaded+2, loader.getNumberOfBundles()); + assertEquals(loaded + 2, loader.getNumberOfBundles()); loader.addBundle(bundle1); loader.addBundle(bundle2); - assertEquals(loaded+2, loader.getNumberOfBundles()); + assertEquals(loaded + 2, loader.getNumberOfBundles()); loader.removeBundle(bundle1); loader.removeBundle(bundle2); assertEquals(loaded, loader.getNumberOfBundles()); @@ -56,10 +55,10 @@ public class TestBundleLoaderImpl { assertTrue(bundle1.hasResource("index.html")); assertTrue(bundle2.hasResource("index2.html")); assertNotNull(bundle1.getLoader()); - assertEquals(0,bundle1.getIndex()); + assertEquals(0, bundle1.getIndex()); bundle1.clean(); bundle2.clean(); - assertEquals(loaded,loader.getNumberOfBundles()); + assertEquals(loaded, loader.getNumberOfBundles()); } diff --git a/sdnr/wt/odlux/core/provider/src/test/java/org/onap/ccsdk/features/sdnr/odlux/test/TestLoadResources.java b/sdnr/wt/odlux/core/provider/src/test/java/org/onap/ccsdk/features/sdnr/odlux/test/TestLoadResources.java index d9cd920de..bef3dba28 100644 --- a/sdnr/wt/odlux/core/provider/src/test/java/org/onap/ccsdk/features/sdnr/odlux/test/TestLoadResources.java +++ b/sdnr/wt/odlux/core/provider/src/test/java/org/onap/ccsdk/features/sdnr/odlux/test/TestLoadResources.java @@ -16,11 +16,10 @@ * ============LICENSE_END========================================================================== */ package org.onap.ccsdk.features.sdnr.odlux.test; -import static org.junit.Assert.*; +import static org.junit.Assert.*; import java.util.regex.Matcher; import java.util.regex.Pattern; - import org.junit.Test; import org.onap.ccsdk.features.sdnr.wt.odlux.ResFilesServlet; @@ -29,15 +28,16 @@ public class TestLoadResources { @Test public void test() { ResFilesServlet servlet = new ResFilesServlet(); - String indexhtml=null; - indexhtml=servlet.loadFileContent("odlux/index.html"); + String indexhtml = null; + indexhtml = servlet.loadFileContent("odlux/index.html"); assertNotNull(indexhtml); final String regex = "require\\(\\[.*\"run\".*\\]"; - final Pattern pattern = Pattern.compile(regex,Pattern.MULTILINE); + final Pattern pattern = Pattern.compile(regex, Pattern.MULTILINE); System.out.println(indexhtml); final Matcher matcher = pattern.matcher(indexhtml); - assertTrue("Can not find patter '"+regex+"'",matcher.find()); + assertTrue("Can not find patter '" + regex + "'", matcher.find()); } + @Test public void test2() { diff --git a/sdnr/wt/odlux/core/provider/src/test/java/org/onap/ccsdk/features/sdnr/odlux/test/TestRedirect.java b/sdnr/wt/odlux/core/provider/src/test/java/org/onap/ccsdk/features/sdnr/odlux/test/TestRedirect.java index bbf542441..68bcae547 100644 --- a/sdnr/wt/odlux/core/provider/src/test/java/org/onap/ccsdk/features/sdnr/odlux/test/TestRedirect.java +++ b/sdnr/wt/odlux/core/provider/src/test/java/org/onap/ccsdk/features/sdnr/odlux/test/TestRedirect.java @@ -18,40 +18,36 @@ package org.onap.ccsdk.features.sdnr.odlux.test; import static org.junit.Assert.*; - +import static org.mockito.Mockito.*; import org.junit.Test; import org.onap.ccsdk.features.sdnr.wt.odlux.IndexServlet; - -import static org.mockito.Mockito.*; - import java.io.IOException; - import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; public class TestRedirect { - private static final int RESPONSECODE_REDIRECT = 301; - - @Test - public void test() { - PublicIndexServlet servlet =new PublicIndexServlet(); - HttpServletRequest req = mock(HttpServletRequest.class); - HttpServletResponse resp = mock(HttpServletResponse.class); - try { - servlet.doGet(req,resp); - } catch (Exception e) { - fail(e.getMessage()); - } - verify(resp).setStatus(RESPONSECODE_REDIRECT); - } - - private static class PublicIndexServlet extends IndexServlet{ - @Override - public void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { - super.doGet(req, resp); - } - } + private static final int RESPONSECODE_REDIRECT = 301; + + @Test + public void test() { + PublicIndexServlet servlet = new PublicIndexServlet(); + HttpServletRequest req = mock(HttpServletRequest.class); + HttpServletResponse resp = mock(HttpServletResponse.class); + try { + servlet.doGet(req, resp); + } catch (Exception e) { + fail(e.getMessage()); + } + verify(resp).setStatus(RESPONSECODE_REDIRECT); + } + + private static class PublicIndexServlet extends IndexServlet { + @Override + public void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { + super.doGet(req, resp); + } + } } diff --git a/sdnr/wt/odlux/core/provider/src/test/java/org/onap/ccsdk/features/sdnr/odlux/test/TestResFileServlet.java b/sdnr/wt/odlux/core/provider/src/test/java/org/onap/ccsdk/features/sdnr/odlux/test/TestResFileServlet.java index 0490c80d5..6d537e38b 100644 --- a/sdnr/wt/odlux/core/provider/src/test/java/org/onap/ccsdk/features/sdnr/odlux/test/TestResFileServlet.java +++ b/sdnr/wt/odlux/core/provider/src/test/java/org/onap/ccsdk/features/sdnr/odlux/test/TestResFileServlet.java @@ -18,18 +18,16 @@ package org.onap.ccsdk.features.sdnr.odlux.test; import static org.junit.Assert.*; - +import static org.mockito.Mockito.*; import java.io.IOException; import java.io.StringWriter; import java.net.HttpURLConnection; - import javax.servlet.ServletContext; import javax.servlet.ServletException; import javax.servlet.ServletOutputStream; import javax.servlet.WriteListener; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; -import static org.mockito.Mockito.*; import org.junit.Test; import org.onap.ccsdk.features.sdnr.wt.odlux.OdluxBundleLoaderImpl; import org.onap.ccsdk.features.sdnr.wt.odlux.ResFilesServlet; @@ -43,7 +41,7 @@ public class TestResFileServlet { @Test public void test() throws ServletException { servlet = new PublicResFilesServlet(); - servlet.init(); + servlet.init(); OdluxBundleLoader loader = OdluxBundleLoaderImpl.getInstance(); OdluxBundle b = new OdluxBundle(); @@ -96,9 +94,10 @@ public class TestResFileServlet { public void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { super.doGet(req, resp); } + @Override public String getMimeType(String fileName) { - return "mimetype"; + return "mimetype"; } } } diff --git a/sdnr/wt/odlux/framework/package.json b/sdnr/wt/odlux/framework/package.json index c94568857..511b97778 100644 --- a/sdnr/wt/odlux/framework/package.json +++ b/sdnr/wt/odlux/framework/package.json @@ -24,7 +24,7 @@ "author": "Matthias Fischer", "license": "Apache-2.0", "peerDependencies": { - "@types/node": "11.9.5", + "@types/node": "11.11.6", "@types/react": "16.9.19", "@types/react-dom": "16.9.5", "@types/react-router-dom": "4.3.1", @@ -44,6 +44,8 @@ "@types/jsonwebtoken": "7.2.8" }, "dependencies": { + "@babel/polyfill": "^7.0.0", + "@types/x2js": "^3.1.0", "http-server": "^0.11.1" } -} \ No newline at end of file +} diff --git a/sdnr/wt/odlux/framework/pom.xml b/sdnr/wt/odlux/framework/pom.xml index b125a4537..5bb7d6f6a 100644 --- a/sdnr/wt/odlux/framework/pom.xml +++ b/sdnr/wt/odlux/framework/pom.xml @@ -46,7 +46,7 @@ ${maven.build.timestamp} ONAP Frankfurt (Neon, mdsal ${odl.mdsal.version}) - 52.3b24c2d(20/04/08) + 56.139cd6d(20/07/08) ONAP SDN-R | ONF Wireless for ${distversion} - Build: ${buildtime} ${buildno} ${project.version} diff --git a/sdnr/wt/odlux/framework/src/components/material-ui/treeView.tsx b/sdnr/wt/odlux/framework/src/components/material-ui/treeView.tsx index 1bb49367c..adf0b8e5a 100644 --- a/sdnr/wt/odlux/framework/src/components/material-ui/treeView.tsx +++ b/sdnr/wt/odlux/framework/src/components/material-ui/treeView.tsx @@ -77,15 +77,18 @@ type TreeViewComponentBaseProps = WithTheme & WithStyles = TreeViewComponentBaseProps & { + initialSearchTerm? : string; onItemClick?: (item: TreeItem) => void; onFolderClick?: (item: TreeItem) => void; } type TreeViewComponentWithExternalSearchProps = TreeViewComponentBaseProps & { items: ExternalTreeItem[]; + initialSearchTerm? : string; searchTerm: string; onSearch: (searchTerm: string) => void; onItemClick?: (item: TreeItem) => void; @@ -94,6 +97,7 @@ type TreeViewComponentWithExternalSearchProps = TreeViewComponentBas type TreeViewComponentWithExternalStateProps = TreeViewComponentBaseProps & TreeViewComponentState & { items: ExternalTreeItem[]; + initialSearchTerm? : string; searchTerm: string; onSearch: (searchTerm: string) => void; onItemClick: (item: TreeItem) => void; @@ -137,7 +141,7 @@ class TreeViewComponent extends React.Component { + if (obj == null) { + return obj === undefined ? "Undefined" : "Null"; + } + return Object.prototype.toString.call(obj).slice(8, -1); +}; + +const isObjectLike = (obj: any) => { + return typeof obj === "object" && obj !== null; +}; + +const isBoolean = (obj: any) => { + return obj === true || obj === false || + (isObjectLike(obj) && getTypeName(obj) === "Boolean"); +}; + +const isNumber = (obj: any) => { + return typeof obj === "number" || + (isObjectLike(obj) && getTypeName(obj) === "Number"); +}; + +const isString = (obj: any) => { + return typeof obj === "string" || + (isObjectLike(obj) && getTypeName(obj) === "String"); +}; + +const isNull = (obj: any) => { + return obj === null; +}; + +const isDate = (obj: any): boolean => { + return isObjectLike(obj) && (obj instanceof Date); +}; + +const useSimpleTableStyles = makeStyles({ + root: { + }, + table: { + fontFamily: "verdana, arial, helvetica, sans-serif", + borderSpacing: "3px", + borderCollapse: "separate", + marginLeft: "30px" + }, + label: { + cursor: "pointer", + }, + th: { + textAlign: "left", + color: "white", + padding: "5px", + backgroundColor: "#cccccc", + }, + td: { + verticalAlign: "top", + padding: "0.5rem 1rem", + borderBottom: "2px solid #DDD" + }, + object: { + }, + objectTh: { + backgroundColor: "#4444cc", + }, + objectTd: { + padding: "0.5rem 1rem", + borderBottom: "2px solid #DDD" + }, +}); + + +type SimpleTableProps = { + classNameTh?: string; + label?: JSX.Element | string | null; + cols?: number; + expand?: boolean; +} + +const SimpleTable: React.FC = (props) => { + const { label = '', cols = 2, expand = true, classNameTh, children } = props; + const [isExpanded, setIsExpanded] = React.useState(expand); + + const classes = useSimpleTableStyles(); + + React.useEffect(() => { + setIsExpanded(expand); + }, [expand]); + + const handleClick = () => { + setIsExpanded(!isExpanded); + }; + + return ( + + {label && ( + + + + ) || null + } + {isExpanded && {children} || null} +
+ {label} +
+ ); +}; + + +type ObjectRendererProps = { + className?: string; + label?: JSX.Element | string | null; + expand?: boolean; + object: { [key: string]: any }; +}; + +const ObjectRenderer: React.FC = (props) => { + const { object, className, label = 'Object', expand = true } = props; + const classes = useSimpleTableStyles(); + + return ( + + { + Object.keys(object).map(key => { + return ( + + {String(key)} + {renderObject(object[key])} + + ); + }) + } + + ); +}; + + +type ArrayRendererProps = { + label?: JSX.Element | string | null; + extraRenderer?: { [label: string]: React.ComponentType<{ label?: JSX.Element | string | null; object: any; }> }; + description?: string; + object: any; +}; + +const ArrayRenderer: React.FC = (props) => { + + return null; +}; + +export const renderObject = (object: any): JSX.Element | string => { + if (isString(object) || isNumber(object) || isBoolean(object)) { + return String(object); + } + return ; +}; diff --git a/sdnr/wt/odlux/framework/src/index.html b/sdnr/wt/odlux/framework/src/index.html index 1a373392d..d51c448a9 100644 --- a/sdnr/wt/odlux/framework/src/index.html +++ b/sdnr/wt/odlux/framework/src/index.html @@ -13,11 +13,13 @@
- diff --git a/sdnr/wt/odlux/framework/src/services/authenticationService.ts b/sdnr/wt/odlux/framework/src/services/authenticationService.ts index d3d62c566..cd8a93a53 100644 --- a/sdnr/wt/odlux/framework/src/services/authenticationService.ts +++ b/sdnr/wt/odlux/framework/src/services/authenticationService.ts @@ -26,7 +26,7 @@ type AuthTokenResponse = { class AuthenticationService { - public async authenticateUser(email: string, password: string, scope: string): Promise { + public async authenticateUserOAuth(email: string, password: string, scope: string): Promise { const result = await requestRest(`oauth2/token`, { method: "POST", headers: { @@ -47,6 +47,24 @@ class AuthenticationService { expires: (new Date().valueOf()) + (resultObj.expires_in * 1000) } || null; } + + public async authenticateUserBasicAuth(email: string, password: string, scope: string): Promise { + const result = await requestRest(`restconf/modules`, { + method: "GET", + headers: { + 'Authorization': "Basic " + btoa(email + ":" + password) + }, + }, false); + if (result) { + return { + username: email, + access_token: btoa(email + ":" + password), + token_type: "Basic", + expires: (new Date()).valueOf() + 2678400000 // 31 days + } + } + return null; + } } export const authenticationService = new AuthenticationService(); diff --git a/sdnr/wt/odlux/framework/src/views/about.tsx b/sdnr/wt/odlux/framework/src/views/about.tsx index db0411793..ca3953af1 100644 --- a/sdnr/wt/odlux/framework/src/views/about.tsx +++ b/sdnr/wt/odlux/framework/src/views/about.tsx @@ -19,29 +19,51 @@ import * as React from 'react'; import * as marked from 'marked'; import * as hljs from 'highlight.js'; import { requestRestExt } from '../services/restService'; +import { Button, Typography } from '@material-ui/core'; const defaultRenderer = new marked.Renderer(); defaultRenderer.link = (href, title, text) => ( `${text}` ); interface AboutState { content: string | null; + isCopiedSuccessfully: boolean; + isContentLoadedSucessfully: boolean; } class AboutComponent extends React.Component { + textarea: React.RefObject; constructor(props: any) { super(props); - this.state = { content: null } + this.state = { content: null, isCopiedSuccessfully:false, isContentLoadedSucessfully: false } + this.textarea = React.createRef(); this.loadAboutContent(); } private loadAboutContent(): void { requestRestExt('/about').then((response) => { - this.setState({ content: response.status == 200 ? response.data : `${response.status} ${response.message}` || "Server error" }) + const content = response.status == 200 ? response.data : `${response.status} ${response.message}` || "Server error"; + const loadedSucessfully = response.status == 200 ? true : false; + this.setState({ content: content, isContentLoadedSucessfully: loadedSucessfully }); }).catch((error) => { this.setState({ content: error }) }) } + + copyToClipboard = (e: React.MouseEvent) =>{ + e.preventDefault(); + + if(this.textarea.current!==null){ + this.textarea.current.select(); + document.execCommand('copy'); + if(e.currentTarget != null){ // refocus on button, otherwhise the textarea would be focused + e.currentTarget.focus(); + } + this.setState({isCopiedSuccessfully: true}); + window.setTimeout(()=>{this.setState({isCopiedSuccessfully: false});},2000); + } + } + render() { const markedOptions: marked.MarkedOptions = { @@ -70,14 +92,33 @@ class AboutComponent extends React.Component { return (
+ { this.state.isContentLoadedSucessfully && +
+ + { + this.state.isCopiedSuccessfully && + + copied successfully + + } +
+ } +
+
+