diff options
Diffstat (limited to 'sdnr/wt/odlux/apps/connectApp')
16 files changed, 534 insertions, 137 deletions
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 |