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';
7 import { DisplayModeType, DisplaySpecification } from '../handlers/viewDescriptionHandler';
9 import { restService } from '../services/restServices';
10 import { YangParser } from '../yang/yangParser';
11 import { Module } from '../models/yang';
15 isViewElementReference,
18 } from '../models/uiModels';
25 getReferencedDataList,
26 resolveViewDescription,
27 } from '../utilities/viewEngineHelper';
29 export class EnableValueSelector extends Action {
30 constructor(public listSpecification: ViewSpecification, public listData: any[], public keyProperty: string, public onValueSelected : (value: any) => void ) {
35 export class SetCollectingSelectionData extends Action {
36 constructor(public busy: boolean) {
41 export class SetSelectedValue extends Action {
42 constructor(public value: any) {
47 export class UpdateDeviceDescription extends Action {
48 constructor( public nodeId: string, public modules: { [name:string]: Module }, public views: ViewSpecification[]) {
53 export class UpdateViewDescription extends Action {
54 constructor(public vPath: string, public viewData: any, public displaySpecification: DisplaySpecification = { displayMode: DisplayModeType.doNotDisplay }) {
59 export class UpdateOutputData extends Action {
60 constructor(public outputData: any) {
65 export const updateNodeIdAsyncActionCreator = (nodeId: string) => async (dispatch: Dispatch, _getState: () => IApplicationStoreState ) => {
67 dispatch(new UpdateDeviceDescription('', {}, []));
68 dispatch(new SetCollectingSelectionData(true));
70 const { availableCapabilities, unavailableCapabilities, importOnlyModules } = await restService.getCapabilitiesByMountId(nodeId);
72 if (!availableCapabilities || availableCapabilities.length <= 0) {
73 dispatch(new SetCollectingSelectionData(false));
74 dispatch(new UpdateDeviceDescription(nodeId, {}, []));
75 dispatch(new UpdateViewDescription('', [], {
76 displayMode: DisplayModeType.displayAsMessage,
77 renderMessage: `NetworkElement : "${nodeId}" has no capabilities.`,
79 throw new Error(`NetworkElement : [${nodeId}] has no capabilities.`);
82 const parser = new YangParser(
84 availableCapabilities.reduce((acc, cur) => {
85 acc[cur.capability] = cur.version;
87 }, {} as { [key: string]: string }),
88 unavailableCapabilities || undefined,
89 importOnlyModules || undefined,
92 for (let i = 0; i < availableCapabilities.length; ++i) {
93 const capRaw = availableCapabilities[i];
95 await parser.addCapability(capRaw.capability, capRaw.version);
97 console.error(`Error in ${capRaw.capability} ${capRaw.version}`, err);
101 parser.postProcess();
103 dispatch(new SetCollectingSelectionData(false));
105 if (process.env.NODE_ENV === 'development' ) {
106 console.log(parser, parser.modules, parser.views);
109 return dispatch(new UpdateDeviceDescription(nodeId, parser.modules, parser.views));
112 export const postProcessDisplaySpecificationActionCreator = (vPath: string, viewData: any, displaySpecification: DisplaySpecification) => async (dispatch: Dispatch, _getState: () => IApplicationStoreState) => {
114 if (displaySpecification.displayMode === DisplayModeType.displayAsObject) {
115 displaySpecification = {
116 ...displaySpecification,
117 viewSpecification: await filterViewElements(vPath, viewData, displaySpecification.viewSpecification),
121 dispatch(new UpdateViewDescription(vPath, viewData, displaySpecification));
124 export const updateViewActionAsyncCreator = (vPath: string) => async (dispatch: Dispatch, getState: () => IApplicationStoreState) => {
125 const pathParts = splitVPath(vPath, /(?:([^\/\["]+)(?:\[([^\]]*)\])?)/g); // 1 = property / 2 = optional key
126 const { configuration: { deviceDescription: { nodeId, modules, views } } } = getState();
127 let dataPath = `/rests/data/network-topology:network-topology/topology=topology-netconf/node=${nodeId}/yang-ext:mount`;
129 let inputViewSpecification: ViewSpecification | undefined = undefined;
130 let outputViewSpecification: ViewSpecification | undefined = undefined;
132 let viewSpecification: ViewSpecification = views[0];
133 let viewElement: ViewElement;
135 let dataMember: string;
136 let extractList: boolean = false;
138 let currentNS: string | null = null;
139 let defaultNS: string | null = null;
141 dispatch(new SetCollectingSelectionData(true));
143 for (let ind = 0; ind < pathParts.length; ++ind) {
144 const [property, key] = pathParts[ind];
145 const namespaceInd = property && property.indexOf(':') || -1;
146 const namespace: string | null = namespaceInd > -1 ? (currentNS = property.slice(0, namespaceInd)) : currentNS;
148 if (ind === 0) { defaultNS = namespace; }
150 viewElement = viewSpecification.elements[property] || viewSpecification.elements[`${namespace}:${property}`];
151 if (!viewElement) throw Error('Property [' + property + '] does not exist.');
153 if (viewElement.isList && !key) {
154 if (pathParts.length - 1 > ind) {
155 dispatch(new SetCollectingSelectionData(false));
156 throw new Error('No key for list [' + property + ']');
157 } else if (vPath.endsWith('[]') && pathParts.length - 1 === ind) {
159 // empty key is used for new element
160 if (viewElement && 'viewId' in viewElement) viewSpecification = views[+viewElement.viewId];
161 const data = Object.keys(viewSpecification.elements).reduce<{ [name: string]: any }>((acc, cur) => {
162 const elm = viewSpecification.elements[cur];
164 acc[elm.id] = elm.default || '';
169 // create display specification
170 const ds: DisplaySpecification = {
171 displayMode: DisplayModeType.displayAsObject,
172 viewSpecification: resolveViewDescription(defaultNS, vPath, viewSpecification),
173 keyProperty: isViewElementList(viewElement!) && viewElement.key || undefined,
176 // update display specification
177 return dispatch(postProcessDisplaySpecificationActionCreator(vPath, data, ds));
179 if (viewElement && isViewElementList(viewElement) && viewSpecification.parentView === '0') {
180 // check if there is a reference as key
181 const listSpecification = views[+viewElement.viewId];
182 const keyElement = viewElement.key && listSpecification.elements[viewElement.key];
183 if (keyElement && isViewElementReference(keyElement)) {
184 const refList = await getReferencedDataList(keyElement.referencePath, dataPath, modules, views);
186 throw new Error(`Could not find refList for [${keyElement.referencePath}].`);
189 throw new Error(`Key property not found for [${keyElement.referencePath}].`);
191 dispatch(new EnableValueSelector(refList.view, refList.data, refList.key, (refKey) => {
192 window.setTimeout(() => dispatch(new PushAction(`${vPath}[${refKey.replace(/\//ig, '%2F')}]`)));
195 // Found a list at root level of a module w/o a reference key.
196 dataPath += `?&fields=${encodeURIComponent(viewElement.id)}(${encodeURIComponent(viewElement.key || '')})`;
197 const restResult = (await restService.getConfigData(dataPath));
198 if (restResult && restResult.status === 200 && restResult.data && restResult.data[viewElement.id] ) {
199 // spoof the not existing view here
200 const refData = restResult.data[viewElement.id];
201 if (!Array.isArray(refData) || !refData.length) {
202 throw new Error('Found a list at root level of a module containing no keys.');
204 if (refData.length > 1) {
205 const refView : ViewSpecification = {
211 [viewElement.key!] : {
215 label: viewElement.key,
217 } as ViewElementString,
220 dispatch(new EnableValueSelector(refView, refData, viewElement.key!, (refKey) => {
221 window.setTimeout(() => dispatch(new PushAction(`${vPath}[${refKey.replace(/\//ig, '%2F')}]`)));
224 window.setTimeout(() => dispatch(new PushAction(`${vPath}[${refData[0]?.id.replace(/\//ig, '%2F')}]`)));
227 throw new Error('Found a list at root level of a module and could not determine the keys.');
229 dispatch(new SetCollectingSelectionData(false));
235 // normal case & replaces unicode %2C if present
236 dataPath += `/${property}${key ? `=${key.replace(/\%2C/g, ',').replace(/\//ig, '%2F')}` : ''}`;
238 // in case of the root element the required namespace will be added later,
239 // while extracting the data
240 dataMember = namespace === defaultNS
242 : `${namespace}:${viewElement.label}`;
246 if (viewElement && 'viewId' in viewElement) {
247 viewSpecification = views[+viewElement.viewId];
248 } else if (viewElement.uiType === 'rpc') {
249 viewSpecification = views[+(viewElement.inputViewId || 0)];
251 // create new instance & flaten
252 inputViewSpecification = viewElement.inputViewId != null && {
253 ...views[+(viewElement.inputViewId || 0)],
254 elements: flattenViewElements(defaultNS, '', views[+(viewElement.inputViewId || 0)].elements, views, viewElement.label),
256 outputViewSpecification = viewElement.outputViewId != null && {
257 ...views[+(viewElement.outputViewId || 0)],
258 elements: flattenViewElements(defaultNS, '', views[+(viewElement.outputViewId || 0)].elements, views, viewElement.label),
265 // do not get any data from netconf if there is no view specified || this is the root element [0] || this is an rpc
266 if (viewSpecification && !(viewSpecification.id === '0' || viewElement!.uiType === 'rpc')) {
267 const restResult = (await restService.getConfigData(dataPath));
268 if (!restResult.data) {
269 // special case: if this is a list without any response
270 if (extractList && restResult.status === 404) {
271 if (!isViewElementList(viewElement!)) {
272 throw new Error(`vPath: [${vPath}]. ViewElement has no key.`);
274 // create display specification
275 const ds: DisplaySpecification = {
276 displayMode: extractList ? DisplayModeType.displayAsList : DisplayModeType.displayAsObject,
277 viewSpecification: resolveViewDescription(defaultNS, vPath, viewSpecification),
278 keyProperty: viewElement.key,
281 // update display specification
282 return dispatch(postProcessDisplaySpecificationActionCreator(vPath, [], ds));
284 throw new Error(`Did not get response from Server. Status: [${restResult.status}]`);
285 } else if (checkResponseCode(restResult)) {
286 const message = restResult.data.errors && restResult.data.errors.error && restResult.data.errors.error[0] && restResult.data.errors.error[0]['error-message'] || '';
287 throw new Error(`Server Error. Status: [${restResult.status}]\n${message}`);
289 // https://tools.ietf.org/html/rfc7951#section-4 the root element may contain a namespace or not !
290 data = restResult.data[`${defaultNS}:${dataMember!}`];
291 if (data === undefined) {
292 data = restResult.data[dataMember!]; // extract dataMember w/o namespace
296 // extract the first element list[key]
297 data = data instanceof Array
301 // extract the list -> key: list
303 ? data[viewElement!.id] || data[viewElement!.label] || [] // if the list is empty, it does not exist
306 } else if (viewElement! && viewElement!.uiType === 'rpc') {
307 // set data to defaults
309 if (inputViewSpecification) {
310 Object.keys(inputViewSpecification.elements).forEach(key => {
311 const elm = inputViewSpecification && inputViewSpecification.elements[key];
312 if (elm && elm.default != undefined) {
313 data[elm.id] = elm.default;
319 // create display specification
320 const ds: DisplaySpecification = viewElement! && viewElement!.uiType === 'rpc'
323 displayMode: DisplayModeType.displayAsRPC,
324 inputViewSpecification: inputViewSpecification && resolveViewDescription(defaultNS, vPath, inputViewSpecification),
325 outputViewSpecification: outputViewSpecification && resolveViewDescription(defaultNS, vPath, outputViewSpecification),
329 displayMode: extractList ? DisplayModeType.displayAsList : DisplayModeType.displayAsObject,
330 viewSpecification: resolveViewDescription(defaultNS, vPath, viewSpecification),
331 keyProperty: isViewElementList(viewElement!) && viewElement.key || undefined,
333 // eslint-disable-next-line max-len
334 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,
337 // update display specification
338 return dispatch(postProcessDisplaySpecificationActionCreator(vPath, data, ds));
339 // https://server.com/#/configuration/Sim12600/core-model:network-element/ltp[LTP-MWPS-TTP-01]
340 // https://server.com/#/configuration/Sim12600/core-model:network-element/ltp[LTP-MWPS-TTP-01]/lp
343 dispatch(new AddErrorInfoAction({ title: 'Problem', message: error.message || `Could not process ${dataPath}` }));
344 dispatch(new SetCollectingSelectionData(false));
350 export const updateDataActionAsyncCreator = (vPath: string, data: any) => async (dispatch: Dispatch, getState: () => IApplicationStoreState) => {
351 const pathParts = splitVPath(vPath, /(?:([^\/\["]+)(?:\[([^\]]*)\])?)/g); // 1 = property / 2 = optional key
352 const { configuration: { deviceDescription: { nodeId, views } } } = getState();
353 let dataPath = `/rests/data/network-topology:network-topology/topology=topology-netconf/node=${nodeId}/yang-ext:mount`;
354 let viewSpecification: ViewSpecification = views[0];
355 let viewElement: ViewElement;
356 let dataMember: string;
357 let embedList: boolean = false;
358 let isNew: string | false = false;
360 let currentNS: string | null = null;
361 let defaultNS: string | null = null;
363 dispatch(new SetCollectingSelectionData(true));
365 for (let ind = 0; ind < pathParts.length; ++ind) {
366 let [property, key] = pathParts[ind];
367 const namespaceInd = property && property.indexOf(':') || -1;
368 const namespace: string | null = namespaceInd > -1 ? (currentNS = property.slice(0, namespaceInd)) : currentNS;
370 if (ind === 0) { defaultNS = namespace; }
371 viewElement = viewSpecification.elements[property] || viewSpecification.elements[`${namespace}:${property}`];
372 if (!viewElement) throw Error('Property [' + property + '] does not exist.');
374 if (isViewElementList(viewElement) && !key) {
376 if (viewElement && viewElement.isList && viewSpecification.parentView === '0') {
377 throw new Error('Found a list at root level of a module w/o a refenrece key.');
379 if (pathParts.length - 1 > ind) {
380 dispatch(new SetCollectingSelectionData(false));
381 throw new Error('No key for list [' + property + ']');
382 } else if (vPath.endsWith('[]') && pathParts.length - 1 === ind) {
383 // handle new element with any number of arguments
384 let keyList = viewElement.key?.split(' ');
385 let dataPathParam = keyList?.map(id => data[id]).join(',');
386 key = viewElement.key && String(dataPathParam) || '';
389 dispatch(new SetCollectingSelectionData(false));
390 throw new Error('No value for key [' + viewElement.key + '] in list [' + property + ']');
395 dataPath += `/${property}${key ? `=${key.replace(/\//ig, '%2F')}` : ''}`;
396 dataMember = viewElement.label;
399 if (viewElement && 'viewId' in viewElement) {
400 viewSpecification = views[+viewElement.viewId];
404 // remove read-only elements
405 const removeReadOnlyElements = (pViewSpecification: ViewSpecification, isList: boolean, pData: any) => {
407 return pData.map((elm : any) => removeReadOnlyElements(pViewSpecification, false, elm));
409 return Object.keys(pData).reduce<{ [key: string]: any }>((acc, cur)=>{
410 const [nsOrName, name] = cur.split(':', 1);
411 const element = pViewSpecification.elements[cur] || pViewSpecification.elements[nsOrName] || pViewSpecification.elements[name];
412 if (!element && process.env.NODE_ENV === 'development' ) {
413 throw new Error('removeReadOnlyElements: Could not determine elment for data.');
415 if (element && element.config) {
416 if (element.uiType === 'object') {
417 const view = views[+element.viewId];
419 throw new Error('removeReadOnlyElements: Internal Error could not determine viewId: ' + element.viewId);
421 acc[cur] = removeReadOnlyElements(view, element.isList != null && element.isList, pData[cur]);
423 acc[cur] = pData[cur];
430 data = removeReadOnlyElements(viewSpecification, embedList, data);
433 // embed the list -> key: list
435 ? { [viewElement!.label]: data }
438 // embed the first element list[key]
443 // do not extract root member (0)
444 if (viewSpecification && viewSpecification.id !== '0') {
445 const updateResult = await restService.setConfigData(dataPath, { [`${currentNS}:${dataMember!}`]: data }); // addDataMember using currentNS
446 if (checkResponseCode(updateResult)) {
447 const message = updateResult.data && updateResult.data.errors && updateResult.data.errors.error && updateResult.data.errors.error[0] && updateResult.data.errors.error[0]['error-message'] || '';
448 throw new Error(`Server Error. Status: [${updateResult.status}]\n${message || updateResult.message || ''}`);
453 return dispatch(new ReplaceAction(`/configuration/${nodeId}/${vPath.replace(/\[\]$/i, `[${isNew}]`)}`)); // navigate to new element
456 // create display specification
457 const ds: DisplaySpecification = {
458 displayMode: embedList ? DisplayModeType.displayAsList : DisplayModeType.displayAsObject,
459 viewSpecification: resolveViewDescription(defaultNS, vPath, viewSpecification),
460 keyProperty: isViewElementList(viewElement!) && viewElement.key || undefined,
463 // update display specification
464 return dispatch(new UpdateViewDescription(vPath, data, ds));
467 dispatch(new AddErrorInfoAction({ title: 'Problem', message: error.message || `Could not change ${dataPath}` }));
470 dispatch(new SetCollectingSelectionData(false));
475 export const removeElementActionAsyncCreator = (vPath: string) => async (dispatch: Dispatch, getState: () => IApplicationStoreState) => {
476 const pathParts = splitVPath(vPath, /(?:([^\/\["]+)(?:\[([^\]]*)\])?)/g); // 1 = property / 2 = optional key
477 const { configuration: { deviceDescription: { nodeId, views } } } = getState();
478 let dataPath = `/rests/data/network-topology:network-topology/topology=topology-netconf/node=${nodeId}/yang-ext:mount`;
479 let viewSpecification: ViewSpecification = views[0];
480 let viewElement: ViewElement;
482 let currentNS: string | null = null;
484 dispatch(new SetCollectingSelectionData(true));
486 for (let ind = 0; ind < pathParts.length; ++ind) {
487 let [property, key] = pathParts[ind];
488 const namespaceInd = property && property.indexOf(':') || -1;
489 const namespace: string | null = namespaceInd > -1 ? (currentNS = property.slice(0, namespaceInd)) : currentNS;
491 viewElement = viewSpecification.elements[property] || viewSpecification.elements[`${namespace}:${property}`];
492 if (!viewElement) throw Error('Property [' + property + '] does not exist.');
494 if (isViewElementList(viewElement) && !key) {
495 if (viewElement && viewElement.isList && viewSpecification.parentView === '0') {
496 throw new Error('Found a list at root level of a module w/o a reference key.');
498 if (pathParts.length - 1 > ind) {
499 dispatch(new SetCollectingSelectionData(false));
500 throw new Error('No key for list [' + property + ']');
501 } else if (vPath.endsWith('[]') && pathParts.length - 1 === ind) {
502 // remove the whole table
506 dataPath += `/${property}${key ? `=${key.replace(/\//ig, '%2F')}` : ''}`;
508 if (viewElement && 'viewId' in viewElement) {
509 viewSpecification = views[+viewElement.viewId];
510 } else if (viewElement.uiType === 'rpc') {
511 viewSpecification = views[+(viewElement.inputViewId || 0)];
515 const updateResult = await restService.removeConfigElement(dataPath);
516 if (checkResponseCode(updateResult)) {
517 const message = updateResult.data && updateResult.data.errors && updateResult.data.errors.error && updateResult.data.errors.error[0] && updateResult.data.errors.error[0]['error-message'] || '';
518 throw new Error(`Server Error. Status: [${updateResult.status}]\n${message || updateResult.message || ''}`);
521 dispatch(new AddErrorInfoAction({ title: 'Problem', message: error.message || `Could not remove ${dataPath}` }));
523 dispatch(new SetCollectingSelectionData(false));
527 export const executeRpcActionAsyncCreator = (vPath: string, data: any) => async (dispatch: Dispatch, getState: () => IApplicationStoreState) => {
528 const pathParts = splitVPath(vPath, /(?:([^\/\["]+)(?:\[([^\]]*)\])?)/g); // 1 = property / 2 = optional key
529 const { configuration: { deviceDescription: { nodeId, views } } } = getState();
530 let dataPath = `/rests/operations/network-topology:network-topology/topology=topology-netconf/node=${nodeId}/yang-ext:mount`;
531 let viewSpecification: ViewSpecification = views[0];
532 let viewElement: ViewElement;
533 let dataMember: string;
534 let embedList: boolean = false;
535 let isNew: string | false = false;
537 let currentNS: string | null = null;
538 let defaultNS: string | null = null;
540 dispatch(new SetCollectingSelectionData(true));
542 for (let ind = 0; ind < pathParts.length; ++ind) {
543 let [property, key] = pathParts[ind];
544 const namespaceInd = property && property.indexOf(':') || -1;
545 const namespace: string | null = namespaceInd > -1 ? (currentNS = property.slice(0, namespaceInd)) : currentNS;
547 if (ind === 0) { defaultNS = namespace; }
548 viewElement = viewSpecification.elements[property] || viewSpecification.elements[`${namespace}:${property}`];
549 if (!viewElement) throw Error('Property [' + property + '] does not exist.');
551 if (isViewElementList(viewElement) && !key) {
553 // if (viewElement && viewElement.isList && viewSpecification.parentView === "0") {
554 // throw new Error("Found a list at root level of a module w/o a reference key.");
556 // if (pathParts.length - 1 > ind) {
557 // dispatch(new SetCollectingSelectionData(false));
558 // throw new Error("No key for list [" + property + "]");
559 // } else if (vPath.endsWith("[]") && pathParts.length - 1 === ind) {
560 // // handle new element
561 // key = viewElement.key && String(data[viewElement.key]) || "";
564 // dispatch(new SetCollectingSelectionData(false));
565 // throw new Error("No value for key [" + viewElement.key + "] in list [" + property + "]");
570 dataPath += `/${property}${key ? `=${key.replace(/\//ig, '%2F')}` : ''}`;
571 dataMember = viewElement.label;
574 if (viewElement && 'viewId' in viewElement) {
575 viewSpecification = views[+viewElement.viewId];
576 } else if (viewElement.uiType === 'rpc') {
577 viewSpecification = views[+(viewElement.inputViewId || 0)];
581 // re-inflate formerly flatten rpc data
582 data = data && Object.keys(data).reduce < { [name: string ]: any }>((acc, cur) => {
583 const innerPathParts = cur.split('.');
585 const updatePath = (obj: any, key: string) => {
586 obj[key] = (pos >= innerPathParts.length)
588 : updatePath(obj[key] || {}, innerPathParts[pos++]);
591 updatePath(acc, innerPathParts[pos++]);
595 // embed the list -> key: list
597 ? { [viewElement!.label]: data }
600 // embed the first element list[key]
605 // do not post root member (0)
606 if ((viewSpecification && viewSpecification.id !== '0') || (dataMember! && !data)) {
607 const updateResult = await restService.executeRpc(dataPath, { [`${defaultNS}:input`]: data || {} });
608 if (checkResponseCode(updateResult)) {
609 const message = updateResult.data && updateResult.data.errors && updateResult.data.errors.error && updateResult.data.errors.error[0] && updateResult.data.errors.error[0]['error-message'] || '';
610 throw new Error(`Server Error. Status: [${updateResult.status}]\n${message || updateResult.message || ''}`);
612 dispatch(new UpdateOutputData(updateResult.data));
614 throw new Error('There is NO RPC specified.');
618 // // update display specification
621 dispatch(new AddErrorInfoAction({ title: 'Problem', message: error.message || `Could not change ${dataPath}` }));
624 dispatch(new SetCollectingSelectionData(false));