Add link calculation app

Add link calculation app to odlux

Issue-ID: CCSDK-2562
Signed-off-by: Aijana Schumann <aijana.schumann@highstreet-technologies.com>
Change-Id: Ifc0a5b2a8bb974dfd85d70a9f05990b1f11925a3
Signed-off-by: Aijana Schumann <aijana.schumann@highstreet-technologies.com>
diff --git a/sdnr/wt/odlux/framework/pom.xml b/sdnr/wt/odlux/framework/pom.xml
index db42ef1..a7c81eb 100644
--- a/sdnr/wt/odlux/framework/pom.xml
+++ b/sdnr/wt/odlux/framework/pom.xml
@@ -46,7 +46,7 @@
     <properties>
         <buildtime>${maven.build.timestamp}</buildtime>
         <distversion>ONAP Frankfurt (Neon, mdsal ${odl.mdsal.version})</distversion>
-        <buildno>57.3e1d5cf(20/08/11)</buildno>
+        <buildno>62.ad364be(20/08/21)</buildno>
         <odlux.version>ONAP SDN-R | ONF Wireless for ${distversion} - Build: ${buildtime} ${buildno} ${project.version}</odlux.version>
     </properties>
 
diff --git a/sdnr/wt/odlux/framework/src/components/material-table/index.tsx b/sdnr/wt/odlux/framework/src/components/material-table/index.tsx
index 3dfbe0b..c5be819 100644
--- a/sdnr/wt/odlux/framework/src/components/material-table/index.tsx
+++ b/sdnr/wt/odlux/framework/src/components/material-table/index.tsx
@@ -234,7 +234,7 @@
                       }}
                       role="checkbox"
                       aria-checked={isSelected}
-                      aria-label={`${(this.props.tableId ? this.props.tableId : 'table')}-row-${(index + 1)}`}
+                      aria-label={`${(this.props.tableId ? this.props.tableId : 'table')}-row`}
                       tabIndex={-1}
                       key={entryId}
                       selected={isSelected}
@@ -284,10 +284,10 @@
           rowsPerPage={rowsPerPage}
           page={page}
           backIconButtonProps={{
-            'aria-label': 'Previous Page',
+            'aria-label': 'previous-page',
           }}
           nextIconButtonProps={{
-            'aria-label': 'Next Page',
+            'aria-label': 'next-page',
           }}
           onChangePage={this.onHandleChangePage}
           onChangeRowsPerPage={this.onHandleChangeRowsPerPage}
diff --git a/sdnr/wt/odlux/framework/src/components/material-table/tableFilter.tsx b/sdnr/wt/odlux/framework/src/components/material-table/tableFilter.tsx
index 464350a..76f778e 100644
--- a/sdnr/wt/odlux/framework/src/components/material-table/tableFilter.tsx
+++ b/sdnr/wt/odlux/framework/src/components/material-table/tableFilter.tsx
@@ -74,11 +74,11 @@
                 ? null
                 : (col.type === ColumnType.boolean)
                   ? <Select className={classes.input} aria-label={col.title ? (col.title as string).toLowerCase() + ' filter' : `${ind + 1}-filter`} value={filter[col.property] !== undefined ? filter[col.property] : ''} onChange={this.createFilterHandler(col.property)} inputProps={{ name: `${col.property}-bool`, id: `${col.property}-bool` }} >
-                    <MenuItem value={undefined} >
+                    <MenuItem value={undefined} aria-label="none-value" >
                       <em>None</em>
                     </MenuItem>
-                    <MenuItem value={true as any as string}>{col.labels ? col.labels["true"] : "true"}</MenuItem>
-                    <MenuItem value={false as any as string}>{col.labels ? col.labels["false"] : "false"}</MenuItem>
+                    <MenuItem aria-label="true-value" value={true as any as string}>{col.labels ? col.labels["true"] : "true"}</MenuItem>
+                    <MenuItem aria-label="false-value" value={false as any as string}>{col.labels ? col.labels["false"] : "false"}</MenuItem>
                   </Select>
                   : <Input className={classes.input} inputProps={{ 'aria-label': col.title ? (col.title as string).toLowerCase() + ' filter' : `${ind + 1}-filter` }} value={filter[col.property] || ''} onChange={this.createFilterHandler(col.property)} />}
             </TableCell>
