Add sdnr wt odlux
Add complete sdnr wireless transport app odlux core and apps
Change-Id: I5dcbfb8f3b790e3bda7c8df67bd69d81958f65e5
Issue-ID: SDNC-576
Signed-off-by: Herbert Eiselt <herbert.eiselt@highstreet-technologies.com>
diff --git a/sdnr/wt/odlux/apps/connectApp/.babelrc b/sdnr/wt/odlux/apps/connectApp/.babelrc
new file mode 100644
index 0000000..3d8cd12
--- /dev/null
+++ b/sdnr/wt/odlux/apps/connectApp/.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/apps/connectApp/package.json b/sdnr/wt/odlux/apps/connectApp/package.json
new file mode 100644
index 0000000..29e1cb9
--- /dev/null
+++ b/sdnr/wt/odlux/apps/connectApp/package.json
@@ -0,0 +1,40 @@
+{
+ "name": "@odlux/connect-app",
+ "version": "0.1.1",
+ "description": "A react based modular UI to display network connect data from a database.",
+ "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": "MIT",
+ "dependencies": {
+ "@odlux/framework" : "*"
+ },
+ "peerDependencies": {
+ "@types/react": "16.4.14",
+ "@types/react-dom": "16.0.8",
+ "@types/react-router-dom": "4.3.1",
+ "@material-ui/core": "3.8.3",
+ "@material-ui/icons": "3.0.2",
+ "@types/classnames": "2.2.6",
+ "@types/flux": "3.1.8",
+ "@types/jquery": "3.3.10",
+ "jquery": "3.3.1",
+ "react": "16.5.2",
+ "react-dom": "16.5.2",
+ "react-router-dom": "4.3.1"
+ }
+}
\ No newline at end of file
diff --git a/sdnr/wt/odlux/apps/connectApp/pom.xml b/sdnr/wt/odlux/apps/connectApp/pom.xml
new file mode 100644
index 0000000..b1cabdb
--- /dev/null
+++ b/sdnr/wt/odlux/apps/connectApp/pom.xml
@@ -0,0 +1,110 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<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">
+
+ <parent>
+ <groupId>org.onap.ccsdk.parent</groupId>
+ <artifactId>odlparent</artifactId>
+ <version>1.2.1-SNAPSHOT</version>
+ <relativePath />
+ </parent>
+ <modelVersion>4.0.0</modelVersion>
+ <groupId>org.onap.ccsdk.features.sdnr.wt</groupId>
+ <artifactId>sdnr-wt-odlux-app-connectApp</artifactId>
+ <version>0.4.1-SNAPSHOT</version>
+ <packaging>bundle</packaging>
+ <name>sdnr-wt-odlux-app-connectApp</name>
+ <licenses>
+ <license>
+ <name>Apache License, Version 2.0</name>
+ <url>http://www.apache.org/licenses/LICENSE-2.0</url>
+ </license>
+ </licenses>
+ <dependencies>
+ <dependency>
+ <groupId>${project.groupId}</groupId>
+ <artifactId>sdnr-wt-odlux-core-model</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ </dependencies>
+ <build>
+ <sourceDirectory>src2/main/java</sourceDirectory>
+ <plugins>
+ <plugin>
+ <artifactId>maven-clean-plugin</artifactId>
+ <version>2.5</version>
+ <configuration>
+ <filesets>
+ <fileset>
+ <directory>dist</directory>
+ <followSymlinks>false</followSymlinks>
+ </fileset>
+ </filesets>
+ </configuration>
+ </plugin>
+ <plugin>
+ <groupId>com.github.eirslett</groupId>
+ <artifactId>frontend-maven-plugin</artifactId>
+ <version>1.8-SNAPSHOT</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>v8.10.0</nodeVersion>
+ <yarnVersion>v1.12.3</yarnVersion>
+ </configuration>
+ </execution>
+ <execution>
+ <id>yarn build</id>
+ <goals>
+ <goal>yarn</goal>
+ </goals>
+ <configuration>
+ <arguments>run build</arguments>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-jar-plugin</artifactId>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>maven-bundle-plugin</artifactId>
+ <extensions>true</extensions>
+ <configuration>
+ <instructions>
+ <Import-Package>org.onap.ccsdk.features.sdnr.wt.odlux.model.*,com.opensymphony.*</Import-Package>
+ <Private-Package></Private-Package>
+ </instructions>
+ </configuration>
+ </plugin>
+ </plugins>
+ <resources>
+ <resource>
+ <directory>dist</directory>
+ <targetPath>odlux</targetPath>
+ </resource>
+ <resource>
+ <directory>src2/main/resources</directory>
+ </resource>
+ </resources>
+ </build>
+ <pluginRepositories>
+ <pluginRepository>
+ <id>highstreet repo</id>
+ <url>https://cloud-highstreet-technologies.com/mvn/</url>
+ <snapshots>
+ <enabled>true</enabled>
+ <updatePolicy>always</updatePolicy>
+ </snapshots>
+ </pluginRepository>
+ </pluginRepositories>
+</project>
\ No newline at end of file
diff --git a/sdnr/wt/odlux/apps/connectApp/src/actions/mountedNetworkElementsActions.ts b/sdnr/wt/odlux/apps/connectApp/src/actions/mountedNetworkElementsActions.ts
new file mode 100644
index 0000000..e342f63
--- /dev/null
+++ b/sdnr/wt/odlux/apps/connectApp/src/actions/mountedNetworkElementsActions.ts
@@ -0,0 +1,90 @@
+import { Action } from '../../../../framework/src/flux/action';
+import { Dispatch } from '../../../../framework/src/flux/store';
+
+import { MountedNetworkElementType } from '../models/mountedNetworkElements';
+import { RequiredNetworkElementType } from '../models/requiredNetworkElements';
+
+import { connectService } from '../services/connectService';
+import { AddSnackbarNotification } from '../../../../framework/src/actions/snackbarActions';
+
+/** Represents the base action. */
+export class BaseAction extends Action { }
+
+/** Represents an action causing the store to load all mounted network elements. */
+export class LoadAllMountedNetworkElementsAction extends BaseAction { }
+
+/** Represents an action causing the store to update all mounted network elements. */
+export class AllMountedNetworkElementsLoadedAction extends BaseAction {
+ constructor(public mountedNetworkElements: MountedNetworkElementType[] | null, public error?: string) {
+ super();
+ }
+}
+
+/** Represents an action causing the store to update all mounted network elements. */
+export class AddMountedNetworkElement extends BaseAction {
+ constructor(public mountedNetworkElement: MountedNetworkElementType | null, public error?: string) {
+ super();
+ }
+}
+
+export class RemoveMountedNetworkElement extends BaseAction {
+ constructor(public mountId: string) {
+ super();
+ }
+}
+
+export class UpdateConnectionStateMountedNetworkElement extends BaseAction {
+ constructor(public mountId: string, connectionState: string) {
+ super();
+ }
+}
+
+
+export class UpdateRequiredMountedNetworkElement extends BaseAction {
+ constructor(public mountId: string, public required: boolean) {
+ super();
+ }
+}
+
+/**
+ * An actioncrator for a async thunk action to add an allready mounted element to the state of this app.
+ * Note: Use this action to add created object notified by the websocket.
+*/
+export const addMountedNetworkElementAsyncActionCreator = (mountId: string) => async (dispatch: Dispatch) => {
+ connectService.getMountedNetworkElementByMountId(mountId).then(mountedNetworkElement => {
+ mountedNetworkElement && dispatch(new AddMountedNetworkElement(mountedNetworkElement));
+ }).catch(error => {
+ dispatch(new AddMountedNetworkElement(null, error));
+ });
+};
+
+/** Represents an async thunk action to load all mounted network elements. */
+export const loadAllMountedNetworkElementsAsync = (dispatch: Dispatch) => {
+ dispatch(new LoadAllMountedNetworkElementsAction());
+ connectService.getMountedNetworkElementsList().then(mountedNetworkElements => {
+ mountedNetworkElements && dispatch(new AllMountedNetworkElementsLoadedAction(mountedNetworkElements));
+ }).catch(error => {
+ dispatch(new AllMountedNetworkElementsLoadedAction(null, error));
+ });
+};
+
+/** Represents an async thunk action to load all mounted network elements. */
+export const mountNetworkElementActionCreatorAsync = (networkElement: RequiredNetworkElementType) => (dispatch: Dispatch) => {
+ connectService.mountNetworkElement(networkElement).then((success) => {
+ success && dispatch(new AddSnackbarNotification({ message: `Requesting mount [${ networkElement.mountId }]`, options: { variant: 'info' } }))
+ || dispatch(new AddSnackbarNotification({ message: `Failed to mount [${ networkElement.mountId }]`, options: { variant: 'warning' } }));
+ }).catch(error => {
+ dispatch(new AddMountedNetworkElement(null, error));
+ });
+};
+
+export const unmountNetworkElementActionCreatorAsync = (mountId: string) => (dispatch: Dispatch) => {
+ connectService.unmountNetworkElement(mountId).then((success) => {
+ success && dispatch(new AddSnackbarNotification({ message: `Requesting unmount [${ mountId }]`, options: { variant: 'info' } }))
+ || dispatch(new AddSnackbarNotification({ message: `Failed to unmount [${ mountId }]`, options: { variant: 'warning' } }));
+ }).catch(error => {
+ dispatch(new AddMountedNetworkElement(null, error));
+ });
+};
+
+
diff --git a/sdnr/wt/odlux/apps/connectApp/src/actions/requiredNetworkElementsActions.ts b/sdnr/wt/odlux/apps/connectApp/src/actions/requiredNetworkElementsActions.ts
new file mode 100644
index 0000000..9793219
--- /dev/null
+++ b/sdnr/wt/odlux/apps/connectApp/src/actions/requiredNetworkElementsActions.ts
@@ -0,0 +1,38 @@
+import { Action } from '../../../../framework/src/flux/action';
+import { Dispatch } from '../../../../framework/src/flux/store';
+import { RequiredNetworkElementType } from '../models/requiredNetworkElements';
+import { requiredNetworkElementsReloadAction } from '../handlers/requiredNetworkElementsHandler';
+import { UpdateRequiredMountedNetworkElement } from '../actions/mountedNetworkElementsActions';
+
+import { AddSnackbarNotification } from '../../../../framework/src/actions/snackbarActions';
+
+import { connectService } from '../services/connectService';
+
+/** Represents the base action. */
+export class BaseAction extends Action { }
+
+
+/** Represents an async thunk action that will add an element to the required network elements. */
+export const addToRequiredNetworkElementsAsyncActionCreator = (element: RequiredNetworkElementType) => (dispatch: Dispatch) => {
+ connectService.insertRequiredNetworkElement(element).then(_ => {
+ window.setTimeout(() => {
+ dispatch(requiredNetworkElementsReloadAction);
+ dispatch(new UpdateRequiredMountedNetworkElement(element.mountId, true));
+ dispatch(new AddSnackbarNotification({ message: `Successfully added [${ element.mountId }]`, options: { variant: 'success' } }));
+ }, 900);
+ });
+};
+
+/** Represents an async thunk action that will delete an element from the required network elements. */
+export const removeFromRequiredNetworkElementsAsyncActionCreator = (element: RequiredNetworkElementType) => (dispatch: Dispatch) => {
+ connectService.deleteRequiredNetworkElement(element).then(_ => {
+ window.setTimeout(() => {
+ dispatch(requiredNetworkElementsReloadAction);
+ dispatch(new UpdateRequiredMountedNetworkElement(element.mountId, false));
+ dispatch(new AddSnackbarNotification({ message: `Successfully removed [${ element.mountId }]`, options: { variant: 'success' } }));
+ }, 900);
+ });
+};
+
+
+
diff --git a/sdnr/wt/odlux/apps/connectApp/src/components/connectionStatusLog.tsx b/sdnr/wt/odlux/apps/connectApp/src/components/connectionStatusLog.tsx
new file mode 100644
index 0000000..b4b08fc
--- /dev/null
+++ b/sdnr/wt/odlux/apps/connectApp/src/components/connectionStatusLog.tsx
@@ -0,0 +1,36 @@
+import * as React from 'react';
+import connect, { IDispatcher, Connect } from '../../../../framework/src/flux/connect';
+import { IApplicationStoreState } from '../../../../framework/src/store/applicationStore';
+import { MaterialTable, ColumnType, MaterialTableCtorType } from '../../../../framework/src/components/material-table';
+
+import { createConnectionStatusLogActions, createConnectionStatusLogProperties } from '../handlers/connectionStatusLogHandler';
+import { ConnectionStatusLogType } from '../models/connectionStatusLog';
+
+const mapProps = (state: IApplicationStoreState) => ({
+ connectionStatusLogProperties: createConnectionStatusLogProperties(state),
+});
+
+const mapDispatch = (dispatcher: IDispatcher) => ({
+ connectionStatusLogActions: createConnectionStatusLogActions(dispatcher.dispatch),
+});
+
+const ConnectionStatusTable = MaterialTable as MaterialTableCtorType<ConnectionStatusLogType>;
+
+type ConnectionStatusLogComponentProps = Connect<typeof mapProps, typeof mapDispatch>;
+
+class ConnectionStatusLogComponent extends React.Component<ConnectionStatusLogComponentProps> {
+ render(): JSX.Element {
+ return (
+ <ConnectionStatusTable columns={ [
+ { property: "timeStamp", title: "Time", type: ColumnType.text },
+ { property: "objectId", title: "Name", type: ColumnType.text },
+ { property: "elementStatus", title: "Connection status", type: ColumnType.text, disableFilter: true, disableSorting: true },
+ ] } idProperty="_id" { ...this.props.connectionStatusLogActions } {...this.props.connectionStatusLogProperties } >
+ </ConnectionStatusTable>
+ );
+ };
+
+}
+
+export const ConnectionStatusLog = connect(mapProps, mapDispatch)(ConnectionStatusLogComponent);
+export default ConnectionStatusLog;
\ No newline at end of file
diff --git a/sdnr/wt/odlux/apps/connectApp/src/components/editNetworkElementDialog.tsx b/sdnr/wt/odlux/apps/connectApp/src/components/editNetworkElementDialog.tsx
new file mode 100644
index 0000000..ee876e8
--- /dev/null
+++ b/sdnr/wt/odlux/apps/connectApp/src/components/editNetworkElementDialog.tsx
@@ -0,0 +1,228 @@
+import * as React from 'react';
+
+import Button from '@material-ui/core/Button';
+import TextField from '@material-ui/core/TextField';
+import Dialog from '@material-ui/core/Dialog';
+import DialogActions from '@material-ui/core/DialogActions';
+import DialogContent from '@material-ui/core/DialogContent';
+import DialogContentText from '@material-ui/core/DialogContentText';
+import DialogTitle from '@material-ui/core/DialogTitle';
+
+import { IDispatcher, connect, Connect } from '../../../../framework/src/flux/connect';
+
+import {
+ addToRequiredNetworkElementsAsyncActionCreator,
+ removeFromRequiredNetworkElementsAsyncActionCreator
+} from '../actions/requiredNetworkElementsActions';
+
+import { RequiredNetworkElementType } from '../models/requiredNetworkElements';
+import { unmountNetworkElementActionCreatorAsync, mountNetworkElementActionCreatorAsync } from '../actions/mountedNetworkElementsActions';
+export enum EditNetworkElementDialogMode {
+ None = "none",
+ UnknownNetworkElementToRequiredNetworkElements = "unknownNetworkElementToRequiredNetworkElements",
+ RequiredNetworkElementToUnknownNetworkElements = "requiredNetworkElementToUnknownNetworkElements",
+ MountNetworkElementToRequiredNetworkElements = "mountNetworkElementToRequiredNetworkElements",
+ MountNetworkElementToUnknonwNetworkElements = "mountNetworkElementToRequiredUnknownElements",
+ MountNetworkElement = "mountNetworkElement",
+ UnmountNetworkElement = "unmountNetworkElement",
+}
+
+const mapDispatch = (dispatcher: IDispatcher) => ({
+ addToRequiredNetworkElements: (element: RequiredNetworkElementType) => {
+ dispatcher.dispatch(addToRequiredNetworkElementsAsyncActionCreator(element));
+ },
+ removeFromRequiredNetworkElements: (element: RequiredNetworkElementType) => {
+ dispatcher.dispatch(removeFromRequiredNetworkElementsAsyncActionCreator(element));
+ },
+ mountNetworkElement: (element: RequiredNetworkElementType) => {
+ dispatcher.dispatch(mountNetworkElementActionCreatorAsync(element));
+ },
+ mountAndRquireNetworkElement: (element: RequiredNetworkElementType) => {
+ dispatcher.dispatch(addToRequiredNetworkElementsAsyncActionCreator(element));
+ dispatcher.dispatch(mountNetworkElementActionCreatorAsync(element));
+ },
+ unmountNetworkElement: (element: RequiredNetworkElementType) => {
+ dispatcher.dispatch(unmountNetworkElementActionCreatorAsync(element && element.mountId));
+ }
+}
+);
+
+type DialogSettings = {
+ dialogTitle: string,
+ dialogDescription: string,
+ applyButtonText: string,
+ cancelButtonText: string,
+ enableMountIdEditor: boolean,
+ enableUsernameEditor: boolean,
+ enableExtendedEditor: boolean,
+}
+
+const settings: { [key: string]: DialogSettings } = {
+ [EditNetworkElementDialogMode.None]: {
+ dialogTitle: "",
+ dialogDescription: "",
+ applyButtonText: "",
+ cancelButtonText: "",
+ enableMountIdEditor: false,
+ enableUsernameEditor: false,
+ enableExtendedEditor: false,
+ },
+ [EditNetworkElementDialogMode.UnknownNetworkElementToRequiredNetworkElements] : {
+ dialogTitle: "Add to required network elements" ,
+ dialogDescription: "Create a new NetworkElement in planning database as clone of existing real NetworkElement." ,
+ applyButtonText: "Add to required network elements" ,
+ cancelButtonText: "Cancel",
+ enableMountIdEditor: false,
+ enableUsernameEditor: true,
+ enableExtendedEditor: false,
+ },
+ [EditNetworkElementDialogMode.RequiredNetworkElementToUnknownNetworkElements]: {
+ dialogTitle: "Remove from required network elements",
+ dialogDescription: "Do you really want to remove the required element:",
+ applyButtonText: "Remove network element",
+ cancelButtonText: "Cancel",
+ enableMountIdEditor: false,
+ enableUsernameEditor: false,
+ enableExtendedEditor: false,
+ },
+ [EditNetworkElementDialogMode.MountNetworkElementToUnknonwNetworkElements]: {
+ dialogTitle: "Mount to unknown network elements",
+ dialogDescription: "Mount this network element:",
+ applyButtonText: "Mount network element",
+ cancelButtonText: "Cancel",
+ enableMountIdEditor: true,
+ enableUsernameEditor: true,
+ enableExtendedEditor: true,
+ },
+ [EditNetworkElementDialogMode.MountNetworkElementToRequiredNetworkElements]: {
+ dialogTitle: "Mount to required network elements",
+ dialogDescription: "Mount this network element:",
+ applyButtonText: "Mount network element",
+ cancelButtonText: "Cancel",
+ enableMountIdEditor: true,
+ enableUsernameEditor: true,
+ enableExtendedEditor: true,
+ },
+ [EditNetworkElementDialogMode.MountNetworkElement]: {
+ dialogTitle: "Mount network element",
+ dialogDescription: "mount this network element:",
+ applyButtonText: "mount network element",
+ cancelButtonText: "Cancel",
+ enableMountIdEditor: false,
+ enableUsernameEditor: false,
+ enableExtendedEditor: false,
+ },
+ [EditNetworkElementDialogMode.UnmountNetworkElement]: {
+ dialogTitle: "Unmount network element",
+ dialogDescription: "unmount this network element:",
+ applyButtonText: "Unmount network element",
+ cancelButtonText: "Cancel",
+ enableMountIdEditor: false,
+ enableUsernameEditor: false,
+ enableExtendedEditor: false,
+ },
+}
+
+type EditNetworkElementDialogComponentProps = Connect<undefined,typeof mapDispatch> & {
+ mode: EditNetworkElementDialogMode;
+ initialNetworkElement: RequiredNetworkElementType;
+ onClose: () => void;
+};
+
+type EditNetworkElementDialogComponentState = RequiredNetworkElementType & {
+ required: boolean;
+};
+
+class EditNetworkElementDialogComponent extends React.Component<EditNetworkElementDialogComponentProps, EditNetworkElementDialogComponentState> {
+ constructor(props: EditNetworkElementDialogComponentProps) {
+ super(props);
+
+ this.state = {
+ mountId: this.props.initialNetworkElement.mountId,
+ host: this.props.initialNetworkElement.host,
+ port: this.props.initialNetworkElement.port,
+ password: this.props.initialNetworkElement.password,
+ username: this.props.initialNetworkElement.username,
+ required: false
+ };
+ }
+
+ render(): JSX.Element {
+ const setting = settings[this.props.mode];
+ return (
+ <Dialog open={ this.props.mode !== EditNetworkElementDialogMode.None }>
+ <DialogTitle id="form-dialog-title">{ setting.dialogTitle }</DialogTitle>
+ <DialogContent>
+ <DialogContentText>
+ { setting.dialogDescription }
+ </DialogContentText>
+ <TextField disabled={ !setting.enableMountIdEditor } spellCheck={false} autoFocus margin="dense" id="name" label="Name" type="text" fullWidth value={ this.state.mountId } onChange={(event)=>{ this.setState({mountId: event.target.value}); } } />
+ <TextField disabled={ !setting.enableMountIdEditor } spellCheck={false} margin="dense" id="ipaddress" label="IP address" type="text" fullWidth value={ this.state.host } onChange={(event)=>{ this.setState({host: event.target.value}); } }/>
+ <TextField disabled={ !setting.enableMountIdEditor } spellCheck={false} margin="dense" id="netconfport" label="NetConf port" type="number" fullWidth value={ this.state.port.toString() } onChange={(event)=>{ this.setState({port: +event.target.value}); } }/>
+ { setting.enableUsernameEditor && <TextField disabled={ !setting.enableUsernameEditor } spellCheck={ false } margin="dense" id="username" label="Username" type="text" fullWidth value={ this.state.username } onChange={ (event) => { this.setState({ username: event.target.value }); } } /> || null }
+ { setting.enableUsernameEditor && <TextField disabled={ !setting.enableUsernameEditor } spellCheck={ false } margin="dense" id="password" label="Password" type="password" fullWidth value={ this.state.password } onChange={ (event) => { this.setState({ password: event.target.value }); } } /> || null }
+ </DialogContent>
+ <DialogActions>
+ <Button onClick={ (event) => {
+ this.onApply({
+ mountId: this.state.mountId,
+ host: this.state.host,
+ port: this.state.port,
+ username: this.state.username,
+ password: this.state.password
+ });
+ event.preventDefault();
+ event.stopPropagation();
+ } } > { setting.applyButtonText } </Button>
+ <Button onClick={ (event) => {
+ this.onCancel();
+ event.preventDefault();
+ event.stopPropagation();
+ } } color="secondary"> { setting.cancelButtonText } </Button>
+ </DialogActions>
+ </Dialog>
+ )
+ }
+
+ private onApply = (element: RequiredNetworkElementType) => {
+ this.props.onClose && this.props.onClose();
+ switch (this.props.mode) {
+ case EditNetworkElementDialogMode.UnknownNetworkElementToRequiredNetworkElements:
+ element && this.props.addToRequiredNetworkElements(element);
+ break;
+ case EditNetworkElementDialogMode.RequiredNetworkElementToUnknownNetworkElements:
+ element && this.props.removeFromRequiredNetworkElements(element);
+ break;
+ case EditNetworkElementDialogMode.MountNetworkElementToUnknonwNetworkElements:
+ element && this.props.mountNetworkElement(element);
+ break;
+ case EditNetworkElementDialogMode.MountNetworkElementToRequiredNetworkElements:
+ element && this.props.mountAndRquireNetworkElement(element);
+ break;
+ case EditNetworkElementDialogMode.MountNetworkElement:
+ element && this.props.mountNetworkElement(element);
+ break;
+ case EditNetworkElementDialogMode.UnmountNetworkElement:
+ element && this.props.unmountNetworkElement(element);
+ break;
+ }
+ };
+
+ private onCancel = () => {
+ this.props.onClose && this.props.onClose();
+ }
+
+ static getDerivedStateFromProps(props: EditNetworkElementDialogComponentProps, state: EditNetworkElementDialogComponentState & { _initialNetworkElement: RequiredNetworkElementType }): EditNetworkElementDialogComponentState & { _initialNetworkElement: RequiredNetworkElementType } {
+ if (props.initialNetworkElement !== state._initialNetworkElement) {
+ state = {
+ ...state,
+ ...props.initialNetworkElement,
+ _initialNetworkElement: props.initialNetworkElement,
+ };
+ }
+ return state;
+ }
+}
+
+export const EditNetworkElementDialog = connect(undefined, mapDispatch)(EditNetworkElementDialogComponent);
+export default EditNetworkElementDialog;
\ No newline at end of file
diff --git a/sdnr/wt/odlux/apps/connectApp/src/components/requiredNetworkElements.tsx b/sdnr/wt/odlux/apps/connectApp/src/components/requiredNetworkElements.tsx
new file mode 100644
index 0000000..13f5fec
--- /dev/null
+++ b/sdnr/wt/odlux/apps/connectApp/src/components/requiredNetworkElements.tsx
@@ -0,0 +1,194 @@
+import * as React from 'react';
+import { Theme, createStyles, withStyles, WithStyles } from '@material-ui/core/styles';
+
+import AddIcon from '@material-ui/icons/Add';
+import LinkIcon from '@material-ui/icons/Link';
+import LinkOffIcon from '@material-ui/icons/LinkOff';
+import RemoveIcon from '@material-ui/icons/RemoveCircleOutline';
+
+import Button from '@material-ui/core/Button';
+import IconButton from '@material-ui/core/IconButton';
+
+import { MaterialTable, ColumnType, MaterialTableCtorType } from '../../../../framework/src/components/material-table';
+import { IApplicationStoreState } from '../../../../framework/src/store/applicationStore';
+import { connect, Connect, IDispatcher } from '../../../../framework/src/flux/connect';
+import { NavigateToApplication } from '../../../../framework/src/actions/navigationActions';
+
+
+import { RequiredNetworkElementType } from '../models/requiredNetworkElements';
+import { createRequiredNetworkElementsActions, createRequiredNetworkElementsProperties } from '../handlers/requiredNetworkElementsHandler';
+
+import EditNetworkElementDialog, { EditNetworkElementDialogMode } from './editNetworkElementDialog';
+import { Tooltip } from '@material-ui/core';
+import { NetworkElementBaseType } from 'models/networkElementBase';
+
+const styles = (theme: Theme) => createStyles({
+ connectionStatusConnected: {
+ color: 'darkgreen',
+ },
+ connectionStatusConnecting: {
+ color: theme.palette.primary.main,
+ },
+ connectionStatusDisconnected: {
+ color: 'red',
+ },
+ button: {
+ margin: 0,
+ padding: "6px 6px",
+ minWidth: 'unset'
+ },
+ spacer: {
+ marginLeft: theme.spacing.unit,
+ marginRight: theme.spacing.unit,
+ display: "inline"
+ }
+});
+
+const mapProps = (state: IApplicationStoreState) => ({
+ requiredNetworkElementsProperties: createRequiredNetworkElementsProperties(state),
+ mountedNetworkElements: state.connectApp.mountedNetworkElements
+});
+
+const mapDispatch = (dispatcher: IDispatcher) => ({
+ requiredNetworkElementsActions: createRequiredNetworkElementsActions(dispatcher.dispatch),
+ navigateToApplication: (applicationName: string, path?: string) => dispatcher.dispatch(new NavigateToApplication(applicationName, path)),
+});
+
+type RequiredNetworkElementsListComponentProps = WithStyles<typeof styles> & Connect<typeof mapProps, typeof mapDispatch>;
+type RequiredNetworkElementsListComponentState = {
+ networkElementToEdit: RequiredNetworkElementType,
+ networkElementEditorMode: EditNetworkElementDialogMode
+}
+
+const emptyRequireNetworkElement = { mountId: '', host: '', port: 0 };
+
+const RequiredNetworkElementTable = MaterialTable as MaterialTableCtorType<RequiredNetworkElementType>;
+
+export class RequiredNetworkElementsListComponent extends React.Component<RequiredNetworkElementsListComponentProps, RequiredNetworkElementsListComponentState> {
+
+ constructor(props: RequiredNetworkElementsListComponentProps) {
+ super(props);
+
+ this.state = {
+ networkElementToEdit: emptyRequireNetworkElement,
+ networkElementEditorMode: EditNetworkElementDialogMode.None
+ };
+ }
+
+ // private navigationCreator
+
+ render(): JSX.Element {
+ const { classes } = this.props;
+ const { networkElementToEdit } = this.state;
+ const addRequireNetworkElementAction = {
+ icon: AddIcon, tooltip: 'Add', onClick: () => {
+ this.setState({
+ networkElementEditorMode: EditNetworkElementDialogMode.MountNetworkElementToRequiredNetworkElements,
+ networkElementToEdit: emptyRequireNetworkElement,
+ });
+ }
+ };
+ return (
+ <>
+ <RequiredNetworkElementTable customActionButtons={ [addRequireNetworkElementAction] } columns={ [
+ { property: "mountId", title: "Name", type: ColumnType.text },
+ {
+ property: "connectionStatus", title: "Connection Status", type: ColumnType.custom, disableFilter: true, disableSorting: true, customControl: ({ rowData }) => {
+ const unknownNetworkElement = this.props.mountedNetworkElements.elements.find(el => el.mountId === rowData.mountId);
+ const connectionStatus = unknownNetworkElement && unknownNetworkElement.connectionStatus || 'disconnected';
+ const cssClasses = connectionStatus === "connected"
+ ? classes.connectionStatusConnected
+ : connectionStatus === "disconnected"
+ ? classes.connectionStatusDisconnected
+ : classes.connectionStatusConnecting
+ return <div className={ cssClasses } >{ connectionStatus } </div>
+
+ }
+ },
+ { property: "host", title: "Host", type: ColumnType.text },
+ { property: "port", title: "Port", type: ColumnType.text },
+ // { property: "username", title: "Username", type: ColumnType.text },
+ // { property: "password", title: "Password", type: ColumnType.text },
+ {
+ property: "actions", title: "Actions", type: ColumnType.custom, customControl: ({ rowData }) => {
+ const unknownNetworkElement = this.props.mountedNetworkElements.elements.find(el => el.mountId === rowData.mountId);
+ const connectionStatus = unknownNetworkElement && unknownNetworkElement.connectionStatus || 'disconnected';
+ return (
+ <>
+ <div className={ classes.spacer }>
+ <Tooltip title={ "Mount" } ><IconButton className={ classes.button } onClick={ event => this.onOpenMountdNetworkElementsDialog(event, rowData) }><LinkIcon /></IconButton></Tooltip>
+ <Tooltip title={ "Unmount" } ><IconButton className={ classes.button } onClick={ event => this.onOpenUnmountdNetworkElementsDialog(event, rowData) }><LinkOffIcon /></IconButton></Tooltip>
+ <Tooltip title={ "Remove" } ><IconButton className={ classes.button } onClick={ event => this.onOpenRemoveRequiredNetworkElementDialog(event, rowData) } ><RemoveIcon /></IconButton></Tooltip>
+ </div>
+ <div className={ classes.spacer }>
+ <Tooltip title={ "Info" } ><Button className={ classes.button } >I</Button></Tooltip>
+ </div>
+ <div className={ classes.spacer }>
+ <Tooltip title={ "Fault" } ><Button className={ classes.button } onClick={ this.navigateToApplicationHandlerCreator("faultApp", rowData) } >F</Button></Tooltip>
+ <Tooltip title={ "Configure" } ><Button className={ classes.button } onClick={ this.navigateToApplicationHandlerCreator("configureApp", rowData)} >C</Button></Tooltip>
+ <Tooltip title={ "Accounting " } ><Button className={ classes.button } onClick={ this.navigateToApplicationHandlerCreator("accountingApp", rowData) }>A</Button></Tooltip>
+ <Tooltip title={ "Performance" } ><Button className={ classes.button } onClick={ this.navigateToApplicationHandlerCreator("performanceApp", rowData) }>P</Button></Tooltip>
+ <Tooltip title={ "Security" } ><Button className={ classes.button } onClick={ this.navigateToApplicationHandlerCreator("securityApp", rowData) }>S</Button></Tooltip>
+ </div>
+ </>
+ )
+ }
+ },
+ ] } idProperty="mountId" { ...this.props.requiredNetworkElementsActions } { ...this.props.requiredNetworkElementsProperties } asynchronus >
+ </RequiredNetworkElementTable>
+ <EditNetworkElementDialog
+ initialNetworkElement={ networkElementToEdit }
+ mode={ this.state.networkElementEditorMode }
+ onClose={ this.onCloseEditNetworkElementDialog }
+ />
+ </>
+ );
+ };
+
+ public componentDidMount() {
+ this.props.requiredNetworkElementsActions.onRefresh();
+ }
+
+ private onOpenRemoveRequiredNetworkElementDialog = (event: React.MouseEvent<HTMLElement>, element: RequiredNetworkElementType) => {
+ this.setState({
+ networkElementToEdit: element,
+ networkElementEditorMode: EditNetworkElementDialogMode.RequiredNetworkElementToUnknownNetworkElements
+ });
+ event.preventDefault();
+ event.stopPropagation();
+ }
+
+ private onOpenUnmountdNetworkElementsDialog = (event: React.MouseEvent<HTMLElement>, element: RequiredNetworkElementType) => {
+ this.setState({
+ networkElementToEdit: element,
+ networkElementEditorMode: EditNetworkElementDialogMode.UnmountNetworkElement
+ });
+ event.preventDefault();
+ event.stopPropagation();
+ }
+
+ private onOpenMountdNetworkElementsDialog = (event: React.MouseEvent<HTMLElement>, element: RequiredNetworkElementType) => {
+ this.setState({
+ networkElementToEdit: element,
+ networkElementEditorMode: EditNetworkElementDialogMode.MountNetworkElement
+ });
+ event.preventDefault();
+ event.stopPropagation();
+ }
+
+ private onCloseEditNetworkElementDialog = () => {
+ this.setState({
+ networkElementEditorMode: EditNetworkElementDialogMode.None,
+ networkElementToEdit: emptyRequireNetworkElement,
+ });
+ }
+
+ private navigateToApplicationHandlerCreator = (applicationName: string, element: NetworkElementBaseType) => (event: React.MouseEvent<HTMLElement>) => {
+ this.props.navigateToApplication(applicationName, element.mountId);
+ event.preventDefault();
+ event.stopPropagation();
+ }
+}
+
+export const RequiredNetworkElementsList = withStyles(styles)(connect(mapProps, mapDispatch)(RequiredNetworkElementsListComponent));
+export default RequiredNetworkElementsList;
\ No newline at end of file
diff --git a/sdnr/wt/odlux/apps/connectApp/src/components/unknownNetworkElements.tsx b/sdnr/wt/odlux/apps/connectApp/src/components/unknownNetworkElements.tsx
new file mode 100644
index 0000000..4321031
--- /dev/null
+++ b/sdnr/wt/odlux/apps/connectApp/src/components/unknownNetworkElements.tsx
@@ -0,0 +1,196 @@
+import * as React from 'react';
+import { Theme, createStyles, WithStyles, withStyles, Tooltip } from '@material-ui/core';
+
+import AddIcon from '@material-ui/icons/Add';
+import LinkOffIcon from '@material-ui/icons/LinkOff';
+import AddCircleIcon from '@material-ui/icons/AddCircleOutline';
+
+import Button from '@material-ui/core/Button';
+import IconButton from '@material-ui/core/IconButton';
+
+import { IApplicationStoreState } from '../../../../framework/src/store/applicationStore';
+import { MaterialTable, ColumnType, MaterialTableCtorType } from '../../../../framework/src/components/material-table';
+import { Connect, connect, IDispatcher } from '../../../../framework/src/flux/connect';
+import { NavigateToApplication } from '../../../../framework/src/actions/navigationActions';
+
+import { RequiredNetworkElementType } from '../models/requiredNetworkElements';
+import { IMountedNetworkElementsState } from '../handlers/mountedNetworkElementsHandler';
+import EditNetworkElementDialog, { EditNetworkElementDialogMode } from './editNetworkElementDialog';
+import { NetworkElementBaseType } from 'models/networkElementBase';
+
+
+const styles = (theme: Theme) => createStyles({
+ button: {
+ margin: 0,
+ padding: "6px 6px",
+ minWidth: 'unset'
+ },
+ spacer: {
+ marginLeft: theme.spacing.unit,
+ marginRight: theme.spacing.unit,
+ display: "inline"
+ }
+});
+
+const mapProps = ({ connectApp: state }: IApplicationStoreState) => ({
+ mountedNetworkElements: state.mountedNetworkElements
+});
+
+const mapDispatch = (dispatcher: IDispatcher) => ({
+ navigateToApplication: (applicationName: string, path?: string) => dispatcher.dispatch(new NavigateToApplication(applicationName, path)),
+});
+type UnknownNetworkElementDisplayType = NetworkElementBaseType & {
+ connectionStatus: string,
+ coreModelRev: string,
+ airInterfaceRev: string
+}
+
+type UnknownNetworkElementsListProps = WithStyles<typeof styles> & Connect<typeof mapProps, typeof mapDispatch> & {}
+
+type UnknownNetworkElementsListState = {
+
+ unknownNetworkElements: UnknownNetworkElementDisplayType[];
+
+ networkElementToEdit: RequiredNetworkElementType;
+ networkElementEditorMode: EditNetworkElementDialogMode;
+}
+
+
+const emptyRequireNetworkElement = { mountId: '', host: '', port: 0 };
+const UnknownNetworkElementTable = MaterialTable as MaterialTableCtorType<UnknownNetworkElementDisplayType>;
+export class UnknownNetworkElementsListComponent extends React.Component<UnknownNetworkElementsListProps, UnknownNetworkElementsListState> {
+
+ constructor(props: UnknownNetworkElementsListProps) {
+ super(props);
+
+ this.state = {
+ unknownNetworkElements: [],
+ networkElementToEdit: emptyRequireNetworkElement,
+ networkElementEditorMode: EditNetworkElementDialogMode.None,
+ };
+ }
+
+ static getDerivedStateFromProps(props: UnknownNetworkElementsListProps, state: UnknownNetworkElementsListState & { _mountedNetworkElements: IMountedNetworkElementsState }) {
+ if (props.mountedNetworkElements != state._mountedNetworkElements) {
+ state.unknownNetworkElements = props.mountedNetworkElements.elements.filter(element => !element.required).map(element => {
+
+ // handle onfCoreModelRevision
+ const onfCoreModelRevision = element.capabilities.find((cap) => {
+ return cap.module === 'core-model' || cap.module === 'CoreModel-CoreNetworkModule-ObjectClasses' ;
+ });
+ const onfAirInterfaceRevision = element.capabilities.find((cap) => {
+ return cap.module === 'microwave-model' || cap.module === 'MicrowaveModel-ObjectClasses-AirInterface' ;
+ });
+ return {
+ mountId: element.mountId,
+ host: element.host,
+ port: element.port,
+ connectionStatus: element.connectionStatus,
+ coreModelRev: onfCoreModelRevision && onfCoreModelRevision.revision || 'unknown',
+ airInterfaceRev: onfAirInterfaceRevision && onfAirInterfaceRevision.revision || 'unknown'
+ }
+ }
+ );
+ }
+ return state;
+ }
+
+ render(): JSX.Element {
+ const { classes } = this.props;
+ const { networkElementToEdit, networkElementEditorMode, unknownNetworkElements } = this.state;
+ const addRequireNetworkElementAction = {
+ icon: AddIcon, tooltip: 'Add', onClick: () => {
+ this.setState({
+ networkElementEditorMode: EditNetworkElementDialogMode.MountNetworkElementToUnknonwNetworkElements,
+ networkElementToEdit: emptyRequireNetworkElement,
+ });
+ }
+ };
+ return (
+ <>
+ <UnknownNetworkElementTable customActionButtons={ [addRequireNetworkElementAction] } asynchronus rows={ unknownNetworkElements } columns={ [
+ { property: "mountId", title: "Name", type: ColumnType.text },
+ { property: "connectionStatus", title: "Connection Status", type: ColumnType.text },
+ { property: "host", title: "Host", type: ColumnType.text },
+ { property: "port", title: "Port", type: ColumnType.text },
+ { property: "coreModelRev", title: "Core Model", type: ColumnType.text },
+ { property: "airInterfaceRev", title: "Air interface", type: ColumnType.text },
+ {
+ property: "actions", title: "Actions", type: ColumnType.custom, customControl: ({ rowData }) => (
+ <>
+ <div className={ classes.spacer }>
+ <Tooltip title={ "Unmount" } ><IconButton className={ classes.button } onClick={ event => this.onOpenUnmountdNetworkElementsDialog(event, rowData) } ><LinkOffIcon /></IconButton></Tooltip>
+ <Tooltip title={ "Add to required" } ><IconButton className={ classes.button } onClick={ event => this.onOpenAddToRequiredNetworkElementsDialog(event, rowData) } ><AddCircleIcon /></IconButton></Tooltip>
+ </div>
+ <div className={ classes.spacer }>
+ <Tooltip title={ "Info" } ><Button className={ classes.button } >I</Button></Tooltip>
+ </div>
+ <div className={ classes.spacer }>
+ <div className={ classes.spacer }>
+ <Tooltip title={ "Fault" } ><Button className={ classes.button } onClick={ this.navigateToApplicationHandlerCreator("faultApp", rowData) } >F</Button></Tooltip>
+ <Tooltip title={ "Configure" } ><Button className={ classes.button } onClick={ this.navigateToApplicationHandlerCreator("configureApp", rowData) } >C</Button></Tooltip>
+ <Tooltip title={ "Accounting " } ><Button className={ classes.button } onClick={ this.navigateToApplicationHandlerCreator("accountingApp", rowData) }>A</Button></Tooltip>
+ <Tooltip title={ "Performance" } ><Button className={ classes.button } onClick={ this.navigateToApplicationHandlerCreator("performanceApp", rowData) }>P</Button></Tooltip>
+ <Tooltip title={ "Security" } ><Button className={ classes.button } onClick={ this.navigateToApplicationHandlerCreator("securityApp", rowData) }>S</Button></Tooltip>
+ </div>
+ </div>
+ </>
+ )
+ },
+ ] } idProperty="mountId" >
+ </UnknownNetworkElementTable>
+
+ <EditNetworkElementDialog
+ mode={ networkElementEditorMode }
+ initialNetworkElement={ networkElementToEdit }
+ onClose={ this.onCloseEditNetworkElementDialog }
+ />
+ </>
+ );
+ };
+
+ private onOpenAddToRequiredNetworkElementsDialog = (event: React.MouseEvent<HTMLElement>, element: UnknownNetworkElementDisplayType) => {
+ this.setState({
+ networkElementToEdit: {
+ mountId: element.mountId,
+ host: element.host,
+ port: element.port,
+ username: 'admin',
+ password: 'admin',
+ },
+ networkElementEditorMode: EditNetworkElementDialogMode.UnknownNetworkElementToRequiredNetworkElements
+ });
+ event.preventDefault();
+ event.stopPropagation();
+ }
+
+ private onOpenUnmountdNetworkElementsDialog = (event: React.MouseEvent<HTMLElement>, element: UnknownNetworkElementDisplayType) => {
+ this.setState({
+ networkElementToEdit: {
+ mountId: element.mountId,
+ host: element.host,
+ port: element.port
+ },
+ networkElementEditorMode: EditNetworkElementDialogMode.UnmountNetworkElement
+ });
+ event.preventDefault();
+ event.stopPropagation();
+ }
+
+ private onCloseEditNetworkElementDialog = () => {
+ this.setState({
+ networkElementEditorMode: EditNetworkElementDialogMode.None,
+ networkElementToEdit: emptyRequireNetworkElement,
+ });
+ }
+
+ private navigateToApplicationHandlerCreator = (applicationName: string, element: NetworkElementBaseType) => (event: React.MouseEvent<HTMLElement>) => {
+ this.props.navigateToApplication(applicationName, element.mountId);
+ event.preventDefault();
+ event.stopPropagation();
+ }
+
+}
+
+export const UnknownNetworkElementsList = withStyles(styles)(connect(mapProps, mapDispatch)(UnknownNetworkElementsListComponent));
+export default UnknownNetworkElementsList;
diff --git a/sdnr/wt/odlux/apps/connectApp/src/handlers/connectAppRootHandler.tsx b/sdnr/wt/odlux/apps/connectApp/src/handlers/connectAppRootHandler.tsx
new file mode 100644
index 0000000..26d02c4
--- /dev/null
+++ b/sdnr/wt/odlux/apps/connectApp/src/handlers/connectAppRootHandler.tsx
@@ -0,0 +1,26 @@
+import { combineActionHandler } from '../../../../framework/src/flux/middleware';
+import { IRequiredNetworkElementsState, requiredNetworkElementsActionHandler } from './requiredNetworkElementsHandler';
+import { IMountedNetworkElementsState, mountedNetworkElementsActionHandler } from './mountedNetworkElementsHandler';
+import { IConnectionStatusLogState, connectionStatusLogActionHandler } from './connectionStatusLogHandler';
+
+export interface IConnectAppStoreState {
+
+ requiredNetworkElements: IRequiredNetworkElementsState;
+ mountedNetworkElements: IMountedNetworkElementsState;
+ connectionStatusLog: IConnectionStatusLogState;
+}
+
+declare module '../../../../framework/src/store/applicationStore' {
+ interface IApplicationStoreState {
+ connectApp: IConnectAppStoreState
+ }
+}
+
+const actionHandlers = {
+ requiredNetworkElements: requiredNetworkElementsActionHandler,
+ mountedNetworkElements: mountedNetworkElementsActionHandler,
+ connectionStatusLog: connectionStatusLogActionHandler
+};
+
+export const connectAppRootHandler = combineActionHandler <IConnectAppStoreState>(actionHandlers);
+export default connectAppRootHandler;
diff --git a/sdnr/wt/odlux/apps/connectApp/src/handlers/connectionStatusLogHandler.tsx b/sdnr/wt/odlux/apps/connectApp/src/handlers/connectionStatusLogHandler.tsx
new file mode 100644
index 0000000..140020e
--- /dev/null
+++ b/sdnr/wt/odlux/apps/connectApp/src/handlers/connectionStatusLogHandler.tsx
@@ -0,0 +1,30 @@
+import { createExternal,IExternalTableState } from '../../../../framework/src/components/material-table/utilities';
+import { createSearchDataHandler } from '../../../../framework/src/utilities/elasticSearch';
+
+import { ConnectionStatusLogType } from '../models/connectionStatusLog';
+export interface IConnectionStatusLogState extends IExternalTableState<ConnectionStatusLogType> { }
+
+// create eleactic search material data fetch handler
+const connectionStatusLogSearchHandler = createSearchDataHandler<{ event: ConnectionStatusLogType }, ConnectionStatusLogType>('sdnevents_v1/eventlog', null,
+ (event) => ({
+ _id: event._id,
+ timeStamp: event._source.event.timeStamp,
+ objectId: event._source.event.objectId,
+ type: event._source.event.type,
+ elementStatus: event._source.event.type === 'ObjectCreationNotificationXml'
+ ? 'connected'
+ : event._source.event.type === 'ObjectDeletionNotificationXml'
+ ? 'disconnected'
+ : 'unknown'
+ }),
+ (name) => `event.${ name }`);
+
+export const {
+ actionHandler: connectionStatusLogActionHandler,
+ createActions: createConnectionStatusLogActions,
+ createProperties: createConnectionStatusLogProperties,
+ reloadAction: connectionStatusLogReloadAction,
+
+ // set value action, to change a value
+} = createExternal<ConnectionStatusLogType>(connectionStatusLogSearchHandler, appState => appState.connectApp.connectionStatusLog);
+
diff --git a/sdnr/wt/odlux/apps/connectApp/src/handlers/mountedNetworkElementsHandler.tsx b/sdnr/wt/odlux/apps/connectApp/src/handlers/mountedNetworkElementsHandler.tsx
new file mode 100644
index 0000000..70b4d8f
--- /dev/null
+++ b/sdnr/wt/odlux/apps/connectApp/src/handlers/mountedNetworkElementsHandler.tsx
@@ -0,0 +1,81 @@
+import { IActionHandler } from '../../../../framework/src/flux/action';
+
+import {
+ AddMountedNetworkElement,
+ AllMountedNetworkElementsLoadedAction,
+ LoadAllMountedNetworkElementsAction,
+ RemoveMountedNetworkElement,
+ UpdateConnectionStateMountedNetworkElement,
+ UpdateRequiredMountedNetworkElement
+} from '../actions/mountedNetworkElementsActions';
+
+import { MountedNetworkElementType } from '../models/mountedNetworkElements';
+
+export interface IMountedNetworkElementsState {
+ elements: MountedNetworkElementType[];
+ busy: boolean;
+}
+
+const mountedNetworkElementsStateInit: IMountedNetworkElementsState = {
+ elements: [],
+ busy: false
+};
+
+export const mountedNetworkElementsActionHandler: IActionHandler<IMountedNetworkElementsState> = (state = mountedNetworkElementsStateInit, action) => {
+ if (action instanceof LoadAllMountedNetworkElementsAction) {
+
+ state = {
+ ...state,
+ busy: true
+ };
+
+ } else if (action instanceof AllMountedNetworkElementsLoadedAction) {
+ if (!action.error && action.mountedNetworkElements) {
+ state = {
+ ...state,
+ elements: action.mountedNetworkElements,
+ busy: false
+ };
+ } else {
+ state = {
+ ...state,
+ busy: false
+ };
+ }
+ } else if (action instanceof AddMountedNetworkElement) {
+ action.mountedNetworkElement && (state = {
+ ...state,
+ elements: [...state.elements, action.mountedNetworkElement],
+ });
+ } else if (action instanceof RemoveMountedNetworkElement) {
+ state = {
+ ...state,
+ elements: state.elements.filter(e => e.mountId !== action.mountId),
+ };
+ } else if (action instanceof UpdateConnectionStateMountedNetworkElement) {
+ const index = state.elements.findIndex(el => el.mountId === action.mountId);
+ if (index > -1) {
+ state = {
+ ...state,
+ elements: [
+ ...state.elements.slice(0, index),
+ { ...state.elements[index], connectionStatus: action.mountId },
+ ...state.elements.slice(index + 1)
+ ]
+ }
+ }
+ } else if (action instanceof UpdateRequiredMountedNetworkElement) {
+ const index = state.elements.findIndex(el => el.mountId === action.mountId);
+ if (index > -1 && (state.elements[index].required !== action.required)) {
+ state = {
+ ...state,
+ elements: [
+ ...state.elements.slice(0, index),
+ { ...state.elements[index], required: action.required },
+ ...state.elements.slice(index + 1)
+ ]
+ }
+ }
+ };
+ return state;
+};
\ No newline at end of file
diff --git a/sdnr/wt/odlux/apps/connectApp/src/handlers/requiredNetworkElementsHandler.tsx b/sdnr/wt/odlux/apps/connectApp/src/handlers/requiredNetworkElementsHandler.tsx
new file mode 100644
index 0000000..b2d5477
--- /dev/null
+++ b/sdnr/wt/odlux/apps/connectApp/src/handlers/requiredNetworkElementsHandler.tsx
@@ -0,0 +1,18 @@
+import { createExternal,IExternalTableState } from '../../../../framework/src/components/material-table/utilities';
+import { createSearchDataHandler } from '../../../../framework/src/utilities/elasticSearch';
+
+import { RequiredNetworkElementType } from '../models/requiredNetworkElements';
+export interface IRequiredNetworkElementsState extends IExternalTableState<RequiredNetworkElementType> { }
+
+// create eleactic search material data fetch handler
+const requiredNetworkElementsSearchHandler = createSearchDataHandler<RequiredNetworkElementType>('mwtn/required-networkelement');
+
+export const {
+ actionHandler: requiredNetworkElementsActionHandler,
+ createActions: createRequiredNetworkElementsActions,
+ createProperties: createRequiredNetworkElementsProperties,
+ reloadAction: requiredNetworkElementsReloadAction,
+
+ // set value action, to change a value
+} = createExternal<RequiredNetworkElementType>(requiredNetworkElementsSearchHandler, appState => appState.connectApp.requiredNetworkElements);
+
diff --git a/sdnr/wt/odlux/apps/connectApp/src/index.html b/sdnr/wt/odlux/apps/connectApp/src/index.html
new file mode 100644
index 0000000..c28708a
--- /dev/null
+++ b/sdnr/wt/odlux/apps/connectApp/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>Document</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", "faultApp"], function (app, connectApp, faultApp) {
+ connectApp.register();
+ faultApp.register();
+ app("./app.tsx")
+ });
+ </script>
+</body>
+
+</html>
\ No newline at end of file
diff --git a/sdnr/wt/odlux/apps/connectApp/src/models/connectionStatusLog.ts b/sdnr/wt/odlux/apps/connectApp/src/models/connectionStatusLog.ts
new file mode 100644
index 0000000..d3aa203
--- /dev/null
+++ b/sdnr/wt/odlux/apps/connectApp/src/models/connectionStatusLog.ts
@@ -0,0 +1,9 @@
+
+export type ConnectionStatusLogType = {
+ _id: string;
+ elementStatus: string;
+ timeStamp: string;
+ objectId: string;
+ type: string;
+}
+
diff --git a/sdnr/wt/odlux/apps/connectApp/src/models/mountedNetworkElements.ts b/sdnr/wt/odlux/apps/connectApp/src/models/mountedNetworkElements.ts
new file mode 100644
index 0000000..4ab7c8e
--- /dev/null
+++ b/sdnr/wt/odlux/apps/connectApp/src/models/mountedNetworkElements.ts
@@ -0,0 +1,11 @@
+import { NetworkElementBaseType } from "./networkElementBase";
+
+/**
+* Represents data of an mounted network elements.
+*/
+export type MountedNetworkElementType = NetworkElementBaseType & {
+ connectionStatus: string;
+ required: boolean;
+ capabilities: { module: string, revision: string }[];
+};
+
diff --git a/sdnr/wt/odlux/apps/connectApp/src/models/networkElementBase.ts b/sdnr/wt/odlux/apps/connectApp/src/models/networkElementBase.ts
new file mode 100644
index 0000000..85390be
--- /dev/null
+++ b/sdnr/wt/odlux/apps/connectApp/src/models/networkElementBase.ts
@@ -0,0 +1,5 @@
+export type NetworkElementBaseType = {
+ mountId: string,
+ host: string,
+ port: number,
+}
\ No newline at end of file
diff --git a/sdnr/wt/odlux/apps/connectApp/src/models/requiredNetworkElements.ts b/sdnr/wt/odlux/apps/connectApp/src/models/requiredNetworkElements.ts
new file mode 100644
index 0000000..08d1f91
--- /dev/null
+++ b/sdnr/wt/odlux/apps/connectApp/src/models/requiredNetworkElements.ts
@@ -0,0 +1,10 @@
+import { NetworkElementBaseType } from "./networkElementBase";
+
+/**
+* Represents data of Required Network Elements.
+*/
+export type RequiredNetworkElementType = NetworkElementBaseType & {
+ username?: string;
+ password?: string;
+}
+
diff --git a/sdnr/wt/odlux/apps/connectApp/src/models/topologyNetconf.ts b/sdnr/wt/odlux/apps/connectApp/src/models/topologyNetconf.ts
new file mode 100644
index 0000000..5cf29c7
--- /dev/null
+++ b/sdnr/wt/odlux/apps/connectApp/src/models/topologyNetconf.ts
@@ -0,0 +1,36 @@
+export interface UnavailableCapability {
+ capability: string;
+ "failure-reason": string;
+}
+
+export interface NetconfNodeTopologyUnavailableCapabilities {
+ "unavailable-capability": UnavailableCapability[];
+}
+
+export interface AvailableCapability {
+ "capability-origin": string;
+ capability: string;
+}
+
+export interface NetconfNodeTopologyAvailableCapabilities {
+ "available-capability": AvailableCapability[];
+}
+
+export interface NetconfNodeTopologyClusteredConnectionStatus {
+ "netconf-master-node": string
+}
+
+export interface TopologyNode {
+ "node-id": string;
+ "netconf-node-topology:clustered-connection-status": NetconfNodeTopologyClusteredConnectionStatus;
+ "netconf-node-topology:unavailable-capabilities": NetconfNodeTopologyUnavailableCapabilities;
+ "netconf-node-topology:available-capabilities": NetconfNodeTopologyAvailableCapabilities;
+ "netconf-node-topology:host": string;
+ "netconf-node-topology:connection-status": string;
+ "netconf-node-topology:port": number;
+}
+
+export interface Topology {
+ "topology-id": string;
+ node: TopologyNode[];
+}
diff --git a/sdnr/wt/odlux/apps/connectApp/src/plugin.tsx b/sdnr/wt/odlux/apps/connectApp/src/plugin.tsx
new file mode 100644
index 0000000..4e61c32
--- /dev/null
+++ b/sdnr/wt/odlux/apps/connectApp/src/plugin.tsx
@@ -0,0 +1,39 @@
+
+import { faPlug } from '@fortawesome/free-solid-svg-icons';
+
+import applicationManager from '../../../framework/src/services/applicationManager';
+import { subscribe, IFormatedMessage } from '../../../framework/src/services/notificationService';
+
+import connectAppRootHandler from './handlers/connectAppRootHandler';
+import ConnectApplication from './views/connectView';
+import { RemoveMountedNetworkElement, addMountedNetworkElementAsyncActionCreator } from './actions/mountedNetworkElementsActions' ;
+import { AddSnackbarNotification } from '../../../framework/src/actions/snackbarActions';
+
+type ObjectNotification = {
+ counter: string;
+ nodeName: string;
+ objectId: string;
+ timeStamp: string;
+}
+
+export function register() {
+ const applicationApi = applicationManager.registerApplication({
+ name: "connectApp",
+ icon: faPlug,
+ rootComponent: ConnectApplication,
+ rootActionHandler: connectAppRootHandler,
+ menuEntry: "Connect App"
+ });
+
+ // subscribe to the websocket notifications
+ subscribe<ObjectNotification & IFormatedMessage>(["ObjectCreationNotification", "ObjectDeletionNotification"], (msg => {
+ const store = applicationApi && applicationApi.applicationStore;
+ if (msg && msg.notifType === "ObjectCreationNotification" && store) {
+ store.dispatch(addMountedNetworkElementAsyncActionCreator(msg.objectId));
+ store.dispatch(new AddSnackbarNotification({ message: `Adding network element [${ msg.objectId }]`, options: { variant: 'info' } }));
+ } else if (msg && msg.notifType === "ObjectDeletionNotification" && store) {
+ store.dispatch(new AddSnackbarNotification({ message: `Removing network element [${ msg.objectId }]`, options: { variant: 'info' } }));
+ store.dispatch(new RemoveMountedNetworkElement(msg.objectId));
+ }
+ }));
+}
\ No newline at end of file
diff --git a/sdnr/wt/odlux/apps/connectApp/src/services/connectService.ts b/sdnr/wt/odlux/apps/connectApp/src/services/connectService.ts
new file mode 100644
index 0000000..0adcd49
--- /dev/null
+++ b/sdnr/wt/odlux/apps/connectApp/src/services/connectService.ts
@@ -0,0 +1,204 @@
+import { RequiredNetworkElementType } from '../models/requiredNetworkElements';
+import { MountedNetworkElementType } from 'models/mountedNetworkElements';
+import { Topology, TopologyNode } from 'models/topologyNetconf';
+
+import { requestRest } from '../../../../framework/src/services/restService';
+import { Result, HitEntry } from '../../../../framework/src/models/elasticSearch';
+
+
+/**
+ * Represents a web api accessor service for all Network Elements actions.
+ */
+class ConnectService {
+ /**
+ * Gets all known required network elements from the backend.
+ */
+ public async getAllRequiredNetworkElements(): Promise<(RequiredNetworkElementType & { _id: string })[] | null> {
+ const path = 'database/mwtn/required-networkelement/_search';
+ const query = { "query": { "match_all": {} } };
+
+ const result = await requestRest<Result<RequiredNetworkElementType>>(path, { method: "POST", body: JSON.stringify(query) });
+ return result && result.hits && result.hits.hits && result.hits.hits.map(ne => ({
+ _id: ne._id,
+ mountId: ne._source && ne._source.mountId,
+ host: ne._source && ne._source.host,
+ port: ne._source && ne._source.port,
+ username: ne._source && ne._source.username,
+ password: ne._source && ne._source.password,
+ })) || null;
+ }
+
+ public async getRequiredNetworkElementByMountId(mountId:string): Promise<(RequiredNetworkElementType & { _id: string }) | null> {
+ const path = `database/mwtn/required-networkelement/${mountId}`;
+
+ const result = await requestRest<HitEntry<RequiredNetworkElementType> & { found: boolean }>(path, { method: "GET" });
+ return result && result.found && result._source && {
+ _id: result._id,
+ mountId: result._source.mountId,
+ host: result._source.host,
+ port: result._source.port,
+ username: result._source.username,
+ password: result._source.password,
+ } || null;
+
+ }
+
+ /**
+ * Inserts data into the required network elements table.
+ */
+ public async insertRequiredNetworkElement(element: RequiredNetworkElementType): Promise<RequiredNetworkElementType | null> {
+ const path = `database/mwtn/required-networkelement/${ element.mountId }`;
+ const result = await requestRest<RequiredNetworkElementType>(path, { method: "POST", body: JSON.stringify(element) });
+ return result || null;
+ }
+
+ /**
+ * Deletes data from the Required Network Elements backend.
+ */
+ public async deleteRequiredNetworkElement(element: RequiredNetworkElementType): Promise<RequiredNetworkElementType | null> {
+ const path = `database/mwtn/required-networkelement/${ element.mountId }`;
+ const result = await requestRest<RequiredNetworkElementType>(path, { method: "DELETE", body: JSON.stringify(element) });
+ return result || null;
+ }
+
+
+
+ private static mapTopologyNode = (mountPoint: TopologyNode, required: boolean ) => {
+ // handle onfCapabilities
+ let onfCapabilities: { module: string, revision: string }[] | undefined = undefined;
+ let onfCoreModelRevision: string[] | undefined = undefined;
+ let onfAirInterfaceRevision: string[] | undefined = undefined;
+
+ const capId = 'netconf-node-topology:available-capabilities';
+ if (mountPoint[capId] && mountPoint[capId]['available-capability']) {
+ onfCapabilities = mountPoint[capId]['available-capability'].filter((cap) => {
+ return cap.capability.includes('?revision=');
+ }).map((cap) => {
+ return {
+ module: cap.capability.split(')')[1],
+ revision: cap.capability.split('?revision=')[1].substring(0, 10)
+ };
+ }).sort((a, b) => {
+ if (a.module < b.module) return -1;
+ if (a.module > b.module) return 1;
+ return 0;
+ });
+ }
+
+ // handle clustered-connection-status
+ const statusId = 'netconf-node-topology:clustered-connection-status';
+ let client = 'localhost';
+
+ if (mountPoint[statusId] && mountPoint[statusId]['netconf-master-node']) {
+ let node = mountPoint[statusId]['netconf-master-node'];
+ node = node.substring(node.indexOf('@'));
+ client = node.substring(1, node.indexOf(':'));
+ }
+ const mountId = mountPoint["node-id"];
+ return {
+ mountId: mountId,
+ host: mountPoint["netconf-node-topology:host"],
+ port: mountPoint["netconf-node-topology:port"],
+ connectionStatus: mountPoint['netconf-node-topology:connection-status'],
+ capabilities: onfCapabilities || [],
+ required: required,
+ client
+ }
+ }
+
+ /** Get all mounted network elements and fills the property required according to the database contents. */
+ public async getMountedNetworkElementsList(): Promise<MountedNetworkElementType[] | null> {
+ const path = 'restconf/operational/network-topology:network-topology/topology/topology-netconf';
+
+ const topologyRequestPomise = requestRest<{ topology: Topology[] | null }>(path, { method: "GET" }, true);
+ const requiredNetworkElementsPromise = this.getAllRequiredNetworkElements();
+
+ const [netconfResponse, requiredNetworkElements] = await Promise.all([topologyRequestPomise, requiredNetworkElementsPromise]);
+
+ // process topologyNetconf (get all known network elements)
+ const topologyNetconf = netconfResponse && netconfResponse.topology && netconfResponse.topology.find(topology => topology["topology-id"] === 'topology-netconf');
+ let mountPoints = topologyNetconf && topologyNetconf.node && topologyNetconf.node.filter(
+ mountPoint => mountPoint['node-id'] !== 'controller-config').map(mountedElement => {
+ const required = requiredNetworkElements && requiredNetworkElements.some(
+ requiredElement => requiredElement.mountId === mountedElement["node-id"]);
+ return ConnectService.mapTopologyNode(mountedElement, !!required);
+ });
+
+ return mountPoints || [];
+ }
+
+ /** Get one mounted network element. */
+ public async getMountedNetworkElementByMountId(mountId: string): Promise<MountedNetworkElementType | null> {
+ const path = 'restconf/operational/network-topology:network-topology/topology/topology-netconf/node/' + mountId;
+ const getMountedNetworkElementByMountIdPromise = requestRest<{ node: TopologyNode[] | null }>(path, { method: "GET" }, true);
+ const getRequiredNetworkElementByMountIdPromise = this.getRequiredNetworkElementByMountId(mountId);
+
+ const [mountedNetworkElement, requiredNetworkElement] = await Promise.all([getMountedNetworkElementByMountIdPromise, getRequiredNetworkElementByMountIdPromise]);
+ return mountedNetworkElement && mountedNetworkElement.node && ConnectService.mapTopologyNode(mountedNetworkElement.node[0], requiredNetworkElement && requiredNetworkElement.mountId === mountedNetworkElement.node[0]["node-id"] || false) || null;
+ }
+
+ /** Mounts an required network element. */
+ public async mountNetworkElement(networkElement: RequiredNetworkElementType): Promise<boolean> {
+ const path = 'restconf/config/network-topology:network-topology/topology/topology-netconf/node/' + networkElement.mountId;
+ const mountXml = [
+ '<node xmlns="urn:TBD:params:xml:ns:yang:network-topology">',
+ `<node-id>${ networkElement.mountId }</node-id>`,
+ `<host xmlns="urn:opendaylight:netconf-node-topology">${ networkElement.host }</host>`,
+ `<port xmlns="urn:opendaylight:netconf-node-topology">${ networkElement.port }</port>`,
+ `<username xmlns="urn:opendaylight:netconf-node-topology">${ networkElement.username }</username>`,
+ `<password xmlns="urn:opendaylight:netconf-node-topology">${ networkElement.password }</password>`,
+ ' <tcp-only xmlns="urn:opendaylight:netconf-node-topology">false</tcp-only>',
+
+ ' <!-- non-mandatory fields with default values, you can safely remove these if you do not wish to override any of these values-->',
+ ' <reconnect-on-changed-schema xmlns="urn:opendaylight:netconf-node-topology">false</reconnect-on-changed-schema>',
+ ' <connection-timeout-millis xmlns="urn:opendaylight:netconf-node-topology">20000</connection-timeout-millis>',
+ ' <max-connection-attempts xmlns="urn:opendaylight:netconf-node-topology">100</max-connection-attempts>',
+ ' <between-attempts-timeout-millis xmlns="urn:opendaylight:netconf-node-topology">2000</between-attempts-timeout-millis>',
+ ' <sleep-factor xmlns="urn:opendaylight:netconf-node-topology">1.5</sleep-factor>',
+
+ ' <!-- keepalive-delay set to 0 turns off keepalives-->',
+ ' <keepalive-delay xmlns="urn:opendaylight:netconf-node-topology">120</keepalive-delay>',
+ '</node>'].join('');
+
+ try {
+ const result = await requestRest<{}>(path, {
+ method: 'PUT',
+ headers: {
+ 'Content-Type': 'application/xml',
+ 'Accept': 'application/xml',
+ 'Authorization': 'Basic YWRtaW46YWRtaW4='
+ },
+ body: mountXml
+ }, true);
+ // expect an empty answer
+ return result === null;
+ } catch {
+ return false;
+ }
+ };
+
+ /** Unmounts a network element by its id. */
+ public async unmountNetworkElement(mountId: string): Promise<boolean> {
+ const path = 'restconf/config/network-topology:network-topology/topology/topology-netconf/node/' + mountId;
+
+ try {
+ const result = await requestRest<{}>(path, {
+ method: 'DELETE',
+ headers: {
+ 'Content-Type': 'application/xml',
+ 'Accept': 'application/xml',
+ 'Authorization': 'Basic YWRtaW46YWRtaW4='
+ },
+ }, true);
+ // expect an empty answer
+ return result === null;
+
+ } catch {
+ return false;
+ }
+ };
+
+
+}
+export const connectService = new ConnectService();
+export default connectService;
diff --git a/sdnr/wt/odlux/apps/connectApp/src/views/connectView.tsx b/sdnr/wt/odlux/apps/connectApp/src/views/connectView.tsx
new file mode 100644
index 0000000..b73eb39
--- /dev/null
+++ b/sdnr/wt/odlux/apps/connectApp/src/views/connectView.tsx
@@ -0,0 +1,93 @@
+import * as React from 'react';
+
+import connect, { IDispatcher, Connect } from '../../../../framework/src/flux/connect';
+import { Panel } from '../../../../framework/src/components/material-ui';
+
+import { requiredNetworkElementsReloadAction } from '../handlers/requiredNetworkElementsHandler';
+import { loadAllMountedNetworkElementsAsync } from '../actions/mountedNetworkElementsActions';
+import { connectionStatusLogReloadAction } from '../handlers/connectionStatusLogHandler';
+
+import { RequiredNetworkElementsList } from '../components/requiredNetworkElements';
+import { ConnectionStatusLog } from '../components/connectionStatusLog';
+import { UnknownNetworkElementsList } from '../components/unknownNetworkElements';
+
+const mapDispatcher = (dispatcher: IDispatcher) => ({
+ onLoadUnknownNetworkElements: () => {
+ dispatcher.dispatch(loadAllMountedNetworkElementsAsync);
+ },
+ onLoadRequiredNetworkElements: () => {
+ dispatcher.dispatch(requiredNetworkElementsReloadAction);
+ },
+ onLoadConnectionStatusLog: () => {
+ dispatcher.dispatch(connectionStatusLogReloadAction);
+ }
+});
+
+type PanelId = null | "RequiredNetworkElements" | "UnknownNetworkElements" | "ConnectionStatusLog";
+
+type ConnectApplicationComponentProps = Connect<undefined, typeof mapDispatcher> ;
+
+type ConnectApplicationComponentState = {
+ activePanel: PanelId;
+};
+
+class ConnectApplicationComponent extends React.Component<ConnectApplicationComponentProps, ConnectApplicationComponentState>{
+ /**
+ * Initialises this instance
+ */
+ constructor(props: ConnectApplicationComponentProps) {
+ super(props);
+
+ this.state = {
+ activePanel: null
+ };
+ }
+ private onTogglePanel = (panelId: PanelId) => {
+ const nextActivePanel = panelId === this.state.activePanel ? null : panelId;
+ this.setState({
+ activePanel: nextActivePanel
+ }, () => {
+ switch (nextActivePanel) {
+ case 'RequiredNetworkElements':
+ this.props.onLoadRequiredNetworkElements();
+ break;
+ case 'UnknownNetworkElements':
+ // todo: should we update the application state ?
+ break;
+ case 'ConnectionStatusLog':
+ this.props.onLoadConnectionStatusLog();
+ break;
+ case null:
+ // do nothing if all panels are closed
+ break;
+ default:
+ console.warn("Unknown nextActivePanel [" + nextActivePanel + "] in connectView");
+ break;
+ }
+ });
+ };
+
+ render(): JSX.Element {
+ const { activePanel } = this.state;
+
+ return (
+ <>
+ <Panel activePanel={ activePanel } panelId={ 'RequiredNetworkElements' } onToggle={ this.onTogglePanel } title={ "Required Network Elements" }>
+ <RequiredNetworkElementsList />
+ </Panel>
+ <Panel activePanel={ activePanel } panelId={ 'UnknownNetworkElements' } onToggle={ this.onTogglePanel } title={ "Unknown Network Elements" }>
+ <UnknownNetworkElementsList />
+ </Panel>
+ <Panel activePanel={ activePanel } panelId={ 'ConnectionStatusLog' } onToggle={ this.onTogglePanel } title={ "Connection Status Log" }>
+ <ConnectionStatusLog />
+ </Panel>
+ </>
+ );
+ };
+ public componentDidMount() {
+ this.props.onLoadUnknownNetworkElements();
+ }
+}
+
+export const ConnectApplication = (connect(undefined, mapDispatcher)(ConnectApplicationComponent));
+export default ConnectApplication;
\ No newline at end of file
diff --git a/sdnr/wt/odlux/apps/connectApp/src2/main/java/org/onap/ccsdk/features/sdnr/wt/odlux/bundles/MyOdluxBundle.java b/sdnr/wt/odlux/apps/connectApp/src2/main/java/org/onap/ccsdk/features/sdnr/wt/odlux/bundles/MyOdluxBundle.java
new file mode 100644
index 0000000..1e882fc
--- /dev/null
+++ b/sdnr/wt/odlux/apps/connectApp/src2/main/java/org/onap/ccsdk/features/sdnr/wt/odlux/bundles/MyOdluxBundle.java
@@ -0,0 +1,68 @@
+/*******************************************************************************
+ * ============LICENSE_START========================================================================
+ * ONAP : ccsdk feature sdnr wt
+ * =================================================================================================
+ * 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==========================================================================
+ ******************************************************************************/
+package org.onap.ccsdk.features.sdnr.wt.odlux.bundles;
+
+import org.onap.ccsdk.features.sdnr.wt.odlux.model.bundles.OdluxBundle;
+import org.onap.ccsdk.features.sdnr.wt.odlux.model.bundles.OdluxBundleLoader;
+
+public class MyOdluxBundle extends OdluxBundle {
+
+ @Override
+ public void initialize() {
+ super.initialize();
+ }
+
+ @Override
+ public void clean() {
+ super.clean();
+ }
+
+ @Override
+ public String getResourceFileContent(String filename) {
+ return super.getResourceFileContent(filename);
+ }
+
+ @Override
+ public boolean hasResource(String filename) {
+ return super.hasResource(filename);
+ }
+
+ @Override
+ public void setBundleName(String bundleName) {
+ super.setBundleName(bundleName);
+ }
+
+ @Override
+ public void setLoader(OdluxBundleLoader loader) {
+ super.setLoader(loader);
+ }
+
+ @Override
+ public String getBundleName() {
+ return super.getBundleName();
+ }
+
+ @Override
+ public OdluxBundleLoader getLoader() {
+ return super.getLoader();
+ }
+
+ public MyOdluxBundle() {
+ super();
+ }
+}
diff --git a/sdnr/wt/odlux/apps/connectApp/src2/main/resources/OSGI-INF/blueprint/blueprint.xml b/sdnr/wt/odlux/apps/connectApp/src2/main/resources/OSGI-INF/blueprint/blueprint.xml
new file mode 100644
index 0000000..adc9b77
--- /dev/null
+++ b/sdnr/wt/odlux/apps/connectApp/src2/main/resources/OSGI-INF/blueprint/blueprint.xml
@@ -0,0 +1,9 @@
+<blueprint xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0">
+ <reference id="loadersvc" availability="mandatory" activation="eager" interface="org.onap.ccsdk.features.sdnr.wt.odlux.model.bundles.OdluxBundleLoader"/>
+
+ <bean id="bundle" init-method="initialize" destroy-method="clean" class="org.onap.ccsdk.features.sdnr.wt.odlux.bundles.MyOdluxBundle">
+ <property name="loader" ref="loadersvc"/>
+ <property name="bundleName" value="connectApp"/>
+ <property name="index" value="0"/>
+ </bean>
+</blueprint>
\ No newline at end of file
diff --git a/sdnr/wt/odlux/apps/connectApp/tsconfig.json b/sdnr/wt/odlux/apps/connectApp/tsconfig.json
new file mode 100644
index 0000000..a66b5d8
--- /dev/null
+++ b/sdnr/wt/odlux/apps/connectApp/tsconfig.json
@@ -0,0 +1,37 @@
+{
+ "compilerOptions": {
+ "baseUrl": "./src",
+ "outDir": "./dist",
+ "sourceMap": true,
+ "forceConsistentCasingInFileNames": true,
+ "allowSyntheticDefaultImports": false,
+ "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/apps/connectApp/webpack.config.js b/sdnr/wt/odlux/apps/connectApp/webpack.config.js
new file mode 100644
index 0000000..a70e629
--- /dev/null
+++ b/sdnr/wt/odlux/apps/connectApp/webpack.config.js
@@ -0,0 +1,139 @@
+/**
+ * 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: {
+ connectApp: ["./plugin.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"
+ }]
+ }]
+ },
+
+ 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: {
+ "/restconf": {
+ target: "http://localhost:8181",
+ secure: false
+ },
+ "/database": {
+ target: "http://localhost:8181",
+ secure: false
+ }
+ }
+ }
+ }];
+}