From e6d0d67fdbe3fc70c996c8df33bd65d3b151dfad Mon Sep 17 00:00:00 2001 From: herbert Date: Sat, 14 Dec 2019 01:05:47 +0100 Subject: update odlux and featureaggregator v2 update odlux and featureaggregator bundles Issue-ID: SDNC-1008 Signed-off-by: herbert Change-Id: I0018d7bfa3a0e6896c1b210b539a574af9808e22 Signed-off-by: herbert --- .../configurationApp/src/actions/deviceActions.ts | 380 +++++++++++++++++++++ 1 file changed, 380 insertions(+) create mode 100644 sdnr/wt/odlux/apps/configurationApp/src/actions/deviceActions.ts (limited to 'sdnr/wt/odlux/apps/configurationApp/src/actions/deviceActions.ts') diff --git a/sdnr/wt/odlux/apps/configurationApp/src/actions/deviceActions.ts b/sdnr/wt/odlux/apps/configurationApp/src/actions/deviceActions.ts new file mode 100644 index 000000000..fc0665325 --- /dev/null +++ b/sdnr/wt/odlux/apps/configurationApp/src/actions/deviceActions.ts @@ -0,0 +1,380 @@ +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 { 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"; + +export class EnableValueSelector extends Action { + constructor(public listSpecification: ViewSpecification, public listData: any[], public keyProperty: string, public onValueSelected : (value: any) => void ) { + super(); + } +} + +export class SetCollectingSelectionData extends Action { + constructor(public busy: boolean) { + super(); + } +} + +export class SetSelectedValue extends Action { + constructor(public value: any) { + super(); + } +} + +export class UpdateDeviceDescription extends Action { + constructor( public nodeId: string, public modules: { [name:string]: Module}, public views: ViewSpecification[]) { + super(); + } +} + +export class UpdatViewDescription extends Action { + constructor(public vPath: string, public view: ViewSpecification, public viewData: any, public displayAsList: boolean = false, public key?: string ) { + super(); + } +} + +export const updateNodeIdAsyncActionCreator = (nodeId: string) => async (dispatch: Dispatch, getState: () => IApplicationStoreState ) => { + const { configuration: { connectedNetworkElements : { rows }} } = getState(); + dispatch(new SetCollectingSelectionData(true)); + const networkElement = rows.find(r => r.nodeId === nodeId) || await restService.getMountedNetworkElementByMountId(nodeId); + if (!networkElement) { + console.error(new Error(`NetworkElement : [${nodeId}] does not exist.`)); + return dispatch(new UpdateDeviceDescription("", { }, [ ])); + } + + if (!networkElement.nodeDetails || !networkElement.nodeDetails.availableCapabilities) { + throw new Error(`NetworkElement : [${nodeId}] has no capabilities.`); + } + const parser = new YangParser(); + + const capParser = /^\(.*\?revision=(\d{4}-\d{2}-\d{2})\)(\S+)$/i; + for (let i = 0; i < networkElement.nodeDetails.availableCapabilities.length; ++i){ + const capRaw = networkElement.nodeDetails.availableCapabilities[i]; + const capMatch = capRaw && capParser.exec(capRaw); + try { + capMatch && await parser.addCapability(capMatch[2], capMatch[1]); + } catch (err) { + console.error(err); + } + } + + parser.postProcess(); + + console.log(parser.modules, parser.views) + + return dispatch(new UpdateDeviceDescription(nodeId, parser.modules, parser.views)); +} + +export const splitVPath = (vPath: string, vPathParser : RegExp): [string, string?][] => { + const pathParts: [string, string?][] = []; + let partMatch: RegExpExecArray | null; + if (vPath) do { + partMatch = vPathParser.exec(vPath); + if (partMatch) { + pathParts.push([partMatch[1], partMatch[2] || undefined]); + } + } while (partMatch) + return pathParts; +} + +const getReferencedDataList = async (refPath: string, dataPath: string, modules: { [name: string]: Module }, views: ViewSpecification[]) => { + const pathParts = splitVPath(refPath, /(?:(?:([^\/\:]+):)?([^\/]+))/g); // 1 = opt: namespace / 2 = property + let referencedModule = modules[pathParts[0][0]]; + + let dataMember: string; + let view: ViewSpecification; + let currentNS: string | null = null; + let dataUrls = [dataPath]; + let data: any; + + for (let i = 0; i < pathParts.length; ++i) { + const [pathPartNS, pathPart] = pathParts[i]; + const namespace = pathPartNS != null ? (currentNS = pathPartNS) : currentNS; + + const viewElement = i === 0 + ? views[0].elements[`${referencedModule.name}:${pathPart}`] + : view!.elements[`${pathPart}`] || view!.elements[`${namespace}:${pathPart}`]; + + if (!viewElement) throw new Error(`Could not find ${pathPart} in ${refPath}`); + if (i < pathParts.length - 1) { + if (!isViewElementObjectOrList(viewElement)) { + throw Error(`Module: [${referencedModule.name}].[${viewElement.label}]. Viewelement is not list or object.`); + } + view = views[+viewElement.viewId]; + const resultingDataUrls : string[] = []; + if (isViewElementList(viewElement)) { + for (let j = 0; j < dataUrls.length; ++j) { + const dataUrl = dataUrls[j]; + const restResult = (await restService.getConfigData(dataUrl)); + if (restResult.data == null || restResult.status < 200 || restResult.status > 299) { + const message = restResult.data && restResult.data.errors && restResult.data.errors.error && restResult.data.errors.error[0] && restResult.data.errors.error[0]["error-message"] || ""; + throw new Error(`Server Error. Status: [${restResult.status}]\n${message || restResult.message || ''}`); + } + + let dataRaw = restResult.data[dataMember!]; + dataRaw = dataRaw instanceof Array + ? dataRaw[0] + : dataRaw; + + data = dataRaw && dataRaw[viewElement.label] || []; + const keys: string[] = data.map((entry: { [key: string]: any } )=> entry[viewElement.key!]); + resultingDataUrls.push(...keys.map(key => `${dataUrl}/${viewElement.label.replace(/\//ig, "%2F")}/${key.replace(/\//ig, "%2F")}`)); + } + dataMember = viewElement.label; + } else { + // just a member, not a list + const pathSegment = (i === 0 + ? `/${referencedModule.name}:${viewElement.label.replace(/\//ig, "%2F")}` + : `/${viewElement.label.replace(/\//ig, "%2F")}`); + resultingDataUrls.push(...dataUrls.map(dataUrl => dataUrl + pathSegment)); + dataMember = viewElement.label; + } + dataUrls = resultingDataUrls; + } else { + data = []; + for (let j = 0; j < dataUrls.length; ++j) { + const dataUrl = dataUrls[j]; + const restResult = (await restService.getConfigData(dataUrl)); + if (restResult.data == null || restResult.status < 200 || restResult.status > 299) { + const message = restResult.data && restResult.data.errors && restResult.data.errors.error && restResult.data.errors.error[0] && restResult.data.errors.error[0]["error-message"] || ""; + throw new Error(`Server Error. Status: [${restResult.status}]\n${message || restResult.message || ''}`); + } + let dataRaw = restResult.data[dataMember!]; + dataRaw = dataRaw instanceof Array + ? dataRaw[0] + : dataRaw; + data.push(dataRaw); + } + // BUG UUID ist nicht in den elements enthalten !!!!!! + const key = viewElement && viewElement.label || pathPart; + return { + view: view!, + data: data, + key: key, + }; + } + } + return null; +} + +const resolveViewDescription = (defaultNS: string | null, vPath: string, view: ViewSpecification, viewData: any, displayAsList: boolean = false, key?: string): UpdatViewDescription =>{ + + // check if-feature | when | and resolve all references. + view = { ...view }; + view.elements = Object.keys(view.elements).reduce<{ [name: string]: ViewElement }>((acc, cur) => { + const elm = view.elements[cur]; + const key = defaultNS && cur.replace(new RegExp(`^${defaultNS}:`, "i"),"") || cur; + if (isViewElementReference(elm)) { + acc[key] = { ...(elm.ref(vPath) || elm), id: key }; + } else { + acc[key] = { ...elm, id: key }; + } + return acc; + }, {}); + return new UpdatViewDescription(vPath, view, viewData, displayAsList, key); +} + +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 viewSpecification: ViewSpecification = views[0]; + let viewElement: ViewElement; + + let dataMember: string; + let extractList: boolean = false; + + 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; + + if (ind === 0) { defaultNS = namespace }; + + viewElement = viewSpecification.elements[property]; + if (!viewElement) throw Error("Property [" + property + "] does not exist."); + + if (viewElement.isList && !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) { + // 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) => { + const elm = viewSpecification.elements[cur]; + if (elm.default) { + acc[elm.id] = elm.default || "" + } + return acc; + }, {}); + return dispatch(resolveViewDescription(defaultNS, vPath, viewSpecification, data, false, isViewElementList(viewElement!) && viewElement.key || undefined)); + } + if (viewElement && isViewElementList(viewElement) && viewSpecification.parentView === "0") { + // check if there is a reference as key + const listSpecification = views[+viewElement.viewId]; + const keyElement = viewElement.key && listSpecification.elements[viewElement.key]; + if (keyElement && isViewElementReference(keyElement)) { + const refList = await getReferencedDataList(keyElement.referencePath, dataPath, modules, views); + if (!refList) { + throw new Error(`Could not find refList for [${keyElement.referencePath}].`); + } + if (!refList.key) { + throw new Error(`Key property not found for [${keyElement.referencePath}].`); + } + dispatch(new EnableValueSelector(refList.view, refList.data, refList.key, (refKey) => { + window.setTimeout(() => dispatch(new PushAction(`${vPath}[${refKey.replace(/\//ig, "%2F")}]`))); + })); + } else { + dispatch(new SetCollectingSelectionData(false)); + throw new Error("Found a list at root level of a module w/o a refenrece key."); + } + return; + } + extractList = true; + } else { + dataPath += `/${property}${key ? `/${key.replace(/\//ig, "%2F")}` : ""}`; + dataMember = namespace === defaultNS + ? viewElement.label + : `${namespace}:${viewElement.label}`; + extractList = false; + } + + if (viewElement && "viewId" in viewElement) viewSpecification = views[+viewElement.viewId]; + } + + let data: any = {}; + if (viewSpecification && viewSpecification.id !== "0") { + const restResult = (await restService.getConfigData(dataPath)); + if (!restResult.data) { + // special case: if this is a list without any response + if (extractList && restResult.status === 404) { + if (!isViewElementList(viewElement!)) { + throw new Error(`vPath: [${vPath}]. ViewElement has no key.`); + } + return dispatch(resolveViewDescription(defaultNS, vPath, viewSpecification, [], extractList, viewElement.key)); + } + throw new Error(`Did not get response from Server. Status: [${restResult.status}]`); + } else if (restResult.status < 200 || restResult.status > 299) { + const message = restResult.data.errors && restResult.data.errors.error && restResult.data.errors.error[0] && restResult.data.errors.error[0]["error-message"] || ""; + throw new Error(`Server Error. Status: [${restResult.status}]\n${message}`); + } else { + data = restResult.data[dataMember!]; // extract dataMember + } + + // extract the first element list[key] + data = data instanceof Array + ? data[0] + : data; + + // extract the list -> key: list + data = extractList + ? data[viewElement!.label] || [] // if the list is empty, it does not exist + : data; + } + + return dispatch(resolveViewDescription(defaultNS, vPath, viewSpecification, data, extractList, isViewElementList(viewElement!) && viewElement.key || undefined)); + // 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) { + history.back(); + dispatch(new AddErrorInfoAction({ title: "Problem", message: error.message || `Could not process ${dataPath}` })); + dispatch(new SetCollectingSelectionData(false)); + } finally { + return; + } +} + +export const updateDataActionAsyncCreator = (vPath: string, data: any) => 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 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]; + 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]; + } + } + + // embed the list -> key: list + data = embedList + ? { [viewElement!.label]: data } + : data; + + // embed the first element list[key] + data = isNew + ? [data] + : data; + + // do not extract root member (0) + if (viewSpecification && viewSpecification.id !== "0") { + const updateResult = await restService.setConfigData(dataPath, { [dataMember!]: data }); // extractDataMember + 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 || ''}`); + } + } + + 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)); + } catch (error) { + history.back(); + dispatch(new AddErrorInfoAction({ title: "Problem", message: error.message || `Could not change ${dataPath}` })); + dispatch(new SetCollectingSelectionData(false)); + } finally { + return; + } +} \ No newline at end of file -- cgit 1.2.3-korg