diff options
Diffstat (limited to 'sdnr/wt-odlux/odlux/apps/maintenanceApp/src')
12 files changed, 987 insertions, 0 deletions
diff --git a/sdnr/wt-odlux/odlux/apps/maintenanceApp/src/actions/maintenenceActions.ts b/sdnr/wt-odlux/odlux/apps/maintenanceApp/src/actions/maintenenceActions.ts new file mode 100644 index 000000000..740abff85 --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/maintenanceApp/src/actions/maintenenceActions.ts @@ -0,0 +1,77 @@ +/* eslint-disable @typescript-eslint/no-unused-expressions */ +/** + * ============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 { AddSnackbarNotification } from '../../../../framework/src/actions/snackbarActions'; +import { Action } from '../../../../framework/src/flux/action'; +import { Dispatch } from '../../../../framework/src/flux/store'; + +import { maintenanceEntriesReloadAction } from '../handlers/maintenanceEntriesHandler'; +import { MaintenanceEntry, spoofSymbol } from '../models/maintenanceEntryType'; +import { maintenenceService } from '../services/maintenenceService'; + +export class BaseAction extends Action { } + +export class LoadAllMainteneceEntriesAction extends BaseAction { } + +export class AllMainteneceEntriesLoadedAction extends BaseAction { + + constructor(public maintenenceEntries: MaintenanceEntry[] | null) { + super(); + + } +} + + +export class UpdateMaintenanceEntry extends BaseAction { + constructor(public maintenenceEntry: MaintenanceEntry) { + super(); + } +} + +/** Represents an async thunk action creator to add an element to the maintenence entries. */ +export const addOrUpdateMaintenenceEntryAsyncActionCreator = (entry: MaintenanceEntry) => (dispatch: Dispatch) => { + maintenenceService.writeMaintenenceEntry(entry).then(result => { + result && window.setTimeout(() => { + // dispatch(loadAllMountedNetworkElementsAsync); + dispatch(new UpdateMaintenanceEntry(entry)); + dispatch(new AddSnackbarNotification({ message: `Successfully ${result && result.created ? 'created' : 'updated'} maintenance settings for [${entry.nodeId}]`, options: { variant: 'success' } })); + }, 900); + dispatch(maintenanceEntriesReloadAction); + }); +}; + +/** Represents an async thunk action creator to delete an element from the maintenence entries. */ +export const removeFromMaintenenceEntrysAsyncActionCreator = (entry: MaintenanceEntry) => (dispatch: Dispatch) => { + maintenenceService.deleteMaintenenceEntry(entry).then(result => { + result && window.setTimeout(() => { + dispatch(new UpdateMaintenanceEntry({ + [spoofSymbol]: true, + mId: entry.mId, + nodeId: entry.nodeId, + description: '', + start: '', + end: '', + active: false, + })); + dispatch(new AddSnackbarNotification({ message: `Successfully removed [${entry.nodeId}]`, options: { variant: 'success' } })); + }, 900); + dispatch(maintenanceEntriesReloadAction); + }); +}; + +// Hint: since there is no notification of changed required network elements, this code is not aware of changes caused outiside of this browser.
\ No newline at end of file diff --git a/sdnr/wt-odlux/odlux/apps/maintenanceApp/src/assets/icons/maintenanceAppIcon.svg b/sdnr/wt-odlux/odlux/apps/maintenanceApp/src/assets/icons/maintenanceAppIcon.svg new file mode 100644 index 000000000..8b99a5e7f --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/maintenanceApp/src/assets/icons/maintenanceAppIcon.svg @@ -0,0 +1,50 @@ +<!-- highstreet technologies GmbH colour scheme + Grey #565656 + LBlue #36A9E1 + DBlue #246DA2 + Green #003F2C / #006C4B + Yellw #C8D400 + Red #D81036 +--> + +<svg version="1.0" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 684.000000 684.000000"> + +<g transform="translate(0.000000,684.000000) scale(0.100000,-0.100000)"> + +<path fill="#565656" d="M3044 6693 c-12 -2 -33 -17 -48 -32 -35 -37 -55 -136 -96 -476 -17 +-142 -32 -260 -33 -260 -1 -1 -60 -19 -132 -40 -243 -71 -406 -139 -600 -250 +-99 -57 -120 -66 -138 -57 -12 6 -124 91 -249 189 -124 98 -252 193 -283 212 +-57 34 -57 34 -89 17 -81 -43 -421 -374 -513 -500 -24 -32 -43 -67 -43 -78 0 +-36 98 -173 337 -472 57 -70 103 -132 103 -138 0 -5 -17 -35 -39 -66 -98 -144 +-275 -566 -301 -715 -14 -78 26 -65 -335 -112 -384 -51 -423 -62 -447 -125 -5 +-14 -9 -179 -9 -376 0 -414 0 -416 78 -433 43 -10 213 -35 518 -77 183 -25 +167 -12 221 -182 75 -238 160 -433 278 -637 l35 -61 -175 -219 c-206 -259 +-274 -357 -274 -393 0 -22 50 -76 258 -282 142 -141 275 -269 295 -284 l36 +-28 38 23 c62 38 177 124 380 286 l193 153 92 -57 c168 -102 383 -193 633 +-269 66 -19 123 -39 127 -43 9 -8 19 -76 53 -361 14 -113 33 -243 42 -289 23 +-119 0 -114 455 -113 237 0 373 4 386 11 44 23 61 101 106 485 l32 269 149 46 +c227 71 395 139 395 160 0 5 -125 127 -277 272 -194 186 -283 263 -297 262 +-12 -1 -75 -11 -141 -23 -318 -56 -700 -29 -973 69 -278 101 -476 226 -685 +435 -275 275 -445 609 -504 988 -23 152 -23 432 1 586 60 394 215 705 493 984 +166 169 314 278 498 368 292 143 539 191 912 176 194 -8 297 -24 446 -72 485 +-156 879 -500 1098 -959 134 -278 196 -617 170 -918 -6 -73 -21 -186 -33 -251 +l-22 -119 274 -268 c268 -261 275 -268 292 -248 23 27 88 198 133 351 31 104 +41 127 59 132 11 3 131 19 266 36 402 50 463 65 477 118 5 15 8 192 8 393 0 +353 -1 368 -20 389 -36 40 -79 49 -469 100 -132 18 -249 36 -261 40 -18 7 -29 +33 -59 137 -66 223 -139 392 -292 669 l-28 51 126 159 c193 246 265 343 294 +399 l27 52 -31 45 c-101 147 -505 538 -555 538 -30 0 -234 -146 -506 -362 +l-104 -82 -51 28 c-244 136 -390 201 -606 271 -63 21 -136 46 -161 57 l-46 19 +-36 297 c-50 403 -58 430 -136 452 -34 9 -671 12 -717 3z"/> + +<path fill="#006C4B" d="M3080 4653 c-77 -8 -195 -35 -232 -54 -21 -10 -45 -30 -54 -44 -15 +-23 -15 -27 0 -49 22 -35 111 -101 277 -207 247 -158 343 -239 384 -326 35 +-74 -5 -237 -108 -438 -40 -77 -68 -116 -122 -170 -77 -77 -194 -145 -249 +-145 -39 0 -127 48 -339 184 -194 124 -291 176 -330 176 -60 0 -105 -86 -86 +-167 28 -127 262 -492 423 -662 101 -106 236 -191 333 -211 27 -5 138 -14 248 +-20 372 -20 506 -66 689 -240 87 -83 629 -652 1291 -1355 181 -192 439 -465 +573 -607 244 -255 245 -256 281 -251 220 32 406 139 512 295 50 73 98 174 106 +222 5 32 0 42 -53 108 -113 140 -296 342 -1073 1183 -513 554 -980 1066 -1074 +1178 -117 137 -174 252 -196 394 -6 39 -15 171 -21 294 -6 123 -15 257 -20 +297 -28 207 -209 414 -464 532 -97 45 -172 66 -286 80 -78 9 -330 11 -410 3z"/> +</g> +</svg> diff --git a/sdnr/wt-odlux/odlux/apps/maintenanceApp/src/components/editMaintenenceEntryDialog.tsx b/sdnr/wt-odlux/odlux/apps/maintenanceApp/src/components/editMaintenenceEntryDialog.tsx new file mode 100644 index 000000000..9ab147ca7 --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/maintenanceApp/src/components/editMaintenenceEntryDialog.tsx @@ -0,0 +1,207 @@ +/* eslint-disable @typescript-eslint/no-unused-expressions */ +/** + * ============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 '@mui/material/Button'; +import Dialog from '@mui/material/Dialog'; +import DialogActions from '@mui/material/DialogActions'; +import DialogContent from '@mui/material/DialogContent'; +import DialogContentText from '@mui/material/DialogContentText'; +import DialogTitle from '@mui/material/DialogTitle'; +import TextField from '@mui/material/TextField'; + +import { connect, Connect, IDispatcher } from '../../../../framework/src/flux/connect'; + +import { FormControl, InputLabel, MenuItem, Select, Typography } from '@mui/material'; +import { + addOrUpdateMaintenenceEntryAsyncActionCreator, + removeFromMaintenenceEntrysAsyncActionCreator, +} from '../actions/maintenenceActions'; +import { MaintenanceEntry } from '../models/maintenanceEntryType'; + +export enum EditMaintenenceEntryDialogMode { + None = 'none', + AddMaintenenceEntry = 'addMaintenenceEntry', + EditMaintenenceEntry = 'editMaintenenceEntry', + RemoveMaintenenceEntry = 'removeMaintenenceEntry', +} + +const mapDispatch = (dispatcher: IDispatcher) => ({ + addOrUpdateMaintenenceEntry: (entry: MaintenanceEntry) => { + dispatcher.dispatch(addOrUpdateMaintenenceEntryAsyncActionCreator(entry)); + }, + removeMaintenenceEntry: (entry: MaintenanceEntry) => { + dispatcher.dispatch(removeFromMaintenenceEntrysAsyncActionCreator(entry)); + }, +}); + +type DialogSettings = { + dialogTitle: string; + dialogDescription: string; + applyButtonText: string; + cancelButtonText: string; + enableMountIdEditor: boolean; + enableTimeEditor: boolean; +}; + +const settings: { [key: string]: DialogSettings } = { + [EditMaintenenceEntryDialogMode.None]: { + dialogTitle: '', + dialogDescription: '', + applyButtonText: '', + cancelButtonText: '', + enableMountIdEditor: false, + enableTimeEditor: false, + }, + [EditMaintenenceEntryDialogMode.AddMaintenenceEntry]: { + dialogTitle: 'Add new maintenence entry', + dialogDescription: '', + applyButtonText: 'Add', + cancelButtonText: 'Cancel', + enableMountIdEditor: true, + enableTimeEditor: true, + }, + [EditMaintenenceEntryDialogMode.EditMaintenenceEntry]: { + dialogTitle: 'Edit maintenence entry', + dialogDescription: '', + applyButtonText: 'Save', + cancelButtonText: 'Cancel', + enableMountIdEditor: false, + enableTimeEditor: true, + }, + [EditMaintenenceEntryDialogMode.RemoveMaintenenceEntry]: { + dialogTitle: 'Remove maintenence entry', + dialogDescription: '', + applyButtonText: 'Remove', + cancelButtonText: 'Cancel', + enableMountIdEditor: false, + enableTimeEditor: false, + }, +}; + +type EditMaintenenceEntryDIalogComponentProps = Connect<undefined, typeof mapDispatch> & { + mode: EditMaintenenceEntryDialogMode; + initialMaintenenceEntry: MaintenanceEntry; + onClose: () => void; +}; + +type EditMaintenenceEntryDIalogComponentState = MaintenanceEntry & { isErrorVisible: boolean }; + +class EditMaintenenceEntryDIalogComponent extends React.Component<EditMaintenenceEntryDIalogComponentProps, EditMaintenenceEntryDIalogComponentState> { + constructor(props: EditMaintenenceEntryDIalogComponentProps) { + super(props); + + this.state = { + ...this.props.initialMaintenenceEntry, + isErrorVisible: false, + }; + } + + render(): JSX.Element { + const setting = settings[this.props.mode]; + return ( + <Dialog open={this.props.mode !== EditMaintenenceEntryDialogMode.None}> + <DialogTitle id="form-dialog-title">{setting.dialogTitle}</DialogTitle> + <DialogContent> + <DialogContentText> + {setting.dialogDescription} + </DialogContentText> + <TextField variant="standard" disabled={!setting.enableMountIdEditor} spellCheck={false} autoFocus margin="dense" id="name" label="Name" type="text" fullWidth value={this.state.nodeId} onChange={(event) => { this.setState({ nodeId: event.target.value }); }} /> + {this.state.isErrorVisible && <Typography variant="body1" color="error" >Name must not be empty.</Typography>} + <TextField variant="standard" disabled={!setting.enableTimeEditor} spellCheck={false} autoFocus margin="dense" id="start" + label="Start (Local DateTime)" type="datetime-local" fullWidth value={this.state.start} onChange={(event) => { this.setState({ start: event.target.value }); }} /> + <TextField variant="standard" disabled={!setting.enableTimeEditor} spellCheck={false} autoFocus margin="dense" id="end" + label="End (Local DateTime)" type="datetime-local" fullWidth value={this.state.end} onChange={(event) => { this.setState({ end: event.target.value }); }} /> + <FormControl variant="standard" fullWidth disabled={!setting.enableTimeEditor}> + <InputLabel htmlFor="active">Active</InputLabel> + <Select variant="standard" value={this.state.active || false} onChange={(event) => { + this.setState({ active: event.target.value as any as boolean }); + }} inputProps={{ name: 'active', id: 'active' }} fullWidth > + <MenuItem value={true as any as string}>active</MenuItem> + <MenuItem value={false as any as string}>not active</MenuItem> + </Select> + </FormControl> + </DialogContent> + <DialogActions> + <Button onClick={(event) => { + + if (this.props.mode === EditMaintenenceEntryDialogMode.AddMaintenenceEntry && this.state.nodeId.trim().length === 0) { + this.setState({ isErrorVisible: true }); + } else { + this.onApply({ + mId: this.state.mId || this.state.nodeId, + nodeId: this.state.nodeId, + description: this.state.description, + start: this.state.start, + end: this.state.end, + active: this.state.active, + }); + this.setState({ isErrorVisible: false }); + } + + event.preventDefault(); + event.stopPropagation(); + }} color="inherit" > {setting.applyButtonText} </Button> + <Button onClick={(event) => { + this.onCancel(); + event.preventDefault(); + event.stopPropagation(); + this.setState({ isErrorVisible: false }); + }} color="secondary"> {setting.cancelButtonText} </Button> + </DialogActions> + </Dialog> + ); + } + + private onApply = (entry: MaintenanceEntry) => { + this.props.onClose && this.props.onClose(); + switch (this.props.mode) { + case EditMaintenenceEntryDialogMode.AddMaintenenceEntry: + entry && this.props.addOrUpdateMaintenenceEntry(entry); + break; + case EditMaintenenceEntryDialogMode.EditMaintenenceEntry: + entry && this.props.addOrUpdateMaintenenceEntry(entry); + break; + case EditMaintenenceEntryDialogMode.RemoveMaintenenceEntry: + entry && this.props.removeMaintenenceEntry(entry); + break; + } + }; + + + private onCancel = () => { + this.props.onClose && this.props.onClose(); + }; + + static getDerivedStateFromProps(props: EditMaintenenceEntryDIalogComponentProps, state: EditMaintenenceEntryDIalogComponentState & { initialMaintenenceEntrie: MaintenanceEntry }): EditMaintenenceEntryDIalogComponentState & { initialMaintenenceEntrie: MaintenanceEntry } { + if (props.initialMaintenenceEntry !== state.initialMaintenenceEntrie) { + // eslint-disable-next-line no-param-reassign + state = { + ...state, + ...props.initialMaintenenceEntry, + initialMaintenenceEntrie: props.initialMaintenenceEntry, + }; + } + return state; + } + +} + +export const EditMaintenenceEntryDIalog = connect(undefined, mapDispatch)(EditMaintenenceEntryDIalogComponent); +export default EditMaintenenceEntryDIalog;
\ No newline at end of file diff --git a/sdnr/wt-odlux/odlux/apps/maintenanceApp/src/components/refreshMaintenanceEntries.tsx b/sdnr/wt-odlux/odlux/apps/maintenanceApp/src/components/refreshMaintenanceEntries.tsx new file mode 100644 index 000000000..e8bd635dd --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/maintenanceApp/src/components/refreshMaintenanceEntries.tsx @@ -0,0 +1,113 @@ +/** + * ============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 '@mui/material/Button'; +import Dialog from '@mui/material/Dialog'; +import DialogActions from '@mui/material/DialogActions'; +import DialogContent from '@mui/material/DialogContent'; +import DialogContentText from '@mui/material/DialogContentText'; +import DialogTitle from '@mui/material/DialogTitle'; + +import { connect, Connect, IDispatcher } from '../../../../framework/src/flux/connect'; + +import { maintenanceEntriesReloadAction } from '../handlers/maintenanceEntriesHandler'; +import { MaintenanceEntry } from '../models/maintenanceEntryType'; + +export enum RefreshMaintenanceEntriesDialogMode { + None = 'none', + RefreshMaintenanceEntriesTable = 'RefreshMaintenanceEntriesTable', +} + +const mapDispatch = (dispatcher: IDispatcher) => ({ + refreshMaintenanceEntries: () => dispatcher.dispatch(maintenanceEntriesReloadAction), +}); + +type DialogSettings = { + dialogTitle: string; + dialogDescription: string; + applyButtonText: string; + cancelButtonText: string; + enableMountIdEditor: boolean; + enableUsernameEditor: boolean; + enableExtendedEditor: boolean; +}; + +const settings: { [key: string]: DialogSettings } = { + [RefreshMaintenanceEntriesDialogMode.None]: { + dialogTitle: '', + dialogDescription: '', + applyButtonText: '', + cancelButtonText: '', + enableMountIdEditor: false, + enableUsernameEditor: false, + enableExtendedEditor: false, + }, + [RefreshMaintenanceEntriesDialogMode.RefreshMaintenanceEntriesTable]: { + dialogTitle: 'Do you want to refresh Maintenance Entries?', + dialogDescription: '', + applyButtonText: 'Yes', + cancelButtonText: 'Cancel', + enableMountIdEditor: true, + enableUsernameEditor: true, + enableExtendedEditor: true, + }, +}; + +type RefreshMaintenanceEntriesDialogComponentProps = Connect<undefined, typeof mapDispatch> & { + mode: RefreshMaintenanceEntriesDialogMode; + onClose: () => void; +}; + +type RefreshMaintenanceEntriesDialogComponentState = MaintenanceEntry & { isNameValid: boolean; isHostSet: boolean }; + +class RefreshMaintenanceEntriesDialogComponent extends React.Component<RefreshMaintenanceEntriesDialogComponentProps, RefreshMaintenanceEntriesDialogComponentState> { + render(): JSX.Element { + const setting = settings[this.props.mode]; + return ( + <Dialog open={this.props.mode !== RefreshMaintenanceEntriesDialogMode.None}> + <DialogTitle id="form-dialog-title" aria-label={`${setting.dialogTitle.replace(/ /g, '-').toLowerCase()}-dialog`}>{setting.dialogTitle}</DialogTitle> + <DialogContent> + <DialogContentText> + {setting.dialogDescription} + </DialogContentText> + </DialogContent> + <DialogActions> + <Button aria-label="dialog-confirm-button" onClick={() => { + this.onRefresh(); + }} color="inherit" > {setting.applyButtonText} </Button> + <Button aria-label="dialog-cancel-button" onClick={() => { + this.onCancel(); + }} color="secondary"> {setting.cancelButtonText} </Button> + </DialogActions> + </Dialog> + ); + } + + private onRefresh = () => { + this.props.refreshMaintenanceEntries(); + this.props.onClose(); + }; + + private onCancel = () => { + this.props.onClose(); + }; +} + +export const RefreshMaintenanceEntriesDialog = connect(undefined, mapDispatch)(RefreshMaintenanceEntriesDialogComponent); +export default RefreshMaintenanceEntriesDialog;
\ No newline at end of file diff --git a/sdnr/wt-odlux/odlux/apps/maintenanceApp/src/handlers/maintenanceAppRootHandler.ts b/sdnr/wt-odlux/odlux/apps/maintenanceApp/src/handlers/maintenanceAppRootHandler.ts new file mode 100644 index 000000000..ced7f2160 --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/maintenanceApp/src/handlers/maintenanceAppRootHandler.ts @@ -0,0 +1,39 @@ +/** + * ============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========================================================================== + */ +// main state handler + +import { combineActionHandler } from '../../../../framework/src/flux/middleware'; + +import { IMaintenanceEntriesState, maintenanceEntriesActionHandler } from './maintenanceEntriesHandler'; + +export interface IMaintenanceAppStoreState { + maintenanceEntries : IMaintenanceEntriesState; +} + +declare module '../../../../framework/src/store/applicationStore' { + interface IApplicationStoreState { + maintenance: IMaintenanceAppStoreState; + } +} + +const actionHandlers = { + maintenanceEntries: maintenanceEntriesActionHandler, +}; + +export const maintenanceAppRootHandler = combineActionHandler<IMaintenanceAppStoreState>(actionHandlers); +export default maintenanceAppRootHandler; diff --git a/sdnr/wt-odlux/odlux/apps/maintenanceApp/src/handlers/maintenanceEntriesHandler.ts b/sdnr/wt-odlux/odlux/apps/maintenanceApp/src/handlers/maintenanceEntriesHandler.ts new file mode 100644 index 000000000..c3fe51e80 --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/maintenanceApp/src/handlers/maintenanceEntriesHandler.ts @@ -0,0 +1,35 @@ +/** + * ============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 { createExternal, IExternalTableState } from '../../../../framework/src/components/material-table/utilities'; +import { createSearchDataHandler } from '../../../../framework/src/utilities/elasticSearch'; + +import { MaintenanceEntry } from '../models/maintenanceEntryType'; +export interface IMaintenanceEntriesState extends IExternalTableState<MaintenanceEntry> { } + +// create elastic search material data fetch handler +const maintenanceEntriesSearchHandler = createSearchDataHandler<MaintenanceEntry>('maintenance'); + +export const { + actionHandler: maintenanceEntriesActionHandler, + createActions: createmaintenanceEntriesActions, + createProperties: createmaintenanceEntriesProperties, + reloadAction: maintenanceEntriesReloadAction, + + // set value action, to change a value +} = createExternal<MaintenanceEntry>(maintenanceEntriesSearchHandler, appState => appState.maintenance.maintenanceEntries); + diff --git a/sdnr/wt-odlux/odlux/apps/maintenanceApp/src/index.html b/sdnr/wt-odlux/odlux/apps/maintenanceApp/src/index.html new file mode 100644 index 000000000..c84aecee1 --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/maintenanceApp/src/index.html @@ -0,0 +1,26 @@ +<!DOCTYPE html> +<html lang="en"> + +<head> + <meta charset="UTF-8"> + <meta name="viewport" content="width=device-width, initial-scale=1.0"> + <meta http-equiv="X-UA-Compatible" content="ie=edge"> + <!-- <link rel="stylesheet" href="./vendor.css"> --> + <title>Minimal App</title> +</head> + +<body> + <div id="app"></div> + <script type="text/javascript" src="./require.js"></script> + <script type="text/javascript" src="./config.js"></script> + <script> + // run the application + require(["app","connectApp", "maintenanceApp"], function (app, connectApp, maintenanceApp) { + connectApp.register(); + maintenanceApp.register(); + app("./app.tsx").runApplication(); + }); + </script> +</body> + +</html>
\ No newline at end of file diff --git a/sdnr/wt-odlux/odlux/apps/maintenanceApp/src/models/maintenanceEntryType.ts b/sdnr/wt-odlux/odlux/apps/maintenanceApp/src/models/maintenanceEntryType.ts new file mode 100644 index 000000000..27cdc8c12 --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/maintenanceApp/src/models/maintenanceEntryType.ts @@ -0,0 +1,33 @@ +/** + * ============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========================================================================== + */ +/** Represents the elestic search db type for maintenence enrties */ + + +export const spoofSymbol = Symbol('Spoof'); + +/** Represents the type for an maintenence entry. */ +export type MaintenanceEntry = { + mId: string; + nodeId: string; + description?: string; + end: string; + start: string; + active: boolean; + [spoofSymbol]?: boolean; +}; + diff --git a/sdnr/wt-odlux/odlux/apps/maintenanceApp/src/pluginMaintenance.tsx b/sdnr/wt-odlux/odlux/apps/maintenanceApp/src/pluginMaintenance.tsx new file mode 100644 index 000000000..0f686cb84 --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/maintenanceApp/src/pluginMaintenance.tsx @@ -0,0 +1,44 @@ +/** + * ============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========================================================================== + */ +// app configuration and main entry point for the app + +import React, { FC } from 'react'; + +import applicationManager from '../../../framework/src/services/applicationManager'; + +import { maintenanceAppRootHandler } from './handlers/maintenanceAppRootHandler'; + +import { MaintenanceView } from './views/maintenanceView'; + +const appIcon = require('./assets/icons/maintenanceAppIcon.svg'); // select app icon + +const App : FC = () => { + return <MaintenanceView />; +}; + +export function register() { + applicationManager.registerApplication({ + name: 'maintenance', + icon: appIcon, + rootComponent: App, + rootActionHandler: maintenanceAppRootHandler, + menuEntry: 'Maintenance', + }); +} + + diff --git a/sdnr/wt-odlux/odlux/apps/maintenanceApp/src/services/maintenenceService.ts b/sdnr/wt-odlux/odlux/apps/maintenanceApp/src/services/maintenenceService.ts new file mode 100644 index 000000000..5fdccc349 --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/maintenanceApp/src/services/maintenenceService.ts @@ -0,0 +1,72 @@ +/** + * ============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 { DeleteResponse, PostResponse } from '../../../../framework/src/models/elasticSearch'; +import { requestRest } from '../../../../framework/src/services/restService'; +import { convertPropertyNames, replaceUpperCase } from '../../../../framework/src/utilities/yangHelper'; +import { MaintenanceEntry } from '../models/maintenanceEntryType'; + +import { convertToISODateString } from '../utils/timeUtils'; + + +export const maintenenceEntryDatabasePath = 'mwtn/maintenancemode'; + +/** + * Represents a web api accessor service for all maintenence entries related actions. + */ +class MaintenenceService { + + /** + * Adds or updates one maintenence entry to the backend. + */ + public async writeMaintenenceEntry(maintenenceEntry: MaintenanceEntry): Promise<PostResponse | null> { + const path = '/rests/operations/data-provider:create-maintenance'; + + const query = { + 'id': maintenenceEntry.mId, + 'node-id': maintenenceEntry.nodeId, + 'active': maintenenceEntry.active, + 'description': maintenenceEntry.description, + 'end': convertToISODateString(maintenenceEntry.end), + 'start': convertToISODateString(maintenenceEntry.start), + }; + + const result = await requestRest<PostResponse>(path, { method: 'POST', body: JSON.stringify(convertPropertyNames({ 'data-provider:input': query }, replaceUpperCase)) }); + return result || null; + } + + /** + * Deletes one maintenence entry by its mountId from the backend. + */ + public async deleteMaintenenceEntry(maintenenceEntry: MaintenanceEntry): Promise<(DeleteResponse) | null> { + const path = '/rests/operations/data-provider:delete-maintenance'; + + const query = { + 'id': maintenenceEntry.mId, + 'node-id': maintenenceEntry.nodeId, + 'active': maintenenceEntry.active, + 'description': maintenenceEntry.description, + 'end': convertToISODateString(maintenenceEntry.end), + 'start': convertToISODateString(maintenenceEntry.start), + }; + const result = await requestRest<DeleteResponse>(path, { method: 'POST', body: JSON.stringify(convertPropertyNames({ 'data-provider:input': query }, replaceUpperCase)) }); + return result || null; + } +} + +export const maintenenceService = new MaintenenceService(); +export default maintenenceService;
\ No newline at end of file diff --git a/sdnr/wt-odlux/odlux/apps/maintenanceApp/src/utils/timeUtils.ts b/sdnr/wt-odlux/odlux/apps/maintenanceApp/src/utils/timeUtils.ts new file mode 100644 index 000000000..0fde5fcad --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/maintenanceApp/src/utils/timeUtils.ts @@ -0,0 +1,45 @@ +/** + * ============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========================================================================== + */ +export function convertToGMTString(dateString: string): string { + const date = new Date(dateString); + const pad = (n: number) => (n < 10) ? '0' + n : n; + + return date.getUTCFullYear() + + '-' + pad(date.getUTCMonth() + 1) + + '-' + pad(date.getUTCDate()) + + 'T' + pad(date.getUTCHours()) + + ':' + pad(date.getUTCMinutes()) + + '+00:00'; +} + +export function convertToLocaleString(rawDate: string | number): string { + const date = new Date(rawDate); + const pad = (n: number) => (n < 10) ? '0' + n : n; + + return date.getFullYear() + + '-' + pad(date.getMonth() + 1) + + '-' + pad(date.getDate()) + + 'T' + pad(date.getHours()) + + ':' + pad(date.getMinutes()); +} + +export function convertToISODateString(rawDate: string | number): string { + const date = new Date(rawDate); + const displayDate = date.toISOString(); + return displayDate.replace(/\.[0-9]{2}/, '.'); +}
\ No newline at end of file diff --git a/sdnr/wt-odlux/odlux/apps/maintenanceApp/src/views/maintenanceView.tsx b/sdnr/wt-odlux/odlux/apps/maintenanceApp/src/views/maintenanceView.tsx new file mode 100644 index 000000000..d54d63c04 --- /dev/null +++ b/sdnr/wt-odlux/odlux/apps/maintenanceApp/src/views/maintenanceView.tsx @@ -0,0 +1,246 @@ +/** + * ============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 React from 'react'; + +import { faBan } from '@fortawesome/free-solid-svg-icons'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import AddIcon from '@mui/icons-material/Add'; +import EditIcon from '@mui/icons-material/Edit'; +import Refresh from '@mui/icons-material/Refresh'; +import RemoveIcon from '@mui/icons-material/RemoveCircleOutline'; +import { Divider, MenuItem, Theme, Typography } from '@mui/material'; +import { WithStyles } from '@mui/styles'; +import createStyles from '@mui/styles/createStyles'; +import withStyles from '@mui/styles/withStyles'; + +import MaterialTable, { ColumnType, MaterialTableCtorType } from '../../../../framework/src/components/material-table'; +import { connect, Connect, IDispatcher } from '../../../../framework/src/flux/connect'; +import { IApplicationStoreState } from '../../../../framework/src/store/applicationStore'; + +import EditMaintenenceEntryDialog, { EditMaintenenceEntryDialogMode } from '../components/editMaintenenceEntryDialog'; +import RefreshMaintenanceEntriesDialog, { RefreshMaintenanceEntriesDialogMode } from '../components/refreshMaintenanceEntries'; +import { createmaintenanceEntriesActions, createmaintenanceEntriesProperties, maintenanceEntriesReloadAction } from '../handlers/maintenanceEntriesHandler'; +import { MaintenanceEntry } from '../models/maintenanceEntryType'; +import { convertToLocaleString } from '../utils/timeUtils'; + +const styles = (theme: Theme) => createStyles({ + button: { + margin: 0, + padding: '6px 6px', + minWidth: 'unset', + }, + spacer: { + marginLeft: theme.spacing(1), + marginRight: theme.spacing(1), + display: 'inline', + }, +}); + +const MaintenanceEntriesTable = MaterialTable as MaterialTableCtorType<MaintenanceEntry>; + +const mapProps = (state: IApplicationStoreState) => ({ + maintenanceEntriesProperties: createmaintenanceEntriesProperties(state), +}); + +const mapDispatcher = (dispatcher: IDispatcher) => ({ + maintenanceEntriesActions: createmaintenanceEntriesActions(dispatcher.dispatch), + onLoadMaintenanceEntries: async () => { + await dispatcher.dispatch(maintenanceEntriesReloadAction); + }, +}); + +const emptyMaintenenceEntry: MaintenanceEntry = { + mId: '', + nodeId: '', + description: '', + start: convertToLocaleString(new Date().valueOf()), + end: convertToLocaleString(new Date().valueOf()), + active: false, +}; + +type MaintenanceViewComponentProps = Connect<typeof mapProps, typeof mapDispatcher> & WithStyles<typeof styles> & {}; + +type MaintenenceViewComponentState = { + maintenenceEntryToEdit: MaintenanceEntry; + maintenanceEntryEditorMode: EditMaintenenceEntryDialogMode; + refreshMaintenenceEntriesEditorMode: RefreshMaintenanceEntriesDialogMode; +}; + +let initialSorted = false; + +class MaintenenceViewComponent extends React.Component<MaintenanceViewComponentProps, MaintenenceViewComponentState> { + + constructor(props: MaintenanceViewComponentProps) { + super(props); + + this.state = { + maintenenceEntryToEdit: emptyMaintenenceEntry, + maintenanceEntryEditorMode: EditMaintenenceEntryDialogMode.None, + refreshMaintenenceEntriesEditorMode: RefreshMaintenanceEntriesDialogMode.None, + }; + + } + + getContextMenu(rowData: MaintenanceEntry): JSX.Element[] { + let buttonArray = [ + <MenuItem aria-label={'1hr-from-now'} onClick={event => this.onOpenPlus1hEditMaintenenceEntryDialog(event, rowData)}><Typography>+1h</Typography></MenuItem>, + <MenuItem aria-label={'8hr-from-now'} onClick={event => this.onOpenPlus8hEditMaintenenceEntryDialog(event, rowData)}><Typography>+8h</Typography></MenuItem>, + <Divider />, + <MenuItem aria-label={'edit'} onClick={event => this.onOpenEditMaintenenceEntryDialog(event, rowData)}><EditIcon /><Typography>Edit</Typography></MenuItem>, + <MenuItem aria-label={'remove'} onClick={event => this.onOpenRemoveMaintenenceEntryDialog(event, rowData)}><RemoveIcon /><Typography>Remove</Typography></MenuItem>, + ]; + return buttonArray; + } + + render() { + const addMaintenenceEntryAction = { + icon: AddIcon, tooltip: 'Add', ariaLabel:'add-element', onClick: () => { + const startTime = (new Date().valueOf()); + const endTime = startTime; + this.setState({ + maintenenceEntryToEdit: { + ...emptyMaintenenceEntry, + start: convertToLocaleString(startTime), + end: convertToLocaleString(endTime), + }, + maintenanceEntryEditorMode: EditMaintenenceEntryDialogMode.AddMaintenenceEntry, + }); + }, + }; + + const refreshMaintenanceEntriesAction = { + icon: Refresh, tooltip: 'Refresh Maintenance Entries', ariaLabel: 'refresh', onClick: () => { + this.setState({ + refreshMaintenenceEntriesEditorMode: RefreshMaintenanceEntriesDialogMode.RefreshMaintenanceEntriesTable, + }); + }, + }; + + const now = new Date().valueOf(); + return ( + <> + <MaintenanceEntriesTable stickyHeader tableId="maintenance-table" title={'Maintenance'} customActionButtons={[refreshMaintenanceEntriesAction, addMaintenenceEntryAction]} columns={ + [ + { property: 'nodeId', title: 'Node Name', type: ColumnType.text }, + { + property: 'notifications', title: 'Notification', width: 50, align: 'center', type: ColumnType.custom, customControl: ({ rowData }) => ( + rowData.active && (Date.parse(rowData.start).valueOf() <= now) && (Date.parse(rowData.end).valueOf() >= now) && <FontAwesomeIcon icon={faBan} /> || null + ), + }, + { property: 'active', title: 'Activation State', type: ColumnType.boolean, labels: { 'true': 'active', 'false': 'not active' } }, + { property: 'start', title: 'Start Date (UTC)', type: ColumnType.text }, + { property: 'end', title: 'End Date (UTC)', type: ColumnType.text }, + ] + } idProperty={'mId'}{...this.props.maintenanceEntriesActions} {...this.props.maintenanceEntriesProperties} asynchronus createContextMenu={rowData => { + return this.getContextMenu(rowData); + }} > + </MaintenanceEntriesTable> + <EditMaintenenceEntryDialog initialMaintenenceEntry={this.state.maintenenceEntryToEdit} mode={this.state.maintenanceEntryEditorMode} + onClose={this.onCloseEditMaintenenceEntryDialog} /> + <RefreshMaintenanceEntriesDialog mode={this.state.refreshMaintenenceEntriesEditorMode} + onClose={this.onCloseRefreshMaintenenceEntryDialog} /> + </> + ); + } + + public componentDidMount() { + + if (!initialSorted) { + initialSorted = true; + this.props.maintenanceEntriesActions.onHandleRequestSort('node-id'); + } else { + this.props.onLoadMaintenanceEntries(); + } + + + } + + private onOpenPlus1hEditMaintenenceEntryDialog = (event: React.MouseEvent<HTMLElement>, entry: MaintenanceEntry) => { + // event.preventDefault(); + // event.stopPropagation(); + const startTime = (new Date().valueOf()); + const endTime = startTime + (1 * 60 * 60 * 1000); + this.setState({ + maintenenceEntryToEdit: { + ...entry, + start: convertToLocaleString(startTime), + end: convertToLocaleString(endTime), + }, + maintenanceEntryEditorMode: EditMaintenenceEntryDialogMode.EditMaintenenceEntry, + }); + }; + + private onOpenPlus8hEditMaintenenceEntryDialog = (event: React.MouseEvent<HTMLElement>, entry: MaintenanceEntry) => { + // event.preventDefault(); + // event.stopPropagation(); + const startTime = (new Date().valueOf()); + const endTime = startTime + (8 * 60 * 60 * 1000); + this.setState({ + maintenenceEntryToEdit: { + ...entry, + start: convertToLocaleString(startTime), + end: convertToLocaleString(endTime), + }, + maintenanceEntryEditorMode: EditMaintenenceEntryDialogMode.EditMaintenenceEntry, + }); + }; + + private onOpenEditMaintenenceEntryDialog = (event: React.MouseEvent<HTMLElement>, entry: MaintenanceEntry) => { + // event.preventDefault(); + // event.stopPropagation(); + const startTime = (new Date().valueOf()); + const endTime = startTime; + this.setState({ + maintenenceEntryToEdit: { + ...entry, + ...(entry.start && endTime ? { start: convertToLocaleString(entry.start), end: convertToLocaleString(entry.end) } : { start: convertToLocaleString(startTime), end: convertToLocaleString(endTime) }), + }, + maintenanceEntryEditorMode: EditMaintenenceEntryDialogMode.EditMaintenenceEntry, + }); + }; + + private onOpenRemoveMaintenenceEntryDialog = (event: React.MouseEvent<HTMLElement>, entry: MaintenanceEntry) => { + // event.preventDefault(); + // event.stopPropagation(); + const startTime = (new Date().valueOf()); + const endTime = startTime; + this.setState({ + maintenenceEntryToEdit: { + ...entry, + ...(entry.start && endTime ? { start: convertToLocaleString(entry.start), end: convertToLocaleString(entry.end) } : { start: convertToLocaleString(startTime), end: convertToLocaleString(endTime) }), + }, + maintenanceEntryEditorMode: EditMaintenenceEntryDialogMode.RemoveMaintenenceEntry, + }); + }; + + private onCloseEditMaintenenceEntryDialog = () => { + this.setState({ + maintenenceEntryToEdit: emptyMaintenenceEntry, + maintenanceEntryEditorMode: EditMaintenenceEntryDialogMode.None, + }); + }; + + private onCloseRefreshMaintenenceEntryDialog = () => { + this.setState({ + refreshMaintenenceEntriesEditorMode: RefreshMaintenanceEntriesDialogMode.None, + }); + }; +} + +export const MaintenanceView = withStyles(styles)(connect(mapProps, mapDispatcher)(MaintenenceViewComponent)); + |