diff options
Diffstat (limited to 'sdnr/wt/odlux/apps/connectApp/src')
10 files changed, 564 insertions, 271 deletions
diff --git a/sdnr/wt/odlux/apps/connectApp/src/actions/tlsKeyActions.ts b/sdnr/wt/odlux/apps/connectApp/src/actions/tlsKeyActions.ts new file mode 100644 index 000000000..1da16d9ad --- /dev/null +++ b/sdnr/wt/odlux/apps/connectApp/src/actions/tlsKeyActions.ts @@ -0,0 +1,60 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2021 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 { Action } from '../../../../framework/src/flux/action'; +import { Dispatch } from '../../../../framework/src/flux/store'; +import { IApplicationStoreState } from '../../../../framework/src/store/applicationStore'; + +import { TlsKeys } from '../models/networkElementConnection'; +import { connectService } from '../services/connectService'; + +/** + * Represents the base action. + */ +export class BaseAction extends Action { } + +/** + * Represents an action causing the store to load all TLS Keys. + */ +export class LoadAllTlsKeyListAction extends BaseAction { } + +/** + * Represents an action causing the store to get all TLS Keys. + */ +export class AllTlsKeyListLoadedAction extends BaseAction { + /** + * Initialize this instance. + * + * @param gets all the tlsKey list from the database. + */ + constructor(public tlsList: TlsKeys[] | null, public error?: string) { + super(); + } +} + +/** + * Represents an asynchronous thunk action to load all tlsKeys + */ + +export const loadAllTlsKeyListAsync = () => async (dispatch: Dispatch) => { + dispatch(new LoadAllTlsKeyListAction()); + connectService.getTlsKeys().then(TlsKeyList => { + dispatch(new AllTlsKeyListLoadedAction(TlsKeyList)); + }).catch(error => { + dispatch(new AllTlsKeyListLoadedAction(null, error)); + }); +}; diff --git a/sdnr/wt/odlux/apps/connectApp/src/components/connectionStatusLog.tsx b/sdnr/wt/odlux/apps/connectApp/src/components/connectionStatusLog.tsx index 7d2f96af3..5a5ebcc45 100644 --- a/sdnr/wt/odlux/apps/connectApp/src/components/connectionStatusLog.tsx +++ b/sdnr/wt/odlux/apps/connectApp/src/components/connectionStatusLog.tsx @@ -54,7 +54,7 @@ class ConnectionStatusLogComponent extends React.Component<ConnectionStatusLogCo render(): JSX.Element { const refreshConnectionStatusLogAction = { - icon: Refresh, tooltip: 'Refresh Connection Status Log Table', ariaLabel:'refresh', onClick: () => { + icon: Refresh, tooltip: 'Refresh Connection Status Log Table',ariaLabel:'refresh', onClick: () => { this.setState({ refreshConnectionStatusLogEditorMode: RefreshConnectionStatusLogDialogMode.RefreshConnectionStatusLogTable }); diff --git a/sdnr/wt/odlux/apps/connectApp/src/components/editNetworkElementDialog.tsx b/sdnr/wt/odlux/apps/connectApp/src/components/editNetworkElementDialog.tsx index 97e6647cf..df265c23d 100644 --- a/sdnr/wt/odlux/apps/connectApp/src/components/editNetworkElementDialog.tsx +++ b/sdnr/wt/odlux/apps/connectApp/src/components/editNetworkElementDialog.tsx @@ -24,7 +24,9 @@ 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 { FormControl, InputLabel, Select, MenuItem, Typography } from '@material-ui/core'; +import { FormControl, InputLabel, Select, MenuItem, Typography, Radio, RadioGroup, Options, FormLabel, FormControlLabel } from '@material-ui/core'; +import { loadAllTlsKeyListAsync } from '../actions/tlsKeyActions'; +import { IApplicationStoreState } from '../../../../framework/src/store/applicationStore'; import { IDispatcher, connect, Connect } from '../../../../framework/src/flux/connect'; @@ -67,13 +69,13 @@ const mapDispatch = (dispatcher: IDispatcher) => ({ //make sure properties are there in case they get renamed const idProperty = propertyOf<UpdateNetworkElement>("id"); const isRequiredProperty = propertyOf<UpdateNetworkElement>("isRequired"); - + if (values.length === 2 && values.includes(idProperty as string) && values.includes(isRequiredProperty as string)) { // do not mount network element, if only isRequired is changed await dispatcher.dispatch(editNetworkElementAsyncActionCreator(element)); - } else if(!(values.length===1 &&values.includes(idProperty as string))) { //do not edit or mount element, if only id was saved into object (no changes made!) + } else if (!(values.length === 1 && values.includes(idProperty as string))) { //do not edit or mount element, if only id was saved into object (no changes made!) await dispatcher.dispatch(editNetworkElementAsyncActionCreator(element)); await dispatcher.dispatch(mountNetworkElementAsyncActionCreator(mountElement)); } @@ -81,7 +83,8 @@ const mapDispatch = (dispatcher: IDispatcher) => ({ removeNetworkElement: async (element: UpdateNetworkElement) => { await dispatcher.dispatch(removeNetworkElementAsyncActionCreator(element)); dispatcher.dispatch(removeWebUriAction(element.id)); - } + }, + getAvailableTlsKeys: async () => await dispatcher.dispatch(loadAllTlsKeyListAsync()), }); type DialogSettings = { @@ -156,26 +159,62 @@ type EditNetworkElementDialogComponentProps = Connect<undefined, typeof mapDispa mode: EditNetworkElementDialogMode; initialNetworkElement: NetworkElementConnection; onClose: () => void; + radioChecked: string }; -type EditNetworkElementDialogComponentState = NetworkElementConnection & { isNameValid: boolean, isHostSet: boolean }; +type EditNetworkElementDialogComponentState = NetworkElementConnection & { + isNameValid: boolean, + isHostSet: boolean, + isPasswordSelected: boolean, + isTlsSelected: boolean, + radioSelected: string, + showPasswordTextField: boolean, + showTlsDropdown: boolean +}; class EditNetworkElementDialogComponent extends React.Component<EditNetworkElementDialogComponentProps, EditNetworkElementDialogComponentState> { constructor(props: EditNetworkElementDialogComponentProps) { super(props); - + this.handleRadioChange = this.handleRadioChange.bind(this); this.state = { nodeId: this.props.initialNetworkElement.nodeId, isRequired: false, host: this.props.initialNetworkElement.host, port: this.props.initialNetworkElement.port, isNameValid: true, - isHostSet: true + isHostSet: true, + isPasswordSelected: true, + isTlsSelected: false, + radioSelected: '', + showPasswordTextField: true, + showTlsDropdown: false }; } + public handleRadioChange = (event: any) => { + this.setState({ + radioSelected: event.target.value, + showPasswordTextField: event.target.value === 'password', + showTlsDropdown: event.target.value === 'tlsKey' + }); + } render(): JSX.Element { const setting = settings[this.props.mode]; + let { showPasswordTextField, showTlsDropdown, radioSelected } = this.state; + radioSelected = this.state.radioSelected.length > 0 ? this.state.radioSelected : this.props.radioChecked; + + if (radioSelected === 'password') { + radioSelected = 'password'; + showPasswordTextField = true; + showTlsDropdown = false; + } else if (radioSelected === 'tlsKey') { + radioSelected = 'tlsKey'; + showPasswordTextField = false; + showTlsDropdown = true; + } + + let tlsKeysList = this.props.state.connect.availableTlsKeys ? this.props.state.connect.availableTlsKeys.tlsKeysList ? this.props.state.connect.availableTlsKeys.tlsKeysList : [] : [] + return ( <Dialog open={this.props.mode !== EditNetworkElementDialogMode.None}> <DialogTitle id="form-dialog-title" aria-label={`${setting.dialogTitle.replace(/ /g, "-").toLowerCase()}-dialog`}>{setting.dialogTitle}</DialogTitle> @@ -187,9 +226,39 @@ class EditNetworkElementDialogComponent extends React.Component<EditNetworkEleme {!this.state.isNameValid && <Typography variant="body1" color="error">Name cannot be empty.</Typography>} <TextField disabled={!setting.enableMountIdEditor} spellCheck={false} margin="dense" id="ipaddress" label="IP address" aria-label="ip adress" type="text" fullWidth value={this.state.host} onChange={(event) => { this.setState({ host: event.target.value }); }} /> {!this.state.isHostSet && <Typography variant="body1" color="error">IP Adress cannot be empty.</Typography>} + <TextField disabled={!setting.enableMountIdEditor} spellCheck={false} margin="dense" id="netconfport" label="NetConf port" aria-label="netconf port" type="number" fullWidth value={this.state.port.toString()} onChange={(event) => { this.setState({ port: +event.target.value }); }} /> {setting.enableUsernameEditor && <TextField disabled={!setting.enableUsernameEditor} spellCheck={false} margin="dense" id="username" label="Username" aria-label="username" type="text" fullWidth value={this.state.username} onChange={(event) => { this.setState({ username: event.target.value }); }} /> || null} - {setting.enableUsernameEditor && <TextField disabled={!setting.enableUsernameEditor} spellCheck={false} margin="dense" id="password" label="Password" aria-label="password" type="password" fullWidth value={this.state.password} onChange={(event) => { this.setState({ password: event.target.value }); }} /> || null} + + {setting.enableUsernameEditor && + <RadioGroup row aria-label="password-tls-key" name="password-tls-key" value={radioSelected} + onChange={this.handleRadioChange} > + <FormControlLabel value='password' control={<Radio />} label="Password" onChange={this.onRadioSelect} /> + <FormControlLabel value='tlsKey' control={<Radio />} label="TlsKey" onChange={this.onRadioSelect} /> + </RadioGroup> || null} + + {setting.enableUsernameEditor && showPasswordTextField && + <TextField disabled={!setting.enableUsernameEditor || !showPasswordTextField} spellCheck={false} margin="dense" + id="password" aria-label="password" type="password" fullWidth value={this.state.password} + onChange={(event) => { this.setState({ password: event.target.value }); }} + /> || null} + + <FormControl fullWidth disabled={!setting.enableUsernameEditor}> + {setting.enableUsernameEditor && showTlsDropdown && + <div> + <InputLabel htmlFor="pass">--Select tls-key--</InputLabel> + <Select disabled={!setting.enableUsernameEditor || !showTlsDropdown} + id="tlsKey" aria-label="tlsKey" value={this.state.tlsKey} fullWidth // displayEmpty + onChange={(event) => { this.setState({ tlsKey: event.target.value as any }); }} + inputProps={{ name: 'tlsKey', id: 'tlsKey' }} > + <MenuItem value={""} disabled >--Select tls-key--</MenuItem> + {tlsKeysList.map(tlsKey => + (<MenuItem value={tlsKey.key} key={tlsKey.key} aria-label={tlsKey.key} >{tlsKey.key}</MenuItem>))} + </Select> + </div> + } + </FormControl> + <FormControl fullWidth disabled={!setting.enableUsernameEditor}> <InputLabel htmlFor="active">Required</InputLabel> <Select aria-label="required-selection" value={this.state.isRequired || false} onChange={(event) => { @@ -212,6 +281,7 @@ class EditNetworkElementDialogComponent extends React.Component<EditNetworkEleme port: this.state.port, username: this.state.username, password: this.state.password, + tlsKey: this.state.tlsKey }); } event.preventDefault(); @@ -227,14 +297,44 @@ class EditNetworkElementDialogComponent extends React.Component<EditNetworkEleme ) } + public renderTlsKeys = () => { + try { + this.props.getAvailableTlsKeys(); + } catch (err) { + console.log(err); + } + } + + public componentDidMount() { + this.renderTlsKeys(); + } + + public onRadioSelect = (e: any) => { + if (e.target.value == 'password') { + this.setState({ isPasswordSelected: true, isTlsSelected: false }) + } else if (e.target.value == 'tlsKey') { + this.setState({ isPasswordSelected: false, isTlsSelected: true }) + } + }; + private onApply = (element: NetworkElementConnection) => { this.props.onClose && this.props.onClose(); let updateElement: UpdateNetworkElement = { id: this.state.nodeId } + if (this.state.isPasswordSelected) { + element.tlsKey = '' + } + else if (this.state.isTlsSelected) { //check here + element.password = '' + } + switch (this.props.mode) { case EditNetworkElementDialogMode.AddNewNetworkElement: element && this.props.addNewNetworkElement(element); + this.setState({ + radioSelected: '' + }); break; case EditNetworkElementDialogMode.MountNetworkElement: element && this.props.mountNetworkElement(element); @@ -247,22 +347,31 @@ class EditNetworkElementDialogComponent extends React.Component<EditNetworkEleme updateElement.isRequired = this.state.isRequired; if (this.props.initialNetworkElement.username !== this.state.username) updateElement.username = this.state.username; - if (this.props.initialNetworkElement.password !== this.state.password) + if (this.props.initialNetworkElement.password !== this.state.password && this.state.isPasswordSelected) { updateElement.password = this.state.password; + updateElement.tlsKey = ''; + } + if (this.props.initialNetworkElement.tlsKey !== this.state.tlsKey && this.state.isTlsSelected) { + updateElement.tlsKey = this.state.tlsKey; + updateElement.password = ''; + } element && this.props.editNetworkElement(updateElement, element); + this.setState({ + radioSelected: '' + }); break; case EditNetworkElementDialogMode.RemoveNetworkElement: element && this.props.removeNetworkElement(updateElement); break; } - this.setState({ password: '', username: '' }); + this.setState({ password: '', username: '', tlsKey: '' }); this.resetRequieredFields(); }; private onCancel = () => { this.props.onClose && this.props.onClose(); - this.setState({ password: '', username: '' }); + this.setState({ password: '', username: '', tlsKey: '', radioSelected: '' }); this.resetRequieredFields(); } diff --git a/sdnr/wt/odlux/apps/connectApp/src/components/networkElements.tsx b/sdnr/wt/odlux/apps/connectApp/src/components/networkElements.tsx index 73706f678..5d0757ab2 100644 --- a/sdnr/wt/odlux/apps/connectApp/src/components/networkElements.tsx +++ b/sdnr/wt/odlux/apps/connectApp/src/components/networkElements.tsx @@ -19,7 +19,7 @@ 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 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'; @@ -68,18 +68,18 @@ 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 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(); - } + if (ev.button === 1) { + setDisabled(!disabled); + ev.preventDefault(); + } }; return ( <div onMouseDown={onMouseDown} > - <MenuItem {...{...props, disabled: props.disabled && disabled }} /> - </div> + <MenuItem {...{ ...props, disabled: props.disabled && disabled }} /> + </div> ); }; @@ -119,17 +119,17 @@ export class NetworkElementsListComponent extends React.Component<NetworkElement networkElementEditorMode: EditNetworkElementDialogMode.None, refreshNetworkElementsEditorMode: RefreshNetworkElementsDialogMode.None, elementInfo: null, - elementInfoFeature:null, + elementInfoFeature: null, infoNetworkElementEditorMode: InfoNetworkElementDialogMode.None }; } - + getContextMenu(rowData: NetworkElementConnection): JSX.Element[] { 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 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>, @@ -160,6 +160,12 @@ export class NetworkElementsListComponent extends React.Component<NetworkElement render(): JSX.Element { const { classes } = this.props; const { networkElementToEdit } = this.state; + let savedRadio = "password"; + if (this.state.networkElementToEdit.password && this.state.networkElementToEdit.password.length > 0) { + savedRadio = 'password' + } else if (this.state.networkElementToEdit.tlsKey && this.state.networkElementToEdit.tlsKey.length > 0) { + savedRadio = 'tlsKey' + } // const mountUri = rowData.id && connectService.getNetworkElementUri(rowData.id); // const mountPolicy = mountUri && getAccessPolicyByUrl(mountUri); @@ -167,7 +173,7 @@ export class NetworkElementsListComponent extends React.Component<NetworkElement const canAdd = true; const addRequireNetworkElementAction = { - icon: AddIcon, tooltip: 'Add', ariaLabel:"add-element", onClick: () => { + icon: AddIcon, tooltip: 'Add', ariaLabel: "add-element", onClick: () => { this.setState({ networkElementEditorMode: EditNetworkElementDialogMode.AddNewNetworkElement, networkElementToEdit: emptyRequireNetworkElement, @@ -176,16 +182,16 @@ export class NetworkElementsListComponent extends React.Component<NetworkElement }; const refreshNetworkElementsAction = { - icon: Refresh, tooltip: 'Refresh Network Elements table', ariaLabel:'refresh', onClick: () => { + icon: Refresh, tooltip: 'Refresh Network Elements table', ariaLabel: 'refresh', onClick: () => { this.setState({ refreshNetworkElementsEditorMode: RefreshNetworkElementsDialogMode.RefreshNetworkElementsTable }); } }; - + return ( <> - <NetworkElementTable stickyHeader tableId="network-element-table" customActionButtons={[refreshNetworkElementsAction, ...canAdd ? [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 }, @@ -202,6 +208,7 @@ export class NetworkElementsListComponent extends React.Component<NetworkElement initialNetworkElement={networkElementToEdit} mode={this.state.networkElementEditorMode} onClose={this.onCloseEditNetworkElementDialog} + radioChecked={savedRadio} /> <RefreshNetworkElementsDialog mode={this.state.refreshNetworkElementsEditorMode} @@ -240,6 +247,11 @@ export class NetworkElementsListComponent extends React.Component<NetworkElement } private onOpenEditNetworkElementDialog = (event: React.MouseEvent<HTMLElement>, element: NetworkElementConnection) => { + let radioSaved; + if (element.password && element.password.length > 0) + radioSaved = 'password' + else if (element.tlsKey && element.tlsKey.length > 0) + radioSaved = 'tlsKey' this.setState({ networkElementToEdit: { nodeId: element.nodeId, @@ -248,6 +260,7 @@ export class NetworkElementsListComponent extends React.Component<NetworkElement port: element.port, username: element.username, password: element.password, + tlsKey: element.tlsKey }, networkElementEditorMode: EditNetworkElementDialogMode.EditNetworkElement }); diff --git a/sdnr/wt/odlux/apps/connectApp/src/handlers/connectAppRootHandler.ts b/sdnr/wt/odlux/apps/connectApp/src/handlers/connectAppRootHandler.ts index 81ee97a0a..dbb9b2c04 100644 --- a/sdnr/wt/odlux/apps/connectApp/src/handlers/connectAppRootHandler.ts +++ b/sdnr/wt/odlux/apps/connectApp/src/handlers/connectAppRootHandler.ts @@ -25,6 +25,7 @@ import { SetPanelAction, AddWebUriList, RemoveWebUri, SetWeburiSearchBusy } from import { PanelId } from '../models/panelId'; import { guiCutThrough } from '../models/guiCutTrough'; import { connectionStatusCountHandler, IConnectionStatusCount } from './connectionStatusCountHandler'; +import { availableTlsKeysActionHandler, IAvailableTlsKeysState } from './tlsKeyHandler'; export interface IConnectAppStoreState { networkElements: INetworkElementsState; @@ -34,6 +35,7 @@ export interface IConnectAppStoreState { elementFeatureInfo: IInfoNetworkElementFeaturesState; guiCutThrough: guiCutThroughState; connectionStatusCount: IConnectionStatusCount; + availableTlsKeys: IAvailableTlsKeysState } const currentOpenPanelHandler: IActionHandler<PanelId> = (state = null, action) => { @@ -92,7 +94,8 @@ const actionHandlers = { elementInfo: infoNetworkElementsActionHandler, elementFeatureInfo: infoNetworkElementFeaturesActionHandler, guiCutThrough: guiCutThroughHandler, - connectionStatusCount: connectionStatusCountHandler + connectionStatusCount: connectionStatusCountHandler, + availableTlsKeys: availableTlsKeysActionHandler }; export const connectAppRootHandler = combineActionHandler<IConnectAppStoreState>(actionHandlers); diff --git a/sdnr/wt/odlux/apps/connectApp/src/handlers/tlsKeyHandler.ts b/sdnr/wt/odlux/apps/connectApp/src/handlers/tlsKeyHandler.ts new file mode 100644 index 000000000..326b3cc8e --- /dev/null +++ b/sdnr/wt/odlux/apps/connectApp/src/handlers/tlsKeyHandler.ts @@ -0,0 +1,55 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2021 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 { IActionHandler } from '../../../../framework/src/flux/action'; + +import { AllTlsKeyListLoadedAction, LoadAllTlsKeyListAction } from '../actions/tlsKeyActions'; +import { TlsKeys } from '../models/networkElementConnection'; + +export interface IAvailableTlsKeysState { + tlsKeysList: TlsKeys[]; + busy: boolean; +} + +const tlsKeysStateInit: IAvailableTlsKeysState = { + tlsKeysList: [], + busy: false, +}; + +export const availableTlsKeysActionHandler: IActionHandler<IAvailableTlsKeysState> = (state = tlsKeysStateInit, action) => { + if (action instanceof LoadAllTlsKeyListAction) { + state = { + ...state, + busy: true + }; + + } else if (action instanceof AllTlsKeyListLoadedAction) { + if (!action.error && action.tlsList) { + state = { + ...state, + tlsKeysList: action.tlsList, + busy: false, + }; + } else { + state = { + ...state, + busy: false + }; + } + } + return state; +};
\ No newline at end of file diff --git a/sdnr/wt/odlux/apps/connectApp/src/index.html b/sdnr/wt/odlux/apps/connectApp/src/index.html index 6f44c25f7..35dbdf71d 100644 --- a/sdnr/wt/odlux/apps/connectApp/src/index.html +++ b/sdnr/wt/odlux/apps/connectApp/src/index.html @@ -19,7 +19,7 @@ connectApp.register(); faultApp.register(); inventoryApp.register(); - app("./app.tsx").configureApplication({ authentication:"basic", enablePolicy: false,}); + app("./app.tsx").configureApplication({ authentication:"oauth", enablePolicy: false, transportpceUrl:"http://test.de"}); app("./app.tsx").runApplication(); }); </script> diff --git a/sdnr/wt/odlux/apps/connectApp/src/models/networkElementConnection.ts b/sdnr/wt/odlux/apps/connectApp/src/models/networkElementConnection.ts index 71eddc808..89070ab39 100644 --- a/sdnr/wt/odlux/apps/connectApp/src/models/networkElementConnection.ts +++ b/sdnr/wt/odlux/apps/connectApp/src/models/networkElementConnection.ts @@ -24,6 +24,7 @@ export type NetworkElementConnection = { port: number; username?: string; password?: string; + tlsKey?: string; weburi?: string; isWebUriUnreachable?: boolean; status?: "Connected" | "mounted" | "unmounted" | "Connecting" | "Disconnected" | "idle"; @@ -47,12 +48,18 @@ export type UpdateNetworkElement = { isRequired?: boolean; username?: string; password?: string; + tlsKey?: string; } export type ConnectionStatus = { status: string } +export type TlsKeys = { + key: string +} + + /** * Checks if a object has a given propertyname, if yes, the name is returned as string. * @throws at compile time if property is not available diff --git a/sdnr/wt/odlux/apps/connectApp/src/pluginConnect.tsx b/sdnr/wt/odlux/apps/connectApp/src/pluginConnect.tsx index 461e14023..1990cc03d 100644 --- a/sdnr/wt/odlux/apps/connectApp/src/pluginConnect.tsx +++ b/sdnr/wt/odlux/apps/connectApp/src/pluginConnect.tsx @@ -88,9 +88,9 @@ export function register() { subscribe<IFormatedMessage>(["object-creation-notification", "object-deletion-notification", "attribute-value-changed-notification"], (msg => { const store = applicationApi.applicationStore; if (msg && msg.type.type === "object-creation-notification" && store) { - store.dispatch(new AddSnackbarNotification({ message: `Adding network element [${msg['node-id']}]`, options: { variant: 'info' } })); + store.dispatch(new AddSnackbarNotification({ message: `Adding network element [${msg.data['object-id-ref']}]`, 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' } })); + store.dispatch(new AddSnackbarNotification({ message: `Updating network element [${msg.data['object-id-ref']}]`, options: { variant: 'info' } })); } if (store) { store.dispatch(updateCurrentViewAsyncAction() as any).then(() => { diff --git a/sdnr/wt/odlux/apps/connectApp/src/services/connectService.ts b/sdnr/wt/odlux/apps/connectApp/src/services/connectService.ts index ed8b0f67d..427acd3ec 100644 --- a/sdnr/wt/odlux/apps/connectApp/src/services/connectService.ts +++ b/sdnr/wt/odlux/apps/connectApp/src/services/connectService.ts @@ -16,243 +16,289 @@ * ============LICENSE_END========================================================================== */ - import { requestRest } from '../../../../framework/src/services/restService'; - import { NetworkElementConnection, ConnectionStatus, UpdateNetworkElement } from '../models/networkElementConnection'; - import { convertPropertyNames, replaceUpperCase } from '../../../../framework/src/utilities/yangHelper'; - import { Result } from '../../../../framework/src/models/elasticSearch'; - - import { FeatureTopology, Topology, TopologyNode, Module } from '../models/topologyNetconf'; - import { guiCutThrough } from '../models/guiCutTrough'; - - /** - * Represents a web api accessor service for all Network Elements actions. - */ - class ConnectService { - public getNetworkElementUri = (nodeId: string) => '/rests/data/network-topology:network-topology/topology=topology-netconf/node=' + nodeId; - public getNetworkElementConnectDataProviderUri = (operation: "create" | "update" | "delete" ) => `/rests/operations/data-provider:${operation}-network-element-connection`; - public getAllWebUriExtensionsForNetworkElementListUri = (nodeId: string) => this.getNetworkElementUri(nodeId) + '/yang-ext:mount/core-model:network-element'; - public getNetworkElementYangLibraryFeature = (nodeId: string) => '/rests/data/network-topology:network-topology/topology=topology-netconf/node=' + nodeId + '/yang-ext:mount/ietf-yang-library:yang-library?content=nonconfig' - - /** - * Inserts a network elements. - */ - public async createNetworkElement(element: NetworkElementConnection): Promise<NetworkElementConnection | null> { - const path = this.getNetworkElementConnectDataProviderUri("create") ; - const result = await requestRest<NetworkElementConnection>(path, { - method: "POST", body: JSON.stringify(convertPropertyNames({ "data-provider:input": element }, replaceUpperCase)) - }); - return result || null; - } - - /** - * Updates a network element. +import { requestRest } from '../../../../framework/src/services/restService'; +import { NetworkElementConnection, ConnectionStatus, UpdateNetworkElement } from '../models/networkElementConnection'; +import { TlsKeys } from '../models/networkElementConnection' +import { convertPropertyNames, replaceUpperCase } from '../../../../framework/src/utilities/yangHelper'; +import { Result } from '../../../../framework/src/models/elasticSearch'; + +import { FeatureTopology, Topology, TopologyNode, Module } from '../models/topologyNetconf'; +import { guiCutThrough } from '../models/guiCutTrough'; + +/** +* Represents a web api accessor service for all Network Elements actions. +*/ +class ConnectService { + public getNetworkElementUri = (nodeId: string) => '/rests/data/network-topology:network-topology/topology=topology-netconf/node=' + nodeId; + public getNetworkElementConnectDataProviderUri = (operation: "create" | "update" | "delete") => `/rests/operations/data-provider:${operation}-network-element-connection`; + public getAllWebUriExtensionsForNetworkElementListUri = (nodeId: string) => this.getNetworkElementUri(nodeId) + '/yang-ext:mount/core-model:network-element'; + public getNetworkElementYangLibraryFeature = (nodeId: string) => '/rests/data/network-topology:network-topology/topology=topology-netconf/node=' + nodeId + '/yang-ext:mount/ietf-yang-library:yang-library?content=nonconfig' + + /** + * Inserts a network elements. */ - public async updateNetworkElement(element: UpdateNetworkElement): Promise<NetworkElementConnection | null> { - const path = this.getNetworkElementConnectDataProviderUri("update"); - const result = await requestRest<NetworkElementConnection>(path, { - method: "POST", body: JSON.stringify(convertPropertyNames({ "data-provider:input": element }, replaceUpperCase)) - }); - return result || null; - } - - /** - * Deletes a network element. - */ - public async deleteNetworkElement(element: UpdateNetworkElement): Promise<NetworkElementConnection | null> { - const query = { - "id": element.id - }; - const path = this.getNetworkElementConnectDataProviderUri("delete"); - const result = await requestRest<NetworkElementConnection>(path, { - method: "POST", body: JSON.stringify(convertPropertyNames({ "data-provider:input": query }, replaceUpperCase)) - }); - return result || null; - } - - /** Mounts network element. */ - public async mountNetworkElement(networkElement: NetworkElementConnection): Promise<boolean> { - const path = this.getNetworkElementUri(networkElement.nodeId); - const mountXml = [ - '<node xmlns="urn:TBD:params:xml:ns:yang:network-topology">', - `<node-id>${networkElement.nodeId}</node-id>`, - `<host xmlns="urn:opendaylight:netconf-node-topology">${networkElement.host}</host>`, - `<port xmlns="urn:opendaylight:netconf-node-topology">${networkElement.port}</port>`, - `<username xmlns="urn:opendaylight:netconf-node-topology">${networkElement.username}</username>`, - `<password xmlns="urn:opendaylight:netconf-node-topology">${networkElement.password}</password>`, - ' <tcp-only xmlns="urn:opendaylight:netconf-node-topology">false</tcp-only>', - - ' <!-- non-mandatory fields with default values, you can safely remove these if you do not wish to override any of these values-->', - ' <reconnect-on-changed-schema xmlns="urn:opendaylight:netconf-node-topology">false</reconnect-on-changed-schema>', - ' <connection-timeout-millis xmlns="urn:opendaylight:netconf-node-topology">20000</connection-timeout-millis>', - ' <max-connection-attempts xmlns="urn:opendaylight:netconf-node-topology">100</max-connection-attempts>', - ' <between-attempts-timeout-millis xmlns="urn:opendaylight:netconf-node-topology">2000</between-attempts-timeout-millis>', - ' <sleep-factor xmlns="urn:opendaylight:netconf-node-topology">1.5</sleep-factor>', - - ' <!-- keepalive-delay set to 0 turns off keepalives-->', - ' <keepalive-delay xmlns="urn:opendaylight:netconf-node-topology">120</keepalive-delay>', - '</node>'].join(''); - - try { - const result = await requestRest<string>(path, { - method: 'PUT', - headers: { - 'Content-Type': 'application/xml', - 'Accept': 'application/xml' - }, - body: mountXml - }); - // expect an empty answer - return result !== null; - } catch { - return false; - } - }; - - /** Unmounts a network element by its id. */ - public async unmountNetworkElement(nodeId: string): Promise<boolean> { - const path = this.getNetworkElementUri(nodeId); - - try { - const result = await requestRest<string>(path, { - method: 'DELETE', - headers: { - 'Content-Type': 'application/xml', - 'Accept': 'application/xml' - }, - }); - // expect an empty answer - return result !== null; - - } catch { - return false; - } - }; - - /** Yang capabilities of the selected network elements. */ - public async infoNetworkElement(nodeId: string): Promise<TopologyNode | null> { - const path = this.getNetworkElementUri(nodeId); - const topologyRequestPomise = requestRest<Topology>(path, { method: "GET" }); - - return topologyRequestPomise && topologyRequestPomise.then(result => { - return result && result["network-topology:node"] && result["network-topology:node"][0] || null; - }); - } - - - /** Yang features of the selected network element module. */ - public async infoNetworkElementFeatures(nodeId: string): Promise<Module[] | null | undefined> { - const path = this.getNetworkElementYangLibraryFeature(nodeId); - const topologyRequestPomise = requestRest<FeatureTopology>(path, { method: "GET" }); - - return topologyRequestPomise && topologyRequestPomise.then(result => { - const resultFinal = result && result['ietf-yang-library:yang-library'] - && result["ietf-yang-library:yang-library"]["module-set"] && - result["ietf-yang-library:yang-library"]["module-set"][0] && - result["ietf-yang-library:yang-library"]["module-set"][0]['module'] || null; - return resultFinal; - }); - } - - - - /** - * Get the connection state of the network element. + public async createNetworkElement(element: NetworkElementConnection): Promise<NetworkElementConnection | null> { + const path = this.getNetworkElementConnectDataProviderUri("create"); + const result = await requestRest<NetworkElementConnection>(path, { + method: "POST", body: JSON.stringify(convertPropertyNames({ "data-provider:input": element }, replaceUpperCase)) + }); + return result || null; + } + + /** + * Updates a network element. + */ + public async updateNetworkElement(element: UpdateNetworkElement): Promise<NetworkElementConnection | null> { + const path = this.getNetworkElementConnectDataProviderUri("update"); + const result = await requestRest<NetworkElementConnection>(path, { + method: "POST", body: JSON.stringify(convertPropertyNames({ "data-provider:input": element }, replaceUpperCase)) + }); + return result || null; + } + + /** + * Deletes a network element. */ - public async getNetworkElementConnectionStatus(element: string): Promise<(ConnectionStatus)[] | null> { - const path = `/rests/operations/data-provider:read-network-element-connection-list`; - const query = { - "data-provider:input": { - "filter": [{ - "property": "node-id", - "filtervalue": element - }], - "pagination": { - "size": 20, - "page": 1 - } - } - } - const result = await requestRest<Result<ConnectionStatus>>(path, { method: "POST", body: JSON.stringify(query) }); - return result && result["data-provider:output"] && result["data-provider:output"].data && result["data-provider:output"].data.map(ne => ({ - status: ne.status - })) || null; - } - - public async getAllWebUriExtensionsForNetworkElementListAsync(neList: string[]): Promise<(guiCutThrough)[]> { - const path = `/rests/operations/data-provider:read-gui-cut-through-entry`; - let webUriList: guiCutThrough[] = [] - const query = { - "data-provider:input": { - "filter": [{ - "property": "id", - "filtervalues": neList - }], - "pagination": { - "size": 20, - "page": 1 - } - } - } - - const result = await requestRest<Result<guiCutThrough>>(path, { method: "POST", body: JSON.stringify(query) }); - const resultData = result && result["data-provider:output"] && result["data-provider:output"].data; - neList.forEach(nodeId => { - let entryNotFound = true; - if (resultData) { - const BreakException = {}; - try { - resultData.forEach(entry => { - if (entry.id == nodeId) { - entryNotFound = false; - if (entry.weburi) { - webUriList.push({ id: nodeId, weburi: entry.weburi }); - } else { - webUriList.push({ id: nodeId, weburi: undefined }); - } - throw BreakException; - } - }); - } catch (e) {} - } - if (entryNotFound) - webUriList.push({ id: nodeId, weburi: undefined }); - }); - return webUriList; - } - - // public async getAllWebUriExtensionsForNetworkElementListAsync(ne: string[]): Promise<(guiCutThrough)[] | null> { - - // let promises: any[] = []; - // let webUris: guiCutThrough[] = [] - - // ne.forEach(nodeId => { - // const path = this.getAllWebUriExtensionsForNetworkElementListUri(nodeId); - - // // add search request to array - // promises.push(requestRest<any>(path, { method: "GET" }) - // .then(result => { - // if (result != null && result['core-model:network-element'] && result['core-model:network-element'].extension) { - // const webUri = result['core-model:network-element'].extension.find((item: any) => item['value-name'] === "webUri") - // if (webUri) { - // webUris.push({ weburi: webUri.value, id: nodeId }); - // } else { - // webUris.push({ weburi: undefined, id: nodeId }); - // } - // } else { - // webUris.push({ weburi: undefined, id: nodeId }); - // } - // }) - // .catch(error => { - // webUris.push({ weburi: undefined, id: nodeId }); - // })) - // }) - // // wait until all promises are done and return weburis - // return Promise.all(promises).then(result => { return webUris }); - // } - - } - - - - export const connectService = new ConnectService(); -
\ No newline at end of file + public async deleteNetworkElement(element: UpdateNetworkElement): Promise<NetworkElementConnection | null> { + const query = { + "id": element.id + }; + const path = this.getNetworkElementConnectDataProviderUri("delete"); + const result = await requestRest<NetworkElementConnection>(path, { + method: "POST", body: JSON.stringify(convertPropertyNames({ "data-provider:input": query }, replaceUpperCase)) + }); + return result || null; + } + + /** Mounts network element. */ + public async mountNetworkElement(networkElement: NetworkElementConnection): Promise<boolean> { + const path = this.getNetworkElementUri(networkElement.nodeId); + const mountXml = [ + '<node xmlns="urn:TBD:params:xml:ns:yang:network-topology">', + `<node-id>${networkElement.nodeId}</node-id>`, + `<host xmlns="urn:opendaylight:netconf-node-topology">${networkElement.host}</host>`, + `<port xmlns="urn:opendaylight:netconf-node-topology">${networkElement.port}</port>`, + `<username xmlns="urn:opendaylight:netconf-node-topology">${networkElement.username}</username>`, + `<password xmlns="urn:opendaylight:netconf-node-topology">${networkElement.password}</password>`, + ' <tcp-only xmlns="urn:opendaylight:netconf-node-topology">false</tcp-only>', + + ' <!-- non-mandatory fields with default values, you can safely remove these if you do not wish to override any of these values-->', + ' <reconnect-on-changed-schema xmlns="urn:opendaylight:netconf-node-topology">false</reconnect-on-changed-schema>', + ' <connection-timeout-millis xmlns="urn:opendaylight:netconf-node-topology">20000</connection-timeout-millis>', + ' <max-connection-attempts xmlns="urn:opendaylight:netconf-node-topology">100</max-connection-attempts>', + ' <between-attempts-timeout-millis xmlns="urn:opendaylight:netconf-node-topology">2000</between-attempts-timeout-millis>', + ' <sleep-factor xmlns="urn:opendaylight:netconf-node-topology">1.5</sleep-factor>', + + ' <!-- keepalive-delay set to 0 turns off keepalives-->', + ' <keepalive-delay xmlns="urn:opendaylight:netconf-node-topology">120</keepalive-delay>', + '</node>'].join(''); + + const tlsXml = [ + '<node xmlns="urn:TBD:params:xml:ns:yang:network-topology">', + `<node-id>${networkElement.nodeId}</node-id>`, + '<key-based xmlns="urn:opendaylight:netconf-node-topology">', + `<key-id xmlns="urn:opendaylight:netconf-node-topology">${networkElement.tlsKey}</key-id>`, + `<username xmlns="urn:opendaylight:netconf-node-topology">${networkElement.username}</username>`, + '</key-based>', + `<host xmlns="urn:opendaylight:netconf-node-topology">${networkElement.host}</host>`, + `<port xmlns="urn:opendaylight:netconf-node-topology">${networkElement.port}</port>`, + '<tcp-only xmlns="urn:opendaylight:netconf-node-topology">false</tcp-only>', + '<protocol xmlns="urn:opendaylight:netconf-node-topology">', + '<name xmlns="urn:opendaylight:netconf-node-topology">TLS</name>', + ' </protocol>', + '<max-connection-attempts xmlns="urn:opendaylight:netconf-node-topology">2</max-connection-attempts>', + '</node>'].join('') + let bodyXml; + if (networkElement.password) { + bodyXml = mountXml + } + else { + bodyXml = tlsXml + } + + try { + const result = await requestRest<string>(path, { + method: 'PUT', + headers: { + 'Content-Type': 'application/xml', + 'Accept': 'application/xml' + }, + body: bodyXml + }); + // expect an empty answer + return result !== null; + } catch { + return false; + } + }; + + /** Unmounts a network element by its id. */ + public async unmountNetworkElement(nodeId: string): Promise<boolean> { + const path = this.getNetworkElementUri(nodeId); + + try { + const result = await requestRest<string>(path, { + method: 'DELETE', + headers: { + 'Content-Type': 'application/xml', + 'Accept': 'application/xml' + }, + }); + // expect an empty answer + return result !== null; + + } catch { + return false; + } + }; + + /** Yang capabilities of the selected network elements. */ + public async infoNetworkElement(nodeId: string): Promise<TopologyNode | null> { + const path = this.getNetworkElementUri(nodeId); + const topologyRequestPomise = requestRest<Topology>(path, { method: "GET" }); + + return topologyRequestPomise && topologyRequestPomise.then(result => { + return result && result["network-topology:node"] && result["network-topology:node"][0] || null; + }); + } + + + /** Yang features of the selected network element module. */ + public async infoNetworkElementFeatures(nodeId: string): Promise<Module[] | null | undefined> { + const path = this.getNetworkElementYangLibraryFeature(nodeId); + const topologyRequestPomise = requestRest<FeatureTopology>(path, { method: "GET" }); + + return topologyRequestPomise && topologyRequestPomise.then(result => { + const resultFinal = result && result['ietf-yang-library:yang-library'] + && result["ietf-yang-library:yang-library"]["module-set"] && + result["ietf-yang-library:yang-library"]["module-set"][0] && + result["ietf-yang-library:yang-library"]["module-set"][0]['module'] || null; + return resultFinal; + }); + } + + + + /** + * Get the connection state of the network element. + */ + public async getNetworkElementConnectionStatus(element: string): Promise<(ConnectionStatus)[] | null> { + const path = `/rests/operations/data-provider:read-network-element-connection-list`; + const query = { + "data-provider:input": { + "filter": [{ + "property": "node-id", + "filtervalue": element + }], + "pagination": { + "size": 20, + "page": 1 + } + } + } + const result = await requestRest<Result<ConnectionStatus>>(path, { method: "POST", body: JSON.stringify(query) }); + return result && result["data-provider:output"] && result["data-provider:output"].data && result["data-provider:output"].data.map(ne => ({ + status: ne.status + })) || null; + } + + /** + * Gets all available tlsKeys. + */ + + public async getTlsKeys(): Promise<(TlsKeys)[] | null> { + const path = '/rests/operations/data-provider:read-tls-key-entry'; + const query = { + "data-provider:input": { + "filter": [], + "sortorder": [], + "pagination": { + "size": 20, + "page": 1 + } + } + }; + + const result = await requestRest<Result<string>>(path, { method: "POST", body: JSON.stringify(query) }); + return result && result["data-provider:output"] && result["data-provider:output"].data && result["data-provider:output"].data.map(ne => ({ + key: ne + })) || null; + } + + public async getAllWebUriExtensionsForNetworkElementListAsync(neList: string[]): Promise<(guiCutThrough)[]> { + const path = `/rests/operations/data-provider:read-gui-cut-through-entry`; + let webUriList: guiCutThrough[] = [] + const query = { + "data-provider:input": { + "filter": [{ + "property": "id", + "filtervalues": neList + }], + "pagination": { + "size": 20, + "page": 1 + } + } + } + + const result = await requestRest<Result<guiCutThrough>>(path, { method: "POST", body: JSON.stringify(query) }); + const resultData = result && result["data-provider:output"] && result["data-provider:output"].data; + neList.forEach(nodeId => { + let entryNotFound = true; + if (resultData) { + const BreakException = {}; + try { + resultData.forEach(entry => { + if (entry.id == nodeId) { + entryNotFound = false; + if (entry.weburi) { + webUriList.push({ id: nodeId, weburi: entry.weburi }); + } else { + webUriList.push({ id: nodeId, weburi: undefined }); + } + throw BreakException; + } + }); + } catch (e) { } + } + if (entryNotFound) + webUriList.push({ id: nodeId, weburi: undefined }); + }); + return webUriList; + } + + // public async getAllWebUriExtensionsForNetworkElementListAsync(ne: string[]): Promise<(guiCutThrough)[] | null> { + + // let promises: any[] = []; + // let webUris: guiCutThrough[] = [] + + // ne.forEach(nodeId => { + // const path = this.getAllWebUriExtensionsForNetworkElementListUri(nodeId); + + // // add search request to array + // promises.push(requestRest<any>(path, { method: "GET" }) + // .then(result => { + // if (result != null && result['core-model:network-element'] && result['core-model:network-element'].extension) { + // const webUri = result['core-model:network-element'].extension.find((item: any) => item['value-name'] === "webUri") + // if (webUri) { + // webUris.push({ weburi: webUri.value, id: nodeId }); + // } else { + // webUris.push({ weburi: undefined, id: nodeId }); + // } + // } else { + // webUris.push({ weburi: undefined, id: nodeId }); + // } + // }) + // .catch(error => { + // webUris.push({ weburi: undefined, id: nodeId }); + // })) + // }) + // // wait until all promises are done and return weburis + // return Promise.all(promises).then(result => { return webUris }); + // } + +} + + + +export const connectService = new ConnectService(); |