diff --git a/sdnr/wt/odlux/framework/src/components/material-ui/listItemLink.tsx b/sdnr/wt/odlux/framework/src/components/material-ui/listItemLink.tsx
index 1a7e58f..23bad66 100644
--- a/sdnr/wt/odlux/framework/src/components/material-ui/listItemLink.tsx
+++ b/sdnr/wt/odlux/framework/src/components/material-ui/listItemLink.tsx
@@ -1,20 +1,20 @@
-/**
- * ============LICENSE_START========================================================================
- * ONAP : ccsdk feature sdnr wt odlux
- * =================================================================================================
- * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved.
- * =================================================================================================
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
- * in compliance with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software distributed under the License
- * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
- * or implied. See the License for the specific language governing permissions and limitations under
- * the License.
- * ============LICENSE_END==========================================================================
- */
+/**

+ * ============LICENSE_START========================================================================

+ * ONAP : ccsdk feature sdnr wt odlux

+ * =================================================================================================

+ * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved.

+ * =================================================================================================

+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except

+ * in compliance with the License. You may obtain a copy of the License at

+ *

+ * http://www.apache.org/licenses/LICENSE-2.0

+ *

+ * Unless required by applicable law or agreed to in writing, software distributed under the License

+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express

+ * or implied. See the License for the specific language governing permissions and limitations under

+ * the License.

+ * ============LICENSE_END==========================================================================

+ */

 import * as React from 'react';

 import { NavLink, Link, Route } from 'react-router-dom';

 

@@ -50,7 +50,7 @@
             : null

           }

         { typeof Primary === 'string'

-          ? <ListItemText primary={ Primary } style={{ padding: 0 }} /> 

+          ? <ListItemText aria-label={"link-to-"+Primary.toLowerCase()} primary={ Primary } style={{ padding: 0 }} /> 

           : <Primary />

           }

         </ListItem>

diff --git a/sdnr/wt/odlux/framework/src/components/navigationMenu.tsx b/sdnr/wt/odlux/framework/src/components/navigationMenu.tsx
index 620abd7..790677a 100644
--- a/sdnr/wt/odlux/framework/src/components/navigationMenu.tsx
+++ b/sdnr/wt/odlux/framework/src/components/navigationMenu.tsx
@@ -120,6 +120,11 @@
     }

   })

 

+  React.useEffect(()=>{

+    // trigger a resize if menu changed in case elements have to re-arrange

+    window.dispatchEvent(new Event('menu-resized'));

+  }, [isOpen])

+

   return (

     <Drawer

       variant="permanent"

diff --git a/sdnr/wt/odlux/framework/src/components/titleBar.tsx b/sdnr/wt/odlux/framework/src/components/titleBar.tsx
index 7168ff4..49c0966 100644
--- a/sdnr/wt/odlux/framework/src/components/titleBar.tsx
+++ b/sdnr/wt/odlux/framework/src/components/titleBar.tsx
@@ -101,8 +101,8 @@
     // create notificationInfo element

     const notificationInfo = state.framework.applicationState.isWebsocketAvailable != undefined ?

       (state.framework.applicationState.isWebsocketAvailable ?

-        <Typography variant="body1" className={classes.notificationInfo}>Notifications <FontAwesomeIcon className={classes.connected} icon={faDotCircle} />  |</Typography> : <Typography variant="body1" className={classes.notificationInfo}>Notifications <FontAwesomeIcon className={classes.notConnected} icon={faBan} /> |</Typography>)

-      : <Typography variant="body1" className={classes.notificationInfo}>Notifications N/A |</Typography>;

+        <Typography aria-label="notifications-are-active" variant="body1" className={classes.notificationInfo}>Notifications <FontAwesomeIcon className={classes.connected} icon={faDotCircle} />  |</Typography> : <Typography aria-label="notifications-are-inactive" variant="body1" className={classes.notificationInfo}>Notifications <FontAwesomeIcon className={classes.notConnected} icon={faBan} /> |</Typography>)

