1 import { storeService } from '../../../../framework/src/services/storeService';
2 import { WhenAST, WhenTokenType } from '../yang/whenParser';
7 isViewElementReference,
9 isViewElementObjectOrList,
12 ViewElementChoiceCase,
14 } from '../models/uiModels';
16 import { Module } from '../models/yang';
18 import { restService } from '../services/restServices';
20 export type HttpResult = {
22 message?: string | undefined;
28 export const checkResponseCode = (restResult: HttpResult) =>{
29 //403 gets handled by the framework from now on
30 return restResult.status !== 403 && ( restResult.status < 200 || restResult.status > 299);
33 export const resolveVPath = (current: string, vPath: string): string => {
34 if (vPath.startsWith('/')) {
37 const parts = current.split('/');
38 const vPathParts = vPath.split('/');
39 for (const part of vPathParts) {
42 } else if (part === '..') {
48 return parts.join('/');
51 export const splitVPath = (vPath: string, vPathParser : RegExp): [string, (string | undefined | null)][] => {
52 const pathParts: [string, (string | undefined | null)][] = [];
53 let partMatch: RegExpExecArray | null;
55 partMatch = vPathParser.exec(vPath);
57 pathParts.push([partMatch[1], partMatch[2] || (partMatch[0].includes('[]') ? null : undefined)]);
63 const derivedFrom = (vPath: string, when: WhenAST, viewData: any, includeSelf = false) => {
64 if (when.args?.length !== 2) {
65 throw new Error('derived-from or derived-from-or-self requires 2 arguments.');
67 const [arg1, arg2] = when.args;
68 if (arg1.type !== WhenTokenType.IDENTIFIER || arg2.type !== WhenTokenType.STRING) {
69 throw new Error('derived-from or derived-from-or-self requires first argument IDENTIFIER and second argument STRING.');
72 if (!storeService.applicationStore) {
73 throw new Error('storeService.applicationStore is not defined.');
76 const pathParts = splitVPath(arg1.value as string || '', /(?:(?:([^\/\:]+):)?([^\/]+))/g);
77 const referenceValueParts = /(?:(?:([^\/\:]+):)?([^\/]+))/g.exec(arg2.value as string || '');
79 if (!pathParts || !referenceValueParts || pathParts.length === 0 || referenceValueParts.length === 0) {
80 throw new Error('derived-from or derived-from-or-self requires first argument PATH and second argument IDENTITY.');
83 if (pathParts[0][1]?.startsWith('..') || pathParts[0][1]?.startsWith('/')) {
84 throw new Error('derived-from or derived-from-or-self currently only supports relative paths.');
87 const { configuration: { deviceDescription: { modules } } } = storeService.applicationStore.state;
88 const dataValue = pathParts.reduce((acc, [ns, prop]) => {
93 const moduleName = ns && (Object.values(modules).find((m: Module) => m.prefix === ns) || Object.values(modules).find((m: Module) => m.name === ns))?.name;
94 return (moduleName) ? acc[`${moduleName}:${prop}`] || acc[prop] : acc[prop];
99 let dataValueParts = dataValue && /(?:(?:([^\/\:]+):)?([^\/]+))/g.exec(dataValue);
100 if (!dataValueParts || dataValueParts.length < 2) {
101 throw new Error(`derived-from or derived-from-or-self value referenced by first argument [${arg1.value}] not found.`);
103 let [, dataValueNs, dataValueProp] = dataValueParts;
104 let dataValueModule: Module = dataValueNs && (Object.values(modules).find((m: Module) => m.name === dataValueNs));
105 let dataValueIdentity = dataValueModule && dataValueModule.identities && (Object.values(dataValueModule.identities).find((i) => i.label === dataValueProp));
107 if (!dataValueIdentity) {
108 throw new Error(`derived-from or derived-from-or-self identity [${dataValue}] referenced by first argument [${arg1.value}] not found.`);
111 const [, referenceValueNs, referenceValueProp] = referenceValueParts;
112 const referenceValueModule = referenceValueNs && (Object.values(modules).find((m: Module) => m.prefix === referenceValueNs));
113 const referenceValueIdentity = referenceValueModule && referenceValueModule.identities && (Object.values(referenceValueModule.identities).find((i) => i.label === referenceValueProp));
115 if (!referenceValueIdentity) {
116 throw new Error(`derived-from or derived-from-or-self identity [${arg2.value}] referenced by second argument not found.`);
119 let result = includeSelf && (referenceValueIdentity === dataValueIdentity);
120 while (dataValueIdentity && dataValueIdentity.base && !result) {
121 dataValueParts = dataValue && /(?:(?:([^\/\:]+):)?([^\/]+))/g.exec(dataValueIdentity.base);
122 const [, innerDataValueNs, innerDataValueProp] = dataValueParts;
123 dataValueModule = innerDataValueNs && (Object.values(modules).find((m: Module) => m.prefix === innerDataValueNs)) || dataValueModule;
124 dataValueIdentity = dataValueModule && dataValueModule.identities && (Object.values(dataValueModule.identities).find((i) => i.label === innerDataValueProp)) ;
125 result = (referenceValueIdentity === dataValueIdentity);
131 const evaluateWhen = async (vPath: string, when: WhenAST, viewData: any): Promise<boolean> => {
133 case WhenTokenType.FUNCTION:
135 case 'derived-from-or-self':
136 return derivedFrom(vPath, when, viewData, true);
138 return derivedFrom(vPath, when, viewData, false);
140 throw new Error(`Unknown function ${when.name}`);
142 case WhenTokenType.AND:
143 return !when.left || !when.right || (await evaluateWhen(vPath, when.left, viewData) && await evaluateWhen(vPath, when.right, viewData));
144 case WhenTokenType.OR:
145 return !when.left || !when.right || (await evaluateWhen(vPath, when.left, viewData) || await evaluateWhen(vPath, when.right, viewData));
146 case WhenTokenType.NOT:
147 return !when.right || ! await evaluateWhen(vPath, when.right, viewData);
148 case WhenTokenType.EXPRESSION:
149 return !(when.value && typeof when.value !== 'string') || await evaluateWhen(vPath, when.value, viewData);
154 export const getReferencedDataList = async (refPath: string, dataPath: string, modules: { [name: string]: Module }, views: ViewSpecification[]) => {
155 const pathParts = splitVPath(refPath, /(?:(?:([^\/\:]+):)?([^\/]+))/g); // 1 = opt: namespace / 2 = property
156 const defaultNS = pathParts[0][0];
157 let referencedModule = modules[defaultNS];
159 let dataMember: string;
160 let view: ViewSpecification;
161 let currentNS: string | null = null;
162 let dataUrls = [dataPath];
165 for (let i = 0; i < pathParts.length; ++i) {
166 const [pathPartNS, pathPart] = pathParts[i];
167 const namespace = pathPartNS != null ? (currentNS = pathPartNS) : currentNS;
169 const viewElement = i === 0
170 ? views[0].elements[`${referencedModule.name}:${pathPart}`]
171 : view!.elements[`${pathPart}`] || view!.elements[`${namespace}:${pathPart}`];
173 if (!viewElement) throw new Error(`Could not find ${pathPart} in ${refPath}`);
174 if (i < pathParts.length - 1) {
175 if (!isViewElementObjectOrList(viewElement)) {
176 throw Error(`Module: [${referencedModule.name}].[${viewElement.label}]. View element is not list or object.`);
178 view = views[+viewElement.viewId];
179 const resultingDataUrls : string[] = [];
180 if (isViewElementList(viewElement)) {
181 for (let j = 0; j < dataUrls.length; ++j) {
182 const dataUrl = dataUrls[j];
183 const restResult = (await restService.getConfigData(dataUrl));
184 if (restResult.data == null || checkResponseCode(restResult)) {
185 const message = restResult.data && restResult.data.errors && restResult.data.errors.error && restResult.data.errors.error[0] && restResult.data.errors.error[0]['error-message'] || '';
186 throw new Error(`Server Error. Status: [${restResult.status}]\n${message || restResult.message || ''}`);
189 let dataRaw = restResult.data[`${defaultNS}:${dataMember!}`];
190 if (dataRaw === undefined) {
191 dataRaw = restResult.data[dataMember!];
193 dataRaw = dataRaw instanceof Array
197 data = dataRaw && dataRaw[viewElement.label] || [];
198 const keys: string[] = data.map((entry: { [key: string]: any } )=> entry[viewElement.key!]);
199 resultingDataUrls.push(...keys.map(key => `${dataUrl}/${viewElement.label.replace(/\//ig, '%2F')}=${key.replace(/\//ig, '%2F')}`));
201 dataMember = viewElement.label;
203 // just a member, not a list
204 const pathSegment = (i === 0
205 ? `/${referencedModule.name}:${viewElement.label.replace(/\//ig, '%2F')}`
206 : `/${viewElement.label.replace(/\//ig, '%2F')}`);
207 resultingDataUrls.push(...dataUrls.map(dataUrl => dataUrl + pathSegment));
208 dataMember = viewElement.label;
210 dataUrls = resultingDataUrls;
213 for (let j = 0; j < dataUrls.length; ++j) {
214 const dataUrl = dataUrls[j];
215 const restResult = (await restService.getConfigData(dataUrl));
216 if (restResult.data == null || checkResponseCode(restResult)) {
217 const message = restResult.data && restResult.data.errors && restResult.data.errors.error && restResult.data.errors.error[0] && restResult.data.errors.error[0]['error-message'] || '';
218 throw new Error(`Server Error. Status: [${restResult.status}]\n${message || restResult.message || ''}`);
220 let dataRaw = restResult.data[`${defaultNS}:${dataMember!}`];
221 if (dataRaw === undefined) {
222 dataRaw = restResult.data[dataMember!];
224 dataRaw = dataRaw instanceof Array
229 // BUG UUID ist nicht in den elements enthalten !!!!!!
230 const key = viewElement && viewElement.label || pathPart;
241 export const resolveViewDescription = (defaultNS: string | null, vPath: string, view: ViewSpecification): ViewSpecification =>{
243 // resolve all references.
245 view.elements = Object.keys(view.elements).reduce<{ [name: string]: ViewElement }>((acc, cur) => {
246 const resolveHistory : ViewElement[] = [];
247 let elm = view.elements[cur];
248 const key = defaultNS && cur.replace(new RegExp(`^${defaultNS}:`, 'i'), '') || cur;
249 while (isViewElementReference(elm)) {
250 const result = (elm.ref(vPath));
252 const [referencedElement, referencedPath] = result;
253 if (resolveHistory.some(hist => hist === referencedElement)) {
254 console.error(`Circle reference found at: ${vPath}`, resolveHistory);
257 elm = referencedElement;
258 vPath = referencedPath;
259 resolveHistory.push(elm);
263 acc[key] = { ...elm, id: key };
270 export const flattenViewElements = (defaultNS: string | null, parentPath: string, elements: { [name: string]: ViewElement }, views: ViewSpecification[], currentPath: string ): { [name: string]: ViewElement } => {
271 if (!elements) return {};
272 return Object.keys(elements).reduce<{ [name: string]: ViewElement }>((acc, cur) => {
273 const elm = elements[cur];
275 // remove the default namespace, and only the default namespace, sine it seems that this is also not in the restconf response
276 const elmKey = defaultNS && elm.id.replace(new RegExp(`^${defaultNS}:`, 'i'), '') || elm.id;
277 const key = parentPath ? `${parentPath}.${elmKey}` : elmKey;
279 if (isViewElementRpc(elm)) {
280 console.warn(`Flatten of RFC not supported ! [${currentPath}][${elm.label}]`);
282 } else if (isViewElementObjectOrList(elm)) {
283 const view = views[+elm.viewId];
284 const inner = view && flattenViewElements(defaultNS, key, view.elements, views, `${currentPath}/${view.name}`);
286 Object.keys(inner).forEach(k => (acc[k] = inner[k]));
288 } else if (isViewElementChoice(elm)) {
292 cases: Object.keys(elm.cases).reduce<{ [name: string]: ViewElementChoiceCase }>((accCases, curCases) => {
293 const caseElement = elm.cases[curCases];
294 accCases[curCases] = {
296 // Hint: do not use key it contains elmKey, which shell be omitted for cases.
297 elements: flattenViewElements(defaultNS, /*key*/ parentPath, caseElement.elements, views, `${currentPath}/${elm.label}`),
312 export const filterViewElements = async (vPath: string, viewData: any, viewSpecification: ViewSpecification) => {
313 // filter elements of viewSpecification by evaluating when property
314 return Object.keys(viewSpecification.elements).reduce(async (accPromise, cur) => {
315 const acc = await accPromise;
316 const elm = viewSpecification.elements[cur];
317 if (!elm.when || await evaluateWhen(vPath || '', elm.when, viewData).catch((ex) => {
318 console.warn(`Error evaluating when clause at: ${viewSpecification.name} for element: ${cur}`, ex);
321 acc.elements[cur] = elm;
324 }, Promise.resolve({ ...viewSpecification, elements: {} as { [key: string]: ViewElement } }));
327 export const createViewData = (namespace: string | null, viewSpecification: ViewSpecification, views: ViewSpecification[]) => Object.keys(viewSpecification.elements).reduce<{ [name: string]: any }>((acc, cur) => {
328 const elm = viewSpecification.elements[cur];
329 let currentNamespace = namespace;
332 acc[key] = elm.default || '';
333 } else if (elm.uiType === 'boolean') {
335 } else if (elm.uiType === 'number') {
337 } else if (elm.uiType === 'string') {
339 } else if (isViewElementObject(elm)) {
340 const view = views[+elm.viewId];
343 currentNamespace = view.ns;
345 acc[key] = createViewData(currentNamespace, view, views);
347 } else if (isViewElementList(elm)) {