diff options
Diffstat (limited to 'sdnr/wt/odlux/apps')
139 files changed, 8108 insertions, 0 deletions
diff --git a/sdnr/wt/odlux/apps/apiDemo/.babelrc b/sdnr/wt/odlux/apps/apiDemo/.babelrc new file mode 100644 index 000000000..3d8cd1260 --- /dev/null +++ b/sdnr/wt/odlux/apps/apiDemo/.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/apiDemo/package.json b/sdnr/wt/odlux/apps/apiDemo/package.json new file mode 100644 index 000000000..8d88a6096 --- /dev/null +++ b/sdnr/wt/odlux/apps/apiDemo/package.json @@ -0,0 +1,40 @@ +{ + "name": "@odlux/api-demo", + "version": "0.1.0", + "description": "A react based modular UI framework", + "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/apiDemo/pom.xml b/sdnr/wt/odlux/apps/apiDemo/pom.xml new file mode 100644 index 000000000..ffa83b70d --- /dev/null +++ b/sdnr/wt/odlux/apps/apiDemo/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-apiDemo</artifactId> + <version>0.4.1-SNAPSHOT</version> + <packaging>bundle</packaging> + <name>sdnr-wt-odlux-app-apiDemo</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/apiDemo/src/actions/modulesSuccess.ts b/sdnr/wt/odlux/apps/apiDemo/src/actions/modulesSuccess.ts new file mode 100644 index 000000000..206ce8ad3 --- /dev/null +++ b/sdnr/wt/odlux/apps/apiDemo/src/actions/modulesSuccess.ts @@ -0,0 +1,8 @@ +import { Action } from '../../../../framework/src/flux/action'; +import { ModuleResult } from '../models/module'; +export class ModulesRequestSuccess extends Action { + constructor(public result: ModuleResult) { + super(); + } +} +// error will be handled by the framework
\ No newline at end of file diff --git a/sdnr/wt/odlux/apps/apiDemo/src/handlers/apiDemoRootHandler.ts b/sdnr/wt/odlux/apps/apiDemo/src/handlers/apiDemoRootHandler.ts new file mode 100644 index 000000000..ec4187659 --- /dev/null +++ b/sdnr/wt/odlux/apps/apiDemo/src/handlers/apiDemoRootHandler.ts @@ -0,0 +1,23 @@ + +import { combineActionHandler } from '../../../../framework/src/flux/middleware'; + +import { IApplicationStoreState } from '../../../../framework/src/store/applicationStore'; + +import { moduleHandler, IModules } from './modulesHandler'; + +export interface IApiDemoStoreState { + modules: IModules +} + +declare module '../../../../framework/src/store/applicationStore' { + interface IApplicationStoreState { + apiDemo: IApiDemoStoreState + } +} + +const actionHandlers = { + modules: moduleHandler +}; + +export const apiDemoRootHandler = combineActionHandler<IApiDemoStoreState>(actionHandlers); +export default apiDemoRootHandler; diff --git a/sdnr/wt/odlux/apps/apiDemo/src/handlers/modulesHandler.ts b/sdnr/wt/odlux/apps/apiDemo/src/handlers/modulesHandler.ts new file mode 100644 index 000000000..2b6c019cc --- /dev/null +++ b/sdnr/wt/odlux/apps/apiDemo/src/handlers/modulesHandler.ts @@ -0,0 +1,16 @@ +import { IActionHandler } from '../../../../framework/src/flux/action'; + +import { ModulesRequestSuccess } from '../actions/modulesSuccess'; +import { Module } from '../models/module'; + +export type IModules = Module[] + +const modulesInit: IModules = []; + +export const moduleHandler: IActionHandler<IModules> = (state = modulesInit, action) => { + if (action instanceof ModulesRequestSuccess) { + return action.result.modules.module; + } + + return state; +}; diff --git a/sdnr/wt/odlux/apps/apiDemo/src/index.html b/sdnr/wt/odlux/apps/apiDemo/src/index.html new file mode 100644 index 000000000..c01df6b13 --- /dev/null +++ b/sdnr/wt/odlux/apps/apiDemo/src/index.html @@ -0,0 +1,24 @@ +<!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>API Demo 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","apiDemo"], function (app) { + app("./app.tsx") + }); + </script> +</body> + +</html>
\ No newline at end of file diff --git a/sdnr/wt/odlux/apps/apiDemo/src/models/module.ts b/sdnr/wt/odlux/apps/apiDemo/src/models/module.ts new file mode 100644 index 000000000..aab8db333 --- /dev/null +++ b/sdnr/wt/odlux/apps/apiDemo/src/models/module.ts @@ -0,0 +1,11 @@ +export type Module = { + name: string; + revision: string; + namespace: string; +} + +export type ModuleResult = { + modules: { + module: Module[] + } +}
\ No newline at end of file diff --git a/sdnr/wt/odlux/apps/apiDemo/src/plugin.tsx b/sdnr/wt/odlux/apps/apiDemo/src/plugin.tsx new file mode 100644 index 000000000..8491760e6 --- /dev/null +++ b/sdnr/wt/odlux/apps/apiDemo/src/plugin.tsx @@ -0,0 +1,36 @@ +import * as React from "react"; +import { withRouter, RouteComponentProps, Route, Switch, Redirect } from 'react-router-dom'; + +import { faNewspaper } from '@fortawesome/free-solid-svg-icons'; + +import applicationManager from '../../../framework/src/services/applicationManager'; +import connect, { Connect } from '../../../framework/src/flux/connect'; +import { ApiAction } from '../../../framework/src/middleware/api'; // for RestConf + +import { apiDemoRootHandler } from './handlers/apiDemoRootHandler'; +import { ModulesRequestSuccess } from './actions/modulesSuccess'; +import { Module } from './models/module'; + +type AppProps = RouteComponentProps & Connect & { modules: Module[], requestModules: () => void }; + +const App = (props: AppProps ) => ( + <> + <button onClick={ props.requestModules }>Load Modules</button> + <ul>{ props.modules.map((mod, ind) => (<li key={ ind }>{ mod.name }</li>)) }</ul> + </> +); + +const FinalApp = withRouter(connect((state) => ({ + modules: state.apiDemo.modules +}), (dispatcher => ({ + requestModules: () => { dispatcher.dispatch(new ApiAction('restconf/modules', ModulesRequestSuccess, true)) } +})))(App)); + +applicationManager.registerApplication({ + name: "apiDemo", + icon: faNewspaper, + rootComponent: FinalApp, + rootActionHandler: apiDemoRootHandler, + menuEntry: "API Demo" +}); + diff --git a/sdnr/wt/odlux/apps/apiDemo/src2/main/java/org/onap/ccsdk/features/sdnr/wt/odlux/bundles/MyOdluxBundle.java b/sdnr/wt/odlux/apps/apiDemo/src2/main/java/org/onap/ccsdk/features/sdnr/wt/odlux/bundles/MyOdluxBundle.java new file mode 100644 index 000000000..1e882fc69 --- /dev/null +++ b/sdnr/wt/odlux/apps/apiDemo/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/apiDemo/src2/main/resources/OSGI-INF/blueprint/blueprint.xml b/sdnr/wt/odlux/apps/apiDemo/src2/main/resources/OSGI-INF/blueprint/blueprint.xml new file mode 100644 index 000000000..0877578e2 --- /dev/null +++ b/sdnr/wt/odlux/apps/apiDemo/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="apiDemo"/> + <property name="index" value="999"/> + </bean> +</blueprint>
\ No newline at end of file diff --git a/sdnr/wt/odlux/apps/apiDemo/tsconfig.json b/sdnr/wt/odlux/apps/apiDemo/tsconfig.json new file mode 100644 index 000000000..a66b5d828 --- /dev/null +++ b/sdnr/wt/odlux/apps/apiDemo/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/apiDemo/webpack.config.js b/sdnr/wt/odlux/apps/apiDemo/webpack.config.js new file mode 100644 index 000000000..1c699e15b --- /dev/null +++ b/sdnr/wt/odlux/apps/apiDemo/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: { + apiDemo: ["./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 CopyWebpackPlugin([{ + // from: '../../../dist/**.*', + // to: path.resolve(__dirname, "dist") + // }]), + 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: "https://dlux.just-run.it", + secure: false, + changeOrigin: true + } + } + } + }]; +} diff --git a/sdnr/wt/odlux/apps/app-feature/pom.xml b/sdnr/wt/odlux/apps/app-feature/pom.xml new file mode 100644 index 000000000..aadee5580 --- /dev/null +++ b/sdnr/wt/odlux/apps/app-feature/pom.xml @@ -0,0 +1,66 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- * ============LICENSE_START======================================================= + * ONAP : CCSDK.feature.sdnr.wt * ================================================================================ + * Copyright (C) 2018 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========================================================= --> + +<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.wt</groupId> + <artifactId>sdnr-wt-odlux-apps-feature</artifactId> + <version>0.4.1-SNAPSHOT</version> + <packaging>feature</packaging> + <name>ccsdk-features-sdnr-wt :: ${project.artifactId} :: feature</name> + + <parent> + <groupId>org.onap.ccsdk.parent</groupId> + <artifactId>single-feature-parent</artifactId> + <version>1.2.1-SNAPSHOT</version> + <relativePath /> + </parent> + <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-app-connectApp</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>${project.groupId}</groupId> + <artifactId>sdnr-wt-odlux-app-faultApp</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>${project.groupId}</groupId> + <artifactId>sdnr-wt-odlux-app-helpApp</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>${project.groupId}</groupId> + <artifactId>sdnr-wt-odlux-app-inventoryApp</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>${project.groupId}</groupId> + <artifactId>sdnr-wt-odlux-app-mediatorApp</artifactId> + <version>${project.version}</version> + </dependency> + </dependencies> +</project> diff --git a/sdnr/wt/odlux/apps/app-installer/pom.xml b/sdnr/wt/odlux/apps/app-installer/pom.xml new file mode 100755 index 000000000..999082fcb --- /dev/null +++ b/sdnr/wt/odlux/apps/app-installer/pom.xml @@ -0,0 +1,136 @@ +<?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"> + <modelVersion>4.0.0</modelVersion> + + <groupId>org.onap.ccsdk.features.sdnr.wt</groupId> + <artifactId>sdnr-wt-odlux-apps-installer</artifactId> + <version>0.4.1-SNAPSHOT</version> + <packaging>pom</packaging> + <name>ccsdk-features-sdnr-wt :: ${project.artifactId}</name> + + <parent> + <groupId>org.onap.ccsdk.parent</groupId> + <artifactId>odlparent-lite</artifactId> + <version>1.2.1-SNAPSHOT</version> + <relativePath /> + </parent> + + <properties> + <application.name>sdnr-wt-odlux-apps</application.name> + <include.transitive.dependencies>false</include.transitive.dependencies> + </properties> + <licenses> + <license> + <name>Apache License, Version 2.0</name> + <url>http://www.apache.org/licenses/LICENSE-2.0</url> + </license> + </licenses> + + <dependencies> + <dependency> + <groupId>org.onap.ccsdk.features.sdnr.wt</groupId> + <artifactId>${application.name}-feature</artifactId> + <version>${project.version}</version> + <type>xml</type> + <classifier>features</classifier> + <exclusions> + <exclusion> + <groupId>*</groupId> + <artifactId>*</artifactId> + </exclusion> + </exclusions> + </dependency> + <dependency> + <groupId>${project.groupId}</groupId> + <artifactId>sdnr-wt-odlux-app-apiDemo</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>${project.groupId}</groupId> + <artifactId>sdnr-wt-odlux-app-connectApp</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>${project.groupId}</groupId> + <artifactId>sdnr-wt-odlux-app-demoApp</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>${project.groupId}</groupId> + <artifactId>sdnr-wt-odlux-app-faultApp</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>${project.groupId}</groupId> + <artifactId>sdnr-wt-odlux-app-helpApp</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>${project.groupId}</groupId> + <artifactId>sdnr-wt-odlux-app-inventoryApp</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>${project.groupId}</groupId> + <artifactId>sdnr-wt-odlux-app-minimumApp</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>${project.groupId}</groupId> + <artifactId>sdnr-wt-odlux-app-mediatorApp</artifactId> + <version>${project.version}</version> + </dependency> + </dependencies> + + <build> + <plugins> + <plugin> + <artifactId>maven-assembly-plugin</artifactId> + <executions> + <execution> + <id>maven-repo-zip</id> + <goals> + <goal>single</goal> + </goals> + <phase>package</phase> + <configuration> + <attach>true</attach> + <finalName>stage/${application.name}-${project.version}</finalName> + <descriptors> + <descriptor>src/assembly/assemble_mvnrepo_zip.xml</descriptor> + </descriptors> + <appendAssemblyId>true</appendAssemblyId> + </configuration> + </execution> + </executions> + </plugin> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-dependency-plugin</artifactId> + <executions> + <execution> + <id>copy-nested-dependencies</id> + <goals> + <goal>copy-dependencies</goal> + </goals> + <phase>prepare-package</phase> + <configuration> + <transitive>true</transitive> + <outputDirectory>${project.build.directory}/assembly/system</outputDirectory> + <overWriteReleases>false</overWriteReleases> + <overWriteSnapshots>true</overWriteSnapshots> + <overWriteIfNewer>true</overWriteIfNewer> + <useRepositoryLayout>true</useRepositoryLayout> + <addParentPoms>false</addParentPoms> + <copyPom>false</copyPom> + <!--<includeArtifactIds>sdnr-wt-apigateway-provider,${application.name}</includeArtifactIds> --> + <!--<scope>provided</scope> --> + </configuration> + </execution> + </executions> + </plugin> + </plugins> + </build> +</project> diff --git a/sdnr/wt/odlux/apps/app-installer/src/assembly/assemble_mvnrepo_zip.xml b/sdnr/wt/odlux/apps/app-installer/src/assembly/assemble_mvnrepo_zip.xml new file mode 100644 index 000000000..c4eb9aa25 --- /dev/null +++ b/sdnr/wt/odlux/apps/app-installer/src/assembly/assemble_mvnrepo_zip.xml @@ -0,0 +1,49 @@ +<!-- + ============LICENSE_START======================================================= + ONAP : CCSDK + ================================================================================ + Copyright (C) 2017 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========================================================= + --> + +<!-- Defines how we build the .zip file which is our distribution. --> + +<assembly + xmlns="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.0 http://maven.apache.org/xsd/assembly-1.1.0.xsd"> + <id>repo</id> + <formats> + <format>zip</format> + </formats> + + <!-- we want "system" and related files right at the root level + as this file is suppose to be unzip on top of a karaf + distro. --> + <includeBaseDirectory>false</includeBaseDirectory> + + <fileSets> + <fileSet> + <directory>target/assembly/</directory> + <outputDirectory>.</outputDirectory> + <excludes> + </excludes> + </fileSet> + </fileSets> + + + +</assembly> diff --git a/sdnr/wt/odlux/apps/connectApp/.babelrc b/sdnr/wt/odlux/apps/connectApp/.babelrc new file mode 100644 index 000000000..3d8cd1260 --- /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 000000000..29e1cb970 --- /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 000000000..b1cabdb5e --- /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 000000000..e342f6314 --- /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 000000000..979321957 --- /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 000000000..b4b08fcf3 --- /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 000000000..ee876e854 --- /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 000000000..13f5fec20 --- /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 000000000..432103128 --- /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 000000000..26d02c4e9 --- /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 000000000..140020eaa --- /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 000000000..70b4d8f2a --- /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 000000000..b2d547717 --- /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 000000000..c28708a83 --- /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 000000000..d3aa20379 --- /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 000000000..4ab7c8e20 --- /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 000000000..85390bef9 --- /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 000000000..08d1f91ec --- /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 000000000..5cf29c708 --- /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 000000000..4e61c326b --- /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 000000000..0adcd49c6 --- /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 000000000..b73eb39d7 --- /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 000000000..1e882fc69 --- /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 000000000..adc9b777b --- /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 000000000..a66b5d828 --- /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 000000000..a70e62976 --- /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 + } + } + } + }]; +} diff --git a/sdnr/wt/odlux/apps/demoApp/.babelrc b/sdnr/wt/odlux/apps/demoApp/.babelrc new file mode 100644 index 000000000..3d8cd1260 --- /dev/null +++ b/sdnr/wt/odlux/apps/demoApp/.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/demoApp/package.json b/sdnr/wt/odlux/apps/demoApp/package.json new file mode 100644 index 000000000..c40714908 --- /dev/null +++ b/sdnr/wt/odlux/apps/demoApp/package.json @@ -0,0 +1,40 @@ +{ + "name": "@odlux/demo-app", + "version": "0.1.0", + "description": "A react based modular UI framework", + "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/demoApp/pom.xml b/sdnr/wt/odlux/apps/demoApp/pom.xml new file mode 100644 index 000000000..eed078d4f --- /dev/null +++ b/sdnr/wt/odlux/apps/demoApp/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-demoApp</artifactId> + <version>0.4.1-SNAPSHOT</version> + <packaging>bundle</packaging> + <name>sdnr-wt-odlux-app-demoApp</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/demoApp/src/actions/authorActions.ts b/sdnr/wt/odlux/apps/demoApp/src/actions/authorActions.ts new file mode 100644 index 000000000..9ce1d59ba --- /dev/null +++ b/sdnr/wt/odlux/apps/demoApp/src/actions/authorActions.ts @@ -0,0 +1,34 @@ +import { Action } from '../../../../framework/src/flux/action'; +import { Dispatch } from '../../../../framework/src/flux/store'; +import { AddErrorInfoAction } from '../../../../framework/src/actions/errorActions'; + +import { IAuthor } from '../models/author'; +import { authorService } from '../services/authorService'; + +export class ApplicationBaseAction extends Action { } + + +export class LoadAllAuthorsAction extends ApplicationBaseAction { + constructor() { + super(); + } +} + +// in React Action is most times a Message +export class AllAuthorsLoadedAction extends ApplicationBaseAction { + constructor(public authors: IAuthor[] | null, public error?: string) { + super(); + + } +} + +export const loadAllAuthorsAsync = (dispatch: Dispatch) => { + dispatch(new LoadAllAuthorsAction()); + authorService.getAllAuthors().then(authors => { + dispatch(new AllAuthorsLoadedAction(authors)); + }, error => { + dispatch(new AllAuthorsLoadedAction(null, error)); + dispatch(new AddErrorInfoAction(error)); + }); +} + diff --git a/sdnr/wt/odlux/apps/demoApp/src/components/counter.tsx b/sdnr/wt/odlux/apps/demoApp/src/components/counter.tsx new file mode 100644 index 000000000..30f7281bb --- /dev/null +++ b/sdnr/wt/odlux/apps/demoApp/src/components/counter.tsx @@ -0,0 +1,17 @@ +import * as React from 'react'; + +export class Counter extends React.Component<{}, { counter: number }> { + constructor(props: {}) { + super(props); + + this.state = { + counter: 0 + }; + } + + render() { + return ( + <button onClick={ () => this.setState({ counter: this.state.counter + 1 }) }>{ this.state.counter }</button> + ) + } +}
\ No newline at end of file diff --git a/sdnr/wt/odlux/apps/demoApp/src/handlers/demoAppRootHandler.ts b/sdnr/wt/odlux/apps/demoApp/src/handlers/demoAppRootHandler.ts new file mode 100644 index 000000000..ee21a9c90 --- /dev/null +++ b/sdnr/wt/odlux/apps/demoApp/src/handlers/demoAppRootHandler.ts @@ -0,0 +1,26 @@ + +import { combineActionHandler } from '../../../../framework/src/flux/middleware'; + +import { IApplicationStoreState } from '../../../../framework/src/store/applicationStore'; + +import { listAuthorsHandler, IListAuthors } from './listAuthorsHandler'; +import { editAuthorHandler, IEditAuthor } from './editAuthorHandler'; + +export interface IDemoAppStoreState { + listAuthors: IListAuthors; + editAuthor: IEditAuthor; +} + +declare module '../../../../framework/src/store/applicationStore' { + interface IApplicationStoreState { + demoApp: IDemoAppStoreState + } +} + +const actionHandlers = { + listAuthors: listAuthorsHandler, + editAuthor: editAuthorHandler, +}; + +export const demoAppRootHandler = combineActionHandler <IDemoAppStoreState>(actionHandlers); +export default demoAppRootHandler; diff --git a/sdnr/wt/odlux/apps/demoApp/src/handlers/editAuthorHandler.ts b/sdnr/wt/odlux/apps/demoApp/src/handlers/editAuthorHandler.ts new file mode 100644 index 000000000..2f00b0d20 --- /dev/null +++ b/sdnr/wt/odlux/apps/demoApp/src/handlers/editAuthorHandler.ts @@ -0,0 +1,16 @@ +import { IActionHandler } from '../../../../framework/src/flux/action'; + +import { IAuthor } from '../models/author'; +export interface IEditAuthor { + author: IAuthor | null; + isDirty: boolean; +} + +const editAuthorInit: IEditAuthor = { + author: null, + isDirty: false +}; + +export const editAuthorHandler: IActionHandler<IEditAuthor> = (state = editAuthorInit, action) => { + return state; +}; diff --git a/sdnr/wt/odlux/apps/demoApp/src/handlers/listAuthorsHandler.ts b/sdnr/wt/odlux/apps/demoApp/src/handlers/listAuthorsHandler.ts new file mode 100644 index 000000000..74228ddb8 --- /dev/null +++ b/sdnr/wt/odlux/apps/demoApp/src/handlers/listAuthorsHandler.ts @@ -0,0 +1,40 @@ +import { IActionHandler } from '../../../../framework/src/flux/action'; + +import { IAuthor } from '../models/author'; +import { LoadAllAuthorsAction, AllAuthorsLoadedAction } from '../actions/authorActions'; + +export interface IListAuthors { + authors: IAuthor[]; + busy: boolean; +} + +const listAuthorsInit: IListAuthors = { + authors: [], + busy: false +}; + +export const listAuthorsHandler: IActionHandler<IListAuthors> = (state = listAuthorsInit, action) => { + if (action instanceof LoadAllAuthorsAction) { + + state = { + ...state, + busy: true + }; + + } else if (action instanceof AllAuthorsLoadedAction) { + if (!action.error && action.authors) { + state = { + ...state, + authors: action.authors, + busy: false + }; + } else { + state = { + ...state, + busy: false + }; + } + } + + return state; +};
\ No newline at end of file diff --git a/sdnr/wt/odlux/apps/demoApp/src/index.html b/sdnr/wt/odlux/apps/demoApp/src/index.html new file mode 100644 index 000000000..e85bcbb03 --- /dev/null +++ b/sdnr/wt/odlux/apps/demoApp/src/index.html @@ -0,0 +1,25 @@ +<!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>Demo 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","demoApp"], function (app, demoApp) { + demoApp.register(); + app("./app.tsx"); + }); + </script> +</body> + +</html>
\ No newline at end of file diff --git a/sdnr/wt/odlux/apps/demoApp/src/models/author.ts b/sdnr/wt/odlux/apps/demoApp/src/models/author.ts new file mode 100644 index 000000000..7e28ae8aa --- /dev/null +++ b/sdnr/wt/odlux/apps/demoApp/src/models/author.ts @@ -0,0 +1,19 @@ +/** + * Represents an author. + */ +export interface IAuthor { + /** + * Defines the unique id of the autor. + */ + id: number; + + /** + * Defines the first name of this author. + */ + firstName: string; + + /** + * Defines the last name of this author. + */ + lastName: string; +} diff --git a/sdnr/wt/odlux/apps/demoApp/src/plugin.tsx b/sdnr/wt/odlux/apps/demoApp/src/plugin.tsx new file mode 100644 index 000000000..f450275c2 --- /dev/null +++ b/sdnr/wt/odlux/apps/demoApp/src/plugin.tsx @@ -0,0 +1,37 @@ +import * as React from "react"; +import { withRouter, RouteComponentProps, Route, Switch, Redirect } from 'react-router-dom'; + +import { faAddressBook, faRegistered } from '@fortawesome/free-solid-svg-icons'; + +import applicationManager from '../../../framework/src/services/applicationManager'; +import connect, { Connect } from '../../../framework/src/flux/connect'; + +import { demoAppRootHandler } from './handlers/demoAppRootHandler'; + +import AuthorsList from './views/authorsList'; +import EditAuthor from './views/editAuthor'; + +import { Counter } from './components/counter'; + +type AppProps = RouteComponentProps & Connect; + +const App = (props: AppProps) => ( + <Switch> + <Route exact path={ `${ props.match.path }/authors` } component={AuthorsList} /> + <Route path={ `${ props.match.path }/authors/:authorId` } component={EditAuthor } /> + <Redirect to={ `${ props.match.path }/authors` } /> + </Switch> +); + +const FinalApp = withRouter(connect()(App)); + +export function register() { + const applicationApi = applicationManager.registerApplication({ + name: "demoApp", + icon: faAddressBook, + rootComponent: FinalApp, + rootActionHandler: demoAppRootHandler, + exportedComponents: { counter: Counter }, + menuEntry: "Demo App" + }); +} diff --git a/sdnr/wt/odlux/apps/demoApp/src/services/authorService.ts b/sdnr/wt/odlux/apps/demoApp/src/services/authorService.ts new file mode 100644 index 000000000..d266f6b3e --- /dev/null +++ b/sdnr/wt/odlux/apps/demoApp/src/services/authorService.ts @@ -0,0 +1,55 @@ +import { IAuthor } from '../models/author'; + +import * as $ from 'jquery'; + +const base_url = 'https://api.mfico.de/v1/authors'; + +/** + * Represents a web api accessor service for all author related actions. + */ +class AuthorService { + + /** + * Gets all known authors from the backend. + * @returns A promise of the type array of @see {@link IAuthor} containing all known authors. + */ + public getAllAuthors(): Promise<IAuthor[]> { + return new Promise((resolve: (value: IAuthor[]) => void, reject: (err: any) => void) => { + $.ajax({ method: "GET", url: base_url }) + .then((data) => { resolve(data); }, (err) => { reject(err) }); + }); + } + + /** + * Gets an author by its id from the backend. + * @returns A promise of the type @see {@link IAuthor} containing the author to get. + */ + public getAuthorById(id: string | number): Promise<IAuthor> { + return new Promise((resolve: (value: IAuthor) => void, reject: (err: any) => void) => { + $.ajax({ method: "GET", url: base_url + "/" + id }) + .then((data) => { resolve(data); }, (err) => { reject(err) }); + }); + } + + +/** + * Saves the given author to the backend api. + * @returns A promise of the type @see {@link IAuthor} containing the autor returned by the backend api. + */ + public saveAuthor(author: IAuthor): Promise<IAuthor> { + return new Promise((resolve: (value: IAuthor) => void, reject: (err: any) => void) => { + // simulate server save + window.setTimeout(() => { + if (Math.random() > 0.8) { + reject("Could not save author."); + } else { + resolve(author); + } + }, 800); // simulate a short network delay + }); + } +} + +// return as a singleton +export const authorService = new AuthorService(); +export default authorService; diff --git a/sdnr/wt/odlux/apps/demoApp/src/views/authorsList.tsx b/sdnr/wt/odlux/apps/demoApp/src/views/authorsList.tsx new file mode 100644 index 000000000..b192fccf8 --- /dev/null +++ b/sdnr/wt/odlux/apps/demoApp/src/views/authorsList.tsx @@ -0,0 +1,70 @@ +import * as React from 'react'; +import { withRouter, RouteComponentProps } from 'react-router-dom'; + +import Table from '@material-ui/core/Table'; +import TableBody from '@material-ui/core/TableBody'; +import TableCell from '@material-ui/core/TableCell'; +import TableHead from '@material-ui/core/TableHead'; +import TableRow from '@material-ui/core/TableRow'; +import Paper from '@material-ui/core/Paper'; // means border + +import connect from '../../../../framework/src/flux/connect'; + +import { loadAllAuthorsAsync } from '../actions/authorActions'; +import { IAuthor } from '../models/author'; + +interface IAuthorsListProps { + authors: IAuthor[], + busy: boolean, + onLoadAllAuthors: () => void +} + +class AuthorsListComponent extends React.Component<RouteComponentProps & IAuthorsListProps> { + + render(): JSX.Element { + const { authors, busy } = this.props; + return ( + <Paper> + <Table > + <TableHead> + <TableRow> + <TableCell numeric>Id</TableCell> + <TableCell >First Name</TableCell> + <TableCell >Last Name</TableCell> + </TableRow> + </TableHead> + <TableBody> + { authors.map(author => ( + <TableRow key={ author.id } onClick={ (e) => this.editAuthor(author) }> + <TableCell>{ author.id }</TableCell> + <TableCell>{ author.firstName }</TableCell> + <TableCell>{ author.lastName }</TableCell> + </TableRow> + )) } + </TableBody> + </Table> + </Paper> + ); + }; + + public componentDidMount() { + this.props.onLoadAllAuthors(); + } + + private editAuthor = (author: IAuthor) => { + author && this.props.history.push(this.props.match.path + '/' + author.id); + }; +} + +export const AuthorsList = withRouter( + connect( + ({ demoApp: state }) => ({ + authors: state.listAuthors.authors, + busy: state.listAuthors.busy + }), + (dispatcher) => ({ + onLoadAllAuthors: () => { + dispatcher.dispatch(loadAllAuthorsAsync) + } + }))(AuthorsListComponent)); +export default AuthorsList; diff --git a/sdnr/wt/odlux/apps/demoApp/src/views/editAuthor.tsx b/sdnr/wt/odlux/apps/demoApp/src/views/editAuthor.tsx new file mode 100644 index 000000000..31bfafc75 --- /dev/null +++ b/sdnr/wt/odlux/apps/demoApp/src/views/editAuthor.tsx @@ -0,0 +1,17 @@ +import * as React from 'react'; +import { withRouter, RouteComponentProps } from 'react-router-dom'; + +type EditAuthorProps = RouteComponentProps<{ authorId: string}>; + +class EditAuthorComponent extends React.Component<EditAuthorProps> { + render(): JSX.Element { + return ( + <div> + <h2>Edit Author { this.props.match.params.authorId }</h2> + </div> + ) + } +} + +export const EditAuthor = withRouter(EditAuthorComponent); +export default EditAuthor;
\ No newline at end of file diff --git a/sdnr/wt/odlux/apps/demoApp/src2/main/java/org/onap/ccsdk/features/sdnr/wt/odlux/bundles/MyOdluxBundle.java b/sdnr/wt/odlux/apps/demoApp/src2/main/java/org/onap/ccsdk/features/sdnr/wt/odlux/bundles/MyOdluxBundle.java new file mode 100644 index 000000000..1e882fc69 --- /dev/null +++ b/sdnr/wt/odlux/apps/demoApp/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/demoApp/src2/main/resources/OSGI-INF/blueprint/blueprint.xml b/sdnr/wt/odlux/apps/demoApp/src2/main/resources/OSGI-INF/blueprint/blueprint.xml new file mode 100644 index 000000000..91f6002ac --- /dev/null +++ b/sdnr/wt/odlux/apps/demoApp/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="demoApp"/> + <property name="index" value="999"/> + </bean> +</blueprint>
\ No newline at end of file diff --git a/sdnr/wt/odlux/apps/demoApp/tsconfig.json b/sdnr/wt/odlux/apps/demoApp/tsconfig.json new file mode 100644 index 000000000..a66b5d828 --- /dev/null +++ b/sdnr/wt/odlux/apps/demoApp/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/demoApp/webpack.config.js b/sdnr/wt/odlux/apps/demoApp/webpack.config.js new file mode 100644 index 000000000..61dd8f6f6 --- /dev/null +++ b/sdnr/wt/odlux/apps/demoApp/webpack.config.js @@ -0,0 +1,134 @@ +/** + * 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: { + demoApp: ["./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: { + "/api": { + target: "http://localhost:3001", + secure: false + } + } + } + }]; +} diff --git a/sdnr/wt/odlux/apps/faultApp/.babelrc b/sdnr/wt/odlux/apps/faultApp/.babelrc new file mode 100644 index 000000000..3d8cd1260 --- /dev/null +++ b/sdnr/wt/odlux/apps/faultApp/.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/faultApp/package.json b/sdnr/wt/odlux/apps/faultApp/package.json new file mode 100644 index 000000000..80d7f7f62 --- /dev/null +++ b/sdnr/wt/odlux/apps/faultApp/package.json @@ -0,0 +1,40 @@ +{ + "name": "@odlux/fault-app", + "version": "0.1.0", + "description": "A react based modular UI to demo the fault management.", + "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/faultApp/pom.xml b/sdnr/wt/odlux/apps/faultApp/pom.xml new file mode 100644 index 000000000..bdb98a6c3 --- /dev/null +++ b/sdnr/wt/odlux/apps/faultApp/pom.xml @@ -0,0 +1,111 @@ +<?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-faultApp</artifactId> + <version>0.4.1-SNAPSHOT</version> + <packaging>bundle</packaging> + <name>sdnr-wt-odlux-app-faultApp</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/faultApp/src/actions/notificationActions.ts b/sdnr/wt/odlux/apps/faultApp/src/actions/notificationActions.ts new file mode 100644 index 000000000..be4908530 --- /dev/null +++ b/sdnr/wt/odlux/apps/faultApp/src/actions/notificationActions.ts @@ -0,0 +1,16 @@ +import { Action } from '../../../../framework/src/flux/action'; + +import { Fault } from '../models/fault'; + +export class FaultApplicationBaseAction extends Action { } + + +export class AddFaultNotificationAction extends FaultApplicationBaseAction { + constructor(public fault:Fault) { + super(); + } +} + +export class ResetFaultNotificationsAction extends FaultApplicationBaseAction { + +} diff --git a/sdnr/wt/odlux/apps/faultApp/src/actions/panelChangeActions.ts b/sdnr/wt/odlux/apps/faultApp/src/actions/panelChangeActions.ts new file mode 100644 index 000000000..dea07a0b0 --- /dev/null +++ b/sdnr/wt/odlux/apps/faultApp/src/actions/panelChangeActions.ts @@ -0,0 +1,9 @@ +import { Action } from '../../../../framework/src/flux/action'; +import { PanelId } from '../models/panelId'; + +export class SetPanelAction extends Action { + constructor(public panelId: PanelId) { + super(); + } +} + diff --git a/sdnr/wt/odlux/apps/faultApp/src/handlers/alarmLogEntriesHandler.ts b/sdnr/wt/odlux/apps/faultApp/src/handlers/alarmLogEntriesHandler.ts new file mode 100644 index 000000000..0b974207b --- /dev/null +++ b/sdnr/wt/odlux/apps/faultApp/src/handlers/alarmLogEntriesHandler.ts @@ -0,0 +1,24 @@ +import { createExternal,IExternalTableState } from '../../../../framework/src/components/material-table/utilities'; +import { createSearchDataHandler } from '../../../../framework/src/utilities/elasticSearch'; + +import { Fault, FaultLog } from '../models/fault'; + +export interface IAlarmLogEntriesState extends IExternalTableState<Fault> { } + +// create eleactic search material data fetch handler +const alarmLogEntriesSearchHandler = createSearchDataHandler<FaultLog, Fault>( + 'sdnevents/faultlog', + null, + (hit) => ({ _id: hit._id, ...hit._source.fault }), + (name) => `fault.${ name }` +); + +export const { + actionHandler: alarmLogEntriesActionHandler, + createActions: createAlarmLogEntriesActions, + createProperties: createAlarmLogEntriesProperties, + reloadAction: alarmLogEntriesReloadAction, + + // set value action, to change a value +} = createExternal<Fault>(alarmLogEntriesSearchHandler, appState => appState.faultApp.alarmLogEntries); + diff --git a/sdnr/wt/odlux/apps/faultApp/src/handlers/currentProblemsHandler.ts b/sdnr/wt/odlux/apps/faultApp/src/handlers/currentProblemsHandler.ts new file mode 100644 index 000000000..6bbe2976f --- /dev/null +++ b/sdnr/wt/odlux/apps/faultApp/src/handlers/currentProblemsHandler.ts @@ -0,0 +1,24 @@ +import { createExternal,IExternalTableState } from '../../../../framework/src/components/material-table/utilities'; +import { createSearchDataHandler } from '../../../../framework/src/utilities/elasticSearch'; + +import { FaultResult, Fault } from '../models/fault'; + +export interface ICurrentProblemsState extends IExternalTableState<Fault> { } + +// create eleactic search material data fetch handler +const currentProblemsSearchHandler = createSearchDataHandler<FaultResult, Fault>( + 'sdnevents/faultcurrent', + null, + (hit) => ({ _id: hit._id, ...hit._source.faultCurrent }), + (name) => `faultCurrent.${name}` + ); + +export const { + actionHandler: currentProblemsActionHandler, + createActions: createCurrentProblemsActions, + createProperties: createCurrentProblemsProperties, + reloadAction: currentProblemsReloadAction, + + // set value action, to change a value +} = createExternal<Fault>(currentProblemsSearchHandler, appState => appState.faultApp.currentProblems); + diff --git a/sdnr/wt/odlux/apps/faultApp/src/handlers/faultAppRootHandler.ts b/sdnr/wt/odlux/apps/faultApp/src/handlers/faultAppRootHandler.ts new file mode 100644 index 000000000..005e3e5a4 --- /dev/null +++ b/sdnr/wt/odlux/apps/faultApp/src/handlers/faultAppRootHandler.ts @@ -0,0 +1,42 @@ +// main state handler + +import { combineActionHandler } from '../../../../framework/src/flux/middleware'; + +// ** do not remove ** +import { IApplicationStoreState } from '../../../../framework/src/store/applicationStore'; +import { IActionHandler } from '../../../../framework/src/flux/action'; + +import { IFaultNotifications, faultNotificationsHandler } from './notificationsHandler'; +import { ICurrentProblemsState, currentProblemsActionHandler } from './currentProblemsHandler'; +import { IAlarmLogEntriesState, alarmLogEntriesActionHandler } from './alarmLogEntriesHandler'; +import { SetPanelAction } from '../actions/panelChangeActions'; + +export interface IFaultAppStoreState { + currentProblems: ICurrentProblemsState; + faultNotifications: IFaultNotifications; + alarmLogEntries: IAlarmLogEntriesState; + currentOpenPanel: string|null; +} + +const currentOpenPanelHandler: IActionHandler<string | null> = (state = null, action) => { + if (action instanceof SetPanelAction) { + state = action.panelId; + } + return state; +} + +declare module '../../../../framework/src/store/applicationStore' { + interface IApplicationStoreState { + faultApp: IFaultAppStoreState; + } +} + +const actionHandlers = { + currentProblems: currentProblemsActionHandler, + faultNotifications: faultNotificationsHandler, + alarmLogEntries: alarmLogEntriesActionHandler, + currentOpenPanel: currentOpenPanelHandler +}; + +export const faultAppRootHandler = combineActionHandler<IFaultAppStoreState>(actionHandlers); +export default faultAppRootHandler; diff --git a/sdnr/wt/odlux/apps/faultApp/src/handlers/notificationsHandler.ts b/sdnr/wt/odlux/apps/faultApp/src/handlers/notificationsHandler.ts new file mode 100644 index 000000000..b73ed14a3 --- /dev/null +++ b/sdnr/wt/odlux/apps/faultApp/src/handlers/notificationsHandler.ts @@ -0,0 +1,30 @@ +import { IActionHandler } from '../../../../framework/src/flux/action'; +import { AddFaultNotificationAction, ResetFaultNotificationsAction } from '../actions/notificationActions'; +import { Fault } from '../models/fault'; + +export interface IFaultNotifications { + faults: Fault[]; + since: Date; +} + +const faultNotoficationsInit: IFaultNotifications = { + faults: [], + since: new Date() +}; + +export const faultNotificationsHandler: IActionHandler<IFaultNotifications> = (state = faultNotoficationsInit, action) => { + if (action instanceof AddFaultNotificationAction) { + state = { + ...state, + faults: [...state.faults, action.fault] + }; + } else if (action instanceof ResetFaultNotificationsAction){ + state = { + ...state, + faults: [], + since: new Date() + }; + } + + return state; +}
\ No newline at end of file diff --git a/sdnr/wt/odlux/apps/faultApp/src/index.html b/sdnr/wt/odlux/apps/faultApp/src/index.html new file mode 100644 index 000000000..5f6794f33 --- /dev/null +++ b/sdnr/wt/odlux/apps/faultApp/src/index.html @@ -0,0 +1,25 @@ +<!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", "faultApp"], function (app, faultApp) { + faultApp.register(); + app("./app.tsx"); + }); + </script> +</body> + +</html>
\ No newline at end of file diff --git a/sdnr/wt/odlux/apps/faultApp/src/models/fault.ts b/sdnr/wt/odlux/apps/faultApp/src/models/fault.ts new file mode 100644 index 000000000..ecd0c9993 --- /dev/null +++ b/sdnr/wt/odlux/apps/faultApp/src/models/fault.ts @@ -0,0 +1,14 @@ +export type FaultType = { + nodeName: string; + counter: string; + timeStamp: string; + objectId: string; + problem: string; + severity: null | 'Warning' | 'Minor' | 'Major' | 'Critical' ; + type: string; +} +export type FaultResult = { faultCurrent: FaultType }; + +export type FaultLog = { fault: FaultType }; + +export type Fault = FaultType & { _id: string }; diff --git a/sdnr/wt/odlux/apps/faultApp/src/models/panelId.ts b/sdnr/wt/odlux/apps/faultApp/src/models/panelId.ts new file mode 100644 index 000000000..56f117ad3 --- /dev/null +++ b/sdnr/wt/odlux/apps/faultApp/src/models/panelId.ts @@ -0,0 +1 @@ +export type PanelId = null | "CurrentProblem" | "AlarmNotifications" | "AlarmLog";
\ No newline at end of file diff --git a/sdnr/wt/odlux/apps/faultApp/src/plugin.tsx b/sdnr/wt/odlux/apps/faultApp/src/plugin.tsx new file mode 100644 index 000000000..6f83e74dc --- /dev/null +++ b/sdnr/wt/odlux/apps/faultApp/src/plugin.tsx @@ -0,0 +1,78 @@ +// app configuration and main entry point for the app + + +import * as React from "react"; +import { withRouter, RouteComponentProps, Route, Switch, Redirect } from 'react-router-dom'; + +import connect, { Connect, IDispatcher } from '../../../framework/src/flux/connect'; + +import { faBell } from '@fortawesome/free-solid-svg-icons'; // select app icon +import applicationManager from '../../../framework/src/services/applicationManager'; +import { subscribe, IFormatedMessage } from '../../../framework/src/services/notificationService'; +import { IApplicationStoreState } from "../../../framework/src/store/applicationStore"; + +import { faultAppRootHandler } from './handlers/faultAppRootHandler'; +import { FaultApplication } from "./views/faultApplication"; + +import { Fault } from "./models/fault"; +import { PanelId } from "./models/panelId"; + +import { SetPanelAction } from "./actions/panelChangeActions"; +import { AddFaultNotificationAction } from "./actions/notificationActions"; + +import { createCurrentProblemsProperties, createCurrentProblemsActions } from "./handlers/currentProblemsHandler"; + +let currentMountId: string | undefined = undefined; + +const mapProps = (state: IApplicationStoreState) => ({ + currentProblemsProperties: createCurrentProblemsProperties(state), +}); + +const mapDisp = (dispatcher: IDispatcher) => ({ + currentProblemsActions: createCurrentProblemsActions(dispatcher.dispatch, true), + setCurrentPanel: (panelId: PanelId) => dispatcher.dispatch(new SetPanelAction(panelId)) +}); + +const FaultApplicationRouteAdapter = connect(mapProps, mapDisp)((props: RouteComponentProps<{ mountId?: string }> & Connect<typeof mapProps, typeof mapDisp>) => { + if (currentMountId !== props.match.params.mountId) { + // route parameter has changed + currentMountId = props.match.params.mountId || undefined; + // Hint: This timeout is need, since it is not recommended to change the state while rendering is in progress ! + window.setTimeout(() => { + if (currentMountId) { + props.setCurrentPanel("CurrentProblem"); + props.currentProblemsActions.onFilterChanged("objectId", currentMountId); + props.currentProblemsProperties.showFilter || (props.currentProblemsActions.onToggleFilter()); + props.currentProblemsActions.onRefresh(); + } + }); + } + return ( + <FaultApplication /> + ) +}); + +const App = withRouter((props: RouteComponentProps) => ( + <Switch> + <Route path={ `${ props.match.path }/:mountId?` } component={ FaultApplicationRouteAdapter } /> + <Redirect to={ `${ props.match.path }` } /> + </Switch> +)); + +export function register() { + const applicationApi = applicationManager.registerApplication({ + name: "faultApp", + icon: faBell, + rootComponent: App, + rootActionHandler: faultAppRootHandler, + menuEntry: "Fault" + }); + + // subscribe to the websocket notifications + subscribe<Fault & IFormatedMessage>("ProblemNotification", (fault => { + const store = applicationApi && applicationApi.applicationStore; + if (fault && store) { + store.dispatch(new AddFaultNotificationAction(fault)); + } + })); +} diff --git a/sdnr/wt/odlux/apps/faultApp/src/views/faultApplication.tsx b/sdnr/wt/odlux/apps/faultApp/src/views/faultApplication.tsx new file mode 100644 index 000000000..9eb3a00ff --- /dev/null +++ b/sdnr/wt/odlux/apps/faultApp/src/views/faultApplication.tsx @@ -0,0 +1,114 @@ +import * as React from 'react'; + +import { withRouter, RouteComponentProps } from 'react-router-dom'; + +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { faExclamationTriangle } from '@fortawesome/free-solid-svg-icons'; + +import { MaterialTable, ColumnType, MaterialTableCtorType } from '../../../../framework/src/components/material-table'; +import { Panel } from '../../../../framework/src/components/material-ui'; + +import { IApplicationStoreState } from '../../../../framework/src/store/applicationStore'; +import connect, { Connect, IDispatcher } from '../../../../framework/src/flux/connect'; + +import { Fault } from '../models/fault'; +import { PanelId } from '../models/panelId'; + +import { createCurrentProblemsProperties, createCurrentProblemsActions, currentProblemsReloadAction } from '../handlers/currentProblemsHandler'; +import { createAlarmLogEntriesProperties, createAlarmLogEntriesActions, alarmLogEntriesReloadAction } from '../handlers/alarmLogEntriesHandler'; +import { SetPanelAction } from '../actions/panelChangeActions'; + +const mapProps = (state: IApplicationStoreState) => ({ + activePanel: state.faultApp.currentOpenPanel, + currentProblemsProperties: createCurrentProblemsProperties(state), + faultNotifications: state.faultApp.faultNotifications, + alarmLogEntriesProperties: createAlarmLogEntriesProperties(state) +}); + +const mapDisp = (dispatcher: IDispatcher) => ({ + currentProblemsActions: createCurrentProblemsActions(dispatcher.dispatch), + alarmLogEntriesActions: createAlarmLogEntriesActions(dispatcher.dispatch), + reloadCurrentProblems: () => dispatcher.dispatch(currentProblemsReloadAction), + reloadAlarmLogEntries: () => dispatcher.dispatch(alarmLogEntriesReloadAction), + setCurrentPanel: (panelId: PanelId) => dispatcher.dispatch(new SetPanelAction(panelId)) +}); + +type FaultApplicationComponentProps = RouteComponentProps & Connect<typeof mapProps, typeof mapDisp>; + + +const FaultTable = MaterialTable as MaterialTableCtorType<Fault>; + +class FaultApplicationComponent extends React.Component<FaultApplicationComponentProps>{ + + render(): JSX.Element { + + const { activePanel } = this.props; + + const onTogglePanel = (panelId: PanelId) => { + const nextActivePanel = panelId === this.props.activePanel ? null : panelId; + this.props.setCurrentPanel(nextActivePanel); + + switch (nextActivePanel) { + case 'CurrentProblem': + this.props.reloadCurrentProblems(); + break; + case 'AlarmLog': + this.props.reloadAlarmLogEntries(); + break; + case 'AlarmNotifications': + case null: + default: + // nothing to do + break; + } + }; + + return ( + <> + <Panel activePanel={ activePanel } panelId={ 'CurrentProblem' } onToggle={ onTogglePanel } title={ 'Current Problem List' }> + <FaultTable idProperty={ '_id' } columns={ [ + { property: "icon", title: "", type: ColumnType.custom, customControl: this.renderIcon }, + { property: "timeStamp", type: ColumnType.text, title: "Time Stamp" }, + { property: "nodeName", title: "Node Name", type: ColumnType.text }, + { property: "counter", title: "Count", type: ColumnType.numeric, width: "100px" }, + { property: "objectId", title: "Object Id", type: ColumnType.text } , + { property: "problem", title: "Alarm Type", type: ColumnType.text }, + { property: "severity", title: "Severity", type: ColumnType.text, width: "140px" }, + ] } { ...this.props.currentProblemsProperties } { ...this.props.currentProblemsActions } /> + </Panel> + <Panel activePanel={ activePanel } panelId={ 'AlarmNotifications' } onToggle={ onTogglePanel } title={ `Alarm Notifications ${this.props.faultNotifications.faults.length} ${this.props.faultNotifications.since}` }> + <FaultTable rows={ this.props.faultNotifications.faults } asynchronus columns={ [ + { property: "icon", title: "", type: ColumnType.custom, customControl: this.renderIcon }, + { property: "timeStamp", title: "Time Stamp" }, + { property: "nodeName", title: "Node Name" }, + { property: "counter", title: "Count", width: "100px" }, + { property: "objectId", title: "Object Id" }, + { property: "problem", title: "Alarm Type" }, + { property: "severity", title: "Severity", width: "140px" }, + ] } idProperty={ '_id' } /> + </Panel> + <Panel activePanel={ activePanel } panelId={ 'AlarmLog' } onToggle={ onTogglePanel } title={ 'Alarm Log' }> + <FaultTable idProperty={ '_id' } columns={ [ + { property: "icon", title: "", type: ColumnType.custom, customControl: this.renderIcon }, + { property: "timeStamp", title: "Time Stamp" }, + { property: "nodeName", title: "Node Name" }, + { property: "counter", title: "Count", type: ColumnType.numeric, width: "100px" }, + { property: "objectId", title: "Object Id" }, + { property: "problem", title: "Alarm Type" }, + { property: "severity", title: "Severity", width: "140px" }, + ] } { ...this.props.alarmLogEntriesProperties } { ...this.props.alarmLogEntriesActions }/> + </Panel> + </> + ); + }; + + private renderIcon = (props: { rowData: Fault }) => { + return ( + <FontAwesomeIcon icon={ faExclamationTriangle } /> + ); + }; + +} + +export const FaultApplication = withRouter(connect(mapProps, mapDisp)(FaultApplicationComponent)); +export default FaultApplication; diff --git a/sdnr/wt/odlux/apps/faultApp/src2/main/java/org/onap/ccsdk/features/sdnr/wt/odlux/bundles/MyOdluxBundle.java b/sdnr/wt/odlux/apps/faultApp/src2/main/java/org/onap/ccsdk/features/sdnr/wt/odlux/bundles/MyOdluxBundle.java new file mode 100644 index 000000000..1e882fc69 --- /dev/null +++ b/sdnr/wt/odlux/apps/faultApp/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/faultApp/src2/main/resources/OSGI-INF/blueprint/blueprint.xml b/sdnr/wt/odlux/apps/faultApp/src2/main/resources/OSGI-INF/blueprint/blueprint.xml new file mode 100644 index 000000000..e7c262bc0 --- /dev/null +++ b/sdnr/wt/odlux/apps/faultApp/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="faultApp"/> + <property name="index" value="10"/> + </bean> +</blueprint>
\ No newline at end of file diff --git a/sdnr/wt/odlux/apps/faultApp/tsconfig.json b/sdnr/wt/odlux/apps/faultApp/tsconfig.json new file mode 100644 index 000000000..a66b5d828 --- /dev/null +++ b/sdnr/wt/odlux/apps/faultApp/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/faultApp/webpack.config.js b/sdnr/wt/odlux/apps/faultApp/webpack.config.js new file mode 100644 index 000000000..a0284c5dd --- /dev/null +++ b/sdnr/wt/odlux/apps/faultApp/webpack.config.js @@ -0,0 +1,134 @@ +/** + * 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: { + faultApp: ["./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: { + "/database": { + target: "http://localhost:8181", + secure: false + } + } + } + }]; +} diff --git a/sdnr/wt/odlux/apps/helpApp/.babelrc b/sdnr/wt/odlux/apps/helpApp/.babelrc new file mode 100644 index 000000000..3d8cd1260 --- /dev/null +++ b/sdnr/wt/odlux/apps/helpApp/.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/helpApp/package.json b/sdnr/wt/odlux/apps/helpApp/package.json new file mode 100644 index 000000000..a1bcc39e7 --- /dev/null +++ b/sdnr/wt/odlux/apps/helpApp/package.json @@ -0,0 +1,45 @@ +{ + "name": "@odlux/help-app", + "version": "0.1.0", + "description": "A react based modular UI providing the help functionaliy.", + "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": "*", + "marked" : "0.6.0", + "@types/marked": "0.6.0", + "highlight.js" : "9.13.1", + "@types/highlight.js": "9.12.3", + "github-markdown-css": "2.10.0" + }, + "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/helpApp/pom.xml b/sdnr/wt/odlux/apps/helpApp/pom.xml new file mode 100644 index 000000000..439e05a4c --- /dev/null +++ b/sdnr/wt/odlux/apps/helpApp/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-helpApp</artifactId> + <version>0.4.1-SNAPSHOT</version> + <packaging>bundle</packaging> + <name>sdnr-wt-odlux-app-helpApp</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/helpApp/src/actions/helpActions.ts b/sdnr/wt/odlux/apps/helpApp/src/actions/helpActions.ts new file mode 100644 index 000000000..ba8969a2c --- /dev/null +++ b/sdnr/wt/odlux/apps/helpApp/src/actions/helpActions.ts @@ -0,0 +1,61 @@ +import { Action } from '../../../../framework/src/flux/action'; +import { Dispatch } from '../../../../framework/src/flux/store'; + +import { TocTreeNode } from '../models/tocNode'; +import helpService from '../services/helpService'; + +export class LoadTocAction extends Action { + constructor() { + super(); + + } +} + +export class TocLoadedAction extends Action { + constructor(public toc?: TocTreeNode[], error?: string) { + super(); + + } +} + +export const requestTocAsyncAction = async (dispatch: Dispatch) => { + dispatch(new LoadTocAction); + try { + const toc = await helpService.getTableOfContents(); + if (toc) { + dispatch(new TocLoadedAction(toc)); + } else { + dispatch(new TocLoadedAction(undefined, "Could not load TOC.")); + } + } catch (err) { + dispatch(new TocLoadedAction(undefined, err)); + } +} + +export class LoadDocumentAction extends Action { + constructor() { + super(); + + } +} + +export class DocumentLoadedAction extends Action { + constructor(public document?: string, public documentPath?: string, error?: string) { + super(); + + } +} + +export const requestDocumentAsyncActionCreator = (path: string) => async (dispatch: Dispatch) => { + dispatch(new LoadDocumentAction); + try { + const doc = await helpService.getDocument(path); + if (doc) { + dispatch(new DocumentLoadedAction(doc, path)); + } else { + dispatch(new DocumentLoadedAction(undefined, undefined, "Could not load document.")); + } + } catch (err) { + dispatch(new DocumentLoadedAction(undefined, undefined, err)); + } +}
\ No newline at end of file diff --git a/sdnr/wt/odlux/apps/helpApp/src/components/markdown.tsx b/sdnr/wt/odlux/apps/helpApp/src/components/markdown.tsx new file mode 100644 index 000000000..ea272bc4d --- /dev/null +++ b/sdnr/wt/odlux/apps/helpApp/src/components/markdown.tsx @@ -0,0 +1,60 @@ +import * as React from 'react'; + +import * as marked from 'marked'; +import * as hljs from 'highlight.js'; + +type MarkdownComponentProps = { + text: string; + className?: string; + markedOptions?: marked.MarkedOptions; + style?: React.CSSProperties +} + +const defaultRenderer = new marked.Renderer(); +defaultRenderer.link = (href, title, text) => ( + `<a target="_blank" rel="noopener noreferrer" href="${ href }" title="${ title }">${ text }</a>` +); + + +class MarkdownComponent extends React.Component<MarkdownComponentProps> { + constructor(props: MarkdownComponentProps) { + super(props); + + const markedOptions: marked.MarkedOptions = { + gfm: true, + tables: true, + breaks: false, + pedantic: false, + sanitize: true, + smartLists: true, + smartypants: false, + langPrefix: 'hljs ', + ...(this.props.markedOptions || {}), + highlight: (code, lang) => { + if (!!(lang && hljs.getLanguage(lang))) { + return hljs.highlight(lang, code).value; + } + return code; + } + }; + + marked.setOptions(markedOptions); + } + render() { + const { text, className, style } = this.props; + + + const html = (marked(text || '', { renderer: this.props.markedOptions && this.props.markedOptions.renderer || defaultRenderer })); + + return ( + <div + dangerouslySetInnerHTML={ { __html: html } } + className={ className } + style={ style } + /> + ); + } +} + +export const Markdown = MarkdownComponent; + diff --git a/sdnr/wt/odlux/apps/helpApp/src/components/subMenuEntry.tsx b/sdnr/wt/odlux/apps/helpApp/src/components/subMenuEntry.tsx new file mode 100644 index 000000000..72bb39e39 --- /dev/null +++ b/sdnr/wt/odlux/apps/helpApp/src/components/subMenuEntry.tsx @@ -0,0 +1,37 @@ +import * as React from 'react'; + +import { IApplicationStoreState } from "../../../../framework/src/store/applicationStore"; +import connect, { Connect, IDispatcher } from '../../../../framework/src/flux/connect'; +import { TreeView, TreeViewCtorType } from '../../../../framework/src/components/material-ui/treeView'; + +import { ListItemText } from '@material-ui/core'; + +import { NavigateToApplication } from '../../../../framework/src/actions/navigationActions'; + +import { TocTreeNode } from '../models/tocNode'; + +const TocTree = TreeView as any as TreeViewCtorType<TocTreeNode>; + +const mapProps = (state: IApplicationStoreState) => ({ + helpToc: state.helpApp.toc, + helpBusy: state.helpApp.busy +}); + +const mapDisp = (dispatcher: IDispatcher) => ({ + requestDocument: (node: TocTreeNode) => dispatcher.dispatch(new NavigateToApplication("helpApp", node.uri)) +}); + +const SubMenuEntryComponent: React.SFC<Connect<typeof mapProps, typeof mapDisp>> = (props) => { + return props.helpToc + ? ( + <TocTree items={ props.helpToc } contentProperty={ "label" } childrenProperty={ "nodes" } depthOffset={ 1 } + useFolderIcons={ false } enableSearchBar={ false } onItemClick={ props.requestDocument } /> + ) + : ( + <ListItemText >Loading ...</ListItemText> + ) +}; + +export const SubMenuEntry = connect(mapProps, mapDisp)(SubMenuEntryComponent); +export default SubMenuEntry; + diff --git a/sdnr/wt/odlux/apps/helpApp/src/handlers/helpAppRootHandler.ts b/sdnr/wt/odlux/apps/helpApp/src/handlers/helpAppRootHandler.ts new file mode 100644 index 000000000..efdc6e83d --- /dev/null +++ b/sdnr/wt/odlux/apps/helpApp/src/handlers/helpAppRootHandler.ts @@ -0,0 +1,58 @@ +// main state handler + +import { IApplicationStoreState } from '../../../../framework/src/store/applicationStore'; +import { TocTreeNode } from 'models/tocNode'; +import { IActionHandler } from '../../../../framework/src/flux/action'; +import { LoadTocAction, TocLoadedAction, LoadDocumentAction, DocumentLoadedAction } from '../actions/helpActions'; + +export interface IHelpAppStoreState { + busy: boolean; + toc: TocTreeNode[] | undefined; + content: string | undefined; + currentPath: string | undefined; +} + +declare module '../../../../framework/src/store/applicationStore' { + interface IApplicationStoreState { + helpApp: IHelpAppStoreState + } +} + +const helpAppStoreStateInit: IHelpAppStoreState = { + busy: false, + toc: undefined, + content: undefined, + currentPath: undefined +}; + +export const helpAppRootHandler: IActionHandler<IHelpAppStoreState> = (state = helpAppStoreStateInit, action) => { + if (action instanceof LoadTocAction) { + state = { + ...state, + busy: true + }; + } else if (action instanceof TocLoadedAction) { + state = { + ...state, + busy: false, + toc: action.toc + }; + } else if (action instanceof LoadDocumentAction) { + state = { + ...state, + busy: true + }; + } else if (action instanceof DocumentLoadedAction) { + state = { + ...state, + busy: false, + content: action.document, + currentPath: action.documentPath + }; + } + + return state; +} + + +export default helpAppRootHandler; diff --git a/sdnr/wt/odlux/apps/helpApp/src/index.html b/sdnr/wt/odlux/apps/helpApp/src/index.html new file mode 100644 index 000000000..2d20410e0 --- /dev/null +++ b/sdnr/wt/odlux/apps/helpApp/src/index.html @@ -0,0 +1,25 @@ +<!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", "helpApp"], function (app, helpApp) { + helpApp.register(); + app("./app.tsx") + }); + </script> +</body> + +</html>
\ No newline at end of file diff --git a/sdnr/wt/odlux/apps/helpApp/src/models/tocNode.ts b/sdnr/wt/odlux/apps/helpApp/src/models/tocNode.ts new file mode 100644 index 000000000..ae73ec43d --- /dev/null +++ b/sdnr/wt/odlux/apps/helpApp/src/models/tocNode.ts @@ -0,0 +1,24 @@ +export type VersionInfo = { + label: string, + path: string, + date: string +} + +export type TocNode = { + label: string; + versions: { + [versionKey: string]: VersionInfo, + current: VersionInfo + }; + nodes?: TocNodeCollection; +} + +export type TocNodeCollection = { [tocNodeKey: string]: TocNode }; + + +export type TocTreeNode = { + label: string; + uri: string; + nodes?: TocTreeNode[]; + disabled?: boolean; +} diff --git a/sdnr/wt/odlux/apps/helpApp/src/plugin.tsx b/sdnr/wt/odlux/apps/helpApp/src/plugin.tsx new file mode 100644 index 000000000..d1cf80804 --- /dev/null +++ b/sdnr/wt/odlux/apps/helpApp/src/plugin.tsx @@ -0,0 +1,69 @@ +// app configuration and main entry point for the app + +import * as React from "react"; +import { withRouter, RouteComponentProps, Route, Switch, Redirect } from 'react-router-dom'; + +import { faFirstAid } from '@fortawesome/free-solid-svg-icons'; // select app icon + +import applicationManager from '../../../framework/src/services/applicationManager'; +import { IApplicationStoreState } from "../../../framework/src/store/applicationStore"; +import connect, { Connect, IDispatcher } from '../../../framework/src/flux/connect'; + +import { requestTocAsyncAction, requestDocumentAsyncActionCreator } from "./actions/helpActions"; +import { helpAppRootHandler } from './handlers/helpAppRootHandler'; + +import { HelpApplication } from './views/helpApplication'; +import { SubMenuEntry } from "./components/subMenuEntry"; + +import '!style-loader!css-loader!highlight.js/styles/default.css'; + +const mapProps = (state: IApplicationStoreState) => ({ + +}); + +const mapDisp = (dispatcher: IDispatcher) => ({ + requestDocument: (path: string) => { + dispatcher.dispatch(requestDocumentAsyncActionCreator(path)); + } +}); + +let currentHelpPath: string | undefined = undefined; + +const HelpApplicationRouteAdapter = connect(mapProps, mapDisp)((props: RouteComponentProps<{ '0'?: string }> & Connect<typeof mapProps, typeof mapDisp>) => { + if (currentHelpPath !== props.match.params["0"]) { + // route parameter has changed + currentHelpPath = props.match.params["0"] || undefined; + // Hint: This timeout is need, since it is not recommended to change the state while rendering is in progress ! + window.setTimeout(() => { + if (currentHelpPath) { + props.requestDocument(currentHelpPath); + } + }); + } + return ( + <HelpApplication /> + ) +}); + +const App = withRouter((props: RouteComponentProps) => ( + <Switch> + <Route path={ `${ props.match.path }/*` } component={ HelpApplicationRouteAdapter } /> + <Redirect to={ `${ props.match.path }` } /> + </Switch> +)); + +export async function register() { + const applicationApi = applicationManager.registerApplication({ + name: "helpApp", + icon: faFirstAid, + rootComponent: App, + rootActionHandler: helpAppRootHandler, + menuEntry: "Help", + subMenuEntry: SubMenuEntry + }); + + // start the initial toc request after the application store is initalized + const store = await applicationApi.applicationStoreInitialized; + store.dispatch(requestTocAsyncAction); + +}
\ No newline at end of file diff --git a/sdnr/wt/odlux/apps/helpApp/src/services/helpService.ts b/sdnr/wt/odlux/apps/helpApp/src/services/helpService.ts new file mode 100644 index 000000000..480cdd04e --- /dev/null +++ b/sdnr/wt/odlux/apps/helpApp/src/services/helpService.ts @@ -0,0 +1,47 @@ +import { requestRest } from '../../../../framework/src/services/restService'; +import { TocTreeNode, TocNodeCollection } from '../models/tocNode'; + +class HelpService { + + private tocNodeCollection: TocTreeNode[] | null = null; + private documents: { [path: string]: string | null } = {}; + + public async getDocument(path: string): Promise<string | null> { + // check if the result is allready in the cache + if (this.documents[path]) return Promise.resolve(this.documents[path]); + + // request the document + const result = await requestRest<string>(`/help/${ path }`.replace(/\/{2,}/i, '/')); + if (result) { + this.documents[path] = result; + } + return this.documents[path] || null; + } + + public async getTableOfContents(): Promise<TocTreeNode[] | null> { + // check if the result is allready in the cache + if (this.tocNodeCollection) return Promise.resolve(this.tocNodeCollection); + + // request the table of contents + const result = await requestRest<TocNodeCollection>('/help/?meta'); + if (result !== false) { + const mapNodesCollection = (col: TocNodeCollection): TocTreeNode[] => { + return Object.keys(col).reduce <TocTreeNode[]>((acc, key) => { + const current = col[key]; + acc.push({ + label: current.label, + uri: current.versions.current.path, + nodes: current.nodes && mapNodesCollection(current.nodes) || undefined + }); + return acc; + }, []); + } + + this.tocNodeCollection = result && mapNodesCollection(result); + } + return this.tocNodeCollection || null; + } +} + +export const helpService = new HelpService(); +export default helpService;
\ No newline at end of file diff --git a/sdnr/wt/odlux/apps/helpApp/src/utilities/path.ts b/sdnr/wt/odlux/apps/helpApp/src/utilities/path.ts new file mode 100644 index 000000000..3cc58a202 --- /dev/null +++ b/sdnr/wt/odlux/apps/helpApp/src/utilities/path.ts @@ -0,0 +1,62 @@ +export const resolvePath = (...paths: string[]): string => { + function resolve(pathA: string, pathB: string) { + // ‘a’ => ['a'] + // 'a/b' => ['a', 'b'] + // '/a/b' => ['', 'a', 'b'] + // '/a/b/' => ['', 'a', 'b', ''] + const pathBParts = pathB.split('/'); + if (pathBParts[0] === '') { + return pathBParts.join('/'); + } + const pathAParts = pathA.split('/'); + const aLastIndex = pathAParts.length - 1; + if (pathAParts[aLastIndex] !== '') { + pathAParts[aLastIndex] = ''; + } + + let part: string; + let i = 0; + while (typeof (part = pathBParts[i]) === 'string') { + switch (part) { + case '..': + pathAParts.pop(); + pathAParts.pop(); + pathAParts.push(''); + break; + case '.': + pathAParts.pop(); + pathAParts.push(''); + break; + default: + pathAParts.pop(); + pathAParts.push(part); + pathAParts.push(''); + break; + } + i++; + } + if (pathBParts[pathBParts.length - 1] !== '') pathAParts.pop(); + return pathAParts.join('/'); + } + + let i = 0; + let path: string; + let r = location.pathname; + + const urlRegex = /^https?\:\/\/([^\/?#]+)(?:[\/?#]|$)/i; + const multiSlashReg = /\/\/+/g; + + while (typeof (path = paths[i]) === 'string') { + debugger; + const matches = path && path.match(urlRegex); + if (matches || !i) { + r = path; + } else { + path = path.replace(multiSlashReg, '/'); + r = resolve(r, path); + } + i++; + } + + return r; +};
\ No newline at end of file diff --git a/sdnr/wt/odlux/apps/helpApp/src/views/helpApplication.tsx b/sdnr/wt/odlux/apps/helpApp/src/views/helpApplication.tsx new file mode 100644 index 000000000..dedb93607 --- /dev/null +++ b/sdnr/wt/odlux/apps/helpApp/src/views/helpApplication.tsx @@ -0,0 +1,53 @@ +import * as React from 'react'; +import * as marked from 'marked'; + +import { resolvePath } from '../utilities/path'; + +import { IApplicationStoreState } from '../../../../framework/src/store/applicationStore'; +import connect, { Connect } from '../../../../framework/src/flux/connect'; + +import { Markdown } from "../components/markdown"; + +import '!style-loader!css-loader!github-markdown-css/github-markdown.css' + +const mapProps = (state: IApplicationStoreState) => ({ + content: state.helpApp.content, + currentPath: state.helpApp.currentPath +}); + +type HelpApplicationComponentProps = Connect<typeof mapProps>; + +class HelpApplicationComponent extends React.Component<HelpApplicationComponentProps> { + + /** + * Initializes a new instance. + */ + constructor(props: HelpApplicationComponentProps) { + super(props); + + this.renderer = new marked.Renderer(); + + this.renderer.link = (href: string, title: string, text: string) => { + // check if href is rel or abs + const absUrlMatch = href.trim().match(/^https?:\/\//i); + return `<a href="${ absUrlMatch ? href : resolvePath('#/helpApp/', this.props.currentPath || '/', href) }" title="${ title }" >${ text }</a>` + }; + + this.renderer.image = (href: string, title: string) => { + return `<img src="${ resolvePath('/help/', this.props.currentPath || '/', href) }" alt="${ title }" />` + }; + + } + + render(): JSX.Element { + return this.props.content ? ( + <Markdown text={ this.props.content } markedOptions={ { renderer: this.renderer } } className="markdown-body" + style={{ maxWidth: "960px", margin: "1.5em auto" }} /> + ) : (<h2>Loading ...</h2>) + } + + private renderer: marked.Renderer; +} + +export const HelpApplication = connect(mapProps)(HelpApplicationComponent); +export default HelpApplication;
\ No newline at end of file diff --git a/sdnr/wt/odlux/apps/helpApp/src2/main/java/org/onap/ccsdk/features/sdnr/wt/odlux/bundles/MyOdluxBundle.java b/sdnr/wt/odlux/apps/helpApp/src2/main/java/org/onap/ccsdk/features/sdnr/wt/odlux/bundles/MyOdluxBundle.java new file mode 100644 index 000000000..1e882fc69 --- /dev/null +++ b/sdnr/wt/odlux/apps/helpApp/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/helpApp/src2/main/resources/OSGI-INF/blueprint/blueprint.xml b/sdnr/wt/odlux/apps/helpApp/src2/main/resources/OSGI-INF/blueprint/blueprint.xml new file mode 100644 index 000000000..a0e3dac11 --- /dev/null +++ b/sdnr/wt/odlux/apps/helpApp/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="helpApp"/> + <property name="index" value="100"/> + </bean> +</blueprint>
\ No newline at end of file diff --git a/sdnr/wt/odlux/apps/helpApp/tsconfig.json b/sdnr/wt/odlux/apps/helpApp/tsconfig.json new file mode 100644 index 000000000..a66b5d828 --- /dev/null +++ b/sdnr/wt/odlux/apps/helpApp/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/helpApp/webpack.config.js b/sdnr/wt/odlux/apps/helpApp/webpack.config.js new file mode 100644 index 000000000..fdae7d957 --- /dev/null +++ b/sdnr/wt/odlux/apps/helpApp/webpack.config.js @@ -0,0 +1,153 @@ +/** + * 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 autoprefixer = require('autoprefixer'); +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: { + helpApp: ["./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" + }] + }, { + test: /\.css$/, + use: [{ + loader: 'style-loader' + }, { + loader: 'css-loader', + options: { + modules: true, + localIdentName: env !== "release" ? '[name]_[local]_[hash:base64:5]' : '[hash]' + } + }, { + loader: 'postcss-loader', + options: { + plugins: () => [autoprefixer] + } + }] + }] + }, + + 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: { + "/help": { + target: "http://localhost:8181", + secure: false + } + } + } + }]; +} diff --git a/sdnr/wt/odlux/apps/inventoryApp/.babelrc b/sdnr/wt/odlux/apps/inventoryApp/.babelrc new file mode 100644 index 000000000..3d8cd1260 --- /dev/null +++ b/sdnr/wt/odlux/apps/inventoryApp/.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/inventoryApp/package.json b/sdnr/wt/odlux/apps/inventoryApp/package.json new file mode 100644 index 000000000..8f77c4bed --- /dev/null +++ b/sdnr/wt/odlux/apps/inventoryApp/package.json @@ -0,0 +1,40 @@ +{ + "name": "@odlux/inventory-app", + "version": "0.1.0", + "description": "A react based modular UI to display network inventory 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/inventoryApp/pom.xml b/sdnr/wt/odlux/apps/inventoryApp/pom.xml new file mode 100644 index 000000000..8a3b35d62 --- /dev/null +++ b/sdnr/wt/odlux/apps/inventoryApp/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-inventoryApp</artifactId> + <version>0.4.1-SNAPSHOT</version> + <packaging>bundle</packaging> + <name>sdnr-wt-odlux-app-inventoryApp</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/inventoryApp/src/index.html b/sdnr/wt/odlux/apps/inventoryApp/src/index.html new file mode 100644 index 000000000..d24358e18 --- /dev/null +++ b/sdnr/wt/odlux/apps/inventoryApp/src/index.html @@ -0,0 +1,25 @@ +<!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>Inventory 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", "inventoryApp"], function (app, inventoryApp) { + inventoryApp.register(); + app("./app.tsx"); + }); + </script> +</body> + +</html>
\ No newline at end of file diff --git a/sdnr/wt/odlux/apps/inventoryApp/src/models/inventory.ts b/sdnr/wt/odlux/apps/inventoryApp/src/models/inventory.ts new file mode 100644 index 000000000..c8c9c5702 --- /dev/null +++ b/sdnr/wt/odlux/apps/inventoryApp/src/models/inventory.ts @@ -0,0 +1,17 @@ +export { HitEntry, Result } from '../../../../framework/src/models'; +export type InventoryType = { + treeLevel: number; + parentUuid: string; + mountpoint: string; + uuid: string; + containedHolder?: (string)[] | null; + manufacturerName?: string ; + manufacturerIdentifier: string; + serial: string; + date: string; + version: string; + description: string; + partTypeId: string; + modelIdentifier: string; + typeName: string; +} diff --git a/sdnr/wt/odlux/apps/inventoryApp/src/plugin.tsx b/sdnr/wt/odlux/apps/inventoryApp/src/plugin.tsx new file mode 100644 index 000000000..52e1d4802 --- /dev/null +++ b/sdnr/wt/odlux/apps/inventoryApp/src/plugin.tsx @@ -0,0 +1,17 @@ +// app configuration and main entry point for the app + + +import { faShoppingBag } from '@fortawesome/free-solid-svg-icons'; // select app icon + +import applicationManager from '../../../framework/src/services/applicationManager'; + +import { Dashboard } from './views/dashboard'; + +export function register() { + applicationManager.registerApplication({ + name: "inventoryApp", + icon: faShoppingBag, + rootComponent: Dashboard, + menuEntry: "Inventory App" + }); +} diff --git a/sdnr/wt/odlux/apps/inventoryApp/src/views/dashboard.tsx b/sdnr/wt/odlux/apps/inventoryApp/src/views/dashboard.tsx new file mode 100644 index 000000000..8d07fa88c --- /dev/null +++ b/sdnr/wt/odlux/apps/inventoryApp/src/views/dashboard.tsx @@ -0,0 +1,73 @@ +import * as React from "react"; +import { MaterialTable, DataCallback, MaterialTableCtorType } from '../../../../framework/src/components/material-table'; + +import { Result, InventoryType } from '../models/inventory'; + + +const url = `${ window.location.origin}/database/sdnevents/inventoryequipment/_search`; + +const fetchData: DataCallback = async (page, rowsPerPage, orderBy, order, filter) => { + const from = rowsPerPage && page != null && !isNaN(+page) + ? (+page) * rowsPerPage + : null; + + const filterKeys = filter && Object.keys(filter) || []; + + const query = { + ...filterKeys.length > 0 ? { + query: { + bool: { + must: filterKeys.reduce((acc, cur) => { + if (acc && filter && filter[cur]) { + acc.push({ [filter[cur].indexOf("*") > -1 || filter[cur].indexOf("?") > -1 ? "wildcard" : "prefix"]: { [cur]: filter[cur] } }); + } + return acc; + }, [] as any[]) + } + } + }: { "query": { "match_all": {} } }, + ...rowsPerPage ? { "size": rowsPerPage } : {} , + ...from ? { "from": from } : {}, + ...orderBy && order ? { "sort": [{ [orderBy]: order }] } : {}, + }; + + const result = await fetch(url, { + method: "POST", // *GET, POST, PUT, DELETE, etc. + mode: "no-cors", // no-cors, cors, *same-origin + cache: "no-cache", // *default, no-cache, reload, force-cache, only-if-cached + headers: { + "Content-Type": "application/json; charset=utf-8", + // "Content-Type": "application/x-www-form-urlencoded", + }, + body: JSON.stringify(query), // body data type must match "Content-Type" header + }); + + if (result.ok) { + const queryResult: Result<InventoryType> = await result.json(); + const data = { + page: Math.min(page || 0, queryResult.hits.total || 0 / (rowsPerPage || 1)), rowCount: queryResult.hits.total, rows: queryResult && queryResult.hits && queryResult.hits.hits && queryResult.hits.hits.map(h => ( + { ...h._source, _id: h._id } + )) || [] + }; + return data; + } + + return { page: 0, rowCount: 0, rows: [] }; +}; + +const InventoryTable = MaterialTable as MaterialTableCtorType<InventoryType & {_id: string}>; + +export const Dashboard : React.SFC = (props) => ( + <InventoryTable onRequestData={ fetchData } columns={ [ + { property: "uuid", title: "Name" }, + { property: "parentUuid", title: "Parent" }, + { property: "mountpoint", title: "Mountpoint" }, + { property: "manufacturerIdentifier", title: "Manufacturer" }, + { property: "serial", title: "Serial" }, + { property: "typeName", title: "Type" }, + ] } title="Inventory" idProperty="_id" > + + </InventoryTable> +); + +export default Dashboard;
\ No newline at end of file diff --git a/sdnr/wt/odlux/apps/inventoryApp/src/views/detail.tsx b/sdnr/wt/odlux/apps/inventoryApp/src/views/detail.tsx new file mode 100644 index 000000000..3f0ffccc2 --- /dev/null +++ b/sdnr/wt/odlux/apps/inventoryApp/src/views/detail.tsx @@ -0,0 +1,24 @@ +import * as React from "react"; +import { withRouter, RouteComponentProps } from 'react-router-dom'; + +import Button from '@material-ui/core/Button'; +import { WithStyles, withStyles, createStyles, Theme } from '@material-ui/core/styles'; // infra for styling + +const styles = (theme: Theme) => createStyles({ + warnButton: { + backgroundColor: theme.palette.primary.dark + } +}); + +type DetailProps = RouteComponentProps<{ id: string }> & WithStyles<typeof styles>; + +export const Detail = withStyles( styles )( withRouter( (props: DetailProps) => ( + <div> + <h1>Detail {props.match.params.id}</h1> + <p>This are the information about {props.staticContext}.</p> + <Button color={"secondary"} variant={"contained"}>Start</Button> + <Button className={ props.classes.warnButton } variant={"contained"}>Stop</Button> + </div> +))); + +export default Detail;
\ No newline at end of file diff --git a/sdnr/wt/odlux/apps/inventoryApp/src2/main/java/org/onap/ccsdk/features/sdnr/wt/odlux/bundles/MyOdluxBundle.java b/sdnr/wt/odlux/apps/inventoryApp/src2/main/java/org/onap/ccsdk/features/sdnr/wt/odlux/bundles/MyOdluxBundle.java new file mode 100644 index 000000000..1e882fc69 --- /dev/null +++ b/sdnr/wt/odlux/apps/inventoryApp/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/inventoryApp/src2/main/resources/OSGI-INF/blueprint/blueprint.xml b/sdnr/wt/odlux/apps/inventoryApp/src2/main/resources/OSGI-INF/blueprint/blueprint.xml new file mode 100644 index 000000000..f0bf05483 --- /dev/null +++ b/sdnr/wt/odlux/apps/inventoryApp/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="inventoryApp"/> + <property name="index" value="70"/> + </bean> +</blueprint>
\ No newline at end of file diff --git a/sdnr/wt/odlux/apps/inventoryApp/tsconfig.json b/sdnr/wt/odlux/apps/inventoryApp/tsconfig.json new file mode 100644 index 000000000..a66b5d828 --- /dev/null +++ b/sdnr/wt/odlux/apps/inventoryApp/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/inventoryApp/webpack.config.js b/sdnr/wt/odlux/apps/inventoryApp/webpack.config.js new file mode 100644 index 000000000..e992a1042 --- /dev/null +++ b/sdnr/wt/odlux/apps/inventoryApp/webpack.config.js @@ -0,0 +1,136 @@ +/** + * 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: { + inventoryApp: ["./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: { + "/database": { + target: "http://localhost:8181", + secure: false + } + } + } + }]; +} diff --git a/sdnr/wt/odlux/apps/mediatorApp/.babelrc b/sdnr/wt/odlux/apps/mediatorApp/.babelrc new file mode 100644 index 000000000..3d8cd1260 --- /dev/null +++ b/sdnr/wt/odlux/apps/mediatorApp/.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/mediatorApp/package.json b/sdnr/wt/odlux/apps/mediatorApp/package.json new file mode 100644 index 000000000..a67c08a69 --- /dev/null +++ b/sdnr/wt/odlux/apps/mediatorApp/package.json @@ -0,0 +1,41 @@ +{ + "name": "@odlux/mediator-app", + "version": "0.1.0", + "description": "A react based modular UI to demo the mediator possible 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": "MIT", + "dependencies": { + "@odlux/framework": "*" + }, + "peerDependencies": { + "@fortawesome/free-solid-svg-icons": "5.6.3", + "@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/mediatorApp/pom.xml b/sdnr/wt/odlux/apps/mediatorApp/pom.xml new file mode 100644 index 000000000..14bd084c1 --- /dev/null +++ b/sdnr/wt/odlux/apps/mediatorApp/pom.xml @@ -0,0 +1,111 @@ +<?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.1.2</version> + <relativePath /> + </parent> + + <modelVersion>4.0.0</modelVersion> + <groupId>org.onap.ccsdk.features.sdnr.wt</groupId> + <artifactId>sdnr-wt-odlux-app-mediatorApp</artifactId> + <version>0.4.1-SNAPSHOT</version> + <packaging>bundle</packaging> + <name>sdnr-wt-odlux-app-mediatorApp</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/mediatorApp/src/actions/avaliableMediatorServersActions.ts b/sdnr/wt/odlux/apps/mediatorApp/src/actions/avaliableMediatorServersActions.ts new file mode 100644 index 000000000..4cbad42dd --- /dev/null +++ b/sdnr/wt/odlux/apps/mediatorApp/src/actions/avaliableMediatorServersActions.ts @@ -0,0 +1,41 @@ +import { Action } from '../../../../framework/src/flux/action'; +import { Dispatch } from '../../../../framework/src/flux/store'; +import { AddSnackbarNotification } from '../../../../framework/src/actions/snackbarActions'; + +import { MediatorServer } from '../models/mediatorServer'; +import { avaliableMediatorServersReloadAction } from '../handlers/avaliableMediatorServersHandler'; +import mediatorService from '../services/mediatorService'; + +/** Represents the base action. */ +export class BaseAction extends Action { } + +/** Represents an async thunk action that will add a server to the avaliable mediator servers. */ +export const addAvaliableMediatorServerAsyncActionCreator = (server: MediatorServer) => (dispatch: Dispatch) => { + mediatorService.insertMediatorServer(server).then(_ => { + window.setTimeout(() => { + dispatch(avaliableMediatorServersReloadAction); + dispatch(new AddSnackbarNotification({ message: `Successfully added [${ server.name }]`, options: { variant: 'success' } })); + }, 900); + }); + }; + + /** Represents an async thunk action that will add a server to the avaliable mediator servers. */ +export const updateAvaliableMediatorServerAsyncActionCreator = (server: MediatorServer) => (dispatch: Dispatch) => { + mediatorService.updateMediatorServer(server).then(_ => { + window.setTimeout(() => { + dispatch(avaliableMediatorServersReloadAction); + dispatch(new AddSnackbarNotification({ message: `Successfully updated [${ server.name }]`, options: { variant: 'success' } })); + }, 900); + }); +}; + + /** Represents an async thunk action that will delete a server from the avaliable mediator servers. */ + export const removeAvaliableMediatorServerAsyncActionCreator = (server: MediatorServer) => (dispatch: Dispatch) => { + mediatorService.deleteMediatorServer(server).then(_ => { + window.setTimeout(() => { + dispatch(avaliableMediatorServersReloadAction); + dispatch(new AddSnackbarNotification({ message: `Successfully removed [${ server.name }]`, options: { variant: 'success' } })); + }, 900); + }); + }; +
\ No newline at end of file diff --git a/sdnr/wt/odlux/apps/mediatorApp/src/actions/mediatorConfigActions.ts b/sdnr/wt/odlux/apps/mediatorApp/src/actions/mediatorConfigActions.ts new file mode 100644 index 000000000..fcfc63e22 --- /dev/null +++ b/sdnr/wt/odlux/apps/mediatorApp/src/actions/mediatorConfigActions.ts @@ -0,0 +1,137 @@ + +import { Action } from '../../../../framework/src/flux/action'; +import { Dispatch } from '../../../../framework/src/flux/store'; + +import { AddSnackbarNotification } from '../../../../framework/src/actions/snackbarActions'; +import { IApplicationStoreState } from '../../../../framework/src/store/applicationStore'; + +import mediatorService from '../services/mediatorService'; +import { MediatorConfig, MediatorConfigResponse } from '../models/mediatorServer'; + +/** Represents the base action. */ +export class BaseAction extends Action { } + +export class SetMediatorBusyByName extends BaseAction { + constructor (public name: string, public isBusy: boolean) { + super(); + } +} + +export class AddMediatorConfig extends BaseAction { + constructor (public mediatorConfig: MediatorConfigResponse) { + super(); + } +} + +export class UpdateMediatorConfig extends BaseAction { + constructor (public name: string, public mediatorConfig: MediatorConfigResponse) { + super(); + } +} + +export class RemoveMediatorConfig extends BaseAction { + constructor (public name: string) { + super(); + } +} + + +export const startMediatorByNameAsyncActionCreator = (name: string) => (dispatch: Dispatch, getState: () => IApplicationStoreState) => { + dispatch(new SetMediatorBusyByName(name, true)); + const { mediatorApp: { mediatorServerState: { url } } } = getState(); + if (url) { + mediatorService.startMediatorByName(url, name).then(msg => { + dispatch(new AddSnackbarNotification({ message: msg + ' ' + name, options: { variant: 'info' } })); + // since there is no notification, a timeout will be need here + window.setTimeout(() => { + mediatorService.getMediatorServerConfigByName(url, name).then(config => { + if (config) { + dispatch(new UpdateMediatorConfig(name, config)); + } else { + dispatch(new AddSnackbarNotification({ message: `Error: reading mediator config for ${name}.`, options: { variant: 'error' } })); + } + dispatch(new SetMediatorBusyByName(name, false)); + }); + }, 2100); + }); + } else { + dispatch(new AddSnackbarNotification({ message: `Error: currently no mediator server selected.`, options: { variant: 'error' } })); + dispatch(new SetMediatorBusyByName(name, false)); + } +}; + +export const stopMediatorByNameAsyncActionCreator = (name: string) => (dispatch: Dispatch, getState: () => IApplicationStoreState) => { + dispatch(new SetMediatorBusyByName(name, true)); + const { mediatorApp: { mediatorServerState: { url } } } = getState(); + if (url) { + mediatorService.stopMediatorByName(url, name).then(msg => { + dispatch(new AddSnackbarNotification({ message: msg + ' ' + name, options: { variant: 'info' } })); + // since there is no notification, a timeout will be need here + window.setTimeout(() => { + mediatorService.getMediatorServerConfigByName(url, name).then(config => { + if (config) { + dispatch(new UpdateMediatorConfig(name, config)); + } else { + dispatch(new AddSnackbarNotification({ message: `Error: reading mediator config for ${name}.`, options: { variant: 'error' } })); + } + dispatch(new SetMediatorBusyByName(name, false)); + }); + }, 2100); + }); + } else { + dispatch(new AddSnackbarNotification({ message: `Error: currently no mediator server selected.`, options: { variant: 'error' } })); + dispatch(new SetMediatorBusyByName(name, false)); + } +}; + +export const addMediatorConfigAsyncActionCreator = (config: MediatorConfig) => (dispatch: Dispatch, getState: () => IApplicationStoreState) => { + const { Name: name } = config; + const { mediatorApp: { mediatorServerState: { url } } } = getState(); + if (url) { + mediatorService.createMediatorConfig(url, config).then(msg => { + dispatch(new AddSnackbarNotification({ message: msg + ' ' + name, options: { variant: 'info' } })); + // since there is no notification, a timeout will be need here + window.setTimeout(() => { + mediatorService.getMediatorServerConfigByName(url, name).then(config => { + if (config) { + dispatch(new AddMediatorConfig(config)); + } else { + dispatch(new AddSnackbarNotification({ message: `Error: reading mediator config for ${name}.`, options: { variant: 'error' } })); + } + }); + }, 2100); + }); + } else { + dispatch(new AddSnackbarNotification({ message: `Error: currently no mediator server selected.`, options: { variant: 'error' } })); + } +}; + +export const updateMediatorConfigAsyncActionCreator = (config: MediatorConfig) => (dispatch: Dispatch) => { + // currently not supported be backend +}; + +export const removeMediatorConfigAsyncActionCreator = (config: MediatorConfig) => (dispatch: Dispatch, getState: () => IApplicationStoreState) => { + const { Name: name } = config; + const { mediatorApp: { mediatorServerState: { url } } } = getState(); + if (url) { + mediatorService.deleteMediatorConfigByName(url, name).then(msg => { + dispatch(new AddSnackbarNotification({ message: msg + ' ' + name, options: { variant: 'info' } })); + // since there is no notification, a timeout will be need here + window.setTimeout(() => { + mediatorService.getMediatorServerConfigByName(url, config.Name).then(config => { + if (!config) { + dispatch(new RemoveMediatorConfig(name)); + } else { + dispatch(new AddSnackbarNotification({ message: `Error: deleting mediator config for ${name}.`, options: { variant: 'error' } })); + } + }); + }, 2100); + }); + } else { + dispatch(new AddSnackbarNotification({ message: `Error: currently no mediator server selected.`, options: { variant: 'error' } })); + dispatch(new SetMediatorBusyByName(name, false)); + } +}; + + + diff --git a/sdnr/wt/odlux/apps/mediatorApp/src/actions/mediatorServerActions.ts b/sdnr/wt/odlux/apps/mediatorApp/src/actions/mediatorServerActions.ts new file mode 100644 index 000000000..b8e8c7e94 --- /dev/null +++ b/sdnr/wt/odlux/apps/mediatorApp/src/actions/mediatorServerActions.ts @@ -0,0 +1,84 @@ +import { Action } from '../../../../framework/src/flux/action'; +import { Dispatch } from '../../../../framework/src/flux/store'; + +import { MediatorServerVersionInfo, MediatorConfig, MediatorConfigResponse, MediatorServerDevice } from '../models/mediatorServer'; +import mediatorService from '../services/mediatorService'; +import { AddSnackbarNotification } from '../../../../framework/src/actions/snackbarActions'; +import { NavigateToApplication } from '../../../../framework/src/actions/navigationActions'; +import { IApplicationStoreState } from '../../../../framework/src/store/applicationStore'; + +/** Represents the base action. */ +export class BaseAction extends Action { } + +export class SetMediatorServerBusy extends BaseAction { + constructor (public isBusy: boolean) { + super(); + } +} + +export class SetMediatorServerInfo extends BaseAction { + /** + * Initializes a new instance of this class. + */ + constructor (public name: string | null, public url: string | null) { + super(); + + } +} + +export class SetMediatorServerVersion extends BaseAction { + /** + * Initializes a new instance of this class. + */ + constructor (public versionInfo: MediatorServerVersionInfo | null) { + super(); + + } +} + +export class SetAllMediatorServerConfigurations extends BaseAction { + /** + * Initializes a new instance of this class. + */ + constructor (public allConfigurations: MediatorConfigResponse[] | null) { + super(); + + } +} + +export class SetMediatorServerSupportedDevices extends BaseAction { + /** + * Initializes a new instance of this class. + */ + constructor (public devices: MediatorServerDevice[] | null) { + super(); + + } +} + +export const initializeMediatorServerAsyncActionCreator = (serverId: string) => (dispatch: Dispatch) => { + dispatch(new SetMediatorServerBusy(true)); + mediatorService.getMediatorServerById(serverId).then(mediatorServer => { + if (!mediatorServer) { + dispatch(new SetMediatorServerBusy(false)); + dispatch(new AddSnackbarNotification({ message: `Error loading mediator server [${serverId}]`, options: { variant: 'error' } })); + dispatch(new NavigateToApplication("mediatorApp")); + return; + } + dispatch(new SetMediatorServerInfo(mediatorServer.name, mediatorServer.url)); + + mediatorService.getMediatorServerVersion(mediatorServer.url).then(versionInfo => { + dispatch(new SetMediatorServerVersion(versionInfo)); + }); + + Promise.all([ + mediatorService.getMediatorServerAllConfigs(mediatorServer.url), + mediatorService.getMediatorServerSupportedDevices(mediatorServer.url) + ]).then(([configurations,supportedDevices]) => { + dispatch(new SetAllMediatorServerConfigurations(configurations)); + dispatch(new SetMediatorServerSupportedDevices(supportedDevices)); + dispatch(new SetMediatorServerBusy(false)); + }); + }); +}; + diff --git a/sdnr/wt/odlux/apps/mediatorApp/src/components/editMediatorConfigDialog.tsx b/sdnr/wt/odlux/apps/mediatorApp/src/components/editMediatorConfigDialog.tsx new file mode 100644 index 000000000..889b05eb5 --- /dev/null +++ b/sdnr/wt/odlux/apps/mediatorApp/src/components/editMediatorConfigDialog.tsx @@ -0,0 +1,315 @@ +import * as React from 'react'; +import { Theme, createStyles, WithStyles, withStyles, Typography, FormControlLabel, Checkbox } from '@material-ui/core'; + +import Button from '@material-ui/core/Button'; +import TextField from '@material-ui/core/TextField'; +import Select from '@material-ui/core/Select'; +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 Tabs from '@material-ui/core/Tabs'; +import Tab from '@material-ui/core/Tab'; + +import Fab from '@material-ui/core/Fab'; +import AddIcon from '@material-ui/icons/Add'; +import DeleteIcon from '@material-ui/icons/Delete'; +import IconButton from '@material-ui/core/IconButton'; + +import { addMediatorConfigAsyncActionCreator, updateMediatorConfigAsyncActionCreator, removeMediatorConfigAsyncActionCreator } from '../actions/mediatorConfigActions'; +import { MediatorConfig, ODLConfig } from '../models/mediatorServer'; +import FormControl from '@material-ui/core/FormControl'; +import InputLabel from '@material-ui/core/InputLabel'; +import MenuItem from '@material-ui/core/MenuItem'; + +import { Panel } from '../../../../framework/src/components/material-ui/panel'; + +import { IDispatcher, connect, Connect } from '../../../../framework/src/flux/connect'; +import { IApplicationStoreState } from '../../../../framework/src/store/applicationStore'; + + +const styles = (theme: Theme) => createStyles({ + root: { + display: 'flex', + flexDirection: 'column', + flex: '1', + }, + fab: { + position: 'absolute', + bottom: theme.spacing.unit, + right: theme.spacing.unit, + }, + title: { + fontSize: 14, + }, + center: { + flex: "1", + display: "flex", + alignItems: "center", + justifyContent: "center", + }, + alignInOneLine: { + display: 'flex', + flexDirection: 'row' + }, + left: { + marginRight: theme.spacing.unit, + }, + right: { + marginLeft: 0, + } +}); + +const TabContainer: React.SFC = ({ children }) => { + return ( + <div style={{ width: "430px", height: "530px", position: "relative", display: 'flex', flexDirection: 'column' }}> + {children} + </div> + ); +} + +export enum EditMediatorConfigDialogMode { + None = "none", + AddMediatorConfig = "addMediatorConfig", + EditMediatorConfig = "editMediatorConfig", + RemoveMediatorConfig = "removeMediatorConfig", +} + +const mapProps = (state: IApplicationStoreState) => ({ + supportedDevices: state.mediatorApp.mediatorServerState.supportedDevices +}); + +const mapDispatch = (dispatcher: IDispatcher) => ({ + addMediatorConfig: (config: MediatorConfig) => { + dispatcher.dispatch(addMediatorConfigAsyncActionCreator(config)); + }, + updateMediatorConfig: (config: MediatorConfig) => { + dispatcher.dispatch(updateMediatorConfigAsyncActionCreator(config)); + }, + removeMediatorConfig: (config: MediatorConfig) => { + dispatcher.dispatch(removeMediatorConfigAsyncActionCreator(config)); + }, +}); + +type DialogSettings = { + dialogTitle: string; + dialogDescription: string; + applyButtonText: string; + cancelButtonText: string; + readonly: boolean; + readonlyName: boolean; +}; + +const settings: { [key: string]: DialogSettings } = { + [EditMediatorConfigDialogMode.None]: { + dialogTitle: "", + dialogDescription: "", + applyButtonText: "", + cancelButtonText: "", + readonly: true, + readonlyName: true, + }, + [EditMediatorConfigDialogMode.AddMediatorConfig]: { + dialogTitle: "Add Medator Configuration", + dialogDescription: "", + applyButtonText: "Add", + cancelButtonText: "Cancel", + readonly: false, + readonlyName: false, + }, + [EditMediatorConfigDialogMode.EditMediatorConfig]: { + dialogTitle: "Edit Medator Configuration", + dialogDescription: "", + applyButtonText: "Update", + cancelButtonText: "Cancel", + readonly: false, + readonlyName: true, + }, + [EditMediatorConfigDialogMode.RemoveMediatorConfig]: { + dialogTitle: "Remove Mediator Configuration", + dialogDescription: "", + applyButtonText: "Remove", + cancelButtonText: "Cancel", + readonly: true, + readonlyName: true, + }, +}; + +type EditMediatorConfigDialogComponentProps = WithStyles<typeof styles> & Connect<typeof mapProps, typeof mapDispatch> & { + mode: EditMediatorConfigDialogMode; + mediatorConfig: MediatorConfig; + onClose: () => void; +}; + +type EditMediatorConfigDialogComponentState = MediatorConfig & { activeTab: number; activeOdlConfig: string }; + +class EditMediatorConfigDialogComponent extends React.Component<EditMediatorConfigDialogComponentProps, EditMediatorConfigDialogComponentState> { + constructor (props: EditMediatorConfigDialogComponentProps) { + super(props); + + this.state = { + ...this.props.mediatorConfig, + activeTab: 0, + activeOdlConfig: "" + }; + } + + private odlConfigValueChangeHandlerCreator = <THtmlElement extends HTMLElement, TKey extends keyof ODLConfig>(index: number, property: TKey, mapValue: (event: React.ChangeEvent<THtmlElement>) => any) => (event: React.ChangeEvent<THtmlElement>) => { + event.stopPropagation(); + event.preventDefault(); + this.setState({ + ODLConfig: [ + ...this.state.ODLConfig.slice(0, index), + { ...this.state.ODLConfig[index], [property]: mapValue(event) }, + ...this.state.ODLConfig.slice(index + 1) + ] + }); + } + + render(): JSX.Element { + const setting = settings[this.props.mode]; + const { classes } = this.props; + return ( + <Dialog open={this.props.mode !== EditMediatorConfigDialogMode.None}> + <DialogTitle id="form-dialog-title">{setting.dialogTitle}</DialogTitle> + <DialogContent> + <DialogContentText> + {setting.dialogDescription} + </DialogContentText> + <Tabs value={this.state.activeTab} indicatorColor="primary" textColor="primary" onChange={(event, value) => this.setState({ activeTab: value })} > + <Tab label="Config" /> + <Tab label="ODL AutoConnect" /> + </Tabs> + { this.state.activeTab === 0 ? <TabContainer > + <TextField disabled={setting.readonly || setting.readonlyName} spellCheck={false} autoFocus margin="dense" id="name" label="Name" type="text" fullWidth value={this.state.Name} onChange={(event) => { this.setState({ Name: event.target.value }); }} /> + <FormControl fullWidth disabled={setting.readonly}> + <InputLabel htmlFor="deviceType">Device</InputLabel> + <Select value={this.state.DeviceType} onChange={(event) => { + const device = this.props.supportedDevices.find(device => device.id === +event.target.value); + if (device) { + this.setState({ + DeviceType: device.id, + NeXMLFile: device.xml + }); + } else { + this.setState({ + DeviceType: -1, + NeXMLFile: "" + }); + } + }} inputProps={{ name: 'deviceType', id: 'deviceType' }} fullWidth > + <MenuItem value={-1}><em>None</em></MenuItem> + {this.props.supportedDevices.map(device => (<MenuItem key={device.id} value={device.id} >{`${device.vendor} - ${device.device} (${device.version || '0.0.0'}) `}</MenuItem>))} + </Select> + </FormControl> + <TextField disabled={setting.readonly} spellCheck={false} autoFocus margin="dense" id="ipAddress" label="IP Address" type="text" fullWidth value={this.state.DeviceIp} onChange={(event) => { this.setState({ DeviceIp: event.target.value }); }} /> + <TextField disabled={setting.readonly} spellCheck={false} autoFocus margin="dense" id="devicePort" label="Port" type="number" fullWidth value={this.state.DevicePort || ""} onChange={(event) => { this.setState({ DevicePort: +event.target.value }); }} /> + <TextField disabled={setting.readonly} spellCheck={false} autoFocus margin="dense" id="trapsPort" label="TrapsPort" type="number" fullWidth value={this.state.TrapPort || ""} onChange={(event) => { this.setState({ TrapPort: +event.target.value }); }} /> + <TextField disabled={setting.readonly} spellCheck={false} autoFocus margin="dense" id="ncUser" label="Netconf User" type="text" fullWidth value={this.state.NcUsername} onChange={(event) => { this.setState({ NcUsername: event.target.value }); }} /> + <TextField disabled={setting.readonly} spellCheck={false} autoFocus margin="dense" id="ncPassword" label="Netconf Password" type="password" fullWidth value={this.state.NcPassword} onChange={(event) => { this.setState({ NcPassword: event.target.value }); }} /> + <TextField disabled={setting.readonly} spellCheck={false} autoFocus margin="dense" id="ncPort" label="Netconf Port" type="number" fullWidth value={this.state.NcPort || ""} onChange={(event) => { this.setState({ NcPort: +event.target.value }); }} /> + </TabContainer> : null} + { this.state.activeTab === 1 ? <TabContainer > + { this.state.ODLConfig && this.state.ODLConfig.length > 0 + ? this.state.ODLConfig.map((cfg, ind) => { + const panelId = `panel-${ind}`; + const deleteButton = (<IconButton onClick={() => { + this.setState({ + ODLConfig: [ + ...this.state.ODLConfig.slice(0, ind), + ...this.state.ODLConfig.slice(ind + 1) + ] + }); + }} ><DeleteIcon /></IconButton>) + return ( + <Panel title={cfg.Server && `${cfg.User ? `${cfg.User}@` : ''}${cfg.Protocol}://${cfg.Server}:${cfg.Port}` || "new odl config"} key={panelId} panelId={panelId} activePanel={this.state.activeOdlConfig} customActionButtons={[deleteButton]} + onToggle={(id) => this.setState({ activeOdlConfig: (this.state.activeOdlConfig === id) ? "" : (id || "") })} > + <div className={classes.alignInOneLine}> + <FormControl className={classes.left} margin={"dense"} > + <InputLabel htmlFor={`protocol-${ind}`}>Protocoll</InputLabel> + <Select value={cfg.Protocol} onChange={ this.odlConfigValueChangeHandlerCreator(ind, "Protocol", e => (e.target.value)) } inputProps={{ name: `protocol-${ind}`, id: `protocol-${ind}` }} fullWidth > + <MenuItem value={"http"}>http</MenuItem> + <MenuItem value={"https"}>https</MenuItem> + </Select> + </FormControl> + <TextField className={classes.left} spellCheck={false} margin="dense" id="hostname" label="Hostname" type="text" value={cfg.Server} onChange={this.odlConfigValueChangeHandlerCreator(ind, "Server", e => e.target.value)} /> + <TextField className={classes.right} style={{ maxWidth: "65px"}} spellCheck={false} margin="dense" id="port" label="Port" type="number" value={cfg.Port|| ""} onChange={this.odlConfigValueChangeHandlerCreator(ind, "Port", e => +e.target.value)} /> + </div> + <div className={classes.alignInOneLine}> + <TextField className={classes.left} spellCheck={false} margin="dense" id="username" label="Username" type="text" value={cfg.User} onChange={this.odlConfigValueChangeHandlerCreator(ind, "User", e => e.target.value)} /> + <TextField className={classes.right} spellCheck={false} margin="dense" id="password" label="Password" type="password" value={cfg.Password} onChange={this.odlConfigValueChangeHandlerCreator(ind, "Password", e => e.target.value)} /> + </div> + <div className={classes.alignInOneLine}> + <FormControlLabel className={classes.right} control={<Checkbox checked={cfg.Trustall} onChange={this.odlConfigValueChangeHandlerCreator(ind, "Trustall", e => e.target.checked)} />} label="Trustall" /> + </div> + </Panel> + ); + }) + : <div className={classes.center} > + <Typography component={"div"} className={classes.title} color="textSecondary" gutterBottom>Please add an ODL auto connect configuration.</Typography> + </div> + } + <Fab className={classes.fab} color="primary" aria-label="Add" onClick={() => this.setState({ + ODLConfig: [...this.state.ODLConfig, { Server: '', Port: 8181, Protocol: 'https', User: 'admin', Password: 'admin', Trustall: false }] + })} > <AddIcon /> </Fab> + + </TabContainer> : null} + + </DialogContent> + <DialogActions> + <Button onClick={(event) => { + this.onApply(Object.keys(this.state).reduce<MediatorConfig & { [kex: string]: any }>((acc, key) => { + // do not copy activeTab and activeOdlConfig + if (key !== "activeTab" && key !== "activeOdlConfig" && key !== "_initialMediatorConfig") acc[key] = (this.state as any)[key]; + return acc; + }, {} as MediatorConfig)); + 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 = (config: MediatorConfig) => { + this.props.onClose && this.props.onClose(); + switch (this.props.mode) { + case EditMediatorConfigDialogMode.AddMediatorConfig: + config && this.props.addMediatorConfig(config); + break; + case EditMediatorConfigDialogMode.EditMediatorConfig: + config && this.props.updateMediatorConfig(config); + break; + case EditMediatorConfigDialogMode.RemoveMediatorConfig: + config && this.props.removeMediatorConfig(config); + break; + } + }; + + private onCancel = () => { + this.props.onClose && this.props.onClose(); + } + + static getDerivedStateFromProps(props: EditMediatorConfigDialogComponentProps, state: EditMediatorConfigDialogComponentState & { _initialMediatorConfig: MediatorConfig }): EditMediatorConfigDialogComponentState & { _initialMediatorConfig: MediatorConfig } { + if (props.mediatorConfig !== state._initialMediatorConfig) { + state = { + ...state, + ...props.mediatorConfig, + _initialMediatorConfig: props.mediatorConfig, + }; + } + return state; + } +} + +export const EditMediatorConfigDialog = withStyles(styles)(connect(mapProps, mapDispatch)(EditMediatorConfigDialogComponent)); +export default EditMediatorConfigDialog; + + diff --git a/sdnr/wt/odlux/apps/mediatorApp/src/components/editMediatorServerDialog.tsx b/sdnr/wt/odlux/apps/mediatorApp/src/components/editMediatorServerDialog.tsx new file mode 100644 index 000000000..3937e9731 --- /dev/null +++ b/sdnr/wt/odlux/apps/mediatorApp/src/components/editMediatorServerDialog.tsx @@ -0,0 +1,156 @@ +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 { addAvaliableMediatorServerAsyncActionCreator, removeAvaliableMediatorServerAsyncActionCreator, updateAvaliableMediatorServerAsyncActionCreator } from '../actions/avaliableMediatorServersActions'; +import { MediatorServer } from '../models/mediatorServer'; + +export enum EditMediatorServerDialogMode { + None = "none", + AddMediatorServer = "addMediatorServer", + EditMediatorServer = "editMediatorServer", + RemoveMediatorServer = "removeMediatorServer", +} + +const mapDispatch = (dispatcher: IDispatcher) => ({ + addMediatorServer: (element: MediatorServer) => { + dispatcher.dispatch(addAvaliableMediatorServerAsyncActionCreator(element)); + }, + updateMediatorServer: (element: MediatorServer) => { + dispatcher.dispatch(updateAvaliableMediatorServerAsyncActionCreator(element)); + }, + removeMediatorServer: (element: MediatorServer) => { + dispatcher.dispatch(removeAvaliableMediatorServerAsyncActionCreator(element)); + }, +}); + +type DialogSettings = { + dialogTitle: string; + dialogDescription: string; + applyButtonText: string; + cancelButtonText: string; + readonly: boolean; +}; + +const settings: { [key: string]: DialogSettings } = { + [EditMediatorServerDialogMode.None]: { + dialogTitle: "", + dialogDescription: "", + applyButtonText: "", + cancelButtonText: "", + readonly: true, + }, + [EditMediatorServerDialogMode.AddMediatorServer]: { + dialogTitle: "Add Medator Server", + dialogDescription: "", + applyButtonText: "Add", + cancelButtonText: "Cancel", + readonly: false, + }, + [EditMediatorServerDialogMode.EditMediatorServer]: { + dialogTitle: "Edit Medator Server", + dialogDescription: "", + applyButtonText: "Update", + cancelButtonText: "Cancel", + readonly: false, + }, + [EditMediatorServerDialogMode.RemoveMediatorServer]: { + dialogTitle: "Remove Mediator Server", + dialogDescription: "", + applyButtonText: "Remove", + cancelButtonText: "Cancel", + readonly: true, + }, +}; + +type EditMediatorServerDialogComponentProps = Connect<undefined,typeof mapDispatch> & { + mode: EditMediatorServerDialogMode; + mediatorServer: MediatorServer; + onClose: () => void; +}; + +type EditMediatorServerDialogComponentState = MediatorServer ; + +class EditMediatorServerDialogComponent extends React.Component<EditMediatorServerDialogComponentProps, EditMediatorServerDialogComponentState> { + constructor(props: EditMediatorServerDialogComponentProps) { + super(props); + + this.state = { + ...this.props.mediatorServer + }; + } + + render(): JSX.Element { + const setting = settings[this.props.mode]; + return ( + <Dialog open={ this.props.mode !== EditMediatorServerDialogMode.None }> + <DialogTitle id="form-dialog-title">{ setting.dialogTitle }</DialogTitle> + <DialogContent> + <DialogContentText> + { setting.dialogDescription } + </DialogContentText> + <TextField disabled spellCheck={false} autoFocus margin="dense" id="id" label="Id" type="text" fullWidth value={ this.state._id } onChange={(event)=>{ this.setState({_id: event.target.value}); } } /> + <TextField disabled={ setting.readonly } spellCheck={false} margin="dense" id="name" label="Name" type="text" fullWidth value={ this.state.name } onChange={(event)=>{ this.setState({name: event.target.value}); } }/> + <TextField disabled={ setting.readonly } spellCheck={false} margin="dense" id="url" label="Url" type="text" fullWidth value={ this.state.url } onChange={(event)=>{ this.setState({url: event.target.value}); } }/> + </DialogContent> + <DialogActions> + <Button onClick={ (event) => { + this.onApply({ + _id: this.state._id, + name: this.state.name, + url: this.state.url, + }); + 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: MediatorServer) => { + this.props.onClose && this.props.onClose(); + switch (this.props.mode) { + case EditMediatorServerDialogMode.AddMediatorServer: + element && this.props.addMediatorServer(element); + break; + case EditMediatorServerDialogMode.EditMediatorServer: + element && this.props.updateMediatorServer(element); + break; + case EditMediatorServerDialogMode.RemoveMediatorServer: + element && this.props.removeMediatorServer(element); + break; + } + }; + + private onCancel = () => { + this.props.onClose && this.props.onClose(); + } + + static getDerivedStateFromProps(props: EditMediatorServerDialogComponentProps, state: EditMediatorServerDialogComponentState & { _initialMediatorServer: MediatorServer }): EditMediatorServerDialogComponentState & { _initialMediatorServer: MediatorServer } { + if (props.mediatorServer !== state._initialMediatorServer) { + state = { + ...state, + ...props.mediatorServer, + _initialMediatorServer: props.mediatorServer, + }; + } + return state; + } +} + +export const EditMediatorServerDialog = connect(undefined, mapDispatch)(EditMediatorServerDialogComponent); +export default EditMediatorServerDialog;
\ No newline at end of file diff --git a/sdnr/wt/odlux/apps/mediatorApp/src/handlers/avaliableMediatorServersHandler.ts b/sdnr/wt/odlux/apps/mediatorApp/src/handlers/avaliableMediatorServersHandler.ts new file mode 100644 index 000000000..447e5e5bd --- /dev/null +++ b/sdnr/wt/odlux/apps/mediatorApp/src/handlers/avaliableMediatorServersHandler.ts @@ -0,0 +1,19 @@ +import { createExternal,IExternalTableState } from '../../../../framework/src/components/material-table/utilities'; +import { createSearchDataHandler } from '../../../../framework/src/utilities/elasticSearch'; + +import { MediatorServer } from '../models/mediatorServer'; +import { mediatorServerResourcePath } from '../services/mediatorService'; + +export interface IAvaliableMediatorServersState extends IExternalTableState<MediatorServer> { } + +// create eleactic search material data fetch handler +const avaliableMediatorServersSearchHandler = createSearchDataHandler<MediatorServer>(mediatorServerResourcePath); + +export const { + actionHandler: avaliableMediatorServersActionHandler, + createActions: createAvaliableMediatorServersActions, + createProperties: createAvaliableMediatorServersProperties, + reloadAction: avaliableMediatorServersReloadAction, + + // set value action, to change a value +} = createExternal<MediatorServer>(avaliableMediatorServersSearchHandler, appState => appState.mediatorApp.avaliableMediatorServers);
\ No newline at end of file diff --git a/sdnr/wt/odlux/apps/mediatorApp/src/handlers/mediatorAppRootHandler.ts b/sdnr/wt/odlux/apps/mediatorApp/src/handlers/mediatorAppRootHandler.ts new file mode 100644 index 000000000..bfebbdf5d --- /dev/null +++ b/sdnr/wt/odlux/apps/mediatorApp/src/handlers/mediatorAppRootHandler.ts @@ -0,0 +1,26 @@ +// main state handler + +import { combineActionHandler } from '../../../../framework/src/flux/middleware'; +import { IApplicationStoreState } from '../../../../framework/src/store/applicationStore'; + +import { IAvaliableMediatorServersState, avaliableMediatorServersActionHandler } from './avaliableMediatorServersHandler' ; +import { MediatorServerState, mediatorServerHandler } from './mediatorServerHandler'; + +export interface IMediatorAppStoreState { + avaliableMediatorServers: IAvaliableMediatorServersState, + mediatorServerState: MediatorServerState, +} + +declare module '../../../../framework/src/store/applicationStore' { + interface IApplicationStoreState { + mediatorApp: IMediatorAppStoreState + } +} + +const actionHandlers = { + avaliableMediatorServers: avaliableMediatorServersActionHandler, + mediatorServerState: mediatorServerHandler, +}; + +export const mediatorAppRootHandler = combineActionHandler<IMediatorAppStoreState>(actionHandlers); +export default mediatorAppRootHandler; diff --git a/sdnr/wt/odlux/apps/mediatorApp/src/handlers/mediatorServerHandler.ts b/sdnr/wt/odlux/apps/mediatorApp/src/handlers/mediatorServerHandler.ts new file mode 100644 index 000000000..2d3f71c82 --- /dev/null +++ b/sdnr/wt/odlux/apps/mediatorApp/src/handlers/mediatorServerHandler.ts @@ -0,0 +1,96 @@ +import { XmlFileInfo, MediatorConfig, BusySymbol, MediatorConfigResponse, MediatorServerDevice } from "../models/mediatorServer"; +import { IActionHandler } from "../../../../framework/src/flux/action"; +import { SetMediatorServerVersion, SetMediatorServerInfo, SetAllMediatorServerConfigurations, SetMediatorServerBusy, SetMediatorServerSupportedDevices } from "../actions/mediatorServerActions"; +import { SetMediatorBusyByName, UpdateMediatorConfig, AddMediatorConfig, RemoveMediatorConfig } from "../actions/mediatorConfigActions"; + +export type MediatorServerState = { + busy: boolean; + name: string| null ; + url: string | null; + serverVersion: string| null; + mediatorVersion: string| null; + nexmls: XmlFileInfo[]; + configurations: MediatorConfigResponse[]; + supportedDevices: MediatorServerDevice[]; +} + +const mediatorServerInit: MediatorServerState = { + busy: false, + name: null, + url: null, + serverVersion : null, + mediatorVersion: null, + nexmls: [], + configurations: [], + supportedDevices: [] +} + +export const mediatorServerHandler: IActionHandler<MediatorServerState> = (state = mediatorServerInit, action) => { + if (action instanceof SetMediatorServerBusy) { + state = { + ...state, + busy: action.isBusy + }; + } else if (action instanceof SetMediatorServerInfo) { + state = { + ...state, + name: action.name, + url: action.url, + }; + } else if (action instanceof SetMediatorServerVersion) { + state = { + ...state, + serverVersion: action.versionInfo && action.versionInfo.server , + mediatorVersion: action.versionInfo && action.versionInfo.mediator , + nexmls: action.versionInfo && [...action.versionInfo.nexmls] || [], + }; + } else if (action instanceof SetAllMediatorServerConfigurations) { + state = { + ...state, + configurations: action.allConfigurations && action.allConfigurations.map(config => ({...config, busy: false}) ) || [], + }; + } else if (action instanceof SetMediatorServerSupportedDevices) { + state = { + ...state, + supportedDevices: action.devices || [], + }; + } else if (action instanceof SetMediatorBusyByName) { + const index = state.configurations.findIndex(config => config.Name === action.name); + if (index > -1) state = { + ...state, + configurations:[ + ...state.configurations.slice(0, index), + { ...state.configurations[index], [BusySymbol]: action.isBusy }, + ...state.configurations.slice(index + 1) + ] + }; + } else if (action instanceof AddMediatorConfig) { + state = { + ...state, + configurations: [ + ...state.configurations, + action.mediatorConfig + ] + }; + } else if (action instanceof UpdateMediatorConfig) { + const index = state.configurations.findIndex(config => config.Name === action.name); + if (index > -1) state = { + ...state, + configurations:[ + ...state.configurations.slice(0, index), + { ...action.mediatorConfig, [BusySymbol]: state.configurations[index][BusySymbol] }, + ...state.configurations.slice(index + 1) + ] + }; + } else if (action instanceof RemoveMediatorConfig) { + const index = state.configurations.findIndex(config => config.Name === action.name); + if (index > -1) state = { + ...state, + configurations:[ + ...state.configurations.slice(0, index), + ...state.configurations.slice(index + 1) + ] + }; + } + return state; +}
\ No newline at end of file diff --git a/sdnr/wt/odlux/apps/mediatorApp/src/index.html b/sdnr/wt/odlux/apps/mediatorApp/src/index.html new file mode 100644 index 000000000..95bf9ec6b --- /dev/null +++ b/sdnr/wt/odlux/apps/mediatorApp/src/index.html @@ -0,0 +1,29 @@ +<!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>Mediator 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", "mediatorApp" ,"connectApp","inventoryApp","faultApp","helpApp"], function (app, mediatorApp, connectApp,inventoryApp,faultApp,helpApp) { + mediatorApp.register(); + connectApp.register(); + inventoryApp.register(); + faultApp.register(); + helpApp.register(); + app("./app.tsx").runApplication(); + }); + </script> +</body> + +</html>
\ No newline at end of file diff --git a/sdnr/wt/odlux/apps/mediatorApp/src/models/mediatorServer.ts b/sdnr/wt/odlux/apps/mediatorApp/src/models/mediatorServer.ts new file mode 100644 index 000000000..ff084b0c1 --- /dev/null +++ b/sdnr/wt/odlux/apps/mediatorApp/src/models/mediatorServer.ts @@ -0,0 +1,60 @@ +export type MediatorServer = { + _id: string; + name: string; + url: string; +} + +export type XmlFileInfo = { + filename: string; + version: string; +} + +export type MediatorServerVersionInfo = { + mediator: string; + server: string; + nexmls: XmlFileInfo[]; +} + +export type ODLConfig = { + User: string; + Password: string; + Port: number; + Protocol: "http" | "https"; + Server: string; + Trustall: boolean; +}; + +export const BusySymbol = Symbol("Busy"); + +export type MediatorConfig = { + Name: string; + DeviceIp: string; + DevicePort: number; + DeviceType: number; + TrapPort: number; + NcUsername: string; + NcPassword: string; + NcPort: number; + NeXMLFile: string; + ODLConfig: ODLConfig[]; +} + +export type MediatorConfigResponse = MediatorConfig & { + IsNCConnected: boolean; + IsNeConnected: boolean; + autorun: boolean; + fwactive: boolean; + islocked: boolean; + ncconnections:{}[]; + pid: number; + // extended properties + [BusySymbol]: boolean; +} + +export type MediatorServerDevice = { + id: number; // DeviceType + device: string; + vendor: string; + version: string; + xml: string; // NeXMLFile +}
\ No newline at end of file diff --git a/sdnr/wt/odlux/apps/mediatorApp/src/plugin.tsx b/sdnr/wt/odlux/apps/mediatorApp/src/plugin.tsx new file mode 100644 index 000000000..0878fee65 --- /dev/null +++ b/sdnr/wt/odlux/apps/mediatorApp/src/plugin.tsx @@ -0,0 +1,66 @@ +// app configuration and main entry point for the app + +import * as React from "react"; +import { withRouter, RouteComponentProps, Route, Switch, Redirect } from 'react-router-dom'; + +import { faGlobe } from '@fortawesome/free-solid-svg-icons'; // select app icon + +import applicationManager from '../../../framework/src/services/applicationManager'; +import { IApplicationStoreState } from "../../../framework/src/store/applicationStore"; +import connect, { Connect, IDispatcher } from '../../../framework/src/flux/connect'; + +import { mediatorAppRootHandler } from './handlers/mediatorAppRootHandler'; +import { avaliableMediatorServersReloadAction } from "./handlers/avaliableMediatorServersHandler"; + +import { MediatorApplication } from "./views/mediatorApplication"; +import { MediatorServerSelection } from "./views/mediatorServerSelection"; +import { initializeMediatorServerAsyncActionCreator } from "./actions/mediatorServerActions"; + +let currentMediatorServerId: string | undefined = undefined; + +const mapDisp = (dispatcher: IDispatcher) => ({ + loadMediatorServer : (mediatorServerId: string) => dispatcher.dispatch(initializeMediatorServerAsyncActionCreator(mediatorServerId)), +}); + +const MediatorServerRouteAdapter = connect(undefined, mapDisp)((props: RouteComponentProps<{ mediatorServerId: string }> & Connect<undefined, typeof mapDisp>) => { + if (currentMediatorServerId !== props.match.params.mediatorServerId) { + // route parameter has changed + currentMediatorServerId = props.match.params.mediatorServerId || undefined; + // Hint: This timeout is need, since it is not recommended to change the state while rendering is in progress ! + window.setTimeout(() => { + if (currentMediatorServerId) { + props.loadMediatorServer(currentMediatorServerId); + } + }); + } + return ( + <MediatorApplication /> + ) +}); + +type AppProps = RouteComponentProps & Connect; + +const App = (props: AppProps) => ( + <Switch> + <Route exact path={ `${ props.match.path }` } component={ MediatorServerSelection } /> + <Route path={ `${ props.match.path }/:mediatorServerId` } component={ MediatorServerRouteAdapter } /> + <Redirect to={ `${ props.match.path }` } /> + </Switch> +); + +const FinalApp = withRouter(connect()(App)); + +export function register() { + const applicationApi = applicationManager.registerApplication({ + name: "mediatorApp", + icon: faGlobe, + rootComponent: FinalApp, + rootActionHandler: mediatorAppRootHandler, + menuEntry: "Mediator App" + }); + + // prefetch all avaliable mediator servers + applicationApi.applicationStoreInitialized.then(applicationStore => { + applicationStore.dispatch(avaliableMediatorServersReloadAction) + }); +}; diff --git a/sdnr/wt/odlux/apps/mediatorApp/src/services/mediatorService.ts b/sdnr/wt/odlux/apps/mediatorApp/src/services/mediatorService.ts new file mode 100644 index 000000000..50fd869b1 --- /dev/null +++ b/sdnr/wt/odlux/apps/mediatorApp/src/services/mediatorService.ts @@ -0,0 +1,149 @@ +import * as $ from 'jquery'; + +import { requestRest } from '../../../../framework/src/services/restService'; +import { MediatorServer, MediatorServerVersionInfo, MediatorConfig, MediatorServerDevice, MediatorConfigResponse } from '../models/mediatorServer'; +import { HitEntry } from '../../../../framework/src/models'; + +export const mediatorServerResourcePath = "mwtn/mediator-server"; + +type MediatorServerResponse<TData> = { code: number, data: TData }; +type IndexableMediatorServer = MediatorServer & { [key: string]: any; } ; + +/** + * Represents a web api accessor service for all mediator server actions. + */ +class MediatorService { + /** + * Inserts data into the mediator servers table. + */ + public async insertMediatorServer(server: IndexableMediatorServer): Promise<MediatorServer | null> { + const path = `database/${mediatorServerResourcePath}`; + const data = Object.keys(server).reduce((acc, cur) => { + if (cur !== "_id") acc[cur] = server[cur]; + return acc; + }, {} as IndexableMediatorServer); + const result = await requestRest<MediatorServer>(path, { method: "POST", body: JSON.stringify(data) }); + return result || null; + } + + /** + * Updates data into the mediator servers table. + */ + public async updateMediatorServer(server: IndexableMediatorServer): Promise<MediatorServer | null> { + const path = `database/${mediatorServerResourcePath}/${server._id}`; + const data = Object.keys(server).reduce((acc, cur) => { + if (cur !== "_id") { acc[cur] = server[cur] } else { acc["id"] = 0 }; + return acc; + }, {} as IndexableMediatorServer); + const result = await requestRest<MediatorServer>(path, { method: "PUT", body: JSON.stringify(data) }); + return result || null; + } + + /** + * Deletes data from the mediator servers table. + */ + public async deleteMediatorServer(server: MediatorServer): Promise<MediatorServer | null> { + const path = `database/${mediatorServerResourcePath}/${server._id}`; + const result = await requestRest<MediatorServer>(path, { method: "DELETE" }); + return result || null; + } + + public async getMediatorServerById(serverId: string): Promise<MediatorServer | null> { + const path = `database/${mediatorServerResourcePath}/${serverId}`; + const result = await requestRest<HitEntry<MediatorServer> & { found: boolean }>(path, { method: "GET" }); + return result && result.found && result._source && { + _id: result._id, + name: result._source.name, + url: result._source.url, + } || null; + } + + // https://cloud-highstreet-technologies.com/wiki/doku.php?id=att:ms:api + + private accassMediatorServer<TData ={}>(mediatorServerUrl: string, task: string, data?: {}): Promise<MediatorServerResponse<TData> | null> { + const url = `${mediatorServerUrl}/api/?task=${task}`; + // return (await requestRest<{ code: number, data: TData}>(path, { method: "POST" })) || null ; + return new Promise<{ code: number, data: TData }>((resolve, reject) => { + $.post({ + url, + data: data, + //contentType: 'application/json' + }).then((result: any) => { + if (typeof result === "string") { + resolve(JSON.parse(result)); + } else { + resolve(result); + }; + }); + }); + } + + public async getMediatorServerVersion(mediatorServerUrl: string): Promise<MediatorServerVersionInfo | null> { + const result = await this.accassMediatorServer<MediatorServerVersionInfo>(mediatorServerUrl, 'version'); + if (result && result.code === 1) return result.data; + return null; + } + + public async getMediatorServerAllConfigs(mediatorServerUrl: string): Promise<MediatorConfigResponse[] | null> { + const result = await this.accassMediatorServer<MediatorConfigResponse[]>(mediatorServerUrl, 'getconfig'); + if (result && result.code === 1) return result.data; + return null; + } + + public async getMediatorServerConfigByName(mediatorServerUrl: string, name: string): Promise<MediatorConfigResponse | null> { + const result = await this.accassMediatorServer<MediatorConfigResponse[]>(mediatorServerUrl, 'getconfig', { name } ); + if (result && result.code === 1 && result.data && result.data.length === 1) return result.data[0]; + return null; + } + + public async getMediatorServerSupportedDevices(mediatorServerUrl: string): Promise<MediatorServerDevice[] | null> { + const result = await this.accassMediatorServer<MediatorServerDevice[]>(mediatorServerUrl, 'getdevices' ); + if (result && result.code === 1) return result.data; + return null; + } + + public async startMediatorByName(mediatorServerUrl: string, name: string): Promise<string | null> { + const result = await this.accassMediatorServer<string>(mediatorServerUrl, 'start', { name } ); + if (result && result.code === 1) return result.data; + return null; + } + + public async stopMediatorByName(mediatorServerUrl: string, name: string): Promise<string | null> { + const result = await this.accassMediatorServer<string>(mediatorServerUrl, 'stop', { name } ); + if (result && result.code === 1) return result.data; + return null; + } + + public async createMediatorConfig(mediatorServerUrl: string, config: MediatorConfig): Promise<string | null> { + const result = await this.accassMediatorServer<string>(mediatorServerUrl, 'create', { config: JSON.stringify(config) } ); + if (result && result.code === 1) return result.data; + return null; + } + + public async updateMediatorConfigByName(mediatorServerUrl: string, config: MediatorConfig): Promise<string | null> { + const result = await this.accassMediatorServer<string>(mediatorServerUrl, 'update', { config: JSON.stringify(config) } ); + if (result && result.code === 1) return result.data; + return null; + } + + public async deleteMediatorConfigByName(mediatorServerUrl: string, name: string): Promise<string | null> { + const result = await this.accassMediatorServer<string>(mediatorServerUrl, 'delete', { name } ); + if (result && result.code === 1) return result.data; + return null; + } + + public async getMediatorServerFreeNcPorts(mediatorServerUrl: string, limit?: number): Promise<number[] | null> { + const result = await this.accassMediatorServer<number[]>(mediatorServerUrl, 'getncports', { limit } ); + if (result && result.code === 1) return result.data; + return null; + } + + public async getMediatorServerFreeSnmpPorts(mediatorServerUrl: string, limit?: number): Promise<number[] | null> { + const result = await this.accassMediatorServer<number[]>(mediatorServerUrl, 'getsnmpports', { limit } ); + if (result && result.code === 1) return result.data; + return null; + } +} + +export const mediatorService = new MediatorService; +export default mediatorService;
\ No newline at end of file diff --git a/sdnr/wt/odlux/apps/mediatorApp/src/views/mediatorApplication.tsx b/sdnr/wt/odlux/apps/mediatorApp/src/views/mediatorApplication.tsx new file mode 100644 index 000000000..11feb46ae --- /dev/null +++ b/sdnr/wt/odlux/apps/mediatorApp/src/views/mediatorApplication.tsx @@ -0,0 +1,220 @@ +import * as React from 'react'; +import { Theme, createStyles, WithStyles, withStyles, Tooltip } from '@material-ui/core'; + +import AddIcon from '@material-ui/icons/Add'; +import IconButton from '@material-ui/core/IconButton'; +import EditIcon from '@material-ui/icons/Edit'; +import DeleteIcon from '@material-ui/icons/Delete'; +import InfoIcon from '@material-ui/icons/Info'; +import StartIcon from '@material-ui/icons/PlayArrow'; +import StopIcon from '@material-ui/icons/Stop'; + +import CircularProgress from '@material-ui/core/CircularProgress' + +import { IApplicationStoreState } from '../../../../framework/src/store/applicationStore'; +import connect, { Connect, IDispatcher } from '../../../../framework/src/flux/connect'; +import MaterialTable, { MaterialTableCtorType, ColumnType } from '../../../../framework/src/components/material-table'; + +import { MediatorConfig, BusySymbol, MediatorConfigResponse } from '../models/mediatorServer'; +import EditMediatorConfigDialog, { EditMediatorConfigDialogMode } from '../components/editMediatorConfigDialog'; +import { startMediatorByNameAsyncActionCreator, stopMediatorByNameAsyncActionCreator } from '../actions/mediatorConfigActions'; +import mediatorService from '../services/mediatorService'; + +const styles = (theme: Theme) => createStyles({ + root: { + display: 'flex', + flexDirection: 'column', + flex: '1', + }, + formControl: { + margin: theme.spacing.unit, + minWidth: 300, + }, + button: { + margin: 0, + padding: "6px 6px", + minWidth: 'unset' + }, + spacer: { + marginLeft: theme.spacing.unit, + marginRight: theme.spacing.unit, + display: "inline" + }, + progress: { + flex: '1 1 100%', + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + pointerEvents: 'none' + } +}); + +const mapProps = (state: IApplicationStoreState) => ({ + serverName: state.mediatorApp.mediatorServerState.name, + serverUrl: state.mediatorApp.mediatorServerState.url, + serverVersion: state.mediatorApp.mediatorServerState.serverVersion, + mediatorVersion: state.mediatorApp.mediatorServerState.mediatorVersion, + configurations: state.mediatorApp.mediatorServerState.configurations, + supportedDevices: state.mediatorApp.mediatorServerState.supportedDevices, + busy: state.mediatorApp.mediatorServerState.busy, +}); + +const mapDispatch = (dispatcher: IDispatcher) => ({ + startMediator: (name: string) => dispatcher.dispatch(startMediatorByNameAsyncActionCreator(name)), + stopMediator: (name: string) => dispatcher.dispatch(stopMediatorByNameAsyncActionCreator(name)), +}); + +const emptyMediatorConfig: MediatorConfig = { + Name: "", + DeviceIp: "127.0.0.1", + DevicePort: 161, + NcUsername: "admin", + NcPassword: "admin", + DeviceType: -1, + NcPort: 4020, + TrapPort: 10020, + NeXMLFile: "", + ODLConfig: [] +}; + +const MediatorServerConfigurationsTable = MaterialTable as MaterialTableCtorType<MediatorConfigResponse>; + +type MediatorApplicationComponentProps = Connect<typeof mapProps, typeof mapDispatch> & WithStyles<typeof styles>; + +type MediatorServerSelectionComponentState = { + busy: boolean, + mediatorConfigToEdit: MediatorConfig, + mediatorConfigEditorMode: EditMediatorConfigDialogMode +} + +class MediatorApplicationComponent extends React.Component<MediatorApplicationComponentProps, MediatorServerSelectionComponentState> { + + constructor (props: MediatorApplicationComponentProps) { + super(props); + + this.state = { + busy: false, + mediatorConfigToEdit: emptyMediatorConfig, + mediatorConfigEditorMode: EditMediatorConfigDialogMode.None, + } + } + + render() { + const { classes } = this.props; + + const renderActions = (rowData: MediatorConfigResponse) => ( + <> + <div className={classes.spacer}> + <Tooltip title={"Start"} > + <IconButton disabled={rowData[BusySymbol]} className={classes.button}> + <StartIcon onClick={(event) => { event.preventDefault(); event.stopPropagation(); this.props.startMediator(rowData.Name); }} /> + </IconButton> + </Tooltip> + <Tooltip title={"Stop"} > + <IconButton disabled={rowData[BusySymbol]} className={classes.button}> + <StopIcon onClick={(event) => { event.preventDefault(); event.stopPropagation(); this.props.stopMediator(rowData.Name); }} /> + </IconButton> + </Tooltip> + </div> + <div className={classes.spacer}> + <Tooltip title={"Info"} ><IconButton className={classes.button}><InfoIcon /></IconButton></Tooltip> + </div> + <div className={classes.spacer}> + {process.env.NODE_ENV === "development" ? <Tooltip title={"Edit"} ><IconButton disabled={rowData[BusySymbol]} className={classes.button} onClick={event => this.onOpenEditConfigurationDialog(event, rowData)}><EditIcon /></IconButton></Tooltip> : null} + <Tooltip title={"Remove"} ><IconButton disabled={rowData[BusySymbol]} className={classes.button} onClick={event => this.onOpenRemoveConfigutationDialog(event, rowData)}><DeleteIcon /></IconButton></Tooltip> + </div> + </> + ); + + const addMediatorConfigAction = { icon: AddIcon, tooltip: 'Add', onClick: this.onOpenAddConfigurationDialog }; + return ( + <div className={classes.root}> + {this.props.busy || this.state.busy + ? <div className={classes.progress}> <CircularProgress color={"secondary"} size={48} /> </div> + : <MediatorServerConfigurationsTable title={this.props.serverName || ''} customActionButtons={[addMediatorConfigAction]} idProperty={"Name"} rows={this.props.configurations} asynchronus columns={[ + { property: "Name", title: "Mediator", type: ColumnType.text }, + { property: "Status", title: "Status", type: ColumnType.custom, customControl: ({ rowData }) => rowData.pid ? (<span>Running</span>) : (<span>Stopped</span>) }, + { property: "DeviceIp", title: "IP Adress", type: ColumnType.text }, + { + property: "Device", title: "Device", type: ColumnType.custom, customControl: ({ rowData }) => { + const dev = this.props.supportedDevices && this.props.supportedDevices.find(dev => dev.id === rowData.DeviceType); + return ( + <span> {dev && `${dev.vendor} - ${dev.device} (${dev.version || '0.0.0'})`} </span> + ) + } + }, + { property: "actions", title: "Actions", type: ColumnType.custom, customControl: ({ rowData }) => renderActions(rowData) }, + ]} /> + } + + <EditMediatorConfigDialog + mediatorConfig={this.state.mediatorConfigToEdit} + mode={this.state.mediatorConfigEditorMode} + onClose={this.onCloseEditMediatorConfigDialog} /> + + </div> + ); + } + + private onOpenAddConfigurationDialog = () => { + // Tries to determine a free port for netconf listener and snpm listener + // it it could not determine free ports the dialog will open any way + // those ports should not be configured from the fontend, furthermore + // the backend should auto configure them and tell the user the result + // after the creation process. + + this.setState({ + busy: true, + }); + this.props.serverUrl && Promise.all([ + mediatorService.getMediatorServerFreeNcPorts(this.props.serverUrl, 1), + mediatorService.getMediatorServerFreeSnmpPorts(this.props.serverUrl, 1), + ]).then(([freeNcPorts, freeSnmpPorts]) => { + if (freeNcPorts && freeSnmpPorts && freeNcPorts.length > 0 && freeSnmpPorts.length > 0) { + this.setState({ + busy: false, + mediatorConfigEditorMode: EditMediatorConfigDialogMode.AddMediatorConfig, + mediatorConfigToEdit: { + ...emptyMediatorConfig, + NcPort: freeNcPorts[0], + TrapPort: freeSnmpPorts[0], + }, + }); + } else { + this.setState({ + busy: false, + mediatorConfigEditorMode: EditMediatorConfigDialogMode.AddMediatorConfig, + mediatorConfigToEdit: { ...emptyMediatorConfig }, + }); + } + }) + + } + + private onOpenEditConfigurationDialog = (event: React.MouseEvent<HTMLElement>, configEntry: MediatorConfig) => { + event.preventDefault(); + event.stopPropagation(); + this.setState({ + mediatorConfigEditorMode: EditMediatorConfigDialogMode.EditMediatorConfig, + mediatorConfigToEdit: configEntry, + }); + } + + private onOpenRemoveConfigutationDialog = (event: React.MouseEvent<HTMLElement>, configEntry: MediatorConfig) => { + event.preventDefault(); + event.stopPropagation(); + this.setState({ + mediatorConfigEditorMode: EditMediatorConfigDialogMode.RemoveMediatorConfig, + mediatorConfigToEdit: configEntry, + }); + } + + private onCloseEditMediatorConfigDialog = () => { + this.setState({ + mediatorConfigEditorMode: EditMediatorConfigDialogMode.None, + mediatorConfigToEdit: emptyMediatorConfig, + }); + } +} + +export const MediatorApplication = withStyles(styles)(connect(mapProps, mapDispatch)(MediatorApplicationComponent)); diff --git a/sdnr/wt/odlux/apps/mediatorApp/src/views/mediatorServerSelection.tsx b/sdnr/wt/odlux/apps/mediatorApp/src/views/mediatorServerSelection.tsx new file mode 100644 index 000000000..38bbdecb4 --- /dev/null +++ b/sdnr/wt/odlux/apps/mediatorApp/src/views/mediatorServerSelection.tsx @@ -0,0 +1,136 @@ +import * as React from 'react'; +import { WithStyles, withStyles, createStyles, Theme, Tooltip } from '@material-ui/core'; + +import AddIcon from '@material-ui/icons/Add'; +import IconButton from '@material-ui/core/IconButton'; +import EditIcon from '@material-ui/icons/Edit'; +import DeleteIcon from '@material-ui/icons/Delete'; + +import { IApplicationStoreState } from '../../../../framework/src/store/applicationStore'; +import connect, { IDispatcher, Connect } from '../../../../framework/src/flux/connect'; +import MaterialTable, { MaterialTableCtorType, ColumnType } from '../../../../framework/src/components/material-table'; + +import { createAvaliableMediatorServersProperties, createAvaliableMediatorServersActions } from '../handlers/avaliableMediatorServersHandler'; + +import { MediatorServer } from '../models/mediatorServer'; +import EditMediatorServerDialog, { EditMediatorServerDialogMode } from '../components/editMediatorServerDialog'; +import { NavigateToApplication } from '../../../../framework/src/actions/navigationActions'; + +const MediatorServersTable = MaterialTable as MaterialTableCtorType<MediatorServer>; + +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 = (state: IApplicationStoreState) => ({ + mediatorServersProperties: createAvaliableMediatorServersProperties(state), +}); + +const mapDispatch = (dispatcher: IDispatcher) => ({ + mediatorServersActions: createAvaliableMediatorServersActions(dispatcher.dispatch), + selectMediatorServer: (mediatorServerId: string) => mediatorServerId && dispatcher.dispatch(new NavigateToApplication("mediatorApp", mediatorServerId)), +}); + +const emptyMediatorServer: MediatorServer = { + _id: "", + name: "", + url: "" +}; + +type MediatorServerSelectionComponentProps = Connect<typeof mapProps, typeof mapDispatch> & WithStyles<typeof styles>; + +type MediatorServerSelectionComponentState = { + mediatorServerToEdit: MediatorServer, + mediatorServerEditorMode: EditMediatorServerDialogMode +} + +class MediatorServerSelectionComponent extends React.Component<MediatorServerSelectionComponentProps, MediatorServerSelectionComponentState> { + + constructor (props: MediatorServerSelectionComponentProps) { + super(props); + + this.state = { + mediatorServerEditorMode: EditMediatorServerDialogMode.None, + mediatorServerToEdit: emptyMediatorServer, + } + } + + render() { + const { classes } = this.props; + + const addMediatorServerActionButton = { + icon: AddIcon, tooltip: 'Add', onClick: () => { + this.setState({ + mediatorServerEditorMode: EditMediatorServerDialogMode.AddMediatorServer, + mediatorServerToEdit: emptyMediatorServer, + }); + } + }; + return ( + <> + <MediatorServersTable customActionButtons={[addMediatorServerActionButton]} idProperty={"_id"} + {...this.props.mediatorServersActions} {...this.props.mediatorServersProperties} columns={[ + { property: "name", title: "Name", type: ColumnType.text }, + { property: "url", title: "Url", type: ColumnType.text }, + { + property: "actions", title: "Actions", type: ColumnType.custom, customControl: ({ rowData }) => ( + <div className={classes.spacer}> + <Tooltip title={"Edit"} ><IconButton className={classes.button} onClick={event => { this.onEditMediatorServer(event, rowData); }}><EditIcon /></IconButton></Tooltip> + <Tooltip title={"Remove"} ><IconButton className={classes.button} onClick={event => { this.onRemoveMediatorServer(event, rowData); }}><DeleteIcon /></IconButton></Tooltip> + </div> + ) + } + ]} onHandleClick={ this.onSelectMediatorServer } /> + <EditMediatorServerDialog + mediatorServer={ this.state.mediatorServerToEdit } + mode={ this.state.mediatorServerEditorMode } + onClose={ this.onCloseEditMediatorServerDialog } /> + </> + ); + } + + private onSelectMediatorServer = (event: React.MouseEvent<HTMLElement>, server: MediatorServer) =>{ + event.preventDefault(); + event.stopPropagation(); + this.props.selectMediatorServer(server && server._id); + + } + + private onEditMediatorServer = (event: React.MouseEvent<HTMLElement>, server: MediatorServer) => { + event.preventDefault(); + event.stopPropagation(); + this.setState({ + mediatorServerEditorMode: EditMediatorServerDialogMode.EditMediatorServer, + mediatorServerToEdit: server, + }); + } + + private onRemoveMediatorServer = (event: React.MouseEvent<HTMLElement>, server: MediatorServer) => { + event.preventDefault(); + event.stopPropagation(); + this.setState({ + mediatorServerEditorMode: EditMediatorServerDialogMode.RemoveMediatorServer, + mediatorServerToEdit: server, + }); + } + + private onCloseEditMediatorServerDialog = () => { + this.setState({ + mediatorServerEditorMode: EditMediatorServerDialogMode.None, + mediatorServerToEdit: emptyMediatorServer, + }); + } +} + + +export const MediatorServerSelection = withStyles(styles)(connect(mapProps, mapDispatch)(MediatorServerSelectionComponent)); +export default MediatorServerSelection;
\ No newline at end of file diff --git a/sdnr/wt/odlux/apps/mediatorApp/src2/main/java/org/onap/ccsdk/features/sdnr/wt/odlux/bundles/MyOdluxBundle.java b/sdnr/wt/odlux/apps/mediatorApp/src2/main/java/org/onap/ccsdk/features/sdnr/wt/odlux/bundles/MyOdluxBundle.java new file mode 100644 index 000000000..1e882fc69 --- /dev/null +++ b/sdnr/wt/odlux/apps/mediatorApp/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/mediatorApp/src2/main/resources/OSGI-INF/blueprint/blueprint.xml b/sdnr/wt/odlux/apps/mediatorApp/src2/main/resources/OSGI-INF/blueprint/blueprint.xml new file mode 100644 index 000000000..c11492bc4 --- /dev/null +++ b/sdnr/wt/odlux/apps/mediatorApp/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="mediatorApp"/> + <property name="index" value="90"/> + </bean> +</blueprint>
\ No newline at end of file diff --git a/sdnr/wt/odlux/apps/mediatorApp/tsconfig.json b/sdnr/wt/odlux/apps/mediatorApp/tsconfig.json new file mode 100644 index 000000000..a66b5d828 --- /dev/null +++ b/sdnr/wt/odlux/apps/mediatorApp/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/mediatorApp/tslint.json b/sdnr/wt/odlux/apps/mediatorApp/tslint.json new file mode 100644 index 000000000..b5143e92c --- /dev/null +++ b/sdnr/wt/odlux/apps/mediatorApp/tslint.json @@ -0,0 +1,4 @@ +{ + "rules":{ + } +}
\ No newline at end of file diff --git a/sdnr/wt/odlux/apps/mediatorApp/webpack.config.js b/sdnr/wt/odlux/apps/mediatorApp/webpack.config.js new file mode 100644 index 000000000..151170b46 --- /dev/null +++ b/sdnr/wt/odlux/apps/mediatorApp/webpack.config.js @@ -0,0 +1,144 @@ +/** + * 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: { + mediatorApp: ["./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: { + "/database/": { + target: "http://localhost:8181", + secure: false + }, + "/restconf/": { + target: "http://localhost:8181", + secure: false + }, + "/help/": { + target: "http://localhost:8181", + secure: false + } + } + } + }]; +} diff --git a/sdnr/wt/odlux/apps/minimumApp/.babelrc b/sdnr/wt/odlux/apps/minimumApp/.babelrc new file mode 100644 index 000000000..3d8cd1260 --- /dev/null +++ b/sdnr/wt/odlux/apps/minimumApp/.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/minimumApp/package.json b/sdnr/wt/odlux/apps/minimumApp/package.json new file mode 100644 index 000000000..0df2cd71e --- /dev/null +++ b/sdnr/wt/odlux/apps/minimumApp/package.json @@ -0,0 +1,40 @@ +{ + "name": "@odlux/minimum-app", + "version": "0.1.0", + "description": "A react based modular UI to demo the minimum possible 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": "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/minimumApp/pom.xml b/sdnr/wt/odlux/apps/minimumApp/pom.xml new file mode 100644 index 000000000..e65d842b6 --- /dev/null +++ b/sdnr/wt/odlux/apps/minimumApp/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-minimumApp</artifactId> + <version>0.4.1-SNAPSHOT</version> + <packaging>bundle</packaging> + <name>sdnr-wt-odlux-app-minimumApp</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/minimumApp/src/handlers/minimumAppRootHandler.ts b/sdnr/wt/odlux/apps/minimumApp/src/handlers/minimumAppRootHandler.ts new file mode 100644 index 000000000..d1d030488 --- /dev/null +++ b/sdnr/wt/odlux/apps/minimumApp/src/handlers/minimumAppRootHandler.ts @@ -0,0 +1,20 @@ +// main state handler + +import { combineActionHandler } from '../../../../framework/src/flux/middleware'; + +import { IApplicationStoreState } from '../../../../framework/src/store/applicationStore'; + +export interface IMinimumAppStoreState { +} + +declare module '../../../../framework/src/store/applicationStore' { + interface IApplicationStoreState { + minimumApp: IMinimumAppStoreState + } +} + +const actionHandlers = { +}; + +export const minimumAppRootHandler = combineActionHandler<IMinimumAppStoreState>(actionHandlers); +export default minimumAppRootHandler; diff --git a/sdnr/wt/odlux/apps/minimumApp/src/index.html b/sdnr/wt/odlux/apps/minimumApp/src/index.html new file mode 100644 index 000000000..58865edc1 --- /dev/null +++ b/sdnr/wt/odlux/apps/minimumApp/src/index.html @@ -0,0 +1,24 @@ +<!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", "minimumApp"], function (app) { + app("./app.tsx") + }); + </script> +</body> + +</html>
\ No newline at end of file diff --git a/sdnr/wt/odlux/apps/minimumApp/src/plugin.tsx b/sdnr/wt/odlux/apps/minimumApp/src/plugin.tsx new file mode 100644 index 000000000..dc698e11b --- /dev/null +++ b/sdnr/wt/odlux/apps/minimumApp/src/plugin.tsx @@ -0,0 +1,31 @@ +// app configuration and main entry point for the app + +import * as React from "react"; +import { withRouter, RouteComponentProps, Route, Switch, Redirect } from 'react-router-dom'; + +import { faLock } from '@fortawesome/free-solid-svg-icons'; // select app icon + +import applicationManager from '../../../framework/src/services/applicationManager'; +import connect, { Connect } from '../../../framework/src/flux/connect'; + +import { minimumAppRootHandler } from './handlers/minimumAppRootHandler'; + +type AppProps = RouteComponentProps & Connect; + +const App = (props: AppProps) => ( + <div>Start your app here!!</div> +); + +const FinalApp = withRouter(connect()(App)); + +export function register() { + applicationManager.registerApplication({ + name: "minimumApp", + icon: faLock, + rootComponent: FinalApp, + rootActionHandler: minimumAppRootHandler, + menuEntry: "Minimum App" + }); +} + + diff --git a/sdnr/wt/odlux/apps/minimumApp/src2/main/java/org/onap/ccsdk/features/sdnr/wt/odlux/bundles/MyOdluxBundle.java b/sdnr/wt/odlux/apps/minimumApp/src2/main/java/org/onap/ccsdk/features/sdnr/wt/odlux/bundles/MyOdluxBundle.java new file mode 100644 index 000000000..1e882fc69 --- /dev/null +++ b/sdnr/wt/odlux/apps/minimumApp/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/minimumApp/src2/main/resources/OSGI-INF/blueprint/blueprint.xml b/sdnr/wt/odlux/apps/minimumApp/src2/main/resources/OSGI-INF/blueprint/blueprint.xml new file mode 100644 index 000000000..4c8ed1343 --- /dev/null +++ b/sdnr/wt/odlux/apps/minimumApp/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="minimumApp"/> + <property name="index" value="999" /> + </bean> +</blueprint>
\ No newline at end of file diff --git a/sdnr/wt/odlux/apps/minimumApp/tsconfig.json b/sdnr/wt/odlux/apps/minimumApp/tsconfig.json new file mode 100644 index 000000000..a66b5d828 --- /dev/null +++ b/sdnr/wt/odlux/apps/minimumApp/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/minimumApp/webpack.config.js b/sdnr/wt/odlux/apps/minimumApp/webpack.config.js new file mode 100644 index 000000000..64a534464 --- /dev/null +++ b/sdnr/wt/odlux/apps/minimumApp/webpack.config.js @@ -0,0 +1,136 @@ +/** + * 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: { + minimumApp: ["./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: { + "/api": { + target: "http://localhost:3001", + secure: false + } + } + } + }]; +} |