ODLUX Connect App Info enhancement 21/122921/1
authorAijana Schumann <aijana.schumann@highstreet-technologies.com>
Wed, 28 Jul 2021 09:19:09 +0000 (11:19 +0200)
committerAijana Schumann <aijana.schumann@highstreet-technologies.com>
Wed, 28 Jul 2021 09:19:09 +0000 (11:19 +0200)
Present yang capabilities in a table view instead of list

Issue-ID: SDNC-1121
Signed-off-by: Aijana Schumann <aijana.schumann@highstreet-technologies.com>
Change-Id: I4904fb132351199b57a851faf07d371fa5e575ab

sdnr/wt/odlux/apps/connectApp/src/actions/commonNetworkElementsActions.ts
sdnr/wt/odlux/apps/connectApp/src/actions/infoNetworkElementActions.ts
sdnr/wt/odlux/apps/connectApp/src/components/infoNetworkElementDialog.tsx
sdnr/wt/odlux/apps/connectApp/src/components/networkElements.tsx
sdnr/wt/odlux/apps/connectApp/src/handlers/connectAppRootHandler.ts
sdnr/wt/odlux/apps/connectApp/src/handlers/infoNetworkElementHandler.ts
sdnr/wt/odlux/apps/connectApp/src/models/topologyNetconf.ts
sdnr/wt/odlux/apps/connectApp/src/models/yangCapabilitiesType.ts
sdnr/wt/odlux/apps/connectApp/src/services/connectService.ts

index 0c32662..26aa8d2 100644 (file)
@@ -64,6 +64,9 @@ export const findWebUrisForGuiCutThroughAsyncAction = (networkElementIds: string
 
   // keep method from executing simultanously; state not used because change of iu isn't needed
 
+  if (isBusy)
+    return;
+  isBusy = true;
 
   const { connect: { guiCutThrough, networkElements } } = getState();
 
@@ -78,16 +81,16 @@ export const findWebUrisForGuiCutThroughAsyncAction = (networkElementIds: string
       if (item.status === "Connected") {
 
         // if (item.coreModelCapability !== "Unsupported") {
-          // element is connected and is added to search list, if it doesn't exist already
-          const exists = guiCutThrough.searchedElements.filter(element => element.id === id).length > 0;
-          if (!exists) {
-            elementsToSearch.push(id);
-
-            //element was found previously, but wasn't connected
-            if (guiCutThrough.notSearchedElements.length > 0 && guiCutThrough.notSearchedElements.includes(id)) {
-              prevFoundElements.push(id);
-            }
+        // element is connected and is added to search list, if it doesn't exist already
+        const exists = guiCutThrough.searchedElements.filter(element => element.id === id).length > 0;
+        if (!exists) {
+          elementsToSearch.push(id);
+
+          //element was found previously, but wasn't connected
+          if (guiCutThrough.notSearchedElements.length > 0 && guiCutThrough.notSearchedElements.includes(id)) {
+            prevFoundElements.push(id);
           }
+        }
         // } else {
         //   // element does not support core model and must not be searched for a weburi  
         //   const id = item.id as string;
@@ -113,9 +116,10 @@ export const findWebUrisForGuiCutThroughAsyncAction = (networkElementIds: string
 
 
   if (elementsToSearch.length > 0 || notConnectedElements.length > 0 || unsupportedElements.length > 0) {
-      const result = await connectService.getAllWebUriExtensionsForNetworkElementListAsync(elementsToSearch);
-     dispatcher(new AddWebUriList(result, notConnectedElements, unsupportedElements, prevFoundElements));
+    const result = await connectService.getAllWebUriExtensionsForNetworkElementListAsync(elementsToSearch);
+    dispatcher(new AddWebUriList(result, notConnectedElements, unsupportedElements, prevFoundElements));
   }
+  isBusy = false;
 
 }
 
index 4ae28aa..bb744e2 100644 (file)
  * the License.
  * ============LICENSE_END==========================================================================
  */
-import { Action } from '../../../../framework/src/flux/action';
-import { Dispatch } from '../../../../framework/src/flux/store';
-
-import { TopologyNode } from '../models/topologyNetconf';
-import { connectService } from '../services/connectService';
-
-/** 
- * Represents the base action. 
- */
-export class BaseAction extends Action { }
-
-/** 
- * Represents an action causing the store to load all element Yang capabilities.
- */
-export class LoadAllElementInfoAction extends BaseAction { }
-
-/** 
- * Represents an action causing the store to update element Yang capabilities. 
- */
-export class AllElementInfoLoadedAction extends BaseAction {
-  /**
-   * Initialize this instance.
-   * @param elementInfo The information of the element which is returned.
-   */
-  constructor(public elementInfo: TopologyNode | null, public error?: string) {
-    super();
-  }
-}
-
-/** 
- * Represents an asynchronous thunk  action to load all yang capabilities. 
- */
-export const loadAllInfoElementAsync = (nodeId: string) => (dispatch: Dispatch) => {
-  dispatch(new LoadAllElementInfoAction());
-  connectService.infoNetworkElement(nodeId).then(info => {
-    dispatch(new AllElementInfoLoadedAction(info));
-  }, error => {
-    dispatch(new AllElementInfoLoadedAction(null, error));
-  });
-} 
\ No newline at end of file
+ import { Action } from '../../../../framework/src/flux/action';
+ import { Dispatch } from '../../../../framework/src/flux/store';
+ import { Module, TopologyNode } from '../models/topologyNetconf';
+ import { connectService } from '../services/connectService';
+ /** 
+  * Represents the base action. 
+  */
+ export class BaseAction extends Action { }
+ /** 
+  * Represents an action causing the store to load all element Yang capabilities.
+  */
+ export class LoadAllElementInfoAction extends BaseAction { }
+ /** 
+  * Represents an action causing the store to update element Yang capabilities. 
+  */
+ export class AllElementInfoLoadedAction extends BaseAction {
+   /**
+    * Initialize this instance.
+    * @param elementInfo The information of the element which is returned.
+    */
+   constructor(public elementInfo: TopologyNode | null, public error?: string) {
+     super();
+   }
+ }
+ /** 
+  * Represents an action causing the store to update element Yang capabilities Module Features. 
+  */
+ export class AllElementInfoFeatureLoadedAction extends BaseAction {
+   /**
+    * Initialize this instance.
+    * @param elementFeatureInfo The information of the element which is returned.
+    */
+   constructor(public elementFeatureInfo: Module[] | null | undefined, public error?: string) {
+     super();
+   }
+ }
+ /** 
+  * Represents an asynchronous thunk  action to load all yang capabilities. 
+  */
+ export const loadAllInfoElementAsync = (nodeId: string) => (dispatch: Dispatch) => {
+   dispatch(new LoadAllElementInfoAction());
+   connectService.infoNetworkElement(nodeId).then(info => {
+     dispatch(new AllElementInfoLoadedAction(info));
+   }, error => {
+     dispatch(new AllElementInfoLoadedAction(null, error));
+   });
+ } 
+ /** 
+  * Represents an asynchronous thunk  action to load all yang features. 
+  */
+ export const loadAllInfoElementFeaturesAsync = (nodeId: string) => (dispatch: Dispatch) => {
+   dispatch(new LoadAllElementInfoAction());
+   connectService.infoNetworkElementFeatures(nodeId).then(infoFeatures => {
+     dispatch(new AllElementInfoFeatureLoadedAction(infoFeatures));
+   }, error => {
+     dispatch(new AllElementInfoFeatureLoadedAction(null, error));
+   });
+ } 
\ No newline at end of file
index df8515e..9b71eb3 100644 (file)
  * the License.
  * ============LICENSE_END==========================================================================
  */
-import * as React from 'react';
-
-import Button from '@material-ui/core/Button';
-import Dialog from '@material-ui/core/Dialog';
-import DialogActions from '@material-ui/core/DialogActions';
-import DialogContent from '@material-ui/core/DialogContent';
-import DialogContentText from '@material-ui/core/DialogContentText';
-import DialogTitle from '@material-ui/core/DialogTitle';
-import Table from '@material-ui/core/Table';
-import TableBody from '@material-ui/core/TableBody';
-import TableCell from '@material-ui/core/TableCell';
-import TableHead from '@material-ui/core/TableHead';
-import TableRow from '@material-ui/core/TableRow';
-import { IDispatcher, connect, Connect } from '../../../../framework/src/flux/connect';
-
-import { NetworkElementConnection } from '../models/networkElementConnection';
-import { AvailableCapabilities } from '../models/yangCapabilitiesType'
-
-export enum InfoNetworkElementDialogMode {
-  None = "none",
-  InfoNetworkElement = "infoNetworkElement"
-}
-
-const mapDispatch = (dispatcher: IDispatcher) => ({
-});
-
-type DialogSettings = {
-  dialogTitle: string,
-  dialogDescription: string,
-  cancelButtonText: string,
-}
-
-const settings: { [key: string]: DialogSettings } = {
-  [InfoNetworkElementDialogMode.None]: {
-    dialogTitle: "",
-    dialogDescription: "",
-    cancelButtonText: "",
-  },
-  [InfoNetworkElementDialogMode.InfoNetworkElement]: {
-    dialogTitle: "Yang capabilities of the network element",
-    dialogDescription: "Available capabilities of the network element",
-    cancelButtonText: "OK",
-  }
-}
-
-type InfoNetworkElementDialogComponentProps = Connect<undefined, typeof mapDispatch> & {
-  mode: InfoNetworkElementDialogMode;
-  initialNetworkElement: NetworkElementConnection;
-  onClose: () => void;
-};
-
-type InfoNetworkElementDialogComponentState = NetworkElementConnection;
-
-class InfoNetworkElementDialogComponent extends React.Component<InfoNetworkElementDialogComponentProps, InfoNetworkElementDialogComponentState> {
-  constructor(props: InfoNetworkElementDialogComponentProps) {
-    super(props);
-
-    this.state = {
-      nodeId: this.props.initialNetworkElement.nodeId,
-      isRequired: false,
-      host: this.props.initialNetworkElement.host,
-      port: this.props.initialNetworkElement.port,
-    };
-  }
-
-  render(): JSX.Element {
-    const setting = settings[this.props.mode];
-    const availableCapabilities = this.props.state.connect.elementInfo.elementInfo["netconf-node-topology:available-capabilities"]["available-capability"];
-    let yangCapabilities: AvailableCapabilities[] = [];
-    
-    availableCapabilities.forEach(value => {
-      const capabilty = value.capability;
-      const indexRevision = capabilty.indexOf("revision=");
-      const indexModule = capabilty.indexOf(")", indexRevision);
-      if (indexRevision > 0 && indexModule > 0) {
-        yangCapabilities.push({
-          module: capabilty.substr(indexModule + 1),
-          revision: capabilty.substr(indexRevision + 9, 10)
-        });
-      }
-    });
-
-    yangCapabilities = yangCapabilities.sort((a,b) => a.module === b.module ? 0 : a.module > b.module ? 1 : -1);
-
-    return (
-      <Dialog open={this.props.mode !== InfoNetworkElementDialogMode.None}>
-        <DialogTitle id="form-dialog-title">{setting.dialogTitle}</DialogTitle>
-        <DialogContent>
-          <DialogContentText>
-            {setting.dialogDescription + " " + this.state.nodeId}
-          </DialogContentText>
-          <Table aria-label="yang-capabilities-table">
-            <TableHead>
-              <TableRow>
-                <TableCell align="right">S.No</TableCell>
-                <TableCell >Module</TableCell>
-                <TableCell >Revision</TableCell>
-              </TableRow>
-            </TableHead>
-            <TableBody>
-              {yangCapabilities.map((yang, index) => (
-                <TableRow aria-label="yang-capabilities-row">
-                  <TableCell>{index + 1}</TableCell>
-                  <TableCell aria-label="yang-module"><a href={`/yang-schema/${yang.module}`} target={"_blank"}> {yang.module} </a></TableCell>
-                  <TableCell aria-label="yang-revision">{yang.revision}</TableCell>
-                </TableRow>
-              ))}
-            </TableBody>
-          </Table>
-        </DialogContent>
-        <DialogActions>
-          <Button aria-label="ok-button" onClick={(event) => {
-            this.onCancel();
-            event.preventDefault();
-            event.stopPropagation();
-          }} color="secondary"> {setting.cancelButtonText} </Button>
-        </DialogActions>
-      </Dialog>
-    )
-  }
-
-  private onCancel = () => {
-    this.props.onClose();
-  }
-
-  static getDerivedStateFromProps(props: InfoNetworkElementDialogComponentProps, state: InfoNetworkElementDialogComponentState & { _initialNetworkElement: NetworkElementConnection }): InfoNetworkElementDialogComponentState & { _initialNetworkElement: NetworkElementConnection } {
-    if (props.initialNetworkElement !== state._initialNetworkElement) {
-      state = {
-        ...state,
-        ...props.initialNetworkElement,
-        _initialNetworkElement: props.initialNetworkElement,
-      };
-    }
-    return state;
-  }
-}
-
-export const InfoNetworkElementDialog = connect(undefined, mapDispatch)(InfoNetworkElementDialogComponent);
-export default InfoNetworkElementDialog;
\ No newline at end of file
+ import * as React from 'react';
+
+ import Button from '@material-ui/core/Button';
+ import Dialog from '@material-ui/core/Dialog';
+ import DialogActions from '@material-ui/core/DialogActions';
+ import DialogTitle from '@material-ui/core/DialogTitle';
+ import { MaterialTable, ColumnType, MaterialTableCtorType } from '../../../../framework/src/components/material-table';
+ import { IDispatcher, connect, Connect } from '../../../../framework/src/flux/connect';
+ import { NetworkElementConnection } from '../models/networkElementConnection';
+ import { AvailableCapabilities } from '../models/yangCapabilitiesType'
+ export enum InfoNetworkElementDialogMode {
+   None = "none",
+   InfoNetworkElement = "infoNetworkElement"
+ }
+ const mapDispatch = (dispatcher: IDispatcher) => ({
+ });
+ const InfoElementTable = MaterialTable as MaterialTableCtorType<AvailableCapabilities>;
+ type DialogSettings = {
+   dialogTitle: string,
+   dialogDescription: string,
+   cancelButtonText: string,
+ }
+ const settings: { [key: string]: DialogSettings } = {
+   [InfoNetworkElementDialogMode.None]: {
+     dialogTitle: "",
+     dialogDescription: "",
+     cancelButtonText: "",
+   },
+   [InfoNetworkElementDialogMode.InfoNetworkElement]: {
+     dialogTitle: "Yang capabilities of the network element",
+     dialogDescription: "",
+     cancelButtonText: "OK",
+   }
+ }
+ type InfoNetworkElementDialogComponentProps = Connect<undefined, typeof mapDispatch> & {
+   mode: InfoNetworkElementDialogMode;
+   initialNetworkElement: NetworkElementConnection;
+   onClose: () => void;
+ };
+ type InfoNetworkElementDialogComponentState = NetworkElementConnection;
+ class InfoNetworkElementDialogComponent extends React.Component<InfoNetworkElementDialogComponentProps, InfoNetworkElementDialogComponentState> {
+   constructor(props: InfoNetworkElementDialogComponentProps) {
+     super(props);
+     this.state = {
+       nodeId: this.props.initialNetworkElement.nodeId,
+       isRequired: false,
+       host: this.props.initialNetworkElement.host,
+       port: this.props.initialNetworkElement.port,
+     };
+   }
+   render(): JSX.Element {
+     const setting = settings[this.props.mode];
+     const availableCapabilities = this.props.state.connect.elementInfo.elementInfo["netconf-node-topology:available-capabilities"]["available-capability"];
+     let yangFeatures = this.props.state.connect.elementFeatureInfo.elementFeatureInfo;
+     let yangCapabilities: AvailableCapabilities[] = [];
+     
+     availableCapabilities.forEach(value => {
+       const capabilty = value.capability;
+       const indexRevision = capabilty.indexOf("revision=");
+       const indexModule = capabilty.indexOf(")", indexRevision);
+       if (indexRevision > 0 && indexModule > 0) {
+         let moduleName = capabilty.substr(indexModule + 1);
+         let ModuleFeaturesList;
+         for(let index = 0; index < yangFeatures.length; index++) {
+           if(yangFeatures[index].name == moduleName) {
+             ModuleFeaturesList = yangFeatures[index].feature? yangFeatures[index].feature : null;
+             break;
+           }
+         }
+         const featuresListCommaSeparated= ModuleFeaturesList? ModuleFeaturesList.toString() : ""
+         let featuresList = featuresListCommaSeparated.replace(',',', ');
+         yangCapabilities.push({
+           module: moduleName,
+           revision: capabilty.substr(indexRevision + 9, 10),
+           features: featuresList 
+          });
+       }
+     });
+     yangCapabilities = yangCapabilities.sort((a,b) => a.module === b.module ? 0 : a.module > b.module ? 1 : -1);
+     return (
+       <>
+         <Dialog open={this.props.mode !== InfoNetworkElementDialogMode.None}  >
+           <DialogTitle id="form-dialog-title">{setting.dialogTitle + ' - ' + this.state.nodeId}</DialogTitle>
+           <InfoElementTable stickyHeader tableId="info-element-table" asynchronus columns={[
+             { property: "module", title: "Module", type: ColumnType.text, width:900 },
+             {
+               property: "revision", title: "Revision", type: ColumnType.custom, customControl: ({ rowData }) => {
+                 return (
+                   <div>
+                     <a href={'/yang-schema/' + rowData.module + '/' + rowData.revision} target="_blank"  > {rowData.revision} </a>
+                   </div>
+                 )
+               }
+             },
+             { property: "features", title: "Features", type: ColumnType.text, width:500 },
+           ]} idProperty="id" rows={yangCapabilities}  >
+           </InfoElementTable>
+           <DialogActions>
+             <Button aria-label="ok-button" onClick={(event) => {
+               this.onCancel();
+               event.preventDefault();
+               event.stopPropagation();
+             }} color="secondary"> {setting.cancelButtonText} </Button>
+           </DialogActions>
+         </Dialog>
+       </>
+     )
+   }
+   private onCancel = () => {
+     this.props.onClose();
+   }
+   static getDerivedStateFromProps(props: InfoNetworkElementDialogComponentProps, state: InfoNetworkElementDialogComponentState & { _initialNetworkElement: NetworkElementConnection }): InfoNetworkElementDialogComponentState & { _initialNetworkElement: NetworkElementConnection } {
+     if (props.initialNetworkElement !== state._initialNetworkElement) {
+       state = {
+         ...state,
+         ...props.initialNetworkElement,
+         _initialNetworkElement: props.initialNetworkElement,
+       };
+     }
+     return state;
+   }
+ }
+ export const InfoNetworkElementDialog = connect(undefined, mapDispatch)(InfoNetworkElementDialogComponent);
+ export default InfoNetworkElementDialog;
\ No newline at end of file
index 84a22a9..73706f6 100644 (file)
@@ -36,12 +36,12 @@ import { NavigateToApplication } from '../../../../framework/src/actions/navigat
 import { createNetworkElementsActions, createNetworkElementsProperties } from '../handlers/networkElementsHandler';
 
 import { NetworkElementConnection } from '../models/networkElementConnection';
-import { TopologyNode } from '../models/topologyNetconf';
+import { ModuleSet, TopologyNode } from '../models/topologyNetconf';
 import EditNetworkElementDialog, { EditNetworkElementDialogMode } from './editNetworkElementDialog';
 import RefreshNetworkElementsDialog, { RefreshNetworkElementsDialogMode } from './refreshNetworkElementsDialog';
 
 import InfoNetworkElementDialog, { InfoNetworkElementDialogMode } from './infoNetworkElementDialog';
-import { loadAllInfoElementAsync } from '../actions/infoNetworkElementActions';
+import { loadAllInfoElementAsync, loadAllInfoElementFeaturesAsync } from '../actions/infoNetworkElementActions';
 import { connectService } from '../services/connectService';
 import { getAccessPolicyByUrl } from '../../../../framework/src/services/restService';
 
@@ -92,6 +92,7 @@ const mapDispatch = (dispatcher: IDispatcher) => ({
   networkElementsActions: createNetworkElementsActions(dispatcher.dispatch),
   navigateToApplication: (applicationName: string, path?: string) => dispatcher.dispatch(new NavigateToApplication(applicationName, path)),
   networkElementInfo: async (nodeId: string) => await dispatcher.dispatch(loadAllInfoElementAsync(nodeId)),
+  networkElementFeaturesInfo: async (nodeId: string) => await dispatcher.dispatch(loadAllInfoElementFeaturesAsync(nodeId))
 });
 
 type NetworkElementsListComponentProps = WithStyles<typeof styles> & Connect<typeof mapProps, typeof mapDispatch>;
@@ -100,7 +101,8 @@ type NetworkElementsListComponentState = {
   networkElementEditorMode: EditNetworkElementDialogMode,
   refreshNetworkElementsEditorMode: RefreshNetworkElementsDialogMode,
   infoNetworkElementEditorMode: InfoNetworkElementDialogMode,
-  elementInfo: TopologyNode | null
+  elementInfo: TopologyNode | null,
+  elementInfoFeature: ModuleSet | null
 }
 
 const emptyRequireNetworkElement: NetworkElementConnection = { id: "", nodeId: "", host: "", port: 0, status: "Disconnected", isRequired: false };
@@ -117,6 +119,7 @@ export class NetworkElementsListComponent extends React.Component<NetworkElement
       networkElementEditorMode: EditNetworkElementDialogMode.None,
       refreshNetworkElementsEditorMode: RefreshNetworkElementsDialogMode.None,
       elementInfo: null,
+      elementInfoFeature:null,
       infoNetworkElementEditorMode: InfoNetworkElementDialogMode.None
     };
   }
@@ -164,7 +167,7 @@ export class NetworkElementsListComponent extends React.Component<NetworkElement
     const canAdd = true;
 
     const addRequireNetworkElementAction = {
-      icon: AddIcon, tooltip: 'Add', onClick: () => {
+      icon: AddIcon, tooltip: 'Add', ariaLabel:"add-element", onClick: () => {
         this.setState({
           networkElementEditorMode: EditNetworkElementDialogMode.AddNewNetworkElement,
           networkElementToEdit: emptyRequireNetworkElement,
@@ -173,7 +176,7 @@ export class NetworkElementsListComponent extends React.Component<NetworkElement
     };
 
     const refreshNetworkElementsAction = {
-      icon: Refresh, tooltip: 'Refresh Network Elements table', onClick: () => {
+      icon: Refresh, tooltip: 'Refresh Network Elements table', ariaLabel:'refresh', onClick: () => {
         this.setState({
           refreshNetworkElementsEditorMode: RefreshNetworkElementsDialogMode.RefreshNetworkElementsTable
         });
@@ -266,6 +269,7 @@ export class NetworkElementsListComponent extends React.Component<NetworkElement
 
   private onOpenInfoNetworkElementDialog = (event: React.MouseEvent<HTMLElement>, element: NetworkElementConnection) => {
     this.props.networkElementInfo(element.nodeId);
+    this.props.networkElementFeaturesInfo(element.nodeId);
     this.setState({
       networkElementToEdit: element,
       infoNetworkElementEditorMode: InfoNetworkElementDialogMode.InfoNetworkElement,
index 70b64c9..81ee97a 100644 (file)
@@ -20,7 +20,7 @@ import { IActionHandler } from '../../../../framework/src/flux/action';
 import { combineActionHandler } from '../../../../framework/src/flux/middleware';
 import { INetworkElementsState, networkElementsActionHandler } from './networkElementsHandler';
 import { IConnectionStatusLogState, connectionStatusLogActionHandler } from './connectionStatusLogHandler';
-import { IInfoNetworkElementsState, infoNetworkElementsActionHandler } from './infoNetworkElementHandler';
+import { IInfoNetworkElementsState, infoNetworkElementsActionHandler, IInfoNetworkElementFeaturesState, infoNetworkElementFeaturesActionHandler } from './infoNetworkElementHandler';
 import { SetPanelAction, AddWebUriList, RemoveWebUri, SetWeburiSearchBusy } from '../actions/commonNetworkElementsActions';
 import { PanelId } from '../models/panelId';
 import { guiCutThrough } from '../models/guiCutTrough';
@@ -31,6 +31,7 @@ export interface IConnectAppStoreState {
   connectionStatusLog: IConnectionStatusLogState;
   currentOpenPanel: PanelId;
   elementInfo: IInfoNetworkElementsState;
+  elementFeatureInfo: IInfoNetworkElementFeaturesState;
   guiCutThrough: guiCutThroughState;
   connectionStatusCount: IConnectionStatusCount;
 }
@@ -48,7 +49,7 @@ interface guiCutThroughState {
   unsupportedElements: string[];
 }
 
-const guiCutThroughHandler: IActionHandler<guiCutThroughState> = (state = { searchedElements: [], notSearchedElements: [], unsupportedElements:[] }, action) => {
+const guiCutThroughHandler: IActionHandler<guiCutThroughState> = (state = { searchedElements: [], notSearchedElements: [], unsupportedElements: [] }, action) => {
   if (action instanceof AddWebUriList) {
     let notSearchedElements: string[];
     let searchedElements: guiCutThrough[];
@@ -73,7 +74,7 @@ const guiCutThroughHandler: IActionHandler<guiCutThroughState> = (state = { sear
     const webUris = state.searchedElements.filter(item => item.id !== nodeId);
     const knownElements = state.notSearchedElements.filter(item => item !== nodeId);
     const unsupportedElement = state.unsupportedElements.filter(item => item != nodeId);
-    state = { notSearchedElements: knownElements, searchedElements: webUris, unsupportedElements: unsupportedElement  };
+    state = { notSearchedElements: knownElements, searchedElements: webUris, unsupportedElements: unsupportedElement };
   }
   return state;
 }
@@ -89,6 +90,7 @@ const actionHandlers = {
   connectionStatusLog: connectionStatusLogActionHandler,
   currentOpenPanel: currentOpenPanelHandler,
   elementInfo: infoNetworkElementsActionHandler,
+  elementFeatureInfo: infoNetworkElementFeaturesActionHandler,
   guiCutThrough: guiCutThroughHandler,
   connectionStatusCount: connectionStatusCountHandler
 };
index d67a81e..3e2d1ce 100644 (file)
  * the License.
  * ============LICENSE_END==========================================================================
  */
-import { IActionHandler } from '../../../../framework/src/flux/action';
+ import { IActionHandler } from '../../../../framework/src/flux/action';
 
-import { AllElementInfoLoadedAction, LoadAllElementInfoAction } from '../actions/infoNetworkElementActions';
-
-import { TopologyNode } from '../models/topologyNetconf';
-
-export interface IInfoNetworkElementsState {
-  elementInfo: TopologyNode;
-  busy: boolean;
-}
-
-const infoNetworkElementsStateInit: IInfoNetworkElementsState = {
-  elementInfo: {
-    "node-id": "",
-    "netconf-node-topology:available-capabilities": {
-      "available-capability": []
-    }
-  },
-  busy: false
-};
-
-export const infoNetworkElementsActionHandler: IActionHandler<IInfoNetworkElementsState> = (state = infoNetworkElementsStateInit, action) => {
-  if (action instanceof LoadAllElementInfoAction) {
-    state = {
-      ...state,
-      busy: true
-    };
-  } else if (action instanceof AllElementInfoLoadedAction) {
-    if (!action.error && action.elementInfo) {
-      state = {
-        ...state,
-        elementInfo: action.elementInfo,
-        busy: false
-      };
-    } else {
-      state = {
-        ...state,
-        busy: false
-      };
-    }
-  }
-  return state;
-};
\ No newline at end of file
+ import { AllElementInfoLoadedAction, AllElementInfoFeatureLoadedAction, LoadAllElementInfoAction } from '../actions/infoNetworkElementActions';
+ import { Module, TopologyNode } from '../models/topologyNetconf';
+ export interface IInfoNetworkElementsState {
+   elementInfo: TopologyNode;
+   busy: boolean;
+ }
+ export interface IInfoNetworkElementFeaturesState {
+   elementFeatureInfo: Module[];
+   busy: boolean;
+ }
+ const infoNetworkElementsStateInit: IInfoNetworkElementsState = {
+   elementInfo: {
+     "node-id": "",
+     "netconf-node-topology:available-capabilities": {
+       "available-capability": []
+     }
+   },
+   busy: false
+ };
+ const infoNetworkElementFeaturesStateInit: IInfoNetworkElementFeaturesState = {
+   elementFeatureInfo: [],
+   busy: false
+ };
+ export const infoNetworkElementsActionHandler: IActionHandler<IInfoNetworkElementsState> = (state = infoNetworkElementsStateInit, action) => {
+   if (action instanceof LoadAllElementInfoAction) {
+     state = {
+       ...state,
+       busy: true
+     };
+   } else if (action instanceof AllElementInfoLoadedAction) {
+     if (!action.error && action.elementInfo) {
+       state = {
+         ...state,
+         elementInfo: action.elementInfo,
+         busy: false
+       };
+     } else {
+       state = {
+         ...state,
+         busy: false
+       };
+     }
+   }
+   return state;
+ };
+ export const infoNetworkElementFeaturesActionHandler: IActionHandler<IInfoNetworkElementFeaturesState> = (state = infoNetworkElementFeaturesStateInit, action) => {
+   if (action instanceof LoadAllElementInfoAction) {
+     state = {
+       ...state,
+       busy: true
+     };
+   } else if (action instanceof AllElementInfoFeatureLoadedAction) {
+     if (!action.error && action.elementFeatureInfo) {
+       state = {
+         ...state,
+         elementFeatureInfo: action.elementFeatureInfo,
+         busy: false
+       };
+     } else {
+       state = {
+         ...state,
+         busy: false
+       };
+     }
+   }
+   return state;
+ };
\ No newline at end of file
index ef22aab..936e20b 100644 (file)
@@ -16,7 +16,7 @@
  * ============LICENSE_END==========================================================================
  */
 
-export interface AvailableCapability {
+ export interface AvailableCapability {
   "capability-origin": string;
   capability: string;
 }
@@ -34,3 +34,26 @@ export interface Topology {
   "topology-id": string;
   "network-topology:node": TopologyNode[];
 }
+
+/**
+  * Represents the type of the features of the Module. 
+  */
+export interface Module {
+  feature?: string[];
+  location?: string[];
+  name: string;
+  namespace?: string;
+  revision?: string;
+}
+
+export interface ModuleFeatures {
+   module: Module[];
+}
+
+export interface ModuleSet {
+  "module-set": ModuleFeatures[];
+}
+
+export interface FeatureTopology {
+  "ietf-yang-library:yang-library" : ModuleSet 
+}
index 2304682..c8cf704 100644 (file)
@@ -16,7 +16,9 @@
  * ============LICENSE_END==========================================================================
  */
 
-export type AvailableCapabilities = {
+ export type AvailableCapabilities = {
+    id?: string,
     module: string,
-    revision: string
+    revision: string,
+    features: string
 }
\ No newline at end of file
index 5d7667a..ed8b0f6 100644 (file)
  * ============LICENSE_END==========================================================================
  */
 
-import { requestRest } from '../../../../framework/src/services/restService';
-import { NetworkElementConnection, ConnectionStatus, UpdateNetworkElement } from '../models/networkElementConnection';
-import { convertPropertyNames, replaceUpperCase } from '../../../../framework/src/utilities/yangHelper';
-import { Result } from '../../../../framework/src/models/elasticSearch';
-
-import { Topology, TopologyNode } from '../models/topologyNetconf';
-import { guiCutThrough } from '../models/guiCutTrough';
-
-/**
-* Represents a web api accessor service for all Network Elements actions.
-*/
-class ConnectService {
-  public getNetworkElementUri = (nodeId: string) => '/rests/data/network-topology:network-topology/topology=topology-netconf/node=' + nodeId;
-  public getNetworkElementConnectDataProviderUri = (operation: "create" | "update" | "delete" ) => `/rests/operations/data-provider:${operation}-network-element-connection`;
-  public getAllWebUriExtensionsForNetworkElementListUri = (nodeId: string) => this.getNetworkElementUri(nodeId) + '/yang-ext:mount/core-model:network-element';
-
-  /**
-   * Inserts a network elements.
-   */
-  public async createNetworkElement(element: NetworkElementConnection): Promise<NetworkElementConnection | null> {
-    const path = this.getNetworkElementConnectDataProviderUri("create") ;
-    const result = await requestRest<NetworkElementConnection>(path, {
-      method: "POST", body: JSON.stringify(convertPropertyNames({ "data-provider:input": element }, replaceUpperCase))
-    });
-    return result || null;
-  }
-
-  /**
-  * Updates a network element.
-  */
-  public async updateNetworkElement(element: UpdateNetworkElement): Promise<NetworkElementConnection | null> {
-    const path = this.getNetworkElementConnectDataProviderUri("update");
-    const result = await requestRest<NetworkElementConnection>(path, {
-      method: "POST", body: JSON.stringify(convertPropertyNames({ "data-provider:input": element }, replaceUpperCase))
-    });
-    return result || null;
-  }
-
-  /**
-    * Deletes a network element.
+ import { requestRest } from '../../../../framework/src/services/restService';
+ import { NetworkElementConnection, ConnectionStatus, UpdateNetworkElement } from '../models/networkElementConnection';
+ import { convertPropertyNames, replaceUpperCase } from '../../../../framework/src/utilities/yangHelper';
+ import { Result } from '../../../../framework/src/models/elasticSearch';
+ import { FeatureTopology, Topology, TopologyNode, Module } from '../models/topologyNetconf';
+ import { guiCutThrough } from '../models/guiCutTrough';
+ /**
+ * Represents a web api accessor service for all Network Elements actions.
+ */
+ class ConnectService {
+   public getNetworkElementUri = (nodeId: string) => '/rests/data/network-topology:network-topology/topology=topology-netconf/node=' + nodeId;
+   public getNetworkElementConnectDataProviderUri = (operation: "create" | "update" | "delete" ) => `/rests/operations/data-provider:${operation}-network-element-connection`;
+   public getAllWebUriExtensionsForNetworkElementListUri = (nodeId: string) => this.getNetworkElementUri(nodeId) + '/yang-ext:mount/core-model:network-element';
+   public getNetworkElementYangLibraryFeature = (nodeId: string) => '/rests/data/network-topology:network-topology/topology=topology-netconf/node=' + nodeId + '/yang-ext:mount/ietf-yang-library:yang-library?content=nonconfig'
+   /**
+    * Inserts a network elements.
     */
-  public async deleteNetworkElement(element: UpdateNetworkElement): Promise<NetworkElementConnection | null> {
-    const query = {
-      "id": element.id
-    };
-    const path = this.getNetworkElementConnectDataProviderUri("delete");
-    const result = await requestRest<NetworkElementConnection>(path, {
-      method: "POST", body: JSON.stringify(convertPropertyNames({ "data-provider:input": query }, replaceUpperCase))
-    });
-    return result || null;
-  }
-
-  /** Mounts network element. */
-  public async mountNetworkElement(networkElement: NetworkElementConnection): Promise<boolean> {
-    const path = this.getNetworkElementUri(networkElement.nodeId);
-    const mountXml = [
-      '<node xmlns="urn:TBD:params:xml:ns:yang:network-topology">',
-      `<node-id>${networkElement.nodeId}</node-id>`,
-      `<host xmlns="urn:opendaylight:netconf-node-topology">${networkElement.host}</host>`,
-      `<port xmlns="urn:opendaylight:netconf-node-topology">${networkElement.port}</port>`,
-      `<username xmlns="urn:opendaylight:netconf-node-topology">${networkElement.username}</username>`,
-      `<password xmlns="urn:opendaylight:netconf-node-topology">${networkElement.password}</password>`,
-      '  <tcp-only xmlns="urn:opendaylight:netconf-node-topology">false</tcp-only>',
-
-      '  <!-- non-mandatory fields with default values, you can safely remove these if you do not wish to override any of these values-->',
-      '  <reconnect-on-changed-schema xmlns="urn:opendaylight:netconf-node-topology">false</reconnect-on-changed-schema>',
-      '  <connection-timeout-millis xmlns="urn:opendaylight:netconf-node-topology">20000</connection-timeout-millis>',
-      '  <max-connection-attempts xmlns="urn:opendaylight:netconf-node-topology">100</max-connection-attempts>',
-      '  <between-attempts-timeout-millis xmlns="urn:opendaylight:netconf-node-topology">2000</between-attempts-timeout-millis>',
-      '  <sleep-factor xmlns="urn:opendaylight:netconf-node-topology">1.5</sleep-factor>',
-
-      '  <!-- keepalive-delay set to 0 turns off keepalives-->',
-      '  <keepalive-delay xmlns="urn:opendaylight:netconf-node-topology">120</keepalive-delay>',
-      '</node>'].join('');
-
-    try {
-      const result = await requestRest<string>(path, {
-        method: 'PUT',
-        headers: {
-          'Content-Type': 'application/xml',
-          'Accept': 'application/xml'
-        },
-        body: mountXml
-      });
-      // expect an empty answer
-      return result !== null;
-    } catch {
-      return false;
-    }
-  };
-
-  /** Unmounts a network element by its id. */
-  public async unmountNetworkElement(nodeId: string): Promise<boolean> {
-    const path = this.getNetworkElementUri(nodeId);
-
-    try {
-      const result = await requestRest<string>(path, {
-        method: 'DELETE',
-        headers: {
-          'Content-Type': 'application/xml',
-          'Accept': 'application/xml'
-        },
-      });
-      // expect an empty answer
-      return result !== null;
-
-    } catch {
-      return false;
-    }
-  };
-
-  /** Yang capabilities of the selected network elements. */
-  public async infoNetworkElement(nodeId: string): Promise<TopologyNode | null> {
-    const path = this.getNetworkElementUri(nodeId);
-    const topologyRequestPomise = requestRest<Topology>(path, { method: "GET" });
-
-    return topologyRequestPomise && topologyRequestPomise.then(result => {
-      return result && result["network-topology:node"] && result["network-topology:node"][0] || null;
-    });
-  }
-
-  /**
-   * Get the connection state of the network element.
+   public async createNetworkElement(element: NetworkElementConnection): Promise<NetworkElementConnection | null> {
+     const path = this.getNetworkElementConnectDataProviderUri("create") ;
+     const result = await requestRest<NetworkElementConnection>(path, {
+       method: "POST", body: JSON.stringify(convertPropertyNames({ "data-provider:input": element }, replaceUpperCase))
+     });
+     return result || null;
+   }
+   /**
+   * Updates a network element.
    */
-  public async getNetworkElementConnectionStatus(element: string): Promise<(ConnectionStatus)[] | null> {
-    const path = `/rests/operations/data-provider:read-network-element-connection-list`;
-    const query = {
-      "data-provider:input": {
-        "filter": [{
-          "property": "node-id",
-          "filtervalue": element
-        }],
-        "pagination": {
-          "size": 20,
-          "page": 1
-        }
-      }
-    }
-    const result = await requestRest<Result<ConnectionStatus>>(path, { method: "POST", body: JSON.stringify(query) });
-    return result && result["data-provider:output"] && result["data-provider:output"].data && result["data-provider:output"].data.map(ne => ({
-      status: ne.status
-    })) || null;
-  }
-
-  public async getAllWebUriExtensionsForNetworkElementListAsync(neList: string[]): Promise<(guiCutThrough)[]> {
-    const path = `/rests/operations/data-provider:read-gui-cut-through-entry`;
-    let webUriList: guiCutThrough[] = []
-    const query = {
-      "data-provider:input": {
-        "filter": [{
-          "property": "id",
-          "filtervalues": neList
-        }],
-        "pagination": {
-          "size": 20,
-          "page": 1
-        }
-      }
-    }
-
-    const result = await requestRest<Result<guiCutThrough>>(path, { method: "POST", body: JSON.stringify(query) });
-    const resultData = result && result["data-provider:output"] && result["data-provider:output"].data;
-    neList.forEach(nodeId => {
-      let entryNotFound = true;
-      if (resultData) {
-        const BreakException = {};
-        try {
-          resultData.forEach(entry => {
-            if (entry.id == nodeId) {
-              entryNotFound = false;
-              if (entry.weburi) {
-                webUriList.push({ id: nodeId, weburi: entry.weburi });
-              } else {
-                webUriList.push({ id: nodeId, weburi: undefined });
-              }
-              throw BreakException;
-            }
-          });
-        } catch (e) {}
-      }
-      if (entryNotFound)
-        webUriList.push({ id: nodeId, weburi: undefined });
-    });
-    return webUriList;
-  }
-
-  //  public async getAllWebUriExtensionsForNetworkElementListAsync(ne: string[]): Promise<(guiCutThrough)[] | null> {
-
-  //   let promises: any[] = [];
-  //   let webUris: guiCutThrough[] = []
-
-  //   ne.forEach(nodeId => {
-  //     const path = this.getAllWebUriExtensionsForNetworkElementListUri(nodeId);
-
-  // // add search request to array
-  //     promises.push(requestRest<any>(path, { method: "GET" })
-  //       .then(result => {
-  //         if (result != null && result['core-model:network-element'] && result['core-model:network-element'].extension) {
-  //           const webUri = result['core-model:network-element'].extension.find((item: any) => item['value-name'] === "webUri")
-  //           if (webUri) {
-  //             webUris.push({ weburi: webUri.value, id: nodeId });
-  //           } else {
-  //             webUris.push({ weburi: undefined, id: nodeId });
-  //           }
-  //         } else {
-  //           webUris.push({ weburi: undefined, id: nodeId });
-  //         }
-  //       })
-  //       .catch(error => {
-  //         webUris.push({ weburi: undefined, id: nodeId });
-  //       }))
-  //   })
-  //   // wait until all promises are done and return weburis
-  //   return Promise.all(promises).then(result => { return webUris });
-  // }
-
-}
-
-
-
-export const connectService = new ConnectService();
+   public async updateNetworkElement(element: UpdateNetworkElement): Promise<NetworkElementConnection | null> {
+     const path = this.getNetworkElementConnectDataProviderUri("update");
+     const result = await requestRest<NetworkElementConnection>(path, {
+       method: "POST", body: JSON.stringify(convertPropertyNames({ "data-provider:input": element }, replaceUpperCase))
+     });
+     return result || null;
+   }
+   /**
+     * Deletes a network element.
+     */
+   public async deleteNetworkElement(element: UpdateNetworkElement): Promise<NetworkElementConnection | null> {
+     const query = {
+       "id": element.id
+     };
+     const path = this.getNetworkElementConnectDataProviderUri("delete");
+     const result = await requestRest<NetworkElementConnection>(path, {
+       method: "POST", body: JSON.stringify(convertPropertyNames({ "data-provider:input": query }, replaceUpperCase))
+     });
+     return result || null;
+   }
+   /** Mounts network element. */
+   public async mountNetworkElement(networkElement: NetworkElementConnection): Promise<boolean> {
+     const path = this.getNetworkElementUri(networkElement.nodeId);
+     const mountXml = [
+       '<node xmlns="urn:TBD:params:xml:ns:yang:network-topology">',
+       `<node-id>${networkElement.nodeId}</node-id>`,
+       `<host xmlns="urn:opendaylight:netconf-node-topology">${networkElement.host}</host>`,
+       `<port xmlns="urn:opendaylight:netconf-node-topology">${networkElement.port}</port>`,
+       `<username xmlns="urn:opendaylight:netconf-node-topology">${networkElement.username}</username>`,
+       `<password xmlns="urn:opendaylight:netconf-node-topology">${networkElement.password}</password>`,
+       '  <tcp-only xmlns="urn:opendaylight:netconf-node-topology">false</tcp-only>',
+       '  <!-- non-mandatory fields with default values, you can safely remove these if you do not wish to override any of these values-->',
+       '  <reconnect-on-changed-schema xmlns="urn:opendaylight:netconf-node-topology">false</reconnect-on-changed-schema>',
+       '  <connection-timeout-millis xmlns="urn:opendaylight:netconf-node-topology">20000</connection-timeout-millis>',
+       '  <max-connection-attempts xmlns="urn:opendaylight:netconf-node-topology">100</max-connection-attempts>',
+       '  <between-attempts-timeout-millis xmlns="urn:opendaylight:netconf-node-topology">2000</between-attempts-timeout-millis>',
+       '  <sleep-factor xmlns="urn:opendaylight:netconf-node-topology">1.5</sleep-factor>',
+       '  <!-- keepalive-delay set to 0 turns off keepalives-->',
+       '  <keepalive-delay xmlns="urn:opendaylight:netconf-node-topology">120</keepalive-delay>',
+       '</node>'].join('');
+     try {
+       const result = await requestRest<string>(path, {
+         method: 'PUT',
+         headers: {
+           'Content-Type': 'application/xml',
+           'Accept': 'application/xml'
+         },
+         body: mountXml
+       });
+       // expect an empty answer
+       return result !== null;
+     } catch {
+       return false;
+     }
+   };
+   /** Unmounts a network element by its id. */
+   public async unmountNetworkElement(nodeId: string): Promise<boolean> {
+     const path = this.getNetworkElementUri(nodeId);
+     try {
+       const result = await requestRest<string>(path, {
+         method: 'DELETE',
+         headers: {
+           'Content-Type': 'application/xml',
+           'Accept': 'application/xml'
+         },
+       });
+       // expect an empty answer
+       return result !== null;
+     } catch {
+       return false;
+     }
+   };
+   /** Yang capabilities of the selected network elements. */
+   public async infoNetworkElement(nodeId: string): Promise<TopologyNode | null> {
+     const path = this.getNetworkElementUri(nodeId);
+     const topologyRequestPomise = requestRest<Topology>(path, { method: "GET" });
+     return topologyRequestPomise && topologyRequestPomise.then(result => {
+       return result && result["network-topology:node"] && result["network-topology:node"][0] || null;
+     });
+   }
+   /** Yang features of the selected network element module. */
+   public async infoNetworkElementFeatures(nodeId: string): Promise<Module[] | null | undefined> {
+     const path = this.getNetworkElementYangLibraryFeature(nodeId);
+     const topologyRequestPomise = requestRest<FeatureTopology>(path, { method: "GET" });
+     return topologyRequestPomise && topologyRequestPomise.then(result => {
+       const resultFinal = result && result['ietf-yang-library:yang-library']
+         && result["ietf-yang-library:yang-library"]["module-set"] &&
+         result["ietf-yang-library:yang-library"]["module-set"][0] &&
+         result["ietf-yang-library:yang-library"]["module-set"][0]['module'] || null;
+       return resultFinal;
+     });
+   }
+   /**
+    * Get the connection state of the network element.
+    */
+   public async getNetworkElementConnectionStatus(element: string): Promise<(ConnectionStatus)[] | null> {
+     const path = `/rests/operations/data-provider:read-network-element-connection-list`;
+     const query = {
+       "data-provider:input": {
+         "filter": [{
+           "property": "node-id",
+           "filtervalue": element
+         }],
+         "pagination": {
+           "size": 20,
+           "page": 1
+         }
+       }
+     }
+     const result = await requestRest<Result<ConnectionStatus>>(path, { method: "POST", body: JSON.stringify(query) });
+     return result && result["data-provider:output"] && result["data-provider:output"].data && result["data-provider:output"].data.map(ne => ({
+       status: ne.status
+     })) || null;
+   }
+   public async getAllWebUriExtensionsForNetworkElementListAsync(neList: string[]): Promise<(guiCutThrough)[]> {
+     const path = `/rests/operations/data-provider:read-gui-cut-through-entry`;
+     let webUriList: guiCutThrough[] = []
+     const query = {
+       "data-provider:input": {
+         "filter": [{
+           "property": "id",
+           "filtervalues": neList
+         }],
+         "pagination": {
+           "size": 20,
+           "page": 1
+         }
+       }
+     }
+     const result = await requestRest<Result<guiCutThrough>>(path, { method: "POST", body: JSON.stringify(query) });
+     const resultData = result && result["data-provider:output"] && result["data-provider:output"].data;
+     neList.forEach(nodeId => {
+       let entryNotFound = true;
+       if (resultData) {
+         const BreakException = {};
+         try {
+           resultData.forEach(entry => {
+             if (entry.id == nodeId) {
+               entryNotFound = false;
+               if (entry.weburi) {
+                 webUriList.push({ id: nodeId, weburi: entry.weburi });
+               } else {
+                 webUriList.push({ id: nodeId, weburi: undefined });
+               }
+               throw BreakException;
+             }
+           });
+         } catch (e) {}
+       }
+       if (entryNotFound)
+         webUriList.push({ id: nodeId, weburi: undefined });
+     });
+     return webUriList;
+   }
+   //  public async getAllWebUriExtensionsForNetworkElementListAsync(ne: string[]): Promise<(guiCutThrough)[] | null> {
+   //   let promises: any[] = [];
+   //   let webUris: guiCutThrough[] = []
+   //   ne.forEach(nodeId => {
+   //     const path = this.getAllWebUriExtensionsForNetworkElementListUri(nodeId);
+   // // add search request to array
+   //     promises.push(requestRest<any>(path, { method: "GET" })
+   //       .then(result => {
+   //         if (result != null && result['core-model:network-element'] && result['core-model:network-element'].extension) {
+   //           const webUri = result['core-model:network-element'].extension.find((item: any) => item['value-name'] === "webUri")
+   //           if (webUri) {
+   //             webUris.push({ weburi: webUri.value, id: nodeId });
+   //           } else {
+   //             webUris.push({ weburi: undefined, id: nodeId });
+   //           }
+   //         } else {
+   //           webUris.push({ weburi: undefined, id: nodeId });
+   //         }
+   //       })
+   //       .catch(error => {
+   //         webUris.push({ weburi: undefined, id: nodeId });
+   //       }))
+   //   })
+   //   // wait until all promises are done and return weburis
+   //   return Promise.all(promises).then(result => { return webUris });
+   // }
+ }
+ export const connectService = new ConnectService();
\ No newline at end of file