diff options
author | Aijana Schumann <aijana.schumann@highstreet-technologies.com> | 2021-08-04 11:59:18 +0200 |
---|---|---|
committer | Aijana Schumann <aijana.schumann@highstreet-technologies.com> | 2021-08-04 16:06:05 +0200 |
commit | 437f67407aece6f7aed8e989638b0d64075f0c0a (patch) | |
tree | 53e9e336cd8544edf8a06c889e33f5b9c98fe083 /sdnr/wt/odlux/apps/configurationApp/src | |
parent | 1c4995eb199437e9c86336efff9972f2049e1532 (diff) |
Update ODLUX
Add various updates and bugfixes to NetworkMap, Configuration, LinkCalculation and ConnectApp
Issue-ID: CCSDK-3414
Signed-off-by: Aijana Schumann <aijana.schumann@highstreet-technologies.com>
Change-Id: I6ea5c3a9d6ccbe9c450da43220654a53fd2f262b
Signed-off-by: Aijana Schumann <aijana.schumann@highstreet-technologies.com>
Diffstat (limited to 'sdnr/wt/odlux/apps/configurationApp/src')
7 files changed, 136 insertions, 86 deletions
diff --git a/sdnr/wt/odlux/apps/configurationApp/src/actions/deviceActions.ts b/sdnr/wt/odlux/apps/configurationApp/src/actions/deviceActions.ts index f80fbfc4d..b5dd310bc 100644 --- a/sdnr/wt/odlux/apps/configurationApp/src/actions/deviceActions.ts +++ b/sdnr/wt/odlux/apps/configurationApp/src/actions/deviceActions.ts @@ -52,26 +52,20 @@ export const updateNodeIdAsyncActionCreator = (nodeId: string) => async (dispatc dispatch(new UpdateDeviceDescription("", {}, [])); dispatch(new SetCollectingSelectionData(true)); - const { avaliableCapabilities, unavaliableCapabilities } = await restService.getCapabilitiesByMoutId(nodeId); - - if (!avaliableCapabilities || avaliableCapabilities.length <= 0) { + const { availableCapabilities, unavailableCapabilities, importOnlyModules } = await restService.getCapabilitiesByMountId(nodeId); + + if (!availableCapabilities || availableCapabilities.length <= 0) { throw new Error(`NetworkElement : [${nodeId}] has no capabilities.`); } - - const capParser = /^\(.*\?revision=(\d{4}-\d{2}-\d{2})\)(\S+)$/i; - - const parser = new YangParser(unavaliableCapabilities?.map(cap => { - const capMatch = cap && capParser.exec(cap.capability); - return { capability:capMatch && capMatch[2] || '', failureReason: cap.failureReason }; - }) || undefined); - - for (let i = 0; i < avaliableCapabilities.length; ++i){ - const capRaw = avaliableCapabilities[i]; - const capMatch = capRaw && capParser.exec(capRaw.capability); + + const parser = new YangParser(unavailableCapabilities || undefined, importOnlyModules || undefined); + + for (let i = 0; i < availableCapabilities.length; ++i){ + const capRaw = availableCapabilities[i]; try { - capMatch && await parser.addCapability(capMatch[2], capMatch[1]); + await parser.addCapability(capRaw.capability, capRaw.version); } catch (err) { - console.error(`Error in ${capMatch && capMatch[2]} ${capMatch && capMatch[1]}`, err); + console.error(`Error in ${capRaw.capability} ${capRaw.version}`, err); } } diff --git a/sdnr/wt/odlux/apps/configurationApp/src/index.html b/sdnr/wt/odlux/apps/configurationApp/src/index.html index 78fff78c5..4a0496bff 100644 --- a/sdnr/wt/odlux/apps/configurationApp/src/index.html +++ b/sdnr/wt/odlux/apps/configurationApp/src/index.html @@ -15,11 +15,13 @@ <script type="text/javascript" src="./config.js"></script> <script> // run the application - require(["app", "connectApp", "maintenanceApp", "configurationApp"], function (app, connectApp, maintenanceApp, configurationApp) { + require(["app", "connectApp", "maintenanceApp", "configurationApp", "faultApp"], function (app, connectApp, maintenanceApp, configurationApp, faultApp) { connectApp.register(); configurationApp.register(); maintenanceApp.register(); - app("./app.tsx").configureApplication({ authentication:"oauth", enablePolicy: true,}); + faultApp.register(); + // app("./app.tsx").configureApplication({ authentication:"oauth", enablePolicy: true,}); + app("./app.tsx").configureApplication({ authentication:"basic", enablePolicy: false,}); app("./app.tsx").runApplication(); }); </script> diff --git a/sdnr/wt/odlux/apps/configurationApp/src/models/yang.ts b/sdnr/wt/odlux/apps/configurationApp/src/models/yang.ts index e4ab6f59f..10f538c2e 100644 --- a/sdnr/wt/odlux/apps/configurationApp/src/models/yang.ts +++ b/sdnr/wt/odlux/apps/configurationApp/src/models/yang.ts @@ -17,12 +17,12 @@ */ import { ViewElement, ViewSpecification } from "./uiModels"; -import { StepLabel } from "@material-ui/core"; export enum ModuleState { stable, instable, - unabaliabe, + importOnly, + unavailable, } export type Token = { diff --git a/sdnr/wt/odlux/apps/configurationApp/src/pluginConfiguration.tsx b/sdnr/wt/odlux/apps/configurationApp/src/pluginConfiguration.tsx index 3bc0e3968..3b9baa657 100644 --- a/sdnr/wt/odlux/apps/configurationApp/src/pluginConfiguration.tsx +++ b/sdnr/wt/odlux/apps/configurationApp/src/pluginConfiguration.tsx @@ -88,7 +88,7 @@ const ConfigurationApplicationRouteAdapter = connect(undefined, mapDisp)((props: // result += `${indention} [${view.canEdit ? 'rw' : 'ro'}] ${view.ns}:${view.name} ${ds.displayMode === DisplayModeType.displayAsList ? '[LIST]' : ''}\r\n`; result += Object.keys(view.elements).reduce((acc, cur) => { const elm = view.elements[cur]; - acc += `${indention} [${elm.config ? 'rw' : 'ro'}:${elm.id}] (${elm.module}:${elm.label}) {${elm.uiType}} ${elm.uiType === "object" && elm.isList ? `as LIST with KEY [${elm.key}]` : ""}\r\n`; + acc += `${indention} [${elm.uiType === "rpc" ? "x" : elm.config ? 'rw' : 'ro'}:${elm.id}] (${elm.module}:${elm.label}) {${elm.uiType}} ${elm.uiType === "object" && elm.isList ? `as LIST with KEY [${elm.key}]` : ""}\r\n`; // acc += `${indention} +${elm.mandatory ? "mandetory" : "none"} - ${elm.path} \r\n`; switch (elm.uiType) { diff --git a/sdnr/wt/odlux/apps/configurationApp/src/services/restServices.ts b/sdnr/wt/odlux/apps/configurationApp/src/services/restServices.ts index bdef64cf2..02060ef12 100644 --- a/sdnr/wt/odlux/apps/configurationApp/src/services/restServices.ts +++ b/sdnr/wt/odlux/apps/configurationApp/src/services/restServices.ts @@ -21,52 +21,101 @@ import { convertPropertyNames, replaceHyphen } from "../../../../framework/src/u import { NetworkElementConnection } from "../models/networkElementConnection"; +type ImportOnlyResponse = { + "ietf-yang-library:yang-library": { + "module-set": { + "import-only-module": { + "name": string, + "revision": string, + }[], + }[], + }, +} + + type CapabilityResponse = { - "network-topology:node": { - "node-id": string, - "netconf-node-topology:available-capabilities": { - "available-capability": { - "capability-origin": string, - "capability": string, - }[] - }, - "netconf-node-topology:unavailable-capabilities": { - "unavailable-capability": { - "capability": string, - "failure-reason": string, - }[] - } - }[] + "network-topology:node": { + "node-id": string, + "netconf-node-topology:available-capabilities": { + "available-capability": { + "capability-origin": string, + "capability": string, + }[] + }, + "netconf-node-topology:unavailable-capabilities": { + "unavailable-capability": { + "capability": string, + "failure-reason": string, + }[] + } + }[] } -type CapabilityAnswer = { - avaliableCapabilities: { - capabilityOrigin: string, - capability: string - }[] | null , - unavaliableCapabilities: { - failureReason: string, - capability: string - }[] | null , +type CapabilityAnswer = { + availableCapabilities: { + capabilityOrigin: string, + capability: string, + version: string, + }[] | null, + unavailableCapabilities: { + failureReason: string, + capability: string, + version: string, + }[] | null, + importOnlyModules: { + name: string, + revision: string, + }[] | null } +const capParser = /^\(.*\?revision=(\d{4}-\d{2}-\d{2})\)(\S+)$/i; + class RestService { public getNetworkElementUri = (nodeId: string) => '/rests/data/network-topology:network-topology/topology=topology-netconf/node=' + nodeId; - public async getCapabilitiesByMoutId(nodeId: string): Promise<CapabilityAnswer> { + public async getImportOnlyModules(nodeId: string): Promise<{ name: string, revision: string }[]> { + const path = `${this.getNetworkElementUri(nodeId)}/yang-ext:mount/ietf-yang-library:yang-library?content=nonconfig&fields=module-set(import-only-module(name;revision))`; + const importOnlyResult = await requestRest<ImportOnlyResponse>(path, { method: "GET" }); + const importOnlyModules = importOnlyResult + ? importOnlyResult["ietf-yang-library:yang-library"]["module-set"][0]["import-only-module"] + : []; + return importOnlyModules; + } + + public async getCapabilitiesByMountId(nodeId: string): Promise<CapabilityAnswer> { const path = this.getNetworkElementUri(nodeId); const capabilitiesResult = await requestRest<CapabilityResponse>(path, { method: "GET" }); - const avaliableCapabilities = capabilitiesResult && capabilitiesResult["network-topology:node"] && capabilitiesResult["network-topology:node"].length > 0 && - capabilitiesResult["network-topology:node"][0]["netconf-node-topology:available-capabilities"] && - capabilitiesResult["network-topology:node"][0]["netconf-node-topology:available-capabilities"]["available-capability"] && - capabilitiesResult["network-topology:node"][0]["netconf-node-topology:available-capabilities"]["available-capability"].map<any>(obj => convertPropertyNames(obj, replaceHyphen)) || []; - - const unavaliableCapabilities = capabilitiesResult && capabilitiesResult["network-topology:node"] && capabilitiesResult["network-topology:node"].length > 0 && - capabilitiesResult["network-topology:node"][0]["netconf-node-topology:unavailable-capabilities"] && - capabilitiesResult["network-topology:node"][0]["netconf-node-topology:unavailable-capabilities"]["unavailable-capability"] && - capabilitiesResult["network-topology:node"][0]["netconf-node-topology:unavailable-capabilities"]["unavailable-capability"].map<any>(obj => convertPropertyNames(obj, replaceHyphen)) || [] - - return { avaliableCapabilities, unavaliableCapabilities }; + const availableCapabilities = capabilitiesResult && capabilitiesResult["network-topology:node"] && capabilitiesResult["network-topology:node"].length > 0 && + (capabilitiesResult["network-topology:node"][0]["netconf-node-topology:available-capabilities"] && + capabilitiesResult["network-topology:node"][0]["netconf-node-topology:available-capabilities"]["available-capability"] && + capabilitiesResult["network-topology:node"][0]["netconf-node-topology:available-capabilities"]["available-capability"].map<any>(obj => convertPropertyNames(obj, replaceHyphen)) || []) + .map(cap => { + const capMatch = cap && capParser.exec(cap.capability); + return capMatch ? { + capabilityOrigin: cap.capabilityOrigin, + capability: capMatch && capMatch[2] || '', + version: capMatch && capMatch[1] || '', + } : null ; + }).filter((cap) => cap != null) || [] as any; + + const unavailableCapabilities = capabilitiesResult && capabilitiesResult["network-topology:node"] && capabilitiesResult["network-topology:node"].length > 0 && + (capabilitiesResult["network-topology:node"][0]["netconf-node-topology:unavailable-capabilities"] && + capabilitiesResult["network-topology:node"][0]["netconf-node-topology:unavailable-capabilities"]["unavailable-capability"] && + capabilitiesResult["network-topology:node"][0]["netconf-node-topology:unavailable-capabilities"]["unavailable-capability"].map<any>(obj => convertPropertyNames(obj, replaceHyphen)) || []) + .map(cap => { + const capMatch = cap && capParser.exec(cap.capability); + return capMatch ? { + failureReason: cap.failureReason, + capability: capMatch && capMatch[2] || '', + version: capMatch && capMatch[1] || '', + } : null ; + }).filter((cap) => cap != null) || [] as any; + + const importOnlyModules = availableCapabilities && availableCapabilities.findIndex((ac: {capability: string }) => ac.capability && ac.capability.toLowerCase() === "ietf-yang-library") > -1 + ? await this.getImportOnlyModules(nodeId) + : null; + + return { availableCapabilities, unavailableCapabilities, importOnlyModules }; } public async getMountedNetworkElementByMountId(nodeId: string): Promise<NetworkElementConnection | null> { @@ -80,7 +129,7 @@ class RestService { return networkElementResult && networkElementResult["data-provider:output"] && networkElementResult["data-provider:output"].data && networkElementResult["data-provider:output"].data.map(obj => convertPropertyNames(obj, replaceHyphen))[0] || null; } - + /** Reads the config data by restconf path. * @param path The restconf path to be used for read. * @returns The data. diff --git a/sdnr/wt/odlux/apps/configurationApp/src/views/configurationApplication.tsx b/sdnr/wt/odlux/apps/configurationApp/src/views/configurationApplication.tsx index db426e814..8d0e19246 100644 --- a/sdnr/wt/odlux/apps/configurationApp/src/views/configurationApplication.tsx +++ b/sdnr/wt/odlux/apps/configurationApp/src/views/configurationApplication.tsx @@ -578,9 +578,11 @@ class ConfigurationApplicationComponent extends React.Component<ConfigurationApp private renderUIViewList(listSpecification: ViewSpecification, dataPath: string, listKeyProperty: string, apiDocPath: string, listData: { [key: string]: any }[]) { const listElements = listSpecification.elements; - const apiDocPathCreate = apiDocPath ? `${location.origin}${apiDocPath + const apiDocPathCreate = apiDocPath ? `${location.origin}${apiDocPath .replace("$$$standard$$$", "topology-netconfnode%20resources%20-%20RestConf%20RFC%208040") - .replace("$$$action$$$", "put")}_${listKeyProperty.replace(/[\/=\-\:]/g, '_')}_` : undefined; + .replace("$$$action$$$", "put")}${listKeyProperty ? `_${listKeyProperty.replace(/[\/=\-\:]/g, '_')}_` : '' }` : undefined; + + const config = listSpecification.config && listKeyProperty; // We can not configure a list with no key. const navigate = (path: string) => { this.props.history.push(`${this.props.match.url}${path}`); @@ -593,7 +595,7 @@ class ConfigurationApplicationComponent extends React.Component<ConfigurationApp onClick: () => { navigate("[]"); // empty key means new element }, - disabled: !listSpecification.config, + disabled: !config, }; const addWithApiDocElementAction = { @@ -603,7 +605,7 @@ class ConfigurationApplicationComponent extends React.Component<ConfigurationApp onClick: () => { window.open(apiDocPathCreate, '_blank'); }, - disabled: !listSpecification.config, + disabled: !config, }; const { classes, removeElement } = this.props; @@ -650,13 +652,13 @@ class ConfigurationApplicationComponent extends React.Component<ConfigurationApp }, []).concat([{ property: "Actions", disableFilter: true, disableSorting: true, type: ColumnType.custom, customControl: (({ rowData }) => { return ( - <DeleteIconWithConfirmation disabled={!listSpecification.config} rowData={rowData} onReload={() => this.props.vPath && this.props.reloadView(this.props.vPath)} /> + <DeleteIconWithConfirmation disabled={!config} rowData={rowData} onReload={() => this.props.vPath && this.props.reloadView(this.props.vPath)} /> ); }) }]) } onHandleClick={(ev, row) => { ev.preventDefault(); - navigate(`[${encodeURIComponent(row[listKeyProperty])}]`); + listKeyProperty && navigate(`[${encodeURIComponent(row[listKeyProperty])}]`); // Do not navigate without key. }} ></SelectElementTable> ); } diff --git a/sdnr/wt/odlux/apps/configurationApp/src/yang/yangParser.ts b/sdnr/wt/odlux/apps/configurationApp/src/yang/yangParser.ts index 2d38976d5..c80bd4c84 100644 --- a/sdnr/wt/odlux/apps/configurationApp/src/yang/yangParser.ts +++ b/sdnr/wt/odlux/apps/configurationApp/src/yang/yangParser.ts @@ -286,7 +286,7 @@ export class YangParser { public static ResolveStack = Symbol("ResolveStack"); - constructor(private _unavailableCapabilities: { failureReason: string; capability: string; }[] = []) { + constructor(private _unavailableCapabilities: { failureReason: string; capability: string; }[] = [], private _importOnlyModules: { name: string; revision: string; }[] = []) { } @@ -298,16 +298,16 @@ export class YangParser { return this._views; } - public async addCapability(capability: string, version?: string) { + public async addCapability(capability: string, version?: string, parentImportOnlyModule?: boolean) { // do not add twice if (this._modules[capability]) { - // console.warn(`Skipped capability: ${capability} since allready contained.` ); + // console.warn(`Skipped capability: ${capability} since already contained.` ); return; } - // // do not add unavaliabe capabilities + // // do not add unavailable capabilities // if (this._unavailableCapabilities.some(c => c.capability === capability)) { - // // console.warn(`Skipped capability: ${capability} since it is marked as unavaliable.` ); + // // console.warn(`Skipped capability: ${capability} since it is marked as unavailable.` ); // return; // } @@ -325,7 +325,8 @@ export class YangParser { throw new Error(`Root element capability ${rootStatement.arg} does not requested ${capability}.`); } - const isUnavaliabe = this._unavailableCapabilities.some(c => c.capability === capability); + const isUnavailable = this._unavailableCapabilities.some(c => c.capability === capability); + const isImportOnly = parentImportOnlyModule === true || this._importOnlyModules.some(c => c.name === capability); const module = this._modules[capability] = { name: rootStatement.arg, @@ -338,9 +339,11 @@ export class YangParser { typedefs: {}, views: {}, elements: {}, - state: isUnavaliabe - ? ModuleState.unabaliabe - : ModuleState.stable, + state: isUnavailable + ? ModuleState.unavailable + : isImportOnly + ? ModuleState.importOnly + : ModuleState.stable, }; await this.handleModule(module, rootStatement, capability); @@ -406,7 +409,7 @@ export class YangParser { // import all required files and set module state if (imports) for (let ind = 0; ind < imports.length; ++ind) { const moduleName = imports[ind].arg!; - await this.addCapability(moduleName); + await this.addCapability(moduleName, undefined, module.state === ModuleState.importOnly); const importedModule = this._modules[imports[ind].arg!]; if (importedModule && importedModule.state > ModuleState.stable) { module.state = Math.max(module.state, ModuleState.instable); @@ -415,7 +418,7 @@ export class YangParser { this.extractTypeDefinitions(rootStatement, module, ""); - this.extractIdentites(rootStatement, 0, module, ""); + this.extractIdentities(rootStatement, 0, module, ""); const groupings = this.extractGroupings(rootStatement, 0, module, ""); this._views.push(...groupings); @@ -440,8 +443,8 @@ export class YangParser { module.views[key] = this._views[viewIdIndex]; } - // add only the UI View if the module is avliable - if (module.state !== ModuleState.unabaliabe) this._views[0].elements[key] = module.elements[key]; + // add only the UI View if the module is available + if (module.state === ModuleState.stable || module.state === ModuleState.instable) this._views[0].elements[key] = module.elements[key]; }); }); return module; @@ -449,7 +452,7 @@ export class YangParser { public postProcess() { - // execute all post processes like resolving in propper order + // execute all post processes like resolving in proper order this._unionsToResolve.forEach(cb => { try { cb(); } catch (error) { console.warn(error.message); @@ -463,7 +466,7 @@ export class YangParser { } }); - // process all augmentations / sort by namespace changes to ensure propper order + // process all augmentations / sort by namespace changes to ensure proper order Object.keys(this.modules).forEach(modKey => { const module = this.modules[modKey]; const augmentKeysWithCounter = Object.keys(module.augments).map((key) => { @@ -517,7 +520,7 @@ export class YangParser { return result; } - const baseIdentites: Identity[] = []; + const baseIdentities: Identity[] = []; Object.keys(this.modules).forEach(modKey => { const module = this.modules[modKey]; Object.keys(module.identities).forEach(idKey => { @@ -526,11 +529,11 @@ export class YangParser { const base = this.resolveIdentity(identity.base, module); base.children?.push(identity); } else { - baseIdentites.push(identity); + baseIdentities.push(identity); } }); }); - baseIdentites.forEach(identity => { + baseIdentities.forEach(identity => { identity.values = identity.children && traverseIdentity(identity.children) || []; }); @@ -598,7 +601,7 @@ export class YangParser { }); } - /** Handles Goupings like named Container */ + /** Handles groupings like named Container */ private extractGroupings(statement: Statement, parentId: number, module: Module, currentPath: string): ViewSpecification[] { const subViews: ViewSpecification[] = []; const groupings = this.extractNodes(statement, "grouping"); @@ -620,7 +623,7 @@ export class YangParser { return subViews; } - /** Handles Augmants also like named Container */ + /** Handles augments also like named container */ private extractAugments(statement: Statement, parentId: number, module: Module, currentPath: string): ViewSpecification[] { const subViews: ViewSpecification[] = []; const augments = this.extractNodes(statement, "augment"); @@ -645,8 +648,8 @@ export class YangParser { return subViews; } - /** Handles Identities */ - private extractIdentites(statement: Statement, parentId: number, module: Module, currentPath: string) { + /** Handles identities */ + private extractIdentities(statement: Statement, parentId: number, module: Module, currentPath: string) { const identities = this.extractNodes(statement, "identity"); module.identities = identities.reduce<{ [name: string]: Identity }>((acc, cur) => { if (!cur.arg) { |