+      : <Typography variant="body1" aria-label="notifications-are-not-available" className={classes.notificationInfo}>Notifications N/A |</Typography>;

 

 

     // add notificationInfo element before help

diff --git a/sdnr/wt/odlux/framework/src/services/notificationService.ts b/sdnr/wt/odlux/framework/src/services/notificationService.ts
index c90da09..4bcc05c 100644
--- a/sdnr/wt/odlux/framework/src/services/notificationService.ts
+++ b/sdnr/wt/odlux/framework/src/services/notificationService.ts
@@ -1,193 +1,193 @@
-/**

- * ============LICENSE_START========================================================================

- * ONAP : ccsdk feature sdnr wt odlux

- * =================================================================================================

- * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved.

- * =================================================================================================

- * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except

- * in compliance with the License. You may obtain a copy of the License at

- *

- * http://www.apache.org/licenses/LICENSE-2.0

- *

- * Unless required by applicable law or agreed to in writing, software distributed under the License

- * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express

- * or implied. See the License for the specific language governing permissions and limitations under

- * the License.

- * ============LICENSE_END==========================================================================

- */

-import * as X2JS from 'x2js';

-import { ApplicationStore } from '../store/applicationStore';

-import { SetWebsocketAction } from '../actions/websocketAction';

-

-const socketUrl = [location.protocol === 'https:' ? 'wss://' : 'ws://', 'admin', ':', 'admin', '@', location.hostname, ':', location.port, '/websocket'].join('');

-const subscriptions: { [scope: string]: SubscriptionCallback[] } = {};

-let socketReady: Promise<WebSocket>;

-let userLoggedOut = false;

-let wasWebsocketConnectionEstablished: undefined | boolean;

-let applicationStore: ApplicationStore | null;

-

-

-export interface IFormatedMessage {

-  notifType: string | null;

-  time: string;

-}

-

-export type SubscriptionCallback<TMessage extends IFormatedMessage = IFormatedMessage> = (msg: TMessage) => void;

-

-function formatData(event: MessageEvent): IFormatedMessage | undefined {

-

-  var x2js = new X2JS();

-  var jsonObj: { [key: string]: IFormatedMessage } = x2js.xml2js(event.data);

-  if (jsonObj && typeof (jsonObj) === 'object') {

-

-    const notifType = Object.keys(jsonObj)[0];

-    const formated = jsonObj[notifType];

-    formated.notifType = notifType;

-    formated.time = new Date().toISOString();

-    return formated;

-  }

-  return undefined;

-

-}

-

-export function subscribe<TMessage extends IFormatedMessage = IFormatedMessage>(scope: string | string[], callback: SubscriptionCallback<TMessage>): boolean {

-  const scopes = scope instanceof Array ? scope : [scope];

-

-  // send all new scopes to subscribe

-  const newScopesToSubscribe: string[] = scopes.reduce((acc: string[], cur: string) => {

-    const currentCallbacks = subscriptions[cur];

-    if (currentCallbacks) {

-      if (!currentCallbacks.some(c => c === callback)) {

-        currentCallbacks.push(callback);

-      }

-    } else {

-      subscriptions[cur] = [callback];

-      acc.push(cur);

-    }

-    return acc;

-  }, []);

-

-  if (newScopesToSubscribe.length === 0) {

-    return true;

-  }

-

-  return true;

-}

-

-

