summaryrefslogtreecommitdiffstats
path: root/sdnr/wt-odlux/odlux/apps/maintenanceApp
diff options
context:
space:
mode:
Diffstat (limited to 'sdnr/wt-odlux/odlux/apps/maintenanceApp')
-rw-r--r--sdnr/wt-odlux/odlux/apps/maintenanceApp/.babelrc17
-rw-r--r--sdnr/wt-odlux/odlux/apps/maintenanceApp/package.json46
-rw-r--r--sdnr/wt-odlux/odlux/apps/maintenanceApp/pom.xml109
-rw-r--r--sdnr/wt-odlux/odlux/apps/maintenanceApp/src/actions/maintenenceActions.ts77
-rw-r--r--sdnr/wt-odlux/odlux/apps/maintenanceApp/src/assets/icons/maintenanceAppIcon.svg50
-rw-r--r--sdnr/wt-odlux/odlux/apps/maintenanceApp/src/components/editMaintenenceEntryDialog.tsx207
-rw-r--r--sdnr/wt-odlux/odlux/apps/maintenanceApp/src/components/refreshMaintenanceEntries.tsx113
-rw-r--r--sdnr/wt-odlux/odlux/apps/maintenanceApp/src/handlers/maintenanceAppRootHandler.ts39
-rw-r--r--sdnr/wt-odlux/odlux/apps/maintenanceApp/src/handlers/maintenanceEntriesHandler.ts35
-rw-r--r--sdnr/wt-odlux/odlux/apps/maintenanceApp/src/index.html26
-rw-r--r--sdnr/wt-odlux/odlux/apps/maintenanceApp/src/models/maintenanceEntryType.ts33
-rw-r--r--sdnr/wt-odlux/odlux/apps/maintenanceApp/src/pluginMaintenance.tsx44
-rw-r--r--sdnr/wt-odlux/odlux/apps/maintenanceApp/src/services/maintenenceService.ts72
-rw-r--r--sdnr/wt-odlux/odlux/apps/maintenanceApp/src/utils/timeUtils.ts45
-rw-r--r--sdnr/wt-odlux/odlux/apps/maintenanceApp/src/views/maintenanceView.tsx246
-rw-r--r--sdnr/wt-odlux/odlux/apps/maintenanceApp/tsconfig.json37
-rw-r--r--sdnr/wt-odlux/odlux/apps/maintenanceApp/webpack.config.js168
17 files changed, 1364 insertions, 0 deletions
diff --git a/sdnr/wt-odlux/odlux/apps/maintenanceApp/.babelrc b/sdnr/wt-odlux/odlux/apps/maintenanceApp/.babelrc
new file mode 100644
index 000000000..3d8cd1260
--- /dev/null
+++ b/sdnr/wt-odlux/odlux/apps/maintenanceApp/.babelrc
@@ -0,0 +1,17 @@
+{
+ "presets": [
+ ["@babel/preset-react"],
+ ["@babel/preset-env", {
+ "targets": {
+ "chrome": "66"
+ },
+ "spec": true,
+ "loose": false,
+ "modules": false,
+ "debug": false,
+ "useBuiltIns": "usage",
+ "forceAllTransforms": true
+ }]
+ ],
+ "plugins": []
+}
diff --git a/sdnr/wt-odlux/odlux/apps/maintenanceApp/package.json b/sdnr/wt-odlux/odlux/apps/maintenanceApp/package.json
new file mode 100644
index 000000000..d7c325409
--- /dev/null
+++ b/sdnr/wt-odlux/odlux/apps/maintenanceApp/package.json
@@ -0,0 +1,46 @@
+{
+ "name": "@odlux/maintenance-app",
+ "version": "0.1.0",
+ "description": "A react based modular UI for the maintenance app.",
+ "main": "index.js",
+ "scripts": {
+ "start": "webpack-dev-server --env debug",
+ "build": "webpack --env release --config webpack.config.js",
+ "build:dev": "webpack --env debug --config webpack.config.js"
+ },
+ "repository": {
+ "type": "git",
+ "url": "https://git.mfico.de/highstreet-technologies/odlux.git"
+ },
+ "keywords": [
+ "reactjs",
+ "redux",
+ "ui",
+ "framework"
+ ],
+ "author": "Matthias Fischer",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@emotion/react": "^11.7.0",
+ "@emotion/styled": "^11.6.0",
+ "@mui/icons-material": "^5.2.0",
+ "@mui/material": "^5.2.2",
+ "@mui/styles": "^5.2.2",
+ "@odlux/connect-app": "*",
+ "@odlux/framework": "*"
+ },
+ "peerDependencies": {
+ "@types/classnames": "2.2.6",
+ "@types/flux": "3.1.8",
+ "@types/jquery": "3.3.10",
+ "@types/react": "17.0.37",
+ "@types/react-dom": "17.0.11",
+ "@types/react-router-dom": "5.1.7",
+ "jquery": "3.3.1",
+ "react": "17.0.2",
+ "react-dom": "17.0.2",
+ "react-router-dom": "5.2.0",
+ "@fortawesome/free-solid-svg-icons": "5.6.3",
+ "@fortawesome/react-fontawesome": "0.1.14"
+ }
+}
diff --git a/sdnr/wt-odlux/odlux/apps/maintenanceApp/pom.xml b/sdnr/wt-odlux/odlux/apps/maintenanceApp/pom.xml
new file mode 100644
index 000000000..03832f736
--- /dev/null
+++ b/sdnr/wt-odlux/odlux/apps/maintenanceApp/pom.xml
@@ -0,0 +1,109 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ ============LICENSE_START=======================================================
+ ~ ONAP : SDNR ODLUX
+ ~ ================================================================================
+ ~ Copyright (C) 2020 AT&T 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=======================================================
+ ~
+ -->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+
+ <groupId>org.onap.ccsdk.features.sdnr.odlux</groupId>
+ <artifactId>sdnr-odlux-app-maintenanceApp</artifactId>
+ <version>1.7.0-SNAPSHOT</version>
+ <packaging>jar</packaging>
+
+ <name>SDNR ODLUX :: ${project.artifactId}</name>
+ <licenses>
+ <license>
+ <name>Apache License, Version 2.0</name>
+ <url>http://www.apache.org/licenses/LICENSE-2.0</url>
+ </license>
+ </licenses>
+
+ <properties>
+ <maven.javadoc.skip>true</maven.javadoc.skip>
+ </properties>
+
+ <build>
+ <resources>
+ <resource>
+ <directory>dist</directory>
+ <targetPath>odlux</targetPath>
+ </resource>
+ </resources>
+ <plugins>
+ <plugin>
+ <artifactId>maven-clean-plugin</artifactId>
+ <configuration>
+ <filesets>
+ <fileset>
+ <directory>dist</directory>
+ <followSymlinks>false</followSymlinks>
+ </fileset>
+ <fileset>
+ <directory>node</directory>
+ <followSymlinks>false</followSymlinks>
+ </fileset>
+ <fileset>
+ <directory>node_modules</directory>
+ <followSymlinks>false</followSymlinks>
+ </fileset>
+ <fileset>
+ <directory>../node_modules</directory>
+ <followSymlinks>false</followSymlinks>
+ </fileset>
+ <!-- eclipse bug build bin folder in basedir -->
+ <fileset>
+ <directory>bin</directory>
+ <followSymlinks>false</followSymlinks>
+ </fileset>
+ </filesets>
+ </configuration>
+ </plugin>
+ <plugin>
+ <groupId>de.jacks-it-lab</groupId>
+ <artifactId>frontend-maven-plugin</artifactId>
+ <version>1.7.2</version>
+ <executions>
+ <execution>
+ <id>install node and yarn</id>
+ <goals>
+ <goal>install-node-and-yarn</goal>
+ </goals>
+ <!-- optional: default phase is "generate-resources" -->
+ <phase>initialize</phase>
+ <configuration>
+ <nodeVersion>v16.17.0</nodeVersion>
+ <yarnVersion>v1.22.19</yarnVersion>
+ </configuration>
+ </execution>
+ <execution>
+ <id>yarn build</id>
+ <goals>
+ <goal>yarn</goal>
+ </goals>
+ <configuration>
+ <arguments>run build</arguments>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ </build>
+</project>
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));
+
diff --git a/sdnr/wt-odlux/odlux/apps/maintenanceApp/tsconfig.json b/sdnr/wt-odlux/odlux/apps/maintenanceApp/tsconfig.json
new file mode 100644
index 000000000..ca65092e0
--- /dev/null
+++ b/sdnr/wt-odlux/odlux/apps/maintenanceApp/tsconfig.json
@@ -0,0 +1,37 @@
+{
+ "compilerOptions": {
+ "baseUrl": "./src",
+ "outDir": "./dist",
+ "sourceMap": true,
+ "forceConsistentCasingInFileNames": true,
+ "allowSyntheticDefaultImports": true,
+ "allowUnreachableCode": false,
+ "allowUnusedLabels": false,
+ "noFallthroughCasesInSwitch": true,
+ "noImplicitAny": true,
+ "noImplicitReturns": true,
+ "noImplicitThis": true,
+ "strictNullChecks": true,
+ "pretty": true,
+ "newLine": "LF",
+ "module": "es2015",
+ "target": "es2016",
+ "moduleResolution": "node",
+ "experimentalDecorators": true,
+ "jsx": "preserve",
+ "lib": [
+ "dom",
+ "es2015",
+ "es2016"
+ ],
+ "types": [
+ "prop-types",
+ "react",
+ "react-dom"
+ ]
+ },
+ "exclude": [
+ "dist",
+ "node_modules"
+ ]
+}
diff --git a/sdnr/wt-odlux/odlux/apps/maintenanceApp/webpack.config.js b/sdnr/wt-odlux/odlux/apps/maintenanceApp/webpack.config.js
new file mode 100644
index 000000000..845fc43a6
--- /dev/null
+++ b/sdnr/wt-odlux/odlux/apps/maintenanceApp/webpack.config.js
@@ -0,0 +1,168 @@
+/**
+ * Webpack 4 configuration file
+ * see https://webpack.js.org/configuration/
+ * see https://webpack.js.org/configuration/dev-server/
+ */
+
+"use strict";
+
+const path = require("path");
+const webpack = require("webpack");
+const CopyWebpackPlugin = require("copy-webpack-plugin");
+const TerserPlugin = require('terser-webpack-plugin');
+
+// const __dirname = (path => path.replace(/^([a-z]\:)/, c => c.toUpperCase()))(process.__dirname());
+
+module.exports = (env) => {
+ const distPath = path.resolve(__dirname, env === "release" ? "." : "../..", "dist");
+ const frameworkPath = path.resolve(__dirname, env === "release" ? "../../framework" : "../..", "dist");
+ return [{
+ name: "App",
+
+ mode: "none", //disable default behavior
+
+ target: "web",
+
+ context: path.resolve(__dirname, "src"),
+
+ entry: {
+ maintenanceApp: ["./pluginMaintenance.tsx"]
+ },
+
+ devtool: env === "release" ? false : "source-map",
+
+ resolve: {
+ extensions: [".ts", ".tsx", ".js", ".jsx"]
+ },
+
+ output: {
+ path: distPath,
+ filename: "[name].js",
+ library: "[name]",
+ libraryTarget: "umd2",
+ chunkFilename: "[name].js"
+ },
+ module: {
+ rules: [{
+ test: /\.tsx?$/,
+ exclude: /node_modules/,
+ use: [{
+ loader: "babel-loader"
+ }, {
+ loader: "ts-loader"
+ }]
+ }, {
+ test: /\.jsx?$/,
+ exclude: /node_modules/,
+ use: [{
+ loader: "babel-loader"
+ }]
+ }, {
+ //don't minify images
+ test: /\.(png|gif|jpg|svg)$/,
+ use: [{
+ loader: 'url-loader',
+ options: {
+ limit: 10,
+ name: './images/[name].[ext]'
+ }
+ }]
+ }]
+ },
+
+ optimization: {
+ noEmitOnErrors: true,
+ namedModules: env !== "release",
+ minimize: env === "release",
+ minimizer: env !== "release" ? [] : [new TerserPlugin({
+ terserOptions: {
+ warnings: false, // false, true, "verbose"
+ compress: {
+ drop_console: true,
+ drop_debugger: true,
+ }
+ }
+ })],
+ },
+
+ plugins: [
+ new webpack.DllReferencePlugin({
+ context: path.resolve(__dirname, "../../framework/src"),
+ manifest: require(path.resolve(frameworkPath, "vendor-manifest.json")),
+ sourceType: "umd2"
+ }),
+ new webpack.DllReferencePlugin({
+ context: path.resolve(__dirname, "../../framework/src"),
+ manifest: require(path.resolve(frameworkPath, "app-manifest.json")),
+ sourceType: "umd2"
+ }),
+ ...(env === "release" ? [
+ new webpack.DefinePlugin({
+ "process.env": {
+ NODE_ENV: "'production'",
+ VERSION: JSON.stringify(require("./package.json").version)
+ }
+ }),
+ ] : [
+ new webpack.DefinePlugin({
+ "process.env": {
+ NODE_ENV: "'development'",
+ VERSION: JSON.stringify(require("./package.json").version)
+ }
+ }),
+ new CopyWebpackPlugin([{
+ from: 'index.html',
+ to: distPath
+ }]),
+ ])
+ ],
+
+ devServer: {
+ public: "http://localhost:3100",
+ contentBase: frameworkPath,
+
+ compress: true,
+ headers: {
+ "Access-Control-Allow-Origin": "*"
+ },
+ host: "0.0.0.0",
+ port: 3100,
+ disableHostCheck: true,
+ historyApiFallback: true,
+ inline: true,
+ hot: false,
+ quiet: false,
+ stats: {
+ colors: true
+ },
+ proxy: {
+ "/oauth2/": {
+ target: "http://10.20.6.29:48181",
+ secure: false
+ },
+ "/database/": {
+ target: "http://10.20.6.29:48181",
+ secure: false
+ },
+ "/restconf/": {
+ target: "http://10.20.6.29:48181",
+ secure: false
+ },
+ "/rests/": {
+ target: "http://10.20.6.29:48181",
+ secure: false
+ },
+ "/help/": {
+ target: "http://10.20.6.29:48181",
+ secure: false
+ },
+ "/websocket": {
+ target: "http://10.20.6.29:48181",
+ ws: true,
+ changeOrigin: true,
+ secure: false
+ }
+ }
+ }
+ }];
+}