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