-export function unsubscribe<TMessage extends IFormatedMessage = IFormatedMessage>(scope: string | string[], callback: SubscriptionCallback<TMessage>): Promise<boolean> {

-  return socketReady.then((notificationSocket) => {

-    const scopes = scope instanceof Array ? scope : [scope];

-    scopes.forEach(s => {

-      const callbacks = subscriptions[s];

-      const index = callbacks && callbacks.indexOf(callback);

-      if (index > -1) {

-        callbacks.splice(index, 1);

-      }

-      if (callbacks.length === 0) {

-        subscriptions[s] === undefined;

-      }

-    });

-

-    // send a subscription to all active scopes

-    const scopesToSubscribe = Object.keys(subscriptions);

-    if (notificationSocket.readyState === notificationSocket.OPEN) {

-      const data = {

-        'data': 'scopes',

-        'scopes': scopesToSubscribe

-      };

-      notificationSocket.send(JSON.stringify(data));

-      return true;

-    }

-    return false;

-  });

-}

-

-export const startNotificationService = (store: ApplicationStore) => {

-  applicationStore = store;

-}

-

-const connect = (): Promise<WebSocket> => {

-  return new Promise((resolve, reject) => {

-    const notificationSocket = new WebSocket(socketUrl);

-

-    notificationSocket.onmessage = (event) => {

-      // process received event

-      if (typeof event.data === 'string') {

-        const formated = formatData(event);

-        if (formated && formated.notifType) {

-          const callbacks = subscriptions[formated.notifType];

-          if (callbacks) {

-            callbacks.forEach(cb => {

-              // ensure all callbacks will be called

-              try {

-                return cb(formated);

-              } catch (reason) {

-                console.error(reason);

-              }

-            });

-          }

-        }

-      }

-    };

-

-    notificationSocket.onerror = function (error) {

-      console.log("Socket error:");

-      console.log(error);

-      reject("Socket error: " + error);

-      if (applicationStore) {

-        applicationStore.dispatch(new SetWebsocketAction(false));

-      }

-    };

-

-    notificationSocket.onopen = function (event) {

-      if (applicationStore) {

-        applicationStore.dispatch(new SetWebsocketAction(true));

-      }

-      console.log("Socket connection opened.");

-      resolve(notificationSocket);

-

-      // send a subscription to all active scopes

-      const scopesToSubscribe = Object.keys(subscriptions);

-      if (notificationSocket.readyState === notificationSocket.OPEN) {

-        const data = {

-          'data': 'scopes',

-          'scopes': scopesToSubscribe

-        };

-        notificationSocket.send(JSON.stringify(data));

-      };

-    };

-

-    notificationSocket.onclose = function (event) {

-      console.log("socket connection closed");

-      if (applicationStore) {

-        applicationStore.dispatch(new SetWebsocketAction(false));

-      }

-      if (!userLoggedOut) {

-        socketReady = connect();

-      }

-    };

-  });

-}

-

-

-

-

-export const startWebsocketSession = () => {

-  socketReady = connect();

-  userLoggedOut = false;

-}

-

-export const endWebsocketSession = () => {

-  if (socketReady) {

-    socketReady.then(websocket => {

-      websocket.close();

-      userLoggedOut = true;

-    });

-  }

-

-}

-

-

-

-

