update odlux for notification change 05/120305/1
authorMichael DÜrre <michael.duerre@highstreet-technologies.com>
Thu, 8 Apr 2021 05:27:18 +0000 (07:27 +0200)
committerMichael DÜrre <michael.duerre@highstreet-technologies.com>
Thu, 8 Apr 2021 05:27:28 +0000 (07:27 +0200)
update due new notification protocol

Issue-ID: CCSDK-3253
Signed-off-by: Michael DÜrre <michael.duerre@highstreet-technologies.com>
Change-Id: Iad65459fdc18603cd1ddbd97bb2211308744bd8b

74 files changed:
sdnr/wt/odlux/apps/apiDemo/package.json
sdnr/wt/odlux/apps/configurationApp/package.json
sdnr/wt/odlux/apps/configurationApp/policies.json
sdnr/wt/odlux/apps/configurationApp/src/actions/deviceActions.ts
sdnr/wt/odlux/apps/configurationApp/src/components/uiElementReference.tsx
sdnr/wt/odlux/apps/configurationApp/src/handlers/connectedNetworkElementsHandler.ts
sdnr/wt/odlux/apps/configurationApp/src/handlers/viewDescriptionHandler.ts
sdnr/wt/odlux/apps/configurationApp/src/models/uiModels.ts
sdnr/wt/odlux/apps/configurationApp/src/pluginConfiguration.tsx
sdnr/wt/odlux/apps/configurationApp/src/services/restServices.ts
sdnr/wt/odlux/apps/configurationApp/src/views/configurationApplication.tsx
sdnr/wt/odlux/apps/configurationApp/src/views/networkElementSelector.tsx
sdnr/wt/odlux/apps/configurationApp/src/yang/yangParser.ts
sdnr/wt/odlux/apps/connectApp/package.json
sdnr/wt/odlux/apps/connectApp/policies.json [new file with mode: 0644]
sdnr/wt/odlux/apps/connectApp/src/actions/commonNetworkElementsActions.ts
sdnr/wt/odlux/apps/connectApp/src/components/connectionStatusLog.tsx
sdnr/wt/odlux/apps/connectApp/src/components/networkElements.tsx
sdnr/wt/odlux/apps/connectApp/src/components/refreshConnectionStatusLogDialog.tsx [new file with mode: 0644]
sdnr/wt/odlux/apps/connectApp/src/components/refreshNetworkElementsDialog.tsx [new file with mode: 0644]
sdnr/wt/odlux/apps/connectApp/src/handlers/connectAppRootHandler.ts
sdnr/wt/odlux/apps/connectApp/src/handlers/networkElementsHandler.ts
sdnr/wt/odlux/apps/connectApp/src/index.html
sdnr/wt/odlux/apps/connectApp/src/models/guiCutTrough.ts
sdnr/wt/odlux/apps/connectApp/src/models/networkElementConnection.ts
sdnr/wt/odlux/apps/connectApp/src/pluginConnect.tsx
sdnr/wt/odlux/apps/connectApp/src/services/connectService.ts
sdnr/wt/odlux/apps/connectApp/src/views/connectView.tsx
sdnr/wt/odlux/apps/connectApp/webpack.config.js
sdnr/wt/odlux/apps/demoApp/package.json
sdnr/wt/odlux/apps/demoApp/src/index.html
sdnr/wt/odlux/apps/eventLogApp/package.json
sdnr/wt/odlux/apps/eventLogApp/src/components/refreshEventLogDialog.tsx [new file with mode: 0644]
sdnr/wt/odlux/apps/eventLogApp/src/views/eventLog.tsx
sdnr/wt/odlux/apps/faultApp/package.json
sdnr/wt/odlux/apps/faultApp/src/components/refreshAlarmLogDialog.tsx [new file with mode: 0644]
sdnr/wt/odlux/apps/faultApp/src/components/refreshCurrentProblemsDialog.tsx [new file with mode: 0644]
sdnr/wt/odlux/apps/faultApp/src/views/faultApplication.tsx
sdnr/wt/odlux/apps/helpApp/package.json
sdnr/wt/odlux/apps/inventoryApp/package.json
sdnr/wt/odlux/apps/inventoryApp/src/components/refreshInventoryDialog.tsx [new file with mode: 0644]
sdnr/wt/odlux/apps/inventoryApp/src/index.html
sdnr/wt/odlux/apps/inventoryApp/src/views/dashboard.tsx
sdnr/wt/odlux/apps/inventoryApp/src/views/treeview.tsx
sdnr/wt/odlux/apps/linkCalculationApp/package.json
sdnr/wt/odlux/apps/linkCalculationApp/src/views/linkCalculationComponent.tsx
sdnr/wt/odlux/apps/maintenanceApp/package.json
sdnr/wt/odlux/apps/maintenanceApp/src/components/refreshMaintenanceEntries.tsx [new file with mode: 0644]
sdnr/wt/odlux/apps/maintenanceApp/src/views/maintenenceView.tsx
sdnr/wt/odlux/apps/mediatorApp/package.json
sdnr/wt/odlux/apps/mediatorApp/src/components/refreshMediatorDialog.tsx [new file with mode: 0644]
sdnr/wt/odlux/apps/mediatorApp/src/views/mediatorServerSelection.tsx
sdnr/wt/odlux/apps/minimumApp/package.json
sdnr/wt/odlux/apps/networkMapApp/package.json
sdnr/wt/odlux/apps/networkMapApp/src/components/details/linkDetails.tsx
sdnr/wt/odlux/apps/networkMapApp/src/handlers/mapReducer.ts
sdnr/wt/odlux/apps/networkMapApp/src/index.html
sdnr/wt/odlux/apps/performanceHistoryApp/package.json
sdnr/wt/odlux/framework/package.json
sdnr/wt/odlux/framework/pom.xml
sdnr/wt/odlux/framework/src/app.tsx
sdnr/wt/odlux/framework/src/components/material-table/index.tsx
sdnr/wt/odlux/framework/src/components/material-table/tableToolbar.tsx
sdnr/wt/odlux/framework/src/components/material-table/utilities.ts
sdnr/wt/odlux/framework/src/handlers/applicationStateHandler.ts
sdnr/wt/odlux/framework/src/services/notificationService.ts
sdnr/wt/odlux/framework/src/services/restService.ts
sdnr/wt/odlux/framework/src/views/about.tsx
sdnr/wt/odlux/framework/tsconfig.json
sdnr/wt/odlux/framework/webpack.config.js
sdnr/wt/odlux/installer/pom.xml
sdnr/wt/odlux/odlux.properties
sdnr/wt/odlux/package.json
sdnr/wt/odlux/yarn.lock

index 230e86a..3805c02 100644 (file)
     "@odlux/framework": "*"
   },
   "peerDependencies": {
-    "@types/react": "16.9.19",
-    "@types/react-dom": "16.9.5",
-    "@types/react-router-dom": "4.3.1",
+    "@types/react": "17.0.3",
+    "@types/react-dom": "17.0.2",
+    "@types/react-router-dom": "5.1.7",
     "@material-ui/core": "4.11.0",
     "@material-ui/icons": "4.9.1",
     "@types/classnames": "2.2.6",
     "@types/flux": "3.1.8",
     "@types/jquery": "3.3.10",
     "jquery": "3.3.1",
-    "react": "16.12.0",
-    "react-dom": "16.12.0",
-    "react-router-dom": "4.3.1"
+    "react": "17.0.1",
+    "react-dom": "17.0.1",
+    "react-router-dom": "5.2.0"
   }
 }
\ No newline at end of file
index d1ab86d..16588cd 100644 (file)
     "@odlux/framework": "*"
   },
   "peerDependencies": {
-    "@types/react": "16.9.19",
-    "@types/react-dom": "16.9.5",
-    "@types/react-router-dom": "4.3.1",
+    "@types/react": "17.0.3",
+    "@types/react-dom": "17.0.2",
+    "@types/react-router-dom": "5.1.7",
     "@material-ui/core": "4.11.0",
     "@material-ui/icons": "4.9.1",
     "@types/classnames": "2.2.6",
     "@types/flux": "3.1.8",
     "@types/jquery": "3.3.10",
     "jquery": "3.3.1",
-    "react": "16.12.0",
-    "react-dom": "16.12.0",
-    "react-router-dom": "4.3.1"
+    "react": "17.0.1",
+    "react-dom": "17.0.1",
+    "react-router-dom": "5.2.0"
   }
 }
\ No newline at end of file
index cd9e9fc..91a0abf 100644 (file)
@@ -1,12 +1,12 @@
 [
   {
-    "path": "/**/operations/cluster-admin**",
-    "actions": {
-      "get": true,
-      "post": true,
-      "put": true,
-      "patch": true,
-      "delete": true
+    "path": "/rests/**/node=Sim2230**",
+    "methods": {
+      "get": false,
+      "post": false,
+      "put": false,
+      "delete": false,
+      "patch": false
     }
   }
 ]
\ No newline at end of file
index d628385..f80fbfc 100644 (file)
@@ -9,6 +9,7 @@ import { restService } from "../services/restServices";
 import { YangParser } from "../yang/yangParser";
 import { Module } from "../models/yang";
 import { ViewSpecification, ViewElement, isViewElementReference, isViewElementList, isViewElementObjectOrList, isViewElementRpc, isViewElementChoise, ViewElementChoiseCase, ViewElementString } from "../models/uiModels";
+import { exception } from 'console';
 
 export class EnableValueSelector extends Action {
   constructor(public listSpecification: ViewSpecification, public listData: any[], public keyProperty: string, public onValueSelected : (value: any) => void ) {
@@ -333,6 +334,7 @@ export const updateViewActionAsyncCreator = (vPath: string) => async (dispatch:
                 const refView : ViewSpecification  = {
                     id: "-1",
                     canEdit: false,
+                    config: false,
                     language: "en-US",
                     elements: {
                         [viewElement.key!] : { 
@@ -441,11 +443,13 @@ export const updateViewActionAsyncCreator = (vPath: string) => async (dispatch:
     // create display specification
     const ds: DisplaySpecification = viewElement! && viewElement!.uiType === "rpc"
       ? {
+        dataPath,
         displayMode: DisplayModeType.displayAsRPC,
         inputViewSpecification: inputViewSpecification && resolveViewDescription(defaultNS, vPath, inputViewSpecification),
         outputViewSpecification: outputViewSpecification && resolveViewDescription(defaultNS, vPath, outputViewSpecification),
       }
       : {
+        dataPath,
         displayMode: extractList ? DisplayModeType.displayAsList : DisplayModeType.displayAsObject,
         viewSpecification: resolveViewDescription(defaultNS, vPath, viewSpecification),
         keyProperty: isViewElementList(viewElement!) && viewElement.key || undefined,
@@ -517,6 +521,35 @@ export const updateDataActionAsyncCreator = (vPath: string, data: any) => async
       }
     }
 
+    // remove read-only elements
+    const removeReadOnlyElements = (viewSpecification: ViewSpecification, isList: boolean, data: any) => {
+      if (isList) {
+        return data.map((elm : any) => removeReadOnlyElements(viewSpecification, false, elm));
+      } else {
+        return Object.keys(data).reduce<{[key: string]: any}>((acc, cur)=>{
+          const [nsOrName, name] = cur.split(':',1);
+          const element = viewSpecification.elements[cur] || viewSpecification.elements[nsOrName] || viewSpecification.elements[name];
+          if (!element && process.env.NODE_ENV === "development" ) {
+            throw new Error("removeReadOnlyElements: Could not determine elment for data.");
+          }
+          if (element && element.config) {
+            if (element.uiType==="object") {
+              const view = views[+element.viewId];
+              if (!view) {
+                throw new Error("removeReadOnlyElements: Internal Error could not determine viewId: "+element.viewId);
+              }
+              acc[cur] = removeReadOnlyElements(view, element.isList != null && element.isList, data[cur]);
+            } else {
+              acc[cur] = data[cur];
+            }
+          }
+          return acc;
+        }, {});
+      }
+    };
+    data = removeReadOnlyElements(viewSpecification, embedList, data);
+
+
     // embed the list -> key: list
     data = embedList
       ? { [viewElement!.label]: data }
index 223c4cb..b7697c8 100644 (file)
@@ -16,7 +16,7 @@
  * ============LICENSE_END==========================================================================
  */
 
-import React from 'react';
+import React, { useState } from 'react';
 import { Tooltip, Button, FormControl, Theme, createStyles, makeStyles } from '@material-ui/core';
 
 import { ViewElement } from '../models/uiModels';
@@ -35,13 +35,14 @@ type UIElementReferenceProps = {
 
 export const UIElementReference: React.FC<UIElementReferenceProps> = (props) => {
   const classes = useStyles();
+  const [disabled, setDisabled] = useState(true);
   const { element } = props;
   return (
-    <FormControl key={element.id} style={{ width: 485, marginLeft: 20, marginRight: 20 }}>
+    <FormControl key={element.id} style={{ width: 485, marginLeft: 20, marginRight: 20 }} onMouseDown={(ev) => { ev.preventDefault(); ev.stopPropagation(); ev.button === 1 && setDisabled(!disabled) }}>
       <Tooltip title={element.description || element.path || ''}>
-        <Button className={classes.button} aria-label={element.label+'-button'} color="secondary" disabled={props.disabled} onClick={() => {
+        <Button className={classes.button} aria-label={element.label+'-button'} color="secondary" disabled={props.disabled && disabled} onClick={() => {
           props.onOpenReference(element);
-        }}>{`${element.label}`}</Button>
+        }}  >{`${element.label}`}</Button>
       </Tooltip>
     </FormControl>
   );
index e6b808b..02f2929 100644 (file)
 
 import { createExternal, IExternalTableState } from '../../../../framework/src/components/material-table/utilities';
 import { createSearchDataHandler } from '../../../../framework/src/utilities/elasticSearch';
+import { getAccessPolicyByUrl } from '../../../../framework/src/services/restService';
 
 import { NetworkElementConnection } from '../models/networkElementConnection';
+import { restService } from '../services/restServices';
 
 export interface IConnectedNetworkElementsState extends IExternalTableState<NetworkElementConnection> { }
 
@@ -33,4 +35,11 @@ export const {
   reloadAction: connectedNetworkElementsReloadAction,
 
   // set value action, to change a value
-} = createExternal<NetworkElementConnection>(connectedNetworkElementsSearchHandler, appState => appState.configuration.connectedNetworkElements);
+} = createExternal<NetworkElementConnection>(connectedNetworkElementsSearchHandler, appState => appState.configuration.connectedNetworkElements, 
+  (ne) => {
+    if (!ne || !ne.id) return true;
+    const neUrl = restService.getNetworkElementUri(ne.id);
+    const policy = getAccessPolicyByUrl(neUrl);
+    return !(policy.GET && policy.POST);
+  }
+);
index ea20364..7a9812b 100644 (file)
@@ -35,10 +35,12 @@ export type DisplaySpecification =  {
   viewSpecification: ViewSpecification;
   keyProperty?: string;
   apidocPath?: string;
+  dataPath?: string;
 } | {
   displayMode: DisplayModeType.displayAsRPC;
   inputViewSpecification?: ViewSpecification;
   outputViewSpecification?: ViewSpecification;
+  dataPath?: string;
 }
 
 export interface IViewDescriptionState {
index 9c03bdf..29484d8 100644 (file)
@@ -220,6 +220,7 @@ export type ViewSpecification = {
   "when"?: string;
   "uses"?: (string[]) & { [ResolveFunction]?: (parent: string) => void };
   "elements": { [name: string]: ViewElement };
+  "config": boolean;
   readonly "canEdit": boolean;
 }
 
index 0cab7b7..3bc0e39 100644 (file)
@@ -29,21 +29,19 @@ import { NetworkElementSelector } from "./views/networkElementSelector";
 
 import ConfigurationApplication from "./views/configurationApplication";
 import { updateNodeIdAsyncActionCreator, updateViewActionAsyncCreator } from "./actions/deviceActions";
+import { DisplayModeType } from "./handlers/viewDescriptionHandler";
+import { ViewSpecification } from "./models/uiModels";
 
 let currentNodeId: string | null | undefined = undefined;
 let currentVirtualPath: string | null | undefined = undefined;
 let lastUrl: string | undefined = undefined;
 
-const mapProps = (state: IApplicationStoreState) => ({
-  // currentProblemsProperties: createCurrentProblemsProperties(state),
-});
-
 const mapDisp = (dispatcher: IDispatcher) => ({
   updateNodeId: (nodeId: string) => dispatcher.dispatch(updateNodeIdAsyncActionCreator(nodeId)),
   updateView: (vPath: string) => dispatcher.dispatch(updateViewActionAsyncCreator(vPath)),
 });
 
-const ConfigurationApplicationRouteAdapter = connect(mapProps, mapDisp)((props: RouteComponentProps<{ nodeId?: string, 0: string }> & Connect<typeof mapProps, typeof mapDisp>) => {
+const ConfigurationApplicationRouteAdapter = connect(undefined, mapDisp)((props: RouteComponentProps<{ nodeId?: string, 0: string }> & Connect<undefined, typeof mapDisp>) => {
   React.useEffect(() => {
     return () => {
       lastUrl = undefined;
@@ -57,17 +55,66 @@ const ConfigurationApplicationRouteAdapter = connect(mapProps, mapDisp)((props:
     window.setTimeout(async () => {
 
       // check if the nodeId has changed
+      let dump = false;
       if (currentNodeId !== props.match.params.nodeId) {
         currentNodeId = props.match.params.nodeId || undefined;
+        if (currentNodeId && currentNodeId.endsWith("|dump")) {
+          dump = true;
+          currentNodeId = currentNodeId.replace(/\|dump$/i, '');
+        }
         currentVirtualPath = null;
         currentNodeId && await props.updateNodeId(currentNodeId);
       }
 
       if (currentVirtualPath !== props.match.params[0]) {
         currentVirtualPath = props.match.params[0];
+        if (currentVirtualPath && currentVirtualPath.endsWith("|dump")) {
+          dump = true;
+          currentVirtualPath = currentVirtualPath.replace(/\|dump$/i, '');
+        }
         await props.updateView(currentVirtualPath);
       }
 
+      if (dump) {
+        const device = props.state.configuration.deviceDescription;
+        const ds = props.state.configuration.viewDescription.displaySpecification;
+
+        const createDump = (view: ViewSpecification | null, level: number = 0) => {
+          if (view === null) return "Empty";
+          const indention = Array(level * 4).fill(' ').join('');
+          let result = '';
+
+          if (!view) debugger;
+          // 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.mandatory ? "mandetory" : "none"} - ${elm.path} \r\n`;
+            
+            switch (elm.uiType) {
+              case "object":
+                acc += createDump(device.views[(elm as any).viewId], level + 1);
+                break;
+              default:
+            }
+            return acc;
+          }, "");
+          return `${result}`;
+        }
+
+        const dump = createDump(ds.displayMode === DisplayModeType.displayAsObject || ds.displayMode === DisplayModeType.displayAsList ? ds.viewSpecification : null, 0);
+        var element = document.createElement('a');
+        element.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(dump));
+        element.setAttribute('download', currentNodeId + ".txt");
+
+        element.style.display = 'none';
+        document.body.appendChild(element);
+
+        element.click();
+
+        document.body.removeChild(element);
+      }
+
     });
   }
   return (
index 239a8e4..bdef64c 100644 (file)
@@ -51,8 +51,10 @@ type CapabilityAnswer = {
 }
 
 class RestService {
+  public getNetworkElementUri = (nodeId: string) => '/rests/data/network-topology:network-topology/topology=topology-netconf/node=' + nodeId;
+
   public async getCapabilitiesByMoutId(nodeId: string): Promise<CapabilityAnswer> {
-    const path = `/rests/data/network-topology:network-topology/topology=topology-netconf/node=${nodeId}`;
+    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"] && 
index dbaa778..e466dba 100644 (file)
@@ -16,7 +16,7 @@
  * ============LICENSE_END==========================================================================
  */
 
-import React from 'react';
+import React, { useState } from 'react';
 import { RouteComponentProps, withRouter } from 'react-router-dom';
 
 import { WithStyles, withStyles, createStyles, Theme } from '@material-ui/core/styles';
@@ -31,6 +31,8 @@ import { DisplayModeType } from '../handlers/viewDescriptionHandler';
 import { SetSelectedValue, splitVPath, updateDataActionAsyncCreator, updateViewActionAsyncCreator, removeElementActionAsyncCreator, executeRpcActionAsyncCreator } from "../actions/deviceActions";
 import { ViewSpecification, isViewElementString, isViewElementNumber, isViewElementBoolean, isViewElementObjectOrList, isViewElementSelection, isViewElementChoise, ViewElement, ViewElementChoise, isViewElementUnion, isViewElementRpc, ViewElementRpc, isViewElementEmpty, isViewElementDate } from "../models/uiModels";
 
+import { getAccessPolicyByUrl } from "../../../../framework/src/services/restService";
+
 import Fab from '@material-ui/core/Fab';
 import AddIcon from '@material-ui/icons/Add';
 import PostAdd from '@material-ui/icons/PostAdd';
@@ -65,106 +67,107 @@ import { UIElementUnion } from '../components/uiElementUnion';
 import { UiElementLeafList } from '../components/uiElementLeafList';
 
 import { useConfirm } from 'material-ui-confirm';
+import restService from '../services/restServices';
 
 const styles = (theme: Theme) => createStyles({
-    header: {
-        "display": "flex",
-        "justifyContent": "space-between",
-    },
-    leftButton: {
-        "justifyContent": "left"
-    },
-    outer: {
-        "flex": "1",
-        "height": "100%",
-        "display": "flex",
-        "alignItems": "center",
-        "justifyContent": "center",
-    },
-    inner: {
-
-    },
-    container: {
-        "height": "100%",
-        "display": "flex",
-        "flexDirection": "column",
-    },
-    "icon": {
-        "marginRight": theme.spacing(0.5),
-        "width": 20,
-        "height": 20,
-    },
-    "fab": {
-        "margin": theme.spacing(1),
+  header: {
+    "display": "flex",
+    "justifyContent": "space-between",
+  },
+  leftButton: {
+    "justifyContent": "left"
+  },
+  outer: {
+    "flex": "1",
+    "height": "100%",
+    "display": "flex",
+    "alignItems": "center",
+    "justifyContent": "center",
+  },
+  inner: {
+
+  },
+  container: {
+    "height": "100%",
+    "display": "flex",
+    "flexDirection": "column",
+  },
+  "icon": {
+    "marginRight": theme.spacing(0.5),
+    "width": 20,
+    "height": 20,
+  },
+  "fab": {
+    "margin": theme.spacing(1),
+  },
+  button: {
+    margin: 0,
+    padding: "6px 6px",
+    minWidth: 'unset'
+  },
+  readOnly: {
+    '& label.Mui-focused': {
+      color: 'green',
     },
-    button: {
-        margin: 0,
-        padding: "6px 6px",
-        minWidth: 'unset'
+    '& .MuiInput-underline:after': {
+      borderBottomColor: 'green',
     },
-    readOnly: {
-        '& label.Mui-focused': {
-            color: 'green',
-        },
-        '& .MuiInput-underline:after': {
-            borderBottomColor: 'green',
-        },
-        '& .MuiOutlinedInput-root': {
-            '& fieldset': {
-                borderColor: 'red',
-            },
-            '&:hover fieldset': {
-                borderColor: 'yellow',
-            },
-            '&.Mui-focused fieldset': {
-                borderColor: 'green',
-            },
-        },
+    '& .MuiOutlinedInput-root': {
+      '& fieldset': {
+        borderColor: 'red',
+      },
+      '&:hover fieldset': {
+        borderColor: 'yellow',
+      },
+      '&.Mui-focused fieldset': {
+        borderColor: 'green',
+      },
     },
-    uiView: {
-        overflowY: "auto",
-    },
-    section: {
-        padding: "15px",
-        borderBottom: `2px solid ${theme.palette.divider}`,
-    },
-    viewElements: {
-        width: 485, marginLeft: 20, marginRight: 20
-    },
-    verificationElements: {
-        width: 485, marginLeft: 20, marginRight: 20
-    },
-    heading: {
-        fontSize: theme.typography.pxToRem(15),
-        fontWeight: theme.typography.fontWeightRegular,
-    },
-    moduleCollection: {
-       marginTop: "16px",
-       overflow: "auto", 
-    },
-    objectReult: {
-       overflow: "auto"    
-    }
+  },
+  uiView: {
+    overflowY: "auto",
+  },
+  section: {
+    padding: "15px",
+    borderBottom: `2px solid ${theme.palette.divider}`,
+  },
+  viewElements: {
+    width: 485, marginLeft: 20, marginRight: 20
+  },
+  verificationElements: {
+    width: 485, marginLeft: 20, marginRight: 20
+  },
+  heading: {
+    fontSize: theme.typography.pxToRem(15),
+    fontWeight: theme.typography.fontWeightRegular,
+  },
+  moduleCollection: {
+    marginTop: "16px",
+    overflow: "auto",
+  },
+  objectReult: {
+    overflow: "auto"
+  }
 });
 
 const mapProps = (state: IApplicationStoreState) => ({
-    collectingData: state.configuration.valueSelector.collectingData,
-    listKeyProperty: state.configuration.valueSelector.keyProperty,
-    listSpecification: state.configuration.valueSelector.listSpecification,
-    listData: state.configuration.valueSelector.listData,
-    vPath: state.configuration.viewDescription.vPath,
-    nodeId: state.configuration.deviceDescription.nodeId,
-    viewData: state.configuration.viewDescription.viewData,
-    outputData: state.configuration.viewDescription.outputData,
-    displaySpecification: state.configuration.viewDescription.displaySpecification,
+  collectingData: state.configuration.valueSelector.collectingData,
+  listKeyProperty: state.configuration.valueSelector.keyProperty,
+  listSpecification: state.configuration.valueSelector.listSpecification,
+  listData: state.configuration.valueSelector.listData,
+  vPath: state.configuration.viewDescription.vPath,
+  nodeId: state.configuration.deviceDescription.nodeId,
+  viewData: state.configuration.viewDescription.viewData,
+  outputData: state.configuration.viewDescription.outputData,
+  displaySpecification: state.configuration.viewDescription.displaySpecification,
 });
 
 const mapDispatch = (dispatcher: IDispatcher) => ({
-    onValueSelected: (value: any) => dispatcher.dispatch(new SetSelectedValue(value)),
-    onUpdateData: (vPath: string, data: any) => dispatcher.dispatch(updateDataActionAsyncCreator(vPath, data)),
-    reloadView: (vPath: string) => dispatcher.dispatch(updateViewActionAsyncCreator(vPath)),
-    removeElement: (vPath: string) => dispatcher.dispatch(removeElementActionAsyncCreator(vPath)),
-    executeRpc: (vPath: string, data: any) => dispatcher.dispatch(executeRpcActionAsyncCreator(vPath, data)),
+  onValueSelected: (value: any) => dispatcher.dispatch(new SetSelectedValue(value)),
+  onUpdateData: (vPath: string, data: any) => dispatcher.dispatch(updateDataActionAsyncCreator(vPath, data)),
+  reloadView: (vPath: string) => dispatcher.dispatch(updateViewActionAsyncCreator(vPath)),
+  removeElement: (vPath: string) => dispatcher.dispatch(removeElementActionAsyncCreator(vPath)),
+  executeRpc: (vPath: string, data: any) => dispatcher.dispatch(executeRpcActionAsyncCreator(vPath, data)),
 });
 
 const SelectElementTable = MaterialTable as MaterialTableCtorType<{ [key: string]: any }>;
@@ -172,661 +175,694 @@ const SelectElementTable = MaterialTable as MaterialTableCtorType<{ [key: string
 type ConfigurationApplicationComponentProps = RouteComponentProps & Connect<typeof mapProps, typeof mapDispatch> & WithStyles<typeof styles>;
 
 type ConfigurationApplicationComponentState = {
-    isNew: boolean;
-    editMode: boolean;
-    canEdit: boolean;
-    viewData: { [key: string]: any } | null;
-    choises: { [path: string]: { selectedCase: string, data: { [property: string]: any } } };
+  isNew: boolean;
+  editMode: boolean;
+  canEdit: boolean;
+  viewData: { [key: string]: any } | null;
+  choises: { [path: string]: { selectedCase: string, data: { [property: string]: any } } };
 }
 
+type GetStatelessComponentProps<T> = T extends (props: infer P & { children?: React.ReactNode }) => any ? P : any
+const AccordionSummaryExt: React.FC<GetStatelessComponentProps<typeof AccordionSummary>> = (props) => {
+  const [disabled, setDisabled] = useState(true);
+  const onMouseDown = (ev: React.MouseEvent<HTMLElement>) => {
+    if (ev.button === 1) {
+      setDisabled(!disabled);
+      ev.preventDefault();
+    }
+  };
+  return (
+    <div onMouseDown={onMouseDown} >
+      <AccordionSummary {...{ ...props, disabled: props.disabled && disabled }} />
+    </div>
+  );
+};
+
 const OldProps = Symbol("OldProps");
 class ConfigurationApplicationComponent extends React.Component<ConfigurationApplicationComponentProps, ConfigurationApplicationComponentState> {
 
-    /**
-     *
-     */
-    constructor(props: ConfigurationApplicationComponentProps) {
-        super(props);
-
-        this.state = {
-            isNew: false,
-            canEdit: false,
-            editMode: false,
-            viewData: null,
-            choises: {},
-        }
+  /**
+   *
+   */
+  constructor(props: ConfigurationApplicationComponentProps) {
+    super(props);
+
+    this.state = {
+      isNew: false,
+      canEdit: false,
+      editMode: false,
+      viewData: null,
+      choises: {},
     }
-
-    private static getChoisesFromElements = (elements: { [name: string]: ViewElement }, viewData: any) => {
-        return Object.keys(elements).reduce((acc, cur) => {
-            const elm = elements[cur];
-            if (isViewElementChoise(elm)) {
-                const caseKeys = Object.keys(elm.cases);
-
-                // find the right case for this choise, use the first one with data, at least use index 0
-                const selectedCase = caseKeys.find(key => {
-                    const caseElm = elm.cases[key];
-                    return Object.keys(caseElm.elements).some(caseElmKey => {
-                        const caseElmElm = caseElm.elements[caseElmKey];
-                        return viewData[caseElmElm.label] !== undefined || viewData[caseElmElm.id] != undefined;
-                    });
-                }) || caseKeys[0];
-
-                // extract all data of the active case
-                const caseElements = elm.cases[selectedCase].elements;
-                const data = Object.keys(caseElements).reduce((dataAcc, dataCur) => {
-                    const dataElm = caseElements[dataCur];
-                    if (isViewElementEmpty(dataElm)) {
-                        dataAcc[dataElm.label] = null;
-                    } else if (viewData[dataElm.label] !== undefined) {
-                        dataAcc[dataElm.label] = viewData[dataElm.label];
-                    } else if (viewData[dataElm.id] !== undefined) {
-                        dataAcc[dataElm.id] = viewData[dataElm.id];
-                    }
-                    return dataAcc;
-                }, {} as { [name: string]: any });
-
-                acc[elm.id] = {
-                    selectedCase,
-                    data,
-                };
-            }
-            return acc;
-        }, {} as { [path: string]: { selectedCase: string, data: { [property: string]: any } } }) || {}
+  }
+
+  private static getChoisesFromElements = (elements: { [name: string]: ViewElement }, viewData: any) => {
+    return Object.keys(elements).reduce((acc, cur) => {
+      const elm = elements[cur];
+      if (isViewElementChoise(elm)) {
+        const caseKeys = Object.keys(elm.cases);
+
+        // find the right case for this choise, use the first one with data, at least use index 0
+        const selectedCase = caseKeys.find(key => {
+          const caseElm = elm.cases[key];
+          return Object.keys(caseElm.elements).some(caseElmKey => {
+            const caseElmElm = caseElm.elements[caseElmKey];
+            return viewData[caseElmElm.label] !== undefined || viewData[caseElmElm.id] != undefined;
+          });
+        }) || caseKeys[0];
+
+        // extract all data of the active case
+        const caseElements = elm.cases[selectedCase].elements;
+        const data = Object.keys(caseElements).reduce((dataAcc, dataCur) => {
+          const dataElm = caseElements[dataCur];
+          if (isViewElementEmpty(dataElm)) {
+            dataAcc[dataElm.label] = null;
+          } else if (viewData[dataElm.label] !== undefined) {
+            dataAcc[dataElm.label] = viewData[dataElm.label];
+          } else if (viewData[dataElm.id] !== undefined) {
+            dataAcc[dataElm.id] = viewData[dataElm.id];
+          }
+          return dataAcc;
+        }, {} as { [name: string]: any });
+
+        acc[elm.id] = {
+          selectedCase,
+          data,
+        };
+      }
+      return acc;
+    }, {} as { [path: string]: { selectedCase: string, data: { [property: string]: any } } }) || {}
+  }
+
+  static getDerivedStateFromProps(nextProps: ConfigurationApplicationComponentProps, prevState: ConfigurationApplicationComponentState & { [OldProps]: ConfigurationApplicationComponentProps }) {
+
+    if (!prevState || !prevState[OldProps] || (prevState[OldProps].viewData !== nextProps.viewData)) {
+      const isNew: boolean = nextProps.vPath?.endsWith("[]") || false;
+      const state = {
+        ...prevState,
+        isNew: isNew,
+        editMode: isNew,
+        viewData: nextProps.viewData || null,
+        [OldProps]: nextProps,
+        choises: nextProps.displaySpecification.displayMode === DisplayModeType.doNotDisplay
+          ? null
+          : nextProps.displaySpecification.displayMode === DisplayModeType.displayAsRPC
+            ? nextProps.displaySpecification.inputViewSpecification && ConfigurationApplicationComponent.getChoisesFromElements(nextProps.displaySpecification.inputViewSpecification.elements, nextProps.viewData) || []
+            : ConfigurationApplicationComponent.getChoisesFromElements(nextProps.displaySpecification.viewSpecification.elements, nextProps.viewData)
+      }
+      return state;
     }
-
-    static getDerivedStateFromProps(nextProps: ConfigurationApplicationComponentProps, prevState: ConfigurationApplicationComponentState & { [OldProps]: ConfigurationApplicationComponentProps }) {
-
-        if (!prevState || !prevState[OldProps] || (prevState[OldProps].viewData !== nextProps.viewData)) {
-            const isNew: boolean = nextProps.vPath?.endsWith("[]") || false;
-            const state = {
-                ...prevState,
-                isNew: isNew,
-                editMode: isNew,
-                viewData: nextProps.viewData || null,
-                [OldProps]: nextProps,
-                choises: nextProps.displaySpecification.displayMode === DisplayModeType.doNotDisplay
-                    ? null
-                    : nextProps.displaySpecification.displayMode === DisplayModeType.displayAsRPC
-                        ? nextProps.displaySpecification.inputViewSpecification && ConfigurationApplicationComponent.getChoisesFromElements(nextProps.displaySpecification.inputViewSpecification.elements, nextProps.viewData) || []
-                        : ConfigurationApplicationComponent.getChoisesFromElements(nextProps.displaySpecification.viewSpecification.elements, nextProps.viewData)
+    return null;
+  }
+
+  private navigate = (path: string) => {
+    this.props.history.push(`${this.props.match.url}${path}`);
+  }
+
+  private changeValueFor = (property: string, value: any) => {
+    this.setState({
+      viewData: {
+        ...this.state.viewData,
+        [property]: value
+      }
+    });
+  }
+
+  private collectData = (elements: { [name: string]: ViewElement }) => {
+    // ensure only active choises will be contained
+    const viewData: { [key: string]: any } = { ...this.state.viewData };
+    const choiseKeys = Object.keys(elements).filter(elmKey => isViewElementChoise(elements[elmKey]));
+    const elementsToRemove = choiseKeys.reduce((acc, curChoiceKey) => {
+      const currentChoice = elements[curChoiceKey] as ViewElementChoise;
+      const selectedCase = this.state.choises[curChoiceKey].selectedCase;
+      Object.keys(currentChoice.cases).forEach(caseKey => {
+        const caseElements = currentChoice.cases[caseKey].elements;
+        if (caseKey === selectedCase) {
+          Object.keys(caseElements).forEach(caseElementKey => {
+            const elm = caseElements[caseElementKey];
+            if (isViewElementEmpty(elm)) {
+              // insert null for all empty elements
+              viewData[elm.id] = null;
             }
-            return state;
-        }
-        return null;
+          });
+          return;
+        };
+        Object.keys(caseElements).forEach(caseElementKey => {
+          acc.push(caseElements[caseElementKey]);
+        });
+      });
+      return acc;
+    }, [] as ViewElement[]);
+
+    return viewData && Object.keys(viewData).reduce((acc, cur) => {
+      if (!elementsToRemove.some(elm => elm.label === cur || elm.id === cur)) {
+        acc[cur] = viewData[cur];
+      }
+      return acc;
+    }, {} as { [key: string]: any });
+  }
+
+  private isPolicyViewElementForbidden = (element: ViewElement, dataPath: string): boolean => {
+    const policy = getAccessPolicyByUrl(`${dataPath}/${element.id}`);
+    return !(policy.GET && policy.POST);
+  }
+
+  private isPolicyModuleForbidden = (moduleName: string, dataPath: string): boolean => {
+    const policy = getAccessPolicyByUrl(`${dataPath}/${moduleName}`);
+    return !(policy.GET && policy.POST);
+  }
+
+  private getEditorForViewElement = (uiElement: ViewElement): (null | React.ComponentType<BaseProps<any>>) => {
+    if (isViewElementEmpty(uiElement)) {
+      return null;
+    } else if (isViewElementSelection(uiElement)) {
+      return UiElementSelection;
+    } else if (isViewElementBoolean(uiElement)) {
+      return UiElementBoolean;
+    } else if (isViewElementString(uiElement)) {
+      return UiElementString;
+    } else if (isViewElementDate(uiElement)) {
+      return UiElementString;
+    } else if (isViewElementNumber(uiElement)) {
+      return UiElementNumber;
+    } else if (isViewElementUnion(uiElement)) {
+      return UIElementUnion;
+    } else {
+      if (process.env.NODE_ENV !== "production") {
+        console.error(`Unknown element type - ${(uiElement as any).uiType} in ${(uiElement as any).id}.`)
+      }
+      return null;
     }
-
-    private navigate = (path: string) => {
-        this.props.history.push(`${this.props.match.url}${path}`);
+  }
+
+  private renderUIElement = (uiElement: ViewElement, viewData: { [key: string]: any }, keyProperty: string | undefined, editMode: boolean, isNew: boolean) => {
+    const isKey = (uiElement.label === keyProperty);
+    const canEdit = editMode && (isNew || (uiElement.config && !isKey));
+
+    // do not show elements w/o any value from the backend
+    if (viewData[uiElement.id] == null && !editMode) {
+      return null;
+    } else if (isViewElementEmpty(uiElement)) {
+      return null;
+    } else if (uiElement.isList) {
+      /* element is a leaf-list */
+      return <UiElementLeafList
+        key={uiElement.id}
+        inputValue={viewData[uiElement.id] == null ? [] : viewData[uiElement.id]}
+        value={uiElement}
+        readOnly={!canEdit}
+        disabled={editMode && !canEdit}
+        onChange={(e) => { this.changeValueFor(uiElement.id, e) }}
+        getEditorForViewElement={this.getEditorForViewElement}
+      />;
+    } else {
+      const Element = this.getEditorForViewElement(uiElement);
+      return Element != null
+        ? (
+          <Element
+            key={uiElement.id}
+            isKey={isKey}
+            inputValue={viewData[uiElement.id] == null ? '' : viewData[uiElement.id]}
+            value={uiElement}
+            readOnly={!canEdit}
+            disabled={editMode && !canEdit}
+            onChange={(e) => { this.changeValueFor(uiElement.id, e) }}
+          />)
+        : null;
     }
-
-    private changeValueFor = (property: string, value: any) => {
-        this.setState({
-            viewData: {
-                ...this.state.viewData,
-                [property]: value
-            }
-        });
+  };
+
+  // private renderUIReference = (uiElement: ViewElement, viewData: { [key: string]: any }, keyProperty: string | undefined, editMode: boolean, isNew: boolean) => {
+  //   const isKey = (uiElement.label === keyProperty);
+  //   const canEdit = editMode && (isNew || (uiElement.config && !isKey));
+  //   if (isViewElementObjectOrList(uiElement)) {
+  //     return (
+  //       <FormControl key={uiElement.id} style={{ width: 485, marginLeft: 20, marginRight: 20 }}>
+  //         <Tooltip title={uiElement.description || ''}>
+  //           <Button className={this.props.classes.leftButton} color="secondary" disabled={this.state.editMode} onClick={() => {
+  //             this.navigate(`/${uiElement.id}`);
+  //           }}>{uiElement.label}</Button>
+  //         </Tooltip>
+  //       </FormControl>
+  //     );
+  //   } else {
+  //     if (process.env.NODE_ENV !== "production") {
+  //       console.error(`Unknown reference type - ${(uiElement as any).uiType} in ${(uiElement as any).id}.`)
+  //     }
+  //     return null;
+  //   }
+  // };
+
+  private renderUIChoise = (uiElement: ViewElementChoise, viewData: { [key: string]: any }, keyProperty: string | undefined, editMode: boolean, isNew: boolean) => {
+    const isKey = (uiElement.label === keyProperty);
+
+    const currentChoise = this.state.choises[uiElement.id];
+    const currentCase = currentChoise && uiElement.cases[currentChoise.selectedCase];
+
+    const canEdit = editMode && (isNew || (uiElement.config && !isKey));
+    if (isViewElementChoise(uiElement)) {
+      const subElements = currentCase?.elements;
+      return (
+        <>
+          <FormControl key={uiElement.id} style={{ width: 485, marginLeft: 20, marginRight: 20 }}>
+            <InputLabel htmlFor={`select-${uiElement.id}`} >{uiElement.label}</InputLabel>
+            <Select
+              aria-label={uiElement.label + '-selection'}
+              required={!!uiElement.mandatory}
+              onChange={(e) => {
+                if (currentChoise.selectedCase === e.target.value) {
+                  return; // nothing changed
+                }
+                this.setState({ choises: { ...this.state.choises, [uiElement.id]: { ...this.state.choises[uiElement.id], selectedCase: e.target.value as string } } });
+              }}
+              readOnly={!canEdit}
+              disabled={editMode && !canEdit}
+              value={this.state.choises[uiElement.id].selectedCase}
+              inputProps={{
+                name: uiElement.id,
+                id: `select-${uiElement.id}`,
+              }}
+            >
+              {
+                Object.keys(uiElement.cases).map(caseKey => {
+                  const caseElm = uiElement.cases[caseKey];
+                  return (
+                    <MenuItem key={caseElm.id} value={caseKey} aria-label={caseKey}><Tooltip title={caseElm.description || ''}><div style={{ width: "100%" }}>{caseElm.label}</div></Tooltip></MenuItem>
+                  );
+                })
+              }
+            </Select>
+          </FormControl>
+          {subElements
+            ? Object.keys(subElements).map(elmKey => {
+              const elm = subElements[elmKey];
+              return this.renderUIElement(elm, viewData, keyProperty, editMode, isNew);
+            })
+            : <h3>Invalid Choise</h3>
+          }
+        </>
+      );
+    } else {
+      if (process.env.NODE_ENV !== "production") {
+        console.error(`Unknown type - ${(uiElement as any).uiType} in ${(uiElement as any).id}.`)
+      }
+      return null;
     }
+  };
 
-    private collectData = (elements: { [name: string]: ViewElement }) => {
-        // ensure only active choises will be contained
-        const viewData: { [key: string]: any } = { ...this.state.viewData };
-        const choiseKeys = Object.keys(elements).filter(elmKey => isViewElementChoise(elements[elmKey]));
-        const elementsToRemove = choiseKeys.reduce((acc, curChoiceKey) => {
-            const currentChoice = elements[curChoiceKey] as ViewElementChoise;
-            const selectedCase = this.state.choises[curChoiceKey].selectedCase;
-            Object.keys(currentChoice.cases).forEach(caseKey => {
-                const caseElements = currentChoice.cases[caseKey].elements;
-                if (caseKey === selectedCase) {
-                    Object.keys(caseElements).forEach(caseElementKey => {
-                        const elm = caseElements[caseElementKey];
-                        if (isViewElementEmpty(elm)) {
-                            // insert null for all empty elements
-                            viewData[elm.id] = null;
-                        }
-                    });
-                    return;
-                };
-                Object.keys(caseElements).forEach(caseElementKey => {
-                    acc.push(caseElements[caseElementKey]);
-                });
-            });
-            return acc;
-        }, [] as ViewElement[]);
+  private renderUIView = (viewSpecification: ViewSpecification, dataPath: string, viewData: { [key: string]: any }, keyProperty: string | undefined, editMode: boolean, isNew: boolean) => {
+    const { classes } = this.props;
 
-        return viewData && Object.keys(viewData).reduce((acc, cur) => {
-            if (!elementsToRemove.some(elm => elm.label === cur || elm.id === cur)) {
-                acc[cur] = viewData[cur];
-            }
-            return acc;
-        }, {} as { [key: string]: any });
-    }
 
-    private getEditorForViewElement = (uiElement: ViewElement): (null | React.ComponentType<BaseProps<any>>) => {
-        if (isViewElementEmpty(uiElement)) {
-            return null;
-        } else if (isViewElementSelection(uiElement)) {
-            return UiElementSelection;
-        } else if (isViewElementBoolean(uiElement)) {
-            return UiElementBoolean;
-        } else if (isViewElementString(uiElement)) {
-            return UiElementString;
-        } else if (isViewElementDate(uiElement)) {
-            return UiElementString;
-        } else if (isViewElementNumber(uiElement)) {
-            return UiElementNumber;
-        } else if (isViewElementUnion(uiElement)) {
-            return UIElementUnion;
-        } else {
-            if (process.env.NODE_ENV !== "production") {
-                console.error(`Unknown element type - ${(uiElement as any).uiType} in ${(uiElement as any).id}.`)
-            }
-            return null;
-        }
-    }
 
-    private renderUIElement = (uiElement: ViewElement, viewData: { [key: string]: any }, keyProperty: string | undefined, editMode: boolean, isNew: boolean) => {
-        const isKey = (uiElement.label === keyProperty);
-        const canEdit = editMode && (isNew || (uiElement.config && !isKey));
-
-        // do not show elements w/o any value from the backend
-        if (viewData[uiElement.id] == null && !editMode) {
-            return null;
-        } else if (isViewElementEmpty(uiElement)) {
-            return null;
-        } else if (uiElement.isList) {
-            /* element is a leaf-list */
-            return <UiElementLeafList
-                key={uiElement.id}
-                inputValue={viewData[uiElement.id] == null ? [] : viewData[uiElement.id]}
-                value={uiElement}
-                readOnly={!canEdit}
-                disabled={editMode && !canEdit}
-                onChange={(e) => { this.changeValueFor(uiElement.id, e) }}
-                getEditorForViewElement={this.getEditorForViewElement}
-            />;
-        } else {
-            const Element = this.getEditorForViewElement(uiElement);
-            return Element != null
-                ? (
-                    <Element
-                        key={uiElement.id}
-                        isKey={isKey}
-                        inputValue={viewData[uiElement.id] == null ? '' : viewData[uiElement.id]}
-                        value={uiElement}
-                        readOnly={!canEdit}
-                        disabled={editMode && !canEdit}
-                        onChange={(e) => { this.changeValueFor(uiElement.id, e) }}
-                    />)
-                : null;
-        }
+    const orderFunc = (vsA: ViewElement, vsB: ViewElement) => {
+      if (keyProperty) {
+        // if (vsA.label === vsB.label) return 0;
+        if (vsA.label === keyProperty) return -1;
+        if (vsB.label === keyProperty) return +1;
+      }
+
+      // if (vsA.uiType === vsB.uiType) return 0;
+      // if (vsA.uiType !== "object" && vsB.uiType !== "object") return 0;
+      // if (vsA.uiType === "object") return +1;
+      return -1;
     };
 
-    // private renderUIReference = (uiElement: ViewElement, viewData: { [key: string]: any }, keyProperty: string | undefined, editMode: boolean, isNew: boolean) => {
-    //   const isKey = (uiElement.label === keyProperty);
-    //   const canEdit = editMode && (isNew || (uiElement.config && !isKey));
-    //   if (isViewElementObjectOrList(uiElement)) {
-    //     return (
-    //       <FormControl key={uiElement.id} style={{ width: 485, marginLeft: 20, marginRight: 20 }}>
-    //         <Tooltip title={uiElement.description || ''}>
-    //           <Button className={this.props.classes.leftButton} color="secondary" disabled={this.state.editMode} onClick={() => {
-    //             this.navigate(`/${uiElement.id}`);
-    //           }}>{uiElement.label}</Button>
-    //         </Tooltip>
-    //       </FormControl>
-    //     );
-    //   } else {
-    //     if (process.env.NODE_ENV !== "production") {
-    //       console.error(`Unknown reference type - ${(uiElement as any).uiType} in ${(uiElement as any).id}.`)
-    //     }
-    //     return null;
-    //   }
-    // };
-
-    private renderUIChoise = (uiElement: ViewElementChoise, viewData: { [key: string]: any }, keyProperty: string | undefined, editMode: boolean, isNew: boolean) => {
-        const isKey = (uiElement.label === keyProperty);
-
-        const currentChoise = this.state.choises[uiElement.id];
-        const currentCase = currentChoise && uiElement.cases[currentChoise.selectedCase];
-
-        const canEdit = editMode && (isNew || (uiElement.config && !isKey));
-        if (isViewElementChoise(uiElement)) {
-            const subElements = currentCase?.elements;
+    const sections = Object.keys(viewSpecification.elements).reduce((acc, cur) => {
+      const elm = viewSpecification.elements[cur];
+      if (isViewElementObjectOrList(elm)) {
+        acc.references.push(elm);
+      } else if (isViewElementChoise(elm)) {
+        acc.choises.push(elm);
+      } else if (isViewElementRpc(elm)) {
+        acc.rpcs.push(elm);
+      } else {
+        acc.elements.push(elm);
+      }
+      return acc;
+    }, { elements: [] as ViewElement[], references: [] as ViewElement[], choises: [] as ViewElementChoise[], rpcs: [] as ViewElementRpc[] });
+
+    sections.elements = sections.elements.sort(orderFunc);
+
+    return (
+      <div className={classes.uiView}>
+        <div className={classes.section} />
+        {sections.elements.length > 0
+          ? (
+            <div className={classes.section}>
+              {sections.elements.map(element => this.renderUIElement(element, viewData, keyProperty, editMode, isNew))}
+            </div>
+          ) : null
+        }
+        {sections.references.length > 0
+          ? (
+            <div className={classes.section}>
+              {sections.references.map(element => (
+                <UIElementReference key={element.id} element={element} disabled={editMode || this.isPolicyViewElementForbidden(element, dataPath)} onOpenReference={(elm) => { this.navigate(`/${elm.id}`) }} />
+              ))}
+            </div>
+          ) : null
+        }
+        {sections.choises.length > 0
+          ? (
+            <div className={classes.section}>
+              {sections.choises.map(element => this.renderUIChoise(element, viewData, keyProperty, editMode, isNew))}
+            </div>
+          ) : null
+        }
+        {sections.rpcs.length > 0
+          ? (
+            <div className={classes.section}>
+              {sections.rpcs.map(element => (
+                <UIElementReference key={element.id} element={element} disabled={editMode || this.isPolicyViewElementForbidden(element, dataPath)} onOpenReference={(elm) => { this.navigate(`/${elm.id}`) }} />
+              ))}
+            </div>
+          ) : null
+        }
+      </div>
+    );
+  };
+
+  private renderUIViewSelector = (viewSpecification: ViewSpecification, dataPath: string, viewData: { [key: string]: any }, keyProperty: string | undefined, editMode: boolean, isNew: boolean) => {
+    const { classes } = this.props;
+    // group by module name
+    const modules = Object.keys(viewSpecification.elements).reduce<{ [key: string]: ViewSpecification }>((acc, cur) => {
+      const elm = viewSpecification.elements[cur];
+      const moduleView = (acc[elm.module] = acc[elm.module] || { ...viewSpecification, elements: {} });
+      moduleView.elements[cur] = elm;
+      return acc;
+    }, {});
+
+    const moduleKeys = Object.keys(modules).sort();
+
+    return (
+      <div className={classes.moduleCollection}>
+        {
+          moduleKeys.map(key => {
+            const moduleView = modules[key];
             return (
-                <>
-                    <FormControl key={uiElement.id} style={{ width: 485, marginLeft: 20, marginRight: 20 }}>
-                        <InputLabel htmlFor={`select-${uiElement.id}`} >{uiElement.label}</InputLabel>
-                        <Select
-                            aria-label={uiElement.label+'-selection'}
-                            required={!!uiElement.mandatory}
-                            onChange={(e) => {
-                                if (currentChoise.selectedCase === e.target.value) {
-                                    return; // nothing changed
-                                }
-                                this.setState({ choises: { ...this.state.choises, [uiElement.id]: { ...this.state.choises[uiElement.id], selectedCase: e.target.value as string } } });
-                            }}
-                            readOnly={!canEdit}
-                            disabled={editMode && !canEdit}
-                            value={this.state.choises[uiElement.id].selectedCase}
-                            inputProps={{
-                                name: uiElement.id,
-                                id: `select-${uiElement.id}`,
-                            }}
-                        >
-                            {
-                                Object.keys(uiElement.cases).map(caseKey => {
-                                    const caseElm = uiElement.cases[caseKey];
-                                    return (
-                                        <MenuItem key={caseElm.id} value={caseKey} aria-label={caseKey}><Tooltip title={caseElm.description || ''}><div style={{ width: "100%" }}>{caseElm.label}</div></Tooltip></MenuItem>
-                                    );
-                                })
-                            }
-                        </Select>
-                    </FormControl>
-                    {subElements
-                        ? Object.keys(subElements).map(elmKey => {
-                            const elm = subElements[elmKey];
-                            return this.renderUIElement(elm, viewData, keyProperty, editMode, isNew);
-                        })
-                        : <h3>Invalid Choise</h3>
-                    }
-                </>
+              <Accordion key={key} defaultExpanded={moduleKeys.length < 4} aria-label={key + '-panel'} >
+                <AccordionSummaryExt expandIcon={<ExpandMoreIcon />} aria-controls={`content-${key}`} id={`header-${key}`} disabled={this.isPolicyModuleForbidden(`${key}:`, dataPath)} >
+                  <Typography className={classes.heading}>{key}</Typography>
+                </AccordionSummaryExt>
+                <AccordionDetails>
+                  {this.renderUIView(moduleView, dataPath, viewData, keyProperty, editMode, isNew)}
+                </AccordionDetails>
+              </Accordion>
             );
-        } else {
-            if (process.env.NODE_ENV !== "production") {
-                console.error(`Unknown type - ${(uiElement as any).uiType} in ${(uiElement as any).id}.`)
-            }
-            return null;
+          })
         }
+      </div>
+    );
+  };
+
+  private renderUIViewList(listSpecification: ViewSpecification, dataPath: string, listKeyProperty: string, apiDocPath: string, listData: { [key: string]: any }[]) {
+    const listElements = listSpecification.elements;
+    const apiDocPathCreate = apiDocPath ? `${location.origin}${apiDocPath
+      .replace("$$$standard$$$", "topology-netconfnode%20resources%20-%20RestConf%20RFC%208040")
+      .replace("$$$action$$$", "put")}_${listKeyProperty.replace(/[\/=\-\:]/g, '_')}_` : undefined;
+
+    const navigate = (path: string) => {
+      this.props.history.push(`${this.props.match.url}${path}`);
     };
 
-    private renderUIView = (viewSpecification: ViewSpecification, viewData: { [key: string]: any }, keyProperty: string | undefined, editMode: boolean, isNew: boolean) => {
-        const { classes } = this.props;
-
-        const orderFunc = (vsA: ViewElement, vsB: ViewElement) => {
-            if (keyProperty) {
-                // if (vsA.label === vsB.label) return 0;
-                if (vsA.label === keyProperty) return -1;
-                if (vsB.label === keyProperty) return +1;
-            }
-
-            // if (vsA.uiType === vsB.uiType) return 0;
-            // if (vsA.uiType !== "object" && vsB.uiType !== "object") return 0;
-            // if (vsA.uiType === "object") return +1;
-            return -1;
-        };
-
-        const sections = Object.keys(viewSpecification.elements).reduce((acc, cur) => {
-            const elm = viewSpecification.elements[cur];
-            if (isViewElementObjectOrList(elm)) {
-                acc.references.push(elm);
-            } else if (isViewElementChoise(elm)) {
-                acc.choises.push(elm);
-            } else if (isViewElementRpc(elm)) {
-                acc.rpcs.push(elm);
-            } else {
-                acc.elements.push(elm);
-            }
-            return acc;
-        }, { elements: [] as ViewElement[], references: [] as ViewElement[], choises: [] as ViewElementChoise[], rpcs: [] as ViewElementRpc[] });
-
-        sections.elements = sections.elements.sort(orderFunc);
-
-        return (
-            <div className={classes.uiView}>
-                <div className={classes.section} />
-                {sections.elements.length > 0
-                    ? (
-                        <div className={classes.section}>
-                            {sections.elements.map(element => this.renderUIElement(element, viewData, keyProperty, editMode, isNew))}
-                        </div>
-                    ) : null
-                }
-                {sections.references.length > 0
-                    ? (
-                        <div className={classes.section}>
-                            {sections.references.map(element => (
-                                <UIElementReference key={element.id} element={element} disabled={editMode} onOpenReference={(elm) => { this.navigate(`/${elm.id}`) }} />
-                            ))}
-                        </div>
-                    ) : null
-                }
-                {sections.choises.length > 0
-                    ? (
-                        <div className={classes.section}>
-                            {sections.choises.map(element => this.renderUIChoise(element, viewData, keyProperty, editMode, isNew))}
-                        </div>
-                    ) : null
-                }
-                {sections.rpcs.length > 0
-                    ? (
-                        <div className={classes.section}>
-                            {sections.rpcs.map(element => (
-                                <UIElementReference key={element.id} element={element} disabled={editMode} onOpenReference={(elm) => { this.navigate(`/${elm.id}`) }} />
-                            ))}
-                        </div>
-                    ) : null
-                }
-            </div>
-        );
+    const addNewElementAction = {
+      icon: AddIcon, 
+      tooltip: 'Add', 
+      onClick: () => {
+        navigate("[]"); // empty key means new element
+      },
+      disabled: !listSpecification.config,
     };
 
-    private renderUIViewSelector = (viewSpecification: ViewSpecification, viewData: { [key: string]: any }, keyProperty: string | undefined, editMode: boolean, isNew: boolean) => {
-        const { classes } = this.props;
-
-        // group by module name
-        const modules = Object.keys(viewSpecification.elements).reduce<{ [key: string]: ViewSpecification }>((acc, cur) => {
-            const elm = viewSpecification.elements[cur];
-            const moduleView = (acc[elm.module] = acc[elm.module] || { ...viewSpecification, elements: {} });
-            moduleView.elements[cur] = elm;
-            return acc;
-        }, {});
-
-        const moduleKeys = Object.keys(modules).sort(); 
-
-        return (
-            <div className={classes.moduleCollection}>
-                {
-                   moduleKeys.map(key => {
-                        const moduleView = modules[key];
-                        return (
-                            <Accordion key={key} defaultExpanded={ moduleKeys.length < 4 } aria-label={key+'-panel'} >
-                                <AccordionSummary expandIcon={<ExpandMoreIcon />} aria-controls={`content-${key}`} id={`header-${key}`} >
-                                    <Typography className={classes.heading}>{key}</Typography>
-                                </AccordionSummary>
-                                <AccordionDetails>
-                                    {this.renderUIView(moduleView, viewData, keyProperty, editMode, isNew)}
-                                </AccordionDetails>
-                            </Accordion>
-                        );
-                    })
-                }
-            </div>
-           );
+    const addWithApiDocElementAction = {
+      icon: PostAdd, 
+      tooltip: 'Add', 
+      onClick: () => {
+        window.open(apiDocPathCreate, '_blank');
+      },
+      disabled: !listSpecification.config,
     };
 
-    private renderUIViewList(listSpecification: ViewSpecification, listKeyProperty: string, apiDocPath: string, listData: { [key: string]: any }[]) {
-        const listElements = listSpecification.elements;
-        const apiDocPathCreate = apiDocPath ? `${location.origin}${apiDocPath
-          .replace("$$$standard$$$","topology-netconfnode%20resources%20-%20RestConf%20RFC%208040")
-          .replace("$$$action$$$","put")}_${listKeyProperty.replace(/[\/=\-\:]/g,'_')}_` : undefined;
-
-        const navigate = (path: string) => {
-            this.props.history.push(`${this.props.match.url}${path}`);
-        };
-
-        const addNewElementAction = {
-            icon: AddIcon, tooltip: 'Add', onClick: () => {
-                navigate("[]"); // empty key means new element
-            }
-        };
-
-        const addWithApiDocElementAction = {
-            icon: PostAdd, tooltip: 'Add', onClick: () => {
-                window.open(apiDocPathCreate, '_blank');
-            }
-        };
-
-        const { classes, removeElement } = this.props;
-
-        const DeleteIconWithConfirmation: React.FC<{ rowData: { [key: string]: any }, onReload: () => void }> = (props) => {
-            const confirm = useConfirm();
-
-            return (
-                <Tooltip title={"Remove"} >
-                    <IconButton className={classes.button} aria-label="remove-element-button"
-                     onClick={async (e) => {
-                        e.stopPropagation();
-                        e.preventDefault();
-                        confirm({ title: "Do you really want to delete this element ?", description: "This action is permanent!", confirmationButtonProps: { color: "secondary" } })
-                            .then(() => removeElement(`${this.props.vPath}[${props.rowData[listKeyProperty]}]`))
-                            .then(props.onReload);
-                    }} >
-                        <RemoveIcon />
-                    </IconButton>
-                </Tooltip>
-            );
-        }
-
-        return (
-            <SelectElementTable stickyHeader idProperty={listKeyProperty} rows={listData} customActionButtons={apiDocPathCreate ? [addNewElementAction, addWithApiDocElementAction] : [addNewElementAction]} columns={
-                Object.keys(listElements).reduce<ColumnModel<{ [key: string]: any }>[]>((acc, cur) => {
-                    const elm = listElements[cur];
-                    if (elm.uiType !== "object" && listData.every(entry => entry[elm.label] != null)) {
-                        if (elm.label !== listKeyProperty) {
-                            acc.push(elm.uiType === "boolean" 
-                            ? { property: elm.label, type: ColumnType.boolean } 
-                            :  elm.uiType === "date" 
-                               ? { property: elm.label, type: ColumnType.date } 
-                               : { property: elm.label, type: elm.uiType === "number" ? ColumnType.numeric : ColumnType.text });
-                        } else {
-                            acc.unshift(elm.uiType === "boolean" 
-                            ? { property: elm.label, type: ColumnType.boolean } 
-                            : elm.uiType === "date" 
-                               ? { property: elm.label, type: ColumnType.date } 
-                               : { property: elm.label, type: elm.uiType === "number" ? ColumnType.numeric : ColumnType.text });
-                        }
-                    }
-                    return acc;
-                }, []).concat([{
-                    property: "Actions", disableFilter: true, disableSorting: true, type: ColumnType.custom, customControl: (({ rowData }) => {
-                        return (
-                            <DeleteIconWithConfirmation rowData={rowData} onReload={() => this.props.vPath && this.props.reloadView(this.props.vPath)} />
-                        );
-                    })
-                }])
-            } onHandleClick={(ev, row) => {
-                ev.preventDefault();
-                navigate(`[${encodeURIComponent(row[listKeyProperty])}]`);
-            }} ></SelectElementTable>
-        );
+    const { classes, removeElement } = this.props;
+
+    const DeleteIconWithConfirmation: React.FC<{disabled?: boolean, rowData: { [key: string]: any }, onReload: () => void }> = (props) => {
+      const confirm = useConfirm();
+
+      return (
+        <Tooltip title={"Remove"} >
+          <IconButton disabled={props.disabled} className={classes.button} aria-label="remove-element-button"
+            onClick={async (e) => {
+              e.stopPropagation();
+              e.preventDefault();
+              confirm({ title: "Do you really want to delete this element ?", description: "This action is permanent!", confirmationButtonProps: { color: "secondary" } })
+                .then(() => removeElement(`${this.props.vPath}[${props.rowData[listKeyProperty]}]`))
+                .then(props.onReload);
+            }} >
+            <RemoveIcon />
+          </IconButton>
+        </Tooltip>
+      );
     }
 
-    private renderUIViewRPC(inputViewSpecification: ViewSpecification | undefined, inputViewData: { [key: string]: any }, outputViewData: { [key: string]: any }, keyProperty: string | undefined, editMode: boolean, isNew: boolean) {
-        const { classes } = this.props;
-
-        const orderFunc = (vsA: ViewElement, vsB: ViewElement) => {
-            if (keyProperty) {
-                // if (vsA.label === vsB.label) return 0;
-                if (vsA.label === keyProperty) return -1;
-                if (vsB.label === keyProperty) return +1;
-            }
-
-            // if (vsA.uiType === vsB.uiType) return 0;
-            // if (vsA.uiType !== "object" && vsB.uiType !== "object") return 0;
-            // if (vsA.uiType === "object") return +1;
-            return -1;
-        };
-
-        const sections = inputViewSpecification && Object.keys(inputViewSpecification.elements).reduce((acc, cur) => {
-            const elm = inputViewSpecification.elements[cur];
-            if (isViewElementObjectOrList(elm)) {
-                console.error("Object should not appear in RPC view !");
-            } else if (isViewElementChoise(elm)) {
-                acc.choises.push(elm);
-            } else if (isViewElementRpc(elm)) {
-                console.error("RPC should not appear in RPC view !");
+    return (
+      <SelectElementTable stickyHeader idProperty={listKeyProperty} rows={listData} customActionButtons={apiDocPathCreate ? [addNewElementAction, addWithApiDocElementAction] : [addNewElementAction]} columns={
+        Object.keys(listElements).reduce<ColumnModel<{ [key: string]: any }>[]>((acc, cur) => {
+          const elm = listElements[cur];
+          if (elm.uiType !== "object" && listData.every(entry => entry[elm.label] != null)) {
+            if (elm.label !== listKeyProperty) {
+              acc.push(elm.uiType === "boolean"
+                ? { property: elm.label, type: ColumnType.boolean }
+                : elm.uiType === "date"
+                  ? { property: elm.label, type: ColumnType.date }
+                  : { property: elm.label, type: elm.uiType === "number" ? ColumnType.numeric : ColumnType.text });
             } else {
-                acc.elements.push(elm);
+              acc.unshift(elm.uiType === "boolean"
+                ? { property: elm.label, type: ColumnType.boolean }
+                : elm.uiType === "date"
+                  ? { property: elm.label, type: ColumnType.date }
+                  : { property: elm.label, type: elm.uiType === "number" ? ColumnType.numeric : ColumnType.text });
             }
-            return acc;
-        }, { elements: [] as ViewElement[], references: [] as ViewElement[], choises: [] as ViewElementChoise[], rpcs: [] as ViewElementRpc[] })
-            || { elements: [] as ViewElement[], references: [] as ViewElement[], choises: [] as ViewElementChoise[], rpcs: [] as ViewElementRpc[] };
-
-        sections.elements = sections.elements.sort(orderFunc);
-
-        return (
-            <>
-                <div className={classes.section} />
-                { sections.elements.length > 0
-                    ? (
-                        <div className={classes.section}>
-                            {sections.elements.map(element => this.renderUIElement(element, inputViewData, keyProperty, editMode, isNew))}
-                        </div>
-                    ) : null
-                }
-                { sections.choises.length > 0
-                    ? (
-                        <div className={classes.section}>
-                            {sections.choises.map(element => this.renderUIChoise(element, inputViewData, keyProperty, editMode, isNew))}
-                        </div>
-                    ) : null
-                }
-                <Button onClick={() => {
-                    const resultingViewData = inputViewSpecification && this.collectData(inputViewSpecification.elements);
-                    this.props.executeRpc(this.props.vPath!, resultingViewData);
-                }} >Exec</Button>
-                <div className={classes.objectReult}>
-                { outputViewData !== undefined
-                    ? renderObject(outputViewData)
-                    : null
-                }
-                </div>
-            </>
-        );
+          }
+          return acc;
+        }, []).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)} />
+            );
+          })
+        }])
+      } onHandleClick={(ev, row) => {
+        ev.preventDefault();
+        navigate(`[${encodeURIComponent(row[listKeyProperty])}]`);
+      }} ></SelectElementTable>
+    );
+  }
+
+  private renderUIViewRPC(inputViewSpecification: ViewSpecification | undefined, dataPath: string, inputViewData: { [key: string]: any }, outputViewData: { [key: string]: any }, keyProperty: string | undefined, editMode: boolean, isNew: boolean) {
+    const { classes } = this.props;
+
+    const orderFunc = (vsA: ViewElement, vsB: ViewElement) => {
+      if (keyProperty) {
+        // if (vsA.label === vsB.label) return 0;
+        if (vsA.label === keyProperty) return -1;
+        if (vsB.label === keyProperty) return +1;
+      }
+
+      // if (vsA.uiType === vsB.uiType) return 0;
+      // if (vsA.uiType !== "object" && vsB.uiType !== "object") return 0;
+      // if (vsA.uiType === "object") return +1;
+      return -1;
     };
 
-    private renderBreadCrumps() {
-        const { editMode } = this.state;
-        const { displaySpecification, vPath, nodeId } = this.props;
-        const pathParts = splitVPath(vPath!, /(?:([^\/\["]+)(?:\[([^\]]*)\])?)/g); // 1 = property / 2 = optional key
-        let lastPath = `/configuration`;
-        let basePath = `/configuration/${nodeId}`;
-        return (
-            <div className={this.props.classes.header}>
-                <div>
-                    <Breadcrumbs aria-label="breadcrumbs">
-                        <Link color="inherit" href="#" aria-label="back-breadcrumb"
-                         onClick={(ev: React.MouseEvent<HTMLElement>) => {
-                            ev.preventDefault();
-                            this.props.history.push(lastPath);
-                        }}>Back</Link>
-                        <Link color="inherit" href="#" 
-                        aria-label={nodeId+'-breadcrumb'}
-                        onClick={(ev: React.MouseEvent<HTMLElement>) => {
-                            ev.preventDefault();
-                            this.props.history.push(`/configuration/${nodeId}`);
-                        }}><span>{nodeId}</span></Link>
-                        {
-                            pathParts.map(([prop, key], ind) => {
-                                const path = `${basePath}/${prop}`;
-                                const keyPath = key && `${basePath}/${prop}[${key}]`;
-                                const propTitle= prop.replace(/^[^:]+:/, "");
-                                const ret = (
-                                    <span key={ind}>
-                                        <Link color="inherit" href="#" 
-                                        aria-label={propTitle+'-breadcrumb'}
-                                        onClick={(ev: React.MouseEvent<HTMLElement>) => {
-                                            ev.preventDefault();
-                                            this.props.history.push(path);
-                                        }}><span>{propTitle}</span></Link>
-                                        {
-                                            keyPath && <Link color="inherit" href="#" 
-                                            aria-label={key+'-breadcrumb'}
-                                            onClick={(ev: React.MouseEvent<HTMLElement>) => {
-                                                ev.preventDefault();
-                                                this.props.history.push(keyPath);
-                                            }}>{`[${key}]`}</Link> || null
-                                        }
-                                    </span>
-                                );
-                                lastPath = basePath;
-                                basePath = keyPath || path;
-                                return ret;
-                            })
-                        }
-                    </Breadcrumbs>
-                </div>
-                {this.state.editMode && (
-                    <Fab color="secondary" aria-label="back-button" className={this.props.classes.fab} onClick={async () => {
-                        this.props.vPath && await this.props.reloadView(this.props.vPath);
-                        this.setState({ editMode: false });
-                    }} ><ArrowBack /></Fab>
-                ) || null}
-                { /* do not show edit if this is a list or it can't be edited */
-                    displaySpecification.displayMode === DisplayModeType.displayAsObject && displaySpecification.viewSpecification.canEdit && (<div>
-                        <Fab color="secondary" aria-label={editMode ? 'save-button' : 'edit-button'} className={this.props.classes.fab} onClick={() => {
-                            if (this.state.editMode) {
-                                // ensure only active choises will be contained
-                                const resultingViewData = this.collectData(displaySpecification.viewSpecification.elements);
-                                this.props.onUpdateData(this.props.vPath!, resultingViewData);
-                            }
-                            this.setState({ editMode: !editMode });
-                        }}>
-                            {editMode
-                                ? <SaveIcon />
-                                : <EditIcon />
-                            }
-                        </Fab>
-                    </div> || null)
-                }
+    const sections = inputViewSpecification && Object.keys(inputViewSpecification.elements).reduce((acc, cur) => {
+      const elm = inputViewSpecification.elements[cur];
+      if (isViewElementObjectOrList(elm)) {
+        console.error("Object should not appear in RPC view !");
+      } else if (isViewElementChoise(elm)) {
+        acc.choises.push(elm);
+      } else if (isViewElementRpc(elm)) {
+        console.error("RPC should not appear in RPC view !");
+      } else {
+        acc.elements.push(elm);
+      }
+      return acc;
+    }, { elements: [] as ViewElement[], references: [] as ViewElement[], choises: [] as ViewElementChoise[], rpcs: [] as ViewElementRpc[] })
+      || { elements: [] as ViewElement[], references: [] as ViewElement[], choises: [] as ViewElementChoise[], rpcs: [] as ViewElementRpc[] };
+
+    sections.elements = sections.elements.sort(orderFunc);
+
+    return (
+      <>
+        <div className={classes.section} />
+        { sections.elements.length > 0
+          ? (
+            <div className={classes.section}>
+              {sections.elements.map(element => this.renderUIElement(element, inputViewData, keyProperty, editMode, isNew))}
             </div>
-        );
-    }
-
-    private renderValueSelector() {
-        const { listKeyProperty, listSpecification, listData, onValueSelected } = this.props;
-        if (!listKeyProperty || !listSpecification) {
-            throw new Error("ListKex ot view not specified.");
+          ) : null
         }
-
-        return (
-            <div className={this.props.classes.container}>
-                <SelectElementTable stickyHeader idProperty={listKeyProperty} rows={listData} columns={
-                    Object.keys(listSpecification.elements).reduce<ColumnModel<{ [key: string]: any }>[]>((acc, cur) => {
-                        const elm = listSpecification.elements[cur];
-                        if (elm.uiType !== "object" && listData.every(entry => entry[elm.label] != null)) {
-                            if (elm.label !== listKeyProperty) {
-                                acc.push({ property: elm.label, type: elm.uiType === "number" ? ColumnType.numeric : ColumnType.text });
-                            } else {
-                                acc.unshift({ property: elm.label, type: elm.uiType === "number" ? ColumnType.numeric : ColumnType.text });
-                            }
-                        }
-                        return acc;
-                    }, [])
-                } onHandleClick={(ev, row) => { ev.preventDefault(); onValueSelected(row); }} ></SelectElementTable>
+        { sections.choises.length > 0
+          ? (
+            <div className={classes.section}>
+              {sections.choises.map(element => this.renderUIChoise(element, inputViewData, keyProperty, editMode, isNew))}
             </div>
-        );
-    }
-
-    private renderValueEditor() {
-        const { displaySpecification: ds, outputData } = this.props;
-        const { viewData, editMode, isNew } = this.state;
-
-        return (
-            <div className={this.props.classes.container}>
-                {this.renderBreadCrumps()}
-                {ds.displayMode === DisplayModeType.doNotDisplay
-                    ? null
-                    : ds.displayMode === DisplayModeType.displayAsList && viewData instanceof Array
-                        ? this.renderUIViewList(ds.viewSpecification, ds.keyProperty!, ds.apidocPath!, viewData)
-                        : ds.displayMode === DisplayModeType.displayAsRPC
-                            ? this.renderUIViewRPC(ds.inputViewSpecification, viewData!, outputData, undefined, true, false)
-                            : this.renderUIViewSelector(ds.viewSpecification, viewData!, ds.keyProperty, editMode, isNew)
-                }
-            </div >
-        );
-    }
-
-    private renderCollectingData() {
-        return (
-            <div className={this.props.classes.outer}>
-                <div className={this.props.classes.inner}>
-                    <Loader />
-                    <h3>Processing ...</h3>
-                </div>
-            </div>
-        );
+          ) : null
+        }
+        <Button onClick={() => {
+          const resultingViewData = inputViewSpecification && this.collectData(inputViewSpecification.elements);
+          this.props.executeRpc(this.props.vPath!, resultingViewData);
+        }} >Exec</Button>
+        <div className={classes.objectReult}>
+          {outputViewData !== undefined
+            ? renderObject(outputViewData)
+            : null
+          }
+        </div>
+      </>
+    );
+  };
+
+  private renderBreadCrumps() {
+    const { editMode } = this.state;
+    const { displaySpecification, vPath, nodeId } = this.props;
+    const pathParts = splitVPath(vPath!, /(?:([^\/\["]+)(?:\[([^\]]*)\])?)/g); // 1 = property / 2 = optional key
+    let lastPath = `/configuration`;
+    let basePath = `/configuration/${nodeId}`;
+    return (
+      <div className={this.props.classes.header}>
+        <div>
+          <Breadcrumbs aria-label="breadcrumbs">
+            <Link color="inherit" href="#" aria-label="back-breadcrumb"
+              onClick={(ev: React.MouseEvent<HTMLElement>) => {
+                ev.preventDefault();
+                this.props.history.push(lastPath);
+              }}>Back</Link>
+            <Link color="inherit" href="#"
+              aria-label={nodeId + '-breadcrumb'}
+              onClick={(ev: React.MouseEvent<HTMLElement>) => {
+                ev.preventDefault();
+                this.props.history.push(`/configuration/${nodeId}`);
+              }}><span>{nodeId}</span></Link>
+            {
+              pathParts.map(([prop, key], ind) => {
+                const path = `${basePath}/${prop}`;
+                const keyPath = key && `${basePath}/${prop}[${key}]`;
+                const propTitle = prop.replace(/^[^:]+:/, "");
+                const ret = (
+                  <span key={ind}>
+                    <Link color="inherit" href="#"
+                      aria-label={propTitle + '-breadcrumb'}
+                      onClick={(ev: React.MouseEvent<HTMLElement>) => {
+                        ev.preventDefault();
+                        this.props.history.push(path);
+                      }}><span>{propTitle}</span></Link>
+                    {
+                      keyPath && <Link color="inherit" href="#"
+                        aria-label={key + '-breadcrumb'}
+                        onClick={(ev: React.MouseEvent<HTMLElement>) => {
+                          ev.preventDefault();
+                          this.props.history.push(keyPath);
+                        }}>{`[${key}]`}</Link> || null
+                    }
+                  </span>
+                );
+                lastPath = basePath;
+                basePath = keyPath || path;
+                return ret;
+              })
+            }
+          </Breadcrumbs>
+        </div>
+        {this.state.editMode && (
+          <Fab color="secondary" aria-label="back-button" className={this.props.classes.fab} onClick={async () => {
+            this.props.vPath && await this.props.reloadView(this.props.vPath);
+            this.setState({ editMode: false });
+          }} ><ArrowBack /></Fab>
+        ) || null}
+        { /* do not show edit if this is a list or it can't be edited */
+          displaySpecification.displayMode === DisplayModeType.displayAsObject && displaySpecification.viewSpecification.canEdit && (<div>
+            <Fab color="secondary" aria-label={editMode ? 'save-button' : 'edit-button'} className={this.props.classes.fab} onClick={() => {
+              if (this.state.editMode) {
+                // ensure only active choises will be contained
+                const resultingViewData = this.collectData(displaySpecification.viewSpecification.elements);
+                this.props.onUpdateData(this.props.vPath!, resultingViewData);
+              }
+              this.setState({ editMode: !editMode });
+            }}>
+              {editMode
+                ? <SaveIcon />
+                : <EditIcon />
+              }
+            </Fab>
+          </div> || null)
+        }
+      </div>
+    );
+  }
+
+  private renderValueSelector() {
+    const { listKeyProperty, listSpecification, listData, onValueSelected } = this.props;
+    if (!listKeyProperty || !listSpecification) {
+      throw new Error("ListKex ot view not specified.");
     }
 
-    render() {
-        return this.props.collectingData || !this.state.viewData
-            ? this.renderCollectingData()
-            : this.props.listSpecification
-                ? this.renderValueSelector()
-                : this.renderValueEditor();
-    }
+    return (
+      <div className={this.props.classes.container}>
+        <SelectElementTable stickyHeader idProperty={listKeyProperty} rows={listData} columns={
+          Object.keys(listSpecification.elements).reduce<ColumnModel<{ [key: string]: any }>[]>((acc, cur) => {
+            const elm = listSpecification.elements[cur];
+            if (elm.uiType !== "object" && listData.every(entry => entry[elm.label] != null)) {
+              if (elm.label !== listKeyProperty) {
+                acc.push({ property: elm.label, type: elm.uiType === "number" ? ColumnType.numeric : ColumnType.text });
+              } else {
+                acc.unshift({ property: elm.label, type: elm.uiType === "number" ? ColumnType.numeric : ColumnType.text });
+              }
+            }
+            return acc;
+          }, [])
+        } onHandleClick={(ev, row) => { ev.preventDefault(); onValueSelected(row); }} ></SelectElementTable>
+      </div>
+    );
+  }
+
+  private renderValueEditor() {
+    const { displaySpecification: ds, outputData } = this.props;
+    const { viewData, editMode, isNew } = this.state;
+
+    return (
+      <div className={this.props.classes.container}>
+        {this.renderBreadCrumps()}
+        {ds.displayMode === DisplayModeType.doNotDisplay
+          ? null
+          : ds.displayMode === DisplayModeType.displayAsList && viewData instanceof Array
+            ? this.renderUIViewList(ds.viewSpecification, ds.dataPath!, ds.keyProperty!, ds.apidocPath!, viewData)
+            : ds.displayMode === DisplayModeType.displayAsRPC
+              ? this.renderUIViewRPC(ds.inputViewSpecification, ds.dataPath!, viewData!, outputData, undefined, true, false)
+              : this.renderUIViewSelector(ds.viewSpecification, ds.dataPath!, viewData!, ds.keyProperty, editMode, isNew)
+        }
+      </div >
+    );
+  }
+
+  private renderCollectingData() {
+    return (
+      <div className={this.props.classes.outer}>
+        <div className={this.props.classes.inner}>
+          <Loader />
+          <h3>Processing ...</h3>
+        </div>
+      </div>
+    );
+  }
+
+  render() {
+    return this.props.collectingData || !this.state.viewData
+      ? this.renderCollectingData()
+      : this.props.listSpecification
+        ? this.renderValueSelector()
+        : this.renderValueEditor();
+  }
 }
 
 export const ConfigurationApplication = withStyles(styles)(withRouter(connect(mapProps, mapDispatch)(ConfigurationApplicationComponent)));
index 5d84998..5cac22e 100644 (file)
@@ -25,7 +25,7 @@ import { MaterialTable, MaterialTableCtorType, ColumnType } from "../../../../fr
 import { createConnectedNetworkElementsProperties, createConnectedNetworkElementsActions } from "../../../configurationApp/src/handlers/connectedNetworkElementsHandler";
 
 import { NetworkElementConnection } from "../models/networkElementConnection";
-import { Tooltip, Button, IconButton } from "@material-ui/core";
+
 
 const mapProps = (state: IApplicationStoreState) => ({
   connectedNetworkElementsProperties: createConnectedNetworkElementsProperties(state),
index c5cb8fb..2d38976 100644 (file)
@@ -278,6 +278,7 @@ export class YangParser {
     name: "root",
     language: "en-US",
     canEdit: false,
+    config: true,
     parentView: "0",
     title: "root",
     elements: {},
@@ -544,6 +545,28 @@ export class YangParser {
         console.warn(error.message);
       }
     });
+
+    // resolve readOnly
+    const resolveReadOnly = (view: ViewSpecification, parentConfig: boolean) => {
+      
+      // update view config
+      view.config = view.config && parentConfig;
+      
+      Object.keys(view.elements).forEach((key) => {
+        const elm = view.elements[key];
+
+        // update element config
+        elm.config = elm.config && view.config;
+        
+        // update all sub-elements of objects
+        if (elm.uiType === "object") {
+          resolveReadOnly(this.views[+elm.viewId], elm.config);
+        }
+
+      })
+    }
+
+    const dump = resolveReadOnly(this.views[0], true); 
   };
 
   private _nextId = 1;
@@ -686,7 +709,7 @@ export class YangParser {
           module: context.name || module.name || '',
           uiType: "object",
           viewId: currentView.id,
-          config: config
+          config: currentView.config,
         });
         acc.push(currentView, ...subViews);
         return acc;
@@ -717,7 +740,7 @@ export class YangParser {
           uiType: "object",
           viewId: currentView.id,
           key: key,
-          config: elmConfig
+          config: elmConfig && currentView.config,
         });
         acc.push(currentView, ...subViews);
         return acc;
@@ -876,6 +899,7 @@ export class YangParser {
       title: statement.arg != null ? statement.arg : undefined,
       language: "en-us",
       canEdit: false,
+      config: config,
       ifFeature: ifFeature,
       when: whenCondition,
       elements: elements.reduce<{ [name: string]: ViewElement }>((acc, cur) => {
@@ -921,7 +945,7 @@ export class YangParser {
               const elm = groupingViewSpec.elements[key];
               // a useRef on root level need a namespace
               viewSpec.elements[parentId === 0 ? `${module.name}:${key}` : key] = {
-                ...groupingViewSpec.elements[key],
+                ...elm,
                 when: elm.when ? `(${groupingViewSpec.when}) and (${elm.when})` : groupingViewSpec.when,
                 ifFeature: elm.ifFeature ? `(${groupingViewSpec.ifFeature}) and (${elm.ifFeature})` : groupingViewSpec.ifFeature,
               };
index 836af66..4fae39c 100644 (file)
     "@odlux/framework": "*"
   },
   "peerDependencies": {
-    "@types/react": "16.9.19",
-    "@types/react-dom": "16.9.5",
-    "@types/react-router-dom": "4.3.1",
+    "@types/react": "17.0.3",
+    "@types/react-dom": "17.0.2",
+    "@types/react-router-dom": "5.1.7",
     "@material-ui/core": "4.11.0",
     "@material-ui/icons": "4.9.1",
     "@types/classnames": "2.2.6",
     "@types/flux": "3.1.8",
     "@types/jquery": "3.3.10",
     "jquery": "3.3.1",
-    "react": "16.12.0",
-    "react-dom": "16.12.0",
-    "react-router-dom": "4.3.1"
+    "react": "17.0.1",
+    "react-dom": "17.0.1",
+    "react-router-dom": "5.2.0"
   }
 }
\ No newline at end of file
diff --git a/sdnr/wt/odlux/apps/connectApp/policies.json b/sdnr/wt/odlux/apps/connectApp/policies.json
new file mode 100644 (file)
index 0000000..2ec7361
--- /dev/null
@@ -0,0 +1,12 @@
+[
+  {
+    "path": "/rests/**/node=Sim2230**",
+    "methods": {
+      "get": true,
+      "post": false,
+      "put": false,
+      "delete": false,
+      "patch": false
+    }
+  }
+]
\ No newline at end of file
index a3bdc68..a83e002 100644 (file)
@@ -23,13 +23,13 @@ import { Action } from '../../../../framework/src/flux/action';
 import { Dispatch } from '../../../../framework/src/flux/store';
 import { IApplicationStoreState } from '../../../../framework/src/store/applicationStore';
 
-import { networkElementsReloadAction, networkElementsReloadActionAsync } from '../handlers/networkElementsHandler';
+import { networkElementsReloadAction } from '../handlers/networkElementsHandler';
 import { connectionStatusLogReloadAction } from '../handlers/connectionStatusLogHandler';
 
 import { PanelId } from '../models/panelId';
 import { guiCutThrough } from '../models/guiCutTrough';
-import connectService from '../services/connectService';
-import { NetworkElementConnection } from '../models/networkElementConnection';
+import { connectService}  from '../services/connectService';
+
 
 export class SetPanelAction extends Action {
   constructor(public panelId: PanelId) {
@@ -38,7 +38,7 @@ export class SetPanelAction extends Action {
 }
 
 export class AddWebUriList extends Action {
-  constructor(public searchedElements: guiCutThrough[], public notSearchedElements: string[], public unsupportedElements: string[], public newlySearchedElements?: string[] ) {
+  constructor(public searchedElements: guiCutThrough[], public notSearchedElements: string[], public unsupportedElements: string[], public newlySearchedElements?: string[]) {
     super();
   }
 }
@@ -60,49 +60,47 @@ export class SetWeburiSearchBusy extends Action {
 }
 
 let isBusy = false;
-export const findWebUrisForGuiCutThroughAsyncAction = (networkElements: NetworkElementConnection[]) => async (dispatcher: Dispatch, getState: () => IApplicationStoreState) => {
+export const findWebUrisForGuiCutThroughAsyncAction = (networkElementIds: string[]) => async (dispatcher: Dispatch, getState: () => IApplicationStoreState) => {
 
   // keep method from executing simultanously; state not used because change of iu isn't needed
-  if (isBusy)
-    return;
-  isBusy = true;
 
-  const { connect: { guiCutThrough } } = getState();
+
+  const { connect: { guiCutThrough, networkElements } } = getState();
 
   let notConnectedElements: string[] = [];
   let elementsToSearch: string[] = [];
   let prevFoundElements: string[] = [];
-  let unsupportedElements: string[]= [];
+  let unsupportedElements: string[] = [];
 
-
-  networkElements.forEach(item => {
-      const id = item.id as string;
+  networkElementIds.forEach(id => {
+    const item = networkElements.rows.find((ne) => ne.id === id);
+    if (item) {
       if (item.status === "Connected") {
 
-        if(item.coreModelCapability!== "Unsupported"){
+        // 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.nodeId === id).length > 0;
+          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;
-          const exists = guiCutThrough.unsupportedElements.filter(element => element === id).length > 0;
-          if(!exists){
-            unsupportedElements.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;
+        //   const exists = guiCutThrough.unsupportedElements.filter(element => element === id).length > 0;
+        //   if (!exists) {
+        //     unsupportedElements.push(id);
+
+        //     //element was found previously, but wasn't connected
+        //     if (guiCutThrough.notSearchedElements.length > 0 && guiCutThrough.notSearchedElements.includes(id)) {
+        //       prevFoundElements.push(id);
+        //     }
+        //   }
+        // }
       }
       else {
         // element isn't connected and cannot be searched for a weburi
@@ -110,25 +108,25 @@ export const findWebUrisForGuiCutThroughAsyncAction = (networkElements: NetworkE
           notConnectedElements.push(item.id as string);
         }
       }
+    }
   });
 
-  
-  if (elementsToSearch.length > 0 || notConnectedElements.length > 0 || unsupportedElements.length>0 ) {
-    const result = await connectService.getAllWebUriExtensionsForNetworkElementListAsync(elementsToSearch);
-  dispatcher(new AddWebUriList(result, notConnectedElements, unsupportedElements, prevFoundElements));
+
+  if (elementsToSearch.length > 0 || notConnectedElements.length > 0 || unsupportedElements.length > 0) {
+      const result = await connectService.getAllWebUriExtensionsForNetworkElementListAsync(elementsToSearch);
+     dispatcher(new AddWebUriList(result, notConnectedElements, unsupportedElements, prevFoundElements));
   }
 
-  isBusy = false;
 }
 
 export const setPanelAction = (panelId: PanelId) => {
   return new SetPanelAction(panelId);
 }
 
-export const updateCurrentViewAsyncAction = () => async (dispatch: Dispatch, getState: () => IApplicationStoreState) => {
+export const updateCurrentViewAsyncAction = () => (dispatch: Dispatch, getState: () => IApplicationStoreState) => {
   const { connect: { currentOpenPanel } } = getState();
   if (currentOpenPanel === "NetworkElements") {
-    return await dispatch(networkElementsReloadActionAsync);
+    return dispatch(networkElementsReloadAction);
   }
   else {
     return dispatch(connectionStatusLogReloadAction);
index f2fd293..94b4872 100644 (file)
@@ -19,9 +19,11 @@ import * as React from 'react';
 import connect, { IDispatcher, Connect } from '../../../../framework/src/flux/connect';
 import { IApplicationStoreState } from '../../../../framework/src/store/applicationStore';
 import { MaterialTable, ColumnType, MaterialTableCtorType } from '../../../../framework/src/components/material-table';
+import  Refresh  from '@material-ui/icons/Refresh';
 
 import { createConnectionStatusLogActions, createConnectionStatusLogProperties } from '../handlers/connectionStatusLogHandler';
 import { NetworkElementConnectionLog } from '../models/networkElementConnectionLog';
+import RefreshConnectionStatusLogDialog, { RefreshConnectionStatusLogDialogMode } from './refreshConnectionStatusLogDialog';
 
 const mapProps = (state: IApplicationStoreState) => ({
   connectionStatusLogProperties: createConnectionStatusLogProperties(state),
@@ -34,22 +36,52 @@ const mapDispatch = (dispatcher: IDispatcher) => ({
 const ConnectionStatusTable = MaterialTable as MaterialTableCtorType<NetworkElementConnectionLog>;
 
 type ConnectionStatusLogComponentProps = Connect<typeof mapProps, typeof mapDispatch>;
+type ConnectionStatusLogComponentState = {
+  refreshConnectionStatusLogEditorMode: RefreshConnectionStatusLogDialogMode
+}
 
 let initialSorted = false;
 
 
-class ConnectionStatusLogComponent extends React.Component<ConnectionStatusLogComponentProps> {
+class ConnectionStatusLogComponent extends React.Component<ConnectionStatusLogComponentProps,ConnectionStatusLogComponentState > {
+  constructor(props: ConnectionStatusLogComponentProps) {
+    super(props);
+
+    this.state = {
+      refreshConnectionStatusLogEditorMode: RefreshConnectionStatusLogDialogMode.None
+    };
+  }
+
   render(): JSX.Element {
+    const refreshConnectionStatusLogAction = {
+      icon: Refresh, tooltip: 'Refresh Connection Status Log Table', onClick: () => {
+        this.setState({
+          refreshConnectionStatusLogEditorMode: RefreshConnectionStatusLogDialogMode.RefreshConnectionStatusLogTable
+        });
+      }
+    };
+
     return (
-      <ConnectionStatusTable stickyHeader tableId="connection-status-table" columns={[
+    <>
+      <ConnectionStatusTable stickyHeader tableId="connection-status-table" customActionButtons={[refreshConnectionStatusLogAction]}  columns={[
         { property: "timestamp", title: "Timestamp", type: ColumnType.text },
         { property: "nodeId", title: "Node Name", type: ColumnType.text },
         { property: "status", title: "Connection Status", type: ColumnType.text },
       ]} idProperty="id" {...this.props.connectionStatusLogActions} {...this.props.connectionStatusLogProperties} >
       </ConnectionStatusTable>
+       <RefreshConnectionStatusLogDialog
+        mode={ this.state.refreshConnectionStatusLogEditorMode }
+        onClose={ this.onCloseRefreshConnectionStatusLogDialog }
+      />
+    </>
     );
   };
 
+  private onCloseRefreshConnectionStatusLogDialog = () => {
+    this.setState({
+      refreshConnectionStatusLogEditorMode: RefreshConnectionStatusLogDialogMode.None
+    });
+  }
   componentDidMount() {
     if (!initialSorted) {
       initialSorted = true;
index 53e1048..84a22a9 100644 (file)
@@ -19,12 +19,14 @@ import * as React from 'react';
 import { Theme, createStyles, withStyles, WithStyles } from '@material-ui/core/styles';
 
 import AddIcon from '@material-ui/icons/Add';
+import  Refresh  from '@material-ui/icons/Refresh';
 import LinkIcon from '@material-ui/icons/Link';
 import LinkOffIcon from '@material-ui/icons/LinkOff';
 import RemoveIcon from '@material-ui/icons/RemoveCircleOutline';
 import EditIcon from '@material-ui/icons/Edit';
 import Info from '@material-ui/icons/Info';
 import ComputerIcon from '@material-ui/icons/Computer';
+import { MenuItem, Divider, Typography } from '@material-ui/core';
 
 import { MaterialTable, ColumnType, MaterialTableCtorType } from '../../../../framework/src/components/material-table';
 import { IApplicationStoreState } from '../../../../framework/src/store/applicationStore';
@@ -34,12 +36,14 @@ import { NavigateToApplication } from '../../../../framework/src/actions/navigat
 import { createNetworkElementsActions, createNetworkElementsProperties } from '../handlers/networkElementsHandler';
 
 import { NetworkElementConnection } from '../models/networkElementConnection';
+import { 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 { TopologyNode } from '../models/topologyNetconf';
-import { MenuItem, Divider, Typography } from '@material-ui/core';
+import { connectService } from '../services/connectService';
+import { getAccessPolicyByUrl } from '../../../../framework/src/services/restService';
 
 const styles = (theme: Theme) => createStyles({
   connectionStatusConnected: {
@@ -63,6 +67,22 @@ const styles = (theme: Theme) => createStyles({
   }
 });
 
+type GetStatelessComponentProps<T> = T extends (props: infer P & { children?: React.ReactNode }) => any ? P : any
+const MenuItemExt : React.FC<GetStatelessComponentProps<typeof MenuItem>> = (props) => {
+  const [disabled, setDisabled] = React.useState(true);
+  const onMouseDown = (ev: React.MouseEvent<HTMLElement>) => {
+      if (ev.button ===1){
+        setDisabled(!disabled);  
+        ev.preventDefault();
+      }
+  };
+  return (
+    <div onMouseDown={onMouseDown} >
+      <MenuItem {...{...props, disabled: props.disabled && disabled }}  />
+    </div>  
+  );
+};
+
 const mapProps = (state: IApplicationStoreState) => ({
   networkElementsProperties: createNetworkElementsProperties(state),
   applicationState: state,
@@ -78,6 +98,7 @@ type NetworkElementsListComponentProps = WithStyles<typeof styles> & Connect<typ
 type NetworkElementsListComponentState = {
   networkElementToEdit: NetworkElementConnection,
   networkElementEditorMode: EditNetworkElementDialogMode,
+  refreshNetworkElementsEditorMode: RefreshNetworkElementsDialogMode,
   infoNetworkElementEditorMode: InfoNetworkElementDialogMode,
   elementInfo: TopologyNode | null
 }
@@ -94,19 +115,21 @@ export class NetworkElementsListComponent extends React.Component<NetworkElement
     this.state = {
       networkElementToEdit: emptyRequireNetworkElement,
       networkElementEditorMode: EditNetworkElementDialogMode.None,
+      refreshNetworkElementsEditorMode: RefreshNetworkElementsDialogMode.None,
       elementInfo: null,
       infoNetworkElementEditorMode: InfoNetworkElementDialogMode.None
     };
   }
-
+  
   getContextMenu(rowData: NetworkElementConnection): JSX.Element[] {
-
-
-
-    const { configuration, fault, inventory } = this.props.applicationState as any;
-    let buttonArray = [
-      <MenuItem aria-label={"mount-button"} onClick={event => this.onOpenMountdNetworkElementsDialog(event, rowData)} ><LinkIcon /><Typography>Mount</Typography></MenuItem>,
-      <MenuItem aria-label={"unmount-button"} onClick={event => this.onOpenUnmountdNetworkElementsDialog(event, rowData)}><LinkOffIcon /><Typography>Unmount</Typography></MenuItem>,
+    const mountUri = rowData.id && connectService.getNetworkElementUri(rowData.id);
+    const mountPolicy = mountUri && getAccessPolicyByUrl(mountUri);
+    const canMount =  mountPolicy && mountPolicy.POST || false;
+    
+    const { configuration} = this.props.applicationState as any;
+    const buttonArray = [
+      <MenuItemExt aria-label={"mount-button"} onClick={event => this.onOpenMountdNetworkElementsDialog(event, rowData)} disabled={!canMount} ><LinkIcon /><Typography>Mount</Typography></MenuItemExt>,
+      <MenuItemExt aria-label={"unmount-button"} onClick={event => this.onOpenUnmountdNetworkElementsDialog(event, rowData)} disabled={!canMount} ><LinkOffIcon /><Typography>Unmount</Typography></MenuItemExt>,
       <Divider />,
       <MenuItem aria-label={"info-button"} onClick={event => this.onOpenInfoNetworkElementDialog(event, rowData)} disabled={rowData.status === "Connecting" || rowData.status === "Disconnected"} ><Info /><Typography>Info</Typography></MenuItem>,
       <MenuItem aria-label={"edit-button"} onClick={event => this.onOpenEditNetworkElementDialog(event, rowData)}><EditIcon /><Typography>Edit</Typography></MenuItem>,
@@ -121,9 +144,9 @@ export class NetworkElementsListComponent extends React.Component<NetworkElement
       <MenuItem onClick={event => this.props.navigateToApplication("security", rowData.nodeId)} disabled={true} ><Typography>Security</Typography></MenuItem>,
     ];
 
-    if (rowData.webUri) {
+    if (rowData.weburi) {
       // add an icon for gui cuttrough, if weburi is available
-      return [<MenuItem aria-label={"web-client-button"} onClick={event => window.open(rowData.webUri, "_blank")} ><ComputerIcon /><Typography>Web Client</Typography></MenuItem>].concat(buttonArray)
+      return [<MenuItem aria-label={"web-client-button"} onClick={event => window.open(rowData.weburi, "_blank")} ><ComputerIcon /><Typography>Web Client</Typography></MenuItem>].concat(buttonArray)
     } else {
       return buttonArray;
     }
@@ -134,6 +157,12 @@ export class NetworkElementsListComponent extends React.Component<NetworkElement
   render(): JSX.Element {
     const { classes } = this.props;
     const { networkElementToEdit } = this.state;
+
+    // const mountUri = rowData.id && connectService.getNetworkElementUri(rowData.id);
+    // const mountPolicy = mountUri && getAccessPolicyByUrl(mountUri);
+    // const canAdd =  mountPolicy && mountPolicy.POST || false;
+    const canAdd = true;
+
     const addRequireNetworkElementAction = {
       icon: AddIcon, tooltip: 'Add', onClick: () => {
         this.setState({
@@ -143,9 +172,17 @@ export class NetworkElementsListComponent extends React.Component<NetworkElement
       }
     };
 
+    const refreshNetworkElementsAction = {
+      icon: Refresh, tooltip: 'Refresh Network Elements table', onClick: () => {
+        this.setState({
+          refreshNetworkElementsEditorMode: RefreshNetworkElementsDialogMode.RefreshNetworkElementsTable
+        });
+      }
+    };
+    
     return (
       <>
-        <NetworkElementTable stickyHeader tableId="network-element-table" customActionButtons={[addRequireNetworkElementAction]} columns={[
+        <NetworkElementTable stickyHeader tableId="network-element-table" customActionButtons={[refreshNetworkElementsAction, ...canAdd ? [addRequireNetworkElementAction]: []]} columns={[
           { property: "nodeId", title: "Node Name", type: ColumnType.text },
           { property: "isRequired", title: "Required", type: ColumnType.boolean },
           { property: "status", title: "Connection Status", type: ColumnType.text },
@@ -163,6 +200,10 @@ export class NetworkElementsListComponent extends React.Component<NetworkElement
           mode={this.state.networkElementEditorMode}
           onClose={this.onCloseEditNetworkElementDialog}
         />
+        <RefreshNetworkElementsDialog
+          mode={this.state.refreshNetworkElementsEditorMode}
+          onClose={this.onCloseRefreshNetworkElementsDialog}
+        />
         <InfoNetworkElementDialog
           initialNetworkElement={networkElementToEdit}
           mode={this.state.infoNetworkElementEditorMode}
@@ -243,7 +284,11 @@ export class NetworkElementsListComponent extends React.Component<NetworkElement
       networkElementToEdit: emptyRequireNetworkElement,
     });
   }
+  private onCloseRefreshNetworkElementsDialog = () => {
+    this.setState({
+      refreshNetworkElementsEditorMode: RefreshNetworkElementsDialogMode.None
+    });
+  }
 }
 
 export const NetworkElementsList = withStyles(styles)(connect(mapProps, mapDispatch)(NetworkElementsListComponent));
-export default NetworkElementsList;
\ No newline at end of file
diff --git a/sdnr/wt/odlux/apps/connectApp/src/components/refreshConnectionStatusLogDialog.tsx b/sdnr/wt/odlux/apps/connectApp/src/components/refreshConnectionStatusLogDialog.tsx
new file mode 100644 (file)
index 0000000..41229ea
--- /dev/null
@@ -0,0 +1,117 @@
+/**
+ * ============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 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 { connectionStatusLogReloadAction } from '../handlers/connectionStatusLogHandler';
+import { IDispatcher, connect, Connect } from '../../../../framework/src/flux/connect';
+
+import { ConnectionStatusLogType } from '../models/connectionStatusLog';
+
+export enum RefreshConnectionStatusLogDialogMode {
+  None = "none",
+  RefreshConnectionStatusLogTable = "RefreshConnectionStatusLogTable",
+}
+
+const mapDispatch = (dispatcher: IDispatcher) => ({
+  refreshConnectionStatusLog: () => dispatcher.dispatch(connectionStatusLogReloadAction)
+});
+
+type DialogSettings = {
+  dialogTitle: string,
+  dialogDescription: string,
+  applyButtonText: string,
+  cancelButtonText: string,
+  enableMountIdEditor: boolean,
+  enableUsernameEditor: boolean,
+  enableExtendedEditor: boolean,
+}
+
+const settings: { [key: string]: DialogSettings } = {
+  [RefreshConnectionStatusLogDialogMode.None]: {
+    dialogTitle: "",
+    dialogDescription: "",
+    applyButtonText: "",
+    cancelButtonText: "",
+    enableMountIdEditor: false,
+    enableUsernameEditor: false,
+    enableExtendedEditor: false,
+  },
+  [RefreshConnectionStatusLogDialogMode.RefreshConnectionStatusLogTable]: {
+    dialogTitle: "Do you want to refresh the Connection Status Log table?",
+    dialogDescription: "",
+    applyButtonText: "Yes",
+    cancelButtonText: "Cancel",
+    enableMountIdEditor: true,
+    enableUsernameEditor: true,
+    enableExtendedEditor: true,
+  }
+}
+
+type RefreshConnectionStatusLogDialogComponentProps = Connect<undefined, typeof mapDispatch> & {
+  mode: RefreshConnectionStatusLogDialogMode;
+  onClose: () => void;
+};
+
+type RefreshConnectionStatusLogDialogComponentState = ConnectionStatusLogType & { isNameValid: boolean, isHostSet: boolean };
+
+class RefreshConnectionStatusLogDialogComponent extends React.Component<RefreshConnectionStatusLogDialogComponentProps, RefreshConnectionStatusLogDialogComponentState> {
+  constructor(props: RefreshConnectionStatusLogDialogComponentProps) {
+    super(props);
+  }
+
+  render(): JSX.Element {
+    const setting = settings[this.props.mode];
+    return (
+      <Dialog open={this.props.mode !== RefreshConnectionStatusLogDialogMode.None}>
+        <DialogTitle id="form-dialog-title" aria-label={`${setting.dialogTitle.replace(/ /g, "-").toLowerCase()}-dialog`}>{setting.dialogTitle}</DialogTitle>
+        <DialogContent>
+          <DialogContentText>
+            {setting.dialogDescription}
+          </DialogContentText>
+        </DialogContent>
+        <DialogActions>
+          <Button aria-label="dialog-confirm-button" onClick={(event) => {
+            this.onRefresh();
+          }} > {setting.applyButtonText} </Button>
+          <Button aria-label="dialog-cancel-button" onClick={(event) => {
+            this.onCancel();
+          }} color="secondary"> {setting.cancelButtonText} </Button>
+        </DialogActions>
+      </Dialog>
+    )
+  }
+
+  private onRefresh = () => {
+    this.props.refreshConnectionStatusLog();
+    this.props.onClose();
+  };
+
+  private onCancel = () => {
+    this.props.onClose();
+  }
+}
+
+export const RefreshConnectionStatusLogDialog = connect(undefined, mapDispatch)(RefreshConnectionStatusLogDialogComponent);
+export default RefreshConnectionStatusLogDialog;
\ No newline at end of file
diff --git a/sdnr/wt/odlux/apps/connectApp/src/components/refreshNetworkElementsDialog.tsx b/sdnr/wt/odlux/apps/connectApp/src/components/refreshNetworkElementsDialog.tsx
new file mode 100644 (file)
index 0000000..a349977
--- /dev/null
@@ -0,0 +1,117 @@
+/**
+ * ============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 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 { networkElementsReloadAction } from '../handlers/networkElementsHandler';
+import { IDispatcher, connect, Connect } from '../../../../framework/src/flux/connect';
+
+import { NetworkElementConnection } from '../models/networkElementConnection';
+
+export enum RefreshNetworkElementsDialogMode {
+  None = "none",
+  RefreshNetworkElementsTable = "RefreshNetworkElementsTable",
+}
+
+const mapDispatch = (dispatcher: IDispatcher) => ({
+  refreshNetworkElement: () => dispatcher.dispatch(networkElementsReloadAction)
+});
+
+type DialogSettings = {
+  dialogTitle: string,
+  dialogDescription: string,
+  applyButtonText: string,
+  cancelButtonText: string,
+  enableMountIdEditor: boolean,
+  enableUsernameEditor: boolean,
+  enableExtendedEditor: boolean,
+}
+
+const settings: { [key: string]: DialogSettings } = {
+  [RefreshNetworkElementsDialogMode.None]: {
+    dialogTitle: "",
+    dialogDescription: "",
+    applyButtonText: "",
+    cancelButtonText: "",
+    enableMountIdEditor: false,
+    enableUsernameEditor: false,
+    enableExtendedEditor: false,
+  },
+  [RefreshNetworkElementsDialogMode.RefreshNetworkElementsTable]: {
+    dialogTitle: "Do you want to refresh the Network Elements table?",
+    dialogDescription: "",
+    applyButtonText: "Yes",
+    cancelButtonText: "Cancel",
+    enableMountIdEditor: true,
+    enableUsernameEditor: true,
+    enableExtendedEditor: true,
+  }
+}
+
+type RefreshNetworkElementsDialogComponentProps = Connect<undefined, typeof mapDispatch> & {
+  mode: RefreshNetworkElementsDialogMode;
+  onClose: () => void;
+};
+
+type RefreshNetworkElementsDialogComponentState = NetworkElementConnection & { isNameValid: boolean, isHostSet: boolean };
+
+class RefreshNetworkElementsDialogComponent extends React.Component<RefreshNetworkElementsDialogComponentProps, RefreshNetworkElementsDialogComponentState> {
+  constructor(props: RefreshNetworkElementsDialogComponentProps) {
+    super(props);
+  }
+
+  render(): JSX.Element {
+    const setting = settings[this.props.mode];
+    return (
+      <Dialog open={this.props.mode !== RefreshNetworkElementsDialogMode.None}>
+        <DialogTitle id="form-dialog-title" aria-label={`${setting.dialogTitle.replace(/ /g, "-").toLowerCase()}-dialog`}>{setting.dialogTitle}</DialogTitle>
+        <DialogContent>
+          <DialogContentText>
+            {setting.dialogDescription}
+          </DialogContentText>
+        </DialogContent>
+        <DialogActions>
+          <Button aria-label="dialog-confirm-button" onClick={(event) => {
+            this.onRefresh();
+          }} > {setting.applyButtonText} </Button>
+          <Button aria-label="dialog-cancel-button" onClick={(event) => {
+            this.onCancel();
+          }} color="secondary"> {setting.cancelButtonText} </Button>
+        </DialogActions>
+      </Dialog>
+    )
+  }
+
+  private onRefresh = () => {
+    this.props.refreshNetworkElement();
+    this.props.onClose();
+  };
+
+  private onCancel = () => {
+    this.props.onClose();
+  }
+}
+
+export const RefreshNetworkElementsDialog = connect(undefined, mapDispatch)(RefreshNetworkElementsDialogComponent);
+export default RefreshNetworkElementsDialog;
\ No newline at end of file
index 302a981..c23e439 100644 (file)
@@ -68,7 +68,7 @@ const guiCutThroughHandler: IActionHandler<guiCutThroughState> = (state = { sear
 
   } else if (action instanceof RemoveWebUri) {
     const nodeId = action.element;
-    const webUris = state.searchedElements.filter(item => item.nodeId !== nodeId);
+    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  };
index 4fe858c..b74a394 100644 (file)
  */
 import { createExternal, IExternalTableState } from '../../../../framework/src/components/material-table/utilities';
 import { createSearchDataHandler } from '../../../../framework/src/utilities/elasticSearch';
+import { getAccessPolicyByUrl } from '../../../../framework/src/services/restService';
 
 import { NetworkElementConnection } from '../models/networkElementConnection';
-import connectService from '../services/connectService';
-import { requestRest } from '../../../../framework/src/services/restService';
+import { connectService } from '../services/connectService';
+
 export interface INetworkElementsState extends IExternalTableState<NetworkElementConnection> { }
 
 // create eleactic search material data fetch handler
@@ -31,8 +32,7 @@ export const {
   createActions: createNetworkElementsActions,
   createProperties: createNetworkElementsProperties,
   reloadAction: networkElementsReloadAction,
-  reloadActionAsync: networkElementsReloadActionAsync
-
+  
   // set value action, to change a value
 } = createExternal<NetworkElementConnection>(networkElementsSearchHandler, appState => {
 
@@ -43,9 +43,9 @@ export const {
     appState.connect.networkElements.rows.forEach(element => {
 
       if (element.status === "Connected") {
-        const webUri = webUris.find(item => item.nodeId === element.id as string);
+        const webUri = webUris.find(item => item.id === element.id as string);
         if (webUri) {
-          element.webUri = webUri.webUri;
+          element.weburi = webUri.weburi;
           element.isWebUriUnreachable = false;
         }
         else {
@@ -56,5 +56,10 @@ export const {
   }
 
   return appState.connect.networkElements
+}, (ne) => {
+  if (!ne || !ne.id) return true;
+  const neUrl = connectService.getNetworkElementUri(ne.id);
+  const policy = getAccessPolicyByUrl(neUrl);
+  return !(policy.GET || policy.POST);
 });
 
index 9c8a206..6f44c25 100644 (file)
@@ -19,6 +19,7 @@
       connectApp.register();
       faultApp.register();
       inventoryApp.register();
+      app("./app.tsx").configureApplication({ authentication:"basic",  enablePolicy:  false,});
       app("./app.tsx").runApplication();
     });
   </script>
index d44eea6..b9f515d 100644 (file)
@@ -16,4 +16,7 @@
  * ============LICENSE_END==========================================================================
  */
 
-export type guiCutThrough = { webUri?: string, nodeId: string }
\ No newline at end of file
+export type guiCutThrough = {
+    id: string,
+    weburi?: string
+}
\ No newline at end of file
index b3586d6..71eddc8 100644 (file)
@@ -24,7 +24,7 @@ export type NetworkElementConnection = {
   port: number;
   username?: string;
   password?: string;
-  webUri?: string;
+  weburi?: string;
   isWebUriUnreachable?: boolean;
   status?: "Connected" | "mounted" | "unmounted" | "Connecting" | "Disconnected" | "idle";
   coreModelCapability?: string;
index f711c44..93bed1a 100644 (file)
@@ -20,19 +20,12 @@ import { faPlug } from '@fortawesome/free-solid-svg-icons';
 
 import applicationManager from '../../../framework/src/services/applicationManager';
 import { subscribe, IFormatedMessage } from '../../../framework/src/services/notificationService';
+import { AddSnackbarNotification } from '../../../framework/src/actions/snackbarActions';
 
 import connectAppRootHandler from './handlers/connectAppRootHandler';
 import ConnectApplication from './views/connectView';
 
-import { AddSnackbarNotification } from '../../../framework/src/actions/snackbarActions';
-import { updateCurrentViewAsyncAction } from './actions/commonNetworkElementsActions';
-
-type ObjectNotification = {
-  counter: string;
-  nodeName: string;
-  objectId: string;
-  timeStamp: string;
-}
+import { findWebUrisForGuiCutThroughAsyncAction, updateCurrentViewAsyncAction } from './actions/commonNetworkElementsActions';
 
 export function register() {
   const applicationApi = applicationManager.registerApplication({
@@ -44,13 +37,19 @@ export function register() {
   });
 
   // subscribe to the websocket notifications
-  subscribe<ObjectNotification & IFormatedMessage>(["ObjectCreationNotification", "ObjectDeletionNotification", "AttributeValueChangedNotification"], (msg => {
+  subscribe<IFormatedMessage>(["object-creation-notification", "object-deletion-notification", "attribute-value-changed-notification"], (msg => {
     const store = applicationApi.applicationStore;
-    if (msg && msg.notifType === "ObjectCreationNotification" && store) {
-      store.dispatch(new AddSnackbarNotification({ message: `Adding network element [${msg.objectId}]`, options: { variant: 'info' } }));
-    } else if (msg && (msg.notifType === "ObjectDeletionNotification" || msg.notifType === "AttributeValueChangedNotification") && store) {
-      store.dispatch(new AddSnackbarNotification({ message: `Updating network element [${msg.objectId}]`, options: { variant: 'info' } }));
+    if (msg && msg.type.type === "object-creation-notification" && store) {
+      store.dispatch(new AddSnackbarNotification({ message: `Adding network element [${msg['node-id']}]`, options: { variant: 'info' } }));
+    } else if (msg && (msg.type.type === "object-deletion-notification" || msg.type.type === "attribute-value-changed-notification") && store) {
+      store.dispatch(new AddSnackbarNotification({ message: `Updating network element [${msg['node-id']}]`, options: { variant: 'info' } }));
+    }
+    if (store) {
+      store.dispatch(updateCurrentViewAsyncAction() as any).then(() => {
+        if (msg['node-id']) {
+          store.dispatch(findWebUrisForGuiCutThroughAsyncAction([msg['node-id']]));
+        }
+      });
     }
-    store && store.dispatch(updateCurrentViewAsyncAction());
   }));
 }
\ No newline at end of file
index fbbfa68..5d7667a 100644 (file)
@@ -28,12 +28,15 @@ 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 = `/rests/operations/data-provider:create-network-element-connection`;
+    const path = this.getNetworkElementConnectDataProviderUri("create") ;
     const result = await requestRest<NetworkElementConnection>(path, {
       method: "POST", body: JSON.stringify(convertPropertyNames({ "data-provider:input": element }, replaceUpperCase))
     });
@@ -44,7 +47,7 @@ class ConnectService {
   * Updates a network element.
   */
   public async updateNetworkElement(element: UpdateNetworkElement): Promise<NetworkElementConnection | null> {
-    const path = `/rests/operations/data-provider:update-network-element-connection`;
+    const path = this.getNetworkElementConnectDataProviderUri("update");
     const result = await requestRest<NetworkElementConnection>(path, {
       method: "POST", body: JSON.stringify(convertPropertyNames({ "data-provider:input": element }, replaceUpperCase))
     });
@@ -58,7 +61,7 @@ class ConnectService {
     const query = {
       "id": element.id
     };
-    const path = `/rests/operations/data-provider:delete-network-element-connection`;
+    const path = this.getNetworkElementConnectDataProviderUri("delete");
     const result = await requestRest<NetworkElementConnection>(path, {
       method: "POST", body: JSON.stringify(convertPropertyNames({ "data-provider:input": query }, replaceUpperCase))
     });
@@ -67,7 +70,7 @@ class ConnectService {
 
   /** Mounts network element. */
   public async mountNetworkElement(networkElement: NetworkElementConnection): Promise<boolean> {
-    const path = '/rests/data/network-topology:network-topology/topology=topology-netconf/node=' + networkElement.nodeId;
+    const path = this.getNetworkElementUri(networkElement.nodeId);
     const mountXml = [
       '<node xmlns="urn:TBD:params:xml:ns:yang:network-topology">',
       `<node-id>${networkElement.nodeId}</node-id>`,
@@ -106,7 +109,7 @@ class ConnectService {
 
   /** Unmounts a network element by its id. */
   public async unmountNetworkElement(nodeId: string): Promise<boolean> {
-    const path = '/rests/data/network-topology:network-topology/topology=topology-netconf/node=' + nodeId;
+    const path = this.getNetworkElementUri(nodeId);
 
     try {
       const result = await requestRest<string>(path, {
@@ -126,7 +129,7 @@ class ConnectService {
 
   /** Yang capabilities of the selected network elements. */
   public async infoNetworkElement(nodeId: string): Promise<TopologyNode | null> {
-    const path = '/rests/data/network-topology:network-topology/topology=topology-netconf/node=' + nodeId;
+    const path = this.getNetworkElementUri(nodeId);
     const topologyRequestPomise = requestRest<Topology>(path, { method: "GET" });
 
     return topologyRequestPomise && topologyRequestPomise.then(result => {
@@ -157,38 +160,80 @@ class ConnectService {
     })) || null;
   }
 
-  public getAllWebUriExtensionsForNetworkElementListAsync(ne: string[]) {
-
-    let promises: any[] = [];
-    let webUris: guiCutThrough[] = []
-
-    ne.forEach(nodeId => {
-      const path = '/rests/data/network-topology:network-topology/topology=topology-netconf/node=' + nodeId + '/yang-ext:mount/core-model:network-element';
+  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
+        }
+      }
+    }
 
-      // 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, nodeId: nodeId });
-            } else {
-              webUris.push({ webUri: undefined, nodeId: nodeId });
+    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;
             }
-          } else {
-            webUris.push({ webUri: undefined, nodeId: nodeId });
-          }
-        })
-        .catch(error => {
-          webUris.push({ webUri: undefined, nodeId: nodeId });
-        }))
-
-    })
-
-    // wait until all promises are done and return weburis
-    return Promise.all(promises).then(result => { return webUris });
+          });
+        } 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();
-export default connectService;
index 1fa5e19..34b1b94 100644 (file)
@@ -40,10 +40,9 @@ const mapProps = (state: IApplicationStoreState) => ({
 const mapDispatcher = (dispatcher: IDispatcher) => ({
   networkElementsActions: createNetworkElementsActions(dispatcher.dispatch),
   connectionStatusLogActions: createConnectionStatusLogActions(dispatcher.dispatch),
-  onLoadNetworkElements: () => {
-    dispatcher.dispatch(networkElementsReloadAction);
-  },
-  loadWebUris: async (networkElements: NetworkElementConnection[]) => { await dispatcher.dispatch(findWebUrisForGuiCutThroughAsyncAction(networkElements)) },
+  onLoadNetworkElements: () => dispatcher.dispatch(networkElementsReloadAction),
+  loadWebUris: (networkElements: NetworkElementConnection[]) => 
+    dispatcher.dispatch(findWebUrisForGuiCutThroughAsyncAction(networkElements.map((ne) => ne.id!))),
   isBusy: (busy: boolean) => dispatcher.dispatch(new SetWeburiSearchBusy(busy)),
   onLoadConnectionStatusLog: () => {
     dispatcher.dispatch(connectionStatusLogReloadAction);
@@ -65,12 +64,14 @@ class ConnectApplicationComponent extends React.Component<ConnectApplicationComp
     //this.props.connectionStatusLogActions.onToggleFilter();
   }
 
-  public componentDidUpdate = async () => {
-    // search for guicutthroughs after networkelements were found
+  public componentDidUpdate = () => {
+    
     const networkElements = this.props.netWorkElements;
 
     if (networkElements.rows.length > 0) {
-      await this.props.loadWebUris(networkElements.rows);
+      // Update all netWorkElements for propper WebUriClient settings in case of table data changes.
+      // e.G: Pagination of the table data (there is no event)
+      this.props.loadWebUris(networkElements.rows);
     }
   }
 
index 7f36e4e..df88a80 100644 (file)
@@ -11,6 +11,8 @@ const webpack = require("webpack");
 const CopyWebpackPlugin = require("copy-webpack-plugin");
 const TerserPlugin = require('terser-webpack-plugin');
 
+const policies = require('./policies.json');
+
 // const __dirname = (path => path.replace(/^([a-z]\:)/, c => c.toUpperCase()))(process.__dirname());
 
 module.exports = (env) => {
@@ -124,34 +126,54 @@ module.exports = (env) => {
       stats: {
         colors: true
       },
-      proxy: {
-        "/oauth2/": {
-          target: "http://sdnr:8181",
+      before: function(app, server, compiler) {
+        app.get('/oauth/policies',(_, res) => res.json(policies));
+      },
+       proxy: {
+        "/about": {
+          target: "http://localhost:18181",
+          secure: false
+        }, 
+        "/yang-schema/": {
+          target: "http://localhost:18181",
+          secure: false
+        },   
+        "/oauth/": {
+          target: "http://localhost:18181",
           secure: false
         },
-        
-          "/oauth/": {
-            target: "http://sdnr:8181",
-            secure: false
-          },
         "/database/": {
-          target: "http://sdnr:8181",
+          target: "http://localhost:18181",
           secure: false
         },
         "/restconf/": {
-          target: "http://sdnr:8181",
+          target: "http://localhost:18181",
           secure: false
         },
         "/rests/": {
-          target: "http://sdnr:8181",
+          target: "http://localhost:18181",
           secure: false
         },
         "/help/": {
-          target: "http://sdnr:8181",
+          target: "http://localhost:18181",
+          secure: false
+        },
+         "/about/": {
+          target: "http://localhost:18181",
+          secure: false
+        },
+        "/tree/": {
+          target: "http://localhost:18181",
           secure: false
         },
         "/websocket": {
-          target: "http://sdnr:8181",
+          target: "http://localhost:18181",
+          ws: true,
+          changeOrigin: true,
+          secure: false
+        },
+        "/apidoc": {
+          target: "http://localhost:18181",
           ws: true,
           changeOrigin: true,
           secure: false
index 9505cfb..7756fe3 100644 (file)
     "@odlux/framework": "*"
   },
   "peerDependencies": {
-    "@types/react": "16.9.19",
-    "@types/react-dom": "16.9.5",
-    "@types/react-router-dom": "4.3.1",
+    "@types/react": "17.0.3",
+    "@types/react-dom": "17.0.2",
+    "@types/react-router-dom": "5.1.7",
     "@material-ui/core": "4.11.0",
     "@material-ui/icons": "4.9.1",
     "@types/classnames": "2.2.6",
     "@types/flux": "3.1.8",
     "@types/jquery": "3.3.10",
     "jquery": "3.3.1",
-    "react": "16.12.0",
-    "react-dom": "16.12.0",
-    "react-router-dom": "4.3.1"
+    "react": "17.0.1",
+    "react-dom": "17.0.1",
+    "react-router-dom": "5.2.0"
   }
 }
\ No newline at end of file
index e85bcbb..521c890 100644 (file)
@@ -17,6 +17,7 @@
     // run the application
     require(["app","demoApp"], function (app, demoApp) {
       demoApp.register();
+      app("./app.tsx").configureApplication({ authentication:"oauth",  enablePolicy:  true,});
       app("./app.tsx");
     });
   </script>
index 4e98a17..dfcebfe 100644 (file)
     "@odlux/framework": "*"
   },
   "peerDependencies": {
-    "@types/react": "16.9.19",
-    "@types/react-dom": "16.9.5",
-    "@types/react-router-dom": "4.3.1",
+    "@types/react": "17.0.3",
+    "@types/react-dom": "17.0.2",
+    "@types/react-router-dom": "5.1.7",
     "@material-ui/core": "4.11.0",
     "@material-ui/icons": "4.9.1",
     "@types/classnames": "2.2.6",
     "@types/flux": "3.1.8",
     "@types/jquery": "3.3.10",
     "jquery": "3.3.1",
-    "react": "16.12.0",
-    "react-dom": "16.12.0",
-    "react-router-dom": "4.3.1"
+    "react": "17.0.1",
+    "react-dom": "17.0.1",
+    "react-router-dom": "5.2.0"
   }
 }
\ No newline at end of file
diff --git a/sdnr/wt/odlux/apps/eventLogApp/src/components/refreshEventLogDialog.tsx b/sdnr/wt/odlux/apps/eventLogApp/src/components/refreshEventLogDialog.tsx
new file mode 100644 (file)
index 0000000..7d86e9f
--- /dev/null
@@ -0,0 +1,117 @@
+/**
+ * ============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 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 { eventLogReloadAction } from '../handlers/eventLogHandler';
+import { IDispatcher, connect, Connect } from '../../../../framework/src/flux/connect';
+
+import { EventLogType } from '../models/eventLogType';
+
+export enum RefreshEventLogDialogMode {
+  None = "none",
+  RefreshEventLogTable = "RefreshEventLogTable",
+}
+
+const mapDispatch = (dispatcher: IDispatcher) => ({
+  refreshEventLog: () => dispatcher.dispatch(eventLogReloadAction)
+});
+
+type DialogSettings = {
+  dialogTitle: string,
+  dialogDescription: string,
+  applyButtonText: string,
+  cancelButtonText: string,
+  enableMountIdEditor: boolean,
+  enableUsernameEditor: boolean,
+  enableExtendedEditor: boolean,
+}
+
+const settings: { [key: string]: DialogSettings } = {
+  [RefreshEventLogDialogMode.None]: {
+    dialogTitle: "",
+    dialogDescription: "",
+    applyButtonText: "",
+    cancelButtonText: "",
+    enableMountIdEditor: false,
+    enableUsernameEditor: false,
+    enableExtendedEditor: false,
+  },
+  [RefreshEventLogDialogMode.RefreshEventLogTable]: {
+    dialogTitle: "Do you want to refresh the Event Log?",
+    dialogDescription: "",
+    applyButtonText: "Yes",
+    cancelButtonText: "Cancel",
+    enableMountIdEditor: true,
+    enableUsernameEditor: true,
+    enableExtendedEditor: true,
+  }
+}
+
+type RefreshEventLogDialogComponentProps = Connect<undefined, typeof mapDispatch> & {
+  mode: RefreshEventLogDialogMode;
+  onClose: () => void;
+};
+
+type RefreshEventLogDialogComponentState = EventLogType & { isNameValid: boolean, isHostSet: boolean };
+
+class RefreshEventLogDialogComponent extends React.Component<RefreshEventLogDialogComponentProps, RefreshEventLogDialogComponentState> {
+  constructor(props: RefreshEventLogDialogComponentProps) {
+    super(props);
+  }
+
+  render(): JSX.Element {
+    const setting = settings[this.props.mode];
+    return (
+      <Dialog open={this.props.mode !== RefreshEventLogDialogMode.None}>
+        <DialogTitle id="form-dialog-title" aria-label={`${setting.dialogTitle.replace(/ /g, "-").toLowerCase()}-dialog`}>{setting.dialogTitle}</DialogTitle>
+        <DialogContent>
+          <DialogContentText>
+            {setting.dialogDescription}
+          </DialogContentText>
+        </DialogContent>
+        <DialogActions>
+          <Button aria-label="dialog-confirm-button" onClick={(event) => {
+            this.onRefresh();
+          }} > {setting.applyButtonText} </Button>
+          <Button aria-label="dialog-cancel-button" onClick={(event) => {
+            this.onCancel();
+          }} color="secondary"> {setting.cancelButtonText} </Button>
+        </DialogActions>
+      </Dialog>
+    )
+  }
+
+  private onRefresh = () => {
+    this.props.refreshEventLog();
+    this.props.onClose();
+  };
+
+  private onCancel = () => {
+    this.props.onClose();
+  }
+}
+
+export const RefreshEventLogDialog = connect(undefined, mapDispatch)(RefreshEventLogDialogComponent);
+export default RefreshEventLogDialog;
\ No newline at end of file
index 3d81e4e..748c66e 100644 (file)
@@ -19,10 +19,12 @@ import * as React from "react";
 
 import { Connect, connect, IDispatcher } from '../../../../framework/src/flux/connect';
 import { MaterialTable, MaterialTableCtorType } from '../../../../framework/src/components/material-table';
+import Refresh from '@material-ui/icons/Refresh';
 
 import { EventLogType } from '../models/eventLogType';
 import { IApplicationStoreState } from "../../../../framework/src/store/applicationStore";
 import { createEventLogProperties, createEventLogActions } from "../handlers/eventLogHandler";
+import RefreshEventLogDialog, { RefreshEventLogDialogMode } from '../components/refreshEventLogDialog';
 
 const EventLogTable = MaterialTable as MaterialTableCtorType<EventLogType & { _id: string }>;
 
@@ -35,22 +37,56 @@ const mapDispatch = (dispatcher: IDispatcher) => ({
   eventLogActions: createEventLogActions(dispatcher.dispatch)
 });
 
+type EventLogComponentProps = Connect<typeof mapProps, typeof mapDispatch>;
+type EventLogComponentState = {
+  refreshEventLogEditorMode: RefreshEventLogDialogMode
+}
 let initalSorted = false;
 
-class EventLogComponent extends React.Component<Connect<typeof mapProps, typeof mapDispatch>> {
-  render() {
-    return <EventLogTable stickyHeader title="Event Log" tableId="event-log-table" idProperty="_id" columns={[
-      { property: "nodeId", title: "Node Name" },
-      { property: "counter", title: "Counter" },
-      { property: "timestamp", title: "Timestamp" },
-      { property: "objectId", title: "Object ID" },
-      { property: "attributeName", title: "Attribute Name" },
-      { property: "newValue", title: "Message" },
-      { property: "sourceType", title: "Source" }
-    ]}  {...this.props.eventLogActions} {...this.props.eventLogProperties} >
-    </EventLogTable>
+class EventLogComponent extends React.Component<EventLogComponentProps, EventLogComponentState> {
+  constructor(props: EventLogComponentProps) {
+    super(props);
+
+    this.state = {
+      refreshEventLogEditorMode: RefreshEventLogDialogMode.None
+    };
+  }
+
+  render(): JSX.Element {
+
+    const refreshEventLogAction = {
+      icon: Refresh, tooltip: 'Refresh Event log', onClick: () => {
+        this.setState({
+          refreshEventLogEditorMode: RefreshEventLogDialogMode.RefreshEventLogTable
+        });
+      }
+    };
+    return (
+      <>
+        <EventLogTable stickyHeader title="Event Log" tableId="event-log-table" idProperty="_id" customActionButtons={[refreshEventLogAction]}
+          columns={[
+            { property: "nodeId", title: "Node Name" },
+            { property: "counter", title: "Counter" },
+            { property: "timestamp", title: "Timestamp" },
+            { property: "objectId", title: "Object ID" },
+            { property: "attributeName", title: "Attribute Name" },
+            { property: "newValue", title: "Message" },
+            { property: "sourceType", title: "Source" }
+          ]}  {...this.props.eventLogActions} {...this.props.eventLogProperties} >
+        </EventLogTable>
+        <RefreshEventLogDialog
+          mode={this.state.refreshEventLogEditorMode}
+          onClose={this.onCloseRefreshEventLogDialog}
+        />
+      </>
+    )
   }
 
+  private onCloseRefreshEventLogDialog = () => {
+    this.setState({
+      refreshEventLogEditorMode: RefreshEventLogDialogMode.None
+    });
+  }
   componentDidMount() {
 
     if (!initalSorted) {
index 7095253..5d1439b 100644 (file)
     "@odlux/framework": "*"
   },
   "peerDependencies": {
-    "@types/react": "16.9.19",
-    "@types/react-dom": "16.9.5",
-    "@types/react-router-dom": "4.3.1",
+    "@types/react": "17.0.3",
+    "@types/react-dom": "17.0.2",
+    "@types/react-router-dom": "5.1.7",
     "@material-ui/core": "4.11.0",
     "@material-ui/icons": "4.9.1",
     "@types/classnames": "2.2.6",
     "@types/flux": "3.1.8",
     "@types/jquery": "3.3.10",
     "jquery": "3.3.1",
-    "react": "16.12.0",
-    "react-dom": "16.12.0",
-    "react-router-dom": "4.3.1"
+    "react": "17.0.1",
+    "react-dom": "17.0.1",
+    "react-router-dom": "5.2.0"
   }
 }
\ No newline at end of file
diff --git a/sdnr/wt/odlux/apps/faultApp/src/components/refreshAlarmLogDialog.tsx b/sdnr/wt/odlux/apps/faultApp/src/components/refreshAlarmLogDialog.tsx
new file mode 100644 (file)
index 0000000..1d1b34c
--- /dev/null
@@ -0,0 +1,117 @@
+/**
+ * ============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 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 { alarmLogEntriesReloadAction } from '../handlers/alarmLogEntriesHandler';
+import { IDispatcher, connect, Connect } from '../../../../framework/src/flux/connect';
+
+import { Fault } from '../models/fault';
+
+export enum RefreshAlarmLogDialogMode {
+  None = "none",
+  RefreshAlarmLogTable = "RefreshAlarmLogTable",
+}
+
+const mapDispatch = (dispatcher: IDispatcher) => ({
+  refreshAlarmLog: () => dispatcher.dispatch(alarmLogEntriesReloadAction)
+});
+
+type DialogSettings = {
+  dialogTitle: string,
+  dialogDescription: string,
+  applyButtonText: string,
+  cancelButtonText: string,
+  enableMountIdEditor: boolean,
+  enableUsernameEditor: boolean,
+  enableExtendedEditor: boolean,
+}
+
+const settings: { [key: string]: DialogSettings } = {
+  [RefreshAlarmLogDialogMode.None]: {
+    dialogTitle: "",
+    dialogDescription: "",
+    applyButtonText: "",
+    cancelButtonText: "",
+    enableMountIdEditor: false,
+    enableUsernameEditor: false,
+    enableExtendedEditor: false,
+  },
+  [RefreshAlarmLogDialogMode.RefreshAlarmLogTable]: {
+    dialogTitle: "Do you want to refresh the Alarm Log?",
+    dialogDescription: "",
+    applyButtonText: "Yes",
+    cancelButtonText: "Cancel",
+    enableMountIdEditor: true,
+    enableUsernameEditor: true,
+    enableExtendedEditor: true,
+  }
+}
+
+type RefreshAlarmLogDialogComponentProps = Connect<undefined, typeof mapDispatch> & {
+  mode: RefreshAlarmLogDialogMode;
+  onClose: () => void;
+};
+
+type RefreshAlarmLogDialogComponentState = Fault & { isNameValid: boolean, isHostSet: boolean };
+
+class RefreshAlarmLogDialogComponent extends React.Component<RefreshAlarmLogDialogComponentProps, RefreshAlarmLogDialogComponentState> {
+  constructor(props: RefreshAlarmLogDialogComponentProps) {
+    super(props);
+  }
+
+  render(): JSX.Element {
+    const setting = settings[this.props.mode];
+    return (
+      <Dialog open={this.props.mode !== RefreshAlarmLogDialogMode.None}>
+        <DialogTitle id="form-dialog-title" aria-label={`${setting.dialogTitle.replace(/ /g, "-").toLowerCase()}-dialog`}>{setting.dialogTitle}</DialogTitle>
+        <DialogContent>
+          <DialogContentText>
+            {setting.dialogDescription}
+          </DialogContentText>
+        </DialogContent>
+        <DialogActions>
+          <Button aria-label="dialog-confirm-button" onClick={(event) => {
+            this.onRefresh();
+          }} > {setting.applyButtonText} </Button>
+          <Button aria-label="dialog-cancel-button" onClick={(event) => {
+            this.onCancel();
+          }} color="secondary"> {setting.cancelButtonText} </Button>
+        </DialogActions>
+      </Dialog>
+    )
+  }
+
+  private onRefresh = () => {
+    this.props.refreshAlarmLog();
+    this.props.onClose();
+  };
+
+  private onCancel = () => {
+    this.props.onClose();
+  }
+}
+
+export const RefreshAlarmLogDialog = connect(undefined, mapDispatch)(RefreshAlarmLogDialogComponent);
+export default RefreshAlarmLogDialog;
\ No newline at end of file
diff --git a/sdnr/wt/odlux/apps/faultApp/src/components/refreshCurrentProblemsDialog.tsx b/sdnr/wt/odlux/apps/faultApp/src/components/refreshCurrentProblemsDialog.tsx
new file mode 100644 (file)
index 0000000..c497220
--- /dev/null
@@ -0,0 +1,117 @@
+/**
+ * ============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 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 { currentProblemsReloadAction } from '../handlers/currentProblemsHandler';
+import { IDispatcher, connect, Connect } from '../../../../framework/src/flux/connect';
+
+import { Fault } from '../models/fault';
+
+export enum RefreshCurrentProblemsDialogMode {
+    None = "none",
+    RefreshCurrentProblemsTable = "RefreshCurrentProblemsTable",
+}
+
+const mapDispatch = (dispatcher: IDispatcher) => ({
+    refreshCurrentProblems: () => dispatcher.dispatch(currentProblemsReloadAction)
+});
+
+type DialogSettings = {
+    dialogTitle: string,
+    dialogDescription: string,
+    applyButtonText: string,
+    cancelButtonText: string,
+    enableMountIdEditor: boolean,
+    enableUsernameEditor: boolean,
+    enableExtendedEditor: boolean,
+}
+
+const settings: { [key: string]: DialogSettings } = {
+    [RefreshCurrentProblemsDialogMode.None]: {
+        dialogTitle: "",
+        dialogDescription: "",
+        applyButtonText: "",
+        cancelButtonText: "",
+        enableMountIdEditor: false,
+        enableUsernameEditor: false,
+        enableExtendedEditor: false,
+    },
+    [RefreshCurrentProblemsDialogMode.RefreshCurrentProblemsTable]: {
+        dialogTitle: "Do you want to refresh the Current Problems List?",
+        dialogDescription: "",
+        applyButtonText: "Yes",
+        cancelButtonText: "Cancel",
+        enableMountIdEditor: true,
+        enableUsernameEditor: true,
+        enableExtendedEditor: true,
+    }
+}
+
+type RefreshCurrentProblemsDialogComponentProps = Connect<undefined, typeof mapDispatch> & {
+    mode: RefreshCurrentProblemsDialogMode;
+    onClose: () => void;
+};
+
+type RefreshCurrentProblemsDialogComponentState = Fault & { isNameValid: boolean, isHostSet: boolean };
+
+class RefreshCurrentProblemsDialogComponent extends React.Component<RefreshCurrentProblemsDialogComponentProps, RefreshCurrentProblemsDialogComponentState> {
+    constructor(props: RefreshCurrentProblemsDialogComponentProps) {
+        super(props);
+    }
+
+    render(): JSX.Element {
+        const setting = settings[this.props.mode];
+        return (
+            <Dialog open={this.props.mode !== RefreshCurrentProblemsDialogMode.None}>
+                <DialogTitle id="form-dialog-title" aria-label={`${setting.dialogTitle.replace(/ /g, "-").toLowerCase()}-dialog`}>{setting.dialogTitle}</DialogTitle>
+                <DialogContent>
+                    <DialogContentText>
+                        {setting.dialogDescription}
+                    </DialogContentText>
+                </DialogContent>
+                <DialogActions>
+                    <Button aria-label="dialog-confirm-button" onClick={(event) => {
+                        this.onRefresh();
+                    }} > {setting.applyButtonText} </Button>
+                    <Button aria-label="dialog-cancel-button" onClick={(event) => {
+                        this.onCancel();
+                    }} color="secondary"> {setting.cancelButtonText} </Button>
+                </DialogActions>
+            </Dialog>
+        )
+    }
+
+    private onRefresh = () => {
+        this.props.refreshCurrentProblems();
+        this.props.onClose();
+    };
+
+    private onCancel = () => {
+        this.props.onClose();
+    }
+}
+
+export const RefreshCurrentProblemsDialog = connect(undefined, mapDispatch)(RefreshCurrentProblemsDialogComponent);
+export default RefreshCurrentProblemsDialog;
\ No newline at end of file
index 2c167c5..d313a20 100644 (file)
@@ -35,8 +35,12 @@ import { createCurrentProblemsProperties, createCurrentProblemsActions, currentP
 import { createAlarmLogEntriesProperties, createAlarmLogEntriesActions, alarmLogEntriesReloadAction } from '../handlers/alarmLogEntriesHandler';
 import { setPanelAction } from '../actions/panelChangeActions';
 import { Tooltip, IconButton, AppBar, Tabs, Tab } from '@material-ui/core';
-import RefreshIcon from '@material-ui/icons/Refresh';
+import Sync from '@material-ui/icons/Sync';
+import Refresh from '@material-ui/icons/Refresh';
+
 import ClearStuckAlarmsDialog, { ClearStuckAlarmsDialogMode } from '../components/clearStuckAlarmsDialog';
+import RefreshAlarmLogDialog, { RefreshAlarmLogDialogMode } from '../components/refreshAlarmLogDialog';
+import RefreshCurrentProblemsDialog, { RefreshCurrentProblemsDialogMode } from '../components/refreshCurrentProblemsDialog';
 import { SetPartialUpdatesAction } from '../actions/partialUpdatesAction';
 
 const mapProps = (state: IApplicationStoreState) => ({
@@ -61,7 +65,9 @@ type FaultApplicationComponentProps = RouteComponentProps & Connect<typeof mapPr
 
 type FaultApplicationState = {
   clearAlarmDialogMode: ClearStuckAlarmsDialogMode,
-  stuckAlarms: string[]
+  stuckAlarms: string[],
+  refreshAlarmLogEditorMode: RefreshAlarmLogDialogMode,
+  refreshCurrentProblemsEditorMode: RefreshCurrentProblemsDialogMode
 }
 
 
@@ -78,7 +84,12 @@ class FaultApplicationComponent extends React.Component<FaultApplicationComponen
    */
   constructor(props: FaultApplicationComponentProps) {
     super(props);
-    this.state = { clearAlarmDialogMode: ClearStuckAlarmsDialogMode.None, stuckAlarms: [] }
+    this.state = {
+      clearAlarmDialogMode: ClearStuckAlarmsDialogMode.None,
+      stuckAlarms: [],
+      refreshAlarmLogEditorMode: RefreshAlarmLogDialogMode.None,
+      refreshCurrentProblemsEditorMode: RefreshCurrentProblemsDialogMode.None
+    }
   }
 
   onDialogClose = () => {
@@ -127,10 +138,27 @@ class FaultApplicationComponent extends React.Component<FaultApplicationComponen
   render(): JSX.Element {
 
     const refreshButton = {
-      icon: RefreshIcon, tooltip: 'Clear stuck alarms', onClick: this.onDialogOpen
+      icon: Sync, tooltip: 'Clear stuck alarms', onClick: this.onDialogOpen
+    };
+
+    const refreshCurrentProblemsAction = {
+      icon: Refresh, tooltip: 'Refresh Current Problems List', onClick: () => {
+        this.setState({
+          refreshCurrentProblemsEditorMode: RefreshCurrentProblemsDialogMode.RefreshCurrentProblemsTable
+        });
+      }
+    };
+
+    const refreshAlarmLogAction = {
+      icon: Refresh, tooltip: 'Refresh Alarm log table', onClick: () => {
+        this.setState({
+          refreshAlarmLogEditorMode: RefreshAlarmLogDialogMode.RefreshAlarmLogTable
+        });
+      }
     };
+
     const areFaultsAvailable = this.props.currentProblemsProperties.rows && this.props.currentProblemsProperties.rows.length > 0
-    const customAction = areFaultsAvailable ? [refreshButton] : [];
+    const customActions = areFaultsAvailable ? [refreshButton, refreshCurrentProblemsAction] : [refreshCurrentProblemsAction];
 
     const { panelId: activePanelId } = this.props;
 
@@ -144,15 +172,22 @@ class FaultApplicationComponent extends React.Component<FaultApplicationComponen
           </Tabs>
         </AppBar>
         {
-          activePanelId === 'CurrentProblem' && <FaultTable stickyHeader tableId="current-problems-table" idProperty={'id'} customActionButtons={customAction} columns={[
-            { property: "icon", title: "", type: ColumnType.custom, customControl: this.renderIcon },
-            { property: "timestamp", type: ColumnType.text, title: "Timestamp" },
-            { property: "nodeId", title: "Node Name", type: ColumnType.text },
-            { property: "counter", title: "Count", type: ColumnType.numeric, width: "100px" },
-            { property: "objectId", title: "Object Id", type: ColumnType.text },
-            { property: "problem", title: "Alarm Type", type: ColumnType.text },
-            { property: "severity", title: "Severity", type: ColumnType.text, width: "140px" },
-          ]} {...this.props.currentProblemsProperties} {...this.props.currentProblemsActions} />
+          activePanelId === 'CurrentProblem' &&
+          <>
+            <FaultTable stickyHeader tableId="current-problems-table" idProperty={'id'} customActionButtons={customActions} columns={[
+              { property: "icon", title: "", type: ColumnType.custom, customControl: this.renderIcon },
+              { property: "timestamp", type: ColumnType.text, title: "Timestamp" },
+              { property: "nodeId", title: "Node Name", type: ColumnType.text },
+              { property: "counter", title: "Count", type: ColumnType.numeric, width: "100px" },
+              { property: "objectId", title: "Object Id", type: ColumnType.text },
+              { property: "problem", title: "Alarm Type", type: ColumnType.text },
+              { property: "severity", title: "Severity", type: ColumnType.text, width: "140px" },
+            ]} {...this.props.currentProblemsProperties} {...this.props.currentProblemsActions} />
+            <RefreshCurrentProblemsDialog
+              mode={this.state.refreshCurrentProblemsEditorMode}
+              onClose={this.onCloseRefreshCurrentProblemsDialog}
+            />
+          </>
         }
         {activePanelId === 'AlarmNotifications' &&
 
@@ -168,18 +203,24 @@ class FaultApplicationComponent extends React.Component<FaultApplicationComponen
 
         }
 
-        {activePanelId === 'AlarmLog' && 
-        <FaultTable stickyHeader idProperty={'id'} tableId="alarm-log-table"
-          columns={[
-          { property: "icon", title: "", type: ColumnType.custom, customControl: this.renderIcon },
-          { property: "timestamp", title: "Timestamp" },
-          { property: "nodeId", title: "Node Name" },
-          { property: "counter", title: "Count", type: ColumnType.numeric, width: "100px" },
-          { property: "objectId", title: "Object Id" },
-          { property: "problem", title: "Alarm Type" },
-          { property: "severity", title: "Severity", width: "140px" },
-          { property: "sourceType", title: "Source", width: "140px" },
-        ]} {...this.props.alarmLogEntriesProperties} {...this.props.alarmLogEntriesActions} />
+        {activePanelId === 'AlarmLog' &&
+          <>
+            <FaultTable stickyHeader idProperty={'id'} tableId="alarm-log-table" customActionButtons={[refreshAlarmLogAction]}
+              columns={[
+                { property: "icon", title: "", type: ColumnType.custom, customControl: this.renderIcon },
+                { property: "timestamp", title: "Timestamp" },
+                { property: "nodeId", title: "Node Name" },
+                { property: "counter", title: "Count", type: ColumnType.numeric, width: "100px" },
+                { property: "objectId", title: "Object Id" },
+                { property: "problem", title: "Alarm Type" },
+                { property: "severity", title: "Severity", width: "140px" },
+                { property: "sourceType", title: "Source", width: "140px" },
+              ]} {...this.props.alarmLogEntriesProperties} {...this.props.alarmLogEntriesActions} />
+            <RefreshAlarmLogDialog
+              mode={this.state.refreshAlarmLogEditorMode}
+              onClose={this.onCloseRefreshAlarmLogDialog}
+            />
+          </>
 
         }
         {
@@ -207,6 +248,16 @@ class FaultApplicationComponent extends React.Component<FaultApplicationComponen
     );
   };
 
+  private onCloseRefreshAlarmLogDialog = () => {
+    this.setState({
+      refreshAlarmLogEditorMode: RefreshAlarmLogDialogMode.None
+    });
+  }
+  private onCloseRefreshCurrentProblemsDialog = () => {
+    this.setState({
+      refreshCurrentProblemsEditorMode: RefreshCurrentProblemsDialogMode.None
+    });
+  }
 }
 
 export const FaultApplication = withRouter(connect(mapProps, mapDisp)(FaultApplicationComponent));
index 0a7593d..58e68dd 100644 (file)
     "github-markdown-css": "2.10.0"
   },
   "peerDependencies": {
-    "@types/react": "16.9.19",
-    "@types/react-dom": "16.9.5",
-    "@types/react-router-dom": "4.3.1",
+    "@types/react": "17.0.3",
+    "@types/react-dom": "17.0.2",
+    "@types/react-router-dom": "5.1.7",
     "@material-ui/core": "4.11.0",
     "@material-ui/icons": "4.9.1",
     "@types/classnames": "2.2.6",
     "@types/flux": "3.1.8",
     "@types/jquery": "3.3.10",
     "jquery": "3.3.1",
-    "react": "16.12.0",
-    "react-dom": "16.12.0",
-    "react-router-dom": "4.3.1"
+    "react": "17.0.1",
+    "react-dom": "17.0.1",
+    "react-router-dom": "5.2.0"
   }
 }
\ No newline at end of file
index 1e11f78..0f6b403 100644 (file)
     "@odlux/framework": "*"
   },
   "peerDependencies": {
-    "@types/react": "16.9.19",
-    "@types/react-dom": "16.9.5",
-    "@types/react-router-dom": "4.3.1",
+    "@types/react": "17.0.3",
+    "@types/react-dom": "17.0.2",
+    "@types/react-router-dom": "5.1.7",
     "@material-ui/core": "4.11.0",
     "@material-ui/icons": "4.9.1",
     "@types/classnames": "2.2.6",
     "@types/flux": "3.1.8",
     "@types/jquery": "3.3.10",
     "jquery": "3.3.1",
-    "react": "16.12.0",
-    "react-dom": "16.12.0",
-    "react-router-dom": "4.3.1"
+    "react": "17.0.1",
+    "react-dom": "17.0.1",
+    "react-router-dom": "5.2.0"
   }
 }
\ No newline at end of file
diff --git a/sdnr/wt/odlux/apps/inventoryApp/src/components/refreshInventoryDialog.tsx b/sdnr/wt/odlux/apps/inventoryApp/src/components/refreshInventoryDialog.tsx
new file mode 100644 (file)
index 0000000..d2efb4e
--- /dev/null
@@ -0,0 +1,117 @@
+/**
+ * ============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 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 { inventoryElementsReloadAction } from '../handlers/inventoryElementsHandler';
+import { IDispatcher, connect, Connect } from '../../../../framework/src/flux/connect';
+
+import { InventoryType } from '../models/inventory';
+
+export enum RefreshInventoryDialogMode {
+  None = "none",
+  RefreshInventoryTable = "RefreshInventoryTable",
+}
+
+const mapDispatch = (dispatcher: IDispatcher) => ({
+  refreshInventory: () => dispatcher.dispatch(inventoryElementsReloadAction)
+});
+
+type DialogSettings = {
+  dialogTitle: string,
+  dialogDescription: string,
+  applyButtonText: string,
+  cancelButtonText: string,
+  enableMountIdEditor: boolean,
+  enableUsernameEditor: boolean,
+  enableExtendedEditor: boolean,
+}
+
+const settings: { [key: string]: DialogSettings } = {
+  [RefreshInventoryDialogMode.None]: {
+    dialogTitle: "",
+    dialogDescription: "",
+    applyButtonText: "",
+    cancelButtonText: "",
+    enableMountIdEditor: false,
+    enableUsernameEditor: false,
+    enableExtendedEditor: false,
+  },
+  [RefreshInventoryDialogMode.RefreshInventoryTable]: {
+    dialogTitle: "Do you want to refresh the Inventory table?",
+    dialogDescription: "",
+    applyButtonText: "Yes",
+    cancelButtonText: "Cancel",
+    enableMountIdEditor: true,
+    enableUsernameEditor: true,
+    enableExtendedEditor: true,
+  }
+}
+
+type RefreshInventoryDialogComponentProps = Connect<undefined, typeof mapDispatch> & {
+  mode: RefreshInventoryDialogMode;
+  onClose: () => void;
+};
+
+type RefreshInventoryDialogComponentState = InventoryType & { isNameValid: boolean, isHostSet: boolean };
+
+class RefreshInventoryDialogComponent extends React.Component<RefreshInventoryDialogComponentProps, RefreshInventoryDialogComponentState> {
+  constructor(props: RefreshInventoryDialogComponentProps) {
+    super(props);
+  }
+
+  render(): JSX.Element {
+    const setting = settings[this.props.mode];
+    return (
+      <Dialog open={this.props.mode !== RefreshInventoryDialogMode.None}>
+        <DialogTitle id="form-dialog-title" aria-label={`${setting.dialogTitle.replace(/ /g, "-").toLowerCase()}-dialog`}>{setting.dialogTitle}</DialogTitle>
+        <DialogContent>
+          <DialogContentText>
+            {setting.dialogDescription}
+          </DialogContentText>
+        </DialogContent>
+        <DialogActions>
+          <Button aria-label="dialog-confirm-button" onClick={(event) => {
+            this.onRefresh();
+          }} > {setting.applyButtonText} </Button>
+          <Button aria-label="dialog-cancel-button" onClick={(event) => {
+            this.onCancel();
+          }} color="secondary"> {setting.cancelButtonText} </Button>
+        </DialogActions>
+      </Dialog>
+    )
+  }
+
+  private onRefresh = () => {
+    this.props.refreshInventory();
+    this.props.onClose();
+  };
+
+  private onCancel = () => {
+    this.props.onClose();
+  }
+}
+
+export const RefreshInventoryDialog = connect(undefined, mapDispatch)(RefreshInventoryDialogComponent);
+export default RefreshInventoryDialog;
\ No newline at end of file
index 75531ec..21f3130 100644 (file)
   <script type="text/javascript" src="./config.js"></script>
   <script>
     // run the application
-    require(["app", "inventoryApp", "connectApp"], function (app, inventoryApp, connectApp) {
+    require(["app", "inventoryApp", "connectApp", "configurationApp"], function (app, inventoryApp, connectApp, configurationApp) {
       inventoryApp.register();
       connectApp.register();
+      configurationApp.register();
       app("./app.tsx").runApplication();
     });
   </script>
index f5ada22..11427fb 100644 (file)
@@ -23,6 +23,7 @@ import connect, { IDispatcher, Connect } from "../../../../framework/src/flux/co
 import { IApplicationStoreState } from "../../../../framework/src/store/applicationStore";
 import { MaterialTable, MaterialTableCtorType, ColumnType } from "../../../../framework/src/components/material-table";
 import { AppBar, Tabs, Tab, MenuItem, Typography } from "@material-ui/core";
+import Refresh from '@material-ui/icons/Refresh';
 import { PanelId } from "../models/panelId";
 import { setPanelAction } from "../actions/panelActions";
 
@@ -36,6 +37,7 @@ import { InventoryType } from '../models/inventory';
 import { createInventoryElementsProperties, createInventoryElementsActions } from "../handlers/inventoryElementsHandler";
 import { NavigateToApplication } from '../../../../framework/src/actions/navigationActions';
 import { updateInventoryTreeAsyncAction } from '../actions/inventoryTreeActions';
+import RefreshInventoryDialog, { RefreshInventoryDialogMode } from '../components/refreshInventoryDialog';
 
 const InventoryTable = MaterialTable as MaterialTableCtorType<InventoryType & { _id: string }>;
 
@@ -49,7 +51,7 @@ const mapProps = (state: IApplicationStoreState) => ({
 const mapDispatch = (dispatcher: IDispatcher) => ({
   connectedNetworkElementsActions: createConnectedNetworkElementsActions(dispatcher.dispatch),
   switchActivePanel: (panelId: PanelId) => {
-    dispatcher.dispatch(setPanelAction(panelId)); 
+    dispatcher.dispatch(setPanelAction(panelId));
   },
   inventoryElementsActions: createInventoryElementsActions(dispatcher.dispatch),
   navigateToApplication: (applicationName: string, path?: string) => dispatcher.dispatch(new NavigateToApplication(applicationName, path)),
@@ -62,8 +64,18 @@ let inventoryInitialSorted = false;
 const ConnectedElementTable = MaterialTable as MaterialTableCtorType<NetworkElementConnection>;
 
 type DashboardComponentProps = RouteComponentProps & Connect<typeof mapProps, typeof mapDispatch>;
+type DashboardComponentState = {
+  refreshInventoryEditorMode: RefreshInventoryDialogMode
+}
+
+class DashboardSelectorComponent extends React.Component<DashboardComponentProps, DashboardComponentState> {
+  constructor(props: DashboardComponentProps) {
+    super(props);
 
-class DashboardSelectorComponent extends React.Component<DashboardComponentProps> {
+    this.state = {
+      refreshInventoryEditorMode: RefreshInventoryDialogMode.None
+    };
+  }
 
   private onHandleTabChange = (event: React.ChangeEvent<{}>, newValue: PanelId) => {
     this.onTogglePanel(newValue);
@@ -111,6 +123,13 @@ class DashboardSelectorComponent extends React.Component<DashboardComponentProps
 
   render() {
 
+    const refreshInventoryAction = {
+      icon: Refresh, tooltip: 'Refresh Inventory', onClick: () => {
+        this.setState({
+          refreshInventoryEditorMode: RefreshInventoryDialogMode.RefreshInventoryTable
+        });
+      }
+    };
     const { panelId: activePanelId } = this.props;
     return (
       <>
@@ -124,32 +143,42 @@ class DashboardSelectorComponent extends React.Component<DashboardComponentProps
         {
 
           activePanelId === "InventoryElementsTable" &&
-
-          <InventoryTable stickyHeader title="Inventory" idProperty="_id" tableId="inventory-table" columns={[
-            { property: "nodeId", title: "Node Name" },
-            { property: "manufacturerIdentifier", title: "Manufacturer" },
-            { property: "parentUuid", title: "Parent" },
-            { property: "uuid", title: "Name" },
-            { property: "serial", title: "Serial" },
-            { property: "version", title: "Version" },
-            { property: "date", title: "Date" },
-            { property: "description", title: "Description" },
-            { property: "partTypeId", title: "Part Type Id" },
-            { property: "modelIdentifier", title: "Model Identifier" },
-            { property: "typeName", title: "Type" },
-            { property: "treeLevel", title: "Containment Level" },
-          ]}  {...this.props.inventoryElementsActions} {...this.props.inventoryElementsProperties}
-            createContextMenu={rowData => {
-
-              return this.getContextMenu(rowData);
-            }} >
-          </InventoryTable>
+          <>
+            <InventoryTable stickyHeader title="Inventory" idProperty="_id" tableId="inventory-table" customActionButtons={[refreshInventoryAction]} columns={[
+              { property: "nodeId", title: "Node Name" },
+              { property: "manufacturerIdentifier", title: "Manufacturer" },
+              { property: "parentUuid", title: "Parent" },
+              { property: "uuid", title: "Name" },
+              { property: "serial", title: "Serial" },
+              { property: "version", title: "Version" },
+              { property: "date", title: "Date" },
+              { property: "description", title: "Description" },
+              { property: "partTypeId", title: "Part Type Id" },
+              { property: "modelIdentifier", title: "Model Identifier" },
+              { property: "typeName", title: "Type" },
+              { property: "treeLevel", title: "Containment Level" },
+            ]}  {...this.props.inventoryElementsActions} {...this.props.inventoryElementsProperties}
+              createContextMenu={rowData => {
+
+                return this.getContextMenu(rowData);
+              }} >
+            </InventoryTable>
+            <RefreshInventoryDialog
+              mode={this.state.refreshInventoryEditorMode}
+              onClose={this.onCloseRefreshInventoryDialog}
+            />
+          </>
 
         }
         {
           activePanelId === "TreeviewTable" &&
 
-          <ConnectedElementTable stickyHeader tableId="treeview-networkelement-selection-table" onHandleClick={(e, row) => { this.props.history.push(`${this.props.match.path}/${row.nodeId}`) }} columns={[
+          <ConnectedElementTable stickyHeader tableId="treeview-networkelement-selection-table"  
+          onHandleClick={(e, row) => { 
+            this.props.history.push(`${this.props.match.path}/${row.nodeId}`);
+            this.props.updateInventoryTree(row.nodeId, '*');
+         }} 
+          columns={[
             { property: "nodeId", title: "Node Name", type: ColumnType.text },
             { property: "isRequired", title: "Required", type: ColumnType.boolean },
             { property: "host", title: "Host", type: ColumnType.text },
@@ -163,6 +192,11 @@ class DashboardSelectorComponent extends React.Component<DashboardComponentProps
     );
   }
 
+  private onCloseRefreshInventoryDialog = () => {
+    this.setState({
+      refreshInventoryEditorMode: RefreshInventoryDialogMode.None
+    });
+  }
   componentDidMount() {
 
     if (this.props.panelId === null) { //set default tab if none is set
index 5ba82ab..9544861 100644 (file)
@@ -24,7 +24,7 @@ import { TreeView, TreeViewCtorType, SearchMode } from '../../../../framework/sr
 
 import { IApplicationStoreState } from "../../../../framework/src/store/applicationStore";
 
-import { updateInventoryTreeAsyncAction, selectInventoryNodeAsyncAction, UpdateSelectedNodeAction, UpdateExpandedNodesAction, setSearchTermAction} from "../actions/inventoryTreeActions";
+import { updateInventoryTreeAsyncAction, selectInventoryNodeAsyncAction, UpdateSelectedNodeAction, UpdateExpandedNodesAction, setSearchTermAction } from "../actions/inventoryTreeActions";
 import { TreeDemoItem } from "../models/inventory";
 
 import { RouteComponentProps } from "react-router-dom";
@@ -66,7 +66,7 @@ const InventoryTree = TreeView as any as TreeViewCtorType<string>;
 
 
 
-type TreeviewComponentProps = RouteComponentProps<{ mountId: string}> & WithStyles<typeof styles> & Connect<typeof mapProps, typeof mapDispatch>
+type TreeviewComponentProps = RouteComponentProps<{ mountId: string }> & WithStyles<typeof styles> & Connect<typeof mapProps, typeof mapDispatch>
 
 type TreeviewComponentState = {
   [propsChache]: {
@@ -78,7 +78,7 @@ type TreeviewComponentState = {
 
 class DashboardComponent extends React.Component<TreeviewComponentProps, TreeviewComponentState> {
 
-  constructor (props: TreeviewComponentProps) {
+  constructor(props: TreeviewComponentProps) {
     super(props);
 
     this.state = {
@@ -89,15 +89,16 @@ class DashboardComponent extends React.Component<TreeviewComponentProps, Treevie
 
   static getDerivedStateFromProps(props: TreeviewComponentProps, state: TreeviewComponentState) {
     if (state[propsChache].rootNodes != props.rootNodes) {
-      state = { ...state, rootNodes: props.rootNodes}
+      state = { ...state, rootNodes: props.rootNodes }
     }
     return state;
   }
 
   render() {
-    const { classes, updateInventoryTree, updateExpendedNodes, expendedItems, selectedNode, selectTreeNode, searchTerm, match: { params: { mountId }} } = this.props;
+    const { classes, updateInventoryTree, updateExpendedNodes, expendedItems, selectedNode, selectTreeNode, searchTerm, match: { params: { mountId } } } = this.props;
+    const scrollbar = { overflow: "auto", paddingRight: "20px" }
     return (
-      <div className={classes.root}>
+      <div style={scrollbar} className={classes.root}>
         <InventoryTree className={classes.tree} items={this.state.rootNodes} enableSearchBar initialSearchTerm={searchTerm} searchMode={SearchMode.OnEnter} searchTerm={searchTerm}
           onSearch={(searchTerm) => updateInventoryTree(mountId, searchTerm)} expandedItems={expendedItems} onFolderClick={(item) => {
             const indexOfItemToToggle = expendedItems.indexOf(item);
@@ -119,11 +120,11 @@ class DashboardComponent extends React.Component<TreeviewComponentProps, Treevie
   }
 
   componentDidMount() {
-    const { updateInventoryTree, searchTerm, match: { params: { mountId } }} = this.props;
+    const { updateInventoryTree, searchTerm, match: { params: { mountId } } } = this.props;
     updateInventoryTree(mountId, searchTerm);
   }
 
-  componentWillUnmount(){
+  componentWillUnmount() {
     this.props.setSearchTerm("");
   }
 }
index 22b2a6c..1717706 100644 (file)
     "yup": "^0.29.3"
   },
   "peerDependencies": {
-    "@types/react": "16.9.19",
-    "@types/react-dom": "16.9.5",
-    "@types/react-router-dom": "4.3.1",
+    "@types/react": "17.0.3",
+    "@types/react-dom": "17.0.2",
+    "@types/react-router-dom": "5.1.7",
     "@material-ui/core": "4.11.0",
     "@material-ui/icons": "4.9.1",
     "@types/classnames": "2.2.6",
     "@types/flux": "3.1.8",
     "@types/jquery": "3.3.10",
     "jquery": "3.3.1",
-    "react": "16.12.0",
-    "react-dom": "16.12.0",
-    "react-router-dom": "4.3.1"
+    "react": "17.0.1",
+    "react-dom": "17.0.1",
+    "react-router-dom": "5.2.0"
   },
   "devDependencies": {
     "@types/yup": "^0.29.7",
index c498379..61a700c 100644 (file)
@@ -324,16 +324,16 @@ class LinkCalculation extends React.Component<linkCalculationProps, initialState
 
           <div >Site A
           <form >
-          <label>Latitude:  <input className={this.state.latitude1Error.length>0 ? 'error' : 'input'} id='Lat1' type='number' onChange={(e: any) => {this.handleChange(e)} }/></label>
+          <label>Latitude:  <input aria-label="site-a-latitude-input" className={this.state.latitude1Error.length>0 ? 'error' : 'input'} id='Lat1' type='number' onChange={(e: any) => {this.handleChange(e)} }/></label>
           <div style={{fontSize:12, color:'red'}}> {this.state.latitude1Error}  </div></form> 
-          <form><label>Longitude: <input className={this.state.longitude1Error.length>0 ? 'error' : 'input'} id='Lon1' type='number' onChange={(e: any) => this.handleChange(e) } /></label><div style={{fontSize:12, color:'red'}}> {this.state.longitude1Error} </div>
+          <form><label>Longitude: <input aria-label="site-a-longitude-input" className={this.state.longitude1Error.length>0 ? 'error' : 'input'} id='Lon1' type='number' onChange={(e: any) => this.handleChange(e) } /></label><div style={{fontSize:12, color:'red'}}> {this.state.longitude1Error} </div>
           </form> 
           </div>
           
           <div>Site B
           <form>
-          <label>Latitude: <input className={this.state.latitude2Error.length>0 ? 'error' : 'input'} id='Lat2' type='number' onChange={(e: any) => {this.handleChange(e) }} /></label><div style={{fontSize:12, color:'red'}}> {this.state.latitude2Error} </div></form>
-          <form><label>Longitude: <input className={this.state.longitude2Error.length>0 ? 'error' : 'input'}  id='Lon2' type='number' onChange={(e: any) => {this.handleChange(e) } }/></label><div style={{fontSize:12, color:'red'}}> {this.state.longitude2Error} </div></form>
+          <label>Latitude: <input aria-label="site-b-latitude-input" className={this.state.latitude2Error.length>0 ? 'error' : 'input'} id='Lat2' type='number' onChange={(e: any) => {this.handleChange(e) }} /></label><div style={{fontSize:12, color:'red'}}> {this.state.latitude2Error} </div></form>
+          <form><label>Longitude: <input aria-label="site-b-longitude-input" className={this.state.longitude2Error.length>0 ? 'error' : 'input'}  id='Lon2' type='number' onChange={(e: any) => {this.handleChange(e) } }/></label><div style={{fontSize:12, color:'red'}}> {this.state.longitude2Error} </div></form>
           </div>
           
           </div>
@@ -341,8 +341,8 @@ class LinkCalculation extends React.Component<linkCalculationProps, initialState
 
 
         <div className='container-1'>
-          <div>{<form><input type='checkbox' id='Annual' value ="Annual" checked= {this.state.worstmonth===false} onClick= {(e: any) => this.setState ({worstmonth: false})}></input>Annual
-                      <input style={{marginLeft:10}} type='checkbox' id='Worst Month' value ="Worst" checked= {this.state.worstmonth===true} onClick= {(e:any)=>this.setState ({worstmonth: true})}></input>WM</form>}</div>
+          <div>{<form><input aria-label="annual" type='checkbox' id='Annual' value ="Annual" checked= {this.state.worstmonth===false} onClick= {(e: any) => this.setState ({worstmonth: false})}></input>Annual
+                      <input aria-label="worst-month" style={{marginLeft:10}} type='checkbox' id='Worst Month' value ="Worst" checked= {this.state.worstmonth===true} onClick= {(e:any)=>this.setState ({worstmonth: true})}></input>WM</form>}</div>
 
 
           <div className='column1'>
@@ -369,55 +369,55 @@ class LinkCalculation extends React.Component<linkCalculationProps, initialState
           <div className='middlecolumn'>
           <div  >Site A</div>
           {this.props.siteA.length>0 &&<div> {this.props.siteA }</div>}
-          <div> {this.props.lat1 && this.LatLonToDMS(this.props.lat1)}</div>
-          <div>{this.props.lon1 && this.LatLonToDMS(this.props.lon1)}</div>
+          <div aria-label="site-a-latitude-dms"> {this.props.lat1 && this.LatLonToDMS(this.props.lat1)}</div>
+          <div aria-label="site-a-longitude-dms">{this.props.lon1 && this.LatLonToDMS(this.props.lon1)}</div>
           <div>0</div>
-          <div>{this.props.amslA.toFixed(2)} m</div>
-          <div>{this.props.aglA.toFixed(2)} m</div>
+          <div aria-label="site-a-amsl">{this.props.amslA.toFixed(2)} m</div>
+          <div aria-label="site-a-antenna-amsl">{this.props.aglA.toFixed(2)} m</div>
         
 
           <div className='column2'>
-          <div>{this.props.distance?.toFixed(3)} km</div>
-          <div>{<form><input type='checkbox' id='Horizontal' value ="Horizontal" checked= {this.props.polarization==='Horizontal'} onClick= {(e: any) => this.props.updatePolarization(e.target.value)}></input>Horizontal
-                      <input style={{marginLeft:10}} type='checkbox' id='Vertical' value ="Vertical" checked= {this.props.polarization==='Vertical'} onClick= {(e:any)=>{this.props.updatePolarization(e.target.value)}}></input>Vertical</form>}</div>
+          <div aria-label="distance-between-sites">{this.props.distance?.toFixed(3)} km</div>
+          <div>{<form><input aria-label="polarization-horizontal" type='checkbox' id='Horizontal' value ="Horizontal" checked= {this.props.polarization==='Horizontal'} onClick= {(e: any) => this.props.updatePolarization(e.target.value)}></input>Horizontal
+                      <input aria-label="polarization-vertical" style={{marginLeft:10}} type='checkbox' id='Vertical' value ="Vertical" checked= {this.props.polarization==='Vertical'} onClick= {(e:any)=>{this.props.updatePolarization(e.target.value)}}></input>Vertical</form>}</div>
           
-              <div> {<select className={this.state.frequencyError.length>0 ? 'error' : 'input'}  onChange={(e) => { this.props.updateFrequency(Number(e.target.value)); e.target.value==='0'? this.setState({frequencyError: 'select a frequency'}): this.setState({frequencyError:''})}}> 
+              <div> {<select aria-label="select-frequency-in-ghz" className={this.state.frequencyError.length>0 ? 'error' : 'input'}  onChange={(e) => { this.props.updateFrequency(Number(e.target.value)); e.target.value==='0'? this.setState({frequencyError: 'select a frequency'}): this.setState({frequencyError:''})}}> 
                       
-                      <option value='0' >Select Freq</option>
-                      <option value='7' >7 GHz</option>
-                      <option value='11' >11 GHz</option>
-                      <option value='15' >15 GHz</option>
-                      <option value='23' >23 GHz</option>
-                      <option value='26' >26 GHz</option>
-                      <option value='28' >28 GHz</option>
-                      <option value='38' >38 GHz</option>
-                      <option value='42' >42 GHz</option>
-                      <option value='80' >80 GHz</option>
+                      <option value='0' aria-label="none-value" >Select Freq</option>
+                      <option value='7' aria-label="7" >7 GHz</option>
+                      <option value='11' aria-label="11" >11 GHz</option>
+                      <option value='15' aria-label="15" >15 GHz</option>
+                      <option value='23' aria-label="23">23 GHz</option>
+                      <option value='26' aria-label="26">26 GHz</option>
+                      <option value='28' aria-label="28">28 GHz</option>
+                      <option value='38' aria-label="38">38 GHz</option>
+                      <option value='42' aria-label="42">42 GHz</option>
+                      <option value='80' aria-label="80">80 GHz</option>
                       </select>} <div style={{fontSize:12, color:'red'}}>  {this.state.frequencyError} </div> </div> 
 
-          <div>{this.props.fsl.toFixed(3)} dB</div>
+          <div aria-label="fspl-value">{this.props.fsl.toFixed(3)} dB</div>
 
-          <div> {<select className={this.state.rainMethodError.length>0 ? 'error' : 'input'} onChange = {(e) => {e.target.value === 'itu' ? this.setState({ rainMethodDisplay: false}):this.setState({ rainMethodDisplay: true}); e.target.value==='0'? this.setState({rainMethodError: 'select a Rain model'}): this.setState({rainMethodError:''}) }}>
-                        <option value='0' >Select Rain Method</option>
-                        <option value='itu' >ITU-R P.837-7</option>
-                        <option value='manual' >Specific Rain</option>
+          <div> {<select aria-label="select-rain-method" className={this.state.rainMethodError.length>0 ? 'error' : 'input'} onChange = {(e) => {e.target.value === 'itu' ? this.setState({ rainMethodDisplay: false}):this.setState({ rainMethodDisplay: true}); e.target.value==='0'? this.setState({rainMethodError: 'select a Rain model'}): this.setState({rainMethodError:''}) }}>
+                        <option value='0' aria-label="none-value" >Select Rain Method</option>
+                        <option value='itu' aria-label="itur8377">ITU-R P.837-7</option>
+                        <option value='manual' aria-label="manual-entry">Specific Rain</option>
                         </select>} <div style={{fontSize:12,color:'red'}}>{this.state.rainMethodError}</div>
             </div> 
-            <div> {<form><input type="number" style={{ width: 70, height: 15, fontSize: 14 }} onChange={(e) => { this.props.updateRainValue(Number(e.target.value)) }}
+            <div> {<form><input aria-label="rain-value" type="number" style={{ width: 70, height: 15, fontSize: 14 }} onChange={(e) => { this.props.updateRainValue(Number(e.target.value)) }}
                     value={this.props.rainVal} disabled={this.state.rainMethodDisplay === false ? true : false}>
                     </input>  mm/hr  {this.state.showWM} {this.props.month}</form> } </div>
-          <div>{this.props.rainAtt.toFixed(3)} dB</div>
+          <div aria-label="rain-attenuation-value">{this.props.rainAtt.toFixed(3)} dB</div>
 
-          <div> {<select className={this.state.attenuationMethodError.length>0 ? 'error' : 'input'} onChange = {(e) => { if (e.target.value!== ''){ this.setState({absorptionMethod : e.target.value}); this.setState({attenuationMethodError:''})  }}}>
-                        <option value='0' >Select Absorption Method</option>
-                        <option value='ITURP67612' >ITU-R P.676-12</option>
-                        <option value='ITURP67611'  >ITU-R P.676-11</option>
-                        <option value='ITURP67610'  >ITU-R P.676-10</option>
+          <div> {<select aria-label="select-absorption-method" className={this.state.attenuationMethodError.length>0 ? 'error' : 'input'} onChange = {(e) => { if (e.target.value!== ''){ this.setState({absorptionMethod : e.target.value}); this.setState({attenuationMethodError:''})  }}}>
+                        <option value='0' aria-label="none-value" >Select Absorption Method</option>
+                        <option value='ITURP67612' aria-label="iturp67612" >ITU-R P.676-12</option>
+                        <option value='ITURP67611' aria-label="iturp67611"  >ITU-R P.676-11</option>
+                        <option value='ITURP67610' aria-label="iturp67610" >ITU-R P.676-10</option>
                         </select>} <div style={{fontSize:12,color:'red'}}>{this.state.attenuationMethodError}</div>
             </div> 
-          <div>{this.props.absorptionOxygen.toFixed(3)} dB</div>
-          <div>{this.props.absorptionWater.toFixed(3)} dB</div>
-          <div>{<button style={{color: '#222', fontFamily:'Arial', boxAlign: 'center', display:'inline-block', insetInlineStart: '20' , alignSelf:'center' }}
+          <div aria-label="absorption-oxygen-value">{this.props.absorptionOxygen.toFixed(3)} dB</div>
+          <div aria-label="absorption-water-value">{this.props.absorptionWater.toFixed(3)} dB</div>
+          <div>{<button aria-label="calculate-button" style={{color: '#222', fontFamily:'Arial', boxAlign: 'center', display:'inline-block', insetInlineStart: '20' , alignSelf:'center' }}
                     onClick = {(e) => this.buttonHandler()} >Calculate</button>} </div>
 
 
@@ -426,11 +426,11 @@ class LinkCalculation extends React.Component<linkCalculationProps, initialState
           <div className= 'middlecolumn'>
           <div  >Site B</div>
           {this.props.siteB.length>0 &&<div> {this.props.siteB}</div>}
-          <div> {this.props.lat2 && this.LatLonToDMS(this.props.lat2)}</div>
-          <div>{this.props.lon2 && this.LatLonToDMS(this.props.lon2)}</div>
+          <div aria-label="site-b-latitude-dms"> {this.props.lat2 && this.LatLonToDMS(this.props.lat2)}</div>
+          <div aria-label="site-b-longitude-dms">{this.props.lon2 && this.LatLonToDMS(this.props.lon2)}</div>
           <div>0</div>
-          <div>{this.props.amslB.toFixed(2)} m</div>
-          <div>{this.props.aglB.toFixed(2)} m</div>
+          <div aria-label="site-b-asml">{this.props.amslB.toFixed(2)} m</div>
+          <div aria-label="site-b-antenna-asml">{this.props.aglB.toFixed(2)} m</div>
 
           
           </div>
index da5b3d2..d2980f1 100644 (file)
     "@odlux/connect-app": "*"
   },
   "peerDependencies": {
-    "@types/react": "16.9.19",
-    "@types/react-dom": "16.9.5",
-    "@types/react-router-dom": "4.3.1",
+    "@types/react": "17.0.3",
+    "@types/react-dom": "17.0.2",
+    "@types/react-router-dom": "5.1.7",
     "@material-ui/core": "4.11.0",
     "@material-ui/icons": "4.9.1",
     "@types/classnames": "2.2.6",
     "@types/flux": "3.1.8",
     "@types/jquery": "3.3.10",
     "jquery": "3.3.1",
-    "react": "16.12.0",
-    "react-dom": "16.12.0",
-    "react-router-dom": "4.3.1"
+    "react": "17.0.1",
+    "react-dom": "17.0.1",
+    "react-router-dom": "5.2.0"
   }
 }
\ No newline at end of file
diff --git a/sdnr/wt/odlux/apps/maintenanceApp/src/components/refreshMaintenanceEntries.tsx b/sdnr/wt/odlux/apps/maintenanceApp/src/components/refreshMaintenanceEntries.tsx
new file mode 100644 (file)
index 0000000..1a00c70
--- /dev/null
@@ -0,0 +1,117 @@
+/**
+ * ============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 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 { IDispatcher, connect, Connect } from '../../../../framework/src/flux/connect';
+
+import { maintenanceEntriesReloadAction } from '../handlers/maintenenceEntriesHandler';
+import { MaintenenceEntry } from '../models/maintenenceEntryType';
+
+export enum RefreshMaintenanceEntriesDialogMode {
+  None = "none",
+  RefreshMaintenanceEntriesTable = "RefreshMaintenanceEntriesTable",
+}
+
+const mapDispatch = (dispatcher: IDispatcher) => ({
+  refreshMaintenanceEntries: () => dispatcher.dispatch(maintenanceEntriesReloadAction)
+});
+
+type DialogSettings = {
+  dialogTitle: string,
+  dialogDescription: string,
+  applyButtonText: string,
+  cancelButtonText: string,
+  enableMountIdEditor: boolean,
+  enableUsernameEditor: boolean,
+  enableExtendedEditor: boolean,
+}
+
+const settings: { [key: string]: DialogSettings } = {
+  [RefreshMaintenanceEntriesDialogMode.None]: {
+    dialogTitle: "",
+    dialogDescription: "",
+    applyButtonText: "",
+    cancelButtonText: "",
+    enableMountIdEditor: false,
+    enableUsernameEditor: false,
+    enableExtendedEditor: false,
+  },
+  [RefreshMaintenanceEntriesDialogMode.RefreshMaintenanceEntriesTable]: {
+    dialogTitle: "Do you want to refresh Maintenance Entries?",
+    dialogDescription: "",
+    applyButtonText: "Yes",
+    cancelButtonText: "Cancel",
+    enableMountIdEditor: true,
+    enableUsernameEditor: true,
+    enableExtendedEditor: true,
+  }
+}
+
+type RefreshMaintenanceEntriesDialogComponentProps = Connect<undefined, typeof mapDispatch> & {
+  mode: RefreshMaintenanceEntriesDialogMode;
+  onClose: () => void;
+};
+
+type RefreshMaintenanceEntriesDialogComponentState = MaintenenceEntry & { isNameValid: boolean, isHostSet: boolean };
+
+class RefreshMaintenanceEntriesDialogComponent extends React.Component<RefreshMaintenanceEntriesDialogComponentProps, RefreshMaintenanceEntriesDialogComponentState> {
+  constructor(props: RefreshMaintenanceEntriesDialogComponentProps) {
+    super(props);
+  }
+
+  render(): JSX.Element {
+    const setting = settings[this.props.mode];
+    return (
+      <Dialog open={this.props.mode !== RefreshMaintenanceEntriesDialogMode.None}>
+        <DialogTitle id="form-dialog-title" aria-label={`${setting.dialogTitle.replace(/ /g, "-").toLowerCase()}-dialog`}>{setting.dialogTitle}</DialogTitle>
+        <DialogContent>
+          <DialogContentText>
+            {setting.dialogDescription}
+          </DialogContentText>
+        </DialogContent>
+        <DialogActions>
+          <Button aria-label="dialog-confirm-button" onClick={(event) => {
+            this.onRefresh();
+          }} > {setting.applyButtonText} </Button>
+          <Button aria-label="dialog-cancel-button" onClick={(event) => {
+            this.onCancel();
+          }} color="secondary"> {setting.cancelButtonText} </Button>
+        </DialogActions>
+      </Dialog>
+    )
+  }
+
+  private onRefresh = () => {
+    this.props.refreshMaintenanceEntries();
+    this.props.onClose();
+  };
+
+  private onCancel = () => {
+    this.props.onClose();
+  }
+}
+
+export const RefreshMaintenanceEntriesDialog = connect(undefined, mapDispatch)(RefreshMaintenanceEntriesDialogComponent);
+export default RefreshMaintenanceEntriesDialog;
\ No newline at end of file
index f52d46d..6b5071d 100644 (file)
@@ -25,9 +25,8 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import AddIcon from '@material-ui/icons/Add';
 import EditIcon from '@material-ui/icons/Edit';
 import RemoveIcon from '@material-ui/icons/RemoveCircleOutline';
-
-import Button from '@material-ui/core/Button';
-import IconButton from '@material-ui/core/IconButton';
+import Refresh from '@material-ui/icons/Refresh';
+import { MenuItem, Divider, Typography } from '@material-ui/core';
 
 import connect, { IDispatcher, Connect } from '../../../../framework/src/flux/connect';
 import MaterialTable, { MaterialTableCtorType, ColumnType } from '../../../../framework/src/components/material-table';
@@ -36,6 +35,7 @@ import { IApplicationStoreState } from '../../../../framework/src/store/applicat
 import { MaintenenceEntry, spoofSymbol } from '../models/maintenenceEntryType';
 
 import EditMaintenenceEntryDialog, { EditMaintenenceEntryDialogMode } from '../components/editMaintenenceEntryDialog';
+import RefreshMaintenanceEntriesDialog, { RefreshMaintenanceEntriesDialogMode } from '../components/refreshMaintenanceEntries';
 import { convertToLocaleString } from '../utils/timeUtils';
 import { createmaintenanceEntriesActions, createmaintenanceEntriesProperties, maintenanceEntriesReloadAction } from '../handlers/maintenenceEntriesHandler';
 
@@ -81,6 +81,7 @@ type MaintenenceViewComponentProps = Connect<typeof mapProps, typeof mapDispatch
 type MaintenenceViewComponentState = {
   maintenenceEntryToEdit: MaintenenceEntry;
   maintenenceEntryEditorMode: EditMaintenenceEntryDialogMode;
+  refreshMaintenenceEntriesEditorMode: RefreshMaintenanceEntriesDialogMode;
 };
 
 let initialSorted = false;
@@ -93,10 +94,22 @@ class MaintenenceViewComponent extends React.Component<MaintenenceViewComponentP
     this.state = {
       maintenenceEntryToEdit: emptyMaintenenceEntry,
       maintenenceEntryEditorMode: EditMaintenenceEntryDialogMode.None,
+      refreshMaintenenceEntriesEditorMode: RefreshMaintenanceEntriesDialogMode.None
     };
 
   }
 
+  getContextMenu(rowData: MaintenenceEntry): JSX.Element[] {
+    let buttonArray = [
+      <MenuItem aria-label={"1hr-from-now"} onClick={event => this.onOpenPlus1hEditMaintenenceEntryDialog(event, rowData)}><Typography>+1h</Typography></MenuItem>,
+      <MenuItem aria-label={"8hr-from-now"} onClick={event => this.onOpenPlus8hEditMaintenenceEntryDialog(event, rowData)}><Typography>+8h</Typography></MenuItem>,
+      <Divider />,
+      <MenuItem aria-label={"edit"} onClick={event => this.onOpenEditMaintenenceEntryDialog(event, rowData)}><EditIcon /><Typography>Edit</Typography></MenuItem>,
+      <MenuItem aria-label={"remove"} onClick={event => this.onOpenRemoveMaintenenceEntryDialog(event, rowData)}><RemoveIcon /><Typography>Remove</Typography></MenuItem>
+    ];
+    return buttonArray;
+  }
+
   render() {
     const { classes } = this.props;
     const addMaintenenceEntryAction = {
@@ -113,10 +126,19 @@ class MaintenenceViewComponent extends React.Component<MaintenenceViewComponentP
         });
       }
     };
+
+    const refreshMaintenanceEntriesAction = {
+      icon: Refresh, tooltip: 'Refresh Maintenance Entries', onClick: () => {
+        this.setState({
+          refreshMaintenenceEntriesEditorMode: RefreshMaintenanceEntriesDialogMode.RefreshMaintenanceEntriesTable
+        });
+      }
+    };
+
     const now = new Date().valueOf();
     return (
       <>
-        <MaintenenceEntriesTable stickyHeader tableId="maintenance-table" title={"Maintenance"} customActionButtons={[addMaintenenceEntryAction]} columns={
+        <MaintenenceEntriesTable stickyHeader tableId="maintenance-table" title={"Maintenance"} customActionButtons={[refreshMaintenanceEntriesAction, addMaintenenceEntryAction]} columns={
           [
             { property: "nodeId", title: "Node Name", type: ColumnType.text },
             {
@@ -126,25 +148,16 @@ class MaintenenceViewComponent extends React.Component<MaintenenceViewComponentP
             },
             { property: "active", title: "Activation State", type: ColumnType.boolean, labels: { "true": "active", "false": "not active" }, },
             { property: "start", title: "Start Date (UTC)", type: ColumnType.text },
-            { property: "end", title: "End Date (UTC)", type: ColumnType.text },
-            {
-              property: "actions", title: "Actions", type: ColumnType.custom, customControl: ({ rowData }) => (
-                <>
-                  <div className={classes.spacer}>
-                    <Tooltip title={"1h from now"} ><Button className={classes.button} onClick={(event) => this.onOpenPlus1hEditMaintenenceEntryDialog(event, rowData)} >+1h</Button></Tooltip>
-                    <Tooltip title={"8h from now"} ><Button className={classes.button} onClick={(event) => this.onOpenPlus8hEditMaintenenceEntryDialog(event, rowData)} >+8h</Button></Tooltip>
-                  </div>
-                  <div className={classes.spacer}>
-                    <Tooltip title={"Edit"} ><IconButton className={classes.button} onClick={(event) => this.onOpenEditMaintenenceEntryDialog(event, rowData)} ><EditIcon /></IconButton></Tooltip>
-                    <Tooltip title={"Remove"} ><IconButton disabled={!!rowData[spoofSymbol]} className={classes.button} onClick={(event) => this.onOpenRemoveMaintenenceEntryDialog(event, rowData)} ><RemoveIcon /></IconButton></Tooltip>
-                  </div>
-                </>
-              )
-            },
+            { property: "end", title: "End Date (UTC)", type: ColumnType.text }
           ]
-        } idProperty={'_id'}{...this.props.maintenanceEntriesActions} {...this.props.maintenanceEntriesProperties} asynchronus > </MaintenenceEntriesTable>
+        } idProperty={'_id'}{...this.props.maintenanceEntriesActions} {...this.props.maintenanceEntriesProperties} asynchronus createContextMenu={rowData => {
+          return this.getContextMenu(rowData);
+        }} >
+        </MaintenenceEntriesTable>
         <EditMaintenenceEntryDialog initialMaintenenceEntry={this.state.maintenenceEntryToEdit} mode={this.state.maintenenceEntryEditorMode}
           onClose={this.onCloseEditMaintenenceEntryDialog} />
+        <RefreshMaintenanceEntriesDialog mode={this.state.refreshMaintenenceEntriesEditorMode}
+          onClose={this.onCloseRefreshMaintenenceEntryDialog} />
       </>
     );
   }
@@ -162,8 +175,8 @@ class MaintenenceViewComponent extends React.Component<MaintenenceViewComponentP
   }
 
   private onOpenPlus1hEditMaintenenceEntryDialog = (event: React.MouseEvent<HTMLElement>, entry: MaintenenceEntry) => {
-    event.preventDefault();
-    event.stopPropagation();
+    // event.preventDefault();
+    // event.stopPropagation();
     const startTime = (new Date().valueOf());
     const endTime = startTime + (1 * 60 * 60 * 1000);
     this.setState({
@@ -177,8 +190,8 @@ class MaintenenceViewComponent extends React.Component<MaintenenceViewComponentP
   }
 
   private onOpenPlus8hEditMaintenenceEntryDialog = (event: React.MouseEvent<HTMLElement>, entry: MaintenenceEntry) => {
-    event.preventDefault();
-    event.stopPropagation();
+    // event.preventDefault();
+    // event.stopPropagation();
     const startTime = (new Date().valueOf());
     const endTime = startTime + (8 * 60 * 60 * 1000);
     this.setState({
@@ -192,8 +205,8 @@ class MaintenenceViewComponent extends React.Component<MaintenenceViewComponentP
   }
 
   private onOpenEditMaintenenceEntryDialog = (event: React.MouseEvent<HTMLElement>, entry: MaintenenceEntry) => {
-    event.preventDefault();
-    event.stopPropagation();
+    // event.preventDefault();
+    // event.stopPropagation();
     const startTime = (new Date().valueOf());
     const endTime = startTime;
     this.setState({
@@ -208,8 +221,8 @@ class MaintenenceViewComponent extends React.Component<MaintenenceViewComponentP
   }
 
   private onOpenRemoveMaintenenceEntryDialog = (event: React.MouseEvent<HTMLElement>, entry: MaintenenceEntry) => {
-    event.preventDefault();
-    event.stopPropagation();
+    // event.preventDefault();
+    // event.stopPropagation();
     const startTime = (new Date().valueOf());
     const endTime = startTime;
     this.setState({
@@ -229,7 +242,13 @@ class MaintenenceViewComponent extends React.Component<MaintenenceViewComponentP
       maintenenceEntryEditorMode: EditMaintenenceEntryDialogMode.None,
     });
   }
+
+  private onCloseRefreshMaintenenceEntryDialog = () => {
+    this.setState({
+      refreshMaintenenceEntriesEditorMode: RefreshMaintenanceEntriesDialogMode.None,
+    });
+  }
 }
 
 export const MaintenenceView = withStyles(styles)(connect(mapProps, mapDispatcher)(MaintenenceViewComponent));
-export default MaintenenceView;
\ No newline at end of file
+export default MaintenenceView;
index ddce567..0f0e746 100644 (file)
   },
   "peerDependencies": {
     "@fortawesome/free-solid-svg-icons": "5.6.3",
-    "@types/react": "16.9.19",
-    "@types/react-dom": "16.9.5",
-    "@types/react-router-dom": "4.3.1",
+    "@types/react": "17.0.3",
+    "@types/react-dom": "17.0.2",
+    "@types/react-router-dom": "5.1.7",
     "@material-ui/core": "4.11.0",
     "@material-ui/icons": "4.9.1",
     "@types/classnames": "2.2.6",
     "@types/flux": "3.1.8",
     "@types/jquery": "3.3.10",
     "jquery": "3.3.1",
-    "react": "16.12.0",
-    "react-dom": "16.12.0",
-    "react-router-dom": "4.3.1"
+    "react": "17.0.1",
+    "react-dom": "17.0.1",
+    "react-router-dom": "5.2.0"
   }
 }
\ No newline at end of file
diff --git a/sdnr/wt/odlux/apps/mediatorApp/src/components/refreshMediatorDialog.tsx b/sdnr/wt/odlux/apps/mediatorApp/src/components/refreshMediatorDialog.tsx
new file mode 100644 (file)
index 0000000..af94f5a
--- /dev/null
@@ -0,0 +1,117 @@
+/**
+ * ============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 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 { avaliableMediatorServersReloadAction } from '../handlers/avaliableMediatorServersHandler';
+import { IDispatcher, connect, Connect } from '../../../../framework/src/flux/connect';
+
+import { MediatorServer } from '../models/mediatorServer';
+
+export enum RefreshMediatorDialogMode {
+  None = "none",
+  RefreshMediatorTable = "RefreshMediatorTable",
+}
+
+const mapDispatch = (dispatcher: IDispatcher) => ({
+  refreshMediator: () => dispatcher.dispatch(avaliableMediatorServersReloadAction)
+});
+
+type DialogSettings = {
+  dialogTitle: string,
+  dialogDescription: string,
+  applyButtonText: string,
+  cancelButtonText: string,
+  enableMountIdEditor: boolean,
+  enableUsernameEditor: boolean,
+  enableExtendedEditor: boolean,
+}
+
+const settings: { [key: string]: DialogSettings } = {
+  [RefreshMediatorDialogMode.None]: {
+    dialogTitle: "",
+    dialogDescription: "",
+    applyButtonText: "",
+    cancelButtonText: "",
+    enableMountIdEditor: false,
+    enableUsernameEditor: false,
+    enableExtendedEditor: false,
+  },
+  [RefreshMediatorDialogMode.RefreshMediatorTable]: {
+    dialogTitle: "Do you want to refresh the Mediator table?",
+    dialogDescription: "",
+    applyButtonText: "Yes",
+    cancelButtonText: "Cancel",
+    enableMountIdEditor: true,
+    enableUsernameEditor: true,
+    enableExtendedEditor: true,
+  }
+}
+
+type RefreshMediatorDialogComponentProps = Connect<undefined, typeof mapDispatch> & {
+  mode: RefreshMediatorDialogMode;
+  onClose: () => void;
+};
+
+type RefreshMediatorDialogComponentState = MediatorServer & { isNameValid: boolean, isHostSet: boolean };
+
+class RefreshMediatorDialogComponent extends React.Component<RefreshMediatorDialogComponentProps, RefreshMediatorDialogComponentState> {
+  constructor(props: RefreshMediatorDialogComponentProps) {
+    super(props);
+  }
+
+  render(): JSX.Element {
+    const setting = settings[this.props.mode];
+    return (
+      <Dialog open={this.props.mode !== RefreshMediatorDialogMode.None}>
+        <DialogTitle id="form-dialog-title" aria-label={`${setting.dialogTitle.replace(/ /g, "-").toLowerCase()}-dialog`}>{setting.dialogTitle}</DialogTitle>
+        <DialogContent>
+          <DialogContentText>
+            {setting.dialogDescription}
+          </DialogContentText>
+        </DialogContent>
+        <DialogActions>
+          <Button aria-label="dialog-confirm-button" onClick={(event) => {
+            this.onRefresh();
+          }} > {setting.applyButtonText} </Button>
+          <Button aria-label="dialog-cancel-button" onClick={(event) => {
+            this.onCancel();
+          }} color="secondary"> {setting.cancelButtonText} </Button>
+        </DialogActions>
+      </Dialog>
+    )
+  }
+
+  private onRefresh = () => {
+    this.props.refreshMediator();
+    this.props.onClose();
+  };
+
+  private onCancel = () => {
+    this.props.onClose();
+  }
+}
+
+export const RefreshMediatorDialog = connect(undefined, mapDispatch)(RefreshMediatorDialogComponent);
+export default RefreshMediatorDialog;
\ No newline at end of file
index c16906a..0f4ebbe 100644 (file)
@@ -22,6 +22,7 @@ import AddIcon from '@material-ui/icons/Add';
 import IconButton from '@material-ui/core/IconButton';
 import EditIcon from '@material-ui/icons/Edit';
 import DeleteIcon from '@material-ui/icons/Delete';
+import Refresh from '@material-ui/icons/Refresh';
 
 import { IApplicationStoreState } from '../../../../framework/src/store/applicationStore';
 import connect, { IDispatcher, Connect } from '../../../../framework/src/flux/connect';
@@ -31,6 +32,7 @@ import { createAvaliableMediatorServersProperties, createAvaliableMediatorServer
 
 import { MediatorServer } from '../models/mediatorServer';
 import EditMediatorServerDialog, { EditMediatorServerDialogMode } from '../components/editMediatorServerDialog';
+import RefreshMediatorDialog, { RefreshMediatorDialogMode } from '../components/refreshMediatorDialog';
 import { NavigateToApplication } from '../../../../framework/src/actions/navigationActions';
 
 const MediatorServersTable = MaterialTable as MaterialTableCtorType<MediatorServer>;
@@ -67,7 +69,8 @@ type MediatorServerSelectionComponentProps = Connect<typeof mapProps, typeof map
 
 type MediatorServerSelectionComponentState = {
   mediatorServerToEdit: MediatorServer,
-  mediatorServerEditorMode: EditMediatorServerDialogMode
+  mediatorServerEditorMode: EditMediatorServerDialogMode,
+  refreshMediatorEditorMode: RefreshMediatorDialogMode
 }
 
 let initialSorted = false;
@@ -80,11 +83,19 @@ class MediatorServerSelectionComponent extends React.Component<MediatorServerSel
     this.state = {
       mediatorServerEditorMode: EditMediatorServerDialogMode.None,
       mediatorServerToEdit: emptyMediatorServer,
+      refreshMediatorEditorMode: RefreshMediatorDialogMode.None
     }
   }
 
   render() {
     const { classes } = this.props;
+    const refreshMediatorAction = {
+      icon: Refresh, tooltip: 'Refresh Mediator Server Table', onClick: () => {
+        this.setState({
+          refreshMediatorEditorMode: RefreshMediatorDialogMode.RefreshMediatorTable
+        });
+      }
+    };
 
     const addMediatorServerActionButton = {
       icon: AddIcon, tooltip: 'Add', onClick: () => {
@@ -96,7 +107,7 @@ class MediatorServerSelectionComponent extends React.Component<MediatorServerSel
     };
     return (
       <>
-        <MediatorServersTable stickyHeader title={"Mediator"} customActionButtons={[addMediatorServerActionButton]} idProperty={"id"}
+        <MediatorServersTable stickyHeader title={"Mediator"} customActionButtons={[refreshMediatorAction, addMediatorServerActionButton]} idProperty={"id"}
           {...this.props.mediatorServersActions} {...this.props.mediatorServersProperties} columns={[
             { property: "name", title: "Name", type: ColumnType.text },
             { property: "url", title: "Url", type: ColumnType.text },
@@ -113,6 +124,10 @@ class MediatorServerSelectionComponent extends React.Component<MediatorServerSel
           mediatorServer={this.state.mediatorServerToEdit}
           mode={this.state.mediatorServerEditorMode}
           onClose={this.onCloseEditMediatorServerDialog} />
+        <RefreshMediatorDialog
+          mode={this.state.refreshMediatorEditorMode}
+          onClose={this.onCloseRefreshMediatorDialog}
+        />
       </>
     );
   }
@@ -158,6 +173,11 @@ class MediatorServerSelectionComponent extends React.Component<MediatorServerSel
       mediatorServerToEdit: emptyMediatorServer,
     });
   }
+  private onCloseRefreshMediatorDialog = () => {
+    this.setState({
+      refreshMediatorEditorMode: RefreshMediatorDialogMode.None
+    });
+  }
 }
 
 
index 069cb13..bcda2fb 100644 (file)
     "@odlux/framework": "*"
   },
   "peerDependencies": {
-    "@types/react": "16.9.19",
-    "@types/react-dom": "16.9.5",
-    "@types/react-router-dom": "4.3.1",
+    "@types/react": "17.0.3",
+    "@types/react-dom": "17.0.2",
+    "@types/react-router-dom": "5.1.7",
     "@material-ui/core": "4.11.0",
     "@material-ui/icons": "4.9.1",
     "@types/classnames": "2.2.6",
     "@types/flux": "3.1.8",
     "@types/jquery": "3.3.10",
     "jquery": "3.3.1",
-    "react": "16.12.0",
-    "react-dom": "16.12.0",
-    "react-router-dom": "4.3.1"
+    "react": "17.0.1",
+    "react-dom": "17.0.1",
+    "react-router-dom": "5.2.0"
   }
 }
\ No newline at end of file
index 7119f1b..cde312c 100644 (file)
     "object.values": "^1.1.1"
   },
   "peerDependencies": {
-    "@types/react": "16.9.19",
-    "@types/react-dom": "16.9.5",
-    "@types/react-router-dom": "4.3.1",
+    "@types/react": "17.0.3",
+    "@types/react-dom": "17.0.2",
+    "@types/react-router-dom": "5.1.7",
     "@material-ui/core": "4.11.0",
     "@material-ui/icons": "4.9.1",
     "@types/classnames": "2.2.6",
     "@types/flux": "3.1.8",
     "@types/jquery": "3.3.10",
     "jquery": "3.3.1",
-    "react": "16.12.0",
-    "react-dom": "16.12.0",
-    "react-router-dom": "4.3.1"
+    "react": "17.0.1",
+    "react-dom": "17.0.1",
+    "react-router-dom": "5.2.0"
     
   }
 }
index b2c7246..57091ae 100644 (file)
@@ -68,7 +68,9 @@ const LinkDetails: React.FunctionComponent<props> = (props) => {
        const distance = props.link.length > 0 ? props.link.length : props.link.calculatedLength;
        const azimuthA = props.link.azimuthA;
        const azimuthB = props.link.azimuthB;
-       window.open(`/#/linkCalculation?lat1=${siteA.lat}&lon1=${siteA.lon}&lat2=${siteB.lat}&lon2=${siteB.lon}&siteA=${nameA}&siteB=${nameB}&azimuthA=${azimuthA}&azimuthB=${azimuthB}&distance=${distance}&amslSiteA=${siteA.amsl}&AGLsiteA=${siteA.antennaHeight}&amslSiteB=${siteB.amsl}&AGLsiteB=${siteB.antennaHeight}`)
+       
+       const baseUrl = window.location.pathname.split('#')[0];
+       window.open(`${baseUrl}#/linkCalculation?lat1=${siteA.lat}&lon1=${siteA.lon}&lat2=${siteB.lat}&lon2=${siteB.lon}&siteA=${nameA}&siteB=${nameB}&azimuthA=${azimuthA}&azimuthB=${azimuthB}&distance=${distance}&amslSiteA=${siteA.amsl}&AGLsiteA=${siteA.antennaHeight}&amslSiteB=${siteB.amsl}&AGLsiteB=${siteB.antennaHeight}`)
 
     }
 
index b820746..442a508 100644 (file)
@@ -56,12 +56,12 @@ export const MapReducer: IActionHandler<mapState> = (state=initialState, action:
     }
     else if(action instanceof HighlightSiteAction){
        
-    state = Object.assign({}, state, {selectedLink: null, selectedSite:{type: "Feature", properties: {id: action.site.name, type:action.site.type}, geometry:{type:"Point", coordinates:[action.site.location.lon,action.site.location.lat ]}}})
+    state = Object.assign({}, state, {selectedLink: null, selectedSite:{type: "Feature", properties: {id: action.site.id, type:action.site.type}, geometry:{type:"Point", coordinates:[action.site.location.lon,action.site.location.lat ]}}})
 
     }else if (action instanceof ZoomToSearchResultAction){
         state = Object.assign({}, state, {zoomToElement:{lat: action.lat, lon: action.lon}});
     }else if (action instanceof AddAlarmAction){
-        state = Object.assign({}, state, {alarmlement:{type: "Feature", properties: {id: action.site.name, type:action.site.type}, geometry:{type:"Point", coordinates:[action.site.location.lon,action.site.location.lat ]}}});
+        state = Object.assign({}, state, {alarmlement:{type: "Feature", properties: {id: action.site.id, type:action.site.type}, geometry:{type:"Point", coordinates:[action.site.location.lon,action.site.location.lat ]}}});
 
     }else if(action instanceof SetCoordinatesAction){
         state = Object.assign({}, state, {lat:action.lat, lon: action.lon, zoom:action.zoom});
index 9a7f77a..e64c08c 100644 (file)
@@ -20,8 +20,8 @@
     require(["app","connectApp","faultApp", "networkMapApp", "configurationApp", "linkCalculationApp"], function (app, connectApp, faultApp, networkMapApp, configurationApp, linkCalculationApp) {
       connectApp.register();
       //faultApp.register();
-      configurationApp.register();
-      linkCalculationApp.register();
+      //configurationApp.register();
+      //linkCalculationApp.register();
       networkMapApp.register();
       app("./app.tsx").runApplication();
     });
index 2b69cc3..305fc07 100644 (file)
     "chart.js": "2.8.0"
   },
   "peerDependencies": {
-    "@types/react": "16.9.19",
-    "@types/react-dom": "16.9.5",
-    "@types/react-router-dom": "4.3.1",
+    "@types/react": "17.0.3",
+    "@types/react-dom": "17.0.2",
+    "@types/react-router-dom": "5.1.7",
     "@material-ui/core": "4.11.0",
     "@material-ui/icons": "4.9.1",
     "@types/classnames": "2.2.6",
     "@types/flux": "3.1.8",
     "@types/jquery": "3.3.10",
     "jquery": "3.3.1",
-    "react": "16.12.0",
-    "react-dom": "16.12.0",
-    "react-router-dom": "4.3.1"
+    "react": "17.0.1",
+    "react-dom": "17.0.1",
+    "react-router-dom": "5.2.0"
   }
 }
\ No newline at end of file
index 3ecd34c..345fc8b 100644 (file)
   "license": "Apache-2.0",\r
   "peerDependencies": {\r
     "@types/node": "11.11.6",\r
-    "@types/react": "16.9.19",\r
-    "@types/react-dom": "16.9.5",\r
-    "@types/react-router-dom": "4.3.1",\r
+    "@types/react": "17.0.3",\r
+    "@types/react-dom": "17.0.2",\r
+    "@types/react-router-dom": "5.1.7",\r
     "@material-ui/core": "4.11.0",\r
     "@material-ui/icons": "4.9.1",\r
     "@types/classnames": "2.2.6",\r
     "@types/flux": "3.1.8",\r
     "@types/jquery": "3.3.10",\r
     "jquery": "3.3.1",\r
-    "react": "16.12.0",\r
-    "react-dom": "16.12.0",\r
-    "react-router-dom": "4.3.1",\r
+    "react": "17.0.1",\r
+    "react-dom": "17.0.1",\r
+    "react-router-dom": "5.2.0",\r
     "@fortawesome/react-fontawesome": "0.1.3",\r
     "@fortawesome/fontawesome-svg-core": "1.2.12",\r
     "@fortawesome/free-solid-svg-icons": "5.6.3",\r
index 3f9ecbe..9d648b5 100644 (file)
@@ -46,7 +46,7 @@
     <properties>
         <buildtime>${maven.build.timestamp}</buildtime>
         <distversion>ONAP Frankfurt (Neon, mdsal ${odl.mdsal.version})</distversion>
-        <buildno>90.49cc396(21/02/17)</buildno>
+        <buildno>96.078ad12(21/03/25)</buildno>
         <odlux.version>ONAP SDN-R | ONF Wireless for ${distversion} - Build: ${buildtime} ${buildno} ${project.version}</odlux.version>
     </properties>
 
                         </goals>
                         <phase>initialize</phase>
                         <configuration>
-                            <arguments>add lerna@3.13.1 -W --exact</arguments>
+                            <arguments>add lerna@3.22.1 -W --exact</arguments>
                             <installDirectory>${project.basedir}</installDirectory>
                             <workingDirectory>${project.basedir}/../</workingDirectory>
                         </configuration>
index 23ae2fb..2d913be 100644 (file)
@@ -68,6 +68,10 @@ export const runApplication = () => {
   const initialToken = localStorage.getItem("userToken");\r
   const applicationStore = applicationStoreCreator();\r
 \r
+  if (initialToken) {\r
+    applicationStore.dispatch(new UpdateUser(User.fromString(initialToken) || undefined));\r
+  }\r
+\r
   window.onerror = function (msg: string, url: string, line: number, col: number, error: Error) {\r
     // Note that col & error are new to the HTML 5 spec and may not be\r
     // supported in every browser.  It worked for me in Chrome.\r
@@ -98,9 +102,7 @@ export const runApplication = () => {
 \r
   ReactDOM.render(<App />, document.getElementById('app'));\r
 \r
-  if (initialToken) {\r
-    applicationStore.dispatch(new UpdateUser(User.fromString(initialToken) || undefined));\r
-  }\r
+  \r
 \r
 };\r
 \r
index 7d4633b..c74fd1a 100644 (file)
@@ -32,13 +32,14 @@ import { EnhancedTableHead } from './tableHead';
 import { EnhancedTableFilter } from './tableFilter';
 
 import { ColumnModel, ColumnType } from './columnModel';
-import { Omit, Menu } from '@material-ui/core';
+import { Omit, Menu, makeStyles } from '@material-ui/core';
 
 import { SvgIconProps } from '@material-ui/core/SvgIcon/SvgIcon';
 
 import { DividerTypeMap } from '@material-ui/core/Divider';
 import { MenuItemProps } from '@material-ui/core/MenuItem';
 import { flexbox } from '@material-ui/system';
+import { RowDisabled } from './utilities';
 export { ColumnModel, ColumnType } from './columnModel';
 
 type propType = string | number | null | undefined | (string | number)[];
@@ -103,6 +104,34 @@ const styles = (theme: Theme) => createStyles({
   }
 });
 
+const useTableRowExtStyles = makeStyles((theme: Theme) => createStyles({
+  disabled: {
+    color: "rgba(180, 180, 180, 0.7)",
+  },
+}));
+
+type GetStatelessComponentProps<T> = T extends (props: infer P & { children?: React.ReactNode }) => any ? P : any;
+type TableRowExtProps = GetStatelessComponentProps<typeof TableRow> & { disabled: boolean };
+const TableRowExt : React.FC<TableRowExtProps> = (props) => {
+  const [disabled, setDisabled] = React.useState(true);
+  const classes = useTableRowExtStyles();
+  
+  const onMouseDown = (ev: React.MouseEvent<HTMLElement>) => {
+      if (ev.button ===1){
+        setDisabled(!disabled);  
+        ev.preventDefault();
+        ev.stopPropagation();
+      } else if (props.disabled && disabled) {
+        ev.preventDefault();
+        ev.stopPropagation();
+      }
+  }; 
+
+  return (   
+    <TableRow {...{...props,  color: props.disabled && disabled ? '#a0a0a0' : undefined , className: props.disabled && disabled ? classes.disabled : '', onMouseDown, onContextMenu: props.disabled && disabled ? onMouseDown : props.onContextMenu } }  /> 
+  );
+};
+
 export type MaterialTableComponentState<TData = {}> = {
   order: 'asc' | 'desc';
   orderBy: string | null;
@@ -130,7 +159,7 @@ type MaterialTableComponentBaseProps<TData> = WithStyles<typeof styles> & {
   enableSelection?: boolean;
   disableSorting?: boolean;
   disableFilter?: boolean;
-  customActionButtons?: { icon: React.ComponentType<SvgIconProps>, tooltip?: string, onClick: () => void }[];
+  customActionButtons?: { icon: React.ComponentType<SvgIconProps>, tooltip?: string, onClick: () => void, disabled?: boolean }[];
   onHandleClick?(event: React.MouseEvent<HTMLTableRowElement>, rowData: TData): void;
   createContextMenu?: (row: TData) => React.ReactElement<MenuItemProps | DividerTypeMap<{}, "hr">, React.ComponentType<MenuItemProps | DividerTypeMap<{}, "hr">>>[];
 };
@@ -222,12 +251,12 @@ class MaterialTableComponent<TData extends {} = {}> extends React.Component<Mate
             <TableBody>
               {showFilter && <EnhancedTableFilter columns={columns} filter={filter} onFilterChanged={this.onFilterChanged} enableSelection={this.props.enableSelection} /> || null}
               {rows // may need ordering here
-                .map((entry: TData & { [key: string]: any }, index) => {
+                .map((entry: TData & { [RowDisabled]?: boolean, [kex: string]: any }, index) => {
                   const entryId = getId(entry);
                   const isSelected = this.isSelected(entryId);
                   const contextMenu = (this.props.createContextMenu && this.state.contextMenuInfo.index === index && this.props.createContextMenu(entry)) || null;
                   return (
-                    <TableRow
+                    <TableRowExt
                       hover
                       onClick={event => {
                         if (this.props.createContextMenu) {
@@ -252,9 +281,10 @@ class MaterialTableComponent<TData extends {} = {}> extends React.Component<Mate
                       tabIndex={-1}
                       key={entryId}
                       selected={isSelected}
+                      disabled={entry[RowDisabled] || false}
                     >
                       {this.props.enableSelection
-                        ? <TableCell padding="checkbox" style={{ width: "50px" }}>
+                        ? <TableCell padding="checkbox" style={{ width: "50px", color:  entry[RowDisabled] || false ? "inherit" : undefined } }>
                           <Checkbox checked={isSelected} />
                         </TableCell>
                         : null
@@ -264,7 +294,7 @@ class MaterialTableComponent<TData extends {} = {}> extends React.Component<Mate
                           col => {
                             const style = col.width ? { width: col.width } : {};
                             return (
-                              <TableCell aria-label={col.title? col.title.toLowerCase().replace(/\s/g, "-") : col.property.toLowerCase().replace(/\s/g, "-")} key={col.property} align={col.type === ColumnType.numeric && !col.align ? "right" : col.align} style={style}>
+                              <TableCell style={ entry[RowDisabled] || false ? { ...style, color: "inherit"  } : style } aria-label={col.title? col.title.toLowerCase().replace(/\s/g, "-") : col.property.toLowerCase().replace(/\s/g, "-")} key={col.property} align={col.type === ColumnType.numeric && !col.align ? "right" : col.align} >
                                 {col.type === ColumnType.custom && col.customControl
                                   ? <col.customControl className={col.className} style={col.style} rowData={entry} />
                                   : col.type === ColumnType.boolean
@@ -280,7 +310,7 @@ class MaterialTableComponent<TData extends {} = {}> extends React.Component<Mate
                         anchorPosition={this.state.contextMenuInfo.mouseY != null && this.state.contextMenuInfo.mouseX != null ? { top: this.state.contextMenuInfo.mouseY, left: this.state.contextMenuInfo.mouseX } : undefined}>
                         {contextMenu}
                       </Menu> || null}
-                    </TableRow>
+                    </TableRowExt>
                   );
                 })}
               {emptyRows > 0 && (
index 3b2f8e0..f7de0a0 100644 (file)
@@ -67,7 +67,7 @@ interface ITableToolbarComponentProps extends WithStyles<typeof styles> {
   numSelected: number | null;
   title?: string;
   tableId?: string;
-  customActionButtons?: { icon: React.ComponentType<SvgIconProps>, tooltip?: string, onClick: () => void }[];
+  customActionButtons?: { icon: React.ComponentType<SvgIconProps>, tooltip?: string, onClick: () => void, disabled?: boolean }[];
   onToggleFilter: () => void;
   onExportToCsv: () => void;
 }
@@ -110,7 +110,7 @@ class TableToolbarComponent extends React.Component<ITableToolbarComponentProps,
           {this.props.customActionButtons
             ? this.props.customActionButtons.map((action, ind) => (
               <Tooltip key={`custom-action-${ind}`} title={action.tooltip || ''}>
-                <IconButton aria-label={buttonPrefix + `custom-action-${ind}`} onClick={() => action.onClick()}>
+                <IconButton disabled={action.disabled} aria-label={buttonPrefix + `custom-action-${ind}`} onClick={() => action.onClick()}>
                   <action.icon />
                 </IconButton>
               </Tooltip>
index 07ffe2f..544e14e 100644 (file)
@@ -21,12 +21,14 @@ import { Dispatch } from '../../flux/store';
 import { AddErrorInfoAction } from '../../actions/errorActions';
 import { IApplicationStoreState } from '../../store/applicationStore';
 
+export const RowDisabled = Symbol("RowDisabled");
 import { DataCallback } from ".";
+
 export interface IExternalTableState<TData> {
   order: 'asc' | 'desc';
   orderBy: string | null;
   selected: any[] | null;
-  rows: TData[];
+  rows: (TData & { [RowDisabled]?: boolean })[];
   total: number;
   page: number;
   rowsPerPage: number;
@@ -36,8 +38,31 @@ export interface IExternalTableState<TData> {
   preFilter: { [property: string]: string };
 }
 
+export type ExternalMethodes<TData> = {
+  reloadAction: (dispatch: Dispatch, getAppState: () => IApplicationStoreState) => Promise<void | AddErrorInfoAction>;
+  createActions: (dispatch: Dispatch, skipRefresh?: boolean) => {
+    onRefresh: () => void;
+    onHandleRequestSort: (orderBy: string) => void;
+    onHandleExplicitRequestSort: (property: string, sortOrder: "asc" | "desc") => void;
+    onToggleFilter: (refresh?: boolean | undefined) => void;
+    onFilterChanged: (property: string, filterTerm: string) => void;
+    onHandleChangePage: (page: number) => void;
+    onHandleChangeRowsPerPage: (rowsPerPage: number | null) => void;
+ };
+ createPreActions: (dispatch: Dispatch, skipRefresh?: boolean) => {
+  onPreFilterChanged: (preFilter: {
+      [key: string]: string;
+  }) => void;
+ };
+ createProperties: (state: IApplicationStoreState) => IExternalTableState<TData>;
+ actionHandler: IActionHandler<IExternalTableState<TData>, Action>;
+}
+
+
 /** Create an actionHandler and actions for external table states. */
-export function createExternal<TData>(callback: DataCallback<TData>, selectState: (appState: IApplicationStoreState) => IExternalTableState<TData>) {
+export function createExternal<TData>(callback: DataCallback<TData>, selectState: (appState: IApplicationStoreState) => IExternalTableState<TData>) : ExternalMethodes<TData> ;
+export function createExternal<TData>(callback: DataCallback<TData>, selectState: (appState: IApplicationStoreState) => IExternalTableState<TData>, disableRow: (data: TData) => boolean) : ExternalMethodes<TData>;
+export function createExternal<TData>(callback: DataCallback<TData>, selectState: (appState: IApplicationStoreState) => IExternalTableState<TData>, disableRow?: (data: TData) => boolean) : ExternalMethodes<TData> {
 
   //#region Actions
   abstract class TableAction extends Action { }
@@ -131,7 +156,9 @@ export function createExternal<TData>(callback: DataCallback<TData>, selectState
       state = {
         ...state,
         loading: false,
-        rows: action.result.rows,
+        rows: disableRow 
+          ? action.result.rows.map((row: TData) => ({...row, [RowDisabled]: disableRow(row) })) 
+          : action.result.rows,
         total: action.result.total,
         page: action.result.page,
       }
@@ -191,7 +218,7 @@ export function createExternal<TData>(callback: DataCallback<TData>, selectState
     dispatch(new RefreshAction());
     const ownState = selectState(getAppState());
     const filter = { ...ownState.preFilter, ...(ownState.showFilter && ownState.filter || {}) };
-    Promise.resolve(callback(ownState.page, ownState.rowsPerPage, ownState.orderBy, ownState.order, filter)).then(result => {
+    return Promise.resolve(callback(ownState.page, ownState.rowsPerPage, ownState.orderBy, ownState.order, filter)).then(result => {
 
       if (ownState.page > 0 && ownState.rowsPerPage * ownState.page > result.total) { //if result is smaller than the currently shown page, new search and repaginate
 
@@ -207,30 +234,7 @@ export function createExternal<TData>(callback: DataCallback<TData>, selectState
       }
 
 
-    }).catch(error => new AddErrorInfoAction(error));
-  };
-
-  const reloadActionAsync = async (dispatch: Dispatch, getAppState: () => IApplicationStoreState) => {
-    dispatch(new RefreshAction());
-    const ownState = selectState(getAppState());
-    const filter = { ...ownState.preFilter, ...(ownState.showFilter && ownState.filter || {}) };
-
-    try {
-      const result = await Promise.resolve(callback(ownState.page, ownState.rowsPerPage, ownState.orderBy, ownState.order, filter));
-
-
-      if (ownState.page > 0 && ownState.rowsPerPage * ownState.page > result.total) { //if result is smaller than the currently shown page, new search and repaginate
-
-        let newPage = Math.floor(result.total / ownState.rowsPerPage);
-
-        const repaginationResult = await Promise.resolve(callback(newPage, ownState.rowsPerPage, ownState.orderBy, ownState.order, filter));
-        dispatch(new SetResultAction(repaginationResult));
-      } else {
-        dispatch(new SetResultAction(result));
-      }
-    } catch (error) {
-      new AddErrorInfoAction(error);
-    }
+    }).catch(error => dispatch(new AddErrorInfoAction(error)));
   };
 
   const createPreActions = (dispatch: Dispatch, skipRefresh: boolean = false) => {
@@ -303,6 +307,6 @@ export function createExternal<TData>(callback: DataCallback<TData>, selectState
     createProperties: createProperties,
     createPreActions: createPreActions,
     actionHandler: externalTableStateActionHandler,
-    reloadActionAsync: reloadActionAsync,
   }
-}
\ No newline at end of file
+}
+
index b5c1ee7..06df670 100644 (file)
@@ -58,7 +58,7 @@ const applicationStateInit: IApplicationState = {
 
 export const configureApplication = (config: ApplicationConfig) => {
   applicationStateInit.authentication = config.authentication === "oauth" ? "oauth" : "basic";
-  applicationStateInit.enablePolicy = config.authentication ? true : false;
+  applicationStateInit.enablePolicy = config.enablePolicy ? true : false;
 }
 
 export const applicationStateHandler: IActionHandler<IApplicationState> = (state = applicationStateInit, action) => {
index 30091b5..5625b1f 100644 (file)
@@ -15,7 +15,6 @@
  * the License.
  * ============LICENSE_END==========================================================================
  */
-import * as X2JS from 'x2js';
 import { ApplicationStore } from '../store/applicationStore';
 import { SetWebsocketAction } from '../actions/websocketAction';
 
@@ -26,81 +25,95 @@ let userLoggedOut = false;
 let wasWebsocketConnectionEstablished: undefined | boolean;
 let applicationStore: ApplicationStore | null;
 
-
 export interface IFormatedMessage {
-  notifType: string | null;
-  time: string;
+    "event-time": string,
+    "data": {
+        "counter": number,
+        "attribute-name": string,
+        "time-stamp": string,
+        "object-id-ref": string,
+        "new-value": string
+    },
+    "node-id": string,
+    "type": {
+        "namespace": string,
+        "revision": string,
+        "type": 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;
-
+function setCurrentSubscriptions(notificationSocket: WebSocket) {
+  const scopesToSubscribe = Object.keys(subscriptions);
+  if (notificationSocket.readyState === notificationSocket.OPEN) {
+    const data = {
+      'data': 'scopes',
+      'scopes':[{
+        "schema":{
+            "namespace":"*",
+            "revision":"*",
+            "notification": scopesToSubscribe 
+         }
+      }]
+    };
+    notificationSocket.send(JSON.stringify(data));
+    return true;
+  };
+  return false;
 }
 
-export function subscribe<TMessage extends IFormatedMessage = IFormatedMessage>(scope: string | string[], callback: SubscriptionCallback<TMessage>): boolean {
+function addScope<TMessage extends IFormatedMessage = IFormatedMessage>(scope: string | string[], callback: SubscriptionCallback<TMessage>) {
   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);
+    // 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);
       }
-    } else {
-      subscriptions[cur] = [callback];
-      acc.push(cur);
-    }
-    return acc;
-  }, []);
+      return acc;
+    }, []);
 
-  if (newScopesToSubscribe.length === 0) {
-    return true;
-  }
+    if (newScopesToSubscribe.length === 0) {
+      return true;
+    }
+    return false;
+}
 
-  return true;
+function removeScope<TMessage extends IFormatedMessage = IFormatedMessage>(scope: string | string[], callback: SubscriptionCallback<TMessage>) {
+  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;
+    }
+  });
 }
 
+export function subscribe<TMessage extends IFormatedMessage = IFormatedMessage>(scope: string | string[], callback: SubscriptionCallback<TMessage>): Promise<boolean> {
+  addScope(scope, callback)
+  return socketReady && socketReady.then((notificationSocket) => {
+    // send a subscription to all active scopes
+    return setCurrentSubscriptions(notificationSocket);
+  }) || 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;
-      }
-    });
-
+  removeScope(scope, callback);
+  return socketReady && socketReady.then((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));
-      return true;
-    }
-    return false;
-  });
+    return setCurrentSubscriptions(notificationSocket);
+  }) || true;
 }
 
 export const startNotificationService = (store: ApplicationStore) => {
@@ -111,24 +124,24 @@ const connect = (): Promise<WebSocket> => {
   return new Promise((resolve, reject) => {
     const notificationSocket = new WebSocket(socketUrl);
 
-    notificationSocket.onmessage = (event) => {
+    notificationSocket.onmessage = (event: MessageEvent<string>) => {
       // process received event
-      if (typeof event.data === 'string') {
-        const formated = formatData(event);
-        if (formated && formated.notifType) {
-          const callbacks = subscriptions[formated.notifType];
+      
+        if (event.data && typeof event.data === "string" ) {
+          const msg = JSON.parse(event.data) as IFormatedMessage;
+          const callbacks = msg?.type?.type && subscriptions[msg.type.type];
           if (callbacks) {
             callbacks.forEach(cb => {
               // ensure all callbacks will be called
               try {
-                return cb(formated);
+                return cb(msg);
               } catch (reason) {
                 console.error(reason);
               }
             });
           }
         }
-      }
+       
     };
 
     notificationSocket.onerror = function (error) {
@@ -148,14 +161,7 @@ const connect = (): Promise<WebSocket> => {
       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));
-      };
+      setCurrentSubscriptions(notificationSocket);
     };
 
     notificationSocket.onclose = function (event) {
@@ -171,8 +177,6 @@ const connect = (): Promise<WebSocket> => {
 }
 
 
-
-
 export const startWebsocketSession = () => {
   socketReady = connect();
   userLoggedOut = false;
index f05c7b8..c7b1224 100644 (file)
@@ -15,6 +15,8 @@
  * the License.
  * ============LICENSE_END==========================================================================
  */
+
+
 import { ApplicationStore } from "../store/applicationStore";
 import { ReplaceAction } from "../actions/navigationActions";
 
@@ -30,6 +32,46 @@ export const formEncode = (params: { [key: string]: string | number }) => Object
   return encodeURIComponent(key) + '=' + encodeURIComponent(params[key].toString());
 }).join('&');
 
+const wildcardToRegexp = (pattern: string) =>  {
+  return new RegExp('^' + pattern.split(/\*\*/).map((p) => p.split(/\*+/).map((i) => i.replace(/[|\\{}()[\]^$+*?.]/g, '\\$&')).join('^[/]')).join('.*') + '$');
+};
+
+export const getAccessPolicyByUrl = (url: string) => {
+  const result = {
+    GET : false,
+    POST: false,
+    PUT: false,
+    PATCH: false,
+    DELETE: false,
+  };
+  
+  if (!applicationStore) return result;
+
+  const { state: { framework: { applicationState: { enablePolicy }, authenticationState: { policies }}} } = applicationStore!;
+  
+  result.GET = true;
+  result.POST = true;
+  result.PUT = true;
+  result.PATCH = true;
+  result.DELETE = true; 
+
+  if (!enablePolicy || !policies || policies.length === 0) return result;
+
+  policies.forEach(p => {
+    const re = wildcardToRegexp(p.path);
+    if (re.test(url)) {
+      result.GET = p.methods.get != null ? p.methods.get : result.GET ;
+      result.POST = p.methods.post != null ? p.methods.post : result.POST ;
+      result.PUT = p.methods.put != null ? p.methods.put : result.PUT ;
+      result.PATCH = p.methods.patch != null ? p.methods.patch : result.PATCH ;
+      result.DELETE = p.methods.delete != null ? p.methods.delete : result.DELETE ;
+    }
+  }); 
+
+  return result;
+
+}
+
 /** Sends a rest request to the given path. 
  * @returns The data, or null it there was any error
  */
index f97d6ff..5d2257a 100644 (file)
@@ -20,6 +20,7 @@ import * as marked from 'marked';
 import * as hljs from 'highlight.js';
 import { requestRestExt } from '../services/restService';
 import { Button, Typography } from '@material-ui/core';
+import createBreakpoints from '@material-ui/core/styles/createBreakpoints';
 const defaultRenderer = new marked.Renderer();
 defaultRenderer.link = (href, title, text) => (
   `<a target="_blank" rel="noopener noreferrer" href="${href}" title="${title}">${text}</a>`
@@ -30,6 +31,23 @@ interface AboutState {
   isContentLoadedSucessfully: boolean;
 }
 
+type odluxVersion= {version:string,build:string, framework: string, 
+  applications:{
+    configurationApp: string,
+    connectApp: string,
+    eventLogApp: string,
+    faultApp: string,
+    helpApp: string,
+    inventoryApp: string,
+    linkCalculationApp: string,
+    maintenanceApp: string,
+    mediatorApp: string,
+    networkMapApp: string,
+    permanceHistoryApp: string
+  }};
+
+type topologyVersion = {version: string};
+
 class AboutComponent extends React.Component<any, AboutState> {
   textarea: React.RefObject<HTMLTextAreaElement>;
 
@@ -40,23 +58,58 @@ class AboutComponent extends React.Component<any, AboutState> {
     this.textarea = React.createRef();
     this.loadAboutContent();
   }
-  private getMarkOdluxVersionMarkdownTable(data:{version:string,build:string}|null|undefined):string{
+
+  private getMarkOdluxVersionMarkdownTable(data:odluxVersion|null|undefined):string{
     if(!data) {
       return "";
+    }else{
+      let applicationVersions= '';
+      if(data.applications){
+
+        applicationVersions = `| Framework | ${data.framework}|\n `+
+        `| ConnectApp | ${data.applications.connectApp}|\n `+
+        `| FaultApp | ${data.applications.faultApp}|\n `+
+        `| MaintenanceApp | ${data.applications.maintenanceApp}|\n `+
+        `| ConfigurationApp | ${data.applications.configurationApp}|\n `+
+        `| PerformanceHistoryApp | ${data.applications.permanceHistoryApp}|\n `+
+        `| InventoryApp | ${data.applications.inventoryApp}|\n `+
+        `| EventLogApp | ${data.applications.eventLogApp}|\n `+
+        `| MediatorApp | ${data.applications.mediatorApp}|\n `+
+        `| NetworkMapApp | ${data.applications.networkMapApp}|\n `+
+        `| LinkCalculatorApp | ${data.applications.linkCalculationApp}|\n `+
+        `| HelpApp | ${data.applications.helpApp}|\n `;
+      }
+    
+    return `| | |\n| --- | --- |\n| Version | ${data.version} |\n| Build timestamp | ${data.build}|\n`+
+    applicationVersions;
     }
-    return `| | |\n| --- | --- |\n| Version | ${data.version} |\n| Build timestamp | ${data.build}|`
   }
+
+  private getTopologyVersionMarkdownTable(data: topologyVersion|null|undefined){ 
+    if(!data){
+      return "No version";
+    }
+    else
+    {
+      return `| | |\n| --- | --- |\n| Version | ${data.version} |\n`
+    }
+  }
+
   private loadAboutContent(): void {
     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 p2 = requestRestExt<odluxVersion>(`${baseUri}version.json`);
+    const p3 = requestRestExt<any>(`/topology/info/version`);
+
+    Promise.all([p1,p2, p3]).then((responses) => {
       const response = responses[0];
-      const response2 = responses[1];    
+      const response2 = responses[1]; 
+      const response3 = responses[2];   
       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 content2 = `\n## ODLUX Version Info\n`+(response2.status == 200 ? this.getMarkOdluxVersionMarkdownTable(response2.data) : `${response2.message}` || "ODLUX Server error");
+      const content3 =  `\n## Topology API Version Info\n`+(response3.status == 200 ? this.getTopologyVersionMarkdownTable(response3.data): `Topology API not available`);
       const loadedSucessfully = response.status == 200 ? true : false;
-      this.setState({ content: (content + content2) || null, isContentLoadedSucessfully: loadedSucessfully });
+      this.setState({ content: (content + content2 + content3 ) || null, isContentLoadedSucessfully: loadedSucessfully });
     }).catch((error) => {
       this.setState({ content: error })
     })
index d801f7c..cb36bc8 100644 (file)
@@ -4,7 +4,7 @@
     "outDir": "./dist",
     "sourceMap": true,
     "forceConsistentCasingInFileNames": true,
-    "allowSyntheticDefaultImports": false,
+    "allowSyntheticDefaultImports": true,
     "allowUnreachableCode": false,
     "allowUnusedLabels": false,
     "noFallthroughCasesInSwitch": true,
index 5d0d8fc..b5d31c2 100644 (file)
@@ -201,55 +201,55 @@ module.exports = (env) => {
       proxy: {\r
         "/about": {\r
           // target: "http://10.20.6.29:48181",\r
-          target: "http://sdnr:8181",\r
+          target: "http://localhost:18181",\r
           secure: false\r
         }, \r
         "/yang-schema/": {\r
-          target: "http://sdnr:8181",\r
+          target: "http://localhost:18181",\r
           secure: false\r
         },   \r
         "/oauth/": {\r
           // target: "https://10.20.35.188:30205",\r
-          target: "http://sdnr:8181",\r
+          target: "http://localhost:18181",\r
           secure: false\r
         },\r
         "/oauth2/": {\r
           // target: "https://10.20.35.188:30205",\r
-          target: "http://sdnr:8181",\r
+          target: "http://localhost:18181",\r
           secure: false\r
         },\r
         "/database/": {\r
-          target: "http://sdnr:8181",\r
+          target: "http://localhost:18181",\r
           secure: false\r
         },\r
         "/restconf/": {\r
-          target: "http://sdnr:8181",\r
+          target: "http://localhost:18181",\r
           secure: false\r
         },\r
         "/rests/": {\r
-          target: "http://sdnr:8181",\r
+          target: "http://localhost:18181",\r
           secure: false\r
         },\r
         "/help/": {\r
-          target: "http://sdnr:8181",\r
+          target: "http://localhost:18181",\r
           secure: false\r
         },\r
          "/about/": {\r
-          target: "http://sdnr:8181",\r
+          target: "http://localhost:18181",\r
           secure: false\r
         },\r
         "/tree/": {\r
-          target: "http://sdnr:8181",\r
+          target: "http://localhost:18181",\r
           secure: false\r
         },\r
         "/websocket": {\r
-          target: "http://sdnr:8181",\r
+          target: "http://localhost:18181",\r
           ws: true,\r
           changeOrigin: true,\r
           secure: false\r
         },\r
         "/apidoc": {\r
-          target: "http://sdnr:8181",\r
+          target: "http://localhost:18181",\r
           ws: true,\r
           changeOrigin: true,\r
           secure: false\r
index 919c0a0..50a90da 100644 (file)
@@ -20,7 +20,8 @@
   ~
   -->
 
-<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
     <modelVersion>4.0.0</modelVersion>
 
     <parent>
index e4d7fdc..e2cc41d 100644 (file)
@@ -1,12 +1,12 @@
-odlux.framework.buildno=90.49cc396(21/02/17)
-odlux.apps.configurationApp.buildno=89.977e4de(21/02/10)
-odlux.apps.connectApp.buildno=89.977e4de(21/02/10)
-odlux.apps.eventLogApp.buildno=81.1c38886(20/12/04)
-odlux.apps.faultApp.buildno=81.1c38886(20/12/04)
-odlux.apps.helpApp.buildno=81.1c38886(20/12/04)
-odlux.apps.inventoryApp.buildno=89.977e4de(21/02/10)
-odlux.apps.linkCalculationApp.buildno=90.49cc396(21/02/17)
-odlux.apps.maintenanceApp.buildno=81.1c38886(20/12/04)
-odlux.apps.mediatorApp.buildno=81.1c38886(20/12/04)
-odlux.apps.networkMapApp.buildno=90.49cc396(21/02/17)
+odlux.framework.buildno=96.078ad12(21/03/25)
+odlux.apps.configurationApp.buildno=96.078ad12(21/03/25)
+odlux.apps.connectApp.buildno=96.078ad12(21/03/25)
+odlux.apps.eventLogApp.buildno=96.078ad12(21/03/25)
+odlux.apps.faultApp.buildno=96.078ad12(21/03/25)
+odlux.apps.helpApp.buildno=96.078ad12(21/03/25)
+odlux.apps.inventoryApp.buildno=96.078ad12(21/03/25)
+odlux.apps.linkCalculationApp.buildno=96.078ad12(21/03/25)
+odlux.apps.maintenanceApp.buildno=96.078ad12(21/03/25)
+odlux.apps.mediatorApp.buildno=96.078ad12(21/03/25)
+odlux.apps.networkMapApp.buildno=96.078ad12(21/03/25)
 odlux.apps.permanceHistoryApp.buildno=81.1c38886(20/12/04)
index c208224..5bfcf74 100644 (file)
     "@material-ui/lab": "4.0.0-alpha.41",
     "@types/classnames": "2.2.6",
     "@types/flux": "3.1.8",
+    "@types/glob-to-regexp": "0.4.0",
     "@types/jquery": "3.3.10",
     "@types/jsonwebtoken": "7.2.8",
-    "@types/minimatch": "3.0.3",
     "@types/node": "11.11.6",
-    "@types/react": "16.9.19",
-    "@types/react-dom": "16.9.5",
-    "@types/react-router-dom": "4.3.1",
+    "@types/react": "17.0.3",
+    "@types/react-dom": "17.0.2",
+    "@types/react-router-dom": "5.1.7",
     "@types/react-transition-group": "2.0.15",
     "classnames": "2.2.6",
     "csstype": "2.6.8",
     "jquery": "3.3.1",
     "jsonwebtoken": "8.3.0",
     "jss": "10.0.3",
-    "lerna": "3.13.1",
+    "lerna": "3.22.1",
     "material-ui-confirm": "2.1.1",
     "notistack": "0.9.6",
     "prop-types": "15.7.2",
-    "react": "16.12.0",
-    "react-dom": "16.12.0",
-    "react-router-dom": "4.3.1",
-    "react-transition-group": "4.3.0",
-    "x2js": "3.2.3"
+    "react": "17.0.1",
+    "react-dom": "17.0.1",
+    "react-router-dom": "5.2.0",
+    "react-transition-group": "4.3.0"
   },
   "devDependencies": {
     "@babel/core": "7.1.0",
     "@babel/polyfill": "7.0.0",
     "@babel/preset-env": "7.1.0",
     "@babel/preset-react": "7.0.0",
+    "@octokit/core": "3.0.0",
     "@types/jest": "23.3.12",
     "autoprefixer": "9.1.5",
     "babel-loader": "8.0.4",
     "css-loader": "1.0.0",
     "extract-text-webpack-plugin": "next",
     "file-loader": "2.0.0",
+    "glob-to-regexp": "0.4.1",
     "html-webpack-include-assets-plugin": "1.0.5",
     "html-webpack-plugin": "3.2.0",
     "jest": "23.6.0",
     "less": "3.8.1",
     "less-loader": "4.1.0",
-    "minimatch": "3.0.4",
     "node-sass": "4.12.0",
     "postcss-loader": "3.0.0",
     "requirejs": "2.3.6",
@@ -77,7 +77,7 @@
     "tslint-eslint-rules": "4.1.1",
     "tslint-react": "3.6.0",
     "tslint-react-recommended": "1.0.15",
-    "typescript": "3.7.2",
+    "typescript": "4.1.4",
     "typings-for-css-modules-loader": "1.7.0",
     "url-loader": "1.1.2",
     "webpack": "4.28.4",
index cedbf4c..842aebb 100644 (file)
   dependencies:
     regenerator-runtime "^0.13.4"
 
+"@babel/runtime@^7.12.1":
+  version "7.13.10"
+  resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.13.10.tgz#47d42a57b6095f4468da440388fdbad8bebf0d7d"
+  integrity sha512-4QPkjJq6Ns3V/RgpEahRk+AGfL0eO6RHHtTWoNNr5mO49G6B5+X6d6THgWEAvTrznU5xYpbAlVKRYcsCgh/Akw==
+  dependencies:
+    regenerator-runtime "^0.13.4"
+
 "@babel/template@^7.1.0", "@babel/template@^7.10.4":
   version "7.10.4"
   resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.10.4.tgz#3251996c4200ebc71d1a8fc405fba940f36ba278"
   resolved "https://registry.yarnpkg.com/@emotion/hash/-/hash-0.8.0.tgz#bbbff68978fefdbe68ccb533bc8cbe1d1afb5413"
   integrity sha512-kBJtf7PH6aWwZ6fka3zQ0p6SBYzx4fl1LoZXE2RrnYST9Xljm7WfKJrU4g/Xr3Beg72MLrp1AWNUmuYJTL7Cow==
 
+"@evocateur/libnpmaccess@^3.1.2":
+  version "3.1.2"
+  resolved "https://registry.yarnpkg.com/@evocateur/libnpmaccess/-/libnpmaccess-3.1.2.tgz#ecf7f6ce6b004e9f942b098d92200be4a4b1c845"
+  integrity sha512-KSCAHwNWro0CF2ukxufCitT9K5LjL/KuMmNzSu8wuwN2rjyKHD8+cmOsiybK+W5hdnwc5M1SmRlVCaMHQo+3rg==
+  dependencies:
+    "@evocateur/npm-registry-fetch" "^4.0.0"
+    aproba "^2.0.0"
+    figgy-pudding "^3.5.1"
+    get-stream "^4.0.0"
+    npm-package-arg "^6.1.0"
+
+"@evocateur/libnpmpublish@^1.2.2":
+  version "1.2.2"
+  resolved "https://registry.yarnpkg.com/@evocateur/libnpmpublish/-/libnpmpublish-1.2.2.tgz#55df09d2dca136afba9c88c759ca272198db9f1a"
+  integrity sha512-MJrrk9ct1FeY9zRlyeoyMieBjGDG9ihyyD9/Ft6MMrTxql9NyoEx2hw9casTIP4CdqEVu+3nQ2nXxoJ8RCXyFg==
+  dependencies:
+    "@evocateur/npm-registry-fetch" "^4.0.0"
+    aproba "^2.0.0"
+    figgy-pudding "^3.5.1"
+    get-stream "^4.0.0"
+    lodash.clonedeep "^4.5.0"
+    normalize-package-data "^2.4.0"
+    npm-package-arg "^6.1.0"
+    semver "^5.5.1"
+    ssri "^6.0.1"
+
+"@evocateur/npm-registry-fetch@^4.0.0":
+  version "4.0.0"
+  resolved "https://registry.yarnpkg.com/@evocateur/npm-registry-fetch/-/npm-registry-fetch-4.0.0.tgz#8c4c38766d8d32d3200fcb0a83f064b57365ed66"
+  integrity sha512-k1WGfKRQyhJpIr+P17O5vLIo2ko1PFLKwoetatdduUSt/aQ4J2sJrJwwatdI5Z3SiYk/mRH9S3JpdmMFd/IK4g==
+  dependencies:
+    JSONStream "^1.3.4"
+    bluebird "^3.5.1"
+    figgy-pudding "^3.4.1"
+    lru-cache "^5.1.1"
+    make-fetch-happen "^5.0.0"
+    npm-package-arg "^6.1.0"
+    safe-buffer "^5.1.2"
+
+"@evocateur/pacote@^9.6.3":
+  version "9.6.5"
+  resolved "https://registry.yarnpkg.com/@evocateur/pacote/-/pacote-9.6.5.tgz#33de32ba210b6f17c20ebab4d497efc6755f4ae5"
+  integrity sha512-EI552lf0aG2nOV8NnZpTxNo2PcXKPmDbF9K8eCBFQdIZwHNGN/mi815fxtmUMa2wTa1yndotICIDt/V0vpEx2w==
+  dependencies:
+    "@evocateur/npm-registry-fetch" "^4.0.0"
+    bluebird "^3.5.3"
+    cacache "^12.0.3"
+    chownr "^1.1.2"
+    figgy-pudding "^3.5.1"
+    get-stream "^4.1.0"
+    glob "^7.1.4"
+    infer-owner "^1.0.4"
+    lru-cache "^5.1.1"
+    make-fetch-happen "^5.0.0"
+    minimatch "^3.0.4"
+    minipass "^2.3.5"
+    mississippi "^3.0.0"
+    mkdirp "^0.5.1"
+    normalize-package-data "^2.5.0"
+    npm-package-arg "^6.1.0"
+    npm-packlist "^1.4.4"
+    npm-pick-manifest "^3.0.0"
+    osenv "^0.1.5"
+    promise-inflight "^1.0.1"
+    promise-retry "^1.1.1"
+    protoduck "^5.0.1"
+    rimraf "^2.6.3"
+    safe-buffer "^5.2.0"
+    semver "^5.7.0"
+    ssri "^6.0.1"
+    tar "^4.4.10"
+    unique-filename "^1.1.1"
+    which "^1.3.1"
+
 "@fimbul/bifrost@^0.21.0":
   version "0.21.0"
   resolved "https://registry.yarnpkg.com/@fimbul/bifrost/-/bifrost-0.21.0.tgz#d0fafa25938fda475657a6a1e407a21bbe02c74e"
     humps "^2.0.1"
     prop-types "^15.5.10"
 
-"@lerna/add@3.13.1":
-  version "3.13.1"
-  resolved "https://registry.yarnpkg.com/@lerna/add/-/add-3.13.1.tgz#2cd7838857edb3b43ed73e3c21f69a20beb9b702"
-  integrity sha512-cXk42YbuhzEnADCK8Qte5laC9Qo03eJLVnr0qKY85jQUM/T4URe3IIUemqpg0CpVATrB+Vz+iNdeqw9ng1iALw==
+"@lerna/add@3.21.0":
+  version "3.21.0"
+  resolved "https://registry.yarnpkg.com/@lerna/add/-/add-3.21.0.tgz#27007bde71cc7b0a2969ab3c2f0ae41578b4577b"
+  integrity sha512-vhUXXF6SpufBE1EkNEXwz1VLW03f177G9uMOFMQkp6OJ30/PWg4Ekifuz9/3YfgB2/GH8Tu4Lk3O51P2Hskg/A==
   dependencies:
-    "@lerna/bootstrap" "3.13.1"
-    "@lerna/command" "3.13.1"
-    "@lerna/filter-options" "3.13.0"
-    "@lerna/npm-conf" "3.13.0"
+    "@evocateur/pacote" "^9.6.3"
+    "@lerna/bootstrap" "3.21.0"
+    "@lerna/command" "3.21.0"
+    "@lerna/filter-options" "3.20.0"
+    "@lerna/npm-conf" "3.16.0"
     "@lerna/validation-error" "3.13.0"
     dedent "^0.7.0"
     npm-package-arg "^6.1.0"
-    p-map "^1.2.0"
-    pacote "^9.5.0"
-    semver "^5.5.0"
-
-"@lerna/batch-packages@3.13.0":
-  version "3.13.0"
-  resolved "https://registry.yarnpkg.com/@lerna/batch-packages/-/batch-packages-3.13.0.tgz#697fde5be28822af9d9dca2f750250b90a89a000"
-  integrity sha512-TgLBTZ7ZlqilGnzJ3xh1KdAHcySfHytgNRTdG9YomfriTU6kVfp1HrXxKJYVGs7ClPUNt2CTFEOkw0tMBronjw==
-  dependencies:
-    "@lerna/package-graph" "3.13.0"
-    "@lerna/validation-error" "3.13.0"
-    npmlog "^4.1.2"
-
-"@lerna/bootstrap@3.13.1":
-  version "3.13.1"
-  resolved "https://registry.yarnpkg.com/@lerna/bootstrap/-/bootstrap-3.13.1.tgz#f2edd7c8093c8b139e78b0ca5f845f23efd01f08"
-  integrity sha512-mKdi5Ds5f82PZwEFyB9/W60I3iELobi1i87sTeVrbJh/um7GvqpSPy7kG/JPxyOdMpB2njX6LiJgw+7b6BEPWw==
-  dependencies:
-    "@lerna/batch-packages" "3.13.0"
-    "@lerna/command" "3.13.1"
-    "@lerna/filter-options" "3.13.0"
-    "@lerna/has-npm-version" "3.13.0"
-    "@lerna/npm-install" "3.13.0"
-    "@lerna/package-graph" "3.13.0"
+    p-map "^2.1.0"
+    semver "^6.2.0"
+
+"@lerna/bootstrap@3.21.0":
+  version "3.21.0"
+  resolved "https://registry.yarnpkg.com/@lerna/bootstrap/-/bootstrap-3.21.0.tgz#bcd1b651be5b0970b20d8fae04c864548123aed6"
+  integrity sha512-mtNHlXpmvJn6JTu0KcuTTPl2jLsDNud0QacV/h++qsaKbhAaJr/FElNZ5s7MwZFUM3XaDmvWzHKaszeBMHIbBw==
+  dependencies:
+    "@lerna/command" "3.21.0"
+    "@lerna/filter-options" "3.20.0"
+    "@lerna/has-npm-version" "3.16.5"
+    "@lerna/npm-install" "3.16.5"
+    "@lerna/package-graph" "3.18.5"
     "@lerna/pulse-till-done" "3.13.0"
-    "@lerna/rimraf-dir" "3.13.0"
-    "@lerna/run-lifecycle" "3.13.0"
-    "@lerna/run-parallel-batches" "3.13.0"
-    "@lerna/symlink-binary" "3.13.0"
-    "@lerna/symlink-dependencies" "3.13.0"
+    "@lerna/rimraf-dir" "3.16.5"
+    "@lerna/run-lifecycle" "3.16.2"
+    "@lerna/run-topologically" "3.18.5"
+    "@lerna/symlink-binary" "3.17.0"
+    "@lerna/symlink-dependencies" "3.17.0"
     "@lerna/validation-error" "3.13.0"
     dedent "^0.7.0"
-    get-port "^3.2.0"
-    multimatch "^2.1.0"
+    get-port "^4.2.0"
+    multimatch "^3.0.0"
     npm-package-arg "^6.1.0"
     npmlog "^4.1.2"
     p-finally "^1.0.0"
-    p-map "^1.2.0"
+    p-map "^2.1.0"
     p-map-series "^1.0.0"
     p-waterfall "^1.0.0"
     read-package-tree "^5.1.6"
-    semver "^5.5.0"
+    semver "^6.2.0"
 
-"@lerna/changed@3.13.1":
-  version "3.13.1"
-  resolved "https://registry.yarnpkg.com/@lerna/changed/-/changed-3.13.1.tgz#dc92476aad43c932fe741969bbd0bcf6146a4c52"
-  integrity sha512-BRXitEJGOkoudbxEewW7WhjkLxFD+tTk4PrYpHLyCBk63pNTWtQLRE6dc1hqwh4emwyGncoyW6RgXfLgMZgryw==
+"@lerna/changed@3.21.0":
+  version "3.21.0"
+  resolved "https://registry.yarnpkg.com/@lerna/changed/-/changed-3.21.0.tgz#108e15f679bfe077af500f58248c634f1044ea0b"
+  integrity sha512-hzqoyf8MSHVjZp0gfJ7G8jaz+++mgXYiNs9iViQGA8JlN/dnWLI5sWDptEH3/B30Izo+fdVz0S0s7ydVE3pWIw==
   dependencies:
-    "@lerna/collect-updates" "3.13.0"
-    "@lerna/command" "3.13.1"
-    "@lerna/listable" "3.13.0"
+    "@lerna/collect-updates" "3.20.0"
+    "@lerna/command" "3.21.0"
+    "@lerna/listable" "3.18.5"
     "@lerna/output" "3.13.0"
-    "@lerna/version" "3.13.1"
 
-"@lerna/check-working-tree@3.13.0":
-  version "3.13.0"
-  resolved "https://registry.yarnpkg.com/@lerna/check-working-tree/-/check-working-tree-3.13.0.tgz#1ddcd4d9b1aceb65efaaa4cd1333a66706d67c9c"
-  integrity sha512-dsdO15NXX5To+Q53SYeCrBEpiqv4m5VkaPZxbGQZNwoRen1MloXuqxSymJANQn+ZLEqarv5V56gydebeROPH5A==
+"@lerna/check-working-tree@3.16.5":
+  version "3.16.5"
+  resolved "https://registry.yarnpkg.com/@lerna/check-working-tree/-/check-working-tree-3.16.5.tgz#b4f8ae61bb4523561dfb9f8f8d874dd46bb44baa"
+  integrity sha512-xWjVBcuhvB8+UmCSb5tKVLB5OuzSpw96WEhS2uz6hkWVa/Euh1A0/HJwn2cemyK47wUrCQXtczBUiqnq9yX5VQ==
   dependencies:
-    "@lerna/describe-ref" "3.13.0"
+    "@lerna/collect-uncommitted" "3.16.5"
+    "@lerna/describe-ref" "3.16.5"
     "@lerna/validation-error" "3.13.0"
 
-"@lerna/child-process@3.13.0":
-  version "3.13.0"
-  resolved "https://registry.yarnpkg.com/@lerna/child-process/-/child-process-3.13.0.tgz#84e35adf3217a6983edd28080657b9596a052674"
-  integrity sha512-0iDS8y2jiEucD4fJHEzKoc8aQJgm7s+hG+0RmDNtfT0MM3n17pZnf5JOMtS1FJp+SEXOjMKQndyyaDIPFsnp6A==
+"@lerna/child-process@3.16.5":
+  version "3.16.5"
+  resolved "https://registry.yarnpkg.com/@lerna/child-process/-/child-process-3.16.5.tgz#38fa3c18064aa4ac0754ad80114776a7b36a69b2"
+  integrity sha512-vdcI7mzei9ERRV4oO8Y1LHBZ3A5+ampRKg1wq5nutLsUA4mEBN6H7JqjWOMY9xZemv6+kATm2ofjJ3lW5TszQg==
   dependencies:
     chalk "^2.3.1"
     execa "^1.0.0"
     strong-log-transformer "^2.0.0"
 
-"@lerna/clean@3.13.1":
-  version "3.13.1"
-  resolved "https://registry.yarnpkg.com/@lerna/clean/-/clean-3.13.1.tgz#9a7432efceccd720a51da5c76f849fc59c5a14ce"
-  integrity sha512-myGIaXv7RUO2qCFZXvx8SJeI+eN6y9SUD5zZ4/LvNogbOiEIlujC5lUAqK65rAHayQ9ltSa/yK6Xv510xhZXZQ==
+"@lerna/clean@3.21.0":
+  version "3.21.0"
+  resolved "https://registry.yarnpkg.com/@lerna/clean/-/clean-3.21.0.tgz#c0b46b5300cc3dae2cda3bec14b803082da3856d"
+  integrity sha512-b/L9l+MDgE/7oGbrav6rG8RTQvRiZLO1zTcG17zgJAAuhlsPxJExMlh2DFwJEVi2les70vMhHfST3Ue1IMMjpg==
   dependencies:
-    "@lerna/command" "3.13.1"
-    "@lerna/filter-options" "3.13.0"
-    "@lerna/prompt" "3.13.0"
+    "@lerna/command" "3.21.0"
+    "@lerna/filter-options" "3.20.0"
+    "@lerna/prompt" "3.18.5"
     "@lerna/pulse-till-done" "3.13.0"
-    "@lerna/rimraf-dir" "3.13.0"
-    p-map "^1.2.0"
+    "@lerna/rimraf-dir" "3.16.5"
+    p-map "^2.1.0"
     p-map-series "^1.0.0"
     p-waterfall "^1.0.0"
 
-"@lerna/cli@3.13.0":
-  version "3.13.0"
-  resolved "https://registry.yarnpkg.com/@lerna/cli/-/cli-3.13.0.tgz#3d7b357fdd7818423e9681a7b7f2abd106c8a266"
-  integrity sha512-HgFGlyCZbYaYrjOr3w/EsY18PdvtsTmDfpUQe8HwDjXlPeCCUgliZjXLOVBxSjiOvPeOSwvopwIHKWQmYbwywg==
+"@lerna/cli@3.18.5":
+  version "3.18.5"
+  resolved "https://registry.yarnpkg.com/@lerna/cli/-/cli-3.18.5.tgz#c90c461542fcd35b6d5b015a290fb0dbfb41d242"
+  integrity sha512-erkbxkj9jfc89vVs/jBLY/fM0I80oLmJkFUV3Q3wk9J3miYhP14zgVEBsPZY68IZlEjT6T3Xlq2xO1AVaatHsA==
   dependencies:
     "@lerna/global-options" "3.13.0"
     dedent "^0.7.0"
     npmlog "^4.1.2"
-    yargs "^12.0.1"
+    yargs "^14.2.2"
 
-"@lerna/collect-updates@3.13.0":
-  version "3.13.0"
-  resolved "https://registry.yarnpkg.com/@lerna/collect-updates/-/collect-updates-3.13.0.tgz#f0828d84ff959ff153d006765659ffc4d68cdefc"
-  integrity sha512-uR3u6uTzrS1p46tHQ/mlHog/nRJGBqskTHYYJbgirujxm6FqNh7Do+I1Q/7zSee407G4lzsNxZdm8IL927HemQ==
+"@lerna/collect-uncommitted@3.16.5":
+  version "3.16.5"
+  resolved "https://registry.yarnpkg.com/@lerna/collect-uncommitted/-/collect-uncommitted-3.16.5.tgz#a494d61aac31cdc7aec4bbe52c96550274132e63"
+  integrity sha512-ZgqnGwpDZiWyzIQVZtQaj9tRizsL4dUOhuOStWgTAw1EMe47cvAY2kL709DzxFhjr6JpJSjXV5rZEAeU3VE0Hg==
   dependencies:
-    "@lerna/child-process" "3.13.0"
-    "@lerna/describe-ref" "3.13.0"
+    "@lerna/child-process" "3.16.5"
+    chalk "^2.3.1"
+    figgy-pudding "^3.5.1"
+    npmlog "^4.1.2"
+
+"@lerna/collect-updates@3.20.0":
+  version "3.20.0"
+  resolved "https://registry.yarnpkg.com/@lerna/collect-updates/-/collect-updates-3.20.0.tgz#62f9d76ba21a25b7d9fbf31c02de88744a564bd1"
+  integrity sha512-qBTVT5g4fupVhBFuY4nI/3FSJtQVcDh7/gEPOpRxoXB/yCSnT38MFHXWl+y4einLciCjt/+0x6/4AG80fjay2Q==
+  dependencies:
+    "@lerna/child-process" "3.16.5"
+    "@lerna/describe-ref" "3.16.5"
     minimatch "^3.0.4"
     npmlog "^4.1.2"
-    slash "^1.0.0"
+    slash "^2.0.0"
 
-"@lerna/command@3.13.1":
-  version "3.13.1"
-  resolved "https://registry.yarnpkg.com/@lerna/command/-/command-3.13.1.tgz#b60dda2c0d9ffbb6030d61ddf7cceedc1e8f7e6e"
-  integrity sha512-SYWezxX+iheWvzRoHCrbs8v5zHPaxAx3kWvZhqi70vuGsdOVAWmaG4IvHLn11ztS+Vpd5PM+ztBWSbnykpLFKQ==
+"@lerna/command@3.21.0":
+  version "3.21.0"
+  resolved "https://registry.yarnpkg.com/@lerna/command/-/command-3.21.0.tgz#9a2383759dc7b700dacfa8a22b2f3a6e190121f7"
+  integrity sha512-T2bu6R8R3KkH5YoCKdutKv123iUgUbW8efVjdGCDnCMthAQzoentOJfDeodBwn0P2OqCl3ohsiNVtSn9h78fyQ==
   dependencies:
-    "@lerna/child-process" "3.13.0"
-    "@lerna/package-graph" "3.13.0"
-    "@lerna/project" "3.13.1"
+    "@lerna/child-process" "3.16.5"
+    "@lerna/package-graph" "3.18.5"
+    "@lerna/project" "3.21.0"
     "@lerna/validation-error" "3.13.0"
     "@lerna/write-log-file" "3.13.0"
+    clone-deep "^4.0.1"
     dedent "^0.7.0"
     execa "^1.0.0"
-    is-ci "^1.0.10"
-    lodash "^4.17.5"
+    is-ci "^2.0.0"
     npmlog "^4.1.2"
 
-"@lerna/conventional-commits@3.13.0":
-  version "3.13.0"
-  resolved "https://registry.yarnpkg.com/@lerna/conventional-commits/-/conventional-commits-3.13.0.tgz#877aa225ca34cca61c31ea02a5a6296af74e1144"
-  integrity sha512-BeAgcNXuocmLhPxnmKU2Vy8YkPd/Uo+vu2i/p3JGsUldzrPC8iF3IDxH7fuXpEFN2Nfogu7KHachd4tchtOppA==
+"@lerna/conventional-commits@3.22.0":
+  version "3.22.0"
+  resolved "https://registry.yarnpkg.com/@lerna/conventional-commits/-/conventional-commits-3.22.0.tgz#2798f4881ee2ef457bdae027ab7d0bf0af6f1e09"
+  integrity sha512-z4ZZk1e8Mhz7+IS8NxHr64wyklHctCJyWpJKEZZPJiLFJ8yKto/x38O80R10pIzC0rr8Sy/OsjSH4bl0TbbgqA==
   dependencies:
     "@lerna/validation-error" "3.13.0"
     conventional-changelog-angular "^5.0.3"
     conventional-changelog-core "^3.1.6"
-    conventional-recommended-bump "^4.0.4"
-    fs-extra "^7.0.0"
+    conventional-recommended-bump "^5.0.0"
+    fs-extra "^8.1.0"
     get-stream "^4.0.0"
+    lodash.template "^4.5.0"
     npm-package-arg "^6.1.0"
     npmlog "^4.1.2"
-    pify "^3.0.0"
-    semver "^5.5.0"
+    pify "^4.0.1"
+    semver "^6.2.0"
 
-"@lerna/create-symlink@3.13.0":
-  version "3.13.0"
-  resolved "https://registry.yarnpkg.com/@lerna/create-symlink/-/create-symlink-3.13.0.tgz#e01133082fe040779712c960683cb3a272b67809"
-  integrity sha512-PTvg3jAAJSAtLFoZDsuTMv1wTOC3XYIdtg54k7uxIHsP8Ztpt+vlilY/Cni0THAqEMHvfiToel76Xdta4TU21Q==
+"@lerna/create-symlink@3.16.2":
+  version "3.16.2"
+  resolved "https://registry.yarnpkg.com/@lerna/create-symlink/-/create-symlink-3.16.2.tgz#412cb8e59a72f5a7d9463e4e4721ad2070149967"
+  integrity sha512-pzXIJp6av15P325sgiIRpsPXLFmkisLhMBCy4764d+7yjf2bzrJ4gkWVMhsv4AdF0NN3OyZ5jjzzTtLNqfR+Jw==
   dependencies:
-    cmd-shim "^2.0.2"
-    fs-extra "^7.0.0"
+    "@zkochan/cmd-shim" "^3.1.0"
+    fs-extra "^8.1.0"
     npmlog "^4.1.2"
 
-"@lerna/create@3.13.1":
-  version "3.13.1"
-  resolved "https://registry.yarnpkg.com/@lerna/create/-/create-3.13.1.tgz#2c1284cfdc59f0d2b88286d78bc797f4ab330f79"
-  integrity sha512-pLENMXgTkQuvKxAopjKeoLOv9fVUCnpTUD7aLrY5d95/1xqSZlnsOcQfUYcpMf3GpOvHc8ILmI5OXkPqjAf54g==
+"@lerna/create@3.22.0":
+  version "3.22.0"
+  resolved "https://registry.yarnpkg.com/@lerna/create/-/create-3.22.0.tgz#d6bbd037c3dc5b425fe5f6d1b817057c278f7619"
+  integrity sha512-MdiQQzCcB4E9fBF1TyMOaAEz9lUjIHp1Ju9H7f3lXze5JK6Fl5NYkouAvsLgY6YSIhXMY8AHW2zzXeBDY4yWkw==
   dependencies:
-    "@lerna/child-process" "3.13.0"
-    "@lerna/command" "3.13.1"
-    "@lerna/npm-conf" "3.13.0"
+    "@evocateur/pacote" "^9.6.3"
+    "@lerna/child-process" "3.16.5"
+    "@lerna/command" "3.21.0"
+    "@lerna/npm-conf" "3.16.0"
     "@lerna/validation-error" "3.13.0"
     camelcase "^5.0.0"
     dedent "^0.7.0"
-    fs-extra "^7.0.0"
-    globby "^8.0.1"
+    fs-extra "^8.1.0"
+    globby "^9.2.0"
     init-package-json "^1.10.3"
     npm-package-arg "^6.1.0"
     p-reduce "^1.0.0"
-    pacote "^9.5.0"
-    pify "^3.0.0"
-    semver "^5.5.0"
-    slash "^1.0.0"
+    pify "^4.0.1"
+    semver "^6.2.0"
+    slash "^2.0.0"
     validate-npm-package-license "^3.0.3"
     validate-npm-package-name "^3.0.0"
     whatwg-url "^7.0.0"
 
-"@lerna/describe-ref@3.13.0":
-  version "3.13.0"
-  resolved "https://registry.yarnpkg.com/@lerna/describe-ref/-/describe-ref-3.13.0.tgz#fb4c3863fd6bcccad67ce7b183887a5fc1942bb6"
-  integrity sha512-UJefF5mLxLae9I2Sbz5RLYGbqbikRuMqdgTam0MS5OhXnyuuKYBUpwBshCURNb1dPBXTQhSwc7+oUhORx8ojCg==
+"@lerna/describe-ref@3.16.5":
+  version "3.16.5"
+  resolved "https://registry.yarnpkg.com/@lerna/describe-ref/-/describe-ref-3.16.5.tgz#a338c25aaed837d3dc70b8a72c447c5c66346ac0"
+  integrity sha512-c01+4gUF0saOOtDBzbLMFOTJDHTKbDFNErEY6q6i9QaXuzy9LNN62z+Hw4acAAZuJQhrVWncVathcmkkjvSVGw==
   dependencies:
-    "@lerna/child-process" "3.13.0"
+    "@lerna/child-process" "3.16.5"
     npmlog "^4.1.2"
 
-"@lerna/diff@3.13.1":
-  version "3.13.1"
-  resolved "https://registry.yarnpkg.com/@lerna/diff/-/diff-3.13.1.tgz#5c734321b0f6c46a3c87f55c99afef3b01d46520"
-  integrity sha512-cKqmpONO57mdvxtp8e+l5+tjtmF04+7E+O0QEcLcNUAjC6UR2OSM77nwRCXDukou/1h72JtWs0jjcdYLwAmApg==
+"@lerna/diff@3.21.0":
+  version "3.21.0"
+  resolved "https://registry.yarnpkg.com/@lerna/diff/-/diff-3.21.0.tgz#e6df0d8b9916167ff5a49fcb02ac06424280a68d"
+  integrity sha512-5viTR33QV3S7O+bjruo1SaR40m7F2aUHJaDAC7fL9Ca6xji+aw1KFkpCtVlISS0G8vikUREGMJh+c/VMSc8Usw==
   dependencies:
-    "@lerna/child-process" "3.13.0"
-    "@lerna/command" "3.13.1"
+    "@lerna/child-process" "3.16.5"
+    "@lerna/command" "3.21.0"
     "@lerna/validation-error" "3.13.0"
     npmlog "^4.1.2"
 
-"@lerna/exec@3.13.1":
-  version "3.13.1"
-  resolved "https://registry.yarnpkg.com/@lerna/exec/-/exec-3.13.1.tgz#4439e90fb0877ec38a6ef933c86580d43eeaf81b"
-  integrity sha512-I34wEP9lrAqqM7tTXLDxv/6454WFzrnXDWpNDbiKQiZs6SIrOOjmm6I4FiQsx+rU3o9d+HkC6tcUJRN5mlJUgA==
+"@lerna/exec@3.21.0":
+  version "3.21.0"
+  resolved "https://registry.yarnpkg.com/@lerna/exec/-/exec-3.21.0.tgz#17f07533893cb918a17b41bcc566dc437016db26"
+  integrity sha512-iLvDBrIE6rpdd4GIKTY9mkXyhwsJ2RvQdB9ZU+/NhR3okXfqKc6py/24tV111jqpXTtZUW6HNydT4dMao2hi1Q==
   dependencies:
-    "@lerna/batch-packages" "3.13.0"
-    "@lerna/child-process" "3.13.0"
-    "@lerna/command" "3.13.1"
-    "@lerna/filter-options" "3.13.0"
-    "@lerna/run-parallel-batches" "3.13.0"
+    "@lerna/child-process" "3.16.5"
+    "@lerna/command" "3.21.0"
+    "@lerna/filter-options" "3.20.0"
+    "@lerna/profiler" "3.20.0"
+    "@lerna/run-topologically" "3.18.5"
     "@lerna/validation-error" "3.13.0"
+    p-map "^2.1.0"
 
-"@lerna/filter-options@3.13.0":
-  version "3.13.0"
-  resolved "https://registry.yarnpkg.com/@lerna/filter-options/-/filter-options-3.13.0.tgz#976e3d8b9fcd47001ab981d276565c1e9f767868"
-  integrity sha512-SRp7DCo9zrf+7NkQxZMkeyO1GRN6GICoB9UcBAbXhLbWisT37Cx5/6+jh49gYB63d/0/WYHSEPMlheUrpv1Srw==
+"@lerna/filter-options@3.20.0":
+  version "3.20.0"
+  resolved "https://registry.yarnpkg.com/@lerna/filter-options/-/filter-options-3.20.0.tgz#0f0f5d5a4783856eece4204708cc902cbc8af59b"
+  integrity sha512-bmcHtvxn7SIl/R9gpiNMVG7yjx7WyT0HSGw34YVZ9B+3xF/83N3r5Rgtjh4hheLZ+Q91Or0Jyu5O3Nr+AwZe2g==
   dependencies:
-    "@lerna/collect-updates" "3.13.0"
-    "@lerna/filter-packages" "3.13.0"
+    "@lerna/collect-updates" "3.20.0"
+    "@lerna/filter-packages" "3.18.0"
     dedent "^0.7.0"
+    figgy-pudding "^3.5.1"
+    npmlog "^4.1.2"
 
-"@lerna/filter-packages@3.13.0":
-  version "3.13.0"
-  resolved "https://registry.yarnpkg.com/@lerna/filter-packages/-/filter-packages-3.13.0.tgz#f5371249e7e1a15928e5e88c544a242e0162c21c"
-  integrity sha512-RWiZWyGy3Mp7GRVBn//CacSnE3Kw82PxE4+H6bQ3pDUw/9atXn7NRX+gkBVQIYeKamh7HyumJtyOKq3Pp9BADQ==
+"@lerna/filter-packages@3.18.0":
+  version "3.18.0"
+  resolved "https://registry.yarnpkg.com/@lerna/filter-packages/-/filter-packages-3.18.0.tgz#6a7a376d285208db03a82958cfb8172e179b4e70"
+  integrity sha512-6/0pMM04bCHNATIOkouuYmPg6KH3VkPCIgTfQmdkPJTullERyEQfNUKikrefjxo1vHOoCACDpy65JYyKiAbdwQ==
   dependencies:
     "@lerna/validation-error" "3.13.0"
-    multimatch "^2.1.0"
+    multimatch "^3.0.0"
     npmlog "^4.1.2"
 
 "@lerna/get-npm-exec-opts@3.13.0":
   dependencies:
     npmlog "^4.1.2"
 
-"@lerna/get-packed@3.13.0":
-  version "3.13.0"
-  resolved "https://registry.yarnpkg.com/@lerna/get-packed/-/get-packed-3.13.0.tgz#335e40d77f3c1855aa248587d3e0b2d8f4b06e16"
-  integrity sha512-EgSim24sjIjqQDC57bgXD9l22/HCS93uQBbGpkzEOzxAVzEgpZVm7Fm1t8BVlRcT2P2zwGnRadIvxTbpQuDPTg==
+"@lerna/get-packed@3.16.0":
+  version "3.16.0"
+  resolved "https://registry.yarnpkg.com/@lerna/get-packed/-/get-packed-3.16.0.tgz#1b316b706dcee86c7baa55e50b087959447852ff"
+  integrity sha512-AjsFiaJzo1GCPnJUJZiTW6J1EihrPkc2y3nMu6m3uWFxoleklsSCyImumzVZJssxMi3CPpztj8LmADLedl9kXw==
   dependencies:
-    fs-extra "^7.0.0"
+    fs-extra "^8.1.0"
     ssri "^6.0.1"
     tar "^4.4.8"
 
-"@lerna/github-client@3.13.1":
-  version "3.13.1"
-  resolved "https://registry.yarnpkg.com/@lerna/github-client/-/github-client-3.13.1.tgz#cb9bf9f01685a0cee0fac63f287f6c3673e45aa3"
-  integrity sha512-iPLUp8FFoAKGURksYEYZzfuo9TRA+NepVlseRXFaWlmy36dCQN20AciINpoXiXGoHcEUHXUKHQvY3ARFdMlf3w==
+"@lerna/github-client@3.22.0":
+  version "3.22.0"
+  resolved "https://registry.yarnpkg.com/@lerna/github-client/-/github-client-3.22.0.tgz#5d816aa4f76747ed736ae64ff962b8f15c354d95"
+  integrity sha512-O/GwPW+Gzr3Eb5bk+nTzTJ3uv+jh5jGho9BOqKlajXaOkMYGBELEAqV5+uARNGWZFvYAiF4PgqHb6aCUu7XdXg==
   dependencies:
-    "@lerna/child-process" "3.13.0"
-    "@octokit/plugin-enterprise-rest" "^2.1.1"
-    "@octokit/rest" "^16.16.0"
+    "@lerna/child-process" "3.16.5"
+    "@octokit/plugin-enterprise-rest" "^6.0.1"
+    "@octokit/rest" "^16.28.4"
     git-url-parse "^11.1.2"
     npmlog "^4.1.2"
 
+"@lerna/gitlab-client@3.15.0":
+  version "3.15.0"
+  resolved "https://registry.yarnpkg.com/@lerna/gitlab-client/-/gitlab-client-3.15.0.tgz#91f4ec8c697b5ac57f7f25bd50fe659d24aa96a6"
+  integrity sha512-OsBvRSejHXUBMgwWQqNoioB8sgzL/Pf1pOUhHKtkiMl6aAWjklaaq5HPMvTIsZPfS6DJ9L5OK2GGZuooP/5c8Q==
+  dependencies:
+    node-fetch "^2.5.0"
+    npmlog "^4.1.2"
+    whatwg-url "^7.0.0"
+
 "@lerna/global-options@3.13.0":
   version "3.13.0"
   resolved "https://registry.yarnpkg.com/@lerna/global-options/-/global-options-3.13.0.tgz#217662290db06ad9cf2c49d8e3100ee28eaebae1"
   integrity sha512-SlZvh1gVRRzYLVluz9fryY1nJpZ0FHDGB66U9tFfvnnxmueckRQxLopn3tXj3NU1kc3QANT2I5BsQkOqZ4TEFQ==
 
-"@lerna/has-npm-version@3.13.0":
-  version "3.13.0"
-  resolved "https://registry.yarnpkg.com/@lerna/has-npm-version/-/has-npm-version-3.13.0.tgz#6e1f7e9336cce3e029066f0175f06dd9d51ad09f"
-  integrity sha512-Oqu7DGLnrMENPm+bPFGOHnqxK8lCnuYr6bk3g/CoNn8/U0qgFvHcq6Iv8/Z04TsvleX+3/RgauSD2kMfRmbypg==
+"@lerna/has-npm-version@3.16.5":
+  version "3.16.5"
+  resolved "https://registry.yarnpkg.com/@lerna/has-npm-version/-/has-npm-version-3.16.5.tgz#ab83956f211d8923ea6afe9b979b38cc73b15326"
+  integrity sha512-WL7LycR9bkftyqbYop5rEGJ9sRFIV55tSGmbN1HLrF9idwOCD7CLrT64t235t3t4O5gehDnwKI5h2U3oxTrF8Q==
   dependencies:
-    "@lerna/child-process" "3.13.0"
-    semver "^5.5.0"
+    "@lerna/child-process" "3.16.5"
+    semver "^6.2.0"
 
-"@lerna/import@3.13.1":
-  version "3.13.1"
-  resolved "https://registry.yarnpkg.com/@lerna/import/-/import-3.13.1.tgz#69d641341a38b79bd379129da1c717d51dd728c7"
-  integrity sha512-A1Vk1siYx1XkRl6w+zkaA0iptV5TIynVlHPR9S7NY0XAfhykjztYVvwtxarlh6+VcNrO9We6if0+FXCrfDEoIg==
+"@lerna/import@3.22.0":
+  version "3.22.0"
+  resolved "https://registry.yarnpkg.com/@lerna/import/-/import-3.22.0.tgz#1a5f0394f38e23c4f642a123e5e1517e70d068d2"
+  integrity sha512-uWOlexasM5XR6tXi4YehODtH9Y3OZrFht3mGUFFT3OIl2s+V85xIGFfqFGMTipMPAGb2oF1UBLL48kR43hRsOg==
   dependencies:
-    "@lerna/child-process" "3.13.0"
-    "@lerna/command" "3.13.1"
-    "@lerna/prompt" "3.13.0"
+    "@lerna/child-process" "3.16.5"
+    "@lerna/command" "3.21.0"
+    "@lerna/prompt" "3.18.5"
     "@lerna/pulse-till-done" "3.13.0"
     "@lerna/validation-error" "3.13.0"
     dedent "^0.7.0"
-    fs-extra "^7.0.0"
+    fs-extra "^8.1.0"
     p-map-series "^1.0.0"
 
-"@lerna/init@3.13.1":
-  version "3.13.1"
-  resolved "https://registry.yarnpkg.com/@lerna/init/-/init-3.13.1.tgz#0392c822abb3d63a75be4916c5e761cfa7b34dda"
-  integrity sha512-M59WACqim8WkH5FQEGOCEZ89NDxCKBfFTx4ZD5ig3LkGyJ8RdcJq5KEfpW/aESuRE9JrZLzVr0IjKbZSxzwEMA==
-  dependencies:
-    "@lerna/child-process" "3.13.0"
-    "@lerna/command" "3.13.1"
-    fs-extra "^7.0.0"
-    p-map "^1.2.0"
-    write-json-file "^2.3.0"
-
-"@lerna/link@3.13.1":
-  version "3.13.1"
-  resolved "https://registry.yarnpkg.com/@lerna/link/-/link-3.13.1.tgz#7d8ed4774bfa198d1780f790a14abb8722a3aad1"
-  integrity sha512-N3h3Fj1dcea+1RaAoAdy4g2m3fvU7m89HoUn5X/Zcw5n2kPoK8kTO+NfhNAatfRV8VtMXst8vbNrWQQtfm0FFw==
-  dependencies:
-    "@lerna/command" "3.13.1"
-    "@lerna/package-graph" "3.13.0"
-    "@lerna/symlink-dependencies" "3.13.0"
-    p-map "^1.2.0"
-    slash "^1.0.0"
+"@lerna/info@3.21.0":
+  version "3.21.0"
+  resolved "https://registry.yarnpkg.com/@lerna/info/-/info-3.21.0.tgz#76696b676fdb0f35d48c83c63c1e32bb5e37814f"
+  integrity sha512-0XDqGYVBgWxUquFaIptW2bYSIu6jOs1BtkvRTWDDhw4zyEdp6q4eaMvqdSap1CG+7wM5jeLCi6z94wS0AuiuwA==
+  dependencies:
+    "@lerna/command" "3.21.0"
+    "@lerna/output" "3.13.0"
+    envinfo "^7.3.1"
+
+"@lerna/init@3.21.0":
+  version "3.21.0"
+  resolved "https://registry.yarnpkg.com/@lerna/init/-/init-3.21.0.tgz#1e810934dc8bf4e5386c031041881d3b4096aa5c"
+  integrity sha512-6CM0z+EFUkFfurwdJCR+LQQF6MqHbYDCBPyhu/d086LRf58GtYZYj49J8mKG9ktayp/TOIxL/pKKjgLD8QBPOg==
+  dependencies:
+    "@lerna/child-process" "3.16.5"
+    "@lerna/command" "3.21.0"
+    fs-extra "^8.1.0"
+    p-map "^2.1.0"
+    write-json-file "^3.2.0"
+
+"@lerna/link@3.21.0":
+  version "3.21.0"
+  resolved "https://registry.yarnpkg.com/@lerna/link/-/link-3.21.0.tgz#8be68ff0ccee104b174b5bbd606302c2f06e9d9b"
+  integrity sha512-tGu9GxrX7Ivs+Wl3w1+jrLi1nQ36kNI32dcOssij6bg0oZ2M2MDEFI9UF2gmoypTaN9uO5TSsjCFS7aR79HbdQ==
+  dependencies:
+    "@lerna/command" "3.21.0"
+    "@lerna/package-graph" "3.18.5"
+    "@lerna/symlink-dependencies" "3.17.0"
+    p-map "^2.1.0"
+    slash "^2.0.0"
 
-"@lerna/list@3.13.1":
-  version "3.13.1"
-  resolved "https://registry.yarnpkg.com/@lerna/list/-/list-3.13.1.tgz#f9513ed143e52156c10ada4070f903c5847dcd10"
-  integrity sha512-635iRbdgd9gNvYLLIbYdQCQLr+HioM5FGJLFS0g3DPGygr6iDR8KS47hzCRGH91LU9NcM1mD1RoT/AChF+QbiA==
+"@lerna/list@3.21.0":
+  version "3.21.0"
+  resolved "https://registry.yarnpkg.com/@lerna/list/-/list-3.21.0.tgz#42f76fafa56dea13b691ec8cab13832691d61da2"
+  integrity sha512-KehRjE83B1VaAbRRkRy6jLX1Cin8ltsrQ7FHf2bhwhRHK0S54YuA6LOoBnY/NtA8bHDX/Z+G5sMY78X30NS9tg==
   dependencies:
-    "@lerna/command" "3.13.1"
-    "@lerna/filter-options" "3.13.0"
-    "@lerna/listable" "3.13.0"
+    "@lerna/command" "3.21.0"
+    "@lerna/filter-options" "3.20.0"
+    "@lerna/listable" "3.18.5"
     "@lerna/output" "3.13.0"
 
-"@lerna/listable@3.13.0":
-  version "3.13.0"
-  resolved "https://registry.yarnpkg.com/@lerna/listable/-/listable-3.13.0.tgz#babc18442c590b549cf0966d20d75fea066598d4"
-  integrity sha512-liYJ/WBUYP4N4MnSVZuLUgfa/jy3BZ02/1Om7xUY09xGVSuNVNEeB8uZUMSC+nHqFHIsMPZ8QK9HnmZb1E/eTA==
+"@lerna/listable@3.18.5":
+  version "3.18.5"
+  resolved "https://registry.yarnpkg.com/@lerna/listable/-/listable-3.18.5.tgz#e82798405b5ed8fc51843c8ef1e7a0e497388a1a"
+  integrity sha512-Sdr3pVyaEv5A7ZkGGYR7zN+tTl2iDcinryBPvtuv20VJrXBE8wYcOks1edBTcOWsPjCE/rMP4bo1pseyk3UTsg==
   dependencies:
-    "@lerna/batch-packages" "3.13.0"
+    "@lerna/query-graph" "3.18.5"
     chalk "^2.3.1"
     columnify "^1.5.4"
 
-"@lerna/log-packed@3.13.0":
-  version "3.13.0"
-  resolved "https://registry.yarnpkg.com/@lerna/log-packed/-/log-packed-3.13.0.tgz#497b5f692a8d0e3f669125da97b0dadfd9e480f3"
-  integrity sha512-Rmjrcz+6aM6AEcEVWmurbo8+AnHOvYtDpoeMMJh9IZ9SmZr2ClXzmD7wSvjTQc8BwOaiWjjC/ukcT0UYA2m7wg==
+"@lerna/log-packed@3.16.0":
+  version "3.16.0"
+  resolved "https://registry.yarnpkg.com/@lerna/log-packed/-/log-packed-3.16.0.tgz#f83991041ee77b2495634e14470b42259fd2bc16"
+  integrity sha512-Fp+McSNBV/P2mnLUYTaSlG8GSmpXM7krKWcllqElGxvAqv6chk2K3c2k80MeVB4WvJ9tRjUUf+i7HUTiQ9/ckQ==
   dependencies:
-    byte-size "^4.0.3"
+    byte-size "^5.0.1"
     columnify "^1.5.4"
     has-unicode "^2.0.1"
     npmlog "^4.1.2"
 
-"@lerna/npm-conf@3.13.0":
-  version "3.13.0"
-  resolved "https://registry.yarnpkg.com/@lerna/npm-conf/-/npm-conf-3.13.0.tgz#6b434ed75ff757e8c14381b9bbfe5d5ddec134a7"
-  integrity sha512-Jg2kANsGnhg+fbPEzE0X9nX5oviEAvWj0nYyOkcE+cgWuT7W0zpnPXC4hA4C5IPQGhwhhh0IxhWNNHtjTuw53g==
+"@lerna/npm-conf@3.16.0":
+  version "3.16.0"
+  resolved "https://registry.yarnpkg.com/@lerna/npm-conf/-/npm-conf-3.16.0.tgz#1c10a89ae2f6c2ee96962557738685300d376827"
+  integrity sha512-HbO3DUrTkCAn2iQ9+FF/eisDpWY5POQAOF1m7q//CZjdC2HSW3UYbKEGsSisFxSfaF9Z4jtrV+F/wX6qWs3CuA==
   dependencies:
     config-chain "^1.1.11"
-    pify "^3.0.0"
+    pify "^4.0.1"
 
-"@lerna/npm-dist-tag@3.13.0":
-  version "3.13.0"
-  resolved "https://registry.yarnpkg.com/@lerna/npm-dist-tag/-/npm-dist-tag-3.13.0.tgz#49ecbe0e82cbe4ad4a8ea6de112982bf6c4e6cd4"
-  integrity sha512-mcuhw34JhSRFrbPn0vedbvgBTvveG52bR2lVE3M3tfE8gmR/cKS/EJFO4AUhfRKGCTFn9rjaSEzlFGYV87pemQ==
+"@lerna/npm-dist-tag@3.18.5":
+  version "3.18.5"
+  resolved "https://registry.yarnpkg.com/@lerna/npm-dist-tag/-/npm-dist-tag-3.18.5.tgz#9ef9abb7c104077b31f6fab22cc73b314d54ac55"
+  integrity sha512-xw0HDoIG6HreVsJND9/dGls1c+lf6vhu7yJoo56Sz5bvncTloYGLUppIfDHQr4ZvmPCK8rsh0euCVh2giPxzKQ==
   dependencies:
+    "@evocateur/npm-registry-fetch" "^4.0.0"
+    "@lerna/otplease" "3.18.5"
     figgy-pudding "^3.5.1"
     npm-package-arg "^6.1.0"
-    npm-registry-fetch "^3.9.0"
     npmlog "^4.1.2"
 
-"@lerna/npm-install@3.13.0":
-  version "3.13.0"
-  resolved "https://registry.yarnpkg.com/@lerna/npm-install/-/npm-install-3.13.0.tgz#88f4cc39f4f737c8a8721256b915ea1bcc6a7227"
-  integrity sha512-qNyfts//isYQxore6fsPorNYJmPVKZ6tOThSH97tP0aV91zGMtrYRqlAoUnDwDdAjHPYEM16hNujg2wRmsqqIw==
+"@lerna/npm-install@3.16.5":
+  version "3.16.5"
+  resolved "https://registry.yarnpkg.com/@lerna/npm-install/-/npm-install-3.16.5.tgz#d6bfdc16f81285da66515ae47924d6e278d637d3"
+  integrity sha512-hfiKk8Eku6rB9uApqsalHHTHY+mOrrHeWEs+gtg7+meQZMTS3kzv4oVp5cBZigndQr3knTLjwthT/FX4KvseFg==
   dependencies:
-    "@lerna/child-process" "3.13.0"
+    "@lerna/child-process" "3.16.5"
     "@lerna/get-npm-exec-opts" "3.13.0"
-    fs-extra "^7.0.0"
+    fs-extra "^8.1.0"
     npm-package-arg "^6.1.0"
     npmlog "^4.1.2"
     signal-exit "^3.0.2"
     write-pkg "^3.1.0"
 
-"@lerna/npm-publish@3.13.0":
-  version "3.13.0"
-  resolved "https://registry.yarnpkg.com/@lerna/npm-publish/-/npm-publish-3.13.0.tgz#5c74808376e778865ffdc5885fe83935e15e60c3"
-  integrity sha512-y4WO0XTaf9gNRkI7as6P2ItVDOxmYHwYto357fjybcnfXgMqEA94c3GJ++jU41j0A9vnmYC6/XxpTd9sVmH9tA==
+"@lerna/npm-publish@3.18.5":
+  version "3.18.5"
+  resolved "https://registry.yarnpkg.com/@lerna/npm-publish/-/npm-publish-3.18.5.tgz#240e4039959fd9816b49c5b07421e11b5cb000af"
+  integrity sha512-3etLT9+2L8JAx5F8uf7qp6iAtOLSMj+ZYWY6oUgozPi/uLqU0/gsMsEXh3F0+YVW33q0M61RpduBoAlOOZnaTg==
   dependencies:
-    "@lerna/run-lifecycle" "3.13.0"
+    "@evocateur/libnpmpublish" "^1.2.2"
+    "@lerna/otplease" "3.18.5"
+    "@lerna/run-lifecycle" "3.16.2"
     figgy-pudding "^3.5.1"
-    fs-extra "^7.0.0"
-    libnpmpublish "^1.1.1"
+    fs-extra "^8.1.0"
+    npm-package-arg "^6.1.0"
     npmlog "^4.1.2"
-    pify "^3.0.0"
+    pify "^4.0.1"
     read-package-json "^2.0.13"
 
-"@lerna/npm-run-script@3.13.0":
-  version "3.13.0"
-  resolved "https://registry.yarnpkg.com/@lerna/npm-run-script/-/npm-run-script-3.13.0.tgz#e5997f045402b9948bdc066033ebb36bf94fc9e4"
-  integrity sha512-hiL3/VeVp+NFatBjkGN8mUdX24EfZx9rQlSie0CMgtjc7iZrtd0jCguLomSCRHYjJuvqgbp+LLYo7nHVykfkaQ==
+"@lerna/npm-run-script@3.16.5":
+  version "3.16.5"
+  resolved "https://registry.yarnpkg.com/@lerna/npm-run-script/-/npm-run-script-3.16.5.tgz#9c2ec82453a26c0b46edc0bb7c15816c821f5c15"
+  integrity sha512-1asRi+LjmVn3pMjEdpqKJZFT/3ZNpb+VVeJMwrJaV/3DivdNg7XlPK9LTrORuKU4PSvhdEZvJmSlxCKyDpiXsQ==
   dependencies:
-    "@lerna/child-process" "3.13.0"
+    "@lerna/child-process" "3.16.5"
     "@lerna/get-npm-exec-opts" "3.13.0"
     npmlog "^4.1.2"
 
+"@lerna/otplease@3.18.5":
+  version "3.18.5"
+  resolved "https://registry.yarnpkg.com/@lerna/otplease/-/otplease-3.18.5.tgz#b77b8e760b40abad9f7658d988f3ea77d4fd0231"
+  integrity sha512-S+SldXAbcXTEDhzdxYLU0ZBKuYyURP/ND2/dK6IpKgLxQYh/z4ScljPDMyKymmEvgiEJmBsPZAAPfmNPEzxjog==
+  dependencies:
+    "@lerna/prompt" "3.18.5"
+    figgy-pudding "^3.5.1"
+
 "@lerna/output@3.13.0":
   version "3.13.0"
   resolved "https://registry.yarnpkg.com/@lerna/output/-/output-3.13.0.tgz#3ded7cc908b27a9872228a630d950aedae7a4989"
   dependencies:
     npmlog "^4.1.2"
 
-"@lerna/pack-directory@3.13.1":
-  version "3.13.1"
-  resolved "https://registry.yarnpkg.com/@lerna/pack-directory/-/pack-directory-3.13.1.tgz#5ad4d0945f86a648f565e24d53c1e01bb3a912d1"
-  integrity sha512-kXnyqrkQbCIZOf1054N88+8h0ItC7tUN5v9ca/aWpx298gsURpxUx/1TIKqijL5TOnHMyIkj0YJmnH/PyBVLKA==
+"@lerna/pack-directory@3.16.4":
+  version "3.16.4"
+  resolved "https://registry.yarnpkg.com/@lerna/pack-directory/-/pack-directory-3.16.4.tgz#3eae5f91bdf5acfe0384510ed53faddc4c074693"
+  integrity sha512-uxSF0HZeGyKaaVHz5FroDY9A5NDDiCibrbYR6+khmrhZtY0Bgn6hWq8Gswl9iIlymA+VzCbshWIMX4o2O8C8ng==
   dependencies:
-    "@lerna/get-packed" "3.13.0"
-    "@lerna/package" "3.13.0"
-    "@lerna/run-lifecycle" "3.13.0"
+    "@lerna/get-packed" "3.16.0"
+    "@lerna/package" "3.16.0"
+    "@lerna/run-lifecycle" "3.16.2"
     figgy-pudding "^3.5.1"
-    npm-packlist "^1.4.1"
+    npm-packlist "^1.4.4"
     npmlog "^4.1.2"
-    tar "^4.4.8"
+    tar "^4.4.10"
     temp-write "^3.4.0"
 
-"@lerna/package-graph@3.13.0":
-  version "3.13.0"
-  resolved "https://registry.yarnpkg.com/@lerna/package-graph/-/package-graph-3.13.0.tgz#607062f8d2ce22b15f8d4a0623f384736e67f760"
-  integrity sha512-3mRF1zuqFE1HEFmMMAIggXy+f+9cvHhW/jzaPEVyrPNLKsyfJQtpTNzeI04nfRvbAh+Gd2aNksvaW/w3xGJnnw==
+"@lerna/package-graph@3.18.5":
+  version "3.18.5"
+  resolved "https://registry.yarnpkg.com/@lerna/package-graph/-/package-graph-3.18.5.tgz#c740e2ea3578d059e551633e950690831b941f6b"
+  integrity sha512-8QDrR9T+dBegjeLr+n9WZTVxUYUhIUjUgZ0gvNxUBN8S1WB9r6H5Yk56/MVaB64tA3oGAN9IIxX6w0WvTfFudA==
   dependencies:
+    "@lerna/prerelease-id-from-version" "3.16.0"
     "@lerna/validation-error" "3.13.0"
     npm-package-arg "^6.1.0"
-    semver "^5.5.0"
+    npmlog "^4.1.2"
+    semver "^6.2.0"
 
-"@lerna/package@3.13.0":
-  version "3.13.0"
-  resolved "https://registry.yarnpkg.com/@lerna/package/-/package-3.13.0.tgz#4baeebc49a57fc9b31062cc59f5ee38384429fc8"
-  integrity sha512-kSKO0RJQy093BufCQnkhf1jB4kZnBvL7kK5Ewolhk5gwejN+Jofjd8DGRVUDUJfQ0CkW1o6GbUeZvs8w8VIZDg==
+"@lerna/package@3.16.0":
+  version "3.16.0"
+  resolved "https://registry.yarnpkg.com/@lerna/package/-/package-3.16.0.tgz#7e0a46e4697ed8b8a9c14d59c7f890e0d38ba13c"
+  integrity sha512-2lHBWpaxcBoiNVbtyLtPUuTYEaB/Z+eEqRS9duxpZs6D+mTTZMNy6/5vpEVSCBmzvdYpyqhqaYjjSLvjjr5Riw==
   dependencies:
-    load-json-file "^4.0.0"
+    load-json-file "^5.3.0"
     npm-package-arg "^6.1.0"
     write-pkg "^3.1.0"
 
-"@lerna/project@3.13.1":
-  version "3.13.1"
-  resolved "https://registry.yarnpkg.com/@lerna/project/-/project-3.13.1.tgz#bce890f60187bd950bcf36c04b5260642e295e79"
-  integrity sha512-/GoCrpsCCTyb9sizk1+pMBrIYchtb+F1uCOn3cjn9yenyG/MfYEnlfrbV5k/UDud0Ei75YBLbmwCbigHkAKazQ==
+"@lerna/prerelease-id-from-version@3.16.0":
+  version "3.16.0"
+  resolved "https://registry.yarnpkg.com/@lerna/prerelease-id-from-version/-/prerelease-id-from-version-3.16.0.tgz#b24bfa789f5e1baab914d7b08baae9b7bd7d83a1"
+  integrity sha512-qZyeUyrE59uOK8rKdGn7jQz+9uOpAaF/3hbslJVFL1NqF9ELDTqjCPXivuejMX/lN4OgD6BugTO4cR7UTq/sZA==
+  dependencies:
+    semver "^6.2.0"
+
+"@lerna/profiler@3.20.0":
+  version "3.20.0"
+  resolved "https://registry.yarnpkg.com/@lerna/profiler/-/profiler-3.20.0.tgz#0f6dc236f4ea8f9ea5f358c6703305a4f32ad051"
+  integrity sha512-bh8hKxAlm6yu8WEOvbLENm42i2v9SsR4WbrCWSbsmOElx3foRnMlYk7NkGECa+U5c3K4C6GeBbwgqs54PP7Ljg==
+  dependencies:
+    figgy-pudding "^3.5.1"
+    fs-extra "^8.1.0"
+    npmlog "^4.1.2"
+    upath "^1.2.0"
+
+"@lerna/project@3.21.0":
+  version "3.21.0"
+  resolved "https://registry.yarnpkg.com/@lerna/project/-/project-3.21.0.tgz#5d784d2d10c561a00f20320bcdb040997c10502d"
+  integrity sha512-xT1mrpET2BF11CY32uypV2GPtPVm6Hgtha7D81GQP9iAitk9EccrdNjYGt5UBYASl4CIDXBRxwmTTVGfrCx82A==
   dependencies:
-    "@lerna/package" "3.13.0"
+    "@lerna/package" "3.16.0"
     "@lerna/validation-error" "3.13.0"
     cosmiconfig "^5.1.0"
     dedent "^0.7.0"
     dot-prop "^4.2.0"
-    glob-parent "^3.1.0"
-    globby "^8.0.1"
-    load-json-file "^4.0.0"
+    glob-parent "^5.0.0"
+    globby "^9.2.0"
+    load-json-file "^5.3.0"
     npmlog "^4.1.2"
-    p-map "^1.2.0"
+    p-map "^2.1.0"
     resolve-from "^4.0.0"
-    write-json-file "^2.3.0"
+    write-json-file "^3.2.0"
 
-"@lerna/prompt@3.13.0":
-  version "3.13.0"
-  resolved "https://registry.yarnpkg.com/@lerna/prompt/-/prompt-3.13.0.tgz#53571462bb3f5399cc1ca6d335a411fe093426a5"
-  integrity sha512-P+lWSFokdyvYpkwC3it9cE0IF2U5yy2mOUbGvvE4iDb9K7TyXGE+7lwtx2thtPvBAfIb7O13POMkv7df03HJeA==
+"@lerna/prompt@3.18.5":
+  version "3.18.5"
+  resolved "https://registry.yarnpkg.com/@lerna/prompt/-/prompt-3.18.5.tgz#628cd545f225887d060491ab95df899cfc5218a1"
+  integrity sha512-rkKj4nm1twSbBEb69+Em/2jAERK8htUuV8/xSjN0NPC+6UjzAwY52/x9n5cfmpa9lyKf/uItp7chCI7eDmNTKQ==
   dependencies:
     inquirer "^6.2.0"
     npmlog "^4.1.2"
 
-"@lerna/publish@3.13.1":
-  version "3.13.1"
-  resolved "https://registry.yarnpkg.com/@lerna/publish/-/publish-3.13.1.tgz#217e401dcb5824cdd6d36555a36303fb7520c514"
-  integrity sha512-KhCJ9UDx76HWCF03i5TD7z5lX+2yklHh5SyO8eDaLptgdLDQ0Z78lfGj3JhewHU2l46FztmqxL/ss0IkWHDL+g==
-  dependencies:
-    "@lerna/batch-packages" "3.13.0"
-    "@lerna/check-working-tree" "3.13.0"
-    "@lerna/child-process" "3.13.0"
-    "@lerna/collect-updates" "3.13.0"
-    "@lerna/command" "3.13.1"
-    "@lerna/describe-ref" "3.13.0"
-    "@lerna/log-packed" "3.13.0"
-    "@lerna/npm-conf" "3.13.0"
-    "@lerna/npm-dist-tag" "3.13.0"
-    "@lerna/npm-publish" "3.13.0"
+"@lerna/publish@3.22.1":
+  version "3.22.1"
+  resolved "https://registry.yarnpkg.com/@lerna/publish/-/publish-3.22.1.tgz#b4f7ce3fba1e9afb28be4a1f3d88222269ba9519"
+  integrity sha512-PG9CM9HUYDreb1FbJwFg90TCBQooGjj+n/pb3gw/eH5mEDq0p8wKdLFe0qkiqUkm/Ub5C8DbVFertIo0Vd0zcw==
+  dependencies:
+    "@evocateur/libnpmaccess" "^3.1.2"
+    "@evocateur/npm-registry-fetch" "^4.0.0"
+    "@evocateur/pacote" "^9.6.3"
+    "@lerna/check-working-tree" "3.16.5"
+    "@lerna/child-process" "3.16.5"
+    "@lerna/collect-updates" "3.20.0"
+    "@lerna/command" "3.21.0"
+    "@lerna/describe-ref" "3.16.5"
+    "@lerna/log-packed" "3.16.0"
+    "@lerna/npm-conf" "3.16.0"
+    "@lerna/npm-dist-tag" "3.18.5"
+    "@lerna/npm-publish" "3.18.5"
+    "@lerna/otplease" "3.18.5"
     "@lerna/output" "3.13.0"
-    "@lerna/pack-directory" "3.13.1"
-    "@lerna/prompt" "3.13.0"
+    "@lerna/pack-directory" "3.16.4"
+    "@lerna/prerelease-id-from-version" "3.16.0"
+    "@lerna/prompt" "3.18.5"
     "@lerna/pulse-till-done" "3.13.0"
-    "@lerna/run-lifecycle" "3.13.0"
-    "@lerna/run-parallel-batches" "3.13.0"
+    "@lerna/run-lifecycle" "3.16.2"
+    "@lerna/run-topologically" "3.18.5"
     "@lerna/validation-error" "3.13.0"
-    "@lerna/version" "3.13.1"
+    "@lerna/version" "3.22.1"
     figgy-pudding "^3.5.1"
-    fs-extra "^7.0.0"
-    libnpmaccess "^3.0.1"
+    fs-extra "^8.1.0"
     npm-package-arg "^6.1.0"
-    npm-registry-fetch "^3.9.0"
     npmlog "^4.1.2"
     p-finally "^1.0.0"
-    p-map "^1.2.0"
+    p-map "^2.1.0"
     p-pipe "^1.2.0"
-    p-reduce "^1.0.0"
-    pacote "^9.5.0"
-    semver "^5.5.0"
+    semver "^6.2.0"
 
 "@lerna/pulse-till-done@3.13.0":
   version "3.13.0"
   dependencies:
     npmlog "^4.1.2"
 
-"@lerna/resolve-symlink@3.13.0":
-  version "3.13.0"
-  resolved "https://registry.yarnpkg.com/@lerna/resolve-symlink/-/resolve-symlink-3.13.0.tgz#3e6809ef53b63fe914814bfa071cd68012e22fbb"
-  integrity sha512-Lc0USSFxwDxUs5JvIisS8JegjA6SHSAWJCMvi2osZx6wVRkEDlWG2B1JAfXUzCMNfHoZX0/XX9iYZ+4JIpjAtg==
+"@lerna/query-graph@3.18.5":
+  version "3.18.5"
+  resolved "https://registry.yarnpkg.com/@lerna/query-graph/-/query-graph-3.18.5.tgz#df4830bb5155273003bf35e8dda1c32d0927bd86"
+  integrity sha512-50Lf4uuMpMWvJ306be3oQDHrWV42nai9gbIVByPBYJuVW8dT8O8pA3EzitNYBUdLL9/qEVbrR0ry1HD7EXwtRA==
   dependencies:
-    fs-extra "^7.0.0"
+    "@lerna/package-graph" "3.18.5"
+    figgy-pudding "^3.5.1"
+
+"@lerna/resolve-symlink@3.16.0":
+  version "3.16.0"
+  resolved "https://registry.yarnpkg.com/@lerna/resolve-symlink/-/resolve-symlink-3.16.0.tgz#37fc7095fabdbcf317c26eb74e0d0bde8efd2386"
+  integrity sha512-Ibj5e7njVHNJ/NOqT4HlEgPFPtPLWsO7iu59AM5bJDcAJcR96mLZ7KGVIsS2tvaO7akMEJvt2P+ErwCdloG3jQ==
+  dependencies:
+    fs-extra "^8.1.0"
     npmlog "^4.1.2"
     read-cmd-shim "^1.0.1"
 
-"@lerna/rimraf-dir@3.13.0":
-  version "3.13.0"
-  resolved "https://registry.yarnpkg.com/@lerna/rimraf-dir/-/rimraf-dir-3.13.0.tgz#bb1006104b4aabcb6985624273254648f872b278"
-  integrity sha512-kte+pMemulre8cmPqljxIYjCmdLByz8DgHBHXB49kz2EiPf8JJ+hJFt0PzEubEyJZ2YE2EVAx5Tv5+NfGNUQyQ==
+"@lerna/rimraf-dir@3.16.5":
+  version "3.16.5"
+  resolved "https://registry.yarnpkg.com/@lerna/rimraf-dir/-/rimraf-dir-3.16.5.tgz#04316ab5ffd2909657aaf388ea502cb8c2f20a09"
+  integrity sha512-bQlKmO0pXUsXoF8lOLknhyQjOZsCc0bosQDoX4lujBXSWxHVTg1VxURtWf2lUjz/ACsJVDfvHZbDm8kyBk5okA==
   dependencies:
-    "@lerna/child-process" "3.13.0"
+    "@lerna/child-process" "3.16.5"
     npmlog "^4.1.2"
     path-exists "^3.0.0"
     rimraf "^2.6.2"
 
-"@lerna/run-lifecycle@3.13.0":
-  version "3.13.0"
-  resolved "https://registry.yarnpkg.com/@lerna/run-lifecycle/-/run-lifecycle-3.13.0.tgz#d8835ee83425edee40f687a55f81b502354d3261"
-  integrity sha512-oyiaL1biZdjpmjh6X/5C4w07wNFyiwXSSHH5GQB4Ay4BPwgq9oNhCcxRoi0UVZlZ1YwzSW8sTwLgj8emkIo3Yg==
+"@lerna/run-lifecycle@3.16.2":
+  version "3.16.2"
+  resolved "https://registry.yarnpkg.com/@lerna/run-lifecycle/-/run-lifecycle-3.16.2.tgz#67b288f8ea964db9ea4fb1fbc7715d5bbb0bce00"
+  integrity sha512-RqFoznE8rDpyyF0rOJy3+KjZCeTkO8y/OB9orPauR7G2xQ7PTdCpgo7EO6ZNdz3Al+k1BydClZz/j78gNCmL2A==
   dependencies:
-    "@lerna/npm-conf" "3.13.0"
+    "@lerna/npm-conf" "3.16.0"
     figgy-pudding "^3.5.1"
-    npm-lifecycle "^2.1.0"
+    npm-lifecycle "^3.1.2"
     npmlog "^4.1.2"
 
-"@lerna/run-parallel-batches@3.13.0":
-  version "3.13.0"
-  resolved "https://registry.yarnpkg.com/@lerna/run-parallel-batches/-/run-parallel-batches-3.13.0.tgz#0276bb4e7cd0995297db82d134ca2bd08d63e311"
-  integrity sha512-bICFBR+cYVF1FFW+Tlm0EhWDioTUTM6dOiVziDEGE1UZha1dFkMYqzqdSf4bQzfLS31UW/KBd/2z8jy2OIjEjg==
+"@lerna/run-topologically@3.18.5":
+  version "3.18.5"
+  resolved "https://registry.yarnpkg.com/@lerna/run-topologically/-/run-topologically-3.18.5.tgz#3cd639da20e967d7672cb88db0f756b92f2fdfc3"
+  integrity sha512-6N1I+6wf4hLOnPW+XDZqwufyIQ6gqoPfHZFkfWlvTQ+Ue7CuF8qIVQ1Eddw5HKQMkxqN10thKOFfq/9NQZ4NUg==
   dependencies:
-    p-map "^1.2.0"
-    p-map-series "^1.0.0"
+    "@lerna/query-graph" "3.18.5"
+    figgy-pudding "^3.5.1"
+    p-queue "^4.0.0"
 
-"@lerna/run@3.13.1":
-  version "3.13.1"
-  resolved "https://registry.yarnpkg.com/@lerna/run/-/run-3.13.1.tgz#87e174c1d271894ddd29adc315c068fb7b1b0117"
-  integrity sha512-nv1oj7bsqppWm1M4ifN+/IIbVu9F4RixrbQD2okqDGYne4RQPAXyb5cEZuAzY/wyGTWWiVaZ1zpj5ogPWvH0bw==
+"@lerna/run@3.21.0":
+  version "3.21.0"
+  resolved "https://registry.yarnpkg.com/@lerna/run/-/run-3.21.0.tgz#2a35ec84979e4d6e42474fe148d32e5de1cac891"
+  integrity sha512-fJF68rT3veh+hkToFsBmUJ9MHc9yGXA7LSDvhziAojzOb0AI/jBDp6cEcDQyJ7dbnplba2Lj02IH61QUf9oW0Q==
   dependencies:
-    "@lerna/batch-packages" "3.13.0"
-    "@lerna/command" "3.13.1"
-    "@lerna/filter-options" "3.13.0"
-    "@lerna/npm-run-script" "3.13.0"
+    "@lerna/command" "3.21.0"
+    "@lerna/filter-options" "3.20.0"
+    "@lerna/npm-run-script" "3.16.5"
     "@lerna/output" "3.13.0"
-    "@lerna/run-parallel-batches" "3.13.0"
+    "@lerna/profiler" "3.20.0"
+    "@lerna/run-topologically" "3.18.5"
     "@lerna/timer" "3.13.0"
     "@lerna/validation-error" "3.13.0"
-    p-map "^1.2.0"
+    p-map "^2.1.0"
 
-"@lerna/symlink-binary@3.13.0":
-  version "3.13.0"
-  resolved "https://registry.yarnpkg.com/@lerna/symlink-binary/-/symlink-binary-3.13.0.tgz#36a9415d468afcb8105750296902f6f000a9680d"
-  integrity sha512-obc4Y6jxywkdaCe+DB0uTxYqP0IQ8mFWvN+k/YMbwH4G2h7M7lCBWgPy8e7xw/50+1II9tT2sxgx+jMus1sTJg==
+"@lerna/symlink-binary@3.17.0":
+  version "3.17.0"
+  resolved "https://registry.yarnpkg.com/@lerna/symlink-binary/-/symlink-binary-3.17.0.tgz#8f8031b309863814883d3f009877f82e38aef45a"
+  integrity sha512-RLpy9UY6+3nT5J+5jkM5MZyMmjNHxZIZvXLV+Q3MXrf7Eaa1hNqyynyj4RO95fxbS+EZc4XVSk25DGFQbcRNSQ==
   dependencies:
-    "@lerna/create-symlink" "3.13.0"
-    "@lerna/package" "3.13.0"
-    fs-extra "^7.0.0"
-    p-map "^1.2.0"
+    "@lerna/create-symlink" "3.16.2"
+    "@lerna/package" "3.16.0"
+    fs-extra "^8.1.0"
+    p-map "^2.1.0"
 
-"@lerna/symlink-dependencies@3.13.0":
-  version "3.13.0"
-  resolved "https://registry.yarnpkg.com/@lerna/symlink-dependencies/-/symlink-dependencies-3.13.0.tgz#76c23ecabda7824db98a0561364f122b457509cf"
-  integrity sha512-7CyN5WYEPkbPLbqHBIQg/YiimBzb5cIGQB0E9IkLs3+racq2vmUNQZn38LOaazQacAA83seB+zWSxlI6H+eXSg==
+"@lerna/symlink-dependencies@3.17.0":
+  version "3.17.0"
+  resolved "https://registry.yarnpkg.com/@lerna/symlink-dependencies/-/symlink-dependencies-3.17.0.tgz#48d6360e985865a0e56cd8b51b308a526308784a"
+  integrity sha512-KmjU5YT1bpt6coOmdFueTJ7DFJL4H1w5eF8yAQ2zsGNTtZ+i5SGFBWpb9AQaw168dydc3s4eu0W0Sirda+F59Q==
   dependencies:
-    "@lerna/create-symlink" "3.13.0"
-    "@lerna/resolve-symlink" "3.13.0"
-    "@lerna/symlink-binary" "3.13.0"
-    fs-extra "^7.0.0"
+    "@lerna/create-symlink" "3.16.2"
+    "@lerna/resolve-symlink" "3.16.0"
+    "@lerna/symlink-binary" "3.17.0"
+    fs-extra "^8.1.0"
     p-finally "^1.0.0"
-    p-map "^1.2.0"
+    p-map "^2.1.0"
     p-map-series "^1.0.0"
 
 "@lerna/timer@3.13.0":
   dependencies:
     npmlog "^4.1.2"
 
-"@lerna/version@3.13.1":
-  version "3.13.1"
-  resolved "https://registry.yarnpkg.com/@lerna/version/-/version-3.13.1.tgz#5e919d13abb13a663dcc7922bb40931f12fb137b"
-  integrity sha512-WpfKc5jZBBOJ6bFS4atPJEbHSiywQ/Gcd+vrwaEGyQHWHQZnPTvhqLuq3q9fIb9sbuhH5pSY6eehhuBrKqTnjg==
-  dependencies:
-    "@lerna/batch-packages" "3.13.0"
-    "@lerna/check-working-tree" "3.13.0"
-    "@lerna/child-process" "3.13.0"
-    "@lerna/collect-updates" "3.13.0"
-    "@lerna/command" "3.13.1"
-    "@lerna/conventional-commits" "3.13.0"
-    "@lerna/github-client" "3.13.1"
+"@lerna/version@3.22.1":
+  version "3.22.1"
+  resolved "https://registry.yarnpkg.com/@lerna/version/-/version-3.22.1.tgz#9805a9247a47ee62d6b81bd9fa5fb728b24b59e2"
+  integrity sha512-PSGt/K1hVqreAFoi3zjD0VEDupQ2WZVlVIwesrE5GbrL2BjXowjCsTDPqblahDUPy0hp6h7E2kG855yLTp62+g==
+  dependencies:
+    "@lerna/check-working-tree" "3.16.5"
+    "@lerna/child-process" "3.16.5"
+    "@lerna/collect-updates" "3.20.0"
+    "@lerna/command" "3.21.0"
+    "@lerna/conventional-commits" "3.22.0"
+    "@lerna/github-client" "3.22.0"
+    "@lerna/gitlab-client" "3.15.0"
     "@lerna/output" "3.13.0"
-    "@lerna/prompt" "3.13.0"
-    "@lerna/run-lifecycle" "3.13.0"
+    "@lerna/prerelease-id-from-version" "3.16.0"
+    "@lerna/prompt" "3.18.5"
+    "@lerna/run-lifecycle" "3.16.2"
+    "@lerna/run-topologically" "3.18.5"
     "@lerna/validation-error" "3.13.0"
     chalk "^2.3.1"
     dedent "^0.7.0"
+    load-json-file "^5.3.0"
     minimatch "^3.0.4"
     npmlog "^4.1.2"
-    p-map "^1.2.0"
+    p-map "^2.1.0"
     p-pipe "^1.2.0"
     p-reduce "^1.0.0"
     p-waterfall "^1.0.0"
-    semver "^5.5.0"
-    slash "^1.0.0"
+    semver "^6.2.0"
+    slash "^2.0.0"
     temp-write "^3.4.0"
+    write-json-file "^3.2.0"
 
 "@lerna/write-log-file@3.13.0":
   version "3.13.0"
   dependencies:
     "@octokit/types" "^5.0.0"
 
+"@octokit/core@3.0.0":
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/@octokit/core/-/core-3.0.0.tgz#4b7bf2a9e744a49abcbb3aca7b4dfc219513e4f9"
+  integrity sha512-FGUUqZbIwl5UPvuUTWq8ly2B12gJGWjYh1DviBzZLXp5LzHUgyzL+NDGsXeE4vszXoGsD/JfpZS+kjkLiD2T9w==
+  dependencies:
+    "@octokit/auth-token" "^2.4.0"
+    "@octokit/graphql" "^4.3.1"
+    "@octokit/request" "^5.4.0"
+    "@octokit/types" "^5.0.0"
+    before-after-hook "^2.1.0"
+    universal-user-agent "^5.0.0"
+
 "@octokit/endpoint@^6.0.1":
   version "6.0.9"
   resolved "https://registry.yarnpkg.com/@octokit/endpoint/-/endpoint-6.0.9.tgz#c6a772e024202b1bd19ab69f90e0536a2598b13e"
     is-plain-object "^5.0.0"
     universal-user-agent "^6.0.0"
 
-"@octokit/plugin-enterprise-rest@^2.1.1":
-  version "2.2.2"
-  resolved "https://registry.yarnpkg.com/@octokit/plugin-enterprise-rest/-/plugin-enterprise-rest-2.2.2.tgz#c0e22067a043e19f96ff9c7832e2a3019f9be75c"
-  integrity sha512-CTZr64jZYhGWNTDGlSJ2mvIlFsm9OEO3LqWn9I/gmoHI4jRBp4kpHoFYNemG4oA75zUAcmbuWblb7jjP877YZw==
+"@octokit/graphql@^4.3.1":
+  version "4.6.1"
+  resolved "https://registry.yarnpkg.com/@octokit/graphql/-/graphql-4.6.1.tgz#f975486a46c94b7dbe58a0ca751935edc7e32cc9"
+  integrity sha512-2lYlvf4YTDgZCTXTW4+OX+9WTLFtEUc6hGm4qM1nlZjzxj+arizM4aHWzBVBCxY9glh7GIs0WEuiSgbVzv8cmA==
+  dependencies:
+    "@octokit/request" "^5.3.0"
+    "@octokit/types" "^6.0.3"
+    universal-user-agent "^6.0.0"
+
+"@octokit/openapi-types@^5.3.2":
+  version "5.3.2"
+  resolved "https://registry.yarnpkg.com/@octokit/openapi-types/-/openapi-types-5.3.2.tgz#b8ac43c5c3d00aef61a34cf744e315110c78deb4"
+  integrity sha512-NxF1yfYOUO92rCx3dwvA2onF30Vdlg7YUkMVXkeptqpzA3tRLplThhFleV/UKWFgh7rpKu1yYRbvNDUtzSopKA==
+
+"@octokit/plugin-enterprise-rest@^6.0.1":
+  version "6.0.1"
+  resolved "https://registry.yarnpkg.com/@octokit/plugin-enterprise-rest/-/plugin-enterprise-rest-6.0.1.tgz#e07896739618dab8da7d4077c658003775f95437"
+  integrity sha512-93uGjlhUD+iNg1iWhUENAtJata6w5nE+V4urXOAlIXdco6xNZtUSfYY8dzp3Udy74aqO/B5UZL80x/YMa5PKRw==
 
 "@octokit/plugin-paginate-rest@^1.1.1":
   version "1.1.2"
     once "^1.4.0"
     universal-user-agent "^6.0.0"
 
-"@octokit/rest@^16.16.0":
+"@octokit/request@^5.3.0", "@octokit/request@^5.4.0":
+  version "5.4.14"
+  resolved "https://registry.yarnpkg.com/@octokit/request/-/request-5.4.14.tgz#ec5f96f78333bb2af390afa5ff66f114b063bc96"
+  integrity sha512-VkmtacOIQp9daSnBmDI92xNIeLuSRDOIuplp/CJomkvzt7M18NXgG044Cx/LFKLgjKt9T2tZR6AtJayba9GTSA==
+  dependencies:
+    "@octokit/endpoint" "^6.0.1"
+    "@octokit/request-error" "^2.0.0"
+    "@octokit/types" "^6.7.1"
+    deprecation "^2.0.0"
+    is-plain-object "^5.0.0"
+    node-fetch "^2.6.1"
+    once "^1.4.0"
+    universal-user-agent "^6.0.0"
+
+"@octokit/rest@^16.28.4":
   version "16.43.2"
   resolved "https://registry.yarnpkg.com/@octokit/rest/-/rest-16.43.2.tgz#c53426f1e1d1044dee967023e3279c50993dd91b"
   integrity sha512-ngDBevLbBTFfrHZeiS7SAMAZ6ssuVmXuya+F/7RaVvlysgGa1JKJkKWY+jV6TCJYcW0OALfJ7nTIGXcBXzycfQ==
   dependencies:
     "@types/node" ">= 8"
 
+"@octokit/types@^6.0.3", "@octokit/types@^6.7.1":
+  version "6.12.2"
+  resolved "https://registry.yarnpkg.com/@octokit/types/-/types-6.12.2.tgz#5b44add079a478b8eb27d78cf384cc47e4411362"
+  integrity sha512-kCkiN8scbCmSq+gwdJV0iLgHc0O/GTPY1/cffo9kECu1MvatLPh9E+qFhfRIktKfHEA6ZYvv6S1B4Wnv3bi3pA==
+  dependencies:
+    "@octokit/openapi-types" "^5.3.2"
+
 "@types/classnames@2.2.6":
   version "2.2.6"
   resolved "https://registry.yarnpkg.com/@types/classnames/-/classnames-2.2.6.tgz#dbe8a666156d556ed018e15a4c65f08937c3f628"
   resolved "https://registry.yarnpkg.com/@types/geojson/-/geojson-7946.0.7.tgz#c8fa532b60a0042219cdf173ca21a975ef0666ad"
   integrity sha512-wE2v81i4C4Ol09RtsWFAqg3BUitWbHSpSlIo+bNdsCJijO9sjme+zm+73ZMCa/qMC8UEERxzGbvmr1cffo2SiQ==
 
+"@types/glob-to-regexp@0.4.0":
+  version "0.4.0"
+  resolved "https://registry.yarnpkg.com/@types/glob-to-regexp/-/glob-to-regexp-0.4.0.tgz#a295047724f4554be8192b4c779c5e44920a2fdc"
+  integrity sha512-unszpTzAknG612PxqtoNUkLM0T3rIAXT8oE9Dyhhbl4eew91jLqcgJZOu5j7GztHg09R8LWCMocRU1ohDFY7Pw==
+
+"@types/glob@^7.1.1":
+  version "7.1.3"
+  resolved "https://registry.yarnpkg.com/@types/glob/-/glob-7.1.3.tgz#e6ba80f36b7daad2c685acd9266382e68985c183"
+  integrity sha512-SEYeGAIQIQX8NN6LDKprLjbrd5dARM5EXsd8GI/A5l0apYI1fGMWgPHSe4ZKL4eozlAyI+doUE9XbYS4xCkQ1w==
+  dependencies:
+    "@types/minimatch" "*"
+    "@types/node" "*"
+
 "@types/highlight.js@9.12.3":
   version "9.12.3"
   resolved "https://registry.yarnpkg.com/@types/highlight.js/-/highlight.js-9.12.3.tgz#b672cfaac25cbbc634a0fd92c515f66faa18dbca"
   resolved "https://registry.yarnpkg.com/@types/marked/-/marked-0.6.0.tgz#e4ac316144a84afda5c2474488d7b9fef3ab9995"
   integrity sha512-TxwhgR9VsIfRDJ3WwFokG8Xu+ea0nYGDRHdI783WJ983uffatz0ytIeUEIBOwPvRy241KRSNVyywQltuTqDh0w==
 
-"@types/minimatch@3.0.3":
+"@types/minimatch@*":
   version "3.0.3"
   resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.3.tgz#3dca0e3f33b200fc7d1139c0cd96c1268cadfd9d"
   integrity sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA==
   resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.3.tgz#2ab0d5da2e5815f94b0b9d4b95d1e5f243ab2ca7"
   integrity sha512-KfRL3PuHmqQLOG+2tGpRO26Ctg+Cq1E01D2DMriKEATHgWLfeNDmq9e29Q9WIky0dQ3NPkd1mzYH8Lm936Z9qw==
 
-"@types/react-dom@16.9.5":
-  version "16.9.5"
-  resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-16.9.5.tgz#5de610b04a35d07ffd8f44edad93a71032d9aaa7"
-  integrity sha512-BX6RQ8s9D+2/gDhxrj8OW+YD4R+8hj7FEM/OJHGNR0KipE1h1mSsf39YeyC81qafkq+N3rU3h3RFbLSwE5VqUg==
+"@types/react-dom@17.0.2":
+  version "17.0.2"
+  resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-17.0.2.tgz#35654cf6c49ae162d5bc90843d5437dc38008d43"
+  integrity sha512-Icd9KEgdnFfJs39KyRyr0jQ7EKhq8U6CcHRMGAS45fp5qgUvxL3ujUCfWFttUK2UErqZNj97t9gsVPNAqcwoCg==
   dependencies:
     "@types/react" "*"
 
-"@types/react-router-dom@4.3.1":
-  version "4.3.1"
-  resolved "https://registry.yarnpkg.com/@types/react-router-dom/-/react-router-dom-4.3.1.tgz#71fe2918f8f60474a891520def40a63997dafe04"
-  integrity sha512-GbztJAScOmQ/7RsQfO4cd55RuH1W4g6V1gDW3j4riLlt+8yxYLqqsiMzmyuXBLzdFmDtX/uU2Bpcm0cmudv44A==
+"@types/react-router-dom@5.1.7":
+  version "5.1.7"
+  resolved "https://registry.yarnpkg.com/@types/react-router-dom/-/react-router-dom-5.1.7.tgz#a126d9ea76079ffbbdb0d9225073eb5797ab7271"
+  integrity sha512-D5mHD6TbdV/DNHYsnwBTv+y73ei+mMjrkGrla86HthE4/PVvL1J94Bu3qABU+COXzpL23T1EZapVVpwHuBXiUg==
   dependencies:
     "@types/history" "*"
     "@types/react" "*"
     "@types/prop-types" "*"
     csstype "^3.0.2"
 
-"@types/react@16.9.19":
-  version "16.9.19"
-  resolved "https://registry.yarnpkg.com/@types/react/-/react-16.9.19.tgz#c842aa83ea490007d29938146ff2e4d9e4360c40"
-  integrity sha512-LJV97//H+zqKWMms0kvxaKYJDG05U2TtQB3chRLF8MPNs+MQh/H1aGlyDUxjaHvu08EAGerdX2z4LTBc7ns77A==
+"@types/react@17.0.3":
+  version "17.0.3"
+  resolved "https://registry.yarnpkg.com/@types/react/-/react-17.0.3.tgz#ba6e215368501ac3826951eef2904574c262cc79"
+  integrity sha512-wYOUxIgs2HZZ0ACNiIayItyluADNbONl7kt8lkLjVK8IitMH5QMyAh75Fwhmo37r1m7L2JaFj03sIfxBVDvRAg==
   dependencies:
     "@types/prop-types" "*"
-    csstype "^2.2.0"
+    "@types/scheduler" "*"
+    csstype "^3.0.2"
+
+"@types/scheduler@*":
+  version "0.16.1"
+  resolved "https://registry.yarnpkg.com/@types/scheduler/-/scheduler-0.16.1.tgz#18845205e86ff0038517aab7a18a62a6b9f71275"
+  integrity sha512-EaCxbanVeyxDRTQBkdLb3Bvl/HK7PBK6UJjsSixB0iHKoWxE5uu2Q/DgtpOhPIojN0Zl1whvOd7PoHs2P0s5eA==
 
 "@types/x2js@^3.1.0":
   version "3.1.0"
   resolved "https://registry.yarnpkg.com/@xtuc/long/-/long-4.2.2.tgz#d291c6a4e97989b5c61d9acf396ae4fe133a718d"
   integrity sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==
 
+"@zkochan/cmd-shim@^3.1.0":
+  version "3.1.0"
+  resolved "https://registry.yarnpkg.com/@zkochan/cmd-shim/-/cmd-shim-3.1.0.tgz#2ab8ed81f5bb5452a85f25758eb9b8681982fd2e"
+  integrity sha512-o8l0+x7C7sMZU3v9GuJIAU10qQLtwR1dtRQIOmlNMtyaqhmpXOzx1HWiYoWfmmf9HHZoAkXpc9TM9PQYF9d4Jg==
+  dependencies:
+    is-windows "^1.0.0"
+    mkdirp-promise "^5.0.1"
+    mz "^2.5.0"
+
 JSONStream@^1.0.4, JSONStream@^1.3.4:
   version "1.3.5"
   resolved "https://registry.yarnpkg.com/JSONStream/-/JSONStream-1.3.5.tgz#3208c1f08d3a4d99261ab64f92302bc15e111ca0"
@@ -2216,6 +2437,11 @@ ansi-styles@^3.2.0, ansi-styles@^3.2.1:
   dependencies:
     color-convert "^1.9.0"
 
+any-promise@^1.0.0:
+  version "1.3.0"
+  resolved "https://registry.yarnpkg.com/any-promise/-/any-promise-1.3.0.tgz#abc6afeedcea52e809cdc0376aed3ce39635d17f"
+  integrity sha1-q8av7tzqUugJzcA3au0845Y10X8=
+
 anymatch@^2.0.0:
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-2.0.0.tgz#bcb24b4f37934d9aa7ac17b4adaf89e7c76ef2eb"
@@ -2286,10 +2512,10 @@ arr-union@^3.1.0:
   resolved "https://registry.yarnpkg.com/arr-union/-/arr-union-3.1.0.tgz#e39b09aea9def866a8f206e288af63919bae39c4"
   integrity sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=
 
-array-differ@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/array-differ/-/array-differ-1.0.0.tgz#eff52e3758249d33be402b8bb8e564bb2b5d4031"
-  integrity sha1-7/UuN1gknTO+QCuLuOVkuytdQDE=
+array-differ@^2.0.3:
+  version "2.1.0"
+  resolved "https://registry.yarnpkg.com/array-differ/-/array-differ-2.1.0.tgz#4b9c1c3f14b906757082925769e8ab904f4801b1"
+  integrity sha512-KbUpJgx909ZscOc/7CLATBFam7P1Z1QRQInvgT0UztM9Q72aGKCunKASAl7WNW0tnPmPyEMeMhdsfWhfmW037w==
 
 array-equal@^1.0.0:
   version "1.0.0"
@@ -2316,7 +2542,7 @@ array-ify@^1.0.0:
   resolved "https://registry.yarnpkg.com/array-ify/-/array-ify-1.0.0.tgz#9e528762b4a9066ad163a6962a364418e9626ece"
   integrity sha1-nlKHYrSpBmrRY6aWKjZEGOlibs4=
 
-array-union@^1.0.1:
+array-union@^1.0.1, array-union@^1.0.2:
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/array-union/-/array-union-1.0.2.tgz#9a34410e4f4e3da23dea375be5be70f24778ec39"
   integrity sha1-mjRBDk9OPaI96jdb5b5w8kd47Dk=
@@ -2338,7 +2564,7 @@ array-unique@^0.3.2:
   resolved "https://registry.yarnpkg.com/array-unique/-/array-unique-0.3.2.tgz#a894b75d4bc4f6cd679ef3244a9fd8f46ae2d428"
   integrity sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=
 
-arrify@^1.0.0, arrify@^1.0.1:
+arrify@^1.0.1:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/arrify/-/arrify-1.0.1.tgz#898508da2226f380df904728456849c1501a4b0d"
   integrity sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=
@@ -2665,6 +2891,11 @@ before-after-hook@^2.0.0:
   resolved "https://registry.yarnpkg.com/before-after-hook/-/before-after-hook-2.1.0.tgz#b6c03487f44e24200dd30ca5e6a1979c5d2fb635"
   integrity sha512-IWIbu7pMqyw3EAJHzzHbWa85b6oud/yfKYg5rqB5hNE8CeMi3nX+2C2sj0HswfblST86hpVEOAb9x34NZd6P7A==
 
+before-after-hook@^2.1.0:
+  version "2.2.0"
+  resolved "https://registry.yarnpkg.com/before-after-hook/-/before-after-hook-2.2.0.tgz#09c40d92e936c64777aa385c4e9b904f8147eaf0"
+  integrity sha512-jH6rKQIfroBbhEXVmI7XmXe3ix5S/PgJqpzdDPnR8JGLHWNYLsYZ6tK5iWOF/Ra3oqEX0NobXGlzbiylIzVphQ==
+
 big.js@^3.1.3:
   version "3.2.0"
   resolved "https://registry.yarnpkg.com/big.js/-/big.js-3.2.0.tgz#a5fc298b81b9e0dca2e458824784b65c52ba588e"
@@ -2944,10 +3175,10 @@ byline@^5.0.0:
   resolved "https://registry.yarnpkg.com/byline/-/byline-5.0.0.tgz#741c5216468eadc457b03410118ad77de8c1ddb1"
   integrity sha1-dBxSFkaOrcRXsDQQEYrXfejB3bE=
 
-byte-size@^4.0.3:
-  version "4.0.4"
-  resolved "https://registry.yarnpkg.com/byte-size/-/byte-size-4.0.4.tgz#29d381709f41aae0d89c631f1c81aec88cd40b23"
-  integrity sha512-82RPeneC6nqCdSwCX2hZUz3JPOvN5at/nTEw/CMf05Smu3Hrpo9Psb7LjN+k+XndNArG1EY8L4+BM3aTM4BCvw==
+byte-size@^5.0.1:
+  version "5.0.1"
+  resolved "https://registry.yarnpkg.com/byte-size/-/byte-size-5.0.1.tgz#4b651039a5ecd96767e71a3d7ed380e48bed4191"
+  integrity sha512-/XuKeqWocKsYa/cBY1YbSJSWWqTi4cFgr9S6OyM7PBaPbr9zvNGwWP33vt0uqGhwDdN+y3yhbXVILEUpnwEWGw==
 
 bytes@3.0.0:
   version "3.0.0"
@@ -2978,7 +3209,7 @@ cacache@^10.0.4:
     unique-filename "^1.1.0"
     y18n "^4.0.0"
 
-cacache@^11.0.2, cacache@^11.3.3:
+cacache@^11.0.2:
   version "11.3.3"
   resolved "https://registry.yarnpkg.com/cacache/-/cacache-11.3.3.tgz#8bd29df8c6a718a6ebd2d010da4d7972ae3bbadc"
   integrity sha512-p8WcneCytvzPxhDvYp31PD039vi77I12W+/KfR9S8AZbaiARFBCpsPJS+9uhWfeBfeAtW7o/4vt3MUqLkbY6nA==
@@ -2998,7 +3229,7 @@ cacache@^11.0.2, cacache@^11.3.3:
     unique-filename "^1.1.1"
     y18n "^4.0.0"
 
-cacache@^12.0.0, cacache@^12.0.2:
+cacache@^12.0.0, cacache@^12.0.2, cacache@^12.0.3:
   version "12.0.4"
   resolved "https://registry.yarnpkg.com/cacache/-/cacache-12.0.4.tgz#668bcbd105aeb5f1d92fe25570ec9525c8faa40c"
   integrity sha512-a0tMB40oefvuInr4Cwb3GerbL9xTj1D5yg0T5xrjGCGyfvbxseIXX7BAO/u/hIXdafzOI5JC3wDwHyf24buOAQ==
@@ -3236,6 +3467,11 @@ ci-info@^1.5.0:
   resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-1.6.0.tgz#2ca20dbb9ceb32d4524a683303313f0304b1e497"
   integrity sha512-vsGdkwSCDpWmP80ncATX7iea5DWQemg1UgCW5J8tqjU3lYw4FBYuj89J0CTVomA7BEfvSZd84GmHko+MxFQU2A==
 
+ci-info@^2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-2.0.0.tgz#67a9e964be31a51e15e5010d58e6f12834002f46"
+  integrity sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==
+
 cipher-base@^1.0.0, cipher-base@^1.0.1, cipher-base@^1.0.3:
   version "1.0.4"
   resolved "https://registry.yarnpkg.com/cipher-base/-/cipher-base-1.0.4.tgz#8760e4ecc272f4c363532f926d874aae2c1397de"
@@ -3315,6 +3551,15 @@ clone-deep@^2.0.1:
     kind-of "^6.0.0"
     shallow-clone "^1.0.0"
 
+clone-deep@^4.0.1:
+  version "4.0.1"
+  resolved "https://registry.yarnpkg.com/clone-deep/-/clone-deep-4.0.1.tgz#c19fd9bdbbf85942b4fd979c84dcf7d5f07c2387"
+  integrity sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==
+  dependencies:
+    is-plain-object "^2.0.4"
+    kind-of "^6.0.2"
+    shallow-clone "^3.0.0"
+
 clone@^1.0.2:
   version "1.0.4"
   resolved "https://registry.yarnpkg.com/clone/-/clone-1.0.4.tgz#da309cc263df15994c688ca902179ca3c7cd7c7e"
@@ -3330,14 +3575,6 @@ clsx@^1.0.4:
   resolved "https://registry.yarnpkg.com/clsx/-/clsx-1.1.1.tgz#98b3134f9abbdf23b2663491ace13c5c03a73188"
   integrity sha512-6/bPho624p3S2pMyvP5kKBPXnI3ufHLObBFCfgx+LkeR5lg2XYy2hqZqUf45ypD8COn2bhgGJSUE+l5dhNBieA==
 
-cmd-shim@^2.0.2:
-  version "2.1.0"
-  resolved "https://registry.yarnpkg.com/cmd-shim/-/cmd-shim-2.1.0.tgz#e59a08d4248dda3bb502044083a4db4ac890579a"
-  integrity sha512-A5C0Cyf2H8sKsHqX0tvIWRXw5/PK++3Dc0lDbsugr90nOECLLuSPahVQBG8pgmgiXgm/TzBWMqI2rWdZwHduAw==
-  dependencies:
-    graceful-fs "^4.1.2"
-    mkdirp "~0.5.0"
-
 co@^4.6.0:
   version "4.6.0"
   resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184"
@@ -3577,7 +3814,7 @@ conventional-commits-filter@^2.0.2, conventional-commits-filter@^2.0.7:
     lodash.ismatch "^4.4.0"
     modify-values "^1.0.0"
 
-conventional-commits-parser@^3.0.2, conventional-commits-parser@^3.0.3:
+conventional-commits-parser@^3.0.3:
   version "3.2.0"
   resolved "https://registry.yarnpkg.com/conventional-commits-parser/-/conventional-commits-parser-3.2.0.tgz#9e261b139ca4b7b29bcebbc54460da36894004ca"
   integrity sha512-XmJiXPxsF0JhAKyfA2Nn+rZwYKJ60nanlbSWwwkGwLQFbugsc0gv1rzc7VbbUWAzJfR1qR87/pNgv9NgmxtBMQ==
@@ -3590,17 +3827,17 @@ conventional-commits-parser@^3.0.2, conventional-commits-parser@^3.0.3:
     through2 "^4.0.0"
     trim-off-newlines "^1.0.0"
 
-conventional-recommended-bump@^4.0.4:
-  version "4.1.1"
-  resolved "https://registry.yarnpkg.com/conventional-recommended-bump/-/conventional-recommended-bump-4.1.1.tgz#37014fadeda267d0607e2fc81124da840a585127"
-  integrity sha512-JT2vKfSP9kR18RXXf55BRY1O3AHG8FPg5btP3l7LYfcWJsiXI6MCf30DepQ98E8Qhowvgv7a8iev0J1bEDkTFA==
+conventional-recommended-bump@^5.0.0:
+  version "5.0.1"
+  resolved "https://registry.yarnpkg.com/conventional-recommended-bump/-/conventional-recommended-bump-5.0.1.tgz#5af63903947b6e089e77767601cb592cabb106ba"
+  integrity sha512-RVdt0elRcCxL90IrNP0fYCpq1uGt2MALko0eyeQ+zQuDVWtMGAy9ng6yYn3kax42lCj9+XBxQ8ZN6S9bdKxDhQ==
   dependencies:
     concat-stream "^2.0.0"
     conventional-changelog-preset-loader "^2.1.1"
     conventional-commits-filter "^2.0.2"
-    conventional-commits-parser "^3.0.2"
+    conventional-commits-parser "^3.0.3"
     git-raw-commits "2.0.0"
-    git-semver-tags "^2.0.2"
+    git-semver-tags "^2.0.3"
     meow "^4.0.0"
     q "^1.5.1"
 
@@ -3820,7 +4057,7 @@ csstype@2.6.8:
   resolved "https://registry.yarnpkg.com/csstype/-/csstype-2.6.8.tgz#0fb6fc2417ffd2816a418c9336da74d7f07db431"
   integrity sha512-msVS9qTuMT5zwAGCVm4mxfrZ18BNc6Csd0oJAtiFMZ1FAx1CCvy2+5MDmYoix63LM/6NDbNtodCiGYGmFgO0dA==
 
-csstype@^2.2.0, csstype@^2.5.2, csstype@^2.6.5:
+csstype@^2.5.2, csstype@^2.6.5:
   version "2.6.13"
   resolved "https://registry.yarnpkg.com/csstype/-/csstype-2.6.13.tgz#a6893015b90e84dd6e85d0e3b442a1e84f2dbe0f"
   integrity sha512-ul26pfSQTZW8dcOnD2iiJssfXw0gdNVX9IJDH/X3K5DGPfj+fUYe3kB+swUY6BF3oZDxaID3AJt+9/ojSAE05A==
@@ -4100,15 +4337,7 @@ diffie-hellman@^5.0.0:
     miller-rabin "^4.0.0"
     randombytes "^2.0.0"
 
-dir-glob@2.0.0:
-  version "2.0.0"
-  resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-2.0.0.tgz#0b205d2b6aef98238ca286598a8204d29d0a0034"
-  integrity sha512-37qirFDz8cA5fimp9feo43fSuRo2gHwaIn6dXL8Ber1dGwUosDrGZeCCXq57WnIqE4aQ+u3eQZzsk1yOzhdwag==
-  dependencies:
-    arrify "^1.0.1"
-    path-type "^3.0.0"
-
-dir-glob@^2.0.0:
+dir-glob@^2.0.0, dir-glob@^2.2.2:
   version "2.2.2"
   resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-2.2.2.tgz#fa09f0694153c8918b18ba0deafae94769fc50c4"
   integrity sha512-f9LBi5QWzIW3I6e//uxZoLBlUt9kcp66qo0sSCxL6YZKc75R1c4MFCoe/LaZiBGmgujvQdxc5Bn3QhfyvK5Hsw==
@@ -4346,6 +4575,16 @@ entities@^2.0.0:
   resolved "https://registry.yarnpkg.com/entities/-/entities-2.1.0.tgz#992d3129cf7df6870b96c57858c249a120f8b8b5"
   integrity sha512-hCx1oky9PFrJ611mf0ifBLBRW8lUUVRlFolb5gWRfIELabBlbp9xZvrqZLZAs+NxFnbfQoeGd8wDkygjg7U85w==
 
+env-paths@^2.2.0:
+  version "2.2.0"
+  resolved "https://registry.yarnpkg.com/env-paths/-/env-paths-2.2.0.tgz#cdca557dc009152917d6166e2febe1f039685e43"
+  integrity sha512-6u0VYSCo/OW6IoD5WCLLy9JUGARbamfSavcNXry/eu8aHVFei6CD3Sw+VGX5alea1i9pgPHW0mbu6Xj0uBh7gA==
+
+envinfo@^7.3.1:
+  version "7.7.3"
+  resolved "https://registry.yarnpkg.com/envinfo/-/envinfo-7.7.3.tgz#4b2d8622e3e7366afb8091b23ed95569ea0208cc"
+  integrity sha512-46+j5QxbPWza0PB1i15nZx0xQ4I/EfQxg9J8Had3b408SV63nEtor2e+oiY63amTo9KTuh2a3XLObNwduxYwwA==
+
 err-code@^1.0.0:
   version "1.1.2"
   resolved "https://registry.yarnpkg.com/err-code/-/err-code-1.1.2.tgz#06e0116d3028f6aef4806849eb0ea6a748ae6960"
@@ -4493,6 +4732,11 @@ etag@~1.8.1:
   resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887"
   integrity sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=
 
+eventemitter3@^3.1.0:
+  version "3.1.2"
+  resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-3.1.2.tgz#2d3d48f9c346698fce83a85d7d664e98535df6e7"
+  integrity sha512-tvtQIeLVHjDkJYnzf2dgVMxfuSGJeM/7UCG17TT4EumTfNtF+0nebF/4zWOIkCreAbtNqhGEboB6BWrwqNaw4Q==
+
 eventemitter3@^4.0.0:
   version "4.0.7"
   resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.7.tgz#2de9b68f6528d5644ef5c59526a1b4a07306169f"
@@ -4713,7 +4957,7 @@ fast-deep-equal@^3.1.1:
   resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525"
   integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==
 
-fast-glob@^2.0.2:
+fast-glob@^2.2.6:
   version "2.2.7"
   resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-2.2.7.tgz#6953857c3afa475fff92ee6015d52da70a4cd39d"
   integrity sha512-g1KuQwHOZAmOZMuBtHdxDtju+T2RT8jgCC9aANsbpdiDDTSnjgfuVsIBNKbUeJI3oKMRExcfNDtJl4OhbffMsw==
@@ -4993,12 +5237,12 @@ from2@^2.1.0:
     inherits "^2.0.1"
     readable-stream "^2.0.0"
 
-fs-extra@^7.0.0:
-  version "7.0.1"
-  resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-7.0.1.tgz#4f189c44aa123b895f722804f55ea23eadc348e9"
-  integrity sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==
+fs-extra@^8.1.0:
+  version "8.1.0"
+  resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-8.1.0.tgz#49d43c45a88cd9677668cb7be1b46efdb8d2e1c0"
+  integrity sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==
   dependencies:
-    graceful-fs "^4.1.2"
+    graceful-fs "^4.2.0"
     jsonfile "^4.0.0"
     universalify "^0.1.0"
 
@@ -5113,10 +5357,10 @@ get-pkg-repo@^1.0.0:
     parse-github-repo-url "^1.3.0"
     through2 "^2.0.0"
 
-get-port@^3.2.0:
-  version "3.2.0"
-  resolved "https://registry.yarnpkg.com/get-port/-/get-port-3.2.0.tgz#dd7ce7de187c06c8bf353796ac71e099f0980ebc"
-  integrity sha1-3Xzn3hh8Bsi/NTeWrHHgmfCYDrw=
+get-port@^4.2.0:
+  version "4.2.0"
+  resolved "https://registry.yarnpkg.com/get-port/-/get-port-4.2.0.tgz#e37368b1e863b7629c43c5a323625f95cf24b119"
+  integrity sha512-/b3jarXkH8KJoOMQc3uVGHASwGLPq3gSFJ7tgJm2diza+bydJPTGOibin2steecKeOylE8oY2JERlVWkAJO6yw==
 
 get-stdin@^4.0.1:
   version "4.0.1"
@@ -5166,7 +5410,7 @@ git-remote-origin-url@^2.0.0:
     gitconfiglocal "^1.0.0"
     pify "^2.3.0"
 
-git-semver-tags@^2.0.2, git-semver-tags@^2.0.3:
+git-semver-tags@^2.0.3:
   version "2.0.3"
   resolved "https://registry.yarnpkg.com/git-semver-tags/-/git-semver-tags-2.0.3.tgz#48988a718acf593800f99622a952a77c405bfa34"
   integrity sha512-tj4FD4ww2RX2ae//jSrXZzrocla9db5h0V7ikPl1P/WwoZar9epdUhwR7XHXSgc+ZkNq72BEEerqQuicoEQfzA==
@@ -5229,13 +5473,18 @@ glob-parent@^3.1.0:
     is-glob "^3.1.0"
     path-dirname "^1.0.0"
 
-glob-parent@~5.1.0:
+glob-parent@^5.0.0, glob-parent@~5.1.0:
   version "5.1.1"
   resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.1.tgz#b6c1ef417c4e5663ea498f1c45afac6916bbc229"
   integrity sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ==
   dependencies:
     is-glob "^4.0.1"
 
+glob-to-regexp@0.4.1:
+  version "0.4.1"
+  resolved "https://registry.yarnpkg.com/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz#c75297087c851b9a578bd217dd59a92f59fe546e"
+  integrity sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==
+
 glob-to-regexp@^0.3.0:
   version "0.3.0"
   resolved "https://registry.yarnpkg.com/glob-to-regexp/-/glob-to-regexp-0.3.0.tgz#8c5a1494d2066c570cc3bfe4496175acc4d502ab"
@@ -5311,18 +5560,19 @@ globby@^7.1.1:
     pify "^3.0.0"
     slash "^1.0.0"
 
-globby@^8.0.1:
-  version "8.0.2"
-  resolved "https://registry.yarnpkg.com/globby/-/globby-8.0.2.tgz#5697619ccd95c5275dbb2d6faa42087c1a941d8d"
-  integrity sha512-yTzMmKygLp8RUpG1Ymu2VXPSJQZjNAZPD4ywgYEaG7e4tBJeUQBO8OpXrf1RCNcEs5alsoJYPAMiIHP0cmeC7w==
+globby@^9.2.0:
+  version "9.2.0"
+  resolved "https://registry.yarnpkg.com/globby/-/globby-9.2.0.tgz#fd029a706c703d29bdd170f4b6db3a3f7a7cb63d"
+  integrity sha512-ollPHROa5mcxDEkwg6bPt3QbEf4pDQSNtd6JPL1YvOvAo/7/0VAm9TccUeoTmarjPw4pfUthSCqcyfNB1I3ZSg==
   dependencies:
-    array-union "^1.0.1"
-    dir-glob "2.0.0"
-    fast-glob "^2.0.2"
-    glob "^7.1.2"
-    ignore "^3.3.5"
-    pify "^3.0.0"
-    slash "^1.0.0"
+    "@types/glob" "^7.1.1"
+    array-union "^1.0.2"
+    dir-glob "^2.2.2"
+    fast-glob "^2.2.6"
+    glob "^7.1.3"
+    ignore "^4.0.3"
+    pify "^4.0.1"
+    slash "^2.0.0"
 
 globule@^1.0.0:
   version "1.3.2"
@@ -5338,7 +5588,7 @@ graceful-fs@4.1.4:
   resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.4.tgz#ef089d2880f033b011823ce5c8fae798da775dbd"
   integrity sha1-7widKIDwM7ARgjzlyPrnmNp3Xb0=
 
-graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2, graceful-fs@^4.1.6:
+graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.2:
   version "4.2.4"
   resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.4.tgz#2256bde14d3632958c465ebc96dc467ca07a29fb"
   integrity sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==
@@ -5480,7 +5730,7 @@ highlight.js@9.13.1:
   resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-9.13.1.tgz#054586d53a6863311168488a0f58d6c505ce641e"
   integrity sha512-Sc28JNQNDzaH6PORtRLMvif9RSn1mYuOoX3omVjnb0+HbpPygU2ALBI0R/wsiqCb4/fcp07Gdo8g+fhtFrQl6A==
 
-history@^4.7.2:
+history@^4.9.0:
   version "4.10.1"
   resolved "https://registry.yarnpkg.com/history/-/history-4.10.1.tgz#33371a65e3a83b267434e2b3f3b1b4c58aad4cf3"
   integrity sha512-36nwAD620w12kuzPAsyINPWJqlNbij+hpK1k9XRloDtym8mxzGYl2c17LnV6IAGB2Dmg4tEa7G7DlawS0+qjew==
@@ -5501,12 +5751,7 @@ hmac-drbg@^1.0.0:
     minimalistic-assert "^1.0.0"
     minimalistic-crypto-utils "^1.0.1"
 
-hoist-non-react-statics@^2.5.0:
-  version "2.5.5"
-  resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-2.5.5.tgz#c5903cf409c0dfd908f388e619d86b9c1174cb47"
-  integrity sha512-rqcy4pJo55FTTLWt+bU8ukscqHeE/e9KWvsOW2b/a3afxQZhwkQdT1rPPCJ0rYXdj4vNcasY8zHTH+jF/qStxw==
-
-hoist-non-react-statics@^3.3.0, hoist-non-react-statics@^3.3.2:
+hoist-non-react-statics@^3.1.0, hoist-non-react-statics@^3.3.0, hoist-non-react-statics@^3.3.2:
   version "3.3.2"
   resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz#ece0acaf71d62c2969c2ec59feff42a4b1a85b45"
   integrity sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==
@@ -5711,7 +5956,7 @@ https-browserify@^1.0.0:
   resolved "https://registry.yarnpkg.com/https-browserify/-/https-browserify-1.0.0.tgz#ec06c10e0a34c0f2faf199f7fd7fc78fffd03c73"
   integrity sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM=
 
-https-proxy-agent@^2.2.1, https-proxy-agent@^2.2.3:
+https-proxy-agent@^2.2.3:
   version "2.2.4"
   resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-2.2.4.tgz#4ee7a737abd92678a293d9b34a1af4d0d08c787b"
   integrity sha512-OmvfoQ53WLjtA9HeYP9RNrWMJzzAz1JGaSFr1nijg0PVR1JaD/xbJq1mdEIIlxGpXp9eSe/O2LgU9DJmTPd0Eg==
@@ -5784,6 +6029,11 @@ ignore@^3.3.5:
   resolved "https://registry.yarnpkg.com/ignore/-/ignore-3.3.10.tgz#0a97fb876986e8081c631160f8f9f389157f0043"
   integrity sha512-Pgs951kaMm5GXP7MOvxERINe3gsaVjUWFm+UZPSq9xYriQAksyhg0csnS0KXSNRD5NmNdapXEpjxG49+AKh/ug==
 
+ignore@^4.0.3:
+  version "4.0.6"
+  resolved "https://registry.yarnpkg.com/ignore/-/ignore-4.0.6.tgz#750e3db5862087b4737ebac8207ffd1ef27b25fc"
+  integrity sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==
+
 image-size@~0.5.0:
   version "0.5.5"
   resolved "https://registry.yarnpkg.com/image-size/-/image-size-0.5.5.tgz#09dfd4ab9d20e29eb1c3e80b8990378df9e3cb9c"
@@ -6025,6 +6275,13 @@ is-ci@^1.0.10:
   dependencies:
     ci-info "^1.5.0"
 
+is-ci@^2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/is-ci/-/is-ci-2.0.0.tgz#6bc6334181810e04b5c22b3d589fdca55026404c"
+  integrity sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w==
+  dependencies:
+    ci-info "^2.0.0"
+
 is-core-module@^2.0.0:
   version "2.1.0"
   resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.1.0.tgz#a4cc031d9b1aca63eecbd18a650e13cb4eeab946"
@@ -6284,7 +6541,7 @@ is-utf8@^0.2.0:
   resolved "https://registry.yarnpkg.com/is-utf8/-/is-utf8-0.2.1.tgz#4b0da1442104d1b336340e80797e865cf39f7d72"
   integrity sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI=
 
-is-windows@^1.0.1, is-windows@^1.0.2:
+is-windows@^1.0.0, is-windows@^1.0.1, is-windows@^1.0.2:
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-1.0.2.tgz#d1850eb9791ecd18e6182ce12a30f396634bb19d"
   integrity sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==
@@ -7044,27 +7301,28 @@ left-pad@^1.3.0:
   resolved "https://registry.yarnpkg.com/left-pad/-/left-pad-1.3.0.tgz#5b8a3a7765dfe001261dde915589e782f8c94d1e"
   integrity sha512-XI5MPzVNApjAyhQzphX8BkmKsKUxD4LdyK24iZeQGinBN9yTQT3bFlCBy/aVx2HrNcqQGsdot8ghrjyrvMCoEA==
 
-lerna@3.13.1:
-  version "3.13.1"
-  resolved "https://registry.yarnpkg.com/lerna/-/lerna-3.13.1.tgz#feaff562176f304bd82329ca29ce46ab6c033463"
-  integrity sha512-7kSz8LLozVsoUNTJzJzy+b8TnV9YdviR2Ee2PwGZSlVw3T1Rn7kOAPZjEi+3IWnOPC96zMPHVmjCmzQ4uubalw==
-  dependencies:
-    "@lerna/add" "3.13.1"
-    "@lerna/bootstrap" "3.13.1"
-    "@lerna/changed" "3.13.1"
-    "@lerna/clean" "3.13.1"
-    "@lerna/cli" "3.13.0"
-    "@lerna/create" "3.13.1"
-    "@lerna/diff" "3.13.1"
-    "@lerna/exec" "3.13.1"
-    "@lerna/import" "3.13.1"
-    "@lerna/init" "3.13.1"
-    "@lerna/link" "3.13.1"
-    "@lerna/list" "3.13.1"
-    "@lerna/publish" "3.13.1"
-    "@lerna/run" "3.13.1"
-    "@lerna/version" "3.13.1"
-    import-local "^1.0.0"
+lerna@3.22.1:
+  version "3.22.1"
+  resolved "https://registry.yarnpkg.com/lerna/-/lerna-3.22.1.tgz#82027ac3da9c627fd8bf02ccfeff806a98e65b62"
+  integrity sha512-vk1lfVRFm+UuEFA7wkLKeSF7Iz13W+N/vFd48aW2yuS7Kv0RbNm2/qcDPV863056LMfkRlsEe+QYOw3palj5Lg==
+  dependencies:
+    "@lerna/add" "3.21.0"
+    "@lerna/bootstrap" "3.21.0"
+    "@lerna/changed" "3.21.0"
+    "@lerna/clean" "3.21.0"
+    "@lerna/cli" "3.18.5"
+    "@lerna/create" "3.22.0"
+    "@lerna/diff" "3.21.0"
+    "@lerna/exec" "3.21.0"
+    "@lerna/import" "3.22.0"
+    "@lerna/info" "3.21.0"
+    "@lerna/init" "3.21.0"
+    "@lerna/link" "3.21.0"
+    "@lerna/list" "3.21.0"
+    "@lerna/publish" "3.22.1"
+    "@lerna/run" "3.21.0"
+    "@lerna/version" "3.22.1"
+    import-local "^2.0.0"
     npmlog "^4.1.2"
 
 less-loader@4.1.0:
@@ -7105,31 +7363,6 @@ levn@~0.3.0:
     prelude-ls "~1.1.2"
     type-check "~0.3.2"
 
-libnpmaccess@^3.0.1:
-  version "3.0.2"
-  resolved "https://registry.yarnpkg.com/libnpmaccess/-/libnpmaccess-3.0.2.tgz#8b2d72345ba3bef90d3b4f694edd5c0417f58923"
-  integrity sha512-01512AK7MqByrI2mfC7h5j8N9V4I7MHJuk9buo8Gv+5QgThpOgpjB7sQBDDkeZqRteFb1QM/6YNdHfG7cDvfAQ==
-  dependencies:
-    aproba "^2.0.0"
-    get-stream "^4.0.0"
-    npm-package-arg "^6.1.0"
-    npm-registry-fetch "^4.0.0"
-
-libnpmpublish@^1.1.1:
-  version "1.1.3"
-  resolved "https://registry.yarnpkg.com/libnpmpublish/-/libnpmpublish-1.1.3.tgz#e3782796722d79eef1a0a22944c117e0c4ca4280"
-  integrity sha512-/3LsYqVc52cHXBmu26+J8Ed7sLs/hgGVFMH1mwYpL7Qaynb9RenpKqIKu0sJ130FB9PMkpMlWjlbtU8A4m7CQw==
-  dependencies:
-    aproba "^2.0.0"
-    figgy-pudding "^3.5.1"
-    get-stream "^4.0.0"
-    lodash.clonedeep "^4.5.0"
-    normalize-package-data "^2.4.0"
-    npm-package-arg "^6.1.0"
-    npm-registry-fetch "^4.0.0"
-    semver "^5.5.1"
-    ssri "^6.0.1"
-
 lightercollective@^0.1.0:
   version "0.1.0"
   resolved "https://registry.yarnpkg.com/lightercollective/-/lightercollective-0.1.0.tgz#70df102c530dcb8d0ccabfe6175a8d00d5f61300"
@@ -7161,6 +7394,17 @@ load-json-file@^4.0.0:
     pify "^3.0.0"
     strip-bom "^3.0.0"
 
+load-json-file@^5.3.0:
+  version "5.3.0"
+  resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-5.3.0.tgz#4d3c1e01fa1c03ea78a60ac7af932c9ce53403f3"
+  integrity sha512-cJGP40Jc/VXUsp8/OrnyKyTZ1y6v/dphm3bioS+RrKXjK2BB6wHUd6JptZEFDGgGahMT+InnZO5i1Ei9mpC8Bw==
+  dependencies:
+    graceful-fs "^4.1.15"
+    parse-json "^4.0.0"
+    pify "^4.0.1"
+    strip-bom "^3.0.0"
+    type-fest "^0.3.0"
+
 loader-runner@^2.3.0, loader-runner@^2.4.0:
   version "2.4.0"
   resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-2.4.0.tgz#ed47066bfe534d7e84c4c7b9998c2a75607d9357"
@@ -7307,7 +7551,7 @@ lodash.tail@^4.1.1:
   resolved "https://registry.yarnpkg.com/lodash.tail/-/lodash.tail-4.1.1.tgz#d2333a36d9e7717c8ad2f7cacafec7c32b444664"
   integrity sha1-0jM6NtnncXyK0vfKyv7HwytERmQ=
 
-lodash.template@^4.0.2:
+lodash.template@^4.0.2, lodash.template@^4.5.0:
   version "4.5.0"
   resolved "https://registry.yarnpkg.com/lodash.template/-/lodash.template-4.5.0.tgz#f976195cf3f347d0d5f52483569fe8031ccce8ab"
   integrity sha512-84vYFxIkmidUiFxidA/KjjH9pAycqW+h980j7Fuz5qxRtO9pgB7MDFTdys1N7A5mcucRiDyEq4fusljItR1T/A==
@@ -7391,7 +7635,7 @@ make-dir@^1.0.0:
   dependencies:
     pify "^3.0.0"
 
-make-dir@^2.0.0:
+make-dir@^2.0.0, make-dir@^2.1.0:
   version "2.1.0"
   resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-2.1.0.tgz#5f0310e18b8be898cc07009295a30ae41e91e6f5"
   integrity sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==
@@ -7404,23 +7648,6 @@ make-error@1.x:
   resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.6.tgz#2eb2e37ea9b67c4891f684a1394799af484cf7a2"
   integrity sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==
 
-make-fetch-happen@^4.0.2:
-  version "4.0.2"
-  resolved "https://registry.yarnpkg.com/make-fetch-happen/-/make-fetch-happen-4.0.2.tgz#2d156b11696fb32bffbafe1ac1bc085dd6c78a79"
-  integrity sha512-YMJrAjHSb/BordlsDEcVcPyTbiJKkzqMf48N8dAJZT9Zjctrkb6Yg4TY9Sq2AwSIQJFn5qBBKVTYt3vP5FMIHA==
-  dependencies:
-    agentkeepalive "^3.4.1"
-    cacache "^11.3.3"
-    http-cache-semantics "^3.8.1"
-    http-proxy-agent "^2.1.0"
-    https-proxy-agent "^2.2.1"
-    lru-cache "^5.1.1"
-    mississippi "^3.0.0"
-    node-fetch-npm "^2.0.2"
-    promise-retry "^1.1.1"
-    socks-proxy-agent "^4.0.0"
-    ssri "^6.0.0"
-
 make-fetch-happen@^5.0.0:
   version "5.0.2"
   resolved "https://registry.yarnpkg.com/make-fetch-happen/-/make-fetch-happen-5.0.2.tgz#aa8387104f2687edca01c8687ee45013d02d19bd"
@@ -7725,6 +7952,14 @@ min-indent@^1.0.0:
   resolved "https://registry.yarnpkg.com/min-indent/-/min-indent-1.0.1.tgz#a63f681673b30571fbe8bc25686ae746eefa9869"
   integrity sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==
 
+mini-create-react-context@^0.4.0:
+  version "0.4.1"
+  resolved "https://registry.yarnpkg.com/mini-create-react-context/-/mini-create-react-context-0.4.1.tgz#072171561bfdc922da08a60c2197a497cc2d1d5e"
+  integrity sha512-YWCYEmd5CQeHGSAKrYvXgmzzkrvssZcuuQDDeqkT+PziKGMgE+0MCCtcKbROzocGBG1meBLl2FotlRwf4gAzbQ==
+  dependencies:
+    "@babel/runtime" "^7.12.1"
+    tiny-warning "^1.0.3"
+
 minimalistic-assert@^1.0.0, minimalistic-assert@^1.0.1:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz#2e194de044626d4a10e7f7fbc00ce73e83e4d5c7"
@@ -7735,7 +7970,7 @@ minimalistic-crypto-utils@^1.0.0, minimalistic-crypto-utils@^1.0.1:
   resolved "https://registry.yarnpkg.com/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz#f6c00c1c0b082246e5c4d99dfb8c7c083b2b582a"
   integrity sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo=
 
-minimatch@3.0.4, minimatch@^3.0.0, minimatch@^3.0.3, minimatch@^3.0.4, minimatch@~3.0.2:
+minimatch@^3.0.3, minimatch@^3.0.4, minimatch@~3.0.2:
   version "3.0.4"
   resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083"
   integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==
@@ -7832,6 +8067,18 @@ mixin-object@^2.0.1:
     for-in "^0.1.3"
     is-extendable "^0.1.1"
 
+mkdirp-promise@^5.0.1:
+  version "5.0.1"
+  resolved "https://registry.yarnpkg.com/mkdirp-promise/-/mkdirp-promise-5.0.1.tgz#e9b8f68e552c68a9c1713b84883f7a1dd039b8a1"
+  integrity sha1-6bj2jlUsaKnBcTuEiD96HdA5uKE=
+  dependencies:
+    mkdirp "*"
+
+mkdirp@*:
+  version "1.0.4"
+  resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e"
+  integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==
+
 mkdirp@0.x, "mkdirp@>=0.5 0", mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@^0.5.3, mkdirp@^0.5.5, mkdirp@~0.5.0:
   version "0.5.5"
   resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.5.tgz#d91cefd62d1436ca0f41620e251288d420099def"
@@ -7889,15 +8136,15 @@ multicast-dns@^6.0.1:
     dns-packet "^1.3.1"
     thunky "^1.0.2"
 
-multimatch@^2.1.0:
-  version "2.1.0"
-  resolved "https://registry.yarnpkg.com/multimatch/-/multimatch-2.1.0.tgz#9c7906a22fb4c02919e2f5f75161b4cdbd4b2a2b"
-  integrity sha1-nHkGoi+0wCkZ4vX3UWG0zb1LKis=
+multimatch@^3.0.0:
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/multimatch/-/multimatch-3.0.0.tgz#0e2534cc6bc238d9ab67e1b9cd5fcd85a6dbf70b"
+  integrity sha512-22foS/gqQfANZ3o+W7ST2x25ueHDVNWl/b9OlGcLpy/iKxjCpvcNCM51YCenUi7Mt/jAjjqv8JwZRs8YP5sRjA==
   dependencies:
-    array-differ "^1.0.0"
-    array-union "^1.0.1"
-    arrify "^1.0.0"
-    minimatch "^3.0.0"
+    array-differ "^2.0.3"
+    array-union "^1.0.2"
+    arrify "^1.0.1"
+    minimatch "^3.0.4"
 
 murmurhash-js@^1.0.0:
   version "1.0.0"
@@ -7914,6 +8161,15 @@ mute-stream@~0.0.4:
   resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.8.tgz#1630c42b2251ff81e2a283de96a5497ea92e5e0d"
   integrity sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==
 
+mz@^2.5.0:
+  version "2.7.0"
+  resolved "https://registry.yarnpkg.com/mz/-/mz-2.7.0.tgz#95008057a56cafadc2bc63dde7f9ff6955948e32"
+  integrity sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==
+  dependencies:
+    any-promise "^1.0.0"
+    object-assign "^4.0.1"
+    thenify-all "^1.0.0"
+
 nan@^2.12.1, nan@^2.13.2:
   version "2.14.2"
   resolved "https://registry.yarnpkg.com/nan/-/nan-2.14.2.tgz#f5376400695168f4cc694ac9393d0c9585eeea19"
@@ -7972,7 +8228,7 @@ node-fetch-npm@^2.0.2:
     json-parse-better-errors "^1.0.0"
     safe-buffer "^5.1.1"
 
-node-fetch@^2.6.1:
+node-fetch@^2.5.0, node-fetch@^2.6.1:
   version "2.6.1"
   resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.1.tgz#045bd323631f76ed2e2b55573394416b639a0052"
   integrity sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==
@@ -8000,22 +8256,22 @@ node-gyp@^3.8.0:
     tar "^2.0.0"
     which "1"
 
-node-gyp@^4.0.0:
-  version "4.0.0"
-  resolved "https://registry.yarnpkg.com/node-gyp/-/node-gyp-4.0.0.tgz#972654af4e5dd0cd2a19081b4b46fe0442ba6f45"
-  integrity sha512-2XiryJ8sICNo6ej8d0idXDEMKfVfFK7kekGCtJAuelGsYHQxhj13KTf95swTCN2dZ/4lTfZ84Fu31jqJEEgjWA==
+node-gyp@^5.0.2:
+  version "5.1.1"
+  resolved "https://registry.yarnpkg.com/node-gyp/-/node-gyp-5.1.1.tgz#eb915f7b631c937d282e33aed44cb7a025f62a3e"
+  integrity sha512-WH0WKGi+a4i4DUt2mHnvocex/xPLp9pYt5R6M2JdFB7pJ7Z34hveZ4nDTGTiLXCkitA9T8HFZjhinBCiVHYcWw==
   dependencies:
-    glob "^7.0.3"
-    graceful-fs "^4.1.2"
-    mkdirp "^0.5.0"
-    nopt "2 || 3"
-    npmlog "0 || 1 || 2 || 3 || 4"
-    osenv "0"
-    request "^2.87.0"
-    rimraf "2"
-    semver "~5.3.0"
-    tar "^4.4.8"
-    which "1"
+    env-paths "^2.2.0"
+    glob "^7.1.4"
+    graceful-fs "^4.2.2"
+    mkdirp "^0.5.1"
+    nopt "^4.0.1"
+    npmlog "^4.1.2"
+    request "^2.88.0"
+    rimraf "^2.6.3"
+    semver "^5.7.1"
+    tar "^4.4.12"
+    which "^1.3.1"
 
 node-int64@^0.4.0:
   version "0.4.0"
@@ -8120,6 +8376,14 @@ node-sass@^4.14.1:
   dependencies:
     abbrev "1"
 
+nopt@^4.0.1:
+  version "4.0.3"
+  resolved "https://registry.yarnpkg.com/nopt/-/nopt-4.0.3.tgz#a375cad9d02fd921278d954c2254d5aa57e15e48"
+  integrity sha512-CvaGwVMztSMJLOeXPrez7fyfObdZqNUK1cPAEzLHrTybIua9pMdmmPR5YwtfNftIOMv3DPUhFaxsZMNTQO20Kg==
+  dependencies:
+    abbrev "1"
+    osenv "^0.1.4"
+
 normalize-package-data@^2.0.0, normalize-package-data@^2.3.0, normalize-package-data@^2.3.2, normalize-package-data@^2.3.4, normalize-package-data@^2.3.5, normalize-package-data@^2.4.0, normalize-package-data@^2.5.0:
   version "2.5.0"
   resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.5.0.tgz#e66db1838b200c1dfc233225d12cb36520e234a8"
@@ -8179,14 +8443,14 @@ npm-bundled@^1.0.1:
   dependencies:
     npm-normalize-package-bin "^1.0.1"
 
-npm-lifecycle@^2.1.0:
-  version "2.1.1"
-  resolved "https://registry.yarnpkg.com/npm-lifecycle/-/npm-lifecycle-2.1.1.tgz#0027c09646f0fd346c5c93377bdaba59c6748fdf"
-  integrity sha512-+Vg6I60Z75V/09pdcH5iUo/99Q/vop35PaI99elvxk56azSVVsdsSsS/sXqKDNwbRRNN1qSxkcO45ZOu0yOWew==
+npm-lifecycle@^3.1.2:
+  version "3.1.5"
+  resolved "https://registry.yarnpkg.com/npm-lifecycle/-/npm-lifecycle-3.1.5.tgz#9882d3642b8c82c815782a12e6a1bfeed0026309"
+  integrity sha512-lDLVkjfZmvmfvpvBzA4vzee9cn+Me4orq0QF8glbswJVEbIcSNWib7qGOffolysc3teCqbbPZZkzbr3GQZTL1g==
   dependencies:
     byline "^5.0.0"
     graceful-fs "^4.1.15"
-    node-gyp "^4.0.0"
+    node-gyp "^5.0.2"
     resolve-from "^4.0.0"
     slide "^1.1.6"
     uid-number "0.0.6"
@@ -8208,7 +8472,7 @@ npm-normalize-package-bin@^1.0.0, npm-normalize-package-bin@^1.0.1:
     semver "^5.6.0"
     validate-npm-package-name "^3.0.0"
 
-npm-packlist@^1.1.12, npm-packlist@^1.4.1:
+npm-packlist@^1.4.4:
   version "1.4.8"
   resolved "https://registry.yarnpkg.com/npm-packlist/-/npm-packlist-1.4.8.tgz#56ee6cc135b9f98ad3d51c1c95da22bbb9b2ef3e"
   integrity sha512-5+AZgwru5IevF5ZdnFglB5wNlHG1AOOuw28WhUq8/8emhBmLv6jX5by4WJCh7lW0uSYZYS6DXqIsyZVIXRZU9A==
@@ -8226,31 +8490,6 @@ npm-pick-manifest@^3.0.0:
     npm-package-arg "^6.0.0"
     semver "^5.4.1"
 
-npm-registry-fetch@^3.9.0:
-  version "3.9.1"
-  resolved "https://registry.yarnpkg.com/npm-registry-fetch/-/npm-registry-fetch-3.9.1.tgz#00ff6e4e35d3f75a172b332440b53e93f4cb67de"
-  integrity sha512-VQCEZlydXw4AwLROAXWUR7QDfe2Y8Id/vpAgp6TI1/H78a4SiQ1kQrKZALm5/zxM5n4HIi+aYb+idUAV/RuY0Q==
-  dependencies:
-    JSONStream "^1.3.4"
-    bluebird "^3.5.1"
-    figgy-pudding "^3.4.1"
-    lru-cache "^5.1.1"
-    make-fetch-happen "^4.0.2"
-    npm-package-arg "^6.1.0"
-
-npm-registry-fetch@^4.0.0:
-  version "4.0.7"
-  resolved "https://registry.yarnpkg.com/npm-registry-fetch/-/npm-registry-fetch-4.0.7.tgz#57951bf6541e0246b34c9f9a38ab73607c9449d7"
-  integrity sha512-cny9v0+Mq6Tjz+e0erFAB+RYJ/AVGzkjnISiobqP8OWj9c9FLoZZu8/SPSKJWE17F1tk4018wfjV+ZbIbqC7fQ==
-  dependencies:
-    JSONStream "^1.3.4"
-    bluebird "^3.5.1"
-    figgy-pudding "^3.4.1"
-    lru-cache "^5.1.1"
-    make-fetch-happen "^5.0.0"
-    npm-package-arg "^6.1.0"
-    safe-buffer "^5.2.0"
-
 npm-run-path@^2.0.0:
   version "2.0.2"
   resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-2.0.2.tgz#35a9232dfa35d7067b4cb2ddf2357b1871536c5f"
@@ -8491,7 +8730,7 @@ os-tmpdir@^1.0.0, os-tmpdir@^1.0.1, os-tmpdir@~1.0.2:
   resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274"
   integrity sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=
 
-osenv@0, osenv@^0.1.5:
+osenv@0, osenv@^0.1.4, osenv@^0.1.5:
   version "0.1.5"
   resolved "https://registry.yarnpkg.com/osenv/-/osenv-0.1.5.tgz#85cdfafaeb28e8677f416e287592b5f3f49ea410"
   integrity sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==
@@ -8556,16 +8795,28 @@ p-map-series@^1.0.0:
   dependencies:
     p-reduce "^1.0.0"
 
-p-map@^1.1.1, p-map@^1.2.0:
+p-map@^1.1.1:
   version "1.2.0"
   resolved "https://registry.yarnpkg.com/p-map/-/p-map-1.2.0.tgz#e4e94f311eabbc8633a1e79908165fca26241b6b"
   integrity sha512-r6zKACMNhjPJMTl8KcFH4li//gkrXWfbD6feV8l6doRHlzljFWGJ2AP6iKaCJXyZmAUMOPtvbW7EXkbWO/pLEA==
 
+p-map@^2.1.0:
+  version "2.1.0"
+  resolved "https://registry.yarnpkg.com/p-map/-/p-map-2.1.0.tgz#310928feef9c9ecc65b68b17693018a665cea175"
+  integrity sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw==
+
 p-pipe@^1.2.0:
   version "1.2.0"
   resolved "https://registry.yarnpkg.com/p-pipe/-/p-pipe-1.2.0.tgz#4b1a11399a11520a67790ee5a0c1d5881d6befe9"
   integrity sha1-SxoROZoRUgpneQ7loMHViB1r7+k=
 
+p-queue@^4.0.0:
+  version "4.0.0"
+  resolved "https://registry.yarnpkg.com/p-queue/-/p-queue-4.0.0.tgz#ed0eee8798927ed6f2c2f5f5b77fdb2061a5d346"
+  integrity sha512-3cRXXn3/O0o3+eVmUroJPSj/esxoEFIm0ZOno/T+NzG/VZgPOqQ8WKmlNqubSEpZmCIngEy34unkHGg83ZIBmg==
+  dependencies:
+    eventemitter3 "^3.1.0"
+
 p-reduce@^1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/p-reduce/-/p-reduce-1.0.0.tgz#18c2b0dd936a4690a529f8231f58a0fdb6a47dfa"
@@ -8588,42 +8839,6 @@ p-waterfall@^1.0.0:
   dependencies:
     p-reduce "^1.0.0"
 
-pacote@^9.5.0:
-  version "9.5.12"
-  resolved "https://registry.yarnpkg.com/pacote/-/pacote-9.5.12.tgz#1e11dd7a8d736bcc36b375a9804d41bb0377bf66"
-  integrity sha512-BUIj/4kKbwWg4RtnBncXPJd15piFSVNpTzY0rysSr3VnMowTYgkGKcaHrbReepAkjTr8lH2CVWRi58Spg2CicQ==
-  dependencies:
-    bluebird "^3.5.3"
-    cacache "^12.0.2"
-    chownr "^1.1.2"
-    figgy-pudding "^3.5.1"
-    get-stream "^4.1.0"
-    glob "^7.1.3"
-    infer-owner "^1.0.4"
-    lru-cache "^5.1.1"
-    make-fetch-happen "^5.0.0"
-    minimatch "^3.0.4"
-    minipass "^2.3.5"
-    mississippi "^3.0.0"
-    mkdirp "^0.5.1"
-    normalize-package-data "^2.4.0"
-    npm-normalize-package-bin "^1.0.0"
-    npm-package-arg "^6.1.0"
-    npm-packlist "^1.1.12"
-    npm-pick-manifest "^3.0.0"
-    npm-registry-fetch "^4.0.0"
-    osenv "^0.1.5"
-    promise-inflight "^1.0.1"
-    promise-retry "^1.1.1"
-    protoduck "^5.0.1"
-    rimraf "^2.6.2"
-    safe-buffer "^5.1.2"
-    semver "^5.6.0"
-    ssri "^6.0.1"
-    tar "^4.4.10"
-    unique-filename "^1.1.1"
-    which "^1.3.1"
-
 pako@~1.0.5:
   version "1.0.11"
   resolved "https://registry.yarnpkg.com/pako/-/pako-1.0.11.tgz#6c9599d340d54dfd3946380252a35705a6b992bf"
@@ -9056,7 +9271,7 @@ promzard@^0.3.0:
   dependencies:
     read "1"
 
-prop-types@15.7.2, prop-types@^15.5.10, prop-types@^15.5.8, prop-types@^15.6.1, prop-types@^15.6.2, prop-types@^15.7.2:
+prop-types@15.7.2, prop-types@^15.5.10, prop-types@^15.5.8, prop-types@^15.6.2, prop-types@^15.7.2:
   version "15.7.2"
   resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.7.2.tgz#52c41e75b8c87e72b9d9360e0206b99dcbffa6c5"
   integrity sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==
@@ -9264,50 +9479,53 @@ react-chartjs-2@2.7.6:
     lodash "^4.17.4"
     prop-types "^15.5.8"
 
-react-dom@16.12.0:
-  version "16.12.0"
-  resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-16.12.0.tgz#0da4b714b8d13c2038c9396b54a92baea633fe11"
-  integrity sha512-LMxFfAGrcS3kETtQaCkTKjMiifahaMySFDn71fZUNpPHZQEzmk/GiAeIT8JSOrHB23fnuCOMruL2a8NYlw+8Gw==
+react-dom@17.0.1:
+  version "17.0.1"
+  resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-17.0.1.tgz#1de2560474ec9f0e334285662ede52dbc5426fc6"
+  integrity sha512-6eV150oJZ9U2t9svnsspTMrWNyHc6chX0KzDeAOXftRa8bNeOKTTfCJ7KorIwenkHd2xqVTBTCZd79yk/lx/Ug==
   dependencies:
     loose-envify "^1.1.0"
     object-assign "^4.1.1"
-    prop-types "^15.6.2"
-    scheduler "^0.18.0"
+    scheduler "^0.20.1"
 
 react-fast-compare@^2.0.1:
   version "2.0.4"
   resolved "https://registry.yarnpkg.com/react-fast-compare/-/react-fast-compare-2.0.4.tgz#e84b4d455b0fec113e0402c329352715196f81f9"
   integrity sha512-suNP+J1VU1MWFKcyt7RtjiSWUjvidmQSlqu+eHslq+342xCbGTYmC0mEhPCOHxlW0CywylOC1u2DFAT+bv4dBw==
 
-react-is@^16.7.0, react-is@^16.8.0, react-is@^16.8.1, react-is@^16.8.6:
+react-is@^16.6.0, react-is@^16.7.0, react-is@^16.8.0, react-is@^16.8.1, react-is@^16.8.6:
   version "16.13.1"
   resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
   integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==
 
-react-router-dom@4.3.1:
-  version "4.3.1"
-  resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-4.3.1.tgz#4c2619fc24c4fa87c9fd18f4fb4a43fe63fbd5c6"
-  integrity sha512-c/MlywfxDdCp7EnB7YfPMOfMD3tOtIjrQlj/CKfNMBxdmpJP8xcz5P/UAFn3JbnQCNUxsHyVVqllF9LhgVyFCA==
+react-router-dom@5.2.0:
+  version "5.2.0"
+  resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-5.2.0.tgz#9e65a4d0c45e13289e66c7b17c7e175d0ea15662"
+  integrity sha512-gxAmfylo2QUjcwxI63RhQ5G85Qqt4voZpUXSEqCwykV0baaOTQDR1f0PmY8AELqIyVc0NEZUj0Gov5lNGcXgsA==
   dependencies:
-    history "^4.7.2"
-    invariant "^2.2.4"
+    "@babel/runtime" "^7.1.2"
+    history "^4.9.0"
     loose-envify "^1.3.1"
-    prop-types "^15.6.1"
-    react-router "^4.3.1"
-    warning "^4.0.1"
+    prop-types "^15.6.2"
+    react-router "5.2.0"
+    tiny-invariant "^1.0.2"
+    tiny-warning "^1.0.0"
 
-react-router@^4.3.1:
-  version "4.3.1"
-  resolved "https://registry.yarnpkg.com/react-router/-/react-router-4.3.1.tgz#aada4aef14c809cb2e686b05cee4742234506c4e"
-  integrity sha512-yrvL8AogDh2X42Dt9iknk4wF4V8bWREPirFfS9gLU1huk6qK41sg7Z/1S81jjTrGHxa3B8R3J6xIkDAA6CVarg==
+react-router@5.2.0:
+  version "5.2.0"
+  resolved "https://registry.yarnpkg.com/react-router/-/react-router-5.2.0.tgz#424e75641ca8747fbf76e5ecca69781aa37ea293"
+  integrity sha512-smz1DUuFHRKdcJC0jobGo8cVbhO3x50tCL4icacOlcwDOEQPq4TMqwx3sY1TP+DvtTgz4nm3thuo7A+BK2U0Dw==
   dependencies:
-    history "^4.7.2"
-    hoist-non-react-statics "^2.5.0"
-    invariant "^2.2.4"
+    "@babel/runtime" "^7.1.2"
+    history "^4.9.0"
+    hoist-non-react-statics "^3.1.0"
     loose-envify "^1.3.1"
+    mini-create-react-context "^0.4.0"
     path-to-regexp "^1.7.0"
-    prop-types "^15.6.1"
-    warning "^4.0.1"
+    prop-types "^15.6.2"
+    react-is "^16.6.0"
+    tiny-invariant "^1.0.2"
+    tiny-warning "^1.0.0"
 
 react-transition-group@4.3.0:
   version "4.3.0"
@@ -9329,14 +9547,13 @@ react-transition-group@^4.4.0:
     loose-envify "^1.4.0"
     prop-types "^15.6.2"
 
-react@16.12.0:
-  version "16.12.0"
-  resolved "https://registry.yarnpkg.com/react/-/react-16.12.0.tgz#0c0a9c6a142429e3614834d5a778e18aa78a0b83"
-  integrity sha512-fglqy3k5E+81pA8s+7K0/T3DBCF0ZDOher1elBFzF7O6arXJgzyu/FW+COxFvAWXJoJN9KIZbT2LXlukwphYTA==
+react@17.0.1:
+  version "17.0.1"
+  resolved "https://registry.yarnpkg.com/react/-/react-17.0.1.tgz#6e0600416bd57574e3f86d92edba3d9008726127"
+  integrity sha512-lG9c9UuMHdcAexXtigOZLX8exLWkW0Ku29qPRU8uhF2R9BN96dLCt0psvzPLlHc5OWkgymP3qwTRgbnw5BKx3w==
   dependencies:
     loose-envify "^1.1.0"
     object-assign "^4.1.1"
-    prop-types "^15.6.2"
 
 read-cmd-shim@^1.0.1:
   version "1.0.5"
@@ -9913,14 +10130,6 @@ sax@^1.2.4:
   resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9"
   integrity sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==
 
-scheduler@^0.18.0:
-  version "0.18.0"
-  resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.18.0.tgz#5901ad6659bc1d8f3fdaf36eb7a67b0d6746b1c4"
-  integrity sha512-agTSHR1Nbfi6ulI0kYNK0203joW2Y5W4po4l+v03tOoiJKpTBbxpNhWDvqc/4IcOw+KLmSiQLTasZ4cab2/UWQ==
-  dependencies:
-    loose-envify "^1.1.0"
-    object-assign "^4.1.1"
-
 scheduler@^0.20.1:
   version "0.20.1"
   resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.20.1.tgz#da0b907e24026b01181ecbc75efdc7f27b5a000c"
@@ -9975,12 +10184,12 @@ selfsigned@^1.9.1:
   dependencies:
     node-forge "^0.10.0"
 
-"semver@2 || 3 || 4 || 5", "semver@2.x || 3.x || 4 || 5", semver@^5.0.1, semver@^5.3.0, semver@^5.4.1, semver@^5.5, semver@^5.5.0, semver@^5.5.1, semver@^5.6.0:
+"semver@2 || 3 || 4 || 5", "semver@2.x || 3.x || 4 || 5", semver@^5.0.1, semver@^5.3.0, semver@^5.4.1, semver@^5.5, semver@^5.5.0, semver@^5.5.1, semver@^5.6.0, semver@^5.7.0, semver@^5.7.1:
   version "5.7.1"
   resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7"
   integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==
 
-semver@^6.0.0:
+semver@^6.0.0, semver@^6.2.0:
   version "6.3.0"
   resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d"
   integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==
@@ -10096,6 +10305,13 @@ shallow-clone@^1.0.0:
     kind-of "^5.0.0"
     mixin-object "^2.0.1"
 
+shallow-clone@^3.0.0:
+  version "3.0.1"
+  resolved "https://registry.yarnpkg.com/shallow-clone/-/shallow-clone-3.0.1.tgz#8f2981ad92531f55035b01fb230769a40e02efa3"
+  integrity sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==
+  dependencies:
+    kind-of "^6.0.2"
+
 shebang-command@^1.2.0:
   version "1.2.0"
   resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea"
@@ -10630,7 +10846,7 @@ tar@^2.0.0:
     fstream "^1.0.12"
     inherits "2"
 
-tar@^4.4.10, tar@^4.4.8:
+tar@^4.4.10, tar@^4.4.12, tar@^4.4.8:
   version "4.4.13"
   resolved "https://registry.yarnpkg.com/tar/-/tar-4.4.13.tgz#43b364bc52888d555298637b10d60790254ab525"
   integrity sha512-w2VwSrBoHa5BsSyH+KxEqeQBAllHhccyMFVHtGtdMpF4W7IRWfZjFiQceJPChOeTsSDVUpER2T8FA93pr0L+QA==
@@ -10723,6 +10939,20 @@ text-extensions@^1.0.0:
   resolved "https://registry.yarnpkg.com/text-extensions/-/text-extensions-1.9.0.tgz#1853e45fee39c945ce6f6c36b2d659b5aabc2a26"
   integrity sha512-wiBrwC1EhBelW12Zy26JeOUkQ5mRu+5o8rpsJk5+2t+Y5vE7e842qtZDQ2g1NpX/29HdyFeJ4nSIhI47ENSxlQ==
 
+thenify-all@^1.0.0:
+  version "1.6.0"
+  resolved "https://registry.yarnpkg.com/thenify-all/-/thenify-all-1.6.0.tgz#1a1918d402d8fc3f98fbf234db0bcc8cc10e9726"
+  integrity sha1-GhkY1ALY/D+Y+/I02wvMjMEOlyY=
+  dependencies:
+    thenify ">= 3.1.0 < 4"
+
+"thenify@>= 3.1.0 < 4":
+  version "3.3.1"
+  resolved "https://registry.yarnpkg.com/thenify/-/thenify-3.3.1.tgz#8932e686a4066038a016dd9e2ca46add9838a95f"
+  integrity sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==
+  dependencies:
+    any-promise "^1.0.0"
+
 throat@^4.0.0:
   version "4.1.0"
   resolved "https://registry.yarnpkg.com/throat/-/throat-4.1.0.tgz#89037cbc92c56ab18926e6ba4cbb200e15672a6a"
@@ -10773,7 +11003,7 @@ tiny-invariant@^1.0.2:
   resolved "https://registry.yarnpkg.com/tiny-invariant/-/tiny-invariant-1.1.0.tgz#634c5f8efdc27714b7f386c35e6760991d230875"
   integrity sha512-ytxQvrb1cPc9WBEI/HSeYYoGD0kWnGEOR8RY6KomWLBVhqz0RgTwVO9dLrGz7dC+nN9llyI7OKAgRq8Vq4ZBSw==
 
-tiny-warning@^1.0.0, tiny-warning@^1.0.2:
+tiny-warning@^1.0.0, tiny-warning@^1.0.2, tiny-warning@^1.0.3:
   version "1.0.3"
   resolved "https://registry.yarnpkg.com/tiny-warning/-/tiny-warning-1.0.3.tgz#94a30db453df4c643d0fd566060d60a875d84754"
   integrity sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==
@@ -11081,6 +11311,11 @@ type-fest@^0.18.0:
   resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.18.0.tgz#2edfa6382d48653707344f7fccdb0443d460e8d6"
   integrity sha512-fbDukFPnJBdn2eZ3RR+5mK2slHLFd6gYHY7jna1KWWy4Yr4XysHuCdXRzy+RiG/HwG4WJat00vdC2UHky5eKiQ==
 
+type-fest@^0.3.0:
+  version "0.3.1"
+  resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.3.1.tgz#63d00d204e059474fe5e1b7c011112bbd1dc29e1"
+  integrity sha512-cUGJnCdr4STbePCgqNFbpVNCepa+kAVohJs1sLhxzdH+gnEoOd8VhbYa7pD3zZYGiURWM2xzEII3fQcRizDkYQ==
+
 type-fest@^0.6.0:
   version "0.6.0"
   resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.6.0.tgz#8d2a2370d3df886eb5c90ada1c5bf6188acf838b"
@@ -11104,10 +11339,10 @@ typedarray@^0.0.6:
   resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777"
   integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=
 
-typescript@3.7.2:
-  version "3.7.2"
-  resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.7.2.tgz#27e489b95fa5909445e9fef5ee48d81697ad18fb"
-  integrity sha512-ml7V7JfiN2Xwvcer+XAf2csGO1bPBdRbFCkYBczNZggrBZ9c7G3riSUeJmqEU5uOtXNPMhE3n+R4FA/3YOAWOQ==
+typescript@4.1.4:
+  version "4.1.4"
+  resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.1.4.tgz#f058636e2f4f83f94ddaae07b20fd5e14598432f"
+  integrity sha512-+Uru0t8qIRgjuCpiSPpfGuhHecMllk5Zsazj5LZvVsEStEjmIRRBZe+jHjGQvsgS7M1wONy2PQXd67EMyV6acg==
 
 typings-for-css-modules-loader@1.7.0:
   version "1.7.0"
@@ -11202,6 +11437,13 @@ universal-user-agent@^4.0.0:
   dependencies:
     os-name "^3.1.0"
 
+universal-user-agent@^5.0.0:
+  version "5.0.0"
+  resolved "https://registry.yarnpkg.com/universal-user-agent/-/universal-user-agent-5.0.0.tgz#a3182aa758069bf0e79952570ca757de3579c1d9"
+  integrity sha512-B5TPtzZleXyPrUMKCpEHFmVhMN6EhmJYjG5PQna9s7mXeSqGTLap4OpqLl5FCEFUI3UBmllkETwKf/db66Y54Q==
+  dependencies:
+    os-name "^3.1.0"
+
 universal-user-agent@^6.0.0:
   version "6.0.0"
   resolved "https://registry.yarnpkg.com/universal-user-agent/-/universal-user-agent-6.0.0.tgz#3381f8503b251c0d9cd21bc1de939ec9df5480ee"
@@ -11225,7 +11467,7 @@ unset-value@^1.0.0:
     has-value "^0.3.1"
     isobject "^3.0.0"
 
-upath@^1.1.1:
+upath@^1.1.1, upath@^1.2.0:
   version "1.2.0"
   resolved "https://registry.yarnpkg.com/upath/-/upath-1.2.0.tgz#8f66dbcd55a883acdae4408af8b035a5044c1894"
   integrity sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg==
@@ -11408,13 +11650,6 @@ walker@~1.0.5:
   dependencies:
     makeerror "1.0.x"
 
-warning@^4.0.1:
-  version "4.0.3"
-  resolved "https://registry.yarnpkg.com/warning/-/warning-4.0.3.tgz#16e9e077eb8a86d6af7d64aa1e05fd85b4678ca3"
-  integrity sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==
-  dependencies:
-    loose-envify "^1.0.0"
-
 watch@~0.18.0:
   version "0.18.0"
   resolved "https://registry.yarnpkg.com/watch/-/watch-0.18.0.tgz#28095476c6df7c90c963138990c0a5423eb4b986"
@@ -11719,7 +11954,7 @@ wrappy@1:
   resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
   integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=
 
-write-file-atomic@^2.0.0, write-file-atomic@^2.1.0, write-file-atomic@^2.3.0:
+write-file-atomic@^2.0.0, write-file-atomic@^2.1.0, write-file-atomic@^2.3.0, write-file-atomic@^2.4.2:
   version "2.4.3"
   resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-2.4.3.tgz#1fd2e9ae1df3e75b8d8c367443c692d4ca81f481"
   integrity sha512-GaETH5wwsX+GcnzhPgKcKjJ6M2Cq3/iZp1WyY/X1CSqrW+jVNM9Y7D8EC2sM4ZG/V8wZlSniJnCKWPmBYAucRQ==
@@ -11728,7 +11963,7 @@ write-file-atomic@^2.0.0, write-file-atomic@^2.1.0, write-file-atomic@^2.3.0:
     imurmurhash "^0.1.4"
     signal-exit "^3.0.2"
 
-write-json-file@^2.2.0, write-json-file@^2.3.0:
+write-json-file@^2.2.0:
   version "2.3.0"
   resolved "https://registry.yarnpkg.com/write-json-file/-/write-json-file-2.3.0.tgz#2b64c8a33004d54b8698c76d585a77ceb61da32f"
   integrity sha1-K2TIozAE1UuGmMdtWFp3zrYdoy8=
@@ -11740,6 +11975,18 @@ write-json-file@^2.2.0, write-json-file@^2.3.0:
     sort-keys "^2.0.0"
     write-file-atomic "^2.0.0"
 
+write-json-file@^3.2.0:
+  version "3.2.0"
+  resolved "https://registry.yarnpkg.com/write-json-file/-/write-json-file-3.2.0.tgz#65bbdc9ecd8a1458e15952770ccbadfcff5fe62a"
+  integrity sha512-3xZqT7Byc2uORAatYiP3DHUUAVEkNOswEWNs9H5KXiicRTvzYzYqKjYc4G7p+8pltvAw641lVByKVtMpf+4sYQ==
+  dependencies:
+    detect-indent "^5.0.0"
+    graceful-fs "^4.1.15"
+    make-dir "^2.1.0"
+    pify "^4.0.1"
+    sort-keys "^2.0.0"
+    write-file-atomic "^2.4.2"
+
 write-pkg@^3.1.0:
   version "3.2.0"
   resolved "https://registry.yarnpkg.com/write-pkg/-/write-pkg-3.2.0.tgz#0e178fe97820d389a8928bc79535dbe68c2cff21"
@@ -11762,13 +12009,6 @@ x2js@*:
   dependencies:
     xmldom "^0.1.19"
 
-x2js@3.2.3:
-  version "3.2.3"
-  resolved "https://registry.yarnpkg.com/x2js/-/x2js-3.2.3.tgz#b59c0c5f18d36c7391b8a1db068e2bc10132c960"
-  integrity sha512-LJNWWp+WFOdsrp2nfTuC6+erPjZSNfeHsrdWPGpesj5g0gj2WwsedsqbN33wZWf/3loQA0jIR58jrx5Cyj7G/Q==
-  dependencies:
-    xmldom "^0.1.19"
-
 xml-name-validator@^3.0.0:
   version "3.0.0"
   resolved "https://registry.yarnpkg.com/xml-name-validator/-/xml-name-validator-3.0.0.tgz#6ae73e06de4d8c6e47f9fb181f78d648ad457c6a"
@@ -11845,6 +12085,14 @@ yargs-parser@^13.1.2:
     camelcase "^5.0.0"
     decamelize "^1.2.0"
 
+yargs-parser@^15.0.1:
+  version "15.0.1"
+  resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-15.0.1.tgz#54786af40b820dcb2fb8025b11b4d659d76323b3"
+  integrity sha512-0OAMV2mAZQrs3FkNpDQcBk1x5HXb8X4twADss4S0Iuk+2dGnLOE/fRHrsYm542GduMveyA77OF4wrNJuanRCWw==
+  dependencies:
+    camelcase "^5.0.0"
+    decamelize "^1.2.0"
+
 yargs-parser@^20.2.3:
   version "20.2.4"
   resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.4.tgz#b42890f14566796f85ae8e3a25290d205f154a54"
@@ -11893,7 +12141,7 @@ yargs@^11.0.0:
     y18n "^3.2.1"
     yargs-parser "^9.0.2"
 
-yargs@^12.0.1, yargs@^12.0.4:
+yargs@^12.0.4:
   version "12.0.5"
   resolved "https://registry.yarnpkg.com/yargs/-/yargs-12.0.5.tgz#05f5997b609647b64f66b81e3b4b10a368e7ad13"
   integrity sha512-Lhz8TLaYnxq/2ObqHDql8dX8CJi97oHxrjUcYtzKbbykPtVW9WB+poxI+NM2UIzsMgNCZTIf0AQwsjK5yMAqZw==
@@ -11927,6 +12175,23 @@ yargs@^13.3.2:
     y18n "^4.0.0"
     yargs-parser "^13.1.2"
 
+yargs@^14.2.2:
+  version "14.2.3"
+  resolved "https://registry.yarnpkg.com/yargs/-/yargs-14.2.3.tgz#1a1c3edced1afb2a2fea33604bc6d1d8d688a414"
+  integrity sha512-ZbotRWhF+lkjijC/VhmOT9wSgyBQ7+zr13+YLkhfsSiTriYsMzkTUFP18pFhWwBeMa5gUc1MzbhrO6/VB7c9Xg==
+  dependencies:
+    cliui "^5.0.0"
+    decamelize "^1.2.0"
+    find-up "^3.0.0"
+    get-caller-file "^2.0.1"
+    require-directory "^2.1.1"
+    require-main-filename "^2.0.0"
+    set-blocking "^2.0.0"
+    string-width "^3.0.0"
+    which-module "^2.0.0"
+    y18n "^4.0.0"
+    yargs-parser "^15.0.1"
+
 yargs@^7.0.0:
   version "7.1.1"
   resolved "https://registry.yarnpkg.com/yargs/-/yargs-7.1.1.tgz#67f0ef52e228d4ee0d6311acede8850f53464df6"