summaryrefslogtreecommitdiffstats
path: root/sdnr/wt/odlux/apps
diff options
context:
space:
mode:
authorMichael DÜrre <michael.duerre@highstreet-technologies.com>2021-04-08 07:27:18 +0200
committerMichael DÜrre <michael.duerre@highstreet-technologies.com>2021-04-08 07:27:28 +0200
commit21e4a946cd24b8a03ea577352f0271ebf7669ffa (patch)
tree4227d8566770b75c2c25b67c764038288cacfe3d /sdnr/wt/odlux/apps
parenta252be83694ae33260d99d5371ed48c1558aa2e8 (diff)
update odlux for notification change
update due new notification protocol Issue-ID: CCSDK-3253 Signed-off-by: Michael DÜrre <michael.duerre@highstreet-technologies.com> Change-Id: Iad65459fdc18603cd1ddbd97bb2211308744bd8b
Diffstat (limited to 'sdnr/wt/odlux/apps')
-rw-r--r--sdnr/wt/odlux/apps/apiDemo/package.json12
-rw-r--r--sdnr/wt/odlux/apps/configurationApp/package.json12
-rw-r--r--sdnr/wt/odlux/apps/configurationApp/policies.json14
-rw-r--r--sdnr/wt/odlux/apps/configurationApp/src/actions/deviceActions.ts33
-rw-r--r--sdnr/wt/odlux/apps/configurationApp/src/components/uiElementReference.tsx9
-rw-r--r--sdnr/wt/odlux/apps/configurationApp/src/handlers/connectedNetworkElementsHandler.ts11
-rw-r--r--sdnr/wt/odlux/apps/configurationApp/src/handlers/viewDescriptionHandler.ts2
-rw-r--r--sdnr/wt/odlux/apps/configurationApp/src/models/uiModels.ts1
-rw-r--r--sdnr/wt/odlux/apps/configurationApp/src/pluginConfiguration.tsx57
-rw-r--r--sdnr/wt/odlux/apps/configurationApp/src/services/restServices.ts4
-rw-r--r--sdnr/wt/odlux/apps/configurationApp/src/views/configurationApplication.tsx1452
-rw-r--r--sdnr/wt/odlux/apps/configurationApp/src/views/networkElementSelector.tsx2
-rw-r--r--sdnr/wt/odlux/apps/configurationApp/src/yang/yangParser.ts30
-rw-r--r--sdnr/wt/odlux/apps/connectApp/package.json12
-rw-r--r--sdnr/wt/odlux/apps/connectApp/policies.json12
-rw-r--r--sdnr/wt/odlux/apps/connectApp/src/actions/commonNetworkElementsActions.ts70
-rw-r--r--sdnr/wt/odlux/apps/connectApp/src/components/connectionStatusLog.tsx36
-rw-r--r--sdnr/wt/odlux/apps/connectApp/src/components/networkElements.tsx73
-rw-r--r--sdnr/wt/odlux/apps/connectApp/src/components/refreshConnectionStatusLogDialog.tsx117
-rw-r--r--sdnr/wt/odlux/apps/connectApp/src/components/refreshNetworkElementsDialog.tsx117
-rw-r--r--sdnr/wt/odlux/apps/connectApp/src/handlers/connectAppRootHandler.ts2
-rw-r--r--sdnr/wt/odlux/apps/connectApp/src/handlers/networkElementsHandler.ts17
-rw-r--r--sdnr/wt/odlux/apps/connectApp/src/index.html1
-rw-r--r--sdnr/wt/odlux/apps/connectApp/src/models/guiCutTrough.ts5
-rw-r--r--sdnr/wt/odlux/apps/connectApp/src/models/networkElementConnection.ts2
-rw-r--r--sdnr/wt/odlux/apps/connectApp/src/pluginConnect.tsx29
-rw-r--r--sdnr/wt/odlux/apps/connectApp/src/services/connectService.ts115
-rw-r--r--sdnr/wt/odlux/apps/connectApp/src/views/connectView.tsx15
-rw-r--r--sdnr/wt/odlux/apps/connectApp/webpack.config.js48
-rw-r--r--sdnr/wt/odlux/apps/demoApp/package.json12
-rw-r--r--sdnr/wt/odlux/apps/demoApp/src/index.html1
-rw-r--r--sdnr/wt/odlux/apps/eventLogApp/package.json12
-rw-r--r--sdnr/wt/odlux/apps/eventLogApp/src/components/refreshEventLogDialog.tsx117
-rw-r--r--sdnr/wt/odlux/apps/eventLogApp/src/views/eventLog.tsx60
-rw-r--r--sdnr/wt/odlux/apps/faultApp/package.json12
-rw-r--r--sdnr/wt/odlux/apps/faultApp/src/components/refreshAlarmLogDialog.tsx117
-rw-r--r--sdnr/wt/odlux/apps/faultApp/src/components/refreshCurrentProblemsDialog.tsx117
-rw-r--r--sdnr/wt/odlux/apps/faultApp/src/views/faultApplication.tsx103
-rw-r--r--sdnr/wt/odlux/apps/helpApp/package.json12
-rw-r--r--sdnr/wt/odlux/apps/inventoryApp/package.json12
-rw-r--r--sdnr/wt/odlux/apps/inventoryApp/src/components/refreshInventoryDialog.tsx117
-rw-r--r--sdnr/wt/odlux/apps/inventoryApp/src/index.html3
-rw-r--r--sdnr/wt/odlux/apps/inventoryApp/src/views/dashboard.tsx80
-rw-r--r--sdnr/wt/odlux/apps/inventoryApp/src/views/treeview.tsx17
-rw-r--r--sdnr/wt/odlux/apps/linkCalculationApp/package.json12
-rw-r--r--sdnr/wt/odlux/apps/linkCalculationApp/src/views/linkCalculationComponent.tsx86
-rw-r--r--sdnr/wt/odlux/apps/maintenanceApp/package.json12
-rw-r--r--sdnr/wt/odlux/apps/maintenanceApp/src/components/refreshMaintenanceEntries.tsx117
-rw-r--r--sdnr/wt/odlux/apps/maintenanceApp/src/views/maintenenceView.tsx77
-rw-r--r--sdnr/wt/odlux/apps/mediatorApp/package.json12
-rw-r--r--sdnr/wt/odlux/apps/mediatorApp/src/components/refreshMediatorDialog.tsx117
-rw-r--r--sdnr/wt/odlux/apps/mediatorApp/src/views/mediatorServerSelection.tsx24
-rw-r--r--sdnr/wt/odlux/apps/minimumApp/package.json12
-rw-r--r--sdnr/wt/odlux/apps/networkMapApp/package.json12
-rw-r--r--sdnr/wt/odlux/apps/networkMapApp/src/components/details/linkDetails.tsx4
-rw-r--r--sdnr/wt/odlux/apps/networkMapApp/src/handlers/mapReducer.ts4
-rw-r--r--sdnr/wt/odlux/apps/networkMapApp/src/index.html4
-rw-r--r--sdnr/wt/odlux/apps/performanceHistoryApp/package.json12
58 files changed, 2513 insertions, 1094 deletions
diff --git a/sdnr/wt/odlux/apps/apiDemo/package.json b/sdnr/wt/odlux/apps/apiDemo/package.json
index 230e86aea..3805c020e 100644
--- a/sdnr/wt/odlux/apps/apiDemo/package.json
+++ b/sdnr/wt/odlux/apps/apiDemo/package.json
@@ -24,17 +24,17 @@
"@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/configurationApp/package.json b/sdnr/wt/odlux/apps/configurationApp/package.json
index d1ab86da7..16588cd18 100644
--- a/sdnr/wt/odlux/apps/configurationApp/package.json
+++ b/sdnr/wt/odlux/apps/configurationApp/package.json
@@ -24,17 +24,17 @@
"@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/configurationApp/policies.json b/sdnr/wt/odlux/apps/configurationApp/policies.json
index cd9e9fc15..91a0abf96 100644
--- a/sdnr/wt/odlux/apps/configurationApp/policies.json
+++ b/sdnr/wt/odlux/apps/configurationApp/policies.json
@@ -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
diff --git a/sdnr/wt/odlux/apps/configurationApp/src/actions/deviceActions.ts b/sdnr/wt/odlux/apps/configurationApp/src/actions/deviceActions.ts
index d6283852c..f80fbfc4d 100644
--- a/sdnr/wt/odlux/apps/configurationApp/src/actions/deviceActions.ts
+++ b/sdnr/wt/odlux/apps/configurationApp/src/actions/deviceActions.ts
@@ -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 }
diff --git a/sdnr/wt/odlux/apps/configurationApp/src/components/uiElementReference.tsx b/sdnr/wt/odlux/apps/configurationApp/src/components/uiElementReference.tsx
index 223c4cb25..b7697c880 100644
--- a/sdnr/wt/odlux/apps/configurationApp/src/components/uiElementReference.tsx
+++ b/sdnr/wt/odlux/apps/configurationApp/src/components/uiElementReference.tsx
@@ -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>
);
diff --git a/sdnr/wt/odlux/apps/configurationApp/src/handlers/connectedNetworkElementsHandler.ts b/sdnr/wt/odlux/apps/configurationApp/src/handlers/connectedNetworkElementsHandler.ts
index e6b808b80..02f2929cd 100644
--- a/sdnr/wt/odlux/apps/configurationApp/src/handlers/connectedNetworkElementsHandler.ts
+++ b/sdnr/wt/odlux/apps/configurationApp/src/handlers/connectedNetworkElementsHandler.ts
@@ -18,8 +18,10 @@
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);
+ }
+);
diff --git a/sdnr/wt/odlux/apps/configurationApp/src/handlers/viewDescriptionHandler.ts b/sdnr/wt/odlux/apps/configurationApp/src/handlers/viewDescriptionHandler.ts
index ea2036415..7a9812bfd 100644
--- a/sdnr/wt/odlux/apps/configurationApp/src/handlers/viewDescriptionHandler.ts
+++ b/sdnr/wt/odlux/apps/configurationApp/src/handlers/viewDescriptionHandler.ts
@@ -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 {
diff --git a/sdnr/wt/odlux/apps/configurationApp/src/models/uiModels.ts b/sdnr/wt/odlux/apps/configurationApp/src/models/uiModels.ts
index 9c03bdf9b..29484d812 100644
--- a/sdnr/wt/odlux/apps/configurationApp/src/models/uiModels.ts
+++ b/sdnr/wt/odlux/apps/configurationApp/src/models/uiModels.ts
@@ -220,6 +220,7 @@ export type ViewSpecification = {
"when"?: string;
"uses"?: (string[]) & { [ResolveFunction]?: (parent: string) => void };
"elements": { [name: string]: ViewElement };
+ "config": boolean;
readonly "canEdit": boolean;
}
diff --git a/sdnr/wt/odlux/apps/configurationApp/src/pluginConfiguration.tsx b/sdnr/wt/odlux/apps/configurationApp/src/pluginConfiguration.tsx
index 0cab7b793..3bc0e3968 100644
--- a/sdnr/wt/odlux/apps/configurationApp/src/pluginConfiguration.tsx
+++ b/sdnr/wt/odlux/apps/configurationApp/src/pluginConfiguration.tsx
@@ -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 (
diff --git a/sdnr/wt/odlux/apps/configurationApp/src/services/restServices.ts b/sdnr/wt/odlux/apps/configurationApp/src/services/restServices.ts
index 239a8e448..bdef64cf2 100644
--- a/sdnr/wt/odlux/apps/configurationApp/src/services/restServices.ts
+++ b/sdnr/wt/odlux/apps/configurationApp/src/services/restServices.ts
@@ -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"] &&
diff --git a/sdnr/wt/odlux/apps/configurationApp/src/views/configurationApplication.tsx b/sdnr/wt/odlux/apps/configurationApp/src/views/configurationApplication.tsx
index dbaa77874..e466dbacc 100644
--- a/sdnr/wt/odlux/apps/configurationApp/src/views/configurationApplication.tsx
+++ b/sdnr/wt/odlux/apps/configurationApp/src/views/configurationApplication.tsx
@@ -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)));
diff --git a/sdnr/wt/odlux/apps/configurationApp/src/views/networkElementSelector.tsx b/sdnr/wt/odlux/apps/configurationApp/src/views/networkElementSelector.tsx
index 5d849986d..5cac22eba 100644
--- a/sdnr/wt/odlux/apps/configurationApp/src/views/networkElementSelector.tsx
+++ b/sdnr/wt/odlux/apps/configurationApp/src/views/networkElementSelector.tsx
@@ -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),
diff --git a/sdnr/wt/odlux/apps/configurationApp/src/yang/yangParser.ts b/sdnr/wt/odlux/apps/configurationApp/src/yang/yangParser.ts
index c5cb8fb4c..2d38976d5 100644
--- a/sdnr/wt/odlux/apps/configurationApp/src/yang/yangParser.ts
+++ b/sdnr/wt/odlux/apps/configurationApp/src/yang/yangParser.ts
@@ -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,
};
diff --git a/sdnr/wt/odlux/apps/connectApp/package.json b/sdnr/wt/odlux/apps/connectApp/package.json
index 836af66ee..4fae39ccb 100644
--- a/sdnr/wt/odlux/apps/connectApp/package.json
+++ b/sdnr/wt/odlux/apps/connectApp/package.json
@@ -24,17 +24,17 @@
"@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
index 000000000..2ec73612e
--- /dev/null
+++ b/sdnr/wt/odlux/apps/connectApp/policies.json
@@ -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
diff --git a/sdnr/wt/odlux/apps/connectApp/src/actions/commonNetworkElementsActions.ts b/sdnr/wt/odlux/apps/connectApp/src/actions/commonNetworkElementsActions.ts
index a3bdc6828..a83e00239 100644
--- a/sdnr/wt/odlux/apps/connectApp/src/actions/commonNetworkElementsActions.ts
+++ b/sdnr/wt/odlux/apps/connectApp/src/actions/commonNetworkElementsActions.ts
@@ -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);
diff --git a/sdnr/wt/odlux/apps/connectApp/src/components/connectionStatusLog.tsx b/sdnr/wt/odlux/apps/connectApp/src/components/connectionStatusLog.tsx
index f2fd2937d..94b4872dd 100644
--- a/sdnr/wt/odlux/apps/connectApp/src/components/connectionStatusLog.tsx
+++ b/sdnr/wt/odlux/apps/connectApp/src/components/connectionStatusLog.tsx
@@ -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;
diff --git a/sdnr/wt/odlux/apps/connectApp/src/components/networkElements.tsx b/sdnr/wt/odlux/apps/connectApp/src/components/networkElements.tsx
index 53e10481a..84a22a99a 100644
--- a/sdnr/wt/odlux/apps/connectApp/src/components/networkElements.tsx
+++ b/sdnr/wt/odlux/apps/connectApp/src/components/networkElements.tsx
@@ -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
index 000000000..41229eae6
--- /dev/null
+++ b/sdnr/wt/odlux/apps/connectApp/src/components/refreshConnectionStatusLogDialog.tsx
@@ -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
index 000000000..a349977ab
--- /dev/null
+++ b/sdnr/wt/odlux/apps/connectApp/src/components/refreshNetworkElementsDialog.tsx
@@ -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
diff --git a/sdnr/wt/odlux/apps/connectApp/src/handlers/connectAppRootHandler.ts b/sdnr/wt/odlux/apps/connectApp/src/handlers/connectAppRootHandler.ts
index 302a981eb..c23e43924 100644
--- a/sdnr/wt/odlux/apps/connectApp/src/handlers/connectAppRootHandler.ts
+++ b/sdnr/wt/odlux/apps/connectApp/src/handlers/connectAppRootHandler.ts
@@ -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 };
diff --git a/sdnr/wt/odlux/apps/connectApp/src/handlers/networkElementsHandler.ts b/sdnr/wt/odlux/apps/connectApp/src/handlers/networkElementsHandler.ts
index 4fe858c69..b74a39427 100644
--- a/sdnr/wt/odlux/apps/connectApp/src/handlers/networkElementsHandler.ts
+++ b/sdnr/wt/odlux/apps/connectApp/src/handlers/networkElementsHandler.ts
@@ -17,10 +17,11 @@
*/
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);
});
diff --git a/sdnr/wt/odlux/apps/connectApp/src/index.html b/sdnr/wt/odlux/apps/connectApp/src/index.html
index 9c8a2063e..6f44c25f7 100644
--- a/sdnr/wt/odlux/apps/connectApp/src/index.html
+++ b/sdnr/wt/odlux/apps/connectApp/src/index.html
@@ -19,6 +19,7 @@
connectApp.register();
faultApp.register();
inventoryApp.register();
+ app("./app.tsx").configureApplication({ authentication:"basic", enablePolicy: false,});
app("./app.tsx").runApplication();
});
</script>
diff --git a/sdnr/wt/odlux/apps/connectApp/src/models/guiCutTrough.ts b/sdnr/wt/odlux/apps/connectApp/src/models/guiCutTrough.ts
index d44eea685..b9f515dc8 100644
--- a/sdnr/wt/odlux/apps/connectApp/src/models/guiCutTrough.ts
+++ b/sdnr/wt/odlux/apps/connectApp/src/models/guiCutTrough.ts
@@ -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
diff --git a/sdnr/wt/odlux/apps/connectApp/src/models/networkElementConnection.ts b/sdnr/wt/odlux/apps/connectApp/src/models/networkElementConnection.ts
index b3586d693..71eddc808 100644
--- a/sdnr/wt/odlux/apps/connectApp/src/models/networkElementConnection.ts
+++ b/sdnr/wt/odlux/apps/connectApp/src/models/networkElementConnection.ts
@@ -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;
diff --git a/sdnr/wt/odlux/apps/connectApp/src/pluginConnect.tsx b/sdnr/wt/odlux/apps/connectApp/src/pluginConnect.tsx
index f711c440c..93bed1aad 100644
--- a/sdnr/wt/odlux/apps/connectApp/src/pluginConnect.tsx
+++ b/sdnr/wt/odlux/apps/connectApp/src/pluginConnect.tsx
@@ -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
diff --git a/sdnr/wt/odlux/apps/connectApp/src/services/connectService.ts b/sdnr/wt/odlux/apps/connectApp/src/services/connectService.ts
index fbbfa68d9..5d7667a7f 100644
--- a/sdnr/wt/odlux/apps/connectApp/src/services/connectService.ts
+++ b/sdnr/wt/odlux/apps/connectApp/src/services/connectService.ts
@@ -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;
diff --git a/sdnr/wt/odlux/apps/connectApp/src/views/connectView.tsx b/sdnr/wt/odlux/apps/connectApp/src/views/connectView.tsx
index 1fa5e1909..34b1b94b6 100644
--- a/sdnr/wt/odlux/apps/connectApp/src/views/connectView.tsx
+++ b/sdnr/wt/odlux/apps/connectApp/src/views/connectView.tsx
@@ -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);
}
}
diff --git a/sdnr/wt/odlux/apps/connectApp/webpack.config.js b/sdnr/wt/odlux/apps/connectApp/webpack.config.js
index 7f36e4e17..df88a80a9 100644
--- a/sdnr/wt/odlux/apps/connectApp/webpack.config.js
+++ b/sdnr/wt/odlux/apps/connectApp/webpack.config.js
@@ -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
diff --git a/sdnr/wt/odlux/apps/demoApp/package.json b/sdnr/wt/odlux/apps/demoApp/package.json
index 9505cfbb0..7756fe328 100644
--- a/sdnr/wt/odlux/apps/demoApp/package.json
+++ b/sdnr/wt/odlux/apps/demoApp/package.json
@@ -24,17 +24,17 @@
"@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/demoApp/src/index.html b/sdnr/wt/odlux/apps/demoApp/src/index.html
index e85bcbb03..521c8902c 100644
--- a/sdnr/wt/odlux/apps/demoApp/src/index.html
+++ b/sdnr/wt/odlux/apps/demoApp/src/index.html
@@ -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>
diff --git a/sdnr/wt/odlux/apps/eventLogApp/package.json b/sdnr/wt/odlux/apps/eventLogApp/package.json
index 4e98a1756..dfcebfe63 100644
--- a/sdnr/wt/odlux/apps/eventLogApp/package.json
+++ b/sdnr/wt/odlux/apps/eventLogApp/package.json
@@ -24,17 +24,17 @@
"@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
index 000000000..7d86e9f04
--- /dev/null
+++ b/sdnr/wt/odlux/apps/eventLogApp/src/components/refreshEventLogDialog.tsx
@@ -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
diff --git a/sdnr/wt/odlux/apps/eventLogApp/src/views/eventLog.tsx b/sdnr/wt/odlux/apps/eventLogApp/src/views/eventLog.tsx
index 3d81e4e46..748c66e51 100644
--- a/sdnr/wt/odlux/apps/eventLogApp/src/views/eventLog.tsx
+++ b/sdnr/wt/odlux/apps/eventLogApp/src/views/eventLog.tsx
@@ -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) {
diff --git a/sdnr/wt/odlux/apps/faultApp/package.json b/sdnr/wt/odlux/apps/faultApp/package.json
index 70952530c..5d1439b85 100644
--- a/sdnr/wt/odlux/apps/faultApp/package.json
+++ b/sdnr/wt/odlux/apps/faultApp/package.json
@@ -24,17 +24,17 @@
"@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
index 000000000..1d1b34cf2
--- /dev/null
+++ b/sdnr/wt/odlux/apps/faultApp/src/components/refreshAlarmLogDialog.tsx
@@ -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
index 000000000..c49722077
--- /dev/null
+++ b/sdnr/wt/odlux/apps/faultApp/src/components/refreshCurrentProblemsDialog.tsx
@@ -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
diff --git a/sdnr/wt/odlux/apps/faultApp/src/views/faultApplication.tsx b/sdnr/wt/odlux/apps/faultApp/src/views/faultApplication.tsx
index 2c167c5b2..d313a2048 100644
--- a/sdnr/wt/odlux/apps/faultApp/src/views/faultApplication.tsx
+++ b/sdnr/wt/odlux/apps/faultApp/src/views/faultApplication.tsx
@@ -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));
diff --git a/sdnr/wt/odlux/apps/helpApp/package.json b/sdnr/wt/odlux/apps/helpApp/package.json
index 0a7593dcc..58e68dd03 100644
--- a/sdnr/wt/odlux/apps/helpApp/package.json
+++ b/sdnr/wt/odlux/apps/helpApp/package.json
@@ -29,17 +29,17 @@
"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
diff --git a/sdnr/wt/odlux/apps/inventoryApp/package.json b/sdnr/wt/odlux/apps/inventoryApp/package.json
index 1e11f78d0..0f6b40379 100644
--- a/sdnr/wt/odlux/apps/inventoryApp/package.json
+++ b/sdnr/wt/odlux/apps/inventoryApp/package.json
@@ -24,17 +24,17 @@
"@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
index 000000000..d2efb4e97
--- /dev/null
+++ b/sdnr/wt/odlux/apps/inventoryApp/src/components/refreshInventoryDialog.tsx
@@ -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
diff --git a/sdnr/wt/odlux/apps/inventoryApp/src/index.html b/sdnr/wt/odlux/apps/inventoryApp/src/index.html
index 75531ec1b..21f31300f 100644
--- a/sdnr/wt/odlux/apps/inventoryApp/src/index.html
+++ b/sdnr/wt/odlux/apps/inventoryApp/src/index.html
@@ -15,9 +15,10 @@
<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>
diff --git a/sdnr/wt/odlux/apps/inventoryApp/src/views/dashboard.tsx b/sdnr/wt/odlux/apps/inventoryApp/src/views/dashboard.tsx
index f5ada22ab..11427fba9 100644
--- a/sdnr/wt/odlux/apps/inventoryApp/src/views/dashboard.tsx
+++ b/sdnr/wt/odlux/apps/inventoryApp/src/views/dashboard.tsx
@@ -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
diff --git a/sdnr/wt/odlux/apps/inventoryApp/src/views/treeview.tsx b/sdnr/wt/odlux/apps/inventoryApp/src/views/treeview.tsx
index 5ba82abe7..9544861ef 100644
--- a/sdnr/wt/odlux/apps/inventoryApp/src/views/treeview.tsx
+++ b/sdnr/wt/odlux/apps/inventoryApp/src/views/treeview.tsx
@@ -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("");
}
}
diff --git a/sdnr/wt/odlux/apps/linkCalculationApp/package.json b/sdnr/wt/odlux/apps/linkCalculationApp/package.json
index 22b2a6c0a..171770603 100644
--- a/sdnr/wt/odlux/apps/linkCalculationApp/package.json
+++ b/sdnr/wt/odlux/apps/linkCalculationApp/package.json
@@ -26,18 +26,18 @@
"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",
diff --git a/sdnr/wt/odlux/apps/linkCalculationApp/src/views/linkCalculationComponent.tsx b/sdnr/wt/odlux/apps/linkCalculationApp/src/views/linkCalculationComponent.tsx
index c498379b7..61a700c71 100644
--- a/sdnr/wt/odlux/apps/linkCalculationApp/src/views/linkCalculationComponent.tsx
+++ b/sdnr/wt/odlux/apps/linkCalculationApp/src/views/linkCalculationComponent.tsx
@@ -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>
diff --git a/sdnr/wt/odlux/apps/maintenanceApp/package.json b/sdnr/wt/odlux/apps/maintenanceApp/package.json
index da5b3d288..d2980f1dc 100644
--- a/sdnr/wt/odlux/apps/maintenanceApp/package.json
+++ b/sdnr/wt/odlux/apps/maintenanceApp/package.json
@@ -25,17 +25,17 @@
"@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
index 000000000..1a00c70c4
--- /dev/null
+++ b/sdnr/wt/odlux/apps/maintenanceApp/src/components/refreshMaintenanceEntries.tsx
@@ -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
diff --git a/sdnr/wt/odlux/apps/maintenanceApp/src/views/maintenenceView.tsx b/sdnr/wt/odlux/apps/maintenanceApp/src/views/maintenenceView.tsx
index f52d46d81..6b5071d90 100644
--- a/sdnr/wt/odlux/apps/maintenanceApp/src/views/maintenenceView.tsx
+++ b/sdnr/wt/odlux/apps/maintenanceApp/src/views/maintenenceView.tsx
@@ -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;
diff --git a/sdnr/wt/odlux/apps/mediatorApp/package.json b/sdnr/wt/odlux/apps/mediatorApp/package.json
index ddce56757..0f0e74673 100644
--- a/sdnr/wt/odlux/apps/mediatorApp/package.json
+++ b/sdnr/wt/odlux/apps/mediatorApp/package.json
@@ -25,17 +25,17 @@
},
"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
index 000000000..af94f5a32
--- /dev/null
+++ b/sdnr/wt/odlux/apps/mediatorApp/src/components/refreshMediatorDialog.tsx
@@ -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
diff --git a/sdnr/wt/odlux/apps/mediatorApp/src/views/mediatorServerSelection.tsx b/sdnr/wt/odlux/apps/mediatorApp/src/views/mediatorServerSelection.tsx
index c16906ad0..0f4ebbe0b 100644
--- a/sdnr/wt/odlux/apps/mediatorApp/src/views/mediatorServerSelection.tsx
+++ b/sdnr/wt/odlux/apps/mediatorApp/src/views/mediatorServerSelection.tsx
@@ -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
+ });
+ }
}
diff --git a/sdnr/wt/odlux/apps/minimumApp/package.json b/sdnr/wt/odlux/apps/minimumApp/package.json
index 069cb138c..bcda2fb58 100644
--- a/sdnr/wt/odlux/apps/minimumApp/package.json
+++ b/sdnr/wt/odlux/apps/minimumApp/package.json
@@ -24,17 +24,17 @@
"@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/networkMapApp/package.json b/sdnr/wt/odlux/apps/networkMapApp/package.json
index 7119f1b38..cde312ce3 100644
--- a/sdnr/wt/odlux/apps/networkMapApp/package.json
+++ b/sdnr/wt/odlux/apps/networkMapApp/package.json
@@ -27,18 +27,18 @@
"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"
}
}
diff --git a/sdnr/wt/odlux/apps/networkMapApp/src/components/details/linkDetails.tsx b/sdnr/wt/odlux/apps/networkMapApp/src/components/details/linkDetails.tsx
index b2c724636..57091aebf 100644
--- a/sdnr/wt/odlux/apps/networkMapApp/src/components/details/linkDetails.tsx
+++ b/sdnr/wt/odlux/apps/networkMapApp/src/components/details/linkDetails.tsx
@@ -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}`)
}
diff --git a/sdnr/wt/odlux/apps/networkMapApp/src/handlers/mapReducer.ts b/sdnr/wt/odlux/apps/networkMapApp/src/handlers/mapReducer.ts
index b820746b5..442a5083c 100644
--- a/sdnr/wt/odlux/apps/networkMapApp/src/handlers/mapReducer.ts
+++ b/sdnr/wt/odlux/apps/networkMapApp/src/handlers/mapReducer.ts
@@ -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});
diff --git a/sdnr/wt/odlux/apps/networkMapApp/src/index.html b/sdnr/wt/odlux/apps/networkMapApp/src/index.html
index 9a7f77a29..e64c08c1e 100644
--- a/sdnr/wt/odlux/apps/networkMapApp/src/index.html
+++ b/sdnr/wt/odlux/apps/networkMapApp/src/index.html
@@ -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();
});
diff --git a/sdnr/wt/odlux/apps/performanceHistoryApp/package.json b/sdnr/wt/odlux/apps/performanceHistoryApp/package.json
index 2b69cc3c7..305fc07f4 100644
--- a/sdnr/wt/odlux/apps/performanceHistoryApp/package.json
+++ b/sdnr/wt/odlux/apps/performanceHistoryApp/package.json
@@ -27,17 +27,17 @@
"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