+/**
+ * ============LICENSE_START========================================================================
+ * ONAP : ccsdk feature sdnr wt odlux
+ * =================================================================================================
+ * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved.
+ * =================================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ * ============LICENSE_END==========================================================================
+ */
+import * as X2JS from 'x2js';
+import { ApplicationStore } from '../store/applicationStore';
+import { SetWebsocketAction } from '../actions/websocketAction';
+
+const socketUrl = [location.protocol === 'https:' ? 'wss://' : 'ws://', 'admin', ':', 'admin', '@', location.hostname, ':', location.port, '/websocket'].join('');
+const subscriptions: { [scope: string]: SubscriptionCallback[] } = {};
+let socketReady: Promise<WebSocket>;
+let userLoggedOut = false;
+let wasWebsocketConnectionEstablished: undefined | boolean;
+let applicationStore: ApplicationStore | null;
+
+
+export interface IFormatedMessage {
+  notifType: string | null;
+  time: string;
+}
+
+export type SubscriptionCallback<TMessage extends IFormatedMessage = IFormatedMessage> = (msg: TMessage) => void;
+
+function formatData(event: MessageEvent): IFormatedMessage | undefined {
+
+  var x2js = new X2JS();
+  var jsonObj: { [key: string]: IFormatedMessage } = x2js.xml2js(event.data);
+  if (jsonObj && typeof (jsonObj) === 'object') {
+
+    const notifType = Object.keys(jsonObj)[0];
+    const formated = jsonObj[notifType];
+    formated.notifType = notifType;
+    formated.time = new Date().toISOString();
+    return formated;
+  }
+  return undefined;
+
+}
+
+export function subscribe<TMessage extends IFormatedMessage = IFormatedMessage>(scope: string | string[], callback: SubscriptionCallback<TMessage>): boolean {
+  const scopes = scope instanceof Array ? scope : [scope];
+
+  // send all new scopes to subscribe
+  const newScopesToSubscribe: string[] = scopes.reduce((acc: string[], cur: string) => {
+    const currentCallbacks = subscriptions[cur];
+    if (currentCallbacks) {
+      if (!currentCallbacks.some(c => c === callback)) {
+        currentCallbacks.push(callback);
+      }
+    } else {
+      subscriptions[cur] = [callback];
+      acc.push(cur);
+    }
+    return acc;
+  }, []);
+
+  if (newScopesToSubscribe.length === 0) {
+    return true;
+  }
+
+  return true;
+}
+
+
+export function unsubscribe<TMessage extends IFormatedMessage = IFormatedMessage>(scope: string | string[], callback: SubscriptionCallback<TMessage>): Promise<boolean> {
+  return socketReady.then((notificationSocket) => {
+    const scopes = scope instanceof Array ? scope : [scope];
+    scopes.forEach(s => {
+      const callbacks = subscriptions[s];
+      const index = callbacks && callbacks.indexOf(callback);
+      if (index > -1) {
+        callbacks.splice(index, 1);
+      }
+      if (callbacks.length === 0) {
+        subscriptions[s] === undefined;
+      }
+    });
+
+    // send a subscription to all active scopes
+    const scopesToSubscribe = Object.keys(subscriptions);
+    if (notificationSocket.readyState === notificationSocket.OPEN) {
+      const data = {
+        'data': 'scopes',
+        'scopes': scopesToSubscribe
+      };
+      notificationSocket.send(JSON.stringify(data));
+      return true;
+    }
+    return false;
+  });
+}
+
+export const startNotificationService = (store: ApplicationStore) => {
+  applicationStore = store;
+}
+
+const connect = (): Promise<WebSocket> => {
+  return new Promise((resolve, reject) => {
+    const notificationSocket = new WebSocket(socketUrl);
+
+    notificationSocket.onmessage = (event) => {
+      // process received event
+      if (typeof event.data === 'string') {
+        const formated = formatData(event);
+        if (formated && formated.notifType) {
+          const callbacks = subscriptions[formated.notifType];
+          if (callbacks) {
+            callbacks.forEach(cb => {
+              // ensure all callbacks will be called
+              try {
+                return cb(formated);
+              } catch (reason) {
+                console.error(reason);
+              }
+            });
+          }
+        }
+      }
+    };
+
+    notificationSocket.onerror = function (error) {
+      console.log("Socket error:");
+      console.log(error);
+      reject("Socket error: " + error);
+      if (applicationStore) {
+        applicationStore.dispatch(new SetWebsocketAction(false));
+      }
+    };
+
+    notificationSocket.onopen = function (event) {
+      if (applicationStore) {
+        applicationStore.dispatch(new SetWebsocketAction(true));
+      }
+      console.log("Socket connection opened.");
+      resolve(notificationSocket);
+
+      // send a subscription to all active scopes
+      const scopesToSubscribe = Object.keys(subscriptions);
+      if (notificationSocket.readyState === notificationSocket.OPEN) {
+        const data = {
+          'data': 'scopes',
+          'scopes': scopesToSubscribe
+        };
+        notificationSocket.send(JSON.stringify(data));
+      };
+    };
+
+    notificationSocket.onclose = function (event) {
+      console.log("socket connection closed");
+      if (applicationStore) {
+        applicationStore.dispatch(new SetWebsocketAction(false));
+      }
+      if (!userLoggedOut) {
+        socketReady = connect();
+      }
+    };
+  });
+}
+
+
+
+
+export const startWebsocketSession = () => {
+  socketReady = connect();
+  userLoggedOut = false;
+}
+
+export const endWebsocketSession = () => {
+  if (socketReady) {
+    socketReady.then(websocket => {
+      websocket.close();
+      userLoggedOut = true;
+    });
+  }
+
+}
+
+
+
+
diff --git a/sdnr/wt/odlux/framework/src/views/about.tsx b/sdnr/wt/odlux/framework/src/views/about.tsx
index c4a5488..f97d6ff 100644
--- a/sdnr/wt/odlux/framework/src/views/about.tsx
+++ b/sdnr/wt/odlux/framework/src/views/about.tsx
@@ -40,11 +40,23 @@
     this.textarea = React.createRef();
     this.loadAboutContent();
   }
