5b4498df86f065383e75e094950cd2f27361c598
[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
6 import { restService } from "../services/restServices";
7 import { YangParser } from "../yang/yangParser";
8 import { Module } from "../models/yang";
9 import { ViewSpecification, ViewElement, isViewElementReference, isViewElementList, isViewElementObjectOrList } from "../models/uiModels";
10 import { AddErrorInfoAction } from "../../../../framework/src/actions/errorActions";
11
12 export class EnableValueSelector extends Action {
13   constructor(public listSpecification: ViewSpecification, public listData: any[], public keyProperty: string, public onValueSelected : (value: any) => void ) {
14     super();
15   }
16 }
17
18 export class SetCollectingSelectionData extends Action {
19   constructor(public busy: boolean) {
20     super();
21   }
22 }
23
24 export class SetSelectedValue extends Action {
25   constructor(public value: any) {
26     super();
27   }
28 }
29
30 export class UpdateDeviceDescription extends Action {
31   constructor( public nodeId: string, public modules: { [name:string]: Module}, public views: ViewSpecification[]) {
32     super();
33   }
34 }
35
36 export class UpdatViewDescription extends Action {
37   constructor(public vPath: string, public view: ViewSpecification, public viewData: any, public displayAsList: boolean = false, public key?: string ) {
38     super();
39   }
40 }
41
42 export const updateNodeIdAsyncActionCreator = (nodeId: string) => async (dispatch: Dispatch, getState: () => IApplicationStoreState ) => {
43   const { configuration: { connectedNetworkElements : { rows }} } = getState();
44   dispatch(new SetCollectingSelectionData(true));
45   const networkElement = rows.find(r => r.nodeId === nodeId) || await restService.getMountedNetworkElementByMountId(nodeId);
46   if (!networkElement) {
47     console.error(new Error(`NetworkElement : [${nodeId}] does not exist.`));
48     return dispatch(new UpdateDeviceDescription("", { }, [ ]));
49   }
50
51   if (!networkElement.nodeDetails || !networkElement.nodeDetails.availableCapabilities) {
52     throw new Error(`NetworkElement : [${nodeId}] has no capabilities.`);
53   }
54   const parser = new YangParser();
55
56   const capParser = /^\(.*\?revision=(\d{4}-\d{2}-\d{2})\)(\S+)$/i;
57   for (let i = 0; i < networkElement.nodeDetails.availableCapabilities.length; ++i){
58     const capRaw = networkElement.nodeDetails.availableCapabilities[i];
59     const capMatch = capRaw && capParser.exec(capRaw);
60     try {
61       capMatch && await parser.addCapability(capMatch[2], capMatch[1]);
62     } catch (err) {
63       console.error(err);
64     }
65   }
66
67   parser.postProcess();
68
69   console.log(parser.modules, parser.views)
70
71   return dispatch(new UpdateDeviceDescription(nodeId, parser.modules, parser.views));
72 }
73
74 export const splitVPath = (vPath: string, vPathParser : RegExp): [string, string?][] => {
75   const pathParts: [string, string?][] = [];
76   let partMatch: RegExpExecArray | null;
77   if (vPath) do {
78     partMatch = vPathParser.exec(vPath);
79     if (partMatch) {
80       pathParts.push([partMatch[1], partMatch[2] || undefined]);
81     }
82   } while (partMatch)
83   return pathParts;
84 }
85
86 const getReferencedDataList = async (refPath: string, dataPath: string, modules: { [name: string]: Module }, views: ViewSpecification[]) => {
87   const pathParts = splitVPath(refPath, /(?:(?:([^\/\:]+):)?([^\/]+))/g);  // 1 = opt: namespace / 2 = property
88   let referencedModule = modules[pathParts[0][0]];
89
90   let dataMember: string;
91   let view: ViewSpecification;
92   let currentNS: string | null = null;
93   let dataUrls = [dataPath];
94   let data: any;
95
96   for (let i = 0; i < pathParts.length; ++i) {
97     const [pathPartNS, pathPart] = pathParts[i];
98     const namespace = pathPartNS != null ? (currentNS = pathPartNS) : currentNS;
99
100     const viewElement = i === 0
101       ? views[0].elements[`${referencedModule.name}:${pathPart}`]
102       : view!.elements[`${pathPart}`] || view!.elements[`${namespace}:${pathPart}`];
103
104     if (!viewElement) throw new Error(`Could not find ${pathPart} in ${refPath}`);
105     if (i < pathParts.length - 1) {
106       if (!isViewElementObjectOrList(viewElement)) {
107         throw Error(`Module: [${referencedModule.name}].[${viewElement.label}]. Viewelement is not list or object.`);
108       }
109       view = views[+viewElement.viewId];
110       const resultingDataUrls : string[] = [];
111       if (isViewElementList(viewElement)) {
112         for (let j = 0; j < dataUrls.length; ++j) {
113           const dataUrl = dataUrls[j];
114           const restResult = (await restService.getConfigData(dataUrl));
115           if (restResult.data == null || restResult.status < 200 || restResult.status > 299) {
116             const message = restResult.data && restResult.data.errors && restResult.data.errors.error && restResult.data.errors.error[0] && restResult.data.errors.error[0]["error-message"] || "";
117             throw new Error(`Server Error. Status: [${restResult.status}]\n${message || restResult.message || ''}`);
118           }
119
120           let dataRaw = restResult.data[dataMember!];
121           dataRaw = dataRaw instanceof Array
122             ? dataRaw[0]
123             : dataRaw;
124
125           data = dataRaw && dataRaw[viewElement.label] || [];
126           const keys: string[] = data.map((entry: { [key: string]: any } )=> entry[viewElement.key!]);
127           resultingDataUrls.push(...keys.map(key => `${dataUrl}/${viewElement.label.replace(/\//ig, "%2F")}/${key.replace(/\//ig, "%2F")}`));
128         }
129         dataMember = viewElement.label;
130       } else {
131         // just a member, not a list
132         const pathSegment = (i === 0
133           ? `/${referencedModule.name}:${viewElement.label.replace(/\//ig, "%2F")}`
134           : `/${viewElement.label.replace(/\//ig, "%2F")}`);
135         resultingDataUrls.push(...dataUrls.map(dataUrl => dataUrl + pathSegment));
136         dataMember = viewElement.label;
137       }
138       dataUrls = resultingDataUrls;
139     } else {
140       data = [];
141       for (let j = 0; j < dataUrls.length; ++j) {
142         const dataUrl = dataUrls[j];
143         const restResult = (await restService.getConfigData(dataUrl));
144         if (restResult.data == null || restResult.status < 200 || restResult.status > 299) {
145           const message = restResult.data && restResult.data.errors && restResult.data.errors.error && restResult.data.errors.error[0] && restResult.data.errors.error[0]["error-message"] || "";
146           throw new Error(`Server Error. Status: [${restResult.status}]\n${message || restResult.message || ''}`);
147         }
148         let dataRaw = restResult.data[dataMember!];
149         dataRaw = dataRaw instanceof Array
150           ? dataRaw[0]
151           : dataRaw;
152         data.push(dataRaw);
153       }
154       // BUG UUID ist nicht in den elements enthalten !!!!!!
155       const key = viewElement && viewElement.label || pathPart;
156       return {
157         view: view!,
158         data: data,
159         key: key,
160       };
161     }
162   }
163   return null;
164 }
165
166 const resolveViewDescription = (defaultNS: string | null, vPath: string, view: ViewSpecification, viewData: any, displayAsList: boolean = false, key?: string): UpdatViewDescription =>{
167
168   // check if-feature | when | and resolve all references.
169   view = { ...view };
170   view.elements = Object.keys(view.elements).reduce<{ [name: string]: ViewElement }>((acc, cur) => {
171     const elm = view.elements[cur];
172     const key = defaultNS && cur.replace(new RegExp(`^${defaultNS}:`, "i"),"") || cur;
173     if (isViewElementReference(elm)) {
174       acc[key] = { ...(elm.ref(vPath) || elm), id: key };
175     } else {
176       acc[key] = { ...elm, id: key };
177     }
178     return acc;
179   }, {});
180   return new UpdatViewDescription(vPath, view, viewData, displayAsList, key);
181 }
182
183 export const updateViewActionAsyncCreator = (vPath: string) => async (dispatch: Dispatch, getState: () => IApplicationStoreState) => {
184   const pathParts = splitVPath(vPath, /(?:([^\/\["]+)(?:\[([^\]]*)\])?)/g); // 1 = property / 2 = optional key
185   const { configuration: { deviceDescription: { nodeId, modules, views } }, framework: { navigationState } } = getState();
186   let dataPath = `/restconf/config/network-topology:network-topology/topology/topology-netconf/node/${nodeId}/yang-ext:mount`;
187   let viewSpecification: ViewSpecification = views[0];
188   let viewElement: ViewElement;
189
190   let dataMember: string;
191   let extractList: boolean = false;
192
193   let currentNS : string | null = null;
194   let defaultNS : string | null = null;
195
196   dispatch(new SetCollectingSelectionData(true));
197   try {
198     for (let ind = 0; ind < pathParts.length; ++ind) {
199       const [property, key] = pathParts[ind];
200       const namespaceInd = property && property.indexOf(":") || -1;
201       const namespace : string | null = namespaceInd > -1 ? (currentNS = property.slice(0, namespaceInd)) : currentNS;
202
203       if (ind === 0) { defaultNS = namespace };
204
205       viewElement = viewSpecification.elements[property] || viewSpecification.elements[`${namespace}:${property}`];
206       if (!viewElement) throw Error("Property [" + property + "] does not exist.");
207
208       if (viewElement.isList && !key) {
209         if (pathParts.length - 1 > ind) {
210           dispatch(new SetCollectingSelectionData(false));
211           throw new Error("No key for list [" + property + "]");
212         } else if (vPath.endsWith("[]") && pathParts.length - 1 === ind) {
213           // empty key is used for new element
214           if (viewElement && "viewId" in viewElement) viewSpecification = views[+viewElement.viewId];
215           const data = Object.keys(viewSpecification.elements).reduce<{ [name: string]: any }>((acc, cur) => {
216             const elm = viewSpecification.elements[cur];
217             if (elm.default) {
218               acc[elm.id] = elm.default || ""
219             }
220             return acc;
221           }, {});
222           return dispatch(resolveViewDescription(defaultNS, vPath, viewSpecification, data, false, isViewElementList(viewElement!) && viewElement.key || undefined));
223         }
224         if (viewElement && isViewElementList(viewElement) && viewSpecification.parentView === "0") {
225           // check if there is a reference as key
226           const listSpecification = views[+viewElement.viewId];
227           const keyElement = viewElement.key && listSpecification.elements[viewElement.key];
228           if (keyElement && isViewElementReference(keyElement)) {
229             const refList = await getReferencedDataList(keyElement.referencePath, dataPath, modules, views);
230             if (!refList) {
231               throw new Error(`Could not find refList for [${keyElement.referencePath}].`);
232             }
233             if (!refList.key) {
234               throw new Error(`Key property not found for [${keyElement.referencePath}].`);
235             }
236             dispatch(new EnableValueSelector(refList.view, refList.data, refList.key, (refKey) => {
237               window.setTimeout(() => dispatch(new PushAction(`${vPath}[${refKey.replace(/\//ig, "%2F")}]`)));
238             }));
239           } else {
240             dispatch(new SetCollectingSelectionData(false));
241             throw new Error("Found a list at root level of a module w/o a refenrece key.");
242           }
243           return;
244         }
245         extractList = true;
246       } else {
247         dataPath += `/${property}${key ? `/${key.replace(/\//ig, "%2F")}` : ""}`;
248         dataMember = namespace === defaultNS
249           ? viewElement.label
250           : `${namespace}:${viewElement.label}`;
251         extractList = false;
252       }
253
254       if (viewElement && "viewId" in viewElement) viewSpecification = views[+viewElement.viewId];
255     }
256
257     let data: any = {};
258     if (viewSpecification && viewSpecification.id !== "0") {
259       const restResult = (await restService.getConfigData(dataPath));
260       if (!restResult.data) {
261         // special case: if this is a list without any response
262         if (extractList && restResult.status === 404) {
263           if (!isViewElementList(viewElement!)) {
264             throw new Error(`vPath: [${vPath}]. ViewElement has no key.`);
265           }
266           return dispatch(resolveViewDescription(defaultNS, vPath, viewSpecification, [], extractList, viewElement.key));
267         }
268         throw new Error(`Did not get response from Server. Status: [${restResult.status}]`);
269       } else if (restResult.status < 200 || restResult.status > 299) {
270         const message = restResult.data.errors && restResult.data.errors.error && restResult.data.errors.error[0] && restResult.data.errors.error[0]["error-message"] || "";
271         throw new Error(`Server Error. Status: [${restResult.status}]\n${message}`);
272       } else {
273         data = restResult.data[dataMember!]; // extract dataMember
274       }
275
276       // extract the first element list[key]
277       data = data instanceof Array
278         ? data[0]
279         : data;
280
281       // extract the list -> key: list
282       data = extractList
283         ? data[viewElement!.label] || [] // if the list is empty, it does not exist
284         : data;
285     }
286
287     return dispatch(resolveViewDescription(defaultNS, vPath, viewSpecification, data, extractList, isViewElementList(viewElement!) && viewElement.key || undefined));
288     // https://beta.just-run.it/#/configuration/Sim12600/core-model:network-element/ltp[LTP-MWPS-TTP-01]
289     // https://beta.just-run.it/#/configuration/Sim12600/core-model:network-element/ltp[LTP-MWPS-TTP-01]/lp
290   } catch (error) {
291     history.back();
292     dispatch(new AddErrorInfoAction({ title: "Problem", message: error.message || `Could not process ${dataPath}` }));
293     dispatch(new SetCollectingSelectionData(false));
294   } finally {
295     return;
296   }
297 }
298
299 export const updateDataActionAsyncCreator = (vPath: string, data: any) => async (dispatch: Dispatch, getState: () => IApplicationStoreState) => {
300   const pathParts = splitVPath(vPath, /(?:([^\/\["]+)(?:\[([^\]]*)\])?)/g); // 1 = property / 2 = optional key
301   const { configuration: { deviceDescription: { nodeId, views } } } = getState();
302   let dataPath = `/restconf/config/network-topology:network-topology/topology/topology-netconf/node/${nodeId}/yang-ext:mount`;
303   let viewSpecification: ViewSpecification = views[0];
304   let viewElement: ViewElement;
305   let dataMember: string;
306   let embedList: boolean = false;
307   let isNew: string | false = false;
308
309   let currentNS: string | null = null;
310   let defaultNS: string | null = null;
311
312   dispatch(new SetCollectingSelectionData(true));
313   try {
314     for (let ind = 0; ind < pathParts.length; ++ind) {
315       let [property, key] = pathParts[ind];
316       const namespaceInd = property && property.indexOf(":") || -1;
317       const namespace: string | null = namespaceInd > -1 ? (currentNS = property.slice(0, namespaceInd)) : currentNS;
318
319       if (ind === 0) { defaultNS = namespace };
320       viewElement = viewSpecification.elements[property];
321       if (!viewElement) throw Error("Property [" + property + "] does not exist.");
322
323       if (isViewElementList(viewElement) && !key) {
324         embedList = true;
325         if (viewElement && viewElement.isList && viewSpecification.parentView === "0") {
326           throw new Error("Found a list at root level of a module w/o a refenrece key.");
327         }
328         if (pathParts.length - 1 > ind) {
329           dispatch(new SetCollectingSelectionData(false));
330           throw new Error("No key for list [" + property + "]");
331         } else if (vPath.endsWith("[]") && pathParts.length - 1 === ind) {
332           // handle new element
333           key = viewElement.key && String(data[viewElement.key]) || "";
334           isNew = key;
335           if (!key) {
336             dispatch(new SetCollectingSelectionData(false));
337             throw new Error("No value for key [" + viewElement.key +"] in list [" + property + "]");
338           }
339         }
340       }
341
342       dataPath += `/${property}${key ? `/${key.replace(/\//ig, "%2F")}` : ""}`;
343       dataMember = viewElement.label;
344       embedList = false;
345
346       if (viewElement && "viewId" in viewElement) {
347         viewSpecification = views[+viewElement.viewId];
348       }
349     }
350
351     // embed the list -> key: list
352     data = embedList
353       ? { [viewElement!.label]: data }
354       : data;
355
356     // embed the first element list[key]
357     data = isNew
358       ? [data]
359       : data;
360
361     // do not extract root member (0)
362     if (viewSpecification && viewSpecification.id !== "0") {
363       const updateResult = await restService.setConfigData(dataPath, { [dataMember!]: data }); // extractDataMember
364       if (updateResult.status < 200 || updateResult.status > 299) {
365         const message = updateResult.data && updateResult.data.errors && updateResult.data.errors.error && updateResult.data.errors.error[0] && updateResult.data.errors.error[0]["error-message"] || "";
366         throw new Error(`Server Error. Status: [${updateResult.status}]\n${message || updateResult.message || ''}`);
367       }
368     }
369
370     return isNew
371       ? dispatch(new ReplaceAction(`/configuration/${nodeId}/${vPath.replace(/\[\]$/i,`[${isNew}]`)}`)) // navigate to new element
372       : dispatch(resolveViewDescription(defaultNS, vPath, viewSpecification, data, embedList, isViewElementList(viewElement!) && viewElement.key || undefined));
373   } catch (error) {
374     history.back();
375     dispatch(new AddErrorInfoAction({ title: "Problem", message: error.message || `Could not change ${dataPath}` }));
376     dispatch(new SetCollectingSelectionData(false));
377   } finally {
378     return;
379   }
380 }