Merge "update odlux for notification change"
[ccsdk/features.git] / sdnr / wt / odlux / apps / configurationApp / src / actions / deviceActions.ts
1 import { Action } from '../../../../framework/src/flux/action';
2 import { Dispatch } from '../../../../framework/src/flux/store';
3 import { IApplicationStoreState } from "../../../../framework/src/store/applicationStore";
4 import { PushAction, ReplaceAction } from "../../../../framework/src/actions/navigationActions";
5 import { AddErrorInfoAction } from "../../../../framework/src/actions/errorActions";
6
7 import { DisplayModeType, DisplaySpecification } from '../handlers/viewDescriptionHandler';
8 import { restService } from "../services/restServices";
9 import { YangParser } from "../yang/yangParser";
10 import { Module } from "../models/yang";
11 import { ViewSpecification, ViewElement, isViewElementReference, isViewElementList, isViewElementObjectOrList, isViewElementRpc, isViewElementChoise, ViewElementChoiseCase, ViewElementString } from "../models/uiModels";
12 import { exception } from 'console';
13
14 export class EnableValueSelector extends Action {
15   constructor(public listSpecification: ViewSpecification, public listData: any[], public keyProperty: string, public onValueSelected : (value: any) => void ) {
16     super();
17   }
18 }
19
20 export class SetCollectingSelectionData extends Action {
21   constructor(public busy: boolean) {
22     super();
23   }
24 }
25
26 export class SetSelectedValue extends Action {
27   constructor(public value: any) {
28     super();
29   }
30 }
31
32 export class UpdateDeviceDescription extends Action {
33   constructor( public nodeId: string, public modules: { [name:string]: Module}, public views: ViewSpecification[]) {
34     super();
35   }
36 }
37
38 export class UpdatViewDescription extends Action {
39   constructor (public vPath: string, public viewData: any, public displaySpecification: DisplaySpecification = { displayMode: DisplayModeType.doNotDisplay }) {
40     super();
41   }
42 }
43
44 export class UpdatOutputData extends Action {
45   constructor (public outputData: any) {
46     super();
47   }
48 }
49
50 export const updateNodeIdAsyncActionCreator = (nodeId: string) => async (dispatch: Dispatch, getState: () => IApplicationStoreState ) => {
51
52   dispatch(new UpdateDeviceDescription("", {}, []));
53   dispatch(new SetCollectingSelectionData(true));
54   
55   const { avaliableCapabilities, unavaliableCapabilities } = await restService.getCapabilitiesByMoutId(nodeId);
56
57   if (!avaliableCapabilities || avaliableCapabilities.length <= 0) {
58     throw new Error(`NetworkElement : [${nodeId}] has no capabilities.`);
59   }
60   
61   const capParser = /^\(.*\?revision=(\d{4}-\d{2}-\d{2})\)(\S+)$/i;
62   
63   const parser = new YangParser(unavaliableCapabilities?.map(cap => {
64       const capMatch = cap && capParser.exec(cap.capability);
65       return { capability:capMatch && capMatch[2] || '', failureReason: cap.failureReason };
66   }) || undefined);
67
68   for (let i = 0; i < avaliableCapabilities.length; ++i){
69     const capRaw = avaliableCapabilities[i];
70     const capMatch = capRaw && capParser.exec(capRaw.capability);
71     try {
72       capMatch && await parser.addCapability(capMatch[2], capMatch[1]);
73     } catch (err) {
74       console.error(`Error in ${capMatch && capMatch[2]} ${capMatch && capMatch[1]}`, err);
75     }
76   }
77
78   parser.postProcess();
79
80   dispatch(new SetCollectingSelectionData(false));
81
82   if (process.env.NODE_ENV === "development" ) {
83       console.log(parser, parser.modules, parser.views);
84   }
85
86   return dispatch(new UpdateDeviceDescription(nodeId, parser.modules, parser.views));
87 }
88
89 export const splitVPath = (vPath: string, vPathParser : RegExp): [string, string?][] => {
90   const pathParts: [string, string?][] = [];
91   let partMatch: RegExpExecArray | null;
92   if (vPath) do {
93     partMatch = vPathParser.exec(vPath);
94     if (partMatch) {
95       pathParts.push([partMatch[1], partMatch[2] || undefined]);
96     }
97   } while (partMatch)
98   return pathParts;
99 }
100
101 const getReferencedDataList = async (refPath: string, dataPath: string, modules: { [name: string]: Module }, views: ViewSpecification[]) => {
102   const pathParts = splitVPath(refPath, /(?:(?:([^\/\:]+):)?([^\/]+))/g);  // 1 = opt: namespace / 2 = property
103   const defaultNS = pathParts[0][0];
104   let referencedModule = modules[defaultNS];
105
106   let dataMember: string;
107   let view: ViewSpecification;
108   let currentNS: string | null = null;
109   let dataUrls = [dataPath];
110   let data: any;
111
112   for (let i = 0; i < pathParts.length; ++i) {
113     const [pathPartNS, pathPart] = pathParts[i];
114     const namespace = pathPartNS != null ? (currentNS = pathPartNS) : currentNS;
115
116     const viewElement = i === 0
117       ? views[0].elements[`${referencedModule.name}:${pathPart}`]
118       : view!.elements[`${pathPart}`] || view!.elements[`${namespace}:${pathPart}`];
119
120     if (!viewElement) throw new Error(`Could not find ${pathPart} in ${refPath}`);
121     if (i < pathParts.length - 1) {
122       if (!isViewElementObjectOrList(viewElement)) {
123         throw Error(`Module: [${referencedModule.name}].[${viewElement.label}]. Viewelement is not list or object.`);
124       }
125       view = views[+viewElement.viewId];
126       const resultingDataUrls : string[] = [];
127       if (isViewElementList(viewElement)) {
128         for (let j = 0; j < dataUrls.length; ++j) {
129           const dataUrl = dataUrls[j];
130           const restResult = (await restService.getConfigData(dataUrl));
131           if (restResult.data == null || restResult.status < 200 || restResult.status > 299) {
132             const message = restResult.data && restResult.data.errors && restResult.data.errors.error && restResult.data.errors.error[0] && restResult.data.errors.error[0]["error-message"] || "";
133             throw new Error(`Server Error. Status: [${restResult.status}]\n${message || restResult.message || ''}`);
134           }
135
136           let dataRaw = restResult.data[`${defaultNS}:${dataMember!}`];
137           if (dataRaw === undefined) {
138               dataRaw = restResult.data[dataMember!];
139           }
140           dataRaw = dataRaw instanceof Array
141             ? dataRaw[0]
142             : dataRaw;
143
144           data = dataRaw && dataRaw[viewElement.label] || [];
145           const keys: string[] = data.map((entry: { [key: string]: any } )=> entry[viewElement.key!]);
146           resultingDataUrls.push(...keys.map(key => `${dataUrl}/${viewElement.label.replace(/\//ig, "%2F")}=${key.replace(/\//ig, "%2F")}`));
147         }
148         dataMember = viewElement.label;
149       } else {
150         // just a member, not a list
151         const pathSegment = (i === 0
152           ? `/${referencedModule.name}:${viewElement.label.replace(/\//ig, "%2F")}`
153           : `/${viewElement.label.replace(/\//ig, "%2F")}`);
154         resultingDataUrls.push(...dataUrls.map(dataUrl => dataUrl + pathSegment));
155         dataMember = viewElement.label;
156       }
157       dataUrls = resultingDataUrls;
158     } else {
159       data = [];
160       for (let j = 0; j < dataUrls.length; ++j) {
161         const dataUrl = dataUrls[j];
162         const restResult = (await restService.getConfigData(dataUrl));
163         if (restResult.data == null || restResult.status < 200 || restResult.status > 299) {
164           const message = restResult.data && restResult.data.errors && restResult.data.errors.error && restResult.data.errors.error[0] && restResult.data.errors.error[0]["error-message"] || "";
165           throw new Error(`Server Error. Status: [${restResult.status}]\n${message || restResult.message || ''}`);
166         }
167         let dataRaw = restResult.data[`${defaultNS}:${dataMember!}`];
168         if (dataRaw === undefined) {
169             dataRaw = restResult.data[dataMember!];
170         }
171         dataRaw = dataRaw instanceof Array
172           ? dataRaw[0]
173           : dataRaw;
174         data.push(dataRaw);
175       }
176       // BUG UUID ist nicht in den elements enthalten !!!!!!
177       const key = viewElement && viewElement.label || pathPart;
178       return {
179         view: view!,
180         data: data,
181         key: key,
182       };
183     }
184   }
185   return null;
186 }
187
188 const resolveViewDescription = (defaultNS: string | null, vPath: string, view: ViewSpecification): ViewSpecification =>{
189
190   // check if-feature | when | and resolve all references.
191   view = { ...view };
192   view.elements = Object.keys(view.elements).reduce<{ [name: string]: ViewElement }>((acc, cur) => {
193     const resolveHistory : ViewElement[] = [];  
194     let elm = view.elements[cur];
195     const key = defaultNS && cur.replace(new RegExp(`^${defaultNS}:`, "i"),"") || cur;
196     while (isViewElementReference(elm)) {
197       const result = (elm.ref(vPath));  
198       if (result) {
199         const [referencedElement, referencedPath] = result;
200         if (resolveHistory.some(hist => hist === referencedElement)) {
201             console.error(`Circle reference found at: ${vPath}`, resolveHistory);
202             break;
203         }
204         elm = referencedElement;
205         vPath = referencedPath;
206         resolveHistory.push(elm);
207       }
208     } 
209     
210     acc[key] = { ...elm, id: key };
211     
212     return acc;
213   }, {});
214   return view;
215 }
216
217 const flatenViewElements = (defaultNS: string | null, parentPath: string, elements: { [name: string]: ViewElement }, views: ViewSpecification[], currentPath: string ): { [name: string]: ViewElement } => {
218   if (!elements) return {};
219   return Object.keys(elements).reduce<{ [name: string]: ViewElement }>((acc, cur) => {
220     const elm = elements[cur];
221
222     // remove the detault namespace, and only the default namespace, sine it seems that this is also not in the restconf response
223     const elmKey = defaultNS && elm.id.replace(new RegExp(`^${defaultNS}:`, "i"), "") || elm.id;
224     const key = parentPath ? `${parentPath}.${elmKey}` : elmKey;
225
226     if (isViewElementRpc(elm)) {
227       console.warn(`Flaten of RFC not supported ! [${currentPath}][${elm.label}]`);
228       return acc;
229     } else if (isViewElementObjectOrList(elm)) {
230       const view = views[+elm.viewId];
231       const inner = view && flatenViewElements(defaultNS, key, view.elements, views, `${currentPath}/${view.name}`);
232       inner && Object.keys(inner).forEach(k => (acc[k] = inner[k]));
233     } else if (isViewElementChoise(elm)) {
234      acc[key] = {
235         ...elm,
236         id: key,
237         cases: Object.keys(elm.cases).reduce<{ [name: string]: ViewElementChoiseCase }>((accCases, curCases) => {
238           const caseElement = elm.cases[curCases];
239           accCases[curCases] = {
240             ...caseElement,
241             // Hint: do not use key it contains elmKey, which shell be omitted for cases.
242             elements: flatenViewElements(defaultNS, /*key*/ parentPath, caseElement.elements, views, `${currentPath}/${elm.label}`)
243           };
244           return accCases;
245         }, {}),
246       };
247     } else {
248       acc[key] = {
249         ...elm,
250         id: key,
251       };
252     }
253     return acc;
254   }, {});
255 };
256
257 export const updateViewActionAsyncCreator = (vPath: string) => async (dispatch: Dispatch, getState: () => IApplicationStoreState) => {
258   const pathParts = splitVPath(vPath, /(?:([^\/\["]+)(?:\[([^\]]*)\])?)/g); // 1 = property / 2 = optional key
259   const { configuration: { deviceDescription: { nodeId, modules, views } }, framework: { navigationState } } = getState();
260   let dataPath = `/rests/data/network-topology:network-topology/topology=topology-netconf/node=${nodeId}/yang-ext:mount`;
261
262   let inputViewSpecification: ViewSpecification | undefined = undefined;
263   let outputViewSpecification: ViewSpecification | undefined = undefined;
264
265   let viewSpecification: ViewSpecification = views[0];
266   let viewElement: ViewElement;
267
268   let dataMember: string;
269   let extractList: boolean = false;
270
271   let currentNS: string | null = null;
272   let defaultNS: string | null = null;
273
274   dispatch(new SetCollectingSelectionData(true));
275   try {
276     for (let ind = 0; ind < pathParts.length; ++ind) {
277       const [property, key] = pathParts[ind];
278       const namespaceInd = property && property.indexOf(":") || -1;
279       const namespace: string | null = namespaceInd > -1 ? (currentNS = property.slice(0, namespaceInd)) : currentNS;
280
281       if (ind === 0) { defaultNS = namespace };
282
283       viewElement = viewSpecification.elements[property] || viewSpecification.elements[`${namespace}:${property}`];
284       if (!viewElement) throw Error("Property [" + property + "] does not exist.");
285
286       if (viewElement.isList && !key) {
287         if (pathParts.length - 1 > ind) {
288           dispatch(new SetCollectingSelectionData(false));
289           throw new Error("No key for list [" + property + "]");
290         } else if (vPath.endsWith("[]") && pathParts.length - 1 === ind) {
291
292           // empty key is used for new element
293           if (viewElement && "viewId" in viewElement) viewSpecification = views[+viewElement.viewId];
294           const data = Object.keys(viewSpecification.elements).reduce<{ [name: string]: any }>((acc, cur) => {
295             const elm = viewSpecification.elements[cur];
296             if (elm.default) {
297               acc[elm.id] = elm.default || ""
298             }
299             return acc;
300           }, {});
301
302           // create display specification
303           const ds: DisplaySpecification = {
304             displayMode: DisplayModeType.displayAsObject,
305             viewSpecification: resolveViewDescription(defaultNS, vPath, viewSpecification),
306             keyProperty: isViewElementList(viewElement!) && viewElement.key || undefined
307           };
308
309           // update display specification
310           return dispatch(new UpdatViewDescription(vPath, data, ds));
311         }
312         if (viewElement && isViewElementList(viewElement) && viewSpecification.parentView === "0") {
313           // check if there is a reference as key
314           const listSpecification = views[+viewElement.viewId];
315           const keyElement = viewElement.key && listSpecification.elements[viewElement.key];
316           if (keyElement && isViewElementReference(keyElement)) {
317             const refList = await getReferencedDataList(keyElement.referencePath, dataPath, modules, views);
318             if (!refList) {
319               throw new Error(`Could not find refList for [${keyElement.referencePath}].`);
320             }
321             if (!refList.key) {
322               throw new Error(`Key property not found for [${keyElement.referencePath}].`);
323             }
324             dispatch(new EnableValueSelector(refList.view, refList.data, refList.key, (refKey) => {
325               window.setTimeout(() => dispatch(new PushAction(`${vPath}[${refKey.replace(/\//ig, "%2F")}]`)));
326             }));
327           } else {
328             // Found a list at root level of a module w/o a refenrece key.
329             dataPath += `?content=config&fields=${encodeURIComponent(viewElement.id)}(${encodeURIComponent(viewElement.key || '')})`; 
330             const restResult = (await restService.getConfigData(dataPath));
331             if (restResult && restResult.status === 200 && restResult.data && restResult.data[viewElement.id] ){
332                 // spoof the not existing view here
333                 const refData = restResult.data[viewElement.id];
334                 const refView : ViewSpecification  = {
335                     id: "-1",
336                     canEdit: false,
337                     config: false,
338                     language: "en-US",
339                     elements: {
340                         [viewElement.key!] : { 
341                            uiType: "string",
342                            config: false,
343                            id: viewElement.key,
344                            label: viewElement.key,
345                            isList: true,
346                         } as ViewElementString
347                     }
348                 };
349                 dispatch(new EnableValueSelector(refView, refData, viewElement.key!, (refKey) => {
350                  window.setTimeout(() => dispatch(new PushAction(`${vPath}[${refKey.replace(/\//ig, "%2F")}]`))); 
351                 }));
352             } else {
353               throw new Error("Found a list at root level of a module and could not determine the keys.");
354             }
355             dispatch(new SetCollectingSelectionData(false));
356           }
357           return;
358         }
359         extractList = true;
360       } else {
361         // normal case 
362         dataPath += `/${property}${key ? `=${key.replace(/\//ig, "%2F")}` : ""}`;
363
364         // in case of the root element the required namespace will be added later,
365         // while extracting the data
366         dataMember = namespace === defaultNS
367           ? viewElement.label
368           : `${namespace}:${viewElement.label}`;
369         extractList = false;
370       }
371
372       if (viewElement && "viewId" in viewElement) {
373         viewSpecification = views[+viewElement.viewId];
374       } else if (viewElement.uiType === "rpc") {
375         viewSpecification = views[+(viewElement.inputViewId || 0)];
376
377         // create new instance & flaten
378         inputViewSpecification = viewElement.inputViewId != null && {
379           ...views[+(viewElement.inputViewId || 0)],
380           elements: flatenViewElements(defaultNS, "", views[+(viewElement.inputViewId || 0)].elements, views, viewElement.label),
381         } || undefined;
382         outputViewSpecification = viewElement.outputViewId != null && {
383           ...views[+(viewElement.outputViewId || 0)],
384           elements: flatenViewElements(defaultNS, "", views[+(viewElement.outputViewId || 0)].elements, views, viewElement.label),
385         } || undefined;
386
387       }
388     }
389
390     let data: any = {};
391     // do not get any data from netconf if there is no view specified || this is the root element [0] || this is an rpc
392     if (viewSpecification && !(viewSpecification.id === "0" || viewElement!.uiType === "rpc")) {
393       const restResult = (await restService.getConfigData(dataPath));
394       if (!restResult.data) {
395         // special case: if this is a list without any response
396         if (extractList && restResult.status === 404) {
397           if (!isViewElementList(viewElement!)) {
398             throw new Error(`vPath: [${vPath}]. ViewElement has no key.`);
399           }
400           // create display specification
401           const ds: DisplaySpecification = {
402             displayMode: extractList ? DisplayModeType.displayAsList : DisplayModeType.displayAsObject,
403             viewSpecification: resolveViewDescription(defaultNS, vPath, viewSpecification),
404             keyProperty: viewElement.key
405           };
406
407           // update display specification
408           return dispatch(new UpdatViewDescription(vPath, [], ds));
409         }
410         throw new Error(`Did not get response from Server. Status: [${restResult.status}]`);
411       } else if (restResult.status < 200 || restResult.status > 299) {
412         const message = restResult.data.errors && restResult.data.errors.error && restResult.data.errors.error[0] && restResult.data.errors.error[0]["error-message"] || "";
413         throw new Error(`Server Error. Status: [${restResult.status}]\n${message}`);
414       } else {
415         // https://tools.ietf.org/html/rfc7951#section-4 the root element may countain a namesapce or not !  
416         data = restResult.data[`${defaultNS}:${dataMember!}`];
417         if (data === undefined) {
418            data = restResult.data[dataMember!]; // extract dataMember w/o namespace
419         }
420       }
421
422       // extract the first element list[key]
423       data = data instanceof Array
424         ? data[0]
425         : data;
426
427       // extract the list -> key: list
428       data = extractList
429         ? data[viewElement!.id] || data[viewElement!.label] || [] // if the list is empty, it does not exist
430         : data;
431
432     } else if (viewElement! && viewElement!.uiType === "rpc") {
433       // set data to defaults
434       data = {};
435       inputViewSpecification && Object.keys(inputViewSpecification.elements).forEach(key => {
436         const elm = inputViewSpecification && inputViewSpecification.elements[key];
437         if (elm && elm.default != undefined) {
438           data[elm.id] = elm.default;
439         }
440       });
441     }
442     
443     // create display specification
444     const ds: DisplaySpecification = viewElement! && viewElement!.uiType === "rpc"
445       ? {
446         dataPath,
447         displayMode: DisplayModeType.displayAsRPC,
448         inputViewSpecification: inputViewSpecification && resolveViewDescription(defaultNS, vPath, inputViewSpecification),
449         outputViewSpecification: outputViewSpecification && resolveViewDescription(defaultNS, vPath, outputViewSpecification),
450       }
451       : {
452         dataPath,
453         displayMode: extractList ? DisplayModeType.displayAsList : DisplayModeType.displayAsObject,
454         viewSpecification: resolveViewDescription(defaultNS, vPath, viewSpecification),
455         keyProperty: isViewElementList(viewElement!) && viewElement.key || undefined,
456         apidocPath:  isViewElementList(viewElement!) &&  `/apidoc/explorer/index.html?urls.primaryName=$$$standard$$$#/mounted%20${nodeId}%20${viewElement!.module || 'MODULE_NOT_DEFINED'}/$$$action$$$_${dataPath.replace(/^\//,'').replace(/[\/=\-\:]/g,'_')}_${viewElement! != null ? `${viewElement.id.replace(/[\/=\-\:]/g,'_')}_` : '' }` || undefined,
457       };
458
459     // update display specification
460     return dispatch(new UpdatViewDescription(vPath, data, ds));
461     // https://beta.just-run.it/#/configuration/Sim12600/core-model:network-element/ltp[LTP-MWPS-TTP-01]
462     // https://beta.just-run.it/#/configuration/Sim12600/core-model:network-element/ltp[LTP-MWPS-TTP-01]/lp
463   } catch (error) {
464     history.back();
465     dispatch(new AddErrorInfoAction({ title: "Problem", message: error.message || `Could not process ${dataPath}` }));
466     dispatch(new SetCollectingSelectionData(false));
467   } finally {
468     return;
469   }
470 };
471
472 export const updateDataActionAsyncCreator = (vPath: string, data: any) => async (dispatch: Dispatch, getState: () => IApplicationStoreState) => {
473   const pathParts = splitVPath(vPath, /(?:([^\/\["]+)(?:\[([^\]]*)\])?)/g); // 1 = property / 2 = optional key
474   const { configuration: { deviceDescription: { nodeId, views } } } = getState();
475   let dataPath = `/rests/data/network-topology:network-topology/topology=topology-netconf/node=${nodeId}/yang-ext:mount`;
476   let viewSpecification: ViewSpecification = views[0];
477   let viewElement: ViewElement;
478   let dataMember: string;
479   let embedList: boolean = false;
480   let isNew: string | false = false;
481
482   let currentNS: string | null = null;
483   let defaultNS: string | null = null;
484
485   dispatch(new SetCollectingSelectionData(true));
486   try {
487     for (let ind = 0; ind < pathParts.length; ++ind) {
488       let [property, key] = pathParts[ind];
489       const namespaceInd = property && property.indexOf(":") || -1;
490       const namespace: string | null = namespaceInd > -1 ? (currentNS = property.slice(0, namespaceInd)) : currentNS;
491
492       if (ind === 0) { defaultNS = namespace };
493       viewElement = viewSpecification.elements[property] || viewSpecification.elements[`${namespace}:${property}`];
494       if (!viewElement) throw Error("Property [" + property + "] does not exist.");
495
496       if (isViewElementList(viewElement) && !key) {
497         embedList = true;
498         if (viewElement && viewElement.isList && viewSpecification.parentView === "0") {
499           throw new Error("Found a list at root level of a module w/o a refenrece key.");
500         }
501         if (pathParts.length - 1 > ind) {
502           dispatch(new SetCollectingSelectionData(false));
503           throw new Error("No key for list [" + property + "]");
504         } else if (vPath.endsWith("[]") && pathParts.length - 1 === ind) {
505           // handle new element
506           key = viewElement.key && String(data[viewElement.key]) || "";
507           isNew = key;
508           if (!key) {
509             dispatch(new SetCollectingSelectionData(false));
510             throw new Error("No value for key [" + viewElement.key + "] in list [" + property + "]");
511           }
512         }
513       }
514
515       dataPath += `/${property}${key ? `=${key.replace(/\//ig, "%2F")}` : ""}`;
516       dataMember = viewElement.label;
517       embedList = false;
518
519       if (viewElement && "viewId" in viewElement) {
520         viewSpecification = views[+viewElement.viewId];
521       }
522     }
523
524     // remove read-only elements
525     const removeReadOnlyElements = (viewSpecification: ViewSpecification, isList: boolean, data: any) => {
526       if (isList) {
527         return data.map((elm : any) => removeReadOnlyElements(viewSpecification, false, elm));
528       } else {
529         return Object.keys(data).reduce<{[key: string]: any}>((acc, cur)=>{
530           const [nsOrName, name] = cur.split(':',1);
531           const element = viewSpecification.elements[cur] || viewSpecification.elements[nsOrName] || viewSpecification.elements[name];
532           if (!element && process.env.NODE_ENV === "development" ) {
533             throw new Error("removeReadOnlyElements: Could not determine elment for data.");
534           }
535           if (element && element.config) {
536             if (element.uiType==="object") {
537               const view = views[+element.viewId];
538               if (!view) {
539                 throw new Error("removeReadOnlyElements: Internal Error could not determine viewId: "+element.viewId);
540               }
541               acc[cur] = removeReadOnlyElements(view, element.isList != null && element.isList, data[cur]);
542             } else {
543               acc[cur] = data[cur];
544             }
545           }
546           return acc;
547         }, {});
548       }
549     };
550     data = removeReadOnlyElements(viewSpecification, embedList, data);
551
552
553     // embed the list -> key: list
554     data = embedList
555       ? { [viewElement!.label]: data }
556       : data;
557
558     // embed the first element list[key]
559     data = isNew
560       ? [data]
561       : data;
562
563     // do not extract root member (0)
564     if (viewSpecification && viewSpecification.id !== "0") {
565       const updateResult = await restService.setConfigData(dataPath, { [`${currentNS}:${dataMember!}`]: data }); // addDataMember using currentNS
566       if (updateResult.status < 200 || updateResult.status > 299) {
567         const message = updateResult.data && updateResult.data.errors && updateResult.data.errors.error && updateResult.data.errors.error[0] && updateResult.data.errors.error[0]["error-message"] || "";
568         throw new Error(`Server Error. Status: [${updateResult.status}]\n${message || updateResult.message || ''}`);
569       }
570     }
571
572     if (isNew) {
573       return dispatch(new ReplaceAction(`/configuration/${nodeId}/${vPath.replace(/\[\]$/i, `[${isNew}]`)}`)) // navigate to new element
574     }
575
576     // create display specification
577     const ds: DisplaySpecification = {
578       displayMode: embedList ? DisplayModeType.displayAsList : DisplayModeType.displayAsObject,
579       viewSpecification: resolveViewDescription(defaultNS, vPath, viewSpecification),
580       keyProperty: isViewElementList(viewElement!) && viewElement.key || undefined,
581     };
582
583     // update display specification
584     return dispatch(new UpdatViewDescription(vPath, data, ds));
585   } catch (error) {
586     history.back();
587     dispatch(new AddErrorInfoAction({ title: "Problem", message: error.message || `Could not change ${dataPath}` }));
588
589   } finally {
590     dispatch(new SetCollectingSelectionData(false));
591     return;
592   }
593 };
594
595 export const removeElementActionAsyncCreator = (vPath: string) => async (dispatch: Dispatch, getState: () => IApplicationStoreState) => {
596   const pathParts = splitVPath(vPath, /(?:([^\/\["]+)(?:\[([^\]]*)\])?)/g); // 1 = property / 2 = optional key
597   const { configuration: { deviceDescription: { nodeId, views } } } = getState();
598   let dataPath = `/rests/data/network-topology:network-topology/topology=topology-netconf/node=${nodeId}/yang-ext:mount`;
599   let viewSpecification: ViewSpecification = views[0];
600   let viewElement: ViewElement;
601
602   let currentNS: string | null = null;
603   let defaultNS: string | null = null;
604
605   dispatch(new SetCollectingSelectionData(true));
606   try {
607     for (let ind = 0; ind < pathParts.length; ++ind) {
608       let [property, key] = pathParts[ind];
609       const namespaceInd = property && property.indexOf(":") || -1;
610       const namespace: string | null = namespaceInd > -1 ? (currentNS = property.slice(0, namespaceInd)) : currentNS;
611
612       if (ind === 0) { defaultNS = namespace };
613       viewElement = viewSpecification.elements[property] || viewSpecification.elements[`${namespace}:${property}`];
614       if (!viewElement) throw Error("Property [" + property + "] does not exist.");
615
616       if (isViewElementList(viewElement) && !key) {
617         if (viewElement && viewElement.isList && viewSpecification.parentView === "0") {
618           throw new Error("Found a list at root level of a module w/o a refenrece key.");
619         }
620         if (pathParts.length - 1 > ind) {
621           dispatch(new SetCollectingSelectionData(false));
622           throw new Error("No key for list [" + property + "]");
623         } else if (vPath.endsWith("[]") && pathParts.length - 1 === ind) {
624           // remove the whole table
625         }
626       }
627
628       dataPath += `/${property}${key ? `=${key.replace(/\//ig, "%2F")}` : ""}`;
629
630       if (viewElement && "viewId" in viewElement) {
631         viewSpecification = views[+viewElement.viewId];
632       } else if (viewElement.uiType === "rpc") {
633         viewSpecification = views[+(viewElement.inputViewId || 0)];
634       }
635     }
636
637     const updateResult = await restService.removeConfigElement(dataPath);
638     if (updateResult.status < 200 || updateResult.status > 299) {
639       const message = updateResult.data && updateResult.data.errors && updateResult.data.errors.error && updateResult.data.errors.error[0] && updateResult.data.errors.error[0]["error-message"] || "";
640       throw new Error(`Server Error. Status: [${updateResult.status}]\n${message || updateResult.message || ''}`);
641     }
642   } catch (error) {
643     dispatch(new AddErrorInfoAction({ title: "Problem", message: error.message || `Could not remove ${dataPath}` }));
644   } finally {
645     dispatch(new SetCollectingSelectionData(false));
646   }
647
648
649 };
650
651 export const executeRpcActionAsyncCreator = (vPath: string, data: any) => async (dispatch: Dispatch, getState: () => IApplicationStoreState) => {
652   const pathParts = splitVPath(vPath, /(?:([^\/\["]+)(?:\[([^\]]*)\])?)/g); // 1 = property / 2 = optional key
653   const { configuration: { deviceDescription: { nodeId, views }, viewDescription: oldViewDescription } } = getState();
654   let dataPath = `/rests/operations/network-topology:network-topology/topology=topology-netconf/node=${nodeId}/yang-ext:mount`;
655   let viewSpecification: ViewSpecification = views[0];
656   let viewElement: ViewElement;
657   let dataMember: string;
658   let embedList: boolean = false;
659   let isNew: string | false = false;
660
661   let currentNS: string | null = null;
662   let defaultNS: string | null = null;
663
664   dispatch(new SetCollectingSelectionData(true));
665   try {
666     for (let ind = 0; ind < pathParts.length; ++ind) {
667       let [property, key] = pathParts[ind];
668       const namespaceInd = property && property.indexOf(":") || -1;
669       const namespace: string | null = namespaceInd > -1 ? (currentNS = property.slice(0, namespaceInd)) : currentNS;
670
671       if (ind === 0) { defaultNS = namespace };
672       viewElement = viewSpecification.elements[property] || viewSpecification.elements[`${namespace}:${property}`];
673       if (!viewElement) throw Error("Property [" + property + "] does not exist.");
674
675       if (isViewElementList(viewElement) && !key) {
676         embedList = true;
677       //   if (viewElement && viewElement.isList && viewSpecification.parentView === "0") {
678       //     throw new Error("Found a list at root level of a module w/o a refenrece key.");
679       //   }
680       //   if (pathParts.length - 1 > ind) {
681       //     dispatch(new SetCollectingSelectionData(false));
682       //     throw new Error("No key for list [" + property + "]");
683       //   } else if (vPath.endsWith("[]") && pathParts.length - 1 === ind) {
684       //     // handle new element
685       //     key = viewElement.key && String(data[viewElement.key]) || "";
686       //     isNew = key;
687       //     if (!key) {
688       //       dispatch(new SetCollectingSelectionData(false));
689       //       throw new Error("No value for key [" + viewElement.key + "] in list [" + property + "]");
690       //     }
691       //   }
692       }
693
694       dataPath += `/${property}${key ? `=${key.replace(/\//ig, "%2F")}` : ""}`;
695       dataMember = viewElement.label;
696       embedList = false;
697
698       if (viewElement && "viewId" in viewElement) {
699         viewSpecification = views[+viewElement.viewId];
700       } else if (viewElement.uiType === "rpc") {
701         viewSpecification = views[+(viewElement.inputViewId || 0)];
702       }
703     }
704
705     // re-inflate formerly flatten rpc data
706     data = data && Object.keys(data).reduce < { [name: string ]: any }>((acc, cur) => {
707       const pathParts = cur.split(".");
708       let pos = 0;
709       const updatePath = (obj: any, key: string) => {
710         obj[key] = (pos >= pathParts.length)
711           ? data[cur]
712           : updatePath(obj[key] || {}, pathParts[pos++]);
713         return obj;
714       }
715       updatePath(acc, pathParts[pos++]);
716       return acc;
717     }, {}) || null;
718
719     // embed the list -> key: list
720     data = embedList
721       ? { [viewElement!.label]: data }
722       : data;
723
724     // embed the first element list[key]
725     data = isNew
726       ? [data]
727       : data;
728
729     // do not post root member (0)
730     if ((viewSpecification && viewSpecification.id !== "0") || (dataMember! && !data)) {
731       const updateResult = await restService.executeRpc(dataPath, { [`${defaultNS}:input`]: data || {} });
732       if (updateResult.status < 200 || updateResult.status > 299) {
733         const message = updateResult.data && updateResult.data.errors && updateResult.data.errors.error && updateResult.data.errors.error[0] && updateResult.data.errors.error[0]["error-message"] || "";
734         throw new Error(`Server Error. Status: [${updateResult.status}]\n${message || updateResult.message || ''}`);
735       }
736       dispatch(new UpdatOutputData(updateResult.data));
737     } else {
738       throw new Error(`There is NO RPC specified.`);
739     }
740
741
742     // // update display specification
743     // return
744   } catch (error) {
745     dispatch(new AddErrorInfoAction({ title: "Problem", message: error.message || `Could not change ${dataPath}` }));
746
747   } finally {
748     dispatch(new SetCollectingSelectionData(false));
749   }
750 };