+  private getMarkOdluxVersionMarkdownTable(data:{version:string,build:string}|null|undefined):string{
+    if(!data) {
+      return "";
+    }
+    return `| | |\n| --- | --- |\n| Version | ${data.version} |\n| Build timestamp | ${data.build}|`
+  }
   private loadAboutContent(): void {
-    requestRestExt<string>('/about').then((response) => {
+    const baseUri = window.location.pathname.substring(0,window.location.pathname.lastIndexOf("/")+1);
+    const p1 = requestRestExt<string>('/about');
+    const p2 = requestRestExt<{version:string,build:string}>(`${baseUri}version.json`);
+    Promise.all([p1,p2]).then((responses) => {
+      const response = responses[0];
+      const response2 = responses[1];    
       const content = response.status == 200 ? response.data : `${response.status} ${response.message}` || "Server error";
+      const content2 = `\n## ODLUX Version Info\n`+(response2.status == 200 ? this.getMarkOdluxVersionMarkdownTable(response2.data) : `${response2.status} ${response2.message}` || "ODLUX Server error");
       const loadedSucessfully = response.status == 200 ? true : false;
-      this.setState({ content: content || null, isContentLoadedSucessfully: loadedSucessfully });
+      this.setState({ content: (content + content2) || null, isContentLoadedSucessfully: loadedSucessfully });
     }).catch((error) => {
       this.setState({ content: error })
     })
diff --git a/sdnr/wt/odlux/framework/src/views/login.tsx b/sdnr/wt/odlux/framework/src/views/login.tsx
index 30b9c85..b06cf76 100644
--- a/sdnr/wt/odlux/framework/src/views/login.tsx
+++ b/sdnr/wt/odlux/framework/src/views/login.tsx
@@ -142,6 +142,7 @@
                 label="Remember me"
               />
               <Button
+                aria-label="login-button"
                 type="submit"
                 fullWidth
                 variant="contained"
diff --git a/sdnr/wt/odlux/framework/webpack.config.js b/sdnr/wt/odlux/framework/webpack.config.js
index c7ef72e..ad5b4cc 100644
--- a/sdnr/wt/odlux/framework/webpack.config.js
+++ b/sdnr/wt/odlux/framework/webpack.config.js
@@ -171,7 +171,11 @@
           new webpack.WatchIgnorePlugin([

             /css\.d\.ts$/,

             /less\.d\.ts$/

-          ])

+          ]),

+          new CopyWebpackPlugin([{

+            from: './assets/version.json',

+            to: './version.json'

+          }])

         ]

     ],

 

@@ -210,6 +214,10 @@
           target: "http://10.20.6.29:48181",

           secure: false

         },

+        "/about": {

+          target: "http://10.20.6.29:48181",

+          secure: false

+        },

         "/websocket": {

           target: "http://10.20.6.29:48181",

           ws: true,