Remove device-mgt react UI app from device-mgt-core

This commit is contained in:
tcdlpds@gmail.com 2020-04-10 01:47:47 +05:30
parent 6e0f386664
commit 92bf262a43
119 changed files with 0 additions and 22748 deletions

View File

@ -1,132 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ Copyright (c) 2019, Entgra (pvt) Ltd. (http://entgra.io) All Rights Reserved.
~
~ Entgra (pvt) Ltd. licenses this file to you 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.
-->
<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>
<parent>
<groupId>org.wso2.carbon.devicemgt</groupId>
<artifactId>device-mgt</artifactId>
<version>4.1.7-SNAPSHOT</version>
</parent>
<artifactId>io.entgra.device.mgt.ui</artifactId>
<version>4.1.7-SNAPSHOT</version>
<packaging>war</packaging>
<name>Entgra - Device Management UI Component</name>
<url>https://entgra.io</url>
<description>This Component contains Device Management UI</description>
<dependencies>
</dependencies>
<build>
<plugins>
<plugin>
<artifactId>maven-war-plugin</artifactId>
<configuration>
<packagingExcludes>WEB-INF/lib/*cxf*.jar</packagingExcludes>
<warName>entgra</warName>
<webResources>
<resource>
<directory>${npm.output.directory}/dist</directory>
</resource>
<resource>
<directory>${npm.output.directory}/public</directory>
<targetPath>public</targetPath>
</resource>
</webResources>
</configuration>
</plugin>
<plugin>
<groupId>com.github.eirslett</groupId>
<artifactId>frontend-maven-plugin</artifactId>
<version>${frontend.mave.version}</version>
<configuration>
<workingDirectory>${npm.working.dir}</workingDirectory>
<!-- where to install npm -->
<installDirectory>${npm.install.dir}</installDirectory>
</configuration>
<executions>
<execution>
<id>install node and npm</id>
<goals>
<goal>install-node-and-npm</goal>
</goals>
<phase>generate-resources</phase>
<configuration>
<nodeVersion>${node.version}</nodeVersion>
<npmVersion>${npm.version}</npmVersion>
</configuration>
</execution>
<execution>
<id>npm install</id>
<goals>
<goal>npm</goal>
</goals>
<!-- Optional configuration which provides for running any npm command -->
<configuration>
<arguments>install</arguments>
</configuration>
</execution>
<execution>
<id>lint</id>
<goals>
<goal>npm</goal>
</goals>
<configuration>
<arguments>run-script lint</arguments>
</configuration>
<phase>generate-resources</phase>
</execution>
<execution>
<id>prod</id>
<goals>
<goal>npm</goal>
</goals>
<configuration>
<arguments>run-script ${npm.build.command}</arguments>
</configuration>
<phase>generate-resources</phase>
</execution>
</executions>
</plugin>
</plugins>
</build>
<profiles>
<profile>
<id>platform-windows</id>
<activation>
<os>
<family>windows</family>
</os>
</activation>
<properties>
<!-- Override the executable names for Windows -->
<npm.executable>npm.cmd</npm.executable>
</properties>
</profile>
</profiles>
<properties>
<maven.test.skip>false</maven.test.skip>
<npm.executable>npm</npm.executable>
<npm.build.command>build_prod</npm.build.command>
<npm.working.dir>./react-app</npm.working.dir>
<npm.install.dir>./react-app/tmp</npm.install.dir>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<npm.output.directory>react-app</npm.output.directory>
</properties>
</project>

View File

@ -1,325 +0,0 @@
{
"parser": "babel-eslint",
"plugins": [
"react",
"babel",
"jsx",
"prettier"
],
"extends": [
"eslint:recommended",
"plugin:react/recommended"
],
"parserOptions": {
"ecmaVersion": 2016,
"sourceType": "module",
"ecmaFeatures": {
"jsx": true
}
},
"settings": {
"react": {
"createClass": "createReactClass",
"pragma": "React",
"version": "16.8.6"
}
},
"env": {
"node": true,
"commonjs": true,
"browser": true,
"jasmine": true,
"es6": true
},
"globals": {
"document": true,
"console": true,
// Only for development purposes
"setTimeout": true,
"window" : true
},
"rules": {
"prettier/prettier": "error",
// Enforce the spacing around the * in generator functions.
"generator-star-spacing": [2, "after"],
// Disallow using variables outside the blocks they are defined (especially
// since only let and const are used, see "no-var").
"block-scoped-var": 2,
// Require camel case names
"camelcase": 2,
// Allow trailing commas for easy list extension. Having them does not
// impair readability, but also not required either.
"comma-dangle": 0,
// Warn about cyclomatic complexity in functions.
"complexity": 1,
// Don't warn for inconsistent naming when capturing this (not so important
// with auto-binding fat arrow functions).
"consistent-this": 0,
// Enforce curly brace conventions for all control statements.
"curly": 2,
// Don't require a default case in switch statements. Avoid being forced to
// add a bogus default when you know all possible cases are handled.
"default-case": 0,
// Encourage the use of dot notation whenever possible.
"dot-notation": 2,
// Allow mixed 'LF' and 'CRLF' as linebreaks.
"linebreak-style": 0,
// Don't enforce the maximum depth that blocks can be nested.
"max-depth": 0,
// Maximum length of a line.
"max-len": [2, 120, 2, { "ignoreStrings": true, "ignoreUrls": true}],
// Maximum depth callbacks can be nested.
"max-nested-callbacks": [2, 3],
// Don't limit the number of parameters that can be used in a function.
"max-params": 0,
// Don't limit the maximum number of statement allowed in a function.
"max-statements": 0,
// Require a capital letter for constructors, only check if all new
// operators are followed by a capital letter. Don't warn when capitalized
// functions are used without the new operator.
"new-cap": [2, {"capIsNew": false}],
// Disallow use of the Array constructor.
"no-array-constructor": 2,
// Allow use of bitwise operators.
"no-bitwise": 0,
// Disallow use of arguments.caller or arguments.callee.
"no-caller": 2,
// Disallow the catch clause parameter name being the same as a variable in
// the outer scope, to avoid confusion.
"no-catch-shadow": 2,
// Disallow assignment in conditional expressions.
"no-cond-assign": 2,
// Allow using the console API.
"no-console": 0,
// Allow using constant expressions in conditions like while (true)
"no-constant-condition": 0,
// Allow use of the continue statement.
"no-continue": 0,
// Disallow control characters in regular expressions.
"no-control-regex": 2,
// Disallow deletion of variables (deleting properties is fine).
"no-delete-var": 2,
// Disallow duplicate arguments in functions.
"no-dupe-args": 2,
// Disallow duplicate keys when creating object literals.
"no-dupe-keys": 2,
// Disallow multiple empty lines
"no-multiple-empty-lines": "error",
// Disallow a duplicate case label.
"no-duplicate-case": 2,
// Disallow else after a return in an if. The else around the second return
// here is useless:
// if (something) { return false; } else { return true; }
"no-else-return": 2,
// Disallow empty statements. This will report an error for:
// try { something(); } catch (e) {}
// but will not report it for:
// try { something(); } catch (e) { /* Silencing the error because ...*/ }
// which is a valid use case.
"no-empty": 2,
// Disallow the use of empty character classes in regular expressions.
"no-empty-character-class": 2,
// Disallow use of labels for anything other then loops and switches.
"no-labels": 2,
// Disallow use of eval(). We have other APIs to evaluate code in content.
"no-eval": 2,
// Disallow assigning to the exception in a catch block.
"no-ex-assign": 2,
// Disallow adding to native types
"no-extend-native": 2,
// Disallow unnecessary function binding.
"no-extra-bind": 2,
// Disallow double-negation boolean casts in a boolean context.
"no-extra-boolean-cast": 2,
// Allow unnecessary parentheses, as they may make the code more readable.
"no-extra-parens": 0,
// Disallow fallthrough of case statements, except if there is a comment.
"no-fallthrough": 2,
// Allow the use of leading or trailing decimal points in numeric literals.
"no-floating-decimal": 0,
// Disallow if as the only statement in an else block.
"no-lonely-if": 2,
// Disallow use of multiline strings (use template strings instead).
"no-multi-str": 2,
// Disallow reassignments of native objects.
"no-native-reassign": 2,
// Disallow nested ternary expressions, they make the code hard to read.
"no-nested-ternary": 2,
// Allow use of new operator with the require function.
"no-new-require": 0,
// Disallow use of octal literals.
"no-octal": 2,
// Allow reassignment of function parameters.
"no-param-reassign": 0,
// Allow string concatenation with __dirname and __filename (not a node env).
"no-path-concat": 0,
// Allow use of unary operators, ++ and --.
"no-plusplus": 0,
// Allow using process.env (not a node environment).
"no-process-env": 0,
// Allow using process.exit (not a node environment).
"no-process-exit": 0,
// Disallow usage of __proto__ property.
"no-proto": 2,
// Disallow declaring the same variable more than once (we use let anyway).
"no-redeclare": 2,
// Disallow multiple spaces in a regular expression literal.
"no-regex-spaces": 2,
// Allow reserved words being used as object literal keys.
"no-reserved-keys": 0,
// Don't restrict usage of specified node modules (not a node environment).
"no-restricted-modules": 0,
// Disallow use of assignment in return statement. It is preferable for a
// single line of code to have only one easily predictable effect.
"no-return-assign": 2,
// Allow use of javascript: urls.
"no-script-url": 0,
// Disallow comparisons where both sides are exactly the same.
"no-self-compare": 2,
// Disallow use of comma operator.
"no-sequences": 2,
// Warn about declaration of variables already declared in the outer scope.
// This isn't an error because it sometimes is useful to use the same name
// in a small helper function rather than having to come up with another
// random name.
// Still, making this a warning can help people avoid being confused.
"no-shadow": 0,
// Require empty line at end of file
"eol-last": "error",
// Disallow shadowing of names such as arguments.
"no-shadow-restricted-names": 2,
"no-space-before-semi": 0,
// Disallow sparse arrays, eg. let arr = [,,2].
// Array destructuring is fine though:
// for (let [, breakpointPromise] of aPromises)
"no-sparse-arrays": 2,
// Allow use of synchronous methods (not a node environment).
"no-sync": 0,
// Allow the use of ternary operators.
"no-ternary": 0,
// Don't allow spaces after end of line
"no-trailing-spaces": "error",
// Disallow throwing literals (eg. throw "error" instead of
// throw new Error("error")).
"no-throw-literal": 2,
// Disallow use of undeclared variables unless mentioned in a /*global */
// block. Note that globals from head.js are automatically imported in tests
// by the import-headjs-globals rule form the mozilla eslint plugin.
"no-undef": 2,
// Allow use of undefined variable.
"no-undefined": 0,
// Disallow the use of Boolean literals in conditional expressions.
"no-unneeded-ternary": 2,
// Disallow unreachable statements after a return, throw, continue, or break
// statement.
"no-unreachable": 2,
// Allow using variables before they are defined.
"no-unused-vars": [2, {"vars": "all", "args": "none"}],
// Disallow global and local variables that arent used, but allow unused function arguments.
"no-use-before-define": 0,
// We use var-only-at-top-level instead of no-var as we allow top level
// vars.
"no-var": 0,
// Allow using TODO/FIXME comments.
"no-warning-comments": 0,
// Disallow use of the with statement.
"no-with": 2,
// Dont require method and property shorthand syntax for object literals.
// We use this in the code a lot, but not consistently, and this seems more
// like something to check at code review time.
"object-shorthand": 0,
// Allow more than one variable declaration per function.
"one-var": 0,
// Single quotes should be used.
"quotes": [2, "single", "avoid-escape"],
// Require use of the second argument for parseInt().
"radix": 2,
// Dont require to sort variables within the same declaration block.
// Anyway, one-var is disabled.
"sort-vars": 0,
"space-after-function-name": 0,
"space-before-function-parentheses": 0,
// Disallow space before function opening parenthesis.
//"space-before-function-paren": [2, "never"],
// Disable the rule that checks if spaces inside {} and [] are there or not.
// Our code is split on conventions, and itd be nice to have 2 rules
// instead, one for [] and one for {}. So, disabling until we write them.
"space-in-brackets": 0,
// Deprecated, will be removed in 1.0.
"space-unary-word-ops": 0,
// Require a space immediately following the // in a line comment.
"spaced-comment": [2, "always"],
// Require "use strict" to be defined globally in the script.
"strict": [2, "global"],
// Disallow comparisons with the value NaN.
"use-isnan": 2,
// Warn about invalid JSDoc comments.
// Disabled for now because of https://github.com/eslint/eslint/issues/2270
// The rule fails on some jsdoc comments like in:
// devtools/client/webconsole/console-output.js
"valid-jsdoc": 0,
// Ensure that the results of typeof are compared against a valid string.
"valid-typeof": 2,
// Allow vars to be declared anywhere in the scope.
"vars-on-top": 0,
// Dont require immediate function invocation to be wrapped in parentheses.
"wrap-iife": 0,
// Don't require regex literals to be wrapped in parentheses (which
// supposedly prevent them from being mistaken for division operators).
"wrap-regex": 0,
// Require for-in loops to have an if statement.
"guard-for-in": 0,
// allow/disallow an empty newline after var statement
"newline-after-var": 0,
// disallow the use of alert, confirm, and prompt
"no-alert": 0,
// disallow the use of deprecated react changes and lifecycle methods
"react/no-deprecated": 0,
// disallow comparisons to null without a type-checking operator
"no-eq-null": 0,
// disallow overwriting functions written as function declarations
"no-func-assign": 0,
// disallow use of eval()-like methods
"no-implied-eval": 0,
// disallow function or variable declarations in nested blocks
"no-inner-declarations": 0,
// disallow invalid regular expression strings in the RegExp constructor
"no-invalid-regexp": 0,
// disallow irregular whitespace outside of strings and comments
"no-irregular-whitespace": 0,
// disallow unnecessary nested blocks
"no-lone-blocks": 0,
// disallow creation of functions within loops
"no-loop-func": 0,
// disallow use of new operator when not part of the assignment or
// comparison
"no-new": 0,
// disallow use of new operator for Function object
"no-new-func": 0,
// disallow use of the Object constructor
"no-new-object": 0,
// disallows creating new instances of String,Number, and Boolean
"no-new-wrappers": 0,
// disallow the use of object properties of the global object (Math and
// JSON) as functions
"no-obj-calls": 0,
// disallow use of octal escape sequences in string literals, such as
// var foo = "Copyright \251";
"no-octal-escape": 0,
// disallow use of undefined when initializing variables
"no-undef-init": 0,
// disallow usage of expressions in statement position
"no-unused-expressions": 0,
// disallow use of void operator
"no-void": 0,
// disallow wrapping of non-IIFE statements in parens
"no-wrap-func": 0,
// require assignment operator shorthand where possible or prohibit it
// entirely
"operator-assignment": 0,
// enforce operators to be placed before or after line breaks
"operator-linebreak": 0,
// disable chacking prop types
"react/prop-types": 0
}
}

View File

@ -1,5 +0,0 @@
{
"singleQuote": true,
"trailingComma": "all",
"parser": "flow"
}

View File

@ -1,28 +0,0 @@
/*
* Copyright (c) 2020, Entgra (pvt) Ltd. (http://entgra.io) All Rights Reserved.
*
* Entgra (pvt) Ltd. licenses this file to you 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.
*/
module.exports = function(api) {
api.cache(true);
const presets = ['@babel/preset-env', '@babel/preset-react'];
const plugins = ['@babel/plugin-proposal-class-properties'];
return {
presets,
plugins,
};
};

View File

@ -1,101 +0,0 @@
{
"name": "entgra",
"version": "1.0.0",
"description": "Entgra device management",
"main": "App.js",
"license": "Apache License 2.0",
"dependencies": {
"acorn": "^6.2.0",
"antd": "^3.23.5",
"axios": "^0.18.1",
"babel-eslint": "^9.0.0",
"bizcharts": "^3.5.6",
"bootstrap": "^4.3.1",
"javascript-time-ago": "^2.0.1",
"keymirror": "^0.1.1",
"leaflet": "^1.3.4",
"leaflet-routing-machine": "^3.2.12",
"lodash.debounce": "^4.0.8",
"moment": "latest",
"prop-types": "latest",
"rc-viewer": "0.0.9",
"react-bootstrap": "^1.0.0-beta.12",
"react-highlight-words": "^0.16.0",
"react-icons": "^3.8.0",
"react-image-viewer-zoom": "^1.0.36",
"react-infinite-scroller": "^1.2.4",
"react-leaflet": "^2.4.0",
"react-moment": "^0.9.7",
"react-router": "^5.0.1",
"react-router-config": "^5.0.1",
"react-router-dom": "^5.0.1",
"react-scripts": "2.1.8",
"react-star-ratings": "^2.3.0",
"react-twemoji": "^0.2.3",
"react-virtualized": "^9.21.1",
"react-websocket": "^2.1.0",
"reqwest": "^2.0.5",
"storm-react-diagrams": "^5.2.1"
},
"devDependencies": {
"@antv/data-set": "^0.10.2",
"@babel/core": "^7.5.4",
"@babel/plugin-proposal-class-properties": "^7.5.0",
"@babel/preset-env": "^7.5.4",
"@babel/preset-react": "^7.0.0",
"@babel/register": "^7.4.4",
"babel-loader": "^8.0.6",
"body-parser": "^1.19.0",
"chai": "^4.1.2",
"cross-env": "^7.0.0",
"css-loader": "^0.28.11",
"eslint": "^5.16.0",
"eslint-config-prettier": "4.3.0",
"eslint-plugin-babel": "5.3.0",
"eslint-plugin-jsx": "0.0.2",
"eslint-plugin-prettier": "3.1.0",
"eslint-plugin-react": "7.14.2",
"express": "^4.17.1",
"express-pino-logger": "^4.0.0",
"file-loader": "^2.0.0",
"html-loader": "^0.5.5",
"html-webpack-plugin": "^3.2.0",
"img-loader": "^3.0.1",
"json-loader": "^0.5.7",
"less": "^3.9.0",
"less-loader": "^4.1.0",
"mini-css-extract-plugin": "^0.5.0",
"mocha": "^5.2.0",
"mock-local-storage": "^1.0.5",
"node-env-run": "^3.0.2",
"node-sass": "^4.12.0",
"nodemon": "^1.19.1",
"npm-run-all": "^4.1.5",
"pino-colada": "^1.4.5",
"postcss-loader": "^3.0.0",
"prettier": "1.18.1",
"qrcode.react": "^1.0.0",
"react": "^16.8.6",
"react-dom": "^16.8.6",
"react-intl": "^2.9.0",
"sass-loader": "^6.0.7",
"style-loader": "^0.18.2",
"url-loader": "^1.1.2",
"webpack": "^4.35.3",
"webpack-cli": "^3.3.5",
"webpack-dev-server": "^3.7.2"
},
"scripts": {
"start": "webpack-dev-server --mode development --open",
"dev": "webpack --mode development",
"build": "webpack --mode production",
"watch": "webpack --watch --mode development",
"test": "react-scripts test --env=jsdom",
"eject": "react-scripts eject",
"build_prod": "cross-env NODE_ENV=production NODE_OPTIONS=--max_old_space_size=4096 webpack -p --display errors-only --hide-modules",
"build_dev": "cross-env NODE_ENV=development webpack -d --watch ",
"server": "node-env-run server --exec nodemon | pino-colada",
"dev2": "run-p server start",
"lint": "eslint \"src/**/*.js\""
}
}

View File

@ -1,43 +0,0 @@
{
"theme": {
"logo": "https://entgra.io/assets/images/svg/logo.svg",
"primaryColor": "rgb(24, 144, 255)"
},
"serverConfig": {
"invoker": {
"uri": "/entgra-ui-request-handler/invoke",
"deviceMgt": "/device-mgt/v1.0"
},
"loginUri": "/entgra-ui-request-handler/login",
"logoutUri": "/entgra-ui-request-handler/logout",
"platform": "entgra"
},
"defaultPlatformIcons": {
"default": {
"icon": "hdd",
"color": "#535c68",
"theme": "outlined"
},
"android": {
"icon": "android",
"color": "#7db343",
"theme": "filled"
},
"ios": {
"icon": "apple",
"color": "#535c68",
"theme": "filled"
},
"windows": {
"icon": "windows",
"color": "#008cc4",
"theme": "filled"
}
},
"geoMap": {
"url": "https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png",
"attribution": "&copy; <a href=&quot;http://osm.org/copyright&quot;>OpenStreetMap</a> contributors",
"defaultZoomLevel": 16,
"timeout": 120
}
}

File diff suppressed because one or more lines are too long

View File

@ -1,15 +0,0 @@
{
"short_name": "App Store",
"name": "WSO2 IoT App Store",
"icons": [
{
"src": "images/favicon.png",
"sizes": "16x16",
"type": "image/png"
}
],
"start_url": "./index.html",
"display": "standalone",
"theme_color": "#000000",
"background_color": "#ffffff"
}

File diff suppressed because one or more lines are too long

View File

@ -1,636 +0,0 @@
/* required styles */
.leaflet-pane,
.leaflet-tile,
.leaflet-marker-icon,
.leaflet-marker-shadow,
.leaflet-tile-container,
.leaflet-pane > svg,
.leaflet-pane > canvas,
.leaflet-zoom-box,
.leaflet-image-layer,
.leaflet-layer {
position: absolute;
left: 0;
top: 0;
}
.leaflet-container {
overflow: hidden;
}
.leaflet-tile,
.leaflet-marker-icon,
.leaflet-marker-shadow {
-webkit-user-select: none;
-moz-user-select: none;
user-select: none;
-webkit-user-drag: none;
}
/* Safari renders non-retina tile on retina better with this, but Chrome is worse */
.leaflet-safari .leaflet-tile {
image-rendering: -webkit-optimize-contrast;
}
/* hack that prevents hw layers "stretching" when loading new tiles */
.leaflet-safari .leaflet-tile-container {
width: 1600px;
height: 1600px;
-webkit-transform-origin: 0 0;
}
.leaflet-marker-icon,
.leaflet-marker-shadow {
display: block;
}
/* .leaflet-container svg: reset svg max-width decleration shipped in Joomla! (joomla.org) 3.x */
/* .leaflet-container img: map is broken in FF if you have max-width: 100% on tiles */
.leaflet-container .leaflet-overlay-pane svg,
.leaflet-container .leaflet-marker-pane img,
.leaflet-container .leaflet-shadow-pane img,
.leaflet-container .leaflet-tile-pane img,
.leaflet-container img.leaflet-image-layer {
max-width: none !important;
max-height: none !important;
}
.leaflet-container.leaflet-touch-zoom {
-ms-touch-action: pan-x pan-y;
touch-action: pan-x pan-y;
}
.leaflet-container.leaflet-touch-drag {
-ms-touch-action: pinch-zoom;
/* Fallback for FF which doesn't support pinch-zoom */
touch-action: none;
touch-action: pinch-zoom;
}
.leaflet-container.leaflet-touch-drag.leaflet-touch-zoom {
-ms-touch-action: none;
touch-action: none;
}
.leaflet-container {
-webkit-tap-highlight-color: transparent;
}
.leaflet-container a {
-webkit-tap-highlight-color: rgba(51, 181, 229, 0.4);
}
.leaflet-tile {
filter: inherit;
visibility: hidden;
}
.leaflet-tile-loaded {
visibility: inherit;
}
.leaflet-zoom-box {
width: 0;
height: 0;
-moz-box-sizing: border-box;
box-sizing: border-box;
z-index: 800;
}
/* workaround for https://bugzilla.mozilla.org/show_bug.cgi?id=888319 */
.leaflet-overlay-pane svg {
-moz-user-select: none;
}
.leaflet-pane { z-index: 400; }
.leaflet-tile-pane { z-index: 200; }
.leaflet-overlay-pane { z-index: 400; }
.leaflet-shadow-pane { z-index: 500; }
.leaflet-marker-pane { z-index: 600; }
.leaflet-tooltip-pane { z-index: 650; }
.leaflet-popup-pane { z-index: 700; }
.leaflet-map-pane canvas { z-index: 100; }
.leaflet-map-pane svg { z-index: 200; }
.leaflet-vml-shape {
width: 1px;
height: 1px;
}
.lvml {
behavior: url(#default#VML);
display: inline-block;
position: absolute;
}
/* control positioning */
.leaflet-control {
position: relative;
z-index: 800;
pointer-events: visiblePainted; /* IE 9-10 doesn't have auto */
pointer-events: auto;
}
.leaflet-top,
.leaflet-bottom {
position: absolute;
z-index: 1000;
pointer-events: none;
}
.leaflet-top {
top: 0;
}
.leaflet-right {
right: 0;
}
.leaflet-bottom {
bottom: 0;
}
.leaflet-left {
left: 0;
}
.leaflet-control {
float: left;
clear: both;
}
.leaflet-right .leaflet-control {
float: right;
}
.leaflet-top .leaflet-control {
margin-top: 10px;
}
.leaflet-bottom .leaflet-control {
margin-bottom: 10px;
}
.leaflet-left .leaflet-control {
margin-left: 10px;
}
.leaflet-right .leaflet-control {
margin-right: 10px;
}
/* zoom and fade animations */
.leaflet-fade-anim .leaflet-tile {
will-change: opacity;
}
.leaflet-fade-anim .leaflet-popup {
opacity: 0;
-webkit-transition: opacity 0.2s linear;
-moz-transition: opacity 0.2s linear;
-o-transition: opacity 0.2s linear;
transition: opacity 0.2s linear;
}
.leaflet-fade-anim .leaflet-map-pane .leaflet-popup {
opacity: 1;
}
.leaflet-zoom-animated {
-webkit-transform-origin: 0 0;
-ms-transform-origin: 0 0;
transform-origin: 0 0;
}
.leaflet-zoom-anim .leaflet-zoom-animated {
will-change: transform;
}
.leaflet-zoom-anim .leaflet-zoom-animated {
-webkit-transition: -webkit-transform 0.25s cubic-bezier(0,0,0.25,1);
-moz-transition: -moz-transform 0.25s cubic-bezier(0,0,0.25,1);
-o-transition: -o-transform 0.25s cubic-bezier(0,0,0.25,1);
transition: transform 0.25s cubic-bezier(0,0,0.25,1);
}
.leaflet-zoom-anim .leaflet-tile,
.leaflet-pan-anim .leaflet-tile {
-webkit-transition: none;
-moz-transition: none;
-o-transition: none;
transition: none;
}
.leaflet-zoom-anim .leaflet-zoom-hide {
visibility: hidden;
}
/* cursors */
.leaflet-interactive {
cursor: pointer;
}
.leaflet-grab {
cursor: -webkit-grab;
cursor: -moz-grab;
}
.leaflet-crosshair,
.leaflet-crosshair .leaflet-interactive {
cursor: crosshair;
}
.leaflet-popup-pane,
.leaflet-control {
cursor: auto;
}
.leaflet-dragging .leaflet-grab,
.leaflet-dragging .leaflet-grab .leaflet-interactive,
.leaflet-dragging .leaflet-marker-draggable {
cursor: move;
cursor: -webkit-grabbing;
cursor: -moz-grabbing;
}
/* marker & overlays interactivity */
.leaflet-marker-icon,
.leaflet-marker-shadow,
.leaflet-image-layer,
.leaflet-pane > svg path,
.leaflet-tile-container {
pointer-events: none;
}
.leaflet-marker-icon.leaflet-interactive,
.leaflet-image-layer.leaflet-interactive,
.leaflet-pane > svg path.leaflet-interactive {
pointer-events: visiblePainted; /* IE 9-10 doesn't have auto */
pointer-events: auto;
}
/* visual tweaks */
.leaflet-container {
background: #ddd;
outline: 0;
}
.leaflet-container a {
color: #0078A8;
}
.leaflet-container a.leaflet-active {
outline: 2px solid orange;
}
.leaflet-zoom-box {
border: 2px dotted #38f;
background: rgba(255,255,255,0.5);
}
/* general typography */
.leaflet-container {
font: 12px/1.5 "Helvetica Neue", Arial, Helvetica, sans-serif;
}
/* general toolbar styles */
.leaflet-bar {
box-shadow: 0 1px 5px rgba(0,0,0,0.65);
border-radius: 4px;
}
.leaflet-bar a,
.leaflet-bar a:hover {
background-color: #fff;
border-bottom: 1px solid #ccc;
width: 26px;
height: 26px;
line-height: 26px;
display: block;
text-align: center;
text-decoration: none;
color: black;
}
.leaflet-bar a,
.leaflet-control-layers-toggle {
background-position: 50% 50%;
background-repeat: no-repeat;
display: block;
}
.leaflet-bar a:hover {
background-color: #f4f4f4;
}
.leaflet-bar a:first-child {
border-top-left-radius: 4px;
border-top-right-radius: 4px;
}
.leaflet-bar a:last-child {
border-bottom-left-radius: 4px;
border-bottom-right-radius: 4px;
border-bottom: none;
}
.leaflet-bar a.leaflet-disabled {
cursor: default;
background-color: #f4f4f4;
color: #bbb;
}
.leaflet-touch .leaflet-bar a {
width: 30px;
height: 30px;
line-height: 30px;
}
.leaflet-touch .leaflet-bar a:first-child {
border-top-left-radius: 2px;
border-top-right-radius: 2px;
}
.leaflet-touch .leaflet-bar a:last-child {
border-bottom-left-radius: 2px;
border-bottom-right-radius: 2px;
}
/* zoom control */
.leaflet-control-zoom-in,
.leaflet-control-zoom-out {
font: bold 18px 'Lucida Console', Monaco, monospace;
text-indent: 1px;
}
.leaflet-touch .leaflet-control-zoom-in, .leaflet-touch .leaflet-control-zoom-out {
font-size: 22px;
}
/* layers control */
.leaflet-control-layers {
box-shadow: 0 1px 5px rgba(0,0,0,0.4);
background: #fff;
border-radius: 5px;
}
.leaflet-control-layers-toggle {
background-image: url(images/layers.png);
width: 36px;
height: 36px;
}
.leaflet-retina .leaflet-control-layers-toggle {
background-image: url(images/layers-2x.png);
background-size: 26px 26px;
}
.leaflet-touch .leaflet-control-layers-toggle {
width: 44px;
height: 44px;
}
.leaflet-control-layers .leaflet-control-layers-list,
.leaflet-control-layers-expanded .leaflet-control-layers-toggle {
display: none;
}
.leaflet-control-layers-expanded .leaflet-control-layers-list {
display: block;
position: relative;
}
.leaflet-control-layers-expanded {
padding: 6px 10px 6px 6px;
color: #333;
background: #fff;
}
.leaflet-control-layers-scrollbar {
overflow-y: scroll;
overflow-x: hidden;
padding-right: 5px;
}
.leaflet-control-layers-selector {
margin-top: 2px;
position: relative;
top: 1px;
}
.leaflet-control-layers label {
display: block;
}
.leaflet-control-layers-separator {
height: 0;
border-top: 1px solid #ddd;
margin: 5px -10px 5px -6px;
}
/* Default icon URLs */
.leaflet-default-icon-path {
background-image: url(images/marker-icon.png);
}
/* attribution and scale controls */
.leaflet-container .leaflet-control-attribution {
background: #fff;
background: rgba(255, 255, 255, 0.7);
margin: 0;
}
.leaflet-control-attribution,
.leaflet-control-scale-line {
padding: 0 5px;
color: #333;
}
.leaflet-control-attribution a {
text-decoration: none;
}
.leaflet-control-attribution a:hover {
text-decoration: underline;
}
.leaflet-container .leaflet-control-attribution,
.leaflet-container .leaflet-control-scale {
font-size: 11px;
}
.leaflet-left .leaflet-control-scale {
margin-left: 5px;
}
.leaflet-bottom .leaflet-control-scale {
margin-bottom: 5px;
}
.leaflet-control-scale-line {
border: 2px solid #777;
border-top: none;
line-height: 1.1;
padding: 2px 5px 1px;
font-size: 11px;
white-space: nowrap;
overflow: hidden;
-moz-box-sizing: border-box;
box-sizing: border-box;
background: #fff;
background: rgba(255, 255, 255, 0.5);
}
.leaflet-control-scale-line:not(:first-child) {
border-top: 2px solid #777;
border-bottom: none;
margin-top: -2px;
}
.leaflet-control-scale-line:not(:first-child):not(:last-child) {
border-bottom: 2px solid #777;
}
.leaflet-touch .leaflet-control-attribution,
.leaflet-touch .leaflet-control-layers,
.leaflet-touch .leaflet-bar {
box-shadow: none;
}
.leaflet-touch .leaflet-control-layers,
.leaflet-touch .leaflet-bar {
border: 2px solid rgba(0,0,0,0.2);
background-clip: padding-box;
}
/* popup */
.leaflet-popup {
position: absolute;
text-align: center;
margin-bottom: 20px;
}
.leaflet-popup-content-wrapper {
padding: 1px;
text-align: left;
border-radius: 12px;
}
.leaflet-popup-content {
margin: 13px 19px;
line-height: 1.4;
}
.leaflet-popup-content p {
margin: 18px 0;
}
.leaflet-popup-tip-container {
width: 40px;
height: 20px;
position: absolute;
left: 50%;
margin-left: -20px;
overflow: hidden;
pointer-events: none;
}
.leaflet-popup-tip {
width: 17px;
height: 17px;
padding: 1px;
margin: -10px auto 0;
-webkit-transform: rotate(45deg);
-moz-transform: rotate(45deg);
-ms-transform: rotate(45deg);
-o-transform: rotate(45deg);
transform: rotate(45deg);
}
.leaflet-popup-content-wrapper,
.leaflet-popup-tip {
background: white;
color: #333;
box-shadow: 0 3px 14px rgba(0,0,0,0.4);
}
.leaflet-container a.leaflet-popup-close-button {
position: absolute;
top: 0;
right: 0;
padding: 4px 4px 0 0;
border: none;
text-align: center;
width: 18px;
height: 14px;
font: 16px/14px Tahoma, Verdana, sans-serif;
color: #c3c3c3;
text-decoration: none;
font-weight: bold;
background: transparent;
}
.leaflet-container a.leaflet-popup-close-button:hover {
color: #999;
}
.leaflet-popup-scrolled {
overflow: auto;
border-bottom: 1px solid #ddd;
border-top: 1px solid #ddd;
}
.leaflet-oldie .leaflet-popup-content-wrapper {
zoom: 1;
}
.leaflet-oldie .leaflet-popup-tip {
width: 24px;
margin: 0 auto;
-ms-filter: "progid:DXImageTransform.Microsoft.Matrix(M11=0.70710678, M12=0.70710678, M21=-0.70710678, M22=0.70710678)";
filter: progid:DXImageTransform.Microsoft.Matrix(M11=0.70710678, M12=0.70710678, M21=-0.70710678, M22=0.70710678);
}
.leaflet-oldie .leaflet-popup-tip-container {
margin-top: -1px;
}
.leaflet-oldie .leaflet-control-zoom,
.leaflet-oldie .leaflet-control-layers,
.leaflet-oldie .leaflet-popup-content-wrapper,
.leaflet-oldie .leaflet-popup-tip {
border: 1px solid #999;
}
/* div icon */
.leaflet-div-icon {
background: #fff;
border: 1px solid #666;
}
/* Tooltip */
/* Base styles for the element that has a tooltip */
.leaflet-tooltip {
position: absolute;
padding: 6px;
background-color: #fff;
border: 1px solid #fff;
border-radius: 3px;
color: #222;
white-space: nowrap;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
pointer-events: none;
box-shadow: 0 1px 3px rgba(0,0,0,0.4);
}
.leaflet-tooltip.leaflet-clickable {
cursor: pointer;
pointer-events: auto;
}
.leaflet-tooltip-top:before,
.leaflet-tooltip-bottom:before,
.leaflet-tooltip-left:before,
.leaflet-tooltip-right:before {
position: absolute;
pointer-events: none;
border: 6px solid transparent;
background: transparent;
content: "";
}
/* Directions */
.leaflet-tooltip-bottom {
margin-top: 6px;
}
.leaflet-tooltip-top {
margin-top: -6px;
}
.leaflet-tooltip-bottom:before,
.leaflet-tooltip-top:before {
left: 50%;
margin-left: -6px;
}
.leaflet-tooltip-top:before {
bottom: 0;
margin-bottom: -12px;
border-top-color: #fff;
}
.leaflet-tooltip-bottom:before {
top: 0;
margin-top: -12px;
margin-left: -6px;
border-bottom-color: #fff;
}
.leaflet-tooltip-left {
margin-left: -6px;
}
.leaflet-tooltip-right {
margin-left: 6px;
}
.leaflet-tooltip-left:before,
.leaflet-tooltip-right:before {
top: 50%;
margin-top: -6px;
}
.leaflet-tooltip-left:before {
right: 0;
margin-right: -12px;
border-left-color: #fff;
}
.leaflet-tooltip-right:before {
left: 0;
margin-left: -12px;
border-right-color: #fff;
}

File diff suppressed because it is too large Load Diff

Before

Width:  |  Height:  |  Size: 443 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.7 MiB

View File

@ -1,798 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 22.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 232 81" style="enable-background:new 0 0 232 81;" xml:space="preserve">
<style type="text/css">
.st0{fill:#FFFFFF;}
.st1{clip-path:url(#SVGID_2_);fill:#308BD6;}
.st2{clip-path:url(#SVGID_2_);fill:#318CD6;}
.st3{clip-path:url(#SVGID_2_);fill:#328DD7;}
.st4{clip-path:url(#SVGID_2_);fill:#338ED7;}
.st5{clip-path:url(#SVGID_2_);fill:#348FD8;}
.st6{clip-path:url(#SVGID_2_);fill:#3590D8;}
.st7{clip-path:url(#SVGID_2_);fill:#3691D9;}
.st8{clip-path:url(#SVGID_2_);fill:#3792D9;}
.st9{clip-path:url(#SVGID_2_);fill:#3893DA;}
.st10{clip-path:url(#SVGID_2_);fill:#3994DA;}
.st11{clip-path:url(#SVGID_2_);fill:#3A95DA;}
.st12{clip-path:url(#SVGID_2_);fill:#3B96DB;}
.st13{clip-path:url(#SVGID_2_);fill:#3C97DB;}
.st14{clip-path:url(#SVGID_2_);fill:#3D98DC;}
.st15{clip-path:url(#SVGID_2_);fill:#3E99DC;}
.st16{clip-path:url(#SVGID_2_);fill:#3F9ADD;}
.st17{clip-path:url(#SVGID_2_);fill:#409BDD;}
.st18{clip-path:url(#SVGID_2_);fill:#419CDE;}
.st19{clip-path:url(#SVGID_2_);fill:#429DDE;}
.st20{clip-path:url(#SVGID_2_);fill:#439EDE;}
.st21{clip-path:url(#SVGID_2_);fill:#449FDF;}
.st22{clip-path:url(#SVGID_2_);fill:#45A0DF;}
.st23{clip-path:url(#SVGID_2_);fill:#46A1E0;}
.st24{clip-path:url(#SVGID_2_);fill:#47A2E0;}
.st25{clip-path:url(#SVGID_2_);fill:#48A4E1;}
.st26{clip-path:url(#SVGID_2_);fill:#49A5E1;}
.st27{clip-path:url(#SVGID_2_);fill:#4AA6E2;}
.st28{clip-path:url(#SVGID_2_);fill:#4BA7E2;}
.st29{clip-path:url(#SVGID_2_);fill:#4CA8E3;}
.st30{clip-path:url(#SVGID_2_);fill:#4DA9E3;}
.st31{clip-path:url(#SVGID_2_);fill:#4EAAE3;}
.st32{clip-path:url(#SVGID_2_);fill:#4FABE4;}
.st33{clip-path:url(#SVGID_2_);fill:#50ACE4;}
.st34{clip-path:url(#SVGID_2_);fill:#51ADE5;}
.st35{clip-path:url(#SVGID_2_);fill:#52AEE5;}
.st36{clip-path:url(#SVGID_2_);fill:#53AFE6;}
.st37{clip-path:url(#SVGID_2_);fill:#54B0E6;}
.st38{clip-path:url(#SVGID_2_);fill:#55B1E7;}
.st39{clip-path:url(#SVGID_2_);fill:#56B2E7;}
.st40{clip-path:url(#SVGID_2_);fill:#57B3E7;}
.st41{clip-path:url(#SVGID_2_);fill:#58B4E8;}
.st42{clip-path:url(#SVGID_2_);fill:#59B5E8;}
.st43{clip-path:url(#SVGID_2_);fill:#5AB6E9;}
.st44{clip-path:url(#SVGID_2_);fill:#5BB7E9;}
.st45{clip-path:url(#SVGID_2_);fill:#5CB8EA;}
.st46{clip-path:url(#SVGID_2_);fill:#5DB9EA;}
.st47{clip-path:url(#SVGID_2_);fill:#5EBAEB;}
.st48{clip-path:url(#SVGID_2_);fill:#5FBBEB;}
.st49{clip-path:url(#SVGID_4_);fill:#2E5E87;}
.st50{clip-path:url(#SVGID_4_);fill:#2E5F88;}
.st51{clip-path:url(#SVGID_4_);fill:#2F5F89;}
.st52{clip-path:url(#SVGID_4_);fill:#2F608A;}
.st53{clip-path:url(#SVGID_4_);fill:#30618B;}
.st54{clip-path:url(#SVGID_4_);fill:#30628C;}
.st55{clip-path:url(#SVGID_4_);fill:#31628D;}
.st56{clip-path:url(#SVGID_4_);fill:#31638E;}
.st57{clip-path:url(#SVGID_4_);fill:#31648F;}
.st58{clip-path:url(#SVGID_4_);fill:#326590;}
.st59{clip-path:url(#SVGID_4_);fill:#326591;}
.st60{clip-path:url(#SVGID_4_);fill:#336692;}
.st61{clip-path:url(#SVGID_4_);fill:#336793;}
.st62{clip-path:url(#SVGID_4_);fill:#346894;}
.st63{clip-path:url(#SVGID_4_);fill:#346895;}
.st64{clip-path:url(#SVGID_4_);fill:#356996;}
.st65{clip-path:url(#SVGID_4_);fill:#356A97;}
.st66{clip-path:url(#SVGID_4_);fill:#356B98;}
.st67{clip-path:url(#SVGID_4_);fill:#366B99;}
.st68{clip-path:url(#SVGID_4_);fill:#366C9A;}
.st69{clip-path:url(#SVGID_4_);fill:#376D9B;}
.st70{clip-path:url(#SVGID_4_);fill:#376E9C;}
.st71{clip-path:url(#SVGID_4_);fill:#386E9D;}
.st72{clip-path:url(#SVGID_4_);fill:#386F9E;}
.st73{clip-path:url(#SVGID_4_);fill:#38709F;}
.st74{clip-path:url(#SVGID_4_);fill:#3971A0;}
.st75{clip-path:url(#SVGID_4_);fill:#3971A1;}
.st76{clip-path:url(#SVGID_4_);fill:#3A72A2;}
.st77{clip-path:url(#SVGID_4_);fill:#3A73A3;}
.st78{clip-path:url(#SVGID_4_);fill:#3B74A4;}
.st79{clip-path:url(#SVGID_4_);fill:#3B74A5;}
.st80{clip-path:url(#SVGID_4_);fill:#3C75A7;}
.st81{clip-path:url(#SVGID_4_);fill:#3C76A8;}
.st82{clip-path:url(#SVGID_4_);fill:#3C76A9;}
.st83{clip-path:url(#SVGID_4_);fill:#3D77AA;}
.st84{clip-path:url(#SVGID_4_);fill:#3D78AB;}
.st85{clip-path:url(#SVGID_4_);fill:#3E79AC;}
.st86{clip-path:url(#SVGID_4_);fill:#3E79AD;}
.st87{clip-path:url(#SVGID_4_);fill:#3F7AAE;}
.st88{clip-path:url(#SVGID_4_);fill:#3F7BAF;}
.st89{clip-path:url(#SVGID_4_);fill:#3F7CB0;}
.st90{clip-path:url(#SVGID_4_);fill:#407CB1;}
.st91{clip-path:url(#SVGID_4_);fill:#407DB2;}
.st92{clip-path:url(#SVGID_4_);fill:#417EB3;}
.st93{clip-path:url(#SVGID_4_);fill:#417FB4;}
.st94{clip-path:url(#SVGID_4_);fill:#427FB5;}
.st95{clip-path:url(#SVGID_4_);fill:#4280B6;}
.st96{clip-path:url(#SVGID_4_);fill:#4281B7;}
.st97{clip-path:url(#SVGID_4_);fill:#4382B8;}
.st98{clip-path:url(#SVGID_4_);fill:#4382B9;}
.st99{clip-path:url(#SVGID_4_);fill:#4483BA;}
.st100{clip-path:url(#SVGID_4_);fill:#4484BB;}
.st101{clip-path:url(#SVGID_4_);fill:#4585BC;}
.st102{clip-path:url(#SVGID_4_);fill:#4585BD;}
.st103{clip-path:url(#SVGID_4_);fill:#4686BE;}
.st104{clip-path:url(#SVGID_4_);fill:#4687BF;}
.st105{clip-path:url(#SVGID_4_);fill:#4688C0;}
.st106{clip-path:url(#SVGID_4_);fill:#4788C1;}
.st107{clip-path:url(#SVGID_4_);fill:#4789C2;}
.st108{clip-path:url(#SVGID_4_);fill:#488AC3;}
.st109{clip-path:url(#SVGID_4_);fill:#488BC4;}
.st110{clip-path:url(#SVGID_4_);fill:#498BC5;}
.st111{clip-path:url(#SVGID_4_);fill:#498CC6;}
.st112{clip-path:url(#SVGID_6_);fill:#4CE8C6;}
.st113{clip-path:url(#SVGID_6_);fill:#4BE8C7;}
.st114{clip-path:url(#SVGID_6_);fill:#4AE7C7;}
.st115{clip-path:url(#SVGID_6_);fill:#49E7C8;}
.st116{clip-path:url(#SVGID_6_);fill:#48E6C8;}
.st117{clip-path:url(#SVGID_6_);fill:#47E6C9;}
.st118{clip-path:url(#SVGID_6_);fill:#46E6C9;}
.st119{clip-path:url(#SVGID_6_);fill:#45E5CA;}
.st120{clip-path:url(#SVGID_6_);fill:#44E5CB;}
.st121{clip-path:url(#SVGID_6_);fill:#43E4CB;}
.st122{clip-path:url(#SVGID_6_);fill:#42E4CC;}
.st123{clip-path:url(#SVGID_6_);fill:#41E4CC;}
.st124{clip-path:url(#SVGID_6_);fill:#40E3CD;}
.st125{clip-path:url(#SVGID_6_);fill:#3FE3CE;}
.st126{clip-path:url(#SVGID_6_);fill:#3EE2CE;}
.st127{clip-path:url(#SVGID_6_);fill:#3DE2CF;}
.st128{clip-path:url(#SVGID_6_);fill:#3CE2CF;}
.st129{clip-path:url(#SVGID_6_);fill:#3BE1D0;}
.st130{clip-path:url(#SVGID_6_);fill:#3AE1D0;}
.st131{clip-path:url(#SVGID_6_);fill:#39E0D1;}
.st132{clip-path:url(#SVGID_6_);fill:#38E0D2;}
.st133{clip-path:url(#SVGID_6_);fill:#37E0D2;}
.st134{clip-path:url(#SVGID_6_);fill:#36DFD3;}
.st135{clip-path:url(#SVGID_6_);fill:#35DFD3;}
.st136{clip-path:url(#SVGID_6_);fill:#34DED4;}
.st137{clip-path:url(#SVGID_6_);fill:#33DED5;}
.st138{clip-path:url(#SVGID_6_);fill:#31DED5;}
.st139{clip-path:url(#SVGID_6_);fill:#30DDD6;}
.st140{clip-path:url(#SVGID_6_);fill:#2FDDD6;}
.st141{clip-path:url(#SVGID_6_);fill:#2EDCD7;}
.st142{clip-path:url(#SVGID_6_);fill:#2DDCD7;}
.st143{clip-path:url(#SVGID_6_);fill:#2CDCD8;}
.st144{clip-path:url(#SVGID_6_);fill:#2BDBD9;}
.st145{clip-path:url(#SVGID_6_);fill:#2ADBD9;}
.st146{clip-path:url(#SVGID_6_);fill:#29DADA;}
.st147{clip-path:url(#SVGID_6_);fill:#28DADA;}
.st148{clip-path:url(#SVGID_6_);fill:#27DADB;}
.st149{clip-path:url(#SVGID_6_);fill:#26D9DB;}
.st150{clip-path:url(#SVGID_6_);fill:#25D9DC;}
.st151{clip-path:url(#SVGID_6_);fill:#24D8DD;}
.st152{clip-path:url(#SVGID_6_);fill:#23D8DD;}
.st153{clip-path:url(#SVGID_6_);fill:#22D8DE;}
.st154{clip-path:url(#SVGID_6_);fill:#21D7DE;}
.st155{clip-path:url(#SVGID_6_);fill:#20D7DF;}
.st156{clip-path:url(#SVGID_6_);fill:#1FD6E0;}
.st157{clip-path:url(#SVGID_6_);fill:#1ED6E0;}
.st158{clip-path:url(#SVGID_6_);fill:#1DD6E1;}
.st159{clip-path:url(#SVGID_6_);fill:#1CD5E1;}
.st160{clip-path:url(#SVGID_6_);fill:#1BD5E2;}
.st161{clip-path:url(#SVGID_6_);fill:#1AD4E2;}
.st162{clip-path:url(#SVGID_6_);fill:#19D4E3;}
.st163{opacity:0.4;}
.st164{clip-path:url(#SVGID_8_);fill:#4CE8C6;}
.st165{clip-path:url(#SVGID_8_);fill:#4BE8C7;}
.st166{clip-path:url(#SVGID_8_);fill:#4AE7C7;}
.st167{clip-path:url(#SVGID_8_);fill:#49E7C8;}
.st168{clip-path:url(#SVGID_8_);fill:#48E6C8;}
.st169{clip-path:url(#SVGID_8_);fill:#47E6C9;}
.st170{clip-path:url(#SVGID_8_);fill:#46E6C9;}
.st171{clip-path:url(#SVGID_8_);fill:#45E5CA;}
.st172{clip-path:url(#SVGID_8_);fill:#44E5CB;}
.st173{clip-path:url(#SVGID_8_);fill:#43E4CB;}
.st174{clip-path:url(#SVGID_8_);fill:#42E4CC;}
.st175{clip-path:url(#SVGID_8_);fill:#41E4CC;}
.st176{clip-path:url(#SVGID_8_);fill:#40E3CD;}
.st177{clip-path:url(#SVGID_8_);fill:#3FE3CE;}
.st178{clip-path:url(#SVGID_8_);fill:#3EE2CE;}
.st179{clip-path:url(#SVGID_8_);fill:#3DE2CF;}
.st180{clip-path:url(#SVGID_8_);fill:#3CE2CF;}
.st181{clip-path:url(#SVGID_8_);fill:#3BE1D0;}
.st182{clip-path:url(#SVGID_8_);fill:#3AE1D0;}
.st183{clip-path:url(#SVGID_8_);fill:#39E0D1;}
.st184{clip-path:url(#SVGID_8_);fill:#38E0D2;}
.st185{clip-path:url(#SVGID_8_);fill:#37E0D2;}
.st186{clip-path:url(#SVGID_8_);fill:#36DFD3;}
.st187{clip-path:url(#SVGID_8_);fill:#35DFD3;}
.st188{clip-path:url(#SVGID_8_);fill:#34DED4;}
.st189{clip-path:url(#SVGID_8_);fill:#33DED5;}
.st190{clip-path:url(#SVGID_8_);fill:#31DED5;}
.st191{clip-path:url(#SVGID_8_);fill:#30DDD6;}
.st192{clip-path:url(#SVGID_8_);fill:#2FDDD6;}
.st193{clip-path:url(#SVGID_8_);fill:#2EDCD7;}
.st194{clip-path:url(#SVGID_8_);fill:#2DDCD7;}
.st195{clip-path:url(#SVGID_8_);fill:#2CDCD8;}
.st196{clip-path:url(#SVGID_8_);fill:#2BDBD9;}
.st197{clip-path:url(#SVGID_8_);fill:#2ADBD9;}
.st198{clip-path:url(#SVGID_8_);fill:#29DADA;}
.st199{clip-path:url(#SVGID_8_);fill:#28DADA;}
.st200{clip-path:url(#SVGID_8_);fill:#27DADB;}
.st201{clip-path:url(#SVGID_8_);fill:#26D9DB;}
.st202{clip-path:url(#SVGID_8_);fill:#25D9DC;}
.st203{clip-path:url(#SVGID_8_);fill:#24D8DD;}
.st204{clip-path:url(#SVGID_8_);fill:#23D8DD;}
.st205{clip-path:url(#SVGID_8_);fill:#22D8DE;}
.st206{clip-path:url(#SVGID_8_);fill:#21D7DE;}
.st207{clip-path:url(#SVGID_8_);fill:#20D7DF;}
.st208{clip-path:url(#SVGID_8_);fill:#1FD6E0;}
.st209{clip-path:url(#SVGID_8_);fill:#1ED6E0;}
.st210{clip-path:url(#SVGID_8_);fill:#1DD6E1;}
.st211{clip-path:url(#SVGID_8_);fill:#1CD5E1;}
.st212{clip-path:url(#SVGID_8_);fill:#1BD5E2;}
.st213{clip-path:url(#SVGID_8_);fill:#1AD4E2;}
.st214{clip-path:url(#SVGID_8_);fill:#19D4E3;}
.st215{opacity:0.5;}
.st216{clip-path:url(#SVGID_10_);fill:#316490;}
.st217{clip-path:url(#SVGID_10_);fill:#316591;}
.st218{clip-path:url(#SVGID_10_);fill:#326692;}
.st219{clip-path:url(#SVGID_10_);fill:#326693;}
.st220{clip-path:url(#SVGID_10_);fill:#336794;}
.st221{clip-path:url(#SVGID_10_);fill:#336895;}
.st222{clip-path:url(#SVGID_10_);fill:#346996;}
.st223{clip-path:url(#SVGID_10_);fill:#346997;}
.st224{clip-path:url(#SVGID_10_);fill:#356A98;}
.st225{clip-path:url(#SVGID_10_);fill:#356B99;}
.st226{clip-path:url(#SVGID_10_);fill:#366C9A;}
.st227{clip-path:url(#SVGID_10_);fill:#366C9B;}
.st228{clip-path:url(#SVGID_10_);fill:#366D9C;}
.st229{clip-path:url(#SVGID_10_);fill:#376E9D;}
.st230{clip-path:url(#SVGID_10_);fill:#376F9E;}
.st231{clip-path:url(#SVGID_10_);fill:#386F9F;}
.st232{clip-path:url(#SVGID_10_);fill:#3870A0;}
.st233{clip-path:url(#SVGID_10_);fill:#3971A1;}
.st234{clip-path:url(#SVGID_10_);fill:#3972A2;}
.st235{clip-path:url(#SVGID_10_);fill:#3A72A3;}
.st236{clip-path:url(#SVGID_10_);fill:#3A73A4;}
.st237{clip-path:url(#SVGID_10_);fill:#3B74A5;}
.st238{clip-path:url(#SVGID_10_);fill:#3B75A6;}
.st239{clip-path:url(#SVGID_10_);fill:#3B75A7;}
.st240{clip-path:url(#SVGID_10_);fill:#3C76A8;}
.st241{clip-path:url(#SVGID_10_);fill:#3C77A9;}
.st242{clip-path:url(#SVGID_10_);fill:#3D78AA;}
.st243{clip-path:url(#SVGID_10_);fill:#3D78AC;}
.st244{clip-path:url(#SVGID_10_);fill:#3E79AD;}
.st245{clip-path:url(#SVGID_10_);fill:#3E7AAE;}
.st246{clip-path:url(#SVGID_10_);fill:#3F7BAF;}
.st247{clip-path:url(#SVGID_10_);fill:#3F7BB0;}
.st248{clip-path:url(#SVGID_10_);fill:#3F7CB1;}
.st249{clip-path:url(#SVGID_10_);fill:#407DB2;}
.st250{clip-path:url(#SVGID_10_);fill:#407EB3;}
.st251{clip-path:url(#SVGID_10_);fill:#417EB4;}
.st252{clip-path:url(#SVGID_10_);fill:#417FB5;}
.st253{clip-path:url(#SVGID_10_);fill:#4280B6;}
.st254{clip-path:url(#SVGID_10_);fill:#4281B7;}
.st255{clip-path:url(#SVGID_10_);fill:#4381B8;}
.st256{clip-path:url(#SVGID_10_);fill:#4382B9;}
.st257{clip-path:url(#SVGID_10_);fill:#4483BA;}
.st258{clip-path:url(#SVGID_10_);fill:#4484BB;}
.st259{clip-path:url(#SVGID_10_);fill:#4484BC;}
.st260{clip-path:url(#SVGID_10_);fill:#4585BD;}
.st261{clip-path:url(#SVGID_10_);fill:#4586BE;}
.st262{clip-path:url(#SVGID_10_);fill:#4687BF;}
.st263{clip-path:url(#SVGID_10_);fill:#4687C0;}
.st264{clip-path:url(#SVGID_10_);fill:#4788C1;}
.st265{clip-path:url(#SVGID_10_);fill:#4789C2;}
.st266{clip-path:url(#SVGID_10_);fill:#488AC3;}
.st267{clip-path:url(#SVGID_10_);fill:#488AC4;}
.st268{clip-path:url(#SVGID_10_);fill:#498BC5;}
.st269{clip-path:url(#SVGID_10_);fill:#498CC6;}
.st270{clip-path:url(#SVGID_12_);fill:#5FBBEB;}
.st271{clip-path:url(#SVGID_12_);fill:#5EBAEB;}
.st272{clip-path:url(#SVGID_12_);fill:#5DB9EA;}
.st273{clip-path:url(#SVGID_12_);fill:#5CB8EA;}
.st274{clip-path:url(#SVGID_12_);fill:#5BB7E9;}
.st275{clip-path:url(#SVGID_12_);fill:#5AB6E9;}
.st276{clip-path:url(#SVGID_12_);fill:#59B5E8;}
.st277{clip-path:url(#SVGID_12_);fill:#58B4E8;}
.st278{clip-path:url(#SVGID_12_);fill:#57B3E7;}
.st279{clip-path:url(#SVGID_12_);fill:#56B2E7;}
.st280{clip-path:url(#SVGID_12_);fill:#55B1E7;}
.st281{clip-path:url(#SVGID_12_);fill:#54B0E6;}
.st282{clip-path:url(#SVGID_12_);fill:#53AFE6;}
.st283{clip-path:url(#SVGID_12_);fill:#52AEE5;}
.st284{clip-path:url(#SVGID_12_);fill:#51ADE5;}
.st285{clip-path:url(#SVGID_12_);fill:#50ACE4;}
.st286{clip-path:url(#SVGID_12_);fill:#4FABE4;}
.st287{clip-path:url(#SVGID_12_);fill:#4EAAE3;}
.st288{clip-path:url(#SVGID_12_);fill:#4DA9E3;}
.st289{clip-path:url(#SVGID_12_);fill:#4CA8E3;}
.st290{clip-path:url(#SVGID_12_);fill:#4BA7E2;}
.st291{clip-path:url(#SVGID_12_);fill:#4AA6E2;}
.st292{clip-path:url(#SVGID_12_);fill:#49A5E1;}
.st293{clip-path:url(#SVGID_12_);fill:#48A4E1;}
.st294{clip-path:url(#SVGID_12_);fill:#47A2E0;}
.st295{clip-path:url(#SVGID_12_);fill:#46A1E0;}
.st296{clip-path:url(#SVGID_12_);fill:#45A0DF;}
.st297{clip-path:url(#SVGID_12_);fill:#449FDF;}
.st298{clip-path:url(#SVGID_12_);fill:#439EDE;}
.st299{clip-path:url(#SVGID_12_);fill:#429DDE;}
.st300{clip-path:url(#SVGID_12_);fill:#419CDE;}
.st301{clip-path:url(#SVGID_12_);fill:#409BDD;}
.st302{clip-path:url(#SVGID_12_);fill:#3F9ADD;}
.st303{clip-path:url(#SVGID_12_);fill:#3E99DC;}
.st304{clip-path:url(#SVGID_12_);fill:#3D98DC;}
.st305{clip-path:url(#SVGID_12_);fill:#3C97DB;}
.st306{clip-path:url(#SVGID_12_);fill:#3B96DB;}
.st307{clip-path:url(#SVGID_12_);fill:#3A95DA;}
.st308{clip-path:url(#SVGID_12_);fill:#3994DA;}
.st309{clip-path:url(#SVGID_12_);fill:#3893DA;}
.st310{clip-path:url(#SVGID_12_);fill:#3792D9;}
.st311{clip-path:url(#SVGID_12_);fill:#3691D9;}
.st312{clip-path:url(#SVGID_12_);fill:#3590D8;}
.st313{clip-path:url(#SVGID_12_);fill:#348FD8;}
.st314{clip-path:url(#SVGID_12_);fill:#338ED7;}
.st315{clip-path:url(#SVGID_12_);fill:#328DD7;}
.st316{clip-path:url(#SVGID_12_);fill:#318CD6;}
.st317{clip-path:url(#SVGID_12_);fill:#308BD6;}
.st318{fill:#316490;}
</style>
<path class="st0" d="M224,81H8c-4.4,0-8-3.6-8-8V8c0-4.4,3.6-8,8-8h216c4.4,0,8,3.6,8,8v65C232,77.4,228.4,81,224,81z"/>
<g>
<g>
<g>
<g>
<g>
<defs>
<polygon id="SVGID_1_" points="15.2,15 15.2,62.9 59.2,56 59.2,8.1 "/>
</defs>
<clipPath id="SVGID_2_">
<use xlink:href="#SVGID_1_" style="overflow:visible;"/>
</clipPath>
<polygon class="st1" points="56.7,62.9 59.2,56 59.2,62.9 "/>
<polygon class="st1" points="55.7,62.9 59.2,53.1 59.2,56 56.7,62.9 "/>
<polygon class="st2" points="54.6,62.9 59.2,50.3 59.2,53.1 55.7,62.9 "/>
<polygon class="st3" points="53.6,62.9 59.2,47.4 59.2,50.3 54.6,62.9 "/>
<polygon class="st4" points="52.6,62.9 59.2,44.6 59.2,47.4 53.6,62.9 "/>
<polygon class="st5" points="51.5,62.9 59.2,41.7 59.2,44.6 52.6,62.9 "/>
<polygon class="st6" points="50.5,62.9 59.2,38.9 59.2,41.7 51.5,62.9 "/>
<polygon class="st7" points="49.4,62.9 59.2,36 59.2,38.9 50.5,62.9 "/>
<polygon class="st8" points="48.4,62.9 59.2,33.1 59.2,36 49.4,62.9 "/>
<polygon class="st9" points="47.4,62.9 59.2,30.3 59.2,33.1 48.4,62.9 "/>
<polygon class="st10" points="46.3,62.9 59.2,27.4 59.2,30.3 47.4,62.9 "/>
<polygon class="st11" points="45.3,62.9 59.2,24.6 59.2,27.4 46.3,62.9 "/>
<polygon class="st12" points="44.2,62.9 59.2,21.7 59.2,24.6 45.3,62.9 "/>
<polygon class="st13" points="43.2,62.9 59.2,18.9 59.2,21.7 44.2,62.9 "/>
<polygon class="st14" points="42.2,62.9 59.2,16 59.2,18.9 43.2,62.9 "/>
<polygon class="st15" points="41.1,62.9 59.2,13.2 59.2,16 42.2,62.9 "/>
<polygon class="st16" points="40.1,62.9 59.2,10.3 59.2,13.2 41.1,62.9 "/>
<polygon class="st17" points="39,62.9 59,8.1 59.2,8.1 59.2,10.3 40.1,62.9 "/>
<polygon class="st18" points="38,62.9 57.9,8.1 59,8.1 39,62.9 "/>
<polygon class="st19" points="37,62.9 56.9,8.1 57.9,8.1 38,62.9 "/>
<polygon class="st20" points="35.9,62.9 55.9,8.1 56.9,8.1 37,62.9 "/>
<polygon class="st21" points="34.9,62.9 54.8,8.1 55.9,8.1 35.9,62.9 "/>
<polygon class="st22" points="33.8,62.9 53.8,8.1 54.8,8.1 34.9,62.9 "/>
<polygon class="st23" points="32.8,62.9 52.7,8.1 53.8,8.1 33.8,62.9 "/>
<polygon class="st24" points="31.8,62.9 51.7,8.1 52.7,8.1 32.8,62.9 "/>
<polygon class="st25" points="30.7,62.9 50.7,8.1 51.7,8.1 31.8,62.9 "/>
<polygon class="st26" points="29.7,62.9 49.6,8.1 50.7,8.1 30.7,62.9 "/>
<polygon class="st27" points="28.7,62.9 48.6,8.1 49.6,8.1 29.7,62.9 "/>
<polygon class="st28" points="27.6,62.9 47.5,8.1 48.6,8.1 28.7,62.9 "/>
<polygon class="st29" points="26.6,62.9 46.5,8.1 47.5,8.1 27.6,62.9 "/>
<polygon class="st30" points="25.5,62.9 45.5,8.1 46.5,8.1 26.6,62.9 "/>
<polygon class="st31" points="24.5,62.9 44.4,8.1 45.5,8.1 25.5,62.9 "/>
<polygon class="st32" points="23.5,62.9 43.4,8.1 44.4,8.1 24.5,62.9 "/>
<polygon class="st33" points="22.4,62.9 42.3,8.1 43.4,8.1 23.5,62.9 "/>
<polygon class="st34" points="21.4,62.9 41.3,8.1 42.3,8.1 22.4,62.9 "/>
<polygon class="st35" points="20.3,62.9 40.3,8.1 41.3,8.1 21.4,62.9 "/>
<polygon class="st36" points="19.3,62.9 39.2,8.1 40.3,8.1 20.3,62.9 "/>
<polygon class="st37" points="18.3,62.9 38.2,8.1 39.2,8.1 19.3,62.9 "/>
<polygon class="st38" points="17.2,62.9 37.1,8.1 38.2,8.1 18.3,62.9 "/>
<polygon class="st39" points="16.2,62.9 36.1,8.1 37.1,8.1 17.2,62.9 "/>
<polygon class="st40" points="15.2,62.7 35.1,8.1 36.1,8.1 16.2,62.9 15.2,62.9 "/>
<polygon class="st41" points="15.2,59.9 34,8.1 35.1,8.1 15.2,62.7 "/>
<polygon class="st42" points="15.2,57 33,8.1 34,8.1 15.2,59.9 "/>
<polygon class="st43" points="15.2,54.2 32,8.1 33,8.1 15.2,57 "/>
<polygon class="st44" points="15.2,51.3 30.9,8.1 32,8.1 15.2,54.2 "/>
<polygon class="st45" points="15.2,48.5 29.9,8.1 30.9,8.1 15.2,51.3 "/>
<polygon class="st46" points="15.2,45.6 28.8,8.1 29.9,8.1 15.2,48.5 "/>
<polygon class="st47" points="15.2,42.7 27.8,8.1 28.8,8.1 15.2,45.6 "/>
<polygon class="st48" points="15.2,39.9 26.8,8.1 27.8,8.1 15.2,42.7 "/>
<polygon class="st48" points="26.8,8.1 15.2,39.9 15.2,8.1 "/>
</g>
</g>
</g>
<g>
<g>
<g>
<defs>
<polygon id="SVGID_3_" points="83,68.7 83,20.8 59.2,8.1 59.2,56 "/>
</defs>
<clipPath id="SVGID_4_">
<use xlink:href="#SVGID_3_" style="overflow:visible;"/>
</clipPath>
<polygon class="st49" points="83,20.8 78.4,8.1 83,8.1 "/>
<polygon class="st49" points="83,22 77.9,8.1 78.4,8.1 83,20.8 "/>
<polygon class="st50" points="83,23.1 77.5,8.1 77.9,8.1 83,22 "/>
<polygon class="st51" points="83,24.3 77.1,8.1 77.5,8.1 83,23.1 "/>
<polygon class="st52" points="83,25.4 76.7,8.1 77.1,8.1 83,24.3 "/>
<polygon class="st53" points="83,26.6 76.3,8.1 76.7,8.1 83,25.4 "/>
<polygon class="st54" points="83,27.7 75.9,8.1 76.3,8.1 83,26.6 "/>
<polygon class="st55" points="83,28.8 75.4,8.1 75.9,8.1 83,27.7 "/>
<polygon class="st56" points="83,30 75,8.1 75.4,8.1 83,28.8 "/>
<polygon class="st57" points="83,31.1 74.6,8.1 75,8.1 83,30 "/>
<polygon class="st58" points="83,32.3 74.2,8.1 74.6,8.1 83,31.1 "/>
<polygon class="st59" points="83,33.4 73.8,8.1 74.2,8.1 83,32.3 "/>
<polygon class="st60" points="83,34.6 73.4,8.1 73.8,8.1 83,33.4 "/>
<polygon class="st61" points="83,35.7 72.9,8.1 73.4,8.1 83,34.6 "/>
<polygon class="st62" points="83,36.9 72.5,8.1 72.9,8.1 83,35.7 "/>
<polygon class="st63" points="83,38 72.1,8.1 72.5,8.1 83,36.9 "/>
<polygon class="st64" points="83,39.1 71.7,8.1 72.1,8.1 83,38 "/>
<polygon class="st65" points="83,40.3 71.3,8.1 71.7,8.1 83,39.1 "/>
<polygon class="st66" points="83,41.4 70.9,8.1 71.3,8.1 83,40.3 "/>
<polygon class="st67" points="83,42.6 70.4,8.1 70.9,8.1 83,41.4 "/>
<polygon class="st68" points="83,43.7 70,8.1 70.4,8.1 83,42.6 "/>
<polygon class="st69" points="83,44.9 69.6,8.1 70,8.1 83,43.7 "/>
<polygon class="st70" points="83,46 69.2,8.1 69.6,8.1 83,44.9 "/>
<polygon class="st71" points="83,47.2 68.8,8.1 69.2,8.1 83,46 "/>
<polygon class="st72" points="83,48.3 68.4,8.1 68.8,8.1 83,47.2 "/>
<polygon class="st73" points="83,49.5 67.9,8.1 68.4,8.1 83,48.3 "/>
<polygon class="st74" points="83,50.6 67.5,8.1 67.9,8.1 83,49.5 "/>
<polygon class="st75" points="83,51.7 67.1,8.1 67.5,8.1 83,50.6 "/>
<polygon class="st76" points="83,52.9 66.7,8.1 67.1,8.1 83,51.7 "/>
<polygon class="st77" points="83,54 66.3,8.1 66.7,8.1 83,52.9 "/>
<polygon class="st78" points="83,55.2 65.9,8.1 66.3,8.1 83,54 "/>
<polygon class="st79" points="83,56.3 65.4,8.1 65.9,8.1 83,55.2 "/>
<polygon class="st80" points="83,57.5 65,8.1 65.4,8.1 83,56.3 "/>
<polygon class="st81" points="83,58.6 64.6,8.1 65,8.1 83,57.5 "/>
<polygon class="st82" points="83,59.8 64.2,8.1 64.6,8.1 83,58.6 "/>
<polygon class="st83" points="83,60.9 63.8,8.1 64.2,8.1 83,59.8 "/>
<polygon class="st84" points="83,62 63.3,8.1 63.8,8.1 83,60.9 "/>
<polygon class="st85" points="83,63.2 62.9,8.1 63.3,8.1 83,62 "/>
<polygon class="st86" points="83,64.3 62.5,8.1 62.9,8.1 83,63.2 "/>
<polygon class="st87" points="83,65.5 62.1,8.1 62.5,8.1 83,64.3 "/>
<polygon class="st88" points="83,66.6 61.7,8.1 62.1,8.1 83,65.5 "/>
<polygon class="st89" points="83,67.8 61.3,8.1 61.7,8.1 83,66.6 "/>
<polygon class="st90" points="82.9,68.7 60.8,8.1 61.3,8.1 83,67.8 83,68.7 "/>
<polygon class="st91" points="82.5,68.7 60.4,8.1 60.8,8.1 82.9,68.7 "/>
<polygon class="st92" points="82.1,68.7 60,8.1 60.4,8.1 82.5,68.7 "/>
<polygon class="st93" points="81.6,68.7 59.6,8.1 60,8.1 82.1,68.7 "/>
<polygon class="st94" points="81.2,68.7 59.2,8.1 59.2,8.1 59.6,8.1 81.6,68.7 "/>
<polygon class="st95" points="80.8,68.7 59.2,9.3 59.2,8.1 81.2,68.7 "/>
<polygon class="st96" points="80.4,68.7 59.2,10.4 59.2,9.3 80.8,68.7 "/>
<polygon class="st97" points="80,68.7 59.2,11.6 59.2,10.4 80.4,68.7 "/>
<polygon class="st98" points="79.6,68.7 59.2,12.7 59.2,11.6 80,68.7 "/>
<polygon class="st99" points="79.1,68.7 59.2,13.8 59.2,12.7 79.6,68.7 "/>
<polygon class="st100" points="78.7,68.7 59.2,15 59.2,13.8 79.1,68.7 "/>
<polygon class="st101" points="78.3,68.7 59.2,16.1 59.2,15 78.7,68.7 "/>
<polygon class="st102" points="77.9,68.7 59.2,17.3 59.2,16.1 78.3,68.7 "/>
<polygon class="st103" points="77.5,68.7 59.2,18.4 59.2,17.3 77.9,68.7 "/>
<polygon class="st104" points="77.1,68.7 59.2,19.6 59.2,18.4 77.5,68.7 "/>
<polygon class="st105" points="76.6,68.7 59.2,20.7 59.2,19.6 77.1,68.7 "/>
<polygon class="st106" points="76.2,68.7 59.2,21.9 59.2,20.7 76.6,68.7 "/>
<polygon class="st107" points="75.8,68.7 59.2,23 59.2,21.9 76.2,68.7 "/>
<polygon class="st108" points="75.4,68.7 59.2,24.1 59.2,23 75.8,68.7 "/>
<polygon class="st109" points="75,68.7 59.2,25.3 59.2,24.1 75.4,68.7 "/>
<polygon class="st110" points="74.6,68.7 59.2,26.4 59.2,25.3 75,68.7 "/>
<polygon class="st111" points="74.1,68.7 59.2,27.6 59.2,26.4 74.6,68.7 "/>
<polygon class="st111" points="59.2,27.6 74.1,68.7 59.2,68.7 "/>
</g>
</g>
</g>
<g>
<g>
<g>
<defs>
<polygon id="SVGID_5_" points="39,75.5 83,68.7 59.2,56 15.2,62.9 "/>
</defs>
<clipPath id="SVGID_6_">
<use xlink:href="#SVGID_5_" style="overflow:visible;"/>
</clipPath>
<path class="st112" d="M83,75.5v-6.9V75.5z"/>
<rect x="82.1" y="56" class="st112" width="0.9" height="19.5"/>
<rect x="81.2" y="56" class="st113" width="0.9" height="19.5"/>
<rect x="80.3" y="56" class="st114" width="0.9" height="19.5"/>
<rect x="79.4" y="56" class="st115" width="0.9" height="19.5"/>
<rect x="78.5" y="56" class="st116" width="0.9" height="19.5"/>
<rect x="77.6" y="56" class="st117" width="0.9" height="19.5"/>
<rect x="76.7" y="56" class="st118" width="0.9" height="19.5"/>
<rect x="75.8" y="56" class="st119" width="0.9" height="19.5"/>
<rect x="74.9" y="56" class="st120" width="0.9" height="19.5"/>
<rect x="74" y="56" class="st121" width="0.9" height="19.5"/>
<rect x="73.1" y="56" class="st122" width="0.9" height="19.5"/>
<rect x="72.2" y="56" class="st123" width="0.9" height="19.5"/>
<rect x="71.3" y="56" class="st124" width="0.9" height="19.5"/>
<rect x="70.4" y="56" class="st125" width="0.9" height="19.5"/>
<rect x="69.5" y="56" class="st126" width="0.9" height="19.5"/>
<rect x="68.6" y="56" class="st127" width="0.9" height="19.5"/>
<rect x="67.7" y="56" class="st128" width="0.9" height="19.5"/>
<rect x="66.8" y="56" class="st129" width="0.9" height="19.5"/>
<rect x="65.9" y="56" class="st130" width="0.9" height="19.5"/>
<rect x="65" y="56" class="st131" width="0.9" height="19.5"/>
<rect x="64.1" y="56" class="st132" width="0.9" height="19.5"/>
<rect x="63.2" y="56" class="st133" width="0.9" height="19.5"/>
<rect x="62.3" y="56" class="st134" width="0.9" height="19.5"/>
<rect x="61.4" y="56" class="st135" width="0.9" height="19.5"/>
<rect x="60.5" y="56" class="st136" width="0.9" height="19.5"/>
<rect x="59.6" y="56" class="st137" width="0.9" height="19.5"/>
<rect x="58.7" y="56" class="st138" width="0.9" height="19.5"/>
<rect x="57.8" y="56" class="st139" width="0.9" height="19.5"/>
<rect x="56.9" y="56" class="st140" width="0.9" height="19.5"/>
<rect x="56" y="56" class="st141" width="0.9" height="19.5"/>
<rect x="55.1" y="56" class="st142" width="0.9" height="19.5"/>
<rect x="54.2" y="56" class="st143" width="0.9" height="19.5"/>
<rect x="53.3" y="56" class="st144" width="0.9" height="19.5"/>
<rect x="52.4" y="56" class="st145" width="0.9" height="19.5"/>
<rect x="51.5" y="56" class="st146" width="0.9" height="19.5"/>
<rect x="50.6" y="56" class="st147" width="0.9" height="19.5"/>
<rect x="49.7" y="56" class="st148" width="0.9" height="19.5"/>
<rect x="48.8" y="56" class="st149" width="0.9" height="19.5"/>
<rect x="47.9" y="56" class="st150" width="0.9" height="19.5"/>
<rect x="47" y="56" class="st151" width="0.9" height="19.5"/>
<rect x="46.1" y="56" class="st152" width="0.9" height="19.5"/>
<rect x="45.2" y="56" class="st153" width="0.9" height="19.5"/>
<rect x="44.3" y="56" class="st154" width="0.9" height="19.5"/>
<rect x="43.4" y="56" class="st155" width="0.9" height="19.5"/>
<rect x="42.5" y="56" class="st156" width="0.9" height="19.5"/>
<rect x="41.6" y="56" class="st157" width="0.9" height="19.5"/>
<rect x="40.7" y="56" class="st158" width="0.9" height="19.5"/>
<rect x="39.8" y="56" class="st159" width="0.9" height="19.5"/>
<rect x="38.9" y="56" class="st160" width="0.9" height="19.5"/>
<rect x="38" y="56" class="st161" width="0.9" height="19.5"/>
<rect x="37.1" y="56" class="st162" width="0.9" height="19.5"/>
<rect x="15.2" y="56" class="st162" width="21.9" height="19.5"/>
</g>
</g>
</g>
</g>
<g class="st163">
<g>
<g>
<defs>
<polygon id="SVGID_7_" points="39,27.6 15.2,15 15.2,62.9 39,75.5 "/>
</defs>
<clipPath id="SVGID_8_">
<use xlink:href="#SVGID_7_" style="overflow:visible;"/>
</clipPath>
<polygon class="st164" points="37.7,75.5 39,73.7 39,75.5 "/>
<polygon class="st165" points="36.4,75.5 39,71.8 39,73.7 37.7,75.5 "/>
<polygon class="st166" points="35.1,75.5 39,70 39,71.8 36.4,75.5 "/>
<polygon class="st167" points="33.8,75.5 39,68.1 39,70 35.1,75.5 "/>
<polygon class="st168" points="32.5,75.5 39,66.2 39,68.1 33.8,75.5 "/>
<polygon class="st169" points="31.9,75.5 31.4,75.2 39,64.4 39,66.2 32.5,75.5 "/>
<polygon class="st170" points="31.4,75.2 30.5,74.6 39,62.5 39,64.4 "/>
<polygon class="st171" points="30.5,74.6 29.7,74 39,60.7 39,62.5 "/>
<polygon class="st172" points="29.7,74 28.8,73.4 39,58.8 39,60.7 "/>
<polygon class="st173" points="28.8,73.4 27.9,72.8 39,57 39,58.8 "/>
<polygon class="st174" points="27.9,72.8 27.1,72.1 39,55.1 39,57 "/>
<polygon class="st175" points="27.1,72.1 26.2,71.5 39,53.3 39,55.1 "/>
<polygon class="st176" points="26.2,71.5 25.3,70.9 39,51.4 39,53.3 "/>
<polygon class="st177" points="25.3,70.9 24.4,70.3 39,49.6 39,51.4 "/>
<polygon class="st178" points="24.4,70.3 23.6,69.7 39,47.7 39,49.6 "/>
<polygon class="st179" points="23.6,69.7 22.7,69.1 39,45.9 39,47.7 "/>
<polygon class="st180" points="22.7,69.1 21.8,68.5 39,44 39,45.9 "/>
<polygon class="st181" points="21.8,68.5 21,67.9 39,42.2 39,44 "/>
<polygon class="st182" points="21,67.9 20.1,67.3 39,40.3 39,42.2 "/>
<polygon class="st183" points="20.1,67.3 19.2,66.7 39,38.4 39,40.3 "/>
<polygon class="st184" points="19.2,66.7 18.4,66 39,36.6 39,38.4 "/>
<polygon class="st185" points="18.4,66 17.5,65.4 39,34.7 39,36.6 "/>
<polygon class="st186" points="17.5,65.4 16.6,64.8 39,32.9 39,34.7 "/>
<polygon class="st187" points="16.6,64.8 15.7,64.2 39,31 39,32.9 "/>
<polygon class="st188" points="15.7,64.2 15.2,63.8 15.2,63.2 39,29.2 39,31 "/>
<polygon class="st189" points="15.2,61.3 39,27.3 39,29.2 15.2,63.2 "/>
<polygon class="st190" points="15.2,59.5 38.4,26.3 39,26.7 39,27.3 15.2,61.3 "/>
<polygon class="st191" points="15.2,57.6 37.5,25.7 38.4,26.3 15.2,59.5 "/>
<polygon class="st192" points="15.2,55.7 36.7,25 37.5,25.7 15.2,57.6 "/>
<polygon class="st193" points="15.2,53.9 35.8,24.4 36.7,25 15.2,55.7 "/>
<polygon class="st194" points="15.2,52 34.9,23.8 35.8,24.4 15.2,53.9 "/>
<polygon class="st195" points="15.2,50.2 34.1,23.2 34.9,23.8 15.2,52 "/>
<polygon class="st196" points="15.2,48.3 33.2,22.6 34.1,23.2 15.2,50.2 "/>
<polygon class="st197" points="15.2,46.5 32.3,22 33.2,22.6 15.2,48.3 "/>
<polygon class="st198" points="15.2,44.6 31.5,21.4 32.3,22 15.2,46.5 "/>
<polygon class="st199" points="15.2,42.8 30.6,20.8 31.5,21.4 15.2,44.6 "/>
<polygon class="st200" points="15.2,40.9 29.7,20.2 30.6,20.8 15.2,42.8 "/>
<polygon class="st201" points="15.2,39.1 28.8,19.6 29.7,20.2 15.2,40.9 "/>
<polygon class="st202" points="15.2,37.2 28,18.9 28.8,19.6 15.2,39.1 "/>
<polygon class="st203" points="15.2,35.4 27.1,18.3 28,18.9 15.2,37.2 "/>
<polygon class="st204" points="15.2,33.5 26.2,17.7 27.1,18.3 15.2,35.4 "/>
<polygon class="st205" points="15.2,31.6 25.4,17.1 26.2,17.7 15.2,33.5 "/>
<polygon class="st206" points="15.2,29.8 24.5,16.5 25.4,17.1 15.2,31.6 "/>
<polygon class="st207" points="15.2,27.9 23.6,15.9 24.5,16.5 15.2,29.8 "/>
<polygon class="st208" points="15.2,26.1 22.7,15.3 23.6,15.9 15.2,27.9 "/>
<polygon class="st209" points="15.2,24.2 21.7,15 22.3,15 22.7,15.3 15.2,26.1 "/>
<polygon class="st210" points="15.2,22.4 20.4,15 21.7,15 15.2,24.2 "/>
<polygon class="st211" points="15.2,20.5 19.1,15 20.4,15 15.2,22.4 "/>
<polygon class="st212" points="15.2,18.7 17.8,15 19.1,15 15.2,20.5 "/>
<polygon class="st213" points="15.2,16.8 16.5,15 17.8,15 15.2,18.7 "/>
<polygon class="st214" points="15.2,15 15.2,15 16.5,15 15.2,16.8 "/>
<polygon class="st214" points="15.2,15 15.2,15 15.2,15 "/>
</g>
</g>
</g>
<g class="st215">
<g>
<g>
<defs>
<polygon id="SVGID_9_" points="83,20.8 59.2,8.1 15.2,15 39,27.6 "/>
</defs>
<clipPath id="SVGID_10_">
<use xlink:href="#SVGID_9_" style="overflow:visible;"/>
</clipPath>
<path class="st216" d="M83,27.6v-6.9V27.6z"/>
<rect x="81.7" y="8.1" class="st216" width="1.3" height="19.5"/>
<rect x="80.4" y="8.1" class="st217" width="1.3" height="19.5"/>
<rect x="79.2" y="8.1" class="st218" width="1.3" height="19.5"/>
<rect x="77.9" y="8.1" class="st219" width="1.3" height="19.5"/>
<rect x="76.7" y="8.1" class="st220" width="1.3" height="19.5"/>
<rect x="75.4" y="8.1" class="st221" width="1.3" height="19.5"/>
<rect x="74.2" y="8.1" class="st222" width="1.3" height="19.5"/>
<rect x="72.9" y="8.1" class="st223" width="1.3" height="19.5"/>
<rect x="71.7" y="8.1" class="st224" width="1.3" height="19.5"/>
<rect x="70.4" y="8.1" class="st225" width="1.3" height="19.5"/>
<rect x="69.2" y="8.1" class="st226" width="1.3" height="19.5"/>
<rect x="67.9" y="8.1" class="st227" width="1.3" height="19.5"/>
<rect x="66.6" y="8.1" class="st228" width="1.3" height="19.5"/>
<rect x="65.4" y="8.1" class="st229" width="1.3" height="19.5"/>
<rect x="64.1" y="8.1" class="st230" width="1.3" height="19.5"/>
<rect x="62.9" y="8.1" class="st231" width="1.3" height="19.5"/>
<rect x="61.6" y="8.1" class="st232" width="1.3" height="19.5"/>
<rect x="60.4" y="8.1" class="st233" width="1.3" height="19.5"/>
<rect x="59.1" y="8.1" class="st234" width="1.3" height="19.5"/>
<rect x="57.9" y="8.1" class="st235" width="1.3" height="19.5"/>
<rect x="56.6" y="8.1" class="st236" width="1.3" height="19.5"/>
<rect x="55.3" y="8.1" class="st237" width="1.3" height="19.5"/>
<rect x="54.1" y="8.1" class="st238" width="1.3" height="19.5"/>
<rect x="52.8" y="8.1" class="st239" width="1.3" height="19.5"/>
<rect x="51.6" y="8.1" class="st240" width="1.3" height="19.5"/>
<rect x="50.3" y="8.1" class="st241" width="1.3" height="19.5"/>
<rect x="49.1" y="8.1" class="st242" width="1.3" height="19.5"/>
<rect x="47.8" y="8.1" class="st243" width="1.3" height="19.5"/>
<rect x="46.6" y="8.1" class="st244" width="1.3" height="19.5"/>
<rect x="45.3" y="8.1" class="st245" width="1.3" height="19.5"/>
<rect x="44.1" y="8.1" class="st246" width="1.3" height="19.5"/>
<rect x="42.8" y="8.1" class="st247" width="1.3" height="19.5"/>
<rect x="41.5" y="8.1" class="st248" width="1.3" height="19.5"/>
<rect x="40.3" y="8.1" class="st249" width="1.3" height="19.5"/>
<rect x="39" y="8.1" class="st250" width="1.3" height="19.5"/>
<rect x="37.8" y="8.1" class="st251" width="1.3" height="19.5"/>
<rect x="36.5" y="8.1" class="st252" width="1.3" height="19.5"/>
<rect x="35.3" y="8.1" class="st253" width="1.3" height="19.5"/>
<rect x="34" y="8.1" class="st254" width="1.3" height="19.5"/>
<rect x="32.8" y="8.1" class="st255" width="1.3" height="19.5"/>
<rect x="31.5" y="8.1" class="st256" width="1.3" height="19.5"/>
<rect x="30.2" y="8.1" class="st257" width="1.3" height="19.5"/>
<rect x="29" y="8.1" class="st258" width="1.3" height="19.5"/>
<rect x="27.7" y="8.1" class="st259" width="1.3" height="19.5"/>
<rect x="26.5" y="8.1" class="st260" width="1.3" height="19.5"/>
<rect x="25.2" y="8.1" class="st261" width="1.3" height="19.5"/>
<rect x="24" y="8.1" class="st262" width="1.3" height="19.5"/>
<rect x="22.7" y="8.1" class="st263" width="1.3" height="19.5"/>
<rect x="21.5" y="8.1" class="st264" width="1.3" height="19.5"/>
<rect x="20.2" y="8.1" class="st265" width="1.3" height="19.5"/>
<rect x="18.9" y="8.1" class="st266" width="1.3" height="19.5"/>
<rect x="17.7" y="8.1" class="st267" width="1.3" height="19.5"/>
<rect x="16.4" y="8.1" class="st268" width="1.3" height="19.5"/>
<polygon class="st269" points="15.2,15 15.2,8.1 16.4,8.1 16.4,27.6 15.2,27.6 "/>
<path class="st269" d="M15.2,8.1V15V8.1z"/>
</g>
</g>
</g>
<g class="st215">
<g>
<g>
<defs>
<polygon id="SVGID_11_" points="39,27.6 39,75.5 83,68.7 83,20.8 "/>
</defs>
<clipPath id="SVGID_12_">
<use xlink:href="#SVGID_11_" style="overflow:visible;"/>
</clipPath>
<polygon class="st270" points="77.2,75.5 83,68.7 83,75.5 "/>
<polygon class="st270" points="76.2,75.5 83,67.5 83,68.7 77.2,75.5 "/>
<polygon class="st271" points="75.3,75.5 83,66.3 83,67.5 76.2,75.5 "/>
<polygon class="st272" points="74.3,75.5 83,65.1 83,66.3 75.3,75.5 "/>
<polygon class="st273" points="73.3,75.5 83,64 83,65.1 74.3,75.5 "/>
<polygon class="st274" points="72.3,75.5 83,62.8 83,64 73.3,75.5 "/>
<polygon class="st275" points="71.3,75.5 83,61.6 83,62.8 72.3,75.5 "/>
<polygon class="st276" points="70.3,75.5 83,60.4 83,61.6 71.3,75.5 "/>
<polygon class="st277" points="69.3,75.5 83,59.3 83,60.4 70.3,75.5 "/>
<polygon class="st278" points="68.4,75.5 83,58.1 83,59.3 69.3,75.5 "/>
<polygon class="st279" points="67.4,75.5 83,56.9 83,58.1 68.4,75.5 "/>
<polygon class="st280" points="66.4,75.5 83,55.7 83,56.9 67.4,75.5 "/>
<polygon class="st281" points="65.4,75.5 83,54.6 83,55.7 66.4,75.5 "/>
<polygon class="st282" points="64.4,75.5 83,53.4 83,54.6 65.4,75.5 "/>
<polygon class="st283" points="63.4,75.5 83,52.2 83,53.4 64.4,75.5 "/>
<polygon class="st284" points="62.4,75.5 83,51 83,52.2 63.4,75.5 "/>
<polygon class="st285" points="61.5,75.5 83,49.9 83,51 62.4,75.5 "/>
<polygon class="st286" points="60.5,75.5 83,48.7 83,49.9 61.5,75.5 "/>
<polygon class="st287" points="59.5,75.5 83,47.5 83,48.7 60.5,75.5 "/>
<polygon class="st288" points="58.5,75.5 83,46.3 83,47.5 59.5,75.5 "/>
<polygon class="st289" points="57.5,75.5 83,45.2 83,46.3 58.5,75.5 "/>
<polygon class="st290" points="56.5,75.5 83,44 83,45.2 57.5,75.5 "/>
<polygon class="st291" points="55.6,75.5 83,42.8 83,44 56.5,75.5 "/>
<polygon class="st292" points="54.6,75.5 83,41.7 83,42.8 55.6,75.5 "/>
<polygon class="st293" points="53.6,75.5 83,40.5 83,41.7 54.6,75.5 "/>
<polygon class="st294" points="52.6,75.5 83,39.3 83,40.5 53.6,75.5 "/>
<polygon class="st295" points="51.6,75.5 83,38.1 83,39.3 52.6,75.5 "/>
<polygon class="st296" points="50.6,75.5 83,37 83,38.1 51.6,75.5 "/>
<polygon class="st297" points="49.6,75.5 83,35.8 83,37 50.6,75.5 "/>
<polygon class="st298" points="48.7,75.5 83,34.6 83,35.8 49.6,75.5 "/>
<polygon class="st299" points="47.7,75.5 83,33.4 83,34.6 48.7,75.5 "/>
<polygon class="st300" points="46.7,75.5 83,32.3 83,33.4 47.7,75.5 "/>
<polygon class="st301" points="45.7,75.5 83,31.1 83,32.3 46.7,75.5 "/>
<polygon class="st302" points="44.7,75.5 83,29.9 83,31.1 45.7,75.5 "/>
<polygon class="st303" points="43.7,75.5 83,28.7 83,29.9 44.7,75.5 "/>
<polygon class="st304" points="42.8,75.5 83,27.6 83,28.7 43.7,75.5 "/>
<polygon class="st305" points="41.8,75.5 83,26.4 83,27.6 42.8,75.5 "/>
<polygon class="st306" points="40.8,75.5 83,25.2 83,26.4 41.8,75.5 "/>
<polygon class="st307" points="39.8,75.5 83,24 83,25.2 40.8,75.5 "/>
<polygon class="st308" points="39,75.4 83,22.9 83,24 39.8,75.5 39,75.5 "/>
<polygon class="st309" points="39,74.2 83,21.7 83,22.9 39,75.4 "/>
<polygon class="st310" points="39,73 82.8,20.8 83,20.8 83,21.7 39,74.2 "/>
<polygon class="st311" points="39,71.8 81.8,20.8 82.8,20.8 39,73 "/>
<polygon class="st312" points="39,70.7 80.8,20.8 81.8,20.8 39,71.8 "/>
<polygon class="st313" points="39,69.5 79.8,20.8 80.8,20.8 39,70.7 "/>
<polygon class="st314" points="39,68.3 78.9,20.8 79.8,20.8 39,69.5 "/>
<polygon class="st315" points="39,67.1 77.9,20.8 78.9,20.8 39,68.3 "/>
<polygon class="st316" points="39,66 76.9,20.8 77.9,20.8 39,67.1 "/>
<polygon class="st317" points="39,64.8 75.9,20.8 76.9,20.8 39,66 "/>
<polygon class="st317" points="75.9,20.8 39,64.8 39,20.8 "/>
</g>
</g>
</g>
<g>
<g>
<g>
<path class="st0" d="M36.6,28.1c0-1.4,1.1-2.7,2.5-2.9l22.5-3.6v31.9c0,2.6-1.9,4.7-4.4,5.1L36.5,62L36.6,28.1z M56.7,27.3
l-15.3,2.4l0,26.6l15.4-2.5V27.3z"/>
</g>
</g>
</g>
<g>
<g>
<rect x="36.6" y="48.3" class="st0" width="4.8" height="28.2"/>
</g>
</g>
<g>
<g>
<rect x="59.2" y="3.9" class="st0" width="2.4" height="36.7"/>
</g>
</g>
<g>
<g>
<path class="st0" d="M83,23.4v42.4c0,1.5-1.1,2.9-2.6,3.1l-35.3,5.7c-1.9,0.3-3.6-1.2-3.6-3.1v-7.6c0-1.5,1.1-2.9,2.6-3.1l14-2.2
c2-0.3,3.5-2.1,3.5-4.1l0.2-39.7c0-2.4,2.5-3.9,4.6-2.8l14.3,7.7C82.1,20.4,83,21.8,83,23.4z M86.5,16L62.3,3
c-0.8-0.6-1.9-0.1-1.9,0.9l-1.1,51c0,0.6-0.4,1.1-1,1.2L40,59c-0.6,0.1-1,0.6-1,1.2l0,18.7c0,0.7,0.7,1.3,1.4,1.2L86,71.8
c0.6-0.1,1-0.6,1-1.2V17C87,16.6,86.8,16.3,86.5,16z"/>
</g>
</g>
<g>
<g>
<path class="st0" d="M59.2,11.7l0,7.5c0,1.6-1.2,3-2.7,3.2l-17.1,2.8c-1.6,0.3-2.7,1.6-2.7,3.2l0,40.3c0,2.5-2.6,4-4.8,2.9
l-14.8-7.9c-1.1-0.6-1.7-1.7-1.7-2.9V17.6c0-1.6,1.2-3,2.8-3.2l37.5-5.8C57.4,8.2,59.2,9.7,59.2,11.7z M58.8,2.6l-45.5,7.9
c-0.8,0.1-1.4,0.8-1.4,1.6l-0.2,49.6c-0.1,0.7,0.3,1.3,0.9,1.6l25.2,15.4c1.2,0.6,2.6-0.2,2.5-1.6L39,29.3c0-0.9,0.6-1.7,1.5-1.8
l18.5-2.1c0.9-0.1,1.5-0.8,1.5-1.7l0.2-19.4C60.8,3.3,59.8,2.4,58.8,2.6z"/>
</g>
</g>
<g>
<path class="st0" d="M47.5,43.8c-0.6,0-1.1-0.4-1.2-1c-0.1-0.7,0.3-1.3,1-1.4l10.1-1.6c0.7-0.1,1.3,0.3,1.4,1
c0.1,0.7-0.3,1.3-1,1.4l-10.1,1.6C47.6,43.8,47.6,43.8,47.5,43.8z"/>
</g>
<g>
<g>
<path class="st0" d="M55.2,42.6L55.2,42.6c0.8-0.1,1.6,0.5,1.6,1.3v8.7l4.5,0V30.1l-4.5,0.2V38c0,1.1-0.8,2-1.8,2.2l-1.7,0.3
L55.2,42.6z"/>
</g>
</g>
</g>
<g>
<g>
<polygon class="st318" points="112.5,28.8 112.5,37.1 125.6,37.1 125.6,41.2 112.5,41.2 112.5,49.5 125.6,49.5 125.6,53.6
108,53.6 108,24.7 125.6,24.7 125.6,28.8 "/>
</g>
<g>
<path class="st318" d="M147.2,38.3v14.2l-3.8,1.8h-0.6v-16c0-1.7-0.8-1.9-1.5-1.9h-4.3c-0.7,0-1.5,0.2-1.5,1.9v14.2l-3.8,1.8H131
v-16c0-4,2-6,6-6h4.3C145.2,32.3,147.2,34.3,147.2,38.3z"/>
</g>
<g>
<polygon class="st318" points="161.5,32.3 161.5,32.9 159.8,36.4 156.7,36.4 156.7,52.5 152.8,54.3 152.2,54.3 152.2,27.7
156,25.9 156.7,25.9 156.7,32.3 "/>
</g>
<g>
<path class="st318" d="M174.6,32.3h-4.3c-4,0-6,2-6,6v9.3c0,4,2,6,6,6h4.3c0.6,0,1.1-0.1,1.5-0.3v2c0,1.7-0.8,1.9-1.5,1.9h-10.3
v4.1h10.3c4,0,6-2,6-6v-17C180.5,34.3,178.5,32.3,174.6,32.3z M176.1,47.6c0,1.7-0.8,1.9-1.5,1.9h-4.3c-0.7,0-1.5-0.2-1.5-1.9
v-9.3c0-1.7,0.8-1.9,1.5-1.9h4.3c0.7,0,1.5,0.2,1.5,1.9V47.6z"/>
<path class="st318" d="M197,32.3v0.6l-1.6,3.5H192c-0.7,0-1.5,0.2-1.5,1.9v14.2l-3.8,1.8H186v-16c0-4,2-6,6-6H197z"/>
</g>
<g>
<path class="st318" d="M208,32.3h-7v4.1h7c0.7,0,1.5,0.2,1.5,1.9v1.1c-0.5-0.2-1-0.3-1.5-0.3h-3.9c-4,0-6,2-6,6v2.6c0,4,2,6,6,6
h3.9c0.6,0,1.1-0.1,1.5-0.3v1.4h0.6l3.8-1.8V38.3C214,34.3,212,32.3,208,32.3z M208,49.5h-3.9c-0.7,0-1.5-0.2-1.5-1.9V45
c0-1.7,0.8-1.9,1.5-1.9h3.9c0.7,0,1.5,0.2,1.5,1.9v2.6C209.5,49.3,208.7,49.5,208,49.5z"/>
</g>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 804 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 798 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 794 KiB

View File

@ -1,67 +0,0 @@
{
"Title" : "Title",
"Description" : "Description",
"ShortDescription" : "Short Description",
"Category" : "Category",
"Visibility" : "Visibility",
"Devices" : "Devices",
"Roles" : "Roles",
"Groups" : "Groups",
"Tags" : "Tags",
"Platform" : "Platform",
"Platforms" : "Platforms",
"Applications": "Applications",
"No.Platform" : "No Platforms",
"Screenshots" : "Screenshots",
"Icon" : "Icon",
"Info" : "Info",
"Banner" : "Banner",
"Create.Application" : "Create Application",
"Back" : "Back",
"Cancel" : "Cancel",
"Finish" : "Finish",
"Continue" : "Continue",
"Name" : "Name",
"Application.Name" : "Application Name",
"General" : "General",
"App.Releases" : "Application Releases",
"Package.Manager" : "Package Manager",
"Save" : "Save",
"Create.Release" : "Create Release",
"Release.Channel" : "Release Channel",
"Release" : "Release",
"New.Release.For" : "New Release for",
"Upload.Package.File" : "Upload Package File",
"Upload" : "Upload",
"Select.from.package.library" : "Select from package library",
"Release.Name" : "Release Name",
"Release.Notes" : "Release Notes",
"Send.for.Review" : "Send for Review",
"Production.Releases" : "Production Releases",
"Beta.Releases" : "Beta Releases",
"Alpha.Releases" : "Alpha Releases",
"Version" : "Version",
"Status" : "Status",
"App.Publisher" : "Application Publisher",
"Search.Apps" : "Search for Applications",
"View.In.Store" : "View in Store",
"Last.Updated" : "Last updated on",
"Installs" : "Installs",
"General.Info" : "General Info",
"Select.Platform": "Select Platform",
"Add.Release" : "Add Release to Application",
"Share.With.Tenants" : "Share with Tenants",
"Disable" : "Disable",
"File.Based" : "File Based",
"Activate" : "Activate",
"Yes" : "Yes",
"No" : "No",
"No.Platform.Tags" : "No Platform Tags",
"Create.Platform" : "Create Platform",
"Optional": "Optional",
"Identifier": "Identifier",
"Next": "Next",
"Platform.Enable": "Enable Platform",
"Share.with.Tenants": "Share between all tenants",
"Platform.Properties": "Platform Properties"
}

View File

@ -1,903 +0,0 @@
/*
* Copyright (c) 2020, Entgra (pvt) Ltd. (http://entgra.io) All Rights Reserved.
*
* Entgra (pvt) Ltd. licenses this file to you 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.
*/
@font-face {
font-family: "Roboto-Medium";
src: url('../../fonts/Roboto-Medium.woff');
src: local("Roboto-Medium"), url("../../fonts/Roboto-Medium.ttf") format("ttf");
src: local("Roboto-Medium"), url("../../fonts/Roboto-Medium.woff") format("woff");
src: local("Roboto-Medium"), url("../../fonts/Roboto-Medium.woff2") format("woff2");
}
@font-face {
font-family: "Roboto-Regular";
src: url("../../fonts/Roboto-Regular.woff");
src: local("Roboto-Regular"), url("../../fonts/Roboto-Regular.ttf") format("ttf");
src: local("Roboto-Regular"), url("../../fonts/Roboto-Regular.woff") format("woff");
src: local("Roboto-Regular"), url("../../fonts/Roboto-Regular.woff2") format("woff2");
}
/*Colors*/
.primary {
color: white;
background-color: #2196f3 !important;
}
.primary-flat {
color: #2196F3 !important;
}
.danger {
color: white;
background-color: #e91e63 !important;
}
.danger-flat {
color: #e91e63 !important;
}
.grey {
color: #b3b3b3 !important;
}
/* ==================================================================== */
/* Custom button styles based on material design specs. */
.custom-raised {
font-family: Roboto-Medium;
text-transform: uppercase !important;
font-size: 14px !important;
padding-left: 16px !important;
border-radius: 2px !important;
padding-right: 16px !important;
height: 36px !important;
border: none !important;
}
.custom-raised:hover {
cursor: pointer;
box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.16), 0 0 0 1px rgba(0, 0, 0, 0.08) !important;
-webkit-box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.16), 0 0 0 1px rgba(0, 0, 0, 0.08) !important;
background-color: #1976D2 !important;
}
.custom-raised:focus {
box-shadow: none !important;
-webkit-box-shadow: none !important;
background-color: #1976D2 !important;
}
.custom-flat {
font-family: Roboto-Medium;
height: 36px !important;
border-radius: 2px !important;
margin-left: 8px !important;
margin-right: 8px !important;
padding-left: 8px !important;
padding-right: 8px !important;
background-color: transparent !important;
text-transform: uppercase;
outline: none !important;
border: none !important;
}
.custom-flat:hover {
cursor: pointer;
background-color: rgba(0, 0, 0, 0.12) !important;
}
.custom-flat:focus {
outline: none !important;
border: none !important;
-webkit-box-shadow: none !important;
box-shadow: none !important;
background-color: rgba(0, 0, 0, 0.40) !important;
}
.circle-button {
border-radius: 100% !important;
height: 36px !important;
width: 36px;
}
/* ==================================================================== */
/* Body Styling */
body {
width: 100%;
font-family: "Roboto-Regular" !important;
font-size: 14px !important;
background-color: #e8e8e8 !important;
}
.app-manager-title {
font-family: "Roboto-Medium";
font-size: 20px;
}
.app-manager-sub-title {
font-family: "Roboto-Regular";
font-size: 18px;
}
#app-mgt-footer {
clear: both;
position: relative;
height: 50px;
width: 100%;
color: white;
background-color: #334d88;
}
/* Login page styles*/
#userName {
border-radius: 0;
}
#password {
border-radius: 0;
}
.login-btn {
float: right;
}
.login-header {
background-color: #3f50b5;
color: white;
height: 128px;
width: 100%;
margin: 0 !important;
padding: 20px;
box-shadow: -2px 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19);
}
#login-card {
width: 25%;
height: 50%;
margin: 10% auto;
font-family: Roboto-Regular;
font-size: 14px;
border-radius: 0;
background-color: #ffffff;
box-shadow: -2px 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19);
}
.login-header-title {
font-family: Roboto-Medium;
font-size: 20px;
font-weight: 500;
}
.login-header-logo {
height: 70px;
width: 150px;
}
.login-form {
margin: 0 !important;
padding: 40px;
}
/* Base layout container */
/* Base layout header content*/
.header-content {
height: 128px !important;
width: 100% !important;
margin: 0 10px 0 0;
background-color: #3f50b5 !important;
position: fixed; /* Set the navbar to fixed position */
top: 0; /* Position the navbar at the top of the page */
z-index: 2;
box-shadow: -2px 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19);
}
/* Contains the header styles.*/
.header {
padding: 24px 24px 10px 24px;
/*margin: 16px 16px 20px 16px;*/
position: relative;
}
#header-text {
color: #ffffff;
font-size: 20px;
font-family: Roboto-Medium;
top: 10px;
margin-left: 10px;
}
/* The buttons in the header (User and Notification)*/
.header-button-container {
display: flex;
justify-content: flex-end;
}
.header-user-name {
font-family: Roboto-Medium;
font-size: 14px;
padding-top: 15px;
color: white;
}
.header-image {
height: 43px;
width: 100px;
margin-right: 24px;
}
#header-button {
border-radius: 50%;
background-color: transparent;
border: none;
height: 50px;
width: 50px;
margin-right: 10px;
position: relative;
outline: none;
}
#header-button:hover {
background-color: #4353bd;
cursor: pointer;
}
#header-button i {
position: absolute;
bottom: 19px;
left: 17px;
}
.btn-header {
margin-top: 15px;
margin-right: 20px;
color: white;
}
#sub-title {
font-family: Roboto-Regular;
font-size: 18px;
font-weight: 600;
padding-top: 5px;
padding-left: 18px;
color: RGBA(0, 0, 0, 1);
}
/* Search box styles */
.search-box {
display: flex;
float: right;
}
.search-box i {
position: absolute;
top: 5px;
color: #BaBaBa;
}
#search {
position: relative;
color: white;
background-color: transparent;
left: 15px;
top: 0px;
height: 25px;
outline: none;
border: none;
border-radius: 0%;
}
/* Application Add button */
#add-btn-container {
position: absolute;
top: 98px;
}
.add-btn {
background-color: #ff5722;
}
.add-btn:hover {
background-color: #E64A19;
}
#sub-title-container {
height: 100px;
padding: 50px 0 20px 0;
}
.application-container {
padding: 0 !important;
min-height: 100% !important;
margin-top: 128px !important;
}
/* Holds the app pages. */
.store-card {
height: auto;
background-color: white;
box-shadow: 2px 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19);
padding: 24px;
}
.platform-link-placeholder {
color: #888888;
float: right;
padding-bottom: 10px;
}
.platform-link-placeholder i {
margin-right: 4px;
}
.application-list {
transition: margin-right .5s;
}
#batch-content {
display: flex;
margin-top: 5px;
}
.app-list-icon {
border-radius: 50%;
height: 50px;
width: 50px
}
.app-table-row {
height: 62px;
cursor: pointer;
padding-top: 6px;
font-family: "Roboto-Regular";
font-size: medium;
}
.app-table-row:hover {
color: white;
background-color: #3f50b5;
}
.app-list-table-header {
margin-top: 30px;
margin-bottom: 10px;
font-family: "Roboto-Medium";
font-size: 15px;
}
.app-view-image {
height: 100px;
width: 100px;
border-radius: 50%;
}
#app-visibility-default {
display: none;
}
#app-image-screenshot {
width: 300px;
height: 300px;
}
#app-image-icon {
width: 300px;
height: 300px;
}
#app-image-banner {
width: 400px;
height: 300px;
}
#form-error {
color: red;
}
.application-create-banner-dropzone {
width: 300px;
height: 150px;
border-radius: 5%;
position: relative;
border: dashed #888888 2px;
}
.application-create-banner-dropzone i {
position: absolute;
top: 65px;
left: 145px;
}
.application-create-screenshot-dropzone {
width: 150px;
height: 150px;
margin: 0 5px 0 5px;
border-radius: 10%;
position: relative;
border: dashed #888888 2px;
}
.application-create-screenshot-dropzone i {
position: absolute;
top: 65px;
left: 65px;
}
.application-create-icon-dropzone {
width: 150px;
height: 150px;
border-radius: 10%;
position: relative;
border: dashed #888888 2px;
}
.application-create-icon-dropzone i {
position: absolute;
top: 65px;
left: 65px;
}
#screenshot-container {
max-width: 600px;
display: flex;
overflow-x: auto;
height: 200px;
}
#app-icon-container {
height: 300px;
overflow-x: auto;
}
#modal-body-content {
max-height: 700px;
padding-left: 24px;
overflow-y: auto;
}
.custom-footer {
justify-content: inherit !important;
margin: 0 !important;
}
.footer-main-btn {
display: flex;
justify-content: flex-end;
}
#img-btn-screenshot {
margin: 0 5px 0 5px;
}
#app-create-modal {
max-width: 850px;
border-radius: 0% !important;
}
.app-create-modal-header {
background-color: #4353bd;
color: white;
padding: 24px !important;
}
.app-create-modal-content {
padding: 0 !important;
}
#store {
border: none;
border-bottom: solid #BDBDBD 1px;
border-radius: 0px;
width: 200px;
}
#version {
border: none;
border-bottom: solid #BDBDBD 1px;
border-radius: 0px;
width: 200px;
}
#app-release-switch-content {
display: flex;
}
#app-release-switch-label {
position: absolute;
float: left;
}
#app-release-switch {
position: absolute;
right: 10px;
}
.image-sub-title {
font-style: italic;
font-size: 12px;
color: #818181;
}
/* Application View */
#application-view-content {
width: 100%;
}
#application-view-row {
margin: 10px 10px 0 20px;
}
#app-icon {
height: 100px;
width: 100px;
border: solid 1px black;
border-radius: 50%;
}
.app-updated-date {
color: #888888;
}
.app-install-count {
font-style: italic;
}
.app-details-tbl {
outline: none;
border-color: #2196F3;
}
.app-details-tbl tr {
margin: 20px 0 0 0;
}
.app-details-tbl td {
margin-left: 10px;
max-width: 400px;
}
/* Application Edit Base Layout */
#application-edit-header {
height: 40px;
width: 100%;
margin-top: 20px;
margin-bottom: 20px;
font-size: 25px;
}
.application-header-text {
margin: 10px 0px 0px 10px;
}
#save-btn-content {
float: right;
}
#app-save-btn {
border-radius: 0%;
}
.save-btn {
margin: 5px 5px 5px 0px;
height: 70%;
width: 50%;
float: right;
}
.save-btn:hover {
cursor: pointer;
}
/*Tab styling*/
div.tab {
float: left;
border-right: 1px solid #d8d8d8;
height: 100%;
}
/* Style the tab buttons */
div.tab button {
display: block;
background-color: inherit;
color: black;
padding: 15px 16px;
width: 100%;
border: none;
outline: none;
text-align: left;
cursor: pointer;
transition: 0.3s;
}
/* Change background color of buttons on hover */
div.tab button:hover {
background-color: #ddd6d7;
cursor: pointer;
}
/* Create an active/current "tab button" class */
div.tab button.active {
background-color: #1b3bcc;
color: white;
}
#application-edit-main-container {
display: flex;
}
#application-edit-outer-content {
height: auto;
}
#app-edit-content {
height: 100%;
position: relative;
}
.back-to-app {
position: absolute;
height: 50px;
width: 50px;
border-radius: 50%;
}
.back-to-app i {
padding: 12px 10px 10px 12px;
}
.back-to-app:hover {
cursor: pointer;
background-color: #dedede;
transition: .5s;
}
/* Create Release and Release management */
.release-header {
margin-top: 20px;
margin-bottom: 20px;
}
.release-create {
height: 150px;
margin-bottom: 20px;
}
.release-detail-content {
width: 100%;
margin-top: 20%;
height: 300px;
}
.form-btn {
float: right;
margin-bottom: 10px;
}
.release-content {
height: 180px;
width: 95%;
border: dashed 1px #626262;
border-radius: 2%;
position: relative;
background-color: #e8e8e8;
}
.release-content:after {
content: "";
letter-spacing: 4px;
}
.release {
margin: 30px 10px 20px 30px;
}
.no-release-content {
position: absolute;
margin-top: 10px;
left: 40%;
}
.button-add:hover {
cursor: pointer;
}
.release-inner {
margin-top: 5%;
}
/* Application Edit General Info */
.app-edit-general-info {
margin-top: 20px;
max-width: 100%;
}
.save-info {
float: right;
margin-bottom: 10px;
}
.app-view-field {
font-family: Roboto-Medium;
font-size: 14px;
}
.app-view-text {
font-family: Roboto-Regular;
font-size: 14px;
}
/* Platform Specific Styles. */
#platform-listing {
margin: 10px;
}
.create-platform i {
margin-right: 10px;
}
#platform-list {
margin-top: 20px;
display: flex;
flex-flow: wrap;
}
.platform-content {
margin: 10px;
padding-top: 16px;
box-shadow: 2px 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19);
}
.platform-content .row {
margin: 0;
}
.platform-content .col {
padding: 0;
}
.platform-content-basic {
padding: 0 16px 0 16px;
display: flex;
}
.platform-content-more-outer {
}
.platform-content-more {
padding: 16px 16px 24px 16px;
}
.platform-content-footer {
display: flex;
padding: 8px 8px 8px 8px;
}
.platform-text-container {
padding: 8px 16px 0 16px;
}
.circle-button {
float: right;
}
.platform-icon-letter {
text-align: center;
text-transform: uppercase;
font-family: Roboto-Medium;
font-size: 70px;
color: white;
padding-top: 15px;
}
.platform-icon-container {
height: 120px;
width: 120px;
background-color: #01579B;
box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.16), 0 0 0 1px rgba(0, 0, 0, 0.08) !important;
-webkit-box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.16), 0 0 0 1px rgba(0, 0, 0, 0.08) !important;
}
.platform-property-container {
padding-top: 20px;
font-family: Roboto-Regular;
font-size: 14px;
}
.platform-property-row {
align-items: center;
}
.circle-btn-clear {
background-color: white !important;
color: rgba(0, 0, 0, 0.50) !important;
}
.circle-btn-clear:hover {
background-color: white !important;
color: rgba(0, 0, 0, 0.38) !important;
}
.circle-btn-clear:focus {
background-color: white !important;
color: rgba(0, 0, 0, 0.60) !important;
}
.data-table-row-cell {
padding-top: 14px;
}
.error-code {
text-align: center;
font-family: Roboto-Medium;
font-weight: 800;
font-size: 15em;
color: #BaBaBa;
}
.error-code p {
}
.error-text {
text-align: center;
font-family: Roboto-Regular;
font-size: 14px;
font-weight: 500;
color: #9e9e9e;
}
.circle-btn-add {
background-color: #bababa !important;
border-radius: 50% !important;
height: 30px !important;
width: 30px;
text-align: -webkit-center;
font-size: 18px;
padding: 6px !important;
}
.circle-btn-add:hover {
background-color: #828282 !important;
}
/**
If you need to change the color of active steps in stepper,
uncomment the following and set the background color and font color as needed.
*/
/*
.stepper-active-index {
background-color: #0a6eff !important;
color: white !important;
}
.stepper-passed-index {
background-color: #0a6eff !important;
color: green !important;
}
*/

View File

@ -1,51 +0,0 @@
/*
* Copyright (c) 2020, Entgra (pvt) Ltd. (http://entgra.io) All Rights Reserved.
*
* Entgra (pvt) Ltd. licenses this file to you 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.
*/
.ant-upload.ant-upload-drag {
height: 170px;
}
.release .release-icon {
margin-right: 15px;
}
.release .release-icon img {
width: 100%;
border-radius: 28%;
}
.release .release-title {
margin-left: 15px;
}
.release .release-screenshot img {
width: 100%;
border-radius: 15px;
padding: 5px;
}
.main-container {
background: #f0f2f5;
min-height: 780px
}
@media only screen and (min-width: 768px) {
.main-container {
padding: 24px;
}
}

View File

@ -1,173 +0,0 @@
/*
* Copyright (c) 2020, Entgra (pvt) Ltd. (http://entgra.io) All Rights Reserved.
*
* Entgra (pvt) Ltd. licenses this file to you 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.
*/
import React from 'react';
import 'antd/dist/antd.less';
import RouteWithSubRoutes from './components/RouteWithSubRoutes';
import { BrowserRouter as Router, Redirect, Switch } from 'react-router-dom';
import axios from 'axios';
import { Layout, Spin, Result, notification } from 'antd';
import ConfigContext from './components/ConfigContext';
const { Content } = Layout;
const loadingView = (
<Layout>
<Content
style={{
padding: '0 0',
paddingTop: 300,
backgroundColor: '#fff',
textAlign: 'center',
}}
>
<Spin tip="Loading..." />
</Content>
</Layout>
);
const errorView = (
<Result
style={{
paddingTop: 200,
}}
status="500"
title="Error occurred while loading the configuration"
subTitle="Please refresh your browser window"
/>
);
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
loading: true,
error: false,
config: {},
};
}
componentDidMount() {
axios
.get(window.location.origin + '/entgra/public/conf/config.json')
.then(res => {
const config = res.data;
this.checkUserLoggedIn(config);
})
.catch(error => {
this.setState({
loading: false,
error: true,
});
});
}
checkUserLoggedIn = config => {
axios
.post(
window.location.origin + '/entgra-ui-request-handler/user',
'platform=entgra',
)
.then(res => {
config.user = res.data.data;
const pageURL = window.location.pathname;
const lastURLSegment = pageURL.substr(pageURL.lastIndexOf('/') + 1);
if (lastURLSegment === 'login') {
window.location.href = window.location.origin + '/entgra/';
} else {
this.getDeviceTypes(config);
}
})
.catch(error => {
if (error.hasOwnProperty('response') && error.response.status === 401) {
const redirectUrl = encodeURI(window.location.href);
const pageURL = window.location.pathname;
const lastURLSegment = pageURL.substr(pageURL.lastIndexOf('/') + 1);
if (lastURLSegment !== 'login') {
window.location.href =
window.location.origin + `/entgra/login?redirect=${redirectUrl}`;
} else {
this.setState({
loading: false,
config: config,
});
}
} else {
this.setState({
loading: false,
error: true,
});
}
});
};
getDeviceTypes = config => {
axios
.get(
window.location.origin +
'/entgra-ui-request-handler/invoke/device-mgt/v1.0/device-types',
)
.then(res => {
config.deviceTypes = JSON.parse(res.data.data);
config.supportedOStypes = [];
config.deviceTypes.forEach(type => {
if (['ios', 'android'].includes(type.name)) {
config.supportedOStypes.push(type);
}
});
this.setState({
config: config,
loading: false,
});
})
.catch(error => {
notification.error({
message: 'There was a problem',
duration: 0,
description: 'Error occurred while trying to load device types.',
});
});
};
render() {
const { loading, error } = this.state;
const applicationView = (
<Router>
<ConfigContext.Provider value={this.state.config}>
<div>
<Switch>
<Redirect exact from="/entgra" to="/entgra/devices" />
{this.props.routes.map(route => (
<RouteWithSubRoutes key={route.path} {...route} />
))}
</Switch>
</div>
</ConfigContext.Provider>
</Router>
);
return (
<div>
{loading && loadingView}
{!loading && !error && applicationView}
{error && errorView}
</div>
);
}
}
export default App;

View File

@ -1,27 +0,0 @@
/*
* Copyright (c) 2020, Entgra (pvt) Ltd. (http://entgra.io) All Rights Reserved.
*
* Entgra (pvt) Ltd. licenses this file to you 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.
*/
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
it('renders without crashing', () => {
const div = document.createElement('div');
ReactDOM.render(<App />, div);
ReactDOM.unmountComponentAtNode(div);
});

View File

@ -1,34 +0,0 @@
/*
* Copyright (c) 2020, Entgra (pvt) Ltd. (http://entgra.io) All Rights Reserved.
*
* Entgra (pvt) Ltd. licenses this file to you 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.
*/
import React from 'react';
const ConfigContext = React.createContext();
export const withConfigContext = Component => {
// eslint-disable-next-line react/display-name
return props => (
<ConfigContext.Consumer>
{context => {
return <Component {...props} context={context} />;
}}
</ConfigContext.Consumer>
);
};
export default ConfigContext;

View File

@ -1,44 +0,0 @@
/*
* Copyright (c) 2020, Entgra (pvt) Ltd. (http://entgra.io) All Rights Reserved.
*
* Entgra (pvt) Ltd. licenses this file to you 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.
*/
import React from 'react';
import { Route } from 'react-router-dom';
class RouteWithSubRoutes extends React.Component {
props;
constructor(props) {
super(props);
this.props = props;
}
render() {
return (
<Route
path={this.props.path}
exact={this.props.exact}
render={props => (
<this.props.component
{...props}
{...this.props}
routes={this.props.routes}
/>
)}
/>
);
}
}
export default RouteWithSubRoutes;

View File

@ -1,45 +0,0 @@
/*
* Copyright (c) 2020, Entgra (pvt) Ltd. (http://entgra.io) All Rights Reserved.
*
* Entgra (pvt) Ltd. licenses this file to you 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.
*/
.App {
padding: 20px;
}
.ant-layout-header{
padding: 0;
height: auto;
box-shadow: 0 2px 8px #f0f1f2;
}
.steps-content {
margin-top: 16px;
border: 1px dashed #e9e9e9;
border-radius: 6px;
background-color: #fafafa;
min-height: 200px;
text-align: center;
padding-top: 80px;
}
.steps-action {
margin-top: 24px;
}
.ant-input-affix-wrapper .ant-input{
min-height: 0;
}

View File

@ -1,28 +0,0 @@
<!--
~ Copyright (c) 2020, Entgra (pvt) Ltd. (http://entgra.io) All Rights Reserved.
~
~ Entgra (pvt) Ltd. licenses this file to you 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.
-->
<!doctype html>
<html>
<head>
<meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=0"/>
<meta charset="UTF-8"/>
<title>Entgra Device Management</title>
<link rel='stylesheet' href='https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.3.3/leaflet.css'>
</head>
<div id="root"></div>
</html>

View File

@ -1,131 +0,0 @@
/*
* Copyright (c) 2020, Entgra (pvt) Ltd. (http://entgra.io) All Rights Reserved.
*
* Entgra (pvt) Ltd. licenses this file to you 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.
*/
import React from 'react';
import ReactDOM from 'react-dom';
import * as serviceWorker from './services/serviceWorkers/serviceWorker';
import App from './App';
import Login from './scenes/Login';
import Home from './scenes/Home';
import './index.css';
import Geo from './scenes/Home/scenes/Geo';
import Notifications from './scenes/Home/scenes/Notifications';
import DeviceEnroll from './scenes/Home/scenes/Devices/scenes/Enroll';
import Groups from './scenes/Home/scenes/Groups';
import Users from './scenes/Home/scenes/Users';
import Policies from './scenes/Home/scenes/Policies';
import AddNewPolicy from './scenes/Home/scenes/Policies/scenes/AddNewPolicy';
import Roles from './scenes/Home/scenes/Roles';
import DeviceTypes from './scenes/Home/scenes/DeviceTypes';
import Certificates from './scenes/Home/scenes/Configurations/scenes/Certificates';
import Devices from './scenes/Home/scenes/Devices';
import ViewPolicy from './scenes/Home/scenes/Policies/scenes/ViewPolicy';
import EditSelectedPolicy from './scenes/Home/scenes/Policies/scenes/EditSelectedPolicy';
import DeviceLocations from './scenes/Home/scenes/DeviceLocations';
const routes = [
{
path: '/entgra/login',
exact: true,
component: Login,
},
{
path: '/entgra',
exact: false,
component: Home,
routes: [
{
path: '/entgra/devices',
component: Devices,
exact: true,
},
{
path: '/entgra/devices/enroll',
component: DeviceEnroll,
exact: true,
},
{
path: '/entgra/geo/history/:deviceType/:deviceIdentifier',
component: Geo,
exact: true,
},
{
path: '/entgra/groups',
component: Groups,
exact: true,
},
{
path: '/entgra/users',
component: Users,
exact: true,
},
{
path: '/entgra/policies',
component: Policies,
exact: true,
},
{
path: '/entgra/policy/add',
component: AddNewPolicy,
exact: true,
},
{
path: '/entgra/policy/view/:policyId',
component: ViewPolicy,
exact: true,
},
{
path: '/entgra/policy/edit/:policyId',
component: EditSelectedPolicy,
exact: true,
},
{
path: '/entgra/roles',
component: Roles,
exact: true,
},
{
path: '/entgra/devicetypes',
component: DeviceTypes,
exact: true,
},
{
path: '/entgra/configurations/certificates',
component: Certificates,
exact: true,
},
{
path: '/entgra/notifications',
component: Notifications,
exact: true,
},
{
path: '/entgra/device-locations',
component: DeviceLocations,
exact: true,
},
],
},
];
ReactDOM.render(<App routes={routes} />, document.getElementById('root'));
// If you want your app e and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls.
// Learn more about service workers: https://bit.ly/CRA-PWA
serviceWorker.unregister();

View File

@ -1,260 +0,0 @@
/*
* Copyright (c) 2020, Entgra (pvt) Ltd. (http://entgra.io) All Rights Reserved.
*
* Entgra (pvt) Ltd. licenses this file to you 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.
*/
import React from 'react';
import axios from 'axios';
import { Icon, message, notification, Table, Tag, Tooltip } from 'antd';
import TimeAgo from 'javascript-time-ago';
// Load locale-specific relative date/time formatting rules.
import en from 'javascript-time-ago/locale/en';
import { withConfigContext } from '../../../../components/ConfigContext';
let config = null;
const columns = [
{
title: 'Device',
dataIndex: 'name',
width: 100,
},
{
title: 'Type',
dataIndex: 'type',
key: 'type',
// eslint-disable-next-line react/display-name
render: type => {
const defaultPlatformIcons = config.defaultPlatformIcons;
let icon = defaultPlatformIcons.default.icon;
let color = defaultPlatformIcons.default.color;
let theme = defaultPlatformIcons.default.theme;
if (defaultPlatformIcons.hasOwnProperty(type)) {
icon = defaultPlatformIcons[type].icon;
color = defaultPlatformIcons[type].color;
theme = defaultPlatformIcons[type].theme;
}
return (
<span style={{ fontSize: 20, color: color, textAlign: 'center' }}>
<Icon type={icon} theme={theme} />
</span>
);
},
// todo add filtering options
},
{
title: 'Owner',
dataIndex: 'enrolmentInfo',
key: 'owner',
render: enrolmentInfo => enrolmentInfo.owner,
// todo add filtering options
},
{
title: 'Ownership',
dataIndex: 'enrolmentInfo',
key: 'ownership',
render: enrolmentInfo => enrolmentInfo.ownership,
// todo add filtering options
},
{
title: 'Status',
dataIndex: 'enrolmentInfo',
key: 'status',
// eslint-disable-next-line react/display-name
render: enrolmentInfo => {
const status = enrolmentInfo.status.toLowerCase();
let color = '#f9ca24';
switch (status) {
case 'active':
color = '#badc58';
break;
case 'created':
color = '#6ab04c';
break;
case 'removed':
color = '#ff7979';
break;
case 'inactive':
color = '#f9ca24';
break;
case 'blocked':
color = '#636e72';
break;
}
return <Tag color={color}>{status}</Tag>;
},
// todo add filtering options
},
{
title: 'Last Updated',
dataIndex: 'enrolmentInfo',
key: 'dateOfLastUpdate',
// eslint-disable-next-line react/display-name
render: data => {
const { dateOfLastUpdate } = data;
const timeAgoString = getTimeAgo(dateOfLastUpdate);
return (
<Tooltip title={new Date(dateOfLastUpdate).toString()}>
{timeAgoString}
</Tooltip>
);
},
// todo add filtering options
},
];
const getTimeAgo = time => {
const timeAgo = new TimeAgo('en-US');
return timeAgo.format(time);
};
class deviceTable extends React.Component {
constructor(props) {
super(props);
config = this.props.context;
TimeAgo.addLocale(en);
this.state = {
data: [],
pagination: {},
loading: false,
selectedRows: [],
paramsObj: {},
};
}
rowSelection = {
onChange: (selectedRowKeys, selectedRows) => {
this.setState({
selectedRows: selectedRows,
});
},
};
componentDidMount() {
if (this.props.apiUrl) {
this.fetch();
}
}
// Rerender component when parameters change
componentDidUpdate(prevProps, prevState, snapshot) {
if (prevProps.apiUrl !== this.props.apiUrl) {
this.fetch();
}
}
// fetch data from api
fetch = (params = {}) => {
this.setState({ loading: true });
// get current page
const currentPage = params.hasOwnProperty('page') ? params.page : 1;
const extraParams = {
offset: 10 * (currentPage - 1), // calculate the offset
limit: 10,
};
const encodedExtraParams = Object.keys(extraParams)
.map(key => key + '=' + extraParams[key])
.join('&');
// send request to the invokerss
axios
.get(this.props.apiUrl + encodedExtraParams)
.then(res => {
if (res.status === 200) {
const pagination = { ...this.state.pagination };
if (
res.data.data.devices.length &&
res.data.data.devices[0].hasOwnProperty('deviceInfo')
) {
columns.push({
title: 'OS Version',
dataIndex: 'deviceInfo',
key: 'osVersion',
render: deviceInfo => deviceInfo.osVersion,
});
}
this.setState({
loading: false,
data: res.data.data,
pagination,
});
}
})
.catch(error => {
if (error.hasOwnProperty('response') && error.response.status === 401) {
// todo display a popop with error
message.error('You are not logged in');
window.location.href = window.location.origin + '/entgra/login';
} else {
notification.error({
message: 'There was a problem',
duration: 0,
description: 'Error occurred while trying to load devices.',
});
}
this.setState({ loading: false });
});
};
handleTableChange = (pagination, filters, sorter) => {
const pager = { ...this.state.pagination };
pager.current = pagination.current;
this.setState({
pagination: pager,
});
this.fetch({
results: pagination.pageSize,
page: pagination.current,
sortField: sorter.field,
sortOrder: sorter.order,
...filters,
});
};
render() {
const { data, pagination, loading } = this.state;
return (
<div>
<Table
columns={columns}
rowKey={record =>
record.deviceIdentifier +
record.enrolmentInfo.owner +
record.enrolmentInfo.ownership
}
dataSource={data.devices}
pagination={{
...pagination,
size: 'small',
total: data.count,
showTotal: (total, range) =>
`showing ${range[0]}-${range[1]} of ${total} devices`,
}}
loading={loading}
onChange={this.handleTableChange}
rowSelection={this.rowSelection}
/>
</div>
);
}
}
export default withConfigContext(deviceTable);

View File

@ -1,38 +0,0 @@
import React from 'react';
import { Form, Input, Button } from 'antd';
class Filter extends React.Component {
handleSubmit = e => {
e.preventDefault();
this.props.form.validateFields((err, values) => {
if (!err) {
Object.keys(values).forEach(
key => values[key] === undefined && delete values[key],
);
this.props.callback({}, values);
}
});
};
render() {
const { getFieldDecorator } = this.props.form;
return (
<Form layout="inline" onSubmit={this.handleSubmit}>
{this.props.fields.map(field => (
<Form.Item key={field.name}>
{getFieldDecorator(field.name)(
<Input placeholder={field.placeholder} />,
)}
</Form.Item>
))}
<Form.Item>
<Button htmlType="submit" shape="circle" icon="search" />
</Form.Item>
</Form>
);
}
}
export default Form.create({ name: 'horizontal_login' })(Filter);

View File

@ -1,81 +0,0 @@
/*
* Copyright (c) 2020, Entgra (pvt) Ltd. (http://entgra.io) All Rights Reserved.
*
* Entgra (pvt) Ltd. licenses this file to you 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.
*/
import React from 'react';
import { notification, Menu, Icon } from 'antd';
import axios from 'axios';
import { withConfigContext } from '../../../../components/ConfigContext';
/*
This class for call the logout api by sending request
*/
class Logout extends React.Component {
constructor(props) {
super(props);
this.state = {
inValid: false,
loading: false,
};
}
/*
This function call the logout api when the request is success
*/
handleSubmit = () => {
const thisForm = this;
const config = this.props.context;
thisForm.setState({
inValid: false,
});
axios
.post(window.location.origin + config.serverConfig.logoutUri)
.then(res => {
// if the api call status is correct then user will logout and then it goes to login page
if (res.status === 200) {
window.location = window.location.origin + '/entgra/login';
}
})
.catch(function(error) {
if (error.hasOwnProperty('response') && error.response.status === 400) {
thisForm.setState({
inValid: true,
});
} else {
notification.error({
message: 'There was a problem',
duration: 0,
description: 'Error occurred while trying to logout.',
});
}
});
};
render() {
return (
<Menu>
<Menu.Item key="1" onClick={this.handleSubmit}>
<Icon type="logout" />
Logout
</Menu.Item>
</Menu>
);
}
}
export default withConfigContext(Logout);

View File

@ -1,194 +0,0 @@
/*
* Copyright (c) 2020, Entgra (pvt) Ltd. (http://entgra.io) All Rights Reserved.
*
* Entgra (pvt) Ltd. licenses this file to you 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.
*/
import React from 'react';
import { Layout, Menu, Icon } from 'antd';
import { Switch, Link } from 'react-router-dom';
import RouteWithSubRoutes from '../../components/RouteWithSubRoutes';
import './styles.css';
import { withConfigContext } from '../../components/ConfigContext';
import Logout from './components/Logout';
const { Header, Content, Footer } = Layout;
const { SubMenu } = Menu;
class Home extends React.Component {
constructor(props) {
super(props);
const mobileWidth = window.innerWidth <= 768 ? '0' : '80';
this.state = {
routes: props.routes,
selectedKeys: [],
deviceTypes: [],
isNavBarCollapsed: false,
mobileWidth,
};
this.logo = this.props.context.theme.logo;
this.config = this.props.context;
}
toggle = () => {
console.log(this.config);
this.setState({
isNavBarCollapsed: !this.state.isNavBarCollapsed,
});
};
render() {
return (
<div>
<Layout className="layout">
<Layout>
<Header style={{ background: '#fff', padding: 0 }}>
<div className="logo-image">
<Link to="/entgra">
<img alt="logo" src={this.logo} />
</Link>
</div>
<Menu
theme="light"
mode="horizontal"
style={{
lineHeight: '64px',
marginRight: 110,
}}
>
<SubMenu
key="devices"
title={
<span>
<Icon type="appstore" />
<span>Devices</span>
</span>
}
>
<Menu.Item key="devices">
<Link to="/entgra/devices">
<span>View</span>
</Link>
</Menu.Item>
<Menu.Item key="deviceEnroll">
<Link to="/entgra/devices/enroll">
<span>Enroll</span>
</Link>
</Menu.Item>
</SubMenu>
<Menu.Item key="groups">
<Link to="/entgra/groups">
<Icon type="deployment-unit" />
<span>Groups</span>
</Link>
</Menu.Item>
<Menu.Item key="users">
<Link to="/entgra/users">
<Icon type="user" />
<span>Users</span>
</Link>
</Menu.Item>
<SubMenu
key="policies"
title={
<span>
<Icon type="audit" />
<span>Policies</span>
</span>
}
>
<Menu.Item key="policiesList">
<Link to="/entgra/policies">
<span>View</span>
</Link>
</Menu.Item>
<Menu.Item key="addPolicy">
<Link to="/entgra/policy/add">
<span>Add New Policy</span>
</Link>
</Menu.Item>
</SubMenu>
<Menu.Item key="roles">
<Link to="/entgra/roles">
<Icon type="book" />
<span>Roles</span>
</Link>
</Menu.Item>
<Menu.Item key="devicetypes">
<Link to="/entgra/devicetypes">
<Icon type="desktop" />
<span>Device Types</span>
</Link>
</Menu.Item>
<SubMenu
key="configurations"
title={
<span>
<Icon type="setting" />
<span>Configurations</span>
</span>
}
>
<Menu.Item key="certificates">
<Link to="/entgra/configurations/certificates">
<span>Certificates</span>
</Link>
</Menu.Item>
</SubMenu>
<Menu.Item key="deviceLocations">
<Link to="/entgra/device-locations">
<Icon type="environment" />
<span>Device Location</span>
</Link>
</Menu.Item>
<Menu.Item className="profile" key="Notifications">
<Link to="/entgra/notifications">
<span>Notifications</span>
</Link>
</Menu.Item>
<SubMenu
className="profile"
title={
<span className="submenu-title-wrapper">
<Icon type="user" />
{this.config.user}
</span>
}
>
<Logout />
</SubMenu>
</Menu>
</Header>
<Content>
<Switch>
{this.state.routes.map(route => (
<RouteWithSubRoutes key={route.path} {...route} />
))}
</Switch>
</Content>
<Footer style={{ textAlign: 'center' }}>©2019 entgra.io</Footer>
</Layout>
</Layout>
</div>
);
}
}
export default withConfigContext(Home);

View File

@ -1,262 +0,0 @@
/*
* Copyright (c) 2020, Entgra (pvt) Ltd. (http://entgra.io) All Rights Reserved.
*
* Entgra (pvt) Ltd. licenses this file to you 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.
*/
import React from 'react';
import axios from 'axios';
import {
Icon,
message,
notification,
Popconfirm,
Table,
Tooltip,
Typography,
} from 'antd';
import TimeAgo from 'javascript-time-ago';
// Load locale-specific relative date/time formatting rules.
import en from 'javascript-time-ago/locale/en';
import { withConfigContext } from '../../../../../../../../components/ConfigContext';
import Moment from 'react-moment';
const { Paragraph, Text } = Typography;
let config = null;
class CertificateTable extends React.Component {
constructor(props) {
super(props);
config = this.props.context;
TimeAgo.addLocale(en);
this.state = {
data: [],
pagination: {},
loading: false,
};
}
componentDidMount() {
this.fetch();
}
// fetch data from api
fetch = (params = {}) => {
this.setState({ loading: true });
// get current page
const currentPage = params.hasOwnProperty('page') ? params.page : 1;
const extraParams = {
offset: 10 * (currentPage - 1), // calculate the offset
limit: 10,
};
const encodedExtraParams = Object.keys(extraParams)
.map(key => key + '=' + extraParams[key])
.join('&');
// send request to the invoker
axios
.get(
window.location.origin +
config.serverConfig.invoker.uri +
'/certificate-mgt/v1.0/admin/certificates' +
encodedExtraParams,
)
.then(res => {
if (res.status === 200) {
const pagination = { ...this.state.pagination };
this.setState({
loading: false,
data: res.data.data.certificates,
pagination,
});
}
})
.catch(error => {
if (error.hasOwnProperty('response') && error.response.status === 401) {
// todo display a popop with error
message.error('You are not logged in');
window.location.href = window.location.origin + '/entgra/login';
} else {
notification.error({
message: 'There was a problem',
duration: 0,
description: 'Error occurred while trying to load devices.',
});
}
this.setState({ loading: false });
});
};
handleTableChange = (pagination, filters, sorter) => {
const pager = { ...this.state.pagination };
pager.current = pagination.current;
this.setState({
pagination: pager,
});
this.fetch({
results: pagination.pageSize,
page: pagination.current,
sortField: sorter.field,
sortOrder: sorter.order,
...filters,
});
};
deleteCertificate = serialNumber => {
axios
.delete(
window.location.origin +
config.serverConfig.invoker.uri +
'/certificate-mgt/v1.0/admin/certificates/' +
serialNumber,
{ headers: { 'Content-Type': 'application/json' } },
)
.then(res => {
if (res.status === 200) {
this.fetch();
notification.success({
message: 'Done',
duration: 4,
description: 'Successfully deleted the certificate.',
});
}
})
.catch(error => {
if (error.hasOwnProperty('response') && error.response.status === 401) {
// todo display a popop with error
message.error('You are not logged in');
window.location.href = window.location.origin + '/entgra/login';
} else {
notification.error({
message: 'There was a problem',
duration: 0,
description:
'Error occurred while trying to delete the certificate.',
});
}
});
};
columns = [
{
title: 'Serial Number',
dataIndex: 'serialNumber',
},
{
title: 'Username',
dataIndex: 'username',
},
{
title: 'Certificate Version',
dataIndex: 'certificateVersion',
},
{
title: 'Certificate Serial',
dataIndex: 'certificateserial',
},
{
title: 'Not Before',
dataIndex: 'notBefore',
render: notBefore => (
<Moment format="YYYY/MM/DD HH:mm" date={notBefore} />
),
},
{
title: 'Not After',
dataIndex: 'notAfter',
render: notAfter => <Moment format="YYYY/MM/DD HH:mm" date={notAfter} />,
},
{
title: 'Subject',
dataIndex: 'subject',
render: subject => (
<Paragraph
style={{ marginBottom: 0 }}
ellipsis={{ rows: 1, expandable: true }}
>
{subject}
</Paragraph>
),
},
{
title: 'Issuer',
dataIndex: 'issuer',
render: issuer => (
<Paragraph
style={{ marginBottom: 0 }}
ellipsis={{ rows: 1, expandable: true }}
>
{issuer}
</Paragraph>
),
},
{
title: 'Actions',
key: 'actions',
dataIndex: 'serialNumber',
render: serialNumber => (
<Tooltip placement="bottom" title={'Remove User'}>
<Popconfirm
placement="top"
title={'Are you sure?'}
onConfirm={() => {
this.deleteCertificate(serialNumber);
}}
okText="Ok"
cancelText="Cancel"
>
<a>
<Text type="danger">
<Icon type="delete" />
</Text>
</a>
</Popconfirm>
</Tooltip>
),
},
];
render() {
const { data, pagination, loading } = this.state;
return (
<div>
<div>
<Table
columns={this.columns}
rowKey={record => record.serialNumber}
dataSource={data}
pagination={{
...pagination,
size: 'small',
// position: "top",
showTotal: (total, range) =>
`showing ${range[0]}-${range[1]} of ${total} devices`,
// showQuickJumper: true
}}
loading={loading}
onChange={this.handleTableChange}
/>
</div>
</div>
);
}
}
export default withConfigContext(CertificateTable);

View File

@ -1,63 +0,0 @@
/*
* Copyright (c) 2020, Entgra (pvt) Ltd. (http://entgra.io) All Rights Reserved.
*
* Entgra (pvt) Ltd. licenses this file to you 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.
*/
import React from 'react';
import { PageHeader, Typography, Breadcrumb, Icon } from 'antd';
import { Link } from 'react-router-dom';
import CertificateTable from './components/CertificateTable';
const { Paragraph } = Typography;
class Certificates extends React.Component {
routes;
constructor(props) {
super(props);
this.routes = props.routes;
}
render() {
return (
<div>
<PageHeader style={{ paddingTop: 0 }}>
<Breadcrumb style={{ paddingBottom: 16 }}>
<Breadcrumb.Item>
<Link to="/entgra/devices">
<Icon type="home" /> Home
</Link>
</Breadcrumb.Item>
<Breadcrumb.Item>Configurations</Breadcrumb.Item>
<Breadcrumb.Item>Certificates</Breadcrumb.Item>
</Breadcrumb>
<div className="wrap">
<h3>Certificates</h3>
<Paragraph>Certificate configurations</Paragraph>
</div>
<div style={{ backgroundColor: '#ffffff', borderRadius: 5 }}>
<CertificateTable />
</div>
</PageHeader>
<div
style={{ background: '#f0f2f5', padding: 24, minHeight: 720 }}
></div>
</div>
);
}
}
export default Certificates;

View File

@ -1,254 +0,0 @@
/*
* Copyright (c) 2020, Entgra (pvt) Ltd. (http://entgra.io) All Rights Reserved.
*
* Entgra (pvt) Ltd. licenses this file to you 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.
*/
import React, { Component } from 'react';
import { Map, TileLayer, Marker, Popup, CircleMarker } from 'react-leaflet';
import L from 'leaflet';
import { withConfigContext } from '../../../../../../components/ConfigContext';
import axios from 'axios';
import { message, notification } from 'antd';
// const { Title, Text } = Typography;
// Icons for the location markers
const pinStart = new L.Icon({
iconUrl: require('./pin_start.svg'),
iconRetinaUrl: require('./pin_start.svg'),
shadowUrl: require('./pin_shadow.svg'),
shadowSize: new L.Point(51, 51),
shadowAnchor: [22, 22],
popupAnchor: [0, -22],
iconSize: new L.Point(50, 50),
tooltipAnchor: [0, -22],
});
class DeviceLocationMap extends Component {
constructor(props) {
super(props);
this.config = this.props.context;
this.customMap = React.createRef();
this.state = {
defaultZoomLevel: 3,
locations: [],
deviceData: {},
minLatitute: -37.43997405227057,
maxLatitute: 37.43997405227057,
minLongtitute: -151.171875,
maxLongtitute: 151.34765625,
zoomLevel: 3,
deviceName: null,
isPopup: false,
};
}
componentDidMount() {
this.getDeviceLocations(
-37.43997405227057,
37.43997405227057,
-151.171875,
151.34765625,
3,
);
}
getDeviceLocations = (
minLatitute,
maxLatitute,
minLongtitute,
maxLongtitute,
zoomLevel,
) => {
axios
.get(
window.location.origin +
this.config.serverConfig.invoker.uri +
this.config.serverConfig.invoker.deviceMgt +
'/geo-services/1.0.0/stats' +
'/device-locations?' +
'minLat=' +
minLatitute +
'&maxLat=' +
maxLatitute +
'&minLong=' +
minLongtitute +
'&maxLong=' +
maxLongtitute +
'&zoom=' +
zoomLevel,
)
.then(res => {
if (res.status === 200) {
// console.log(JSON.parse(res.data.data));
this.setState({
locations: JSON.parse(res.data.data),
});
}
})
.catch(error => {
if (error.hasOwnProperty('response') && error.response.status === 401) {
message.error('You are not logged in');
window.location.href = window.location.origin + '/entgra/login';
} else {
notification.error({
message: 'There was a problem',
duration: 0,
description: 'Error occurred while trying to fetch locations......',
});
}
});
};
setMarkers = positions => {
let markers = [];
positions.map(location => {
if (location.count > 1) {
markers.push(
<CircleMarker
key={location.deviceIdentification}
center={[
location.coordinates.latitude,
location.coordinates.longitude,
]}
radius={10}
fillOpacity={1}
color={'#c0392b'}
stroke={false}
>
<Popup>{location.count}</Popup>
</CircleMarker>,
);
} else {
markers.push(
<Marker
key={location.deviceIdentification}
icon={pinStart}
position={[
location.coordinates.latitude,
location.coordinates.longitude,
]}
onMouseOver={this
.mouseOver
// location.deviceType,
// location.deviceIdentification,
()}
onMouseOut={this.mouseOut}
>
<Popup
autoClose={true}
style={{ display: this.state.isPopup ? 'block' : 'none' }}
>
{/* {this.getPopupData(*/}
{/* // location.deviceType, // location.deviceIdentification, // )}*/}
{/* {this.state.deviceData.name}*/}
{this.state.deviceName}
</Popup>
</Marker>,
);
}
});
return (
<div>
{markers.map(markker => {
return markker;
})}
</div>
);
};
mouseOut = () => {
this.setState({
isPopup: false,
});
};
// todo get device details when mouse over the maker
mouseOver = (deviceType, deviceIdentification) => {
// let aaa = null;
// axios
// .get(
// window.location.origin +
// this.config.serverConfig.invoker.uri +
// this.config.serverConfig.invoker.deviceMgt +
// `/devices/1.0.0/${deviceType}/${deviceIdentification}`,
// )
// .then(res => {
// if (res.status === 200) {
// // console.log(res.data.data);
// this.setState({
// deviceData: res.data.data,
// isPopup: true,
// });
// // aaa = res.data.data;
// // return (
// // <div>
// // <Title>{aaa.name}</Title>
// // <Row>
// // <Text> Type : {aaa.type}</Text>
// // </Row>
// // <Row>
// // <Text> Status : {aaa.enrolmentInfo.status}</Text>
// // </Row>
// // <Row>
// // <Text> Owner : {aaa.enrolmentInfo.owner}</Text>
// // </Row>
// // </div>
// // );
// }
// })
// .catch(error => {
// if (error.hasOwnProperty('response') && error.response.status === 401) {
// message.error('You are not logged in');
// window.location.href = window.location.origin + '/entgra/login';
// } else {
// notification.error({
// message: 'There was a problem',
// duration: 0,
// description: 'Error occurred while trying to fetch locations......',
// });
// }
// });
};
// Todo : get bound and zoom level when doing movement and pass id to getDeviceLocations function
handleMovements = event => {
console.log(this.customMap.current.leafletElement.getZoom());
};
render() {
const position = [this.state.lat, this.state.lng];
const attribution = this.config.geoMap.attribution;
const url = this.config.geoMap.url;
return (
<div style={{ backgroundColor: '#ffffff', borderRadius: 5, padding: 5 }}>
<Map
ref={this.customMap}
center={position}
zoom={this.state.defaultZoomLevel}
resize={this.onResize}
onMoveEnd={this.handleMovements}
>
<TileLayer url={url} attribution={attribution} />
{this.setMarkers(this.state.locations)}
</Map>
</div>
);
}
}
export default withConfigContext(DeviceLocationMap);

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 69 KiB

View File

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"><defs><style>.a{fill:#414042;}.b{fill:#fff;}</style></defs><title>map</title><circle class="a" cx="50" cy="50" r="40.2134"/><circle class="b" cx="50" cy="50" r="18.64704"/></svg>

Before

Width:  |  Height:  |  Size: 240 B

View File

@ -1,39 +0,0 @@
import React from 'react';
import { Breadcrumb, PageHeader } from 'antd';
import DeviceLocationMap from './Component/DeviceLocationMap';
class DeviceLocations extends React.Component {
routes;
constructor(props) {
super(props);
this.routes = props.routes;
}
render() {
return (
<div>
<PageHeader style={{ paddingTop: 0 }}>
<Breadcrumb style={{ paddingBottom: 16 }}>
<Breadcrumb.Item>Devices Location</Breadcrumb.Item>
</Breadcrumb>
<div className="wrap">
<h3>Devices Location </h3>
</div>
</PageHeader>
<div
style={{
background: '#f0f2f5',
padding: 24,
minHeight: 720,
alignItems: 'center',
}}
>
<DeviceLocationMap />
</div>
</div>
);
}
}
export default DeviceLocations;

View File

@ -1,130 +0,0 @@
/*
* Copyright (c) 2020, Entgra (pvt) Ltd. (http://entgra.io) All Rights Reserved.
*
* Entgra (pvt) Ltd. licenses this file to you 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.
*/
import React from 'react';
import axios from 'axios';
import { Card, Col, Icon, message, notification, Row } from 'antd';
import TimeAgo from 'javascript-time-ago';
// Load locale-specific relative date/time formatting rules.
import en from 'javascript-time-ago/locale/en';
import { withConfigContext } from '../../../../../../components/ConfigContext';
let apiUrl;
class Index extends React.Component {
constructor(props) {
super(props);
TimeAgo.addLocale(en);
this.state = {
data: [],
pagination: {},
loading: false,
selectedRows: [],
};
}
componentDidMount() {
this.fetchUsers();
}
// fetch data from api
fetchUsers = (params = {}) => {
const config = this.props.context;
this.setState({ loading: true });
apiUrl =
window.location.origin +
config.serverConfig.invoker.uri +
config.serverConfig.invoker.deviceMgt +
'/device-types';
// send request to the invokerss
axios
.get(apiUrl)
.then(res => {
if (res.status === 200) {
const pagination = { ...this.state.pagination };
this.setState({
loading: false,
data: JSON.parse(res.data.data),
pagination,
});
}
})
.catch(error => {
if (error.hasOwnProperty('response') && error.response.status === 401) {
// todo display a popop with error
message.error('You are not logged in');
window.location.href = window.location.origin + '/entgra/login';
} else {
notification.error({
message: 'There was a problem',
duration: 0,
description: 'Error occurred while trying to load device types.',
});
}
this.setState({ loading: false });
});
};
handleTableChange = (pagination, filters, sorter) => {
const pager = { ...this.state.pagination };
pager.current = pagination.current;
this.setState({
pagination: pager,
});
this.fetch({
results: pagination.pageSize,
page: pagination.current,
sortField: sorter.field,
sortOrder: sorter.order,
...filters,
});
};
render() {
const { data } = this.state;
const { Meta } = Card;
const itemCard = data.map(data => (
<Col span={5} key={data.id}>
<Card
size="default"
style={{ width: 200 }}
bordered={true}
actions={[
<Icon type="setting" key="setting" />,
<Icon type="edit" key="edit" />,
]}
>
<Meta
avatar={<Icon type="desktop" key="device-types" />}
title={data.name}
/>
</Card>
</Col>
));
return (
<div style={{ background: '#ECECEC', padding: '20px' }}>
<Row gutter={16}>{itemCard}</Row>
</div>
);
}
}
export default withConfigContext(Index);

View File

@ -1,62 +0,0 @@
/*
* Copyright (c) 2020, Entgra (pvt) Ltd. (http://entgra.io) All Rights Reserved.
*
* Entgra (pvt) Ltd. licenses this file to you 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.
*/
import React from 'react';
import { PageHeader, Typography, Breadcrumb, Icon } from 'antd';
import { Link } from 'react-router-dom';
import DeviceTypesTable from './components/DeviceTypesTable';
const { Paragraph } = Typography;
class DeviceTypes extends React.Component {
routes;
constructor(props) {
super(props);
this.routes = props.routes;
}
render() {
return (
<div>
<PageHeader style={{ paddingTop: 0 }}>
<Breadcrumb style={{ paddingBottom: 16 }}>
<Breadcrumb.Item>
<Link to="/entgra">
<Icon type="home" /> Home
</Link>
</Breadcrumb.Item>
<Breadcrumb.Item>Device Types</Breadcrumb.Item>
</Breadcrumb>
<div className="wrap">
<h3>Device Types</h3>
<Paragraph>All device types for device management.</Paragraph>
</div>
<div style={{ backgroundColor: '#ffffff', borderRadius: 5 }}>
<DeviceTypesTable />
</div>
</PageHeader>
<div
style={{ background: '#f0f2f5', padding: 24, minHeight: 720 }}
></div>
</div>
);
}
}
export default DeviceTypes;

View File

@ -1,133 +0,0 @@
/*
* Copyright (c) 2020, Entgra (pvt) Ltd. (http://entgra.io) All Rights Reserved.
*
* Entgra (pvt) Ltd. licenses this file to you 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.
*/
import React from 'react';
import { Button, Tooltip, Popconfirm, Divider } from 'antd';
class BulkActionBar extends React.Component {
constructor(props) {
super(props);
this.state = {
selectedMultiple: false,
selectedSingle: false,
canDelete: true,
};
}
// This method checks whether NON-REMOVED devices are selected
onDeleteDeviceCall = () => {
let tempDeleteState;
for (let i = 0; i < this.props.selectedRows.length; i++) {
if (this.props.selectedRows[i].enrolmentInfo.status != 'REMOVED') {
tempDeleteState = false;
break;
}
tempDeleteState = true;
}
this.setState({ canDelete: tempDeleteState });
};
onConfirmDelete = () => {
if (this.state.canDelete) {
this.props.deleteDevice();
}
};
onConfirmDisenroll = () => {
this.props.disenrollDevice();
};
onDeviceGroupCall = () => {
this.props.getGroups();
};
render() {
const isSelected = this.props.selectedRows.length > 0;
const isSelectedSingle = this.props.selectedRows.length == 1;
return (
<div style={{ display: isSelected ? 'inline' : 'none', padding: '11px' }}>
<Tooltip
placement="bottom"
title={'Delete Device'}
autoAdjustOverflow={true}
>
<Popconfirm
placement="topLeft"
title={
this.state.canDelete
? 'Are you sure you want to delete?'
: 'You can only delete disenrolled devices'
}
onConfirm={this.onConfirmDelete}
okText="Ok"
cancelText="Cancel"
>
<Button
type="link"
shape="circle"
icon="delete"
size={'default'}
onClick={this.onDeleteDeviceCall}
disabled={!isSelected}
style={{ margin: '2px' }}
/>
</Popconfirm>
</Tooltip>
<Divider type="vertical" />
<Tooltip placement="bottom" title={'Disenroll Device'}>
<Popconfirm
placement="topLeft"
title={'Are you sure?'}
onConfirm={this.onConfirmDisenroll}
okText="Ok"
disabled={!isSelectedSingle}
cancelText="Cancel"
>
<Button
type="link"
shape="circle"
icon="close"
size={'default'}
style={{
display: isSelectedSingle ? 'inline' : 'none',
margin: '2px',
}}
/>
</Popconfirm>
</Tooltip>
<Divider
type="vertical"
style={{ display: isSelectedSingle ? 'inline-block' : 'none' }}
/>
<Tooltip placement="bottom" title={'Add to group'}>
<Button
type="link"
shape="circle"
icon="deployment-unit"
size={'default'}
onClick={this.onDeviceGroupCall}
style={{ margin: '2px' }}
/>
</Tooltip>
</div>
);
}
}
export default BulkActionBar;

View File

@ -1,553 +0,0 @@
/*
* Copyright (c) 2020, Entgra (pvt) Ltd. (http://entgra.io) All Rights Reserved.
*
* Entgra (pvt) Ltd. licenses this file to you 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.
*/
import React from 'react';
import axios from 'axios';
import {
Icon,
message,
Modal,
notification,
Select,
Table,
Tag,
Tooltip,
} from 'antd';
import TimeAgo from 'javascript-time-ago';
// Load locale-specific relative date/time formatting rules.
import en from 'javascript-time-ago/locale/en';
import { withConfigContext } from '../../../../../../components/ConfigContext';
import BulkActionBar from './components/BulkActionBar';
import { Link } from 'react-router-dom';
let config = null;
const columns = [
{
title: 'Device',
dataIndex: 'name',
width: 100,
},
{
title: 'Type',
dataIndex: 'type',
key: 'type',
// eslint-disable-next-line react/display-name
render: type => {
const defaultPlatformIcons = config.defaultPlatformIcons;
let icon = defaultPlatformIcons.default.icon;
let color = defaultPlatformIcons.default.color;
let theme = defaultPlatformIcons.default.theme;
if (defaultPlatformIcons.hasOwnProperty(type)) {
icon = defaultPlatformIcons[type].icon;
color = defaultPlatformIcons[type].color;
theme = defaultPlatformIcons[type].theme;
}
return (
<span style={{ fontSize: 20, color: color, textAlign: 'center' }}>
<Icon type={icon} theme={theme} />
</span>
);
},
// todo add filtering options
},
{
title: 'Owner',
dataIndex: 'enrolmentInfo',
key: 'owner',
render: enrolmentInfo => enrolmentInfo.owner,
// todo add filtering options
},
{
title: 'Ownership',
dataIndex: 'enrolmentInfo',
key: 'ownership',
render: enrolmentInfo => enrolmentInfo.ownership,
// todo add filtering options
},
{
title: 'Status',
dataIndex: 'enrolmentInfo',
key: 'status',
// eslint-disable-next-line react/display-name
render: enrolmentInfo => {
const status = enrolmentInfo.status.toLowerCase();
let color = '#f9ca24';
switch (status) {
case 'active':
color = '#badc58';
break;
case 'created':
color = '#6ab04c';
break;
case 'removed':
color = '#ff7979';
break;
case 'inactive':
color = '#f9ca24';
break;
case 'blocked':
color = '#636e72';
break;
}
return <Tag color={color}>{status}</Tag>;
},
// todo add filtering options
},
{
title: 'Last Updated',
dataIndex: 'enrolmentInfo',
key: 'dateOfLastUpdate',
// eslint-disable-next-line react/display-name
render: data => {
const { dateOfLastUpdate } = data;
const timeAgoString = getTimeAgo(dateOfLastUpdate);
return (
<Tooltip title={new Date(dateOfLastUpdate).toString()}>
{timeAgoString}
</Tooltip>
);
},
// todo add filtering options
},
{
title: '',
dataIndex: 'deviceIdentifier',
key: 'actions',
// eslint-disable-next-line react/display-name
render: (data, row) => {
const { type, deviceIdentifier } = row;
return (
<Link to={`/entgra/geo/history/${type}/${deviceIdentifier}`}>
<Icon type="environment" /> Location History
</Link>
);
},
},
];
const getTimeAgo = time => {
const timeAgo = new TimeAgo('en-US');
return timeAgo.format(time);
};
class DeviceTable extends React.Component {
constructor(props) {
super(props);
config = this.props.context;
TimeAgo.addLocale(en);
this.state = {
data: [],
pagination: {},
loading: false,
selectedRows: [],
deviceGroups: [],
groupModalVisible: false,
selectedGroupId: [],
selectedRowKeys: [],
};
}
componentDidMount() {
this.fetch();
}
// fetch data from api
fetch = (params = {}) => {
const config = this.props.context;
this.setState({ loading: true });
// get current page
const currentPage = params.hasOwnProperty('page') ? params.page : 1;
const extraParams = {
offset: 10 * (currentPage - 1), // calculate the offset
limit: 10,
requireDeviceInfo: true,
};
const encodedExtraParams = Object.keys(extraParams)
.map(key => key + '=' + extraParams[key])
.join('&');
// send request to the invoker
axios
.get(
window.location.origin +
config.serverConfig.invoker.uri +
config.serverConfig.invoker.deviceMgt +
'/devices?' +
encodedExtraParams,
)
.then(res => {
if (res.status === 200) {
const pagination = { ...this.state.pagination };
this.setState({
loading: false,
data: res.data.data.devices,
pagination,
});
}
})
.catch(error => {
if (error.hasOwnProperty('response') && error.response.status === 401) {
// todo display a popop with error
message.error('You are not logged in');
window.location.href = window.location.origin + '/entgra/login';
} else {
notification.error({
message: 'There was a problem',
duration: 0,
description: 'Error occurred while trying to load devices.',
});
}
this.setState({ loading: false });
});
};
deleteDevice = () => {
const config = this.props.context;
this.setState({ loading: true });
const deviceData = this.state.selectedRows.map(obj => obj.deviceIdentifier);
// send request to the invoker
axios
.put(
window.location.origin +
config.serverConfig.invoker.uri +
config.serverConfig.invoker.deviceMgt +
'/admin/devices/permanent-delete',
deviceData,
{ headers: { 'Content-Type': 'application/json' } },
)
.then(res => {
if (res.status === 200) {
this.fetch();
}
})
.catch(error => {
if (error.hasOwnProperty('response') && error.response.status === 401) {
// todo display a popop with error
message.error('You are not logged in');
window.location.href = window.location.origin + '/entgra/login';
} else {
notification.error({
message: 'There was a problem',
duration: 0,
description: 'Error occurred while trying to delete devices.',
});
}
this.setState({ loading: false });
});
};
disenrollDevice = () => {
const config = this.props.context;
this.setState({ loading: true });
const deviceData = this.state.selectedRows[0];
// send request to the invoker
axios
.delete(
window.location.origin +
config.serverConfig.invoker.uri +
config.serverConfig.invoker.deviceMgt +
'/devices/type/' +
deviceData.type +
'/id/' +
deviceData.deviceIdentifier,
{ headers: { 'Content-Type': 'application/json' } },
)
.then(res => {
if (res.status === 200) {
this.fetch();
this.setState({
selectedRowKeys: [],
});
notification.success({
message: 'Done',
duration: 4,
description: 'Successfully dis-enrolled the device.',
});
}
})
.catch(error => {
if (error.hasOwnProperty('response') && error.response.status === 401) {
// todo display a popop with error
message.error('You are not logged in');
window.location.href = window.location.origin + '/entgra/login';
} else {
notification.error({
message: 'There was a problem',
duration: 0,
description: 'Error occurred while trying to dis-enroll devices.',
});
}
this.setState({ loading: false });
});
};
addDevicesToGroup = groupId => {
const config = this.props.context;
this.setState({ loading: true });
let apiUrl;
let deviceData;
if (this.state.selectedRows.length === 1) {
apiUrl =
window.location.origin +
config.serverConfig.invoker.uri +
config.serverConfig.invoker.deviceMgt +
'/groups/device/assign';
deviceData = {
deviceIdentifier: {
id: this.state.selectedRows[0].deviceIdentifier,
type: this.state.selectedRows[0].type,
},
deviceGroupIds: groupId,
};
} else if (!groupId[0]) {
apiUrl =
window.location.origin +
config.serverConfig.invoker.uri +
config.serverConfig.invoker.deviceMgt +
'/groups/id/' +
groupId +
'/devices/add';
deviceData = this.state.selectedRows.map(obj => ({
id: obj.deviceIdentifier,
type: obj.type,
}));
} else {
apiUrl =
window.location.origin +
config.serverConfig.invoker.uri +
config.serverConfig.invoker.deviceMgt +
'/groups/id/' +
groupId[0] +
'/devices/add';
deviceData = this.state.selectedRows.map(obj => ({
id: obj.deviceIdentifier,
type: obj.type,
}));
}
// send request to the invoker
axios
.post(apiUrl, deviceData, {
headers: { 'Content-Type': 'application/json' },
})
.then(res => {
if (res.status === 200) {
this.setState({
loading: false,
});
notification.success({
message: 'Done',
duration: 4,
description: 'Successfully added to the device group.',
});
}
})
.catch(error => {
if (error.hasOwnProperty('response') && error.response.status === 401) {
// todo display a popop with error
message.error('You are not logged in');
window.location.href = window.location.origin + '/entgra/login';
} else {
notification.error({
message: 'There was a problem',
duration: 0,
description: 'Error occurred while adding to the device group.',
});
}
this.setState({ loading: false });
});
};
getGroups = () => {
this.setState({
groupModalVisible: true,
});
// send request to the invoker
axios
.get(
window.location.origin +
config.serverConfig.invoker.uri +
config.serverConfig.invoker.deviceMgt +
'/groups',
)
.then(res => {
this.setState({ deviceGroups: res.data.data.deviceGroups });
})
.catch(error => {
if (error.hasOwnProperty('response') && error.response.status === 401) {
// todo display a popop with error
message.error('You are not logged in');
window.location.href = window.location.origin + '/entgra/login';
} else {
notification.error({
message: 'There was a problem',
duration: 0,
description: 'Error occurred while retrieving device groups.',
});
}
this.setState({ loading: false });
});
};
handleOk = e => {
if (this.state.selectedGroupId) {
this.addDevicesToGroup(this.state.selectedGroupId);
this.setState({
groupModalVisible: false,
});
} else {
notification.error({
message: 'There was a problem',
duration: 0,
description: 'Please select a group.',
});
}
};
handleCancel = e => {
this.setState({
groupModalVisible: false,
});
};
onGroupSelectChange = value => {
this.setState({ selectedGroupId: value });
};
handleTableChange = (pagination, filters, sorter) => {
const pager = { ...this.state.pagination };
pager.current = pagination.current;
this.setState({
pagination: pager,
});
this.fetch({
results: pagination.pageSize,
page: pagination.current,
sortField: sorter.field,
sortOrder: sorter.order,
...filters,
});
};
onSelectChange = (selectedRowKeys, selectedRows) => {
this.setState({
selectedRowKeys,
selectedRows: selectedRows,
});
};
render() {
const {
data,
pagination,
loading,
selectedRows,
selectedRowKeys,
} = this.state;
const isSelectedSingle = this.state.selectedRows.length == 1;
let selectedText;
if (isSelectedSingle) {
selectedText = 'You have selected 1 device';
} else {
selectedText =
'You have selected ' + this.state.selectedRows.length + ' devices';
}
const rowSelection = {
selectedRowKeys,
selectedRows,
onChange: this.onSelectChange,
};
let item = this.state.deviceGroups.map(data => (
<Select.Option value={data.id} key={data.id}>
{data.name}
</Select.Option>
));
return (
<div>
<BulkActionBar
deleteDevice={this.deleteDevice}
getGroups={this.getGroups}
disenrollDevice={this.disenrollDevice}
selectedRows={this.state.selectedRows}
/>
<div>
<Table
columns={columns}
rowKey={record =>
record.deviceIdentifier +
record.enrolmentInfo.owner +
record.enrolmentInfo.ownership
}
dataSource={data}
pagination={{
...pagination,
size: 'small',
// position: "top",
showTotal: (total, range) =>
`showing ${range[0]}-${range[1]} of ${total} devices`,
// showQuickJumper: true
}}
loading={loading}
onChange={this.handleTableChange}
rowSelection={rowSelection}
/>
</div>
<Modal
title="Grouping Devices"
width="350px"
visible={this.state.groupModalVisible}
onOk={this.handleOk}
onCancel={this.handleCancel}
>
<p>{selectedText}</p>
<Select
mode={isSelectedSingle ? 'multiple' : 'default'}
showSearch
style={{ display: 'block' }}
placeholder="Select Group"
optionFilterProp="children"
onChange={this.onGroupSelectChange}
>
{item}
</Select>
</Modal>
</div>
);
}
}
export default withConfigContext(DeviceTable);

View File

@ -1,62 +0,0 @@
/*
* Copyright (c) 2020, Entgra (pvt) Ltd. (http://entgra.io) All Rights Reserved.
*
* Entgra (pvt) Ltd. licenses this file to you 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.
*/
import React from 'react';
import { PageHeader, Typography, Breadcrumb, Icon } from 'antd';
import { Link } from 'react-router-dom';
import DeviceTable from './components/DevicesTable';
const { Paragraph } = Typography;
class Devices extends React.Component {
routes;
constructor(props) {
super(props);
this.routes = props.routes;
}
render() {
return (
<div>
<PageHeader style={{ paddingTop: 0 }}>
<Breadcrumb style={{ paddingBottom: 16 }}>
<Breadcrumb.Item>
<Link to="/entgra/devices">
<Icon type="home" /> Home
</Link>
</Breadcrumb.Item>
<Breadcrumb.Item>Devices</Breadcrumb.Item>
</Breadcrumb>
<div className="wrap">
<h3>Devices</h3>
<Paragraph>All enrolled devices</Paragraph>
</div>
<div style={{ backgroundColor: '#ffffff', borderRadius: 5 }}>
<DeviceTable />
</div>
</PageHeader>
<div
style={{ background: '#f0f2f5', padding: 24, minHeight: 720 }}
></div>
</div>
);
}
}
export default Devices;

View File

@ -1,78 +0,0 @@
/*
* Copyright (c) 2020, Entgra (pvt) Ltd. (http://entgra.io) All Rights Reserved.
*
* Entgra (pvt) Ltd. licenses this file to you 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.
*/
import React from 'react';
import { Card, Col, Icon, Row } from 'antd';
import TimeAgo from 'javascript-time-ago';
// Load locale-specific relative date/time formatting rules.
import en from 'javascript-time-ago/locale/en';
import { withConfigContext } from '../../../../../../../../../../components/ConfigContext';
class DeviceType extends React.Component {
constructor(props) {
super(props);
TimeAgo.addLocale(en);
this.config = this.props.context;
this.state = {
data: this.config.deviceTypes,
pagination: {},
loading: false,
selectedRows: [],
};
}
onClickCard = (e, deviceType) => {
this.props.getDeviceType(deviceType);
};
render() {
const { data } = this.state;
const { Meta } = Card;
const itemCard = data.map(data => (
<Col span={5} key={data.id}>
<Card
size="default"
style={{ width: 150 }}
bordered={true}
onClick={e => this.onClickCard(e, data.name)}
cover={
<Icon
type="android"
key="device-types"
style={{
color: '#ffffff',
backgroundColor: '#4b92db',
fontSize: '100px',
padding: '20px',
}}
/>
}
>
<Meta title={data.name} />
</Card>
</Col>
));
return (
<div>
<Row gutter={16}>{itemCard}</Row>
</div>
);
}
}
export default withConfigContext(DeviceType);

View File

@ -1,128 +0,0 @@
/*
* Copyright (c) 2020, Entgra (pvt) Ltd. (http://entgra.io) All Rights Reserved.
*
* Entgra (pvt) Ltd. licenses this file to you 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.
*/
import React from 'react';
import { Form, Row, Col, Card, Steps } from 'antd';
import { withConfigContext } from '../../../../../../../../components/ConfigContext';
import DeviceType from './components/DeviceType';
import SelectEnrollmentType from '../SelectEnrolmentType';
import EnrollDevice from '../EnrollDevice';
import DownloadAgent from '../DownloadAgent';
const { Step } = Steps;
class AddDevice extends React.Component {
constructor(props) {
super(props);
this.config = this.props.context;
this.state = {
isAddDeviceModalVisible: false,
current: 0,
deviceType: 'android',
enrollmentType: 'qr',
};
}
getDeviceType = deviceType => {
this.setState({
current: 1,
deviceType: deviceType,
});
};
goNext = () => {
this.setState({
current: 2,
});
};
goBackToDeviceType = () => {
this.setState({
current: 0,
});
};
goBackToDownloadAgent = () => {
this.setState({
current: 1,
});
};
goBackToEnrollmentType = () => {
this.setState({
current: 2,
});
};
getEnrollmentData = enrollmentType => {
this.setState({
current: 3,
enrollmentType: enrollmentType,
});
};
render() {
const { current, deviceType, enrollmentType } = this.state;
return (
<div>
<Row>
<Col span={16} offset={4}>
<Steps style={{ minHeight: 32 }} current={current}>
<Step key="DeviceType" title="Select Device Type" />
<Step key="DownloadAgent" title="Download Agent" />
<Step key="EnrollmentType" title="Enrollment Type" />
<Step key="EnrollDevice" title="Enroll Device" />
</Steps>
</Col>
<Col span={16} offset={4}>
<Card style={{ marginTop: 24 }}>
<div style={{ display: current === 0 ? 'unset' : 'none' }}>
<DeviceType getDeviceType={this.getDeviceType} />
</div>
<div style={{ display: current === 1 ? 'unset' : 'none' }}>
<DownloadAgent
deviceType={deviceType}
goNext={this.goNext}
goBack={this.goBackToDeviceType}
/>
</div>
<div style={{ display: current === 2 ? 'unset' : 'none' }}>
<SelectEnrollmentType
deviceType={deviceType}
getEnrollmentData={this.getEnrollmentData}
goBack={this.goBackToDownloadAgent}
/>
</div>
<div style={{ display: current === 3 ? 'unset' : 'none' }}>
<EnrollDevice
deviceType={deviceType}
enrollmentType={enrollmentType}
goBack={this.goBackToEnrollmentType}
/>
</div>
</Card>
</Col>
</Row>
</div>
);
}
}
export default withConfigContext(
Form.create({ name: 'add-device' })(AddDevice),
);

View File

@ -1,173 +0,0 @@
/*
* Copyright (c) 2020, Entgra (pvt) Ltd. (http://entgra.io) All Rights Reserved.
*
* Entgra (pvt) Ltd. licenses this file to you 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.
*/
import React from 'react';
import { Button, Card, Divider, message, notification } from 'antd';
import TimeAgo from 'javascript-time-ago/modules/JavascriptTimeAgo';
import en from 'javascript-time-ago/locale/en';
import axios from 'axios';
import { withConfigContext } from '../../../../../../../../components/ConfigContext';
import QRCode from 'qrcode.react';
class DownloadAgent extends React.Component {
constructor(props) {
super(props);
this.config = this.props.context;
TimeAgo.addLocale(en);
this.state = {
pagination: {},
loading: false,
selectedRows: [],
buttonTitle: 'Download Agent',
skipButtonTitle: 'Skip',
};
}
onClickSkip = () => {
this.props.goNext();
};
onClickGoBack = () => {
this.props.goBack();
};
onClickDownloadAgent = () => {
this.downloadAgent();
};
// fetch data from api
downloadAgent = () => {
const { deviceType } = this.props;
this.setState({ loading: true, buttonTitle: 'Downloading..' });
const apiUrl =
window.location.origin +
'/api/application-mgt/v1.0/artifact/' +
deviceType +
'/agent/-1234';
// send request to the invokerss
axios
.get(apiUrl)
.then(res => {
if (res.status === 200) {
// Download file in same window
const url = window.URL.createObjectURL(new Blob([res.data]));
const link = document.createElement('a');
link.href = url;
link.setAttribute('download', 'android-agent.apk'); // or any other extension
document.body.appendChild(link);
link.click();
this.setState({
loading: false,
buttonTitle: 'Download Agent',
skipButtonTitle: 'Next',
});
}
})
.catch(error => {
if (error.hasOwnProperty('response') && error.response.status === 401) {
// todo display a popop with error
message.error('You are not logged in');
window.location.href = window.location.origin + '/entgra/login';
} else {
notification.error({
message: 'There was a problem',
duration: 0,
description:
'Error occurred while trying to download Entgra Android Agent',
});
}
this.setState({ loading: false });
});
};
render() {
const { loading, buttonTitle, skipButtonTitle } = this.state;
const { deviceType } = this.props;
const apiUrl =
window.location.origin +
'/api/application-mgt/v1.0/artifact/' +
deviceType +
'/agent/-1234';
return (
<div>
<Divider orientation="left">Step 01 - Get your Android Agent.</Divider>
<div>
<p>
The Android agent can be downloaded by using following QR. The
generated QR code can be scanned, and the agent APK downloaded from
the link, and transferred to the device and then installed.
</p>
</div>
<div style={{ margin: '30px', textAlign: 'center' }}>
<Divider>Scan to get the Android Agent.</Divider>
<div
style={{
marginBottm: 10,
display: 'inline-block',
}}
>
<Card hoverable>
<QRCode size={200} value={apiUrl} />
</Card>
</div>
<Divider>OR</Divider>
<div style={{ textAlign: 'center', marginTop: 10, marginBottom: 10 }}>
<Button
type="primary"
size={'default'}
onClick={this.onClickDownloadAgent}
loading={loading}
>
{buttonTitle}
</Button>
</div>
<p>
Need help? Read&nbsp;
<a
href={
'https://entgra-documentation.gitlab.io/v3.8.0/docs/guide-to-work-with-the-product/' +
'enrollment-guide/enroll-android/'
}
>
Entgra IoT Server documentation.
</a>
</p>
</div>
<div style={{ textAlign: 'right' }}>
<Button type="primary" size={'default'} onClick={this.onClickSkip}>
{skipButtonTitle}
</Button>
<Button
style={{ marginLeft: 10 }}
type="default"
size={'default'}
onClick={this.onClickGoBack}
>
Back
</Button>
</div>
</div>
);
}
}
export default withConfigContext(DownloadAgent);

View File

@ -1,217 +0,0 @@
/*
* Copyright (c) 2020, Entgra (pvt) Ltd. (http://entgra.io) All Rights Reserved.
*
* Entgra (pvt) Ltd. licenses this file to you 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.
*/
import React from 'react';
import {
Divider,
message,
notification,
Select,
Card,
Spin,
Button,
Row,
Col,
} from 'antd';
import TimeAgo from 'javascript-time-ago/modules/JavascriptTimeAgo';
import en from 'javascript-time-ago/locale/en';
import { withConfigContext } from '../../../../../../../../components/ConfigContext';
import axios from 'axios';
import QRCode from 'qrcode.react';
import QRPlaceholder from '../../../../../../../../../public/images/qr-code.png';
import installAgent from '../../../../../../../../../public/images/install_agent.png';
import register from '../../../../../../../../../public/images/register.png';
import registration from '../../../../../../../../../public/images/registration.png';
import setProfile from '../../../../../../../../../public/images/set_profile.png';
const { Option } = Select;
class EnrollDevice extends React.Component {
constructor(props) {
super(props);
this.config = this.props.context;
TimeAgo.addLocale(en);
this.state = {
isSelected: false,
loading: false,
payload: {},
};
}
// fetch data from api
generateQRCode = ownershipType => {
const { deviceType } = this.props;
this.setState({ loading: true });
let apiUrl =
window.location.origin +
this.config.serverConfig.invoker.uri +
'/device-mgt/' +
deviceType +
'/v1.0/configuration/enrollment-qr-config/' +
ownershipType;
// send request to the invokerss
axios
.get(apiUrl)
.then(res => {
if (res.status === 200) {
this.setState({
loading: false,
payload: res.data.data,
isSelected: true,
});
}
})
.catch(error => {
if (error.hasOwnProperty('response') && error.response.status === 401) {
// todo display a popop with error
message.error('You are not logged in');
window.location.href = window.location.origin + '/entgra/login';
} else {
notification.error({
message: 'There was a problem',
duration: 0,
description: 'Error occurred while trying to get QR code payload.',
});
}
this.setState({ loading: false });
});
};
onChange = value => {
this.generateQRCode(value);
};
onClick = () => {
this.props.goBack();
};
render() {
const { payload, isSelected, loading } = this.state;
const { enrollmentType } = this.props;
return (
<div style={{ textAlign: 'center', fontSize: 12 }}>
<div
style={{
display: enrollmentType === 'manual' ? 'inline-block' : 'none',
}}
>
<Row gutter={[16, 16]}>
<Col span={6}>
<h1>Step 1</h1>
<p>
{/* eslint-disable-next-line react/no-unescaped-entities */}
Let's start by installing the Android agent on your device. Open
the downloaded file, and tap INSTALL.
</p>
<img src={installAgent} />
</Col>
<Col span={6}>
<h1>Step 2</h1>
<p>Tap Skip to proceed with the default enrollment process.</p>
<img src={setProfile} />
</Col>
<Col span={6}>
<h1>Step 3</h1>
<p>
Enter the server address based on your environment, in the text
box provided.
</p>
<img src={registration} />
</Col>
<Col span={6}>
<h1>Step 4</h1>
<p>Enter your:</p>
<div>
<p>Organization: carbon.super</p>
<p>Username: admin</p>
<p>Password: Your password</p>
</div>
<img src={register} />
</Col>
</Row>
</div>
<div
style={{
display: enrollmentType === 'qr' ? 'inline-block' : 'none',
}}
>
<Divider>Generate QR code to QR based Provisioning.</Divider>
<div style={{ textAlign: 'center' }}>
<div style={{ marginBottom: 10 }}>
<Select
showSearch
style={{ width: 200, textAlign: 'center' }}
placeholder="Select device ownership"
optionFilterProp="children"
onChange={this.onChange}
filterOption={(input, option) =>
option.props.children
.toLowerCase()
.indexOf(input.toLowerCase()) >= 0
}
>
<Option key={'byod'} value={'BYOD'}>
{'BYOD'}
</Option>
<Option key={'cope'} value={'COPE'}>
{'COPE'}
</Option>
<Option key={'COSU'} value={'COSU'}>
{'COSU (KIOSK)'}
</Option>
<Option key={'WORK PROFILE'} value={'WORK_PROFILE'}>
{'WORK PROFILE'}
</Option>
<Option key={'GOOGLE_ENTERPRISE'} value={'GOOGLE_ENTERPRISE'}>
{'GOOGLE_WORK_PROFILE'}
</Option>
</Select>
</div>
<Spin spinning={loading}>
<div
style={{
display: isSelected ? 'inline-block' : 'none',
}}
>
<Card hoverable>
<QRCode size={300} value={JSON.stringify(payload)} />
</Card>
</div>
<div style={{ display: !isSelected ? 'inline-block' : 'none' }}>
<Card hoverable>
<img src={QRPlaceholder} />
</Card>
</div>
</Spin>
</div>
</div>
<div style={{ textAlign: 'right', marginTop: 10 }}>
<Button type="default" size={'default'} onClick={this.onClick}>
Back
</Button>
</div>
</div>
);
}
}
export default withConfigContext(EnrollDevice);

View File

@ -1,94 +0,0 @@
/*
* Copyright (c) 2020, Entgra (pvt) Ltd. (http://entgra.io) All Rights Reserved.
*
* Entgra (pvt) Ltd. licenses this file to you 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.
*/
import React from 'react';
import { Button, Divider } from 'antd';
import TimeAgo from 'javascript-time-ago/modules/JavascriptTimeAgo';
import en from 'javascript-time-ago/locale/en';
import { withConfigContext } from '../../../../../../../../components/ConfigContext';
class SelectEnrollmentType extends React.Component {
constructor(props) {
super(props);
this.config = this.props.context;
TimeAgo.addLocale(en);
this.state = {
pagination: {},
loading: false,
selectedRows: [],
};
}
onEnrollWithQR = () => {
this.props.getEnrollmentData('qr');
};
onEnrollManually = () => {
this.props.getEnrollmentData('manual');
};
onClick = () => {
this.props.goBack();
};
render() {
return (
<div>
<Divider orientation="left">
Step 02 - Enroll the Android Agent.
</Divider>
<div>
<p>
{' '}
Your device can be enrolled with Entgra IoTS automatically via QR
code. To enroll first download agent as mentioned in Step 1 then
proceed with the ENROLL WITH QR option from the device setup
activity. Thereafter select the ownership configuration and scan the
generated QR to complete the process.
</p>
</div>
<div style={{ margin: '30px' }}>
<Button
type="primary"
size={'default'}
onClick={this.onEnrollWithQR}
style={{ marginRight: 10 }}
>
Enroll Using QR
</Button>
<Button
type="primary"
size={'default'}
onClick={this.onEnrollManually}
>
Enroll Manually
</Button>
</div>
<div style={{ textAlign: 'right' }}>
<Button type="default" size={'default'} onClick={this.onClick}>
Back
</Button>
</div>
</div>
);
}
}
export default withConfigContext(SelectEnrollmentType);

View File

@ -1,47 +0,0 @@
import React from 'react';
import { PageHeader, Typography, Breadcrumb, Icon } from 'antd';
import { Link } from 'react-router-dom';
import AddDevice from './components/AddDevice';
const { Paragraph } = Typography;
class DeviceEnroll extends React.Component {
routes;
constructor(props) {
super(props);
this.routes = props.routes;
}
render() {
return (
<div>
<PageHeader style={{ paddingTop: 0 }}>
<Breadcrumb style={{ paddingBottom: 16 }}>
<Breadcrumb.Item>
<Link to="/entgra/devices">
<Icon type="home" /> Home
</Link>
</Breadcrumb.Item>
<Breadcrumb.Item>
<Link to="/entgra/devices">Devices</Link>
</Breadcrumb.Item>
<Breadcrumb.Item>Enroll Device</Breadcrumb.Item>
</Breadcrumb>
<div className="wrap">
<h3>Devices</h3>
<Paragraph>All enrolled devices</Paragraph>
</div>
<div style={{ borderRadius: 5 }}>
<AddDevice />
</div>
</PageHeader>
<div
style={{ background: '#f0f2f5', padding: 24, minHeight: 720 }}
></div>
</div>
);
}
}
export default DeviceEnroll;

View File

@ -1,145 +0,0 @@
/*
* Copyright (c) 2020, Entgra (pvt) Ltd. (http://entgra.io) All Rights Reserved.
*
* Entgra (pvt) Ltd. licenses this file to you 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.
*/
import React, { Component, Fragment } from 'react';
import {
Map,
TileLayer,
Marker,
Polyline,
Popup,
Tooltip,
} from 'react-leaflet';
import L from 'leaflet';
import { withConfigContext } from '../../../../../../components/ConfigContext';
// Icons for the location markers
const pinStart = new L.Icon({
iconUrl: require('./pin_start.svg'),
iconRetinaUrl: require('./pin_start.svg'),
shadowUrl: require('./pin_shadow.svg'),
shadowSize: new L.Point(51, 51),
shadowAnchor: [22, 22],
popupAnchor: [0, -22],
iconSize: new L.Point(50, 50),
tooltipAnchor: [0, -22],
});
const pinEnd = new L.Icon({
iconUrl: require('./pin_end.svg'),
iconRetinaUrl: require('./pin_end.svg'),
shadowUrl: require('./pin_shadow.svg'),
shadowSize: new L.Point(51, 51),
shadowAnchor: [22, 22],
popupAnchor: [0, -22],
iconSize: new L.Point(65, 65),
tooltipAnchor: [0, -28],
});
class CustomMap extends Component {
constructor(props) {
super(props);
}
/**
* Polyline draw for historical locations
* @param locationHistorySnapshots - location data object
* @returns content
*/
polylineMarker = locationHistorySnapshots => {
const polyLines = [];
locationHistorySnapshots.forEach(locationHistorySnapshots => {
polyLines.push(
<Polyline
key={polyLines.length}
color="#414042"
positions={locationHistorySnapshots.map(snapshot => {
return [snapshot.latitude, snapshot.longitude];
})}
smoothFactor={10}
weight={5}
/>,
);
});
for (let i = 1; i < locationHistorySnapshots.length; i++) {
const startPosition = locationHistorySnapshots[i][0];
const endingPosition =
locationHistorySnapshots[i - 1][
locationHistorySnapshots[i - 1].length - 1
];
polyLines.push(
<Polyline
key={polyLines.length}
color="#414042"
positions={[
[startPosition.latitude, startPosition.longitude],
[endingPosition.latitude, endingPosition.longitude],
]}
smoothFactor={10}
weight={5}
dashArray="7"
/>,
);
}
return (
<div style={{ display: 'none' }}>
{polyLines.map(polyLine => {
return polyLine;
})}
</div>
);
};
render() {
const locationHistorySnapshots = this.props.locationHistorySnapshots;
const config = this.props.context;
const attribution = config.geoMap.attribution;
const url = config.geoMap.url;
const firstSnapshot = locationHistorySnapshots[0][0];
const lastSnapshotList =
locationHistorySnapshots[locationHistorySnapshots.length - 1];
const lastSnapshot = lastSnapshotList[lastSnapshotList.length - 1];
const startingPoint = [firstSnapshot.latitude, firstSnapshot.longitude];
const endPoint = [lastSnapshot.latitude, lastSnapshot.longitude];
const zoom = config.geoMap.defaultZoomLevel;
return (
<div style={{ backgroundColor: '#ffffff', borderRadius: 5, padding: 5 }}>
<Map center={startingPoint} zoom={zoom}>
<TileLayer url={url} attribution={attribution} />
<Fragment>
{this.polylineMarker(locationHistorySnapshots)}
<Marker icon={pinStart} position={startingPoint}>
<Popup keepInView={true}>Start</Popup>
<Tooltip direction="top" permanent={true}>
Start
</Tooltip>
</Marker>
<Marker icon={pinEnd} position={endPoint}>
<Tooltip direction="top" permanent={true}>
End
</Tooltip>
</Marker>
</Fragment>
</Map>
</div>
);
}
}
export default withConfigContext(CustomMap);

View File

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 122.80151 122.80151"><defs><style>.a,.b{fill:#8dc63f;}.a{opacity:0.34;}.c{fill:#fff;}</style></defs><title>map</title><circle class="a" cx="61.40076" cy="61.40076" r="57.704"/><circle class="b" cx="61.40076" cy="61.40076" r="40.2134"/><circle class="c" cx="61.40076" cy="61.40076" r="18.64704"/></svg>

Before

Width:  |  Height:  |  Size: 354 B

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 69 KiB

View File

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"><defs><style>.a{fill:#414042;}.b{fill:#fff;}</style></defs><title>map</title><circle class="a" cx="50" cy="50" r="40.2134"/><circle class="b" cx="50" cy="50" r="18.64704"/></svg>

Before

Width:  |  Height:  |  Size: 240 B

View File

@ -1,172 +0,0 @@
/*
* Copyright (c) 2020, Entgra (pvt) Ltd. (http://entgra.io) All Rights Reserved.
*
* Entgra (pvt) Ltd. licenses this file to you 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.
*/
import React from 'react';
import moment from 'moment';
import { message, notification, Empty, DatePicker } from 'antd';
import axios from 'axios';
import { withConfigContext } from '../../../../../../components/ConfigContext';
import GeoCustomMap from '../CustomMap';
import './styles.css';
class GeoDashboard extends React.Component {
constructor(props) {
super(props);
let start = moment(
new Date(
new Date().getFullYear(),
new Date().getMonth(),
new Date().getDate(),
0,
0,
0,
0,
),
);
let end = moment(start)
.add(1, 'days')
.subtract(1, 'seconds');
this.state = {
deviceData: [],
selectedDevice: '',
locationHistorySnapshots: [],
loading: false,
start: start,
end: end,
buttonTooltip: 'Fetch Locations',
};
}
componentDidMount() {
this.fetchLocationHistory();
}
/**
* Call back on apply button in the date time picker
* @param startDate - start date
* @param endDate - end date
*/
applyCallback = (dates, dateStrings) => {
this.setState({
start: dateStrings[0],
end: dateStrings[1],
});
};
fetchLocationHistory = () => {
const toInMills = moment(this.state.end);
const fromInMills = moment(this.state.start);
const config = this.props.context;
this.setState({ loading: true });
axios
.get(
window.location.origin +
config.serverConfig.invoker.uri +
config.serverConfig.invoker.deviceMgt +
'/devices/' +
this.props.deviceType +
'/' +
this.props.deviceIdentifier +
'/location-history?' +
'from=' +
fromInMills +
'&to=' +
toInMills,
)
.then(res => {
if (res.status === 200) {
this.setState({
loading: false,
locationHistorySnapshots: res.data.data.locationHistorySnapshots,
});
}
})
.catch(error => {
if (error.hasOwnProperty('response') && error.response.status === 401) {
message.error('You are not logged in');
window.location.href = window.location.origin + '/entgra/login';
} else {
notification.error({
message: 'There was a problem',
duration: 0,
description: 'Error occurred while trying to fetch locations......',
});
}
this.setState({ loading: false });
console.log(error);
});
};
/**
* Geo Dashboard controller
*/
controllerBar = () => {
const { RangePicker } = DatePicker;
let now = new Date();
let start = moment(
new Date(now.getFullYear(), now.getMonth(), now.getDate(), 0, 0, 0, 0),
);
let end = moment(start)
.add(1, 'days')
.subtract(1, 'seconds');
let ranges = {
'Today Only': [moment(start), moment(end)],
'Yesterday Only': [
moment(start).subtract(1, 'days'),
moment(end).subtract(1, 'days'),
],
'3 Days': [moment(start).subtract(3, 'days'), moment(end)],
'5 Days': [moment(start).subtract(5, 'days'), moment(end)],
'1 Week': [moment(start).subtract(7, 'days'), moment(end)],
'2 Weeks': [moment(start).subtract(14, 'days'), moment(end)],
'1 Month': [moment(start).subtract(1, 'months'), moment(end)],
};
return (
<div className="controllerDiv">
<RangePicker
ranges={ranges}
style={{ marginRight: 20, width: 400 }}
showTime
format="YYYY-MM-DD HH:mm:ss"
defaultValue={[this.state.start, this.state.end]}
onChange={this.applyCallback}
onOk={this.fetchLocationHistory}
size="large"
/>
</div>
);
};
render() {
const { locationHistorySnapshots } = this.state;
return (
<div className="container">
{this.controllerBar()}
{locationHistorySnapshots.length > 0 ? (
<GeoCustomMap locationHistorySnapshots={locationHistorySnapshots} />
) : (
<Empty />
)}
</div>
);
}
}
export default withConfigContext(GeoDashboard);

View File

@ -1,10 +0,0 @@
.leaflet-container {
align-content: center;
padding-top: 80px;
height: 550px;
width: 100%;
}
.controllerDiv {
padding-bottom: 30px;
}

View File

@ -1,73 +0,0 @@
/*
* Copyright (c) 2020, Entgra (pvt) Ltd. (http://entgra.io) All Rights Reserved.
*
* Entgra (pvt) Ltd. licenses this file to you 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.
*/
import React from 'react';
import { PageHeader, Typography, Breadcrumb, Icon } from 'antd';
import { Link } from 'react-router-dom';
import GeoDashboard from './components/GeoDashboard';
const { Paragraph } = Typography;
class Geo extends React.Component {
routes;
constructor(props) {
super(props);
this.routes = props.routes;
}
render() {
const { deviceIdentifier, deviceType } = this.props.match.params;
return (
<div>
<PageHeader style={{ paddingTop: 0 }}>
<Breadcrumb style={{ paddingBottom: 16 }}>
<Breadcrumb.Item>
<Link to="/entgra">
<Icon type="home" /> Home
</Link>
</Breadcrumb.Item>
<Breadcrumb.Item>
<Link to="/entgra">Devices</Link>
</Breadcrumb.Item>
<Breadcrumb.Item>{`Location History - ${deviceType} / ${deviceIdentifier}`}</Breadcrumb.Item>
</Breadcrumb>
<div className="wrap">
<h3>Location History </h3>
<Paragraph>{`${deviceType} / ${deviceIdentifier}`}</Paragraph>
</div>
</PageHeader>
<div
style={{
background: '#f0f2f5',
padding: 24,
minHeight: 720,
alignItems: 'center',
}}
>
<GeoDashboard
deviceIdentifier={deviceIdentifier}
deviceType={deviceType}
/>
</div>
</div>
);
}
}
export default Geo;

View File

@ -1,174 +0,0 @@
/*
* Copyright (c) 2020, Entgra (pvt) Ltd. (http://entgra.io) All Rights Reserved.
*
* Entgra (pvt) Ltd. licenses this file to you 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.
*/
import React from 'react';
import { Button, Form, Input, message, Modal, notification } from 'antd';
import axios from 'axios';
import { withConfigContext } from '../../../../../../../../components/ConfigContext';
class AddGroup extends React.Component {
constructor(props) {
super(props);
this.state = {
addModalVisible: false,
name: '',
description: '',
};
}
onConfirmAdGroup = () => {
const config = this.props.context;
const groupData = {
name: this.state.name,
description: this.state.description,
};
// send request to the invoker
axios
.post(
window.location.origin +
config.serverConfig.invoker.uri +
config.serverConfig.invoker.deviceMgt +
'/groups',
groupData,
{ headers: { 'Content-Type': 'application/json' } },
)
.then(res => {
if (res.status === 201) {
this.props.fetchGroups();
notification.success({
message: 'Done',
duration: 4,
description: 'Successfully added the group.',
});
}
})
.catch(error => {
if (error.hasOwnProperty('response') && error.response.status === 401) {
// todo display a popop with error
message.error('You are not logged in');
window.location.href = window.location.origin + '/entgra/login';
} else {
notification.error({
message: 'There was a problem',
duration: 0,
description: 'Error occurred while trying to add group.',
});
}
});
};
openAddModal = () => {
this.setState({
addModalVisible: true,
});
};
handleAddOk = e => {
this.props.form.validateFields(err => {
if (!err) {
this.onConfirmAdGroup();
this.setState({
addModalVisible: false,
});
}
});
};
handleAddCancel = e => {
this.setState({
addModalVisible: false,
});
};
onChangeName = e => {
this.setState({
name: e.currentTarget.value,
});
};
onChangeDescription = e => {
this.setState({
description: e.currentTarget.value,
});
};
render() {
const { getFieldDecorator } = this.props.form;
return (
<div>
<div>
<Button
type="primary"
icon="plus"
size={'default'}
onClick={this.openAddModal}
>
Add Group
</Button>
</div>
<div>
<Modal
title="ADD NEW GROUP"
width="40%"
visible={this.state.addModalVisible}
onOk={this.handleAddOk}
onCancel={this.handleAddCancel}
footer={[
<Button key="cancel" onClick={this.handleAddCancel}>
Cancel
</Button>,
<Button key="submit" type="primary" onClick={this.handleAddOk}>
Submit
</Button>,
]}
>
<div style={{ alignItems: 'center' }}>
<p>Create new device group on IoT Server.</p>
<Form labelCol={{ span: 5 }} wrapperCol={{ span: 18 }}>
<Form.Item label="Name" style={{ display: 'block' }}>
{getFieldDecorator('name', {
rules: [
{
required: true,
message: 'Please input group name',
},
],
})(<Input onChange={this.onChangeName} />)}
</Form.Item>
<Form.Item label="Description" style={{ display: 'block' }}>
{getFieldDecorator('description', {
rules: [
{
required: true,
message: 'Please input group description',
},
],
})(<Input onChange={this.onChangeDescription} />)}
</Form.Item>
</Form>
</div>
</Modal>
</div>
</div>
);
}
}
export default withConfigContext(Form.create({ name: 'add-group' })(AddGroup));

View File

@ -1,395 +0,0 @@
/*
* Copyright (c) 2020, Entgra (pvt) Ltd. (http://entgra.io) All Rights Reserved.
*
* Entgra (pvt) Ltd. licenses this file to you 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.
*/
import React from 'react';
import {
Button,
Divider,
Form,
Icon,
Input,
message,
Modal,
notification,
Popconfirm,
Select,
Tooltip,
Typography,
} from 'antd';
import axios from 'axios';
import { withConfigContext } from '../../../../../../../../components/ConfigContext';
const { Text } = Typography;
class GroupActions extends React.Component {
constructor(props) {
super(props);
this.state = {
editModalVisible: false,
shareModalVisible: false,
name: this.props.data.name,
description: this.props.data.description,
groupDataObject: {},
rolesData: [],
shareRolesData: [],
};
}
onConfirmDeleteGroup = () => {
const config = this.props.context;
// send request to the invoker
axios
.delete(
window.location.origin +
config.serverConfig.invoker.uri +
config.serverConfig.invoker.deviceMgt +
'/groups/id/' +
this.props.data.id,
{ headers: { 'Content-Type': 'application/json' } },
)
.then(res => {
if (res.status === 200) {
this.props.fetchGroups();
notification.success({
message: 'Done',
duration: 4,
description: 'Successfully deleted the group.',
});
}
})
.catch(error => {
if (error.hasOwnProperty('response') && error.response.status === 401) {
// todo display a popop with error
message.error('You are not logged in');
window.location.href = window.location.origin + '/entgra/login';
} else {
notification.error({
message: 'There was a problem',
duration: 0,
description: 'Error occurred while trying to delete group.',
});
}
});
};
onConfirmUpdateGroup = data => {
const config = this.props.context;
// send request to the invoker
axios
.put(
window.location.origin +
config.serverConfig.invoker.uri +
config.serverConfig.invoker.deviceMgt +
'/groups/id/' +
this.props.data.id,
data,
)
.then(res => {
if (res.status === 200) {
this.props.fetchGroups();
notification.success({
message: 'Done',
duration: 4,
description: 'Successfully updated the group.',
});
}
})
.catch(error => {
if (error.hasOwnProperty('response') && error.response.status === 401) {
// todo display a popop with error
message.error('You are not logged in');
window.location.href = window.location.origin + '/entgra/login';
} else {
notification.error({
message: 'There was a problem',
duration: 0,
description: 'Error occurred while trying to update group.',
});
}
});
};
fetchUserRoles = (params = {}) => {
const config = this.props.context;
const apiUrl =
window.location.origin +
config.serverConfig.invoker.uri +
config.serverConfig.invoker.deviceMgt +
'/roles';
// send request to the invokerss
axios
.get(apiUrl)
.then(res => {
if (res.status === 200) {
this.setState({
rolesData: res.data.data.roles,
});
}
})
.catch(error => {
if (error.hasOwnProperty('response') && error.response.status === 401) {
// todo display a popop with error
message.error('You are not logged in');
window.location.href = window.location.origin + '/entgra/login';
} else {
notification.error({
message: 'There was a problem',
duration: 0,
description: 'Error occurred while trying to load roles.',
});
}
});
};
onConfirmShareGroup = data => {
const config = this.props.context;
// send request to the invoker
axios
.post(
window.location.origin +
config.serverConfig.invoker.uri +
config.serverConfig.invoker.deviceMgt +
'/groups/id/' +
this.props.data.id +
'/share',
data,
)
.then(res => {
if (res.status === 200) {
this.props.fetchGroups();
notification.success({
message: 'Done',
duration: 4,
description: 'Successfully shared the group.',
});
}
})
.catch(error => {
if (error.hasOwnProperty('response') && error.response.status === 401) {
// todo display a popop with error
message.error('You are not logged in');
window.location.href = window.location.origin + '/entgra/login';
} else {
notification.error({
message: 'There was a problem',
duration: 0,
description: 'Error occurred while trying to share group.',
});
}
});
};
openEditModal = () => {
this.setState({
editModalVisible: true,
});
};
openShareModal = () => {
this.fetchUserRoles();
this.setState({
shareModalVisible: true,
});
};
handleEditOk = e => {
const groupDataObject = {
name: this.state.name,
description: this.state.description,
id: this.props.data.id,
owner: this.props.data.owner,
groupProperties: this.props.data.groupProperties,
};
this.setState({ groupDataObject });
this.props.form.validateFields(err => {
if (!err) {
this.onConfirmUpdateGroup(this.state.groupDataObject);
this.setState({
editModalVisible: false,
});
}
});
};
handleEditCancel = e => {
this.setState({
editModalVisible: false,
});
};
handleShareOk = e => {
this.setState({
shareModalVisible: false,
});
this.onConfirmShareGroup(this.state.shareRolesData);
};
handleShareCancel = e => {
this.setState({
shareModalVisible: false,
});
};
onChangeName = e => {
this.setState({
name: e.currentTarget.value,
});
};
onChangeDescription = e => {
this.setState({
description: e.currentTarget.value,
});
};
handleRolesDropdownChange = value => {
this.setState({
shareRolesData: value,
});
};
render() {
const isAdminGroups = this.props.data.id == 1 || this.props.data.id == 2;
const { getFieldDecorator } = this.props.form;
let item = this.state.rolesData.map(data => (
<Select.Option value={data} key={data}>
{data}
</Select.Option>
));
return (
<div>
<div style={{ display: isAdminGroups ? 'none' : 'inline' }}>
<Tooltip placement="top" title={'Share Group'}>
<a>
<Icon type="share-alt" onClick={this.openShareModal} />
</a>
</Tooltip>
<Divider type="vertical" />
<Tooltip placement="top" title={'Edit Group'}>
<a>
<Icon type="edit" onClick={this.openEditModal} />
</a>
</Tooltip>
<Divider type="vertical" />
<Tooltip placement="bottom" title={'Delete Group'}>
<Popconfirm
placement="top"
title={'Are you sure?'}
onConfirm={this.onConfirmDeleteGroup}
okText="Ok"
cancelText="Cancel"
>
<a>
<Text type="danger">
<Icon type="delete" />
</Text>
</a>
</Popconfirm>
</Tooltip>
</div>
<div>
<Modal
title="Update Group"
width="40%"
visible={this.state.editModalVisible}
onOk={this.handleEditOk}
onCancel={this.handleEditCancel}
footer={[
<Button key="cancel" onClick={this.handleEditCancel}>
Cancel
</Button>,
<Button key="submit" type="primary" onClick={this.handleEditOk}>
Submit
</Button>,
]}
>
<div style={{ alignItems: 'center' }}>
<p>Enter new name and description for the group</p>
<Form labelCol={{ span: 5 }} wrapperCol={{ span: 18 }}>
<Form.Item label="Name" style={{ display: 'block' }}>
{getFieldDecorator('name', {
initialValue: this.props.data.name,
rules: [
{
required: true,
message: 'Please input group name',
},
],
})(<Input onChange={this.onChangeName} />)}
</Form.Item>
<Form.Item label="Description" style={{ display: 'block' }}>
{getFieldDecorator('description', {
initialValue: this.props.data.description,
rules: [
{
required: true,
message: 'Please input group description',
},
],
})(<Input onChange={this.onChangeDescription} />)}
</Form.Item>
</Form>
</div>
</Modal>
</div>
<div>
<Modal
title="Share Group"
width="500px"
visible={this.state.shareModalVisible}
onOk={this.handleShareOk}
onCancel={this.handleShareCancel}
footer={[
<Button key="new-role" onClick={this.handleShareCancel}>
New Role
</Button>,
<Button key="new-role-selection" onClick={this.handleShareCancel}>
New Role from Selection
</Button>,
<Button key="submit" type="primary" onClick={this.handleShareOk}>
Share
</Button>,
]}
>
<p>Select user role(s)</p>
<Select
mode="multiple"
defaultValue={'admin'}
style={{ width: '100%' }}
onChange={this.handleRolesDropdownChange}
>
{item}
</Select>
,
</Modal>
</div>
</div>
);
}
}
export default withConfigContext(
Form.create({ name: 'group-actions' })(GroupActions),
);

View File

@ -1,136 +0,0 @@
/*
* Copyright (c) 2020, Entgra (pvt) Ltd. (http://entgra.io) All Rights Reserved.
*
* Entgra (pvt) Ltd. licenses this file to you 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.
*/
import React from 'react';
import { Button, Modal } from 'antd';
import TimeAgo from 'javascript-time-ago';
// Load locale-specific relative date/time formatting rules.
import en from 'javascript-time-ago/locale/en';
import { withConfigContext } from '../../../../../../../../components/ConfigContext';
import DevicesTable from '../../../../../../components/DevicesTable';
let apiUrl;
class GroupDevicesModal extends React.Component {
constructor(props) {
super(props);
TimeAgo.addLocale(en);
this.state = {
data: [],
pagination: {},
loading: false,
selectedRows: [],
visible: false,
apiUrl: null,
};
}
openDrawer = () => {
this.setState({ visible: true });
const groupData = {
groupId: this.props.groupData.id,
groupName: this.props.groupData.name,
};
this.fetchGroupDevices(groupData);
};
handleModalCancel = () => {
this.setState({
visible: false,
});
};
// fetch data from api
fetchGroupDevices = (params = {}, filters = {}) => {
const config = this.props.context;
// get current page
const currentPage = params.hasOwnProperty('page') ? params.page : 1;
const extraParams = {
offset: 10 * (currentPage - 1), // calculate the offset
limit: 10,
...params,
};
const encodedExtraParams = Object.keys(extraParams)
.map(key => key + '=' + extraParams[key])
.join('&');
apiUrl =
window.location.origin +
config.serverConfig.invoker.uri +
config.serverConfig.invoker.deviceMgt +
'/devices?' +
encodedExtraParams;
this.setState({ apiUrl: apiUrl });
};
handleTableChange = (pagination, filters, sorter) => {
const pager = { ...this.state.pagination };
pager.current = pagination.current;
this.setState({
pagination: pager,
});
this.fetchGroupDevices({
results: pagination.pageSize,
page: pagination.current,
sortField: sorter.field,
sortOrder: sorter.order,
...filters,
});
};
render() {
const { apiUrl, visible } = this.state;
return (
<div>
<Button
type="primary"
size={'small'}
icon="desktop"
onClick={this.openDrawer}
>
Devices
</Button>
<Modal
title="DEVICES"
width="80%"
visible={visible}
onCancel={this.handleModalCancel}
footer={[
<Button key="cancel" onClick={this.handleModalCancel}>
Cancel
</Button>,
<Button key="ok" type="primary" onClick={this.handleModalCancel}>
OK
</Button>,
]}
>
<div style={{ alignItems: 'center' }}>
<DevicesTable apiUrl={apiUrl} />
</div>
</Modal>
</div>
);
}
}
export default withConfigContext(GroupDevicesModal);

View File

@ -1,219 +0,0 @@
/*
* Copyright (c) 2020, Entgra (pvt) Ltd. (http://entgra.io) All Rights Reserved.
*
* Entgra (pvt) Ltd. licenses this file to you 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.
*/
import React from 'react';
import axios from 'axios';
import { message, notification, Table } from 'antd';
import TimeAgo from 'javascript-time-ago';
// Load locale-specific relative date/time formatting rules.
import en from 'javascript-time-ago/locale/en';
import { withConfigContext } from '../../../../../../components/ConfigContext';
import GroupActions from './components/GroupActions';
import AddGroup from './components/AddGroup';
import Filter from '../../../../components/Filter';
import GroupDevicesModal from './components/GroupDevicesModal';
const searchFields = [
{
name: 'name',
placeholder: 'Name',
},
{
name: 'owner',
placeholder: 'Owner',
},
];
let apiUrl;
class GroupsTable extends React.Component {
constructor(props) {
super(props);
TimeAgo.addLocale(en);
this.state = {
data: [],
pagination: {},
loading: false,
selectedRows: [],
};
}
columns = [
{
title: 'Group Name',
dataIndex: 'name',
width: 100,
},
{
title: 'Owner',
dataIndex: 'owner',
key: 'owner',
// render: enrolmentInfo => enrolmentInfo.owner
// todo add filtering options
},
{
title: 'Description',
dataIndex: 'description',
key: 'description',
// render: enrolmentInfo => enrolmentInfo.ownership
// todo add filtering options
},
{
title: 'Action',
dataIndex: 'id',
key: 'action',
render: (id, row) => (
<span>
<GroupActions data={row} fetchGroups={this.fetchGroups} />
</span>
),
},
{
title: 'Devices',
dataIndex: 'id',
key: 'details',
render: (id, row) => <GroupDevicesModal groupData={row} />,
},
{
title: 'Devices',
dataIndex: 'id',
key: 'details',
render: (id, row) => <GroupDevicesModal groupData={row} />,
},
];
rowSelection = {
onChange: (selectedRowKeys, selectedRows) => {
this.setState({
selectedRows: selectedRows,
});
},
};
componentDidMount() {
this.fetchGroups();
}
// fetch data from api
fetchGroups = (params = {}, filters = {}) => {
const config = this.props.context;
this.setState({ loading: true });
// get current page
const currentPage = params.hasOwnProperty('page') ? params.page : 1;
const extraParams = {
offset: 10 * (currentPage - 1), // calculate the offset
limit: 10,
...filters,
};
const encodedExtraParams = Object.keys(extraParams)
.map(key => key + '=' + extraParams[key])
.join('&');
apiUrl =
window.location.origin +
config.serverConfig.invoker.uri +
config.serverConfig.invoker.deviceMgt +
'/admin/groups?' +
encodedExtraParams;
// send request to the invokerss
axios
.get(apiUrl)
.then(res => {
if (res.status === 200) {
const pagination = { ...this.state.pagination };
this.setState({
loading: false,
data: res.data.data,
pagination,
});
}
})
.catch(error => {
if (error.hasOwnProperty('response') && error.response.status === 401) {
// todo display a popop with error
message.error('You are not logged in');
window.location.href = window.location.origin + '/entgra/login';
} else {
notification.error({
message: 'There was a problem',
duration: 0,
description: 'Error occurred while trying to load device groups.',
});
}
this.setState({ loading: false });
});
};
handleTableChange = (pagination, filters, sorter) => {
const pager = { ...this.state.pagination };
pager.current = pagination.current;
this.setState({
pagination: pager,
});
this.fetchGroups({
results: pagination.pageSize,
page: pagination.current,
sortField: sorter.field,
sortOrder: sorter.order,
...filters,
});
};
render() {
const { data, pagination, loading } = this.state;
return (
<div>
<div style={{ background: '#f0f2f5' }}>
<AddGroup
fetchGroups={this.fetchGroups}
style={{ marginBottom: '10px' }}
/>
</div>
<div style={{ textAlign: 'right' }}>
<Filter fields={searchFields} callback={this.fetchGroups} />
</div>
<div style={{ backgroundColor: '#ffffff', borderRadius: 5 }}>
<Table
columns={this.columns}
rowKey={record => record.id}
dataSource={data.deviceGroups}
pagination={{
...pagination,
size: 'small',
total: data.count,
pageSize: 10,
showTotal: (total, range) =>
`showing ${range[0]}-${range[1]} of ${total} groups`,
}}
loading={loading}
onChange={this.handleTableChange}
rowSelection={this.rowSelection}
/>
</div>
</div>
);
}
}
export default withConfigContext(GroupsTable);

View File

@ -1,59 +0,0 @@
/*
* Copyright (c) 2020, Entgra (pvt) Ltd. (http://entgra.io) All Rights Reserved.
*
* Entgra (pvt) Ltd. licenses this file to you 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.
*/
import React from 'react';
import { PageHeader, Typography, Breadcrumb, Icon } from 'antd';
import { Link } from 'react-router-dom';
import GroupsTable from './components/GroupsTable';
const { Paragraph } = Typography;
class Groups extends React.Component {
routes;
constructor(props) {
super(props);
this.routes = props.routes;
}
render() {
return (
<div>
<PageHeader style={{ paddingTop: 0 }}>
<Breadcrumb style={{ paddingBottom: 16 }}>
<Breadcrumb.Item>
<Link to="/entgra">
<Icon type="home" /> Home
</Link>
</Breadcrumb.Item>
<Breadcrumb.Item>Groups</Breadcrumb.Item>
</Breadcrumb>
<div className="wrap">
<h3>Groups</h3>
<Paragraph>All device groups.</Paragraph>
</div>
</PageHeader>
<div style={{ background: '#f0f2f5', padding: 24, minHeight: 720 }}>
<GroupsTable />
</div>
</div>
);
}
}
export default Groups;

View File

@ -1,197 +0,0 @@
/*
* Copyright (c) 2020, Entgra (pvt) Ltd. (http://entgra.io) All Rights Reserved.
*
* Entgra (pvt) Ltd. licenses this file to you 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.
*/
import React from 'react';
import axios from 'axios';
import { Icon, Table } from 'antd';
import TimeAgo from 'javascript-time-ago';
// Load locale-specific relative date/time formatting rules.
import en from 'javascript-time-ago/locale/en';
import { withConfigContext } from '../../../../../../components/ConfigContext';
import { handleApiError } from '../../../../../../services/utils/errorHandler';
let config = null;
const columns = [
{
title: 'Device',
dataIndex: 'deviceName',
width: 100,
sorter: (a, b) => a.deviceName.localeCompare(b.deviceName),
},
{
title: 'Type',
dataIndex: 'deviceType',
key: 'type',
// eslint-disable-next-line react/display-name
render: type => {
const defaultPlatformIcons = config.defaultPlatformIcons;
let icon = defaultPlatformIcons.default.icon;
let color = defaultPlatformIcons.default.color;
let theme = defaultPlatformIcons.default.theme;
if (defaultPlatformIcons.hasOwnProperty(type)) {
icon = defaultPlatformIcons[type].icon;
color = defaultPlatformIcons[type].color;
theme = defaultPlatformIcons[type].theme;
}
return (
<span style={{ fontSize: 20, color: color, textAlign: 'center' }}>
<Icon type={icon} theme={theme} />
</span>
);
},
},
{
title: 'Description',
dataIndex: 'description',
key: 'description',
},
];
class NotificationsTable extends React.Component {
constructor(props) {
super(props);
config = this.props.context;
TimeAgo.addLocale(en);
this.state = {
data: [],
pagination: {},
loading: false,
selectedRows: [],
paramsObj: {},
};
}
rowSelection = {
onChange: (selectedRowKeys, selectedRows) => {
this.setState({
selectedRows: selectedRows,
});
},
};
componentDidMount() {
this.fetchData();
}
// Rerender component when parameters change
componentDidUpdate(prevProps, prevState, snapshot) {
if (prevProps.notificationType !== this.props.notificationType) {
this.fetchData();
}
}
// fetch data from api
fetchData = (params = {}) => {
// const policyReportData = this.props;
this.setState({ loading: true });
// get current page
const currentPage = params.hasOwnProperty('page') ? params.page : 1;
let extraParams;
extraParams = {
offset: 10 * (currentPage - 1), // calculate the offset
limit: 10,
};
const encodedExtraParams = Object.keys(extraParams)
.map(key => key + '=' + extraParams[key])
.join('&');
let apiUrl;
if (this.props.notificationType === 'unread') {
apiUrl =
window.location.origin +
config.serverConfig.invoker.uri +
config.serverConfig.invoker.deviceMgt +
'/notifications?status=NEW&' +
encodedExtraParams;
} else {
apiUrl =
window.location.origin +
config.serverConfig.invoker.uri +
config.serverConfig.invoker.deviceMgt +
'/notifications?' +
encodedExtraParams;
}
// send request to the invoker
axios
.get(apiUrl)
.then(res => {
if (res.status === 200) {
const pagination = { ...this.state.pagination };
this.setState({
loading: false,
data: res.data.data,
pagination,
});
}
})
.catch(error => {
handleApiError(error, 'Error occurred while trying to load devices.');
this.setState({ loading: false });
});
};
handleTableChange = (pagination, filters, sorter) => {
const pager = { ...this.state.pagination };
pager.current = pagination.current;
this.setState({
pagination: pager,
});
this.fetchData({
results: pagination.pageSize,
page: pagination.current,
sortField: sorter.field,
sortOrder: sorter.order,
...filters,
});
};
render() {
let { data, pagination, loading } = this.state;
return (
<div>
<Table
columns={columns}
rowKey={record => record.id}
dataSource={data.notifications}
pagination={{
...pagination,
size: 'small',
// position: "top",
total: data.count,
showTotal: (total, range) =>
`showing ${range[0]}-${range[1]} of ${total} notifications`,
}}
loading={loading}
onChange={this.handleTableChange}
rowSelection={this.rowSelection}
/>
</div>
);
}
}
export default withConfigContext(NotificationsTable);

View File

@ -1,132 +0,0 @@
/*
* Copyright (c) 2020, Entgra (pvt) Ltd. (http://entgra.io) All Rights Reserved.
*
* Entgra (pvt) Ltd. licenses this file to you 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.
*/
import React from 'react';
import {
message,
notification,
Button,
PageHeader,
Breadcrumb,
Icon,
Radio,
} from 'antd';
import { withConfigContext } from '../../../../components/ConfigContext';
import axios from 'axios';
import { Link } from 'react-router-dom';
import NotificationsTable from './Components/NotificationsTable';
class Notifications extends React.Component {
constructor(props) {
super(props);
this.state = {
visible: false,
data: [],
notificationType: 'all',
};
}
handleModeChange = e => {
const notificationType = e.target.value;
this.setState({ notificationType });
};
clearNotifications = () => {
const config = this.props.context;
axios
.put(
window.location.origin +
config.serverConfig.invoker.uri +
config.serverConfig.invoker.deviceMgt +
'/notifications/clear-all',
{ 'Content-Type': 'application/json; charset=utf-8' },
)
.then(res => {
if (res.status === 200) {
notification.success({
message: 'Done',
duration: 0,
description: 'All notifications are cleared.',
});
}
})
.catch(error => {
if (error.hasOwnProperty('response') && error.response.status === 401) {
// todo display a popop with error
message.error('You are not logged in');
window.location.href = window.location.origin + '/entgra/login';
} else {
notification.error({
message: 'There was a problem',
duration: 0,
description: 'Error occurred while trying to clear notifications.',
});
}
});
};
render() {
const { notificationType } = this.state;
return (
<div>
<PageHeader style={{ paddingTop: 0 }}>
<Breadcrumb style={{ paddingBottom: 16 }}>
<Breadcrumb.Item>
<Link to="/entgra">
<Icon type="home" /> Home
</Link>
</Breadcrumb.Item>
<Breadcrumb.Item>Notifications</Breadcrumb.Item>
</Breadcrumb>
<div className="wrap" style={{ marginBottom: '10px' }}>
<h3>DEVICE NOTIFICATIONS</h3>
<Radio.Group
onChange={this.handleModeChange}
defaultValue={'all'}
value={notificationType}
style={{ marginBottom: 8, marginRight: 5 }}
>
<Radio.Button value={'all'}>All Notifications</Radio.Button>
<Radio.Button value={'unread'}>Unread Notifications</Radio.Button>
</Radio.Group>
<Button
type="primary"
style={{
marginRight: 10,
marginBottom: 8,
display: notificationType === 'unread' ? 'inline' : 'none',
}}
onClick={this.clearNotifications}
>
Clear All Notifications
</Button>
<div style={{ backgroundColor: '#ffffff', borderRadius: 5 }}>
<NotificationsTable notificationType={notificationType} />
</div>
</div>
</PageHeader>
<div
style={{ background: '#f0f2f5', padding: 24, minHeight: 720 }}
></div>
</div>
);
}
}
export default withConfigContext(Notifications);

View File

@ -1,260 +0,0 @@
/*
* Copyright (c) 2020, Entgra (pvt) Ltd. (http://entgra.io) All Rights Reserved.
*
* Entgra (pvt) Ltd. licenses this file to you 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.
*/
import React from 'react';
import { withConfigContext } from '../../../../../../../../components/ConfigContext';
import { Button, Col, Form, message, notification, Radio, Select } from 'antd';
import axios from 'axios';
const { Option } = Select;
class AssignGroups extends React.Component {
constructor(props) {
super(props);
this.config = this.props.context;
this.userSelector = React.createRef();
this.roleSelector = React.createRef();
this.state = {
roles: [],
users: [],
groups: [],
};
}
componentDidMount() {
this.getRolesList();
this.getGroupsList();
}
handleSetUserRoleFormItem = event => {
if (event.target.value === 'roleSelector') {
this.roleSelector.current.style.cssText = 'display: block;';
this.userSelector.current.style.cssText = 'display: none;';
} else {
this.roleSelector.current.style.cssText = 'display: none;';
this.userSelector.current.style.cssText = 'display: block;';
}
};
// generate payload by adding Assign Groups
onHandleContinue = (e, formName) => {
this.props.form.validateFields((err, values) => {
if (!err) {
if (typeof values.roles === 'string') {
values.roles = [values.roles];
}
if (!values.users) {
delete values.users;
}
if (values.deviceGroups === 'NONE') {
delete values.deviceGroups;
}
this.props.getPolicyPayloadData(formName, values);
this.props.getNextStep();
}
});
};
getRolesList = () => {
let apiURL =
window.location.origin +
this.config.serverConfig.invoker.uri +
this.config.serverConfig.invoker.deviceMgt +
'/roles?user-store=PRIMARY&limit=100';
axios
.get(apiURL)
.then(res => {
if (res.status === 200) {
this.setState({
roles: res.data.data.roles,
});
}
})
.catch(error => {
if (error.hasOwnProperty('response') && error.response.status === 401) {
// todo display a popop with error
message.error('You are not logged in');
window.location.href = window.location.origin + '/entgra/login';
} else {
notification.error({
message: 'There was a problem',
duration: 0,
description: 'Error occurred while trying to load roles.',
});
}
});
};
getUsersList = value => {
let apiURL =
window.location.origin +
this.config.serverConfig.invoker.uri +
this.config.serverConfig.invoker.deviceMgt +
'/users/search/usernames?filter=' +
value +
'&domain=Primary';
axios
.get(apiURL)
.then(res => {
if (res.status === 200) {
let users = JSON.parse(res.data.data);
this.setState({
users,
});
}
})
.catch(error => {
if (error.hasOwnProperty('response') && error.response.status === 401) {
// todo display a popop with error
message.error('You are not logged in');
window.location.href = window.location.origin + '/entgra/login';
} else {
notification.error({
message: 'There was a problem',
duration: 0,
description: 'Error occurred while trying to load users.',
});
}
});
};
// fetch data from api
getGroupsList = () => {
let apiUrl =
window.location.origin +
this.config.serverConfig.invoker.uri +
this.config.serverConfig.invoker.deviceMgt +
'/admin/groups';
// send request to the invokerss
axios
.get(apiUrl)
.then(res => {
if (res.status === 200) {
this.setState({
groups: res.data.data.deviceGroups,
});
}
})
.catch(error => {
if (error.hasOwnProperty('response') && error.response.status === 401) {
// todo display a popop with error
message.error('You are not logged in');
window.location.href = window.location.origin + '/entgra/login';
} else {
notification.error({
message: 'There was a problem',
duration: 0,
description: 'Error occurred while trying to load device groups.',
});
}
});
};
render() {
const { getFieldDecorator } = this.props.form;
return (
<div>
<div>
<Radio.Group
defaultValue={'roleSelector'}
onChange={this.handleSetUserRoleFormItem}
>
<Radio value="roleSelector">Set User role(s)</Radio>
<Radio value="userSelector">Set User(s)</Radio>
</Radio.Group>
<div
id={'roleSelector'}
ref={this.roleSelector}
style={{ display: 'block' }}
>
<Form.Item>
{getFieldDecorator('roles', {
initialValue: 'ANY',
})(
<Select
mode="multiple"
style={{ width: '100%' }}
defaultActiveFirstOption={true}
>
<Option value={'ANY'}>Any</Option>
{this.state.roles.map(role => (
<Option key={role} value={role}>
{role}
</Option>
))}
</Select>,
)}
</Form.Item>
</div>
<div
id={'userSelector'}
ref={this.userSelector}
style={{ display: 'none' }}
>
<Form.Item>
{getFieldDecorator('users', {})(
<Select
mode="multiple"
style={{ width: '100%' }}
onSearch={this.getUsersList}
>
{this.state.users.map(user => (
<Option key={user.username} value={user.username}>
{user.username}
</Option>
))}
</Select>,
)}
</Form.Item>
</div>
</div>
<Form.Item label={'Select Groups'} style={{ display: 'block' }}>
{getFieldDecorator('deviceGroups', {
initialValue: 'NONE',
})(
<Select mode="multiple" style={{ width: '100%' }}>
<Option value={'NONE'}>NONE</Option>
{this.state.groups.map(group => (
<Option key={group.name} value={group.name}>
{group.name}
</Option>
))}
</Select>,
)}
</Form.Item>
<Col span={16} offset={20}>
<div style={{ marginTop: 24 }}>
<Button style={{ marginRight: 8 }} onClick={this.props.getPrevStep}>
Back
</Button>
<Button
type="primary"
onClick={e => this.onHandleContinue(e, 'groupData')}
>
Continue
</Button>
</div>
</Col>
</div>
);
}
}
export default withConfigContext(Form.create()(AssignGroups));

View File

@ -1,971 +0,0 @@
/*
* Copyright (c) 2020, Entgra (pvt) Ltd. (http://entgra.io) All Rights Reserved.
*
* Entgra (pvt) Ltd. licenses this file to you 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.
*/
import React from 'react';
import {
Tabs,
Row,
Col,
Switch,
Input,
Typography,
Form,
Collapse,
Checkbox,
Select,
Tooltip,
Icon,
Table,
Alert,
Upload,
Popconfirm,
Button,
Radio,
} from 'antd';
import { withConfigContext } from '../../../../../../../../components/ConfigContext';
import '../../../../styles.css';
import moment from 'moment';
const { Text, Title, Paragraph } = Typography;
const { TabPane } = Tabs;
const { Option } = Select;
const { TextArea } = Input;
const subPanelpayloadAttributesforCheckboxes = {};
const subPanelpayloadAttributesforSelect = {};
let formContainers = {};
class ConfigureProfile extends React.Component {
constructor(props) {
super(props);
this.config = this.props.context;
this.state = {
loading: false,
isDisplayMain: 'none',
activePanelKeys: [],
activeSubPanelKeys: [],
subFormList: [],
subPanelpayloadAttributes: {},
count: 0,
dataArray: [],
customInputDataArray: [],
inputTableDataSources: {},
addPolicyForms: null,
};
}
// convert time from 24h format to 12h format
timeConverter = time => {
time = time
.toString()
.match(/^([01]\d|2[0-3])(:)([0-5]\d)(:[0-5]\d)?$/) || [time];
if (time.length > 1) {
time = time.slice(1);
time[5] = +time[0] < 12 ? ' AM' : ' PM';
time[0] = +time[0] % 12 || 12;
}
return time.join('');
};
// get Option value from start Time, end Time and time difference between 2 values
getOptionForTimeSelectors = (startTimeValue, endTimeValue, timeIncrement) => {
let timeOptions = [];
let time = new Date(
moment()
.startOf('day')
.format('YYYY/MM/DD'),
);
let tempValue = startTimeValue;
time.setMinutes(time.getMinutes() + tempValue);
let startOption = (
<Option value={String(tempValue)}>
{this.timeConverter(
`${String(time)
.split(' ')[4]
.substring(0, 5)}`,
)}
</Option>
);
timeOptions.push(startOption);
while (tempValue !== endTimeValue) {
time = new Date(
moment()
.startOf('day')
.format('YYYY/MM/DD'),
);
tempValue += timeIncrement;
if (tempValue > 1440) {
tempValue = 0;
continue;
}
time.setMinutes(time.getMinutes() + tempValue);
let option = (
<Option value={String(tempValue)}>
{this.timeConverter(
`${String(time)
.split(' ')[4]
.substring(0, 5)}`,
)}
</Option>
);
timeOptions.push(option);
}
return timeOptions;
};
// handle items which handle from radio buttons
handleRadioPanel = (e, subPanel) => {
{
subPanel.map((panel, i) => {
if (panel.value === e.target.value) {
document.getElementById(panel.value).style.display = 'block';
} else {
document.getElementById(panel.value).style.display = 'none';
}
});
}
};
// handle items which handle from select options
handleSelectedPanel = (e, subPanel) => {
{
subPanel.map((panel, i) => {
if (panel.id === e) {
document.getElementById(panel.id).style.display = 'block';
} else {
document.getElementById(panel.id).style.display = 'none';
}
});
}
};
// handle items which handle from checkbox
handleSubPanel = e => {
if (e.target.checked) {
let joined = this.state.activeSubPanelKeys.concat(e.target.id);
this.setState({ activeSubPanelKeys: joined });
} else {
let index = this.state.activeSubPanelKeys.indexOf(e.target.id);
if (index !== -1) {
this.state.activeSubPanelKeys.splice(index, 1);
let removed = this.state.activeSubPanelKeys;
this.setState({ activeSubPanelKeys: removed });
}
}
};
// handle Switch on off button
handleMainPanel = (e, ref) => {
if (e) {
let joined = this.state.activePanelKeys.concat(ref);
this.setState({ activePanelKeys: joined });
} else {
let index = this.state.activePanelKeys.indexOf(ref);
if (index !== -1) {
this.state.activePanelKeys.splice(index, 1);
let removed = this.state.activePanelKeys;
this.setState({ activePanelKeys: removed });
}
}
};
handleCustomInputTable = event => {
const { count, customInputDataArray } = this.state;
const newData = [
{
key: count,
CERT_NAME: `${event.file.name}`,
},
];
this.setState({
customInputDataArray: [...customInputDataArray, newData],
count: count + 1,
});
};
handleAdd = array => {
const { count, inputTableDataSources } = this.state;
const newData = [
{
key: count,
},
];
inputTableDataSources[array].push(newData);
Object.defineProperty(inputTableDataSources, array, {
value: inputTableDataSources[array],
});
this.setState({
inputTableDataSources,
count: count + 1,
});
};
getColumns = ({ getFieldDecorator }, arr) => {
const columnArray = [];
const actionColumn = [
{
title: '',
dataIndex: 'operation',
render: (name, row) => (
<Form.Item>
<Popconfirm title="Sure to delete?">
<a>
<Text type="danger">
<Icon type="delete" />
</Text>
</a>
</Popconfirm>
</Form.Item>
),
},
];
Object.values(arr).map((columnData, c) => {
if (columnData.type === 'input') {
const column = {
title: `${columnData.name}`,
dataIndex: `${columnData.key}`,
key: `${columnData.key}`,
render: (name, row, i) => (
<Form.Item>
{getFieldDecorator(`${columnData.key}${i}`, {})(
<Input
type={columnData.others.inputType}
placeholder={columnData.others.placeholder}
/>,
)}
</Form.Item>
),
};
columnArray.push(column);
} else if (columnData.type === 'upload') {
const column = {
title: `${columnData.name}`,
dataIndex: `${columnData.key}`,
key: `${columnData.key}`,
render: (name, row, i) => (
<Form.Item>
{getFieldDecorator(`${columnData.key}${i}`, {})(
<Upload>
<Button>
<Icon type="upload" /> Choose file
</Button>
</Upload>,
)}
</Form.Item>
),
};
columnArray.push(column);
} else if (columnData.type === 'select') {
const column = {
title: `${columnData.name}`,
dataIndex: `${columnData.key}`,
key: `${columnData.key}`,
render: (name, row, i) => (
<Form.Item>
{getFieldDecorator(`${columnData.key}${i}`, {
initialValue: columnData.others.initialDataIndex,
})(
<Select>
{columnData.others.option.map((option, i) => {
return (
<Option key={i} value={option.key}>
{option.value}
</Option>
);
})}
</Select>,
)}
</Form.Item>
),
};
columnArray.push(column);
}
});
const columns = columnArray.concat(actionColumn);
return columns;
};
// generate payload by adding policy configurations
onHandleContinue = (e, formname) => {
const allFields = this.props.form.getFieldsValue();
let activeFields = [];
let subContentsList = {};
for (let i = 0; i < this.state.activePanelKeys.length; i++) {
Object.keys(allFields).map(key => {
if (key.includes(`${this.state.activePanelKeys[i]}-`)) {
if (
subPanelpayloadAttributesforCheckboxes.hasOwnProperty(
`${this.state.activePanelKeys[i]}`,
)
) {
Object.entries(
subPanelpayloadAttributesforCheckboxes[
this.state.activePanelKeys[i]
],
).map(([subPanel, subContent]) => {
subContentsList[subContent] = {};
if (
allFields[`${this.state.activePanelKeys[i]}-${subPanel}`] ===
true
) {
activeFields.push(key);
} else if (!key.includes(`-${subPanel}`)) {
activeFields.push(key);
}
});
} else if (
subPanelpayloadAttributesforSelect.hasOwnProperty(
`${this.state.activePanelKeys[i]}`,
)
) {
Object.entries(
subPanelpayloadAttributesforSelect[this.state.activePanelKeys[i]],
).map(([subPanelSwitch, subPanels]) => {
if (
subPanels.includes(allFields[subPanelSwitch]) &&
key.includes(`${subPanelSwitch}-${allFields[subPanelSwitch]}-`)
) {
activeFields.push(key);
} else {
for (let panel of subPanels) {
if (!key.includes(`${subPanelSwitch}-${panel}-`)) {
activeFields.push(key);
}
}
}
});
} else {
activeFields.push(key);
}
}
});
}
// validate fields and get profile features list
this.props.form.validateFields(activeFields, (err, values) => {
if (!err) {
let profileFeaturesList = [];
for (let i = 0; i < this.state.activePanelKeys.length; i++) {
let content = {};
Object.entries(values).map(([key, value]) => {
if (key.includes(`${this.state.activePanelKeys[i]}-`)) {
if (
subPanelpayloadAttributesforCheckboxes.hasOwnProperty(
`${this.state.activePanelKeys[i]}`,
)
) {
Object.entries(
subPanelpayloadAttributesforCheckboxes[
this.state.activePanelKeys[i]
],
).map(([subPanel, contentKey]) => {
if (key.includes(`-${subPanel}-`)) {
subContentsList[contentKey][
key.replace(
`${this.state.activePanelKeys[i]}-${subPanel}-`,
'',
)
] = value;
content = { ...content, ...subContentsList };
} else {
content[
key.replace(`${this.state.activePanelKeys[i]}-`, '')
] = value;
}
});
}
if (this.state.activePanelKeys[i] in formContainers) {
formContainers[this.state.activePanelKeys[i]].forEach(
subFeature => {
if (
key.includes(
`${this.state.activePanelKeys[i]}-${subFeature}-`,
)
) {
let subFormContent = {};
subFormContent[
key.replace(
`${this.state.activePanelKeys[i]}-${subFeature}-`,
'',
)
] = value;
let feature = {
featureCode: subFeature,
deviceType: 'android',
content: subFormContent,
};
profileFeaturesList.push(feature);
}
},
);
}
if (
subPanelpayloadAttributesforSelect.hasOwnProperty(
`${this.state.activePanelKeys[i]}`,
)
) {
Object.keys(
subPanelpayloadAttributesforSelect[
this.state.activePanelKeys[i]
],
).map(subPanelSwitch => {
if (
key.includes(`${subPanelSwitch}-${values[subPanelSwitch]}-`)
) {
content[
key.replace(
`${subPanelSwitch}-${values[subPanelSwitch]}-`,
'',
)
] = value;
} else {
content[
key.replace(`${this.state.activePanelKeys[i]}-`, '')
] = value;
}
});
} else {
content[
key.replace(`${this.state.activePanelKeys[i]}-`, '')
] = value;
}
}
});
if (!(this.state.activePanelKeys[i] in formContainers)) {
let feature = {
featureCode: this.state.activePanelKeys[i],
deviceType: 'android',
content: content,
};
profileFeaturesList.push(feature);
}
}
this.props.getPolicyPayloadData(formname, profileFeaturesList);
this.props.getNextStep();
}
});
};
// generate form items
getPanelItems = (panel, panelId) => {
const { getFieldDecorator } = this.props.form;
const subPanelListforCheckbox = {};
const subPanelListforSelect = {};
return panel.map((item, k) => {
switch (item.type) {
case 'select':
if (item.optional.hasOwnProperty('subPanel')) {
return (
<div>
<Form.Item
key={k}
label={
<span>
{item.label}&nbsp;
<Tooltip title={item.tooltip} placement="right">
<Icon type="question-circle-o" />
</Tooltip>
</span>
}
style={{ display: 'block' }}
>
{getFieldDecorator(`${item.id}`, {
initialValue: `${item.optional.option[0].name}`,
})(
<Select
onChange={e =>
this.handleSelectedPanel(e, item.optional.subPanel)
}
>
{item.optional.option.map((option, i) => {
return (
<Option key={i} value={option.value}>
{option.name}
</Option>
);
})}
</Select>,
)}
</Form.Item>
<div className={'sub-panel-container'}>
{item.optional.subPanel.map((panel, i) => {
return (
<div
id={panel.id}
key={i}
style={
panel.id === item.optional.initialDataIndex
? { display: 'block' }
: { display: 'none' }
}
>
{this.getPanelItems(panel.panelItem)}
</div>
);
})}
</div>
</div>
);
}
return (
<Form.Item
key={k}
label={
<span>
{item.label}&nbsp;
<Tooltip title={item.tooltip} placement="right">
<Icon type="question-circle-o" />
</Tooltip>
</span>
}
style={{ display: 'block' }}
>
{getFieldDecorator(`${item.id}`, {
initialValue: `${item.optional.option[0].name}`,
})(
<Select>
{item.optional.option.map((option, i) => {
return (
<Option key={i} value={option.value}>
{option.name}
</Option>
);
})}
</Select>,
)}
</Form.Item>
);
case 'timeSelector':
return (
<Form.Item
key={k}
label={
<span>
{item.label}&nbsp;
<Tooltip title={item.tooltip} placement="right">
<Icon type="question-circle-o" />
</Tooltip>
</span>
}
style={{ display: 'block' }}
>
{getFieldDecorator(`${item.id}`, {
initialValue: item.optional.initialDataIndex,
})(
<Select>
{this.getOptionForTimeSelectors(
item.optional.firstOptionValue,
item.optional.lastOptionValue,
item.optional.valueDifference,
)}
</Select>,
)}
</Form.Item>
);
case 'input':
return (
<Form.Item
key={k}
label={
<span>
{item.label}&nbsp;
<Tooltip title={item.tooltip} placement="right">
<Icon type="question-circle-o" />
</Tooltip>
</span>
}
style={{ display: 'block' }}
>
{getFieldDecorator(`${item.id}`, {
initialValue: null,
rules: [
{
pattern: new RegExp(`${item.optional.rules.regex}`),
message: `${item.optional.rules.validationMsg}`,
},
],
})(<Input placeholder={item.optional.placeholder} />)}
</Form.Item>
);
case 'checkbox':
if (item.optional.hasOwnProperty('subPanel')) {
return (
<div key={k}>
<Collapse
bordered={false}
activeKey={this.state.activeSubPanelKeys}
>
<Collapse.Panel
key={item.id}
showArrow={false}
style={{ border: 0 }}
header={
<Form.Item key={k}>
{getFieldDecorator(`${item.id}`, {
valuePropName: 'checked',
initialValue: item.optional.ischecked,
})(
<Checkbox onChange={this.handleSubPanel}>
<span>
{item.label}&nbsp;
<Tooltip title={item.tooltip} placement="right">
<Icon type="question-circle-o" />
</Tooltip>
</span>
</Checkbox>,
)}
</Form.Item>
}
>
<div>
<div>
{item.optional.subPanel.map((panel, i) => {
subPanelListforCheckbox[panel.others.itemSwitch] =
panel.others.itemPayload;
if (
subPanelpayloadAttributesforCheckboxes.hasOwnProperty(
panelId,
)
) {
Object.assign(
subPanelpayloadAttributesforCheckboxes[panelId],
subPanelListforCheckbox,
);
} else {
subPanelpayloadAttributesforCheckboxes[
panelId
] = subPanelListforCheckbox;
}
return (
<div key={i}>
{this.getPanelItems(panel.panelItem, panelId)}
</div>
);
})}
</div>
</div>
</Collapse.Panel>
</Collapse>
</div>
);
}
return (
<Form.Item key={k}>
{getFieldDecorator(`${item.id}`, {
valuePropName: 'checked',
initialValue: item.optional.ischecked,
})(
<Checkbox>
<span>
{item.label}&nbsp;
<Tooltip title={item.tooltip} placement="right">
<Icon type="question-circle-o" />
</Tooltip>
</span>
</Checkbox>,
)}
</Form.Item>
);
case 'textArea':
return (
<Form.Item
key={k}
label={
<span>
{item.label}&nbsp;
<Tooltip title={item.tooltip} placement="right">
<Icon type="question-circle-o" />
</Tooltip>
</span>
}
style={{ display: 'block' }}
>
{getFieldDecorator(`${item.id}`, {
initialValue: null,
})(
<TextArea
placeholder={item.optional.placeholder}
rows={item.optional.row}
/>,
)}
</Form.Item>
);
case 'radioGroup':
// eslint-disable-next-line no-case-declarations
let subPanelkeys = [];
return (
<div>
<Form.Item
key={k}
label={
<span>
{item.label}&nbsp;
<Tooltip title={item.tooltip} placement="right">
<Icon type="question-circle-o" />
</Tooltip>
</span>
}
style={{ display: 'block' }}
>
{getFieldDecorator(`${item.id}`, {
initialValue: `${item.optional.initialValue}`,
})(
<Radio.Group
onChange={e =>
this.handleRadioPanel(e, item.optional.radio)
}
>
{item.optional.radio.map((option, i) => {
return (
<Radio key={i} value={option.value}>
{option.name}
</Radio>
);
})}
</Radio.Group>,
)}
</Form.Item>
<div className={'sub-panel-container'}>
{item.optional.subPanel.map((panel, i) => {
if (panel.hasOwnProperty('others')) {
subPanelkeys.push(panel.id);
subPanelListforSelect[`${item.id}`] = subPanelkeys;
subPanelpayloadAttributesforSelect[
panelId
] = subPanelListforSelect;
}
return (
<div
key={i}
id={panel.id}
style={
panel.id === item.optional.initialValue
? { display: 'block' }
: { display: 'none' }
}
>
{this.getPanelItems(panel.panelItem)}
</div>
);
})}
</div>
</div>
);
case 'title':
return (
<Title key={k} level={4}>
{item.label}{' '}
</Title>
);
case 'paragraph':
return (
<Paragraph key={k} style={{ marginTop: 20 }}>
{item.label}{' '}
</Paragraph>
);
case 'alert':
return (
<Alert key={k} description={item.label} type="warning" showIcon />
);
case 'upload':
return (
<Form.Item
key={k}
label={
<span>
{item.label}&nbsp;
<Tooltip title={item.tooltip} placement="right">
<Icon type="question-circle-o" />
</Tooltip>
</span>
}
>
{getFieldDecorator('upload', {})(
<Upload>
<Button>
<Icon type="upload" /> Click to upload
</Button>
</Upload>,
)}
</Form.Item>
);
case 'inputTable':
if (
!(`${item.optional.dataSource}` in this.state.inputTableDataSources)
) {
Object.defineProperty(
this.state.inputTableDataSources,
`${item.optional.dataSource}`,
{ value: [], writable: true },
);
}
return (
<div key={k}>
<Button
onClick={() => this.handleAdd(item.optional.dataSource)}
type="primary"
style={{ marginBottom: 16 }}
>
<Icon type="plus-circle" />
{item.optional.button.name}
</Button>
<Table
id={item.id}
dataSource={
this.state.inputTableDataSources[item.optional.dataSource]
}
columns={this.getColumns(
{ getFieldDecorator },
item.optional.columns,
)}
/>
</div>
);
case 'customInputTable':
return (
<div key={k}>
<Upload onChange={this.handleCustomInputTable}>
<Button type="primary" style={{ marginBottom: 16 }}>
<Icon type="plus-circle" />
{item.optional.button.name}
</Button>
</Upload>
<Table
id={item.id}
dataSource={this.state.customInputDataArray}
columns={this.getColumns(
{ getFieldDecorator },
item.optional.columns,
)}
/>
</div>
);
default:
return null;
}
});
};
render() {
const { policyUIConfigurationsList } = this.props;
const { getFieldDecorator } = this.props.form;
return (
<div className="tab-container">
<Tabs tabPosition={'left'} size={'large'}>
{policyUIConfigurationsList.map((element, i) => {
return (
<TabPane tab={<span>{element.name}</span>} key={i}>
{Object.values(element.panels).map((panel, j) => {
panel = panel.panel;
let subForms = [];
return (
<div key={j}>
<Collapse
bordered={false}
activeKey={this.state.activePanelKeys}
>
<Collapse.Panel
key={panel.panelId}
showArrow={false}
style={{ border: 0 }}
header={
<div>
<Row>
<Col offset={0} span={14}>
<Title level={4}> {panel.title} </Title>
</Col>
<Col offset={8} span={1}>
<Switch
checkedChildren="ON"
unCheckedChildren="OFF"
onChange={e =>
this.handleMainPanel(
e,
`${panel.panelId}`,
)
}
/>
</Col>
</Row>
<Row>{panel.description}</Row>
</div>
}
>
{panel.hasOwnProperty('panelItem') && (
<div>
<Form name={panel.panelId}>
<Form.Item style={{ display: 'none' }}>
{getFieldDecorator(`${panel.panelId}`, {
initialValue: ' ',
})(<Input />)}
</Form.Item>
{this.getPanelItems(
panel.panelItem,
panel.panelId,
)}
</Form>
</div>
)}
{panel.hasOwnProperty('subFormLists') && (
<div>
{Object.values(panel.subFormLists).map(
(form, i) => {
subForms.push(form.id);
formContainers[`${panel.panelId}`] = subForms;
return (
<Form name={form.id} key={i}>
<Form.Item style={{ display: 'none' }}>
{getFieldDecorator(`${form.id}`, {
initialValue: ' ',
})(<Input />)}
</Form.Item>
{this.getPanelItems(form.panelItem)}
</Form>
);
},
)}
</div>
)}
</Collapse.Panel>
</Collapse>
</div>
);
})}
</TabPane>
);
})}
</Tabs>
<Col span={16} offset={20}>
<div style={{ marginTop: 24 }}>
<Button style={{ marginRight: 8 }} onClick={this.props.getPrevStep}>
Back
</Button>
<Button
type="primary"
onClick={e => this.onHandleContinue(e, 'configureProfileData')}
>
Continue
</Button>
</div>
</Col>
</div>
);
}
}
export default withConfigContext(Form.create()(ConfigureProfile));

View File

@ -1,87 +0,0 @@
/*
* Copyright (c) 2020, Entgra (pvt) Ltd. (http://entgra.io) All Rights Reserved.
*
* Entgra (pvt) Ltd. licenses this file to you 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.
*/
import React from 'react';
import { withConfigContext } from '../../../../../../../../components/ConfigContext';
import { Button, Col, Form, Input } from 'antd';
const { TextArea } = Input;
class PublishDevices extends React.Component {
constructor(props) {
super(props);
this.config = this.props.context;
}
onClickSavePolicy = (event, isPublish, formName) => {
this.props.form.validateFields((err, values) => {
if (!err) {
values.active = isPublish;
this.props.getPolicyPayloadData(formName, values);
}
});
};
render() {
const { getFieldDecorator } = this.props.form;
return (
<div>
<Form.Item
label={'Set a name to your policy *'}
style={{ display: 'block' }}
>
{getFieldDecorator('policyName', {
rules: [
{
pattern: new RegExp('^.{1,30}$'),
message: 'Should be 1-to-30 characters long',
},
],
})(<Input placeholder={'Should be 1 to 30 characters long'} />)}
</Form.Item>
<Form.Item label={'Add a Description'} style={{ display: 'block' }}>
{getFieldDecorator('description', {})(<TextArea rows={8} />)}
</Form.Item>
<Col span={16} offset={18}>
<div style={{ marginTop: 24 }}>
<Button style={{ marginRight: 8 }} onClick={this.props.getPrevStep}>
Back
</Button>
<Button
type="primary"
style={{ marginRight: 8 }}
onClick={e =>
this.onClickSavePolicy(e, true, 'publishDevicesData')
}
>
Save & Publish
</Button>
<Button
type="primary"
onClick={e =>
this.onClickSavePolicy(e, false, 'publishDevicesData')
}
>
Save
</Button>
</div>
</Col>
</div>
);
}
}
export default withConfigContext(Form.create()(PublishDevices));

View File

@ -1,123 +0,0 @@
/*
* Copyright (c) 2020, Entgra (pvt) Ltd. (http://entgra.io) All Rights Reserved.
*
* Entgra (pvt) Ltd. licenses this file to you 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.
*/
import React from 'react';
import axios from 'axios';
import { Card, Col, Icon, message, notification, Row } from 'antd';
import { withConfigContext } from '../../../../../../../../components/ConfigContext';
class SelectPlatform extends React.Component {
constructor(props) {
super(props);
this.config = this.props.context;
this.state = {
data: [],
loading: false,
};
}
componentDidMount() {
this.getDeviceTypes();
}
onClickCard = (e, type, formname) => {
this.props.getPolicyConfigJson(type);
let deviceType = {
deviceType: type,
};
this.props.getPolicyPayloadData(formname, deviceType);
};
// fetch data from api
getDeviceTypes() {
this.setState({ loading: true });
let apiUrl =
window.location.origin +
this.config.serverConfig.invoker.uri +
this.config.serverConfig.invoker.deviceMgt +
'/device-types';
// send request to the invokers
axios
.get(apiUrl)
.then(res => {
if (res.status === 200) {
this.setState({
loading: false,
data: JSON.parse(res.data.data),
});
}
})
.catch(error => {
if (error.hasOwnProperty('response') && error.response.status === 401) {
// todo display a popop with error
message.error('You are not logged in');
window.location.href = window.location.origin + '/entgra/login';
} else {
notification.error({
message: 'There was a problem',
duration: 0,
description: 'Error occurred while trying to load device types.',
});
}
this.setState({ loading: false });
});
}
render() {
const { data } = this.state;
const { Meta } = Card;
const itemCard = data.map(data => (
<Col span={5} key={data.id}>
<a>
<Card
size="default"
style={{ width: 150 }}
bordered={true}
onClick={e =>
this.onClickCard(e, data.name, 'selectedPlatformData')
}
cover={
<Icon
type={data.name === 'ios' ? 'apple' : data.name}
key="device-types"
style={{
color: '#ffffff',
backgroundColor: '#4b92db',
fontSize: '100px',
padding: '20px',
}}
/>
}
>
<Meta title={data.name} />
</Card>
</a>
</Col>
));
return (
<div>
<Row gutter={16}>{itemCard}</Row>
</div>
);
}
}
export default withConfigContext(SelectPlatform);

View File

@ -1,171 +0,0 @@
/*
* Copyright (c) 2020, Entgra (pvt) Ltd. (http://entgra.io) All Rights Reserved.
*
* Entgra (pvt) Ltd. licenses this file to you 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.
*/
import React from 'react';
import {
Button,
Col,
Form,
Icon,
message,
notification,
Radio,
Select,
Tooltip,
} from 'antd';
import { withConfigContext } from '../../../../../../../../components/ConfigContext';
import axios from 'axios';
const { Option } = Select;
class SelectPolicyType extends React.Component {
constructor(props) {
super(props);
this.config = this.props.context;
this.state = {
correctivePoliciesList: [],
};
}
componentDidMount() {
this.fetchPolicies();
}
// generate payload using Select policy type
onHandleContinue = (e, formName) => {
this.props.form.validateFields((err, values) => {
if (!err) {
if (values.correctiveActions === 'NONE') {
values.correctiveActions = [];
}
this.props.getPolicyPayloadData(formName, values);
this.props.getNextStep();
}
});
};
fetchPolicies = () => {
let apiUrl =
window.location.origin +
this.config.serverConfig.invoker.uri +
this.config.serverConfig.invoker.deviceMgt +
'/policies';
// send request to the invokerss
axios
.get(apiUrl)
.then(res => {
if (res.status === 200) {
let policies = res.data.data.policies;
let correctivePolicies = [];
for (let i = 0; i < policies.length; i++) {
if (policies[i].policyType === 'CORRECTIVE') {
correctivePolicies.push(
<Option key={policies[i].profileId}>
{policies[i].policyName}
</Option>,
);
}
}
this.setState({
correctivePoliciesList: correctivePolicies,
});
}
})
.catch(error => {
if (error.hasOwnProperty('response') && error.response.status === 401) {
// todo display a popop with error
message.error('You are not logged in');
window.location.href = window.location.origin + '/entgra/login';
} else {
notification.error({
message: 'There was a problem',
duration: 0,
description: 'Error occurred while trying to load policies.',
});
}
this.setState({ loading: false });
});
};
handlePolicyTypes = event => {
if (event.target.value === 'GENERAL') {
document.getElementById('generalPolicySubPanel').style.display = 'block';
} else {
document.getElementById('generalPolicySubPanel').style.display = 'none';
}
};
render() {
const { getFieldDecorator } = this.props.form;
return (
<div>
<Form.Item style={{ display: 'block' }}>
{getFieldDecorator('policyType', {
initialValue: 'GENERAL',
})(
<Radio.Group onChange={this.handlePolicyTypes}>
<Radio value="GENERAL">General Policy</Radio>
<Radio value="CORRECTIVE">Corrective Policy</Radio>
</Radio.Group>,
)}
</Form.Item>
<div id="generalPolicySubPanel" style={{ display: 'block' }}>
<Form.Item
label={
<span>
Select Corrective Policy&nbsp;
<Tooltip
title={
'Select the corrective policy to be applied when this general policy is violated'
}
placement="right"
>
<Icon type="question-circle-o" />
</Tooltip>
</span>
}
>
{getFieldDecorator('correctiveActions', {
initialValue: 'NONE',
})(
<Select style={{ width: '100%' }}>
<Option value="NONE">None</Option>
{this.state.correctivePoliciesList}
</Select>,
)}
</Form.Item>
</div>
<Col span={16} offset={20}>
<div style={{ marginTop: 24 }}>
<Button style={{ marginRight: 8 }} onClick={this.props.getPrevStep}>
Back
</Button>
<Button
type="primary"
onClick={e => this.onHandleContinue(e, 'policyTypeData')}
>
Continue
</Button>
</div>
</Col>
</div>
);
}
}
export default withConfigContext(Form.create()(SelectPolicyType));

View File

@ -1,233 +0,0 @@
/*
* Copyright (c) 2020, Entgra (pvt) Ltd. (http://entgra.io) All Rights Reserved.
*
* Entgra (pvt) Ltd. licenses this file to you 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.
*/
import React from 'react';
import { Form, Row, Col, Card, Steps, message, notification } from 'antd';
import { withConfigContext } from '../../../../../../components/ConfigContext';
import SelectPlatform from './components/SelectPlatform';
import ConfigureProfile from './components/ConfigureProfile';
import SelectPolicyType from './components/SelectPolicyType';
import AssignGroups from './components/AssignGroups';
import PublishDevices from './components/PublishDevices';
import axios from 'axios';
const { Step } = Steps;
class AddPolicy extends React.Component {
constructor(props) {
super(props);
this.config = this.props.context;
this.state = {
currentStepIndex: 0,
isLoading: false,
policyUIConfigurationsList: [],
newPolicyPayload: { compliance: 'enforce' },
policyProfile: {},
payloadData: {},
};
}
getPolicyPayloadData = (dataName, dataValue) => {
Object.defineProperty(this.state.payloadData, dataName, {
value: dataValue,
writable: true,
});
if (dataName === 'publishDevicesData') {
this.createPayload();
}
};
createPayload = () => {
const {
publishDevicesData,
selectedPlatformData,
configureProfileData,
policyTypeData,
groupData,
} = this.state.payloadData;
const profile = {
profileName: publishDevicesData.policyName,
deviceType: selectedPlatformData.deviceType,
profileFeaturesList: configureProfileData,
};
const payload = {
policyName: publishDevicesData.policyName,
description: publishDevicesData.description,
compliance: 'enforce',
ownershipType: null,
active: publishDevicesData.active,
...policyTypeData,
profile: profile,
...groupData,
};
this.onAddNewPolicy(JSON.stringify(payload));
};
getPolicyConfigJson = type => {
this.setState({ isLoading: true });
let apiUrl =
window.location.origin +
this.config.serverConfig.invoker.uri +
this.config.serverConfig.invoker.deviceMgt +
'/device-types/' +
type +
'/ui-policy-configurations';
// send request to the invokers
axios
.get(apiUrl)
.then(res => {
if (res.status === 200) {
this.setState({
isLoading: false,
policyUIConfigurationsList: JSON.parse(res.data.data),
currentStepIndex: 1,
});
}
})
.catch(error => {
if (error.hasOwnProperty('response') && error.response.status === 401) {
// todo display a popop with error
message.error('You are not logged in');
window.location.href = window.location.origin + '/entgra/login';
} else {
notification.error({
message: 'There was a problem',
duration: 0,
description: 'Error occurred while trying to load Policy details.',
});
}
this.setState({ isLoading: false });
});
};
onAddNewPolicy = value => {
axios
.post(
window.location.origin +
this.config.serverConfig.invoker.uri +
this.config.serverConfig.invoker.deviceMgt +
'/policies/',
value,
{ headers: { 'Content-Type': 'application-json' } },
)
.then(res => {
if (res.status === 201) {
notification.success({
message: 'Done',
duration: 4,
description: 'Successfully added new Policy.',
});
}
})
.catch(error => {
if (error.hasOwnProperty('response') && error.response.status === 401) {
// todo display a popop with error
message.error('You are not logged in');
window.location.href = window.location.origin + '/entgra/login';
} else {
notification.error({
message: 'There was a problem',
duration: 0,
description: 'Error occurred while trying to add New Policy.',
});
}
});
};
getNextStep = () => {
const currentStepIndex = this.state.currentStepIndex + 1;
this.setState({ currentStepIndex });
};
getPrevStep = () => {
const currentStepIndex = this.state.currentStepIndex - 1;
this.setState({ currentStepIndex });
};
render() {
const { currentStepIndex, policyUIConfigurationsList } = this.state;
return (
<div>
<Row>
<Col span={20} offset={2}>
<Steps style={{ minHeight: 32 }} current={currentStepIndex}>
<Step key="Platform" title="Select a Platform" />
<Step key="ProfileConfigure" title="Configure profile" />
<Step key="PolicyType" title="Select policy type" />
<Step key="AssignGroups" title="Assign to groups" />
<Step key="Publish" title="Publish to devices" />
</Steps>
</Col>
<Col span={16} offset={4}>
<Card style={{ marginTop: 24 }}>
<div
style={{ display: currentStepIndex === 0 ? 'unset' : 'none' }}
>
<SelectPlatform
getPolicyConfigJson={this.getPolicyConfigJson}
getPolicyPayloadData={this.getPolicyPayloadData}
/>
</div>
<div
style={{ display: currentStepIndex === 1 ? 'unset' : 'none' }}
>
<ConfigureProfile
policyUIConfigurationsList={policyUIConfigurationsList}
getPolicyPayloadData={this.getPolicyPayloadData}
getPrevStep={this.getPrevStep}
getNextStep={this.getNextStep}
/>
</div>
<div
style={{ display: currentStepIndex === 2 ? 'unset' : 'none' }}
>
<SelectPolicyType
getPolicyPayloadData={this.getPolicyPayloadData}
getPrevStep={this.getPrevStep}
getNextStep={this.getNextStep}
/>
</div>
<div
style={{ display: currentStepIndex === 3 ? 'unset' : 'none' }}
>
<AssignGroups
getPolicyPayloadData={this.getPolicyPayloadData}
getPrevStep={this.getPrevStep}
getNextStep={this.getNextStep}
/>
</div>
<div
style={{ display: currentStepIndex === 4 ? 'unset' : 'none' }}
>
<PublishDevices
getPolicyPayloadData={this.getPolicyPayloadData}
getPrevStep={this.getPrevStep}
/>
</div>
</Card>
</Col>
</Row>
</div>
);
}
}
export default withConfigContext(
Form.create({ name: 'add-policy' })(AddPolicy),
);

View File

@ -1,269 +0,0 @@
/*
* Copyright (c) 2020, Entgra (pvt) Ltd. (http://entgra.io) All Rights Reserved.
*
* Entgra (pvt) Ltd. licenses this file to you 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.
*/
import React from 'react';
import { withConfigContext } from '../../../../../../../../components/ConfigContext';
import { Button, Col, Form, message, notification, Radio, Select } from 'antd';
import axios from 'axios';
const { Option } = Select;
class AssignGroups extends React.Component {
constructor(props) {
super(props);
this.config = this.props.context;
this.userSelector = React.createRef();
this.roleSelector = React.createRef();
this.state = {
roles: [],
users: [],
groups: [],
};
}
componentDidMount() {
this.getRolesList();
this.getGroupsList();
}
handleSetUserRoleFormItem = event => {
if (event.target.value === 'roleSelector') {
this.roleSelector.current.style.cssText = 'display: block;';
this.userSelector.current.style.cssText = 'display: none;';
} else {
this.roleSelector.current.style.cssText = 'display: none;';
this.userSelector.current.style.cssText = 'display: block;';
}
};
// generate payload by adding Assign Groups
onHandleContinue = (e, formName) => {
this.props.form.validateFields((err, values) => {
if (!err) {
if (typeof values.roles === 'string') {
values.roles = [values.roles];
}
if (!values.users) {
delete values.users;
}
if (values.deviceGroups === 'NONE') {
delete values.deviceGroups;
}
this.props.getPolicyPayloadData(formName, values);
this.props.getNextStep();
}
});
};
getRolesList = () => {
let apiURL =
window.location.origin +
this.config.serverConfig.invoker.uri +
this.config.serverConfig.invoker.deviceMgt +
'/roles?user-store=PRIMARY&limit=100';
axios
.get(apiURL)
.then(res => {
if (res.status === 200) {
this.setState({
roles: res.data.data.roles,
});
}
})
.catch(error => {
if (error.hasOwnProperty('response') && error.response.status === 401) {
// todo display a popop with error
message.error('You are not logged in');
window.location.href = window.location.origin + '/entgra/login';
} else {
notification.error({
message: 'There was a problem',
duration: 0,
description: 'Error occurred while trying to load roles.',
});
}
});
};
getUsersList = value => {
let apiURL =
window.location.origin +
this.config.serverConfig.invoker.uri +
this.config.serverConfig.invoker.deviceMgt +
'/users/search/usernames?filter=' +
value +
'&domain=Primary';
axios
.get(apiURL)
.then(res => {
if (res.status === 200) {
let users = JSON.parse(res.data.data);
this.setState({
users,
});
}
})
.catch(error => {
if (error.hasOwnProperty('response') && error.response.status === 401) {
// todo display a popop with error
message.error('You are not logged in');
window.location.href = window.location.origin + '/entgra/login';
} else {
notification.error({
message: 'There was a problem',
duration: 0,
description: 'Error occurred while trying to load users.',
});
}
});
};
// fetch data from api
getGroupsList = () => {
let apiUrl =
window.location.origin +
this.config.serverConfig.invoker.uri +
this.config.serverConfig.invoker.deviceMgt +
'/admin/groups';
// send request to the invokerss
axios
.get(apiUrl)
.then(res => {
if (res.status === 200) {
this.setState({
groups: res.data.data.deviceGroups,
});
}
})
.catch(error => {
if (error.hasOwnProperty('response') && error.response.status === 401) {
// todo display a popop with error
message.error('You are not logged in');
window.location.href = window.location.origin + '/entgra/login';
} else {
notification.error({
message: 'There was a problem',
duration: 0,
description: 'Error occurred while trying to load device groups.',
});
}
});
};
render() {
const { policyData } = this.props;
const { getFieldDecorator } = this.props.form;
let deviceGroups = null;
if (policyData.deviceGroups.length > 0) {
deviceGroups = policyData.deviceGroups;
} else {
deviceGroups = 'NONE';
}
return (
<div>
<div>
<Radio.Group
defaultValue={'roleSelector'}
onChange={this.handleSetUserRoleFormItem}
>
<Radio value="roleSelector">Set User role(s)</Radio>
<Radio value="userSelector">Set User(s)</Radio>
</Radio.Group>
<div
id={'roleSelector'}
ref={this.roleSelector}
style={{ display: 'block' }}
>
<Form.Item>
{getFieldDecorator('roles', {
initialValue: policyData.roles,
})(
<Select
mode="multiple"
style={{ width: '100%' }}
defaultActiveFirstOption={true}
>
<Option value={'ANY'}>Any</Option>
{this.state.roles.map(role => (
<Option key={role} value={role}>
{role}
</Option>
))}
</Select>,
)}
</Form.Item>
</div>
<div
id={'userSelector'}
ref={this.userSelector}
style={{ display: 'none' }}
>
<Form.Item>
{getFieldDecorator('users', {
initialValue: policyData.users,
})(
<Select
mode="multiple"
style={{ width: '100%' }}
onSearch={this.getUsersList}
>
{this.state.users.map(user => (
<Option key={user.username} value={user.username}>
{user.username}
</Option>
))}
</Select>,
)}
</Form.Item>
</div>
</div>
<Form.Item label={'Select Groups'} style={{ display: 'block' }}>
{getFieldDecorator('deviceGroups', {
initialValue: deviceGroups,
})(
<Select mode="multiple" style={{ width: '100%' }}>
<Option value={'NONE'}>NONE</Option>
{this.state.groups.map(group => (
<Option key={group.name} value={group.name}>
{group.name}
</Option>
))}
</Select>,
)}
</Form.Item>
<Col span={16} offset={20}>
<div style={{ marginTop: 24 }}>
<Button style={{ marginRight: 8 }} onClick={this.props.getPrevStep}>
Back
</Button>
<Button
type="primary"
onClick={e => this.onHandleContinue(e, 'groupData')}
>
Continue
</Button>
</div>
</Col>
</div>
);
}
}
export default withConfigContext(Form.create()(AssignGroups));

View File

@ -1,949 +0,0 @@
/*
* Copyright (c) 2020, Entgra (pvt) Ltd. (http://entgra.io) All Rights Reserved.
*
* Entgra (pvt) Ltd. licenses this file to you 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.
*/
import React from 'react';
import {
Tabs,
Row,
Col,
Switch,
Input,
Typography,
Form,
Collapse,
Checkbox,
Select,
Tooltip,
Icon,
Table,
Alert,
Upload,
Popconfirm,
Button,
Radio,
} from 'antd';
import { withConfigContext } from '../../../../../../../../components/ConfigContext';
import '../../../../styles.css';
import moment from 'moment';
const { Text, Title, Paragraph } = Typography;
const { TabPane } = Tabs;
const { Option } = Select;
const { TextArea } = Input;
const subPanelpayloadAttributes = {};
let subFormContainer = {};
let radioSubPanelSwitches = [];
let initialRadioPanels = {};
let radioPanelStatusList = {};
class ConfigureProfile extends React.Component {
constructor(props) {
super(props);
this.config = this.props.context;
this.state = {
loading: false,
activePanelKeys: [],
activeSubPanelKeys: [],
subFormList: [],
subPanelpayloadAttributes: {},
customInputDataArray: [],
inputTableDataSources: {},
subPanelRadio: {},
};
}
componentDidMount() {}
setProfileInfo = e => {
let activePolicies = [];
let activePolicyFields = {};
let activeSubPanels = [];
let subPanelRadio = this.state.subPanelRadio;
this.setState({
subPanelRadio: radioPanelStatusList,
});
const allFields = this.props.form.getFieldsValue();
this.props.policyFeatureList.map(element => {
activePolicies.push(element.featureCode);
let featureData = JSON.parse(element.content);
Object.keys(featureData).map(key => {
if (element.featureCode in subPanelpayloadAttributes) {
Object.entries(subPanelpayloadAttributes[element.featureCode]).map(
([panelKey, payloadAttr]) => {
if (key === payloadAttr) {
activeSubPanels.push(`${element.featureCode}-${panelKey}`);
}
},
);
let regex = new RegExp(`${element.featureCode}.+${key}`, 'g');
Object.keys(allFields).map(fieldName => {
if (fieldName.match(regex) != null) {
activePolicyFields[fieldName] = featureData[key];
}
});
} else if (element.featureCode in subFormContainer) {
let regex = new RegExp(`.+${element.featureCode}-${key}`, 'g');
Object.keys(allFields).map(fieldName => {
if (fieldName.match(regex) != null) {
activePolicyFields[fieldName] = featureData[key];
if (
!activePolicies.includes(
fieldName.replace(`-${element.featureCode}-${key}`, ''),
)
) {
activePolicies.push(
fieldName.replace(`-${element.featureCode}-${key}`, ''),
);
}
}
});
} else {
let regex = new RegExp(`${element.featureCode}.+${key}`, 'g');
Object.keys(allFields).map(fieldName => {
if (fieldName.match(regex) != null) {
activePolicyFields[fieldName] = featureData[key];
}
if (
radioSubPanelSwitches.includes(
`${element.featureCode}-${featureData[key]}`,
)
) {
let subPanelViewStatus = {
[featureData[key]]: true,
};
Object.assign(subPanelRadio, subPanelViewStatus);
}
});
}
});
});
this.props.form.setFieldsValue(activePolicyFields);
Object.keys(initialRadioPanels).map(policy => {
if (!activePolicies.includes(policy)) {
for (let subPanelKey of initialRadioPanels[policy]) {
let subPanelViewStatus = {
[subPanelKey]: true,
};
Object.assign(subPanelRadio, subPanelViewStatus);
}
}
});
this.setState({
activePanelKeys: activePolicies,
activeSubPanelKeys: activeSubPanels,
subPanelRadio,
});
};
// convert time from 24h format to 12h format
timeConverter = time => {
time = time
.toString()
.match(/^([01]\d|2[0-3])(:)([0-5]\d)(:[0-5]\d)?$/) || [time];
if (time.length > 1) {
time = time.slice(1);
time[5] = +time[0] < 12 ? ' AM' : ' PM';
time[0] = +time[0] % 12 || 12;
}
return time.join('');
};
// get Option value from start Time, end Time and time difference between 2 values
getOptionForTimeSelectors = (startTimeValue, endTimeValue, timeIncrement) => {
let timeOptions = [];
let time = new Date(
moment()
.startOf('day')
.format('YYYY/MM/DD'),
);
let tempValue = startTimeValue;
time.setMinutes(time.getMinutes() + tempValue);
let startOption = (
<Option value={String(tempValue)}>
{this.timeConverter(
`${String(time)
.split(' ')[4]
.substring(0, 5)}`,
)}
</Option>
);
timeOptions.push(startOption);
while (tempValue !== endTimeValue) {
time = new Date(
moment()
.startOf('day')
.format('YYYY/MM/DD'),
);
tempValue += timeIncrement;
if (tempValue > 1440) {
tempValue = 0;
continue;
}
time.setMinutes(time.getMinutes() + tempValue);
let option = (
<Option value={String(tempValue)}>
{this.timeConverter(
`${String(time)
.split(' ')[4]
.substring(0, 5)}`,
)}
</Option>
);
timeOptions.push(option);
}
return timeOptions;
};
// handle items which handle from radio buttons
handleRadioPanel = (e, subPanel) => {
{
subPanel.map((panel, i) => {
if (panel.value === e.target.value) {
document.getElementById(panel.value).style.display = 'block';
} else {
document.getElementById(panel.value).style.display = 'none';
}
});
}
};
// handle items which handle from select options
handleSelectedPanel = (e, subPanel) => {
{
subPanel.map((panel, i) => {
if (panel.id === e) {
document.getElementById(panel.id).style.display = 'block';
} else {
document.getElementById(panel.id).style.display = 'none';
}
});
}
};
// handle items which handle from checkbox
handleSubPanel = e => {
if (e.target.checked) {
let joined = this.state.activeSubPanelKeys.concat(e.target.id);
this.setState({ activeSubPanelKeys: joined });
} else {
let index = this.state.activeSubPanelKeys.indexOf(e.target.id);
if (index !== -1) {
this.state.activeSubPanelKeys.splice(index, 1);
let removed = this.state.activeSubPanelKeys;
this.setState({ activeSubPanelKeys: removed });
}
}
};
// handle Switch on off button
handleMainPanel = (e, ref) => {
if (e) {
let joined = this.state.activePanelKeys.concat(ref);
this.setState({ activePanelKeys: joined });
} else {
let index = this.state.activePanelKeys.indexOf(ref);
if (index !== -1) {
this.state.activePanelKeys.splice(index, 1);
let removed = this.state.activePanelKeys;
this.setState({ activePanelKeys: removed });
}
}
};
handleCustomInputTable = event => {
const { count, customInputDataArray } = this.state;
const newData = [
{
key: count,
CERT_NAME: `${event.file.name}`,
},
];
this.setState({
customInputDataArray: [...customInputDataArray, newData],
count: count + 1,
});
};
handleAdd = array => {
const { count, inputTableDataSources } = this.state;
const newData = [
{
key: count,
},
];
inputTableDataSources[array].push(newData);
Object.defineProperty(inputTableDataSources, array, {
value: inputTableDataSources[array],
});
this.setState({
inputTableDataSources,
count: count + 1,
});
};
getColumns = ({ getFieldDecorator }, arr) => {
const columnArray = [];
const actionColumn = [
{
title: '',
dataIndex: 'operation',
render: (name, row) => (
<Form.Item>
<Popconfirm title="Sure to delete?">
<a>
<Text type="danger">
<Icon type="delete" />
</Text>
</a>
</Popconfirm>
</Form.Item>
),
},
];
Object.values(arr).map((columnData, c) => {
if (columnData.type === 'input') {
const column = {
title: `${columnData.name}`,
dataIndex: `${columnData.key}`,
key: `${columnData.key}`,
render: (name, row, i) => (
<Form.Item>
{getFieldDecorator(`${columnData.key}${i}`, {})(
<Input
type={columnData.others.inputType}
placeholder={columnData.others.placeholder}
/>,
)}
</Form.Item>
),
};
columnArray.push(column);
} else if (columnData.type === 'upload') {
const column = {
title: `${columnData.name}`,
dataIndex: `${columnData.key}`,
key: `${columnData.key}`,
render: (name, row, i) => (
<Form.Item>
{getFieldDecorator(`${columnData.key}${i}`, {})(
<Upload>
<Button>
<Icon type="upload" /> Choose file
</Button>
</Upload>,
)}
</Form.Item>
),
};
columnArray.push(column);
} else if (columnData.type === 'select') {
const column = {
title: `${columnData.name}`,
dataIndex: `${columnData.key}`,
key: `${columnData.key}`,
render: (name, row, i) => (
<Form.Item>
{getFieldDecorator(`${columnData.key}${i}`, {
initialValue: columnData.others.initialDataIndex,
})(
<Select>
{columnData.others.option.map((option, i) => {
return (
<Option key={i} value={option.key}>
{option.value}
</Option>
);
})}
</Select>,
)}
</Form.Item>
),
};
columnArray.push(column);
}
});
const columns = columnArray.concat(actionColumn);
return columns;
};
// generate payload by adding policy configurations
onHandleContinue = (e, formname) => {
const allFields = this.props.form.getFieldsValue();
let activeFields = [];
// get currently active field list
for (let i = 0; i < this.state.activePanelKeys.length; i++) {
Object.keys(allFields).map(key => {
if (key.includes(`${this.state.activePanelKeys[i]}-`)) {
if (
subPanelpayloadAttributes.hasOwnProperty(
`${this.state.activePanelKeys[i]}`,
)
) {
Object.keys(
subPanelpayloadAttributes[this.state.activePanelKeys[i]],
).map(subPanel => {
if (`${this.state.activePanelKeys[i]}-${subPanel}` === true) {
if (key.includes(`-${subPanel}-`)) {
activeFields.push(key);
}
} else if (!key.includes(`-${subPanel}-`)) {
activeFields.push(key);
}
});
} else {
activeFields.push(key);
}
}
});
}
this.onFieldValidate(activeFields, formname);
};
onFieldValidate = (fields, formName) => {
// validate fields and get profile features list
this.props.form.validateFields(fields, (err, values) => {
if (!err) {
let profileFeaturesList = [];
for (let i = 0; i < this.state.activePanelKeys.length; i++) {
let content = {};
Object.entries(values).map(([key, value]) => {
if (key.includes(`${this.state.activePanelKeys[i]}-`)) {
content[
key.replace(`${this.state.activePanelKeys[i]}-`, '')
] = value;
}
});
let feature = {
featureCode: this.state.activePanelKeys[i],
deviceType: this.props.deviceType,
content: content,
};
profileFeaturesList.push(feature);
}
this.props.getPolicyPayloadData(formName, profileFeaturesList);
this.props.getNextStep();
}
});
};
// generate form items
getPanelItems = (panel, panelId) => {
const { getFieldDecorator } = this.props.form;
const subPanelList = {};
let initialRadioOptions = [];
return panel.map((item, k) => {
switch (item.type) {
case 'select':
if (item.optional.hasOwnProperty('subPanel')) {
return (
<div>
<Form.Item
key={k}
label={
<span>
{item.label}&nbsp;
<Tooltip title={item.tooltip} placement="right">
<Icon type="question-circle-o" />
</Tooltip>
</span>
}
style={{ display: 'block' }}
>
{getFieldDecorator(`${item.id}`, {
initialValue: `${item.optional.option[0].name}`,
})(
<Select
onChange={e =>
this.handleSelectedPanel(e, item.optional.subPanel)
}
>
{item.optional.option.map((option, i) => {
return (
<Option key={i} value={option.value}>
{option.name}
</Option>
);
})}
</Select>,
)}
</Form.Item>
<div className={'sub-panel-container'}>
{item.optional.subPanel.map((panel, i) => {
return (
<div
id={panel.id}
key={i}
style={
panel.id === item.optional.initialDataIndex
? { display: 'block' }
: { display: 'none' }
}
>
{this.getPanelItems(panel.panelItem)}
</div>
);
})}
</div>
</div>
);
}
return (
<Form.Item
key={k}
label={
<span>
{item.label}&nbsp;
<Tooltip title={item.tooltip} placement="right">
<Icon type="question-circle-o" />
</Tooltip>
</span>
}
style={{ display: 'block' }}
>
{getFieldDecorator(`${item.id}`, {
initialValue: `${item.optional.option[0].name}`,
})(
<Select>
{item.optional.option.map((option, i) => {
return (
<Option key={i} value={option.value}>
{option.name}
</Option>
);
})}
</Select>,
)}
</Form.Item>
);
case 'timeSelector':
return (
<Form.Item
key={k}
label={
<span>
{item.label}&nbsp;
<Tooltip title={item.tooltip} placement="right">
<Icon type="question-circle-o" />
</Tooltip>
</span>
}
style={{ display: 'block' }}
>
{getFieldDecorator(`${item.id}`, {
initialValue: item.optional.initialDataIndex,
})(
<Select>
{this.getOptionForTimeSelectors(
item.optional.firstOptionValue,
item.optional.lastOptionValue,
item.optional.valueDifference,
)}
</Select>,
)}
</Form.Item>
);
case 'input':
return (
<Form.Item
key={k}
label={
<span>
{item.label}&nbsp;
<Tooltip title={item.tooltip} placement="right">
<Icon type="question-circle-o" />
</Tooltip>
</span>
}
style={{ display: 'block' }}
>
{getFieldDecorator(`${item.id}`, {
rules: [
{
pattern: new RegExp(`${item.optional.rules.regex}`),
message: `${item.optional.rules.validationMsg}`,
},
],
})(<Input placeholder={item.optional.placeholder} />)}
</Form.Item>
);
case 'checkbox':
if (item.optional.hasOwnProperty('subPanel')) {
return (
<div key={k}>
<Collapse
bordered={false}
activeKey={this.state.activeSubPanelKeys}
>
<Collapse.Panel
key={item.id}
showArrow={false}
style={{ border: 0 }}
header={
<Form.Item key={k}>
{getFieldDecorator(`${item.id}`, {
valuePropName: 'checked',
initialValue: item.optional.ischecked,
})(
<Checkbox onChange={this.handleSubPanel}>
<span>
{item.label}&nbsp;
<Tooltip title={item.tooltip} placement="right">
<Icon type="question-circle-o" />
</Tooltip>
</span>
</Checkbox>,
)}
</Form.Item>
}
>
<div>
<div>
{item.optional.subPanel.map((panel, i) => {
subPanelList[panel.others.itemSwitch] =
panel.others.itemPayload;
if (
subPanelpayloadAttributes.hasOwnProperty(panelId)
) {
Object.assign(
subPanelpayloadAttributes[panelId],
subPanelList,
);
} else {
subPanelpayloadAttributes[panelId] = subPanelList;
}
return (
<div key={i}>
{this.getPanelItems(panel.panelItem, panelId)}
</div>
);
})}
</div>
</div>
</Collapse.Panel>
</Collapse>
</div>
);
}
return (
<Form.Item key={k}>
{getFieldDecorator(`${item.id}`, {
valuePropName: 'checked',
initialValue: item.optional.ischecked,
})(
<Checkbox>
<span>
{item.label}&nbsp;
<Tooltip title={item.tooltip} placement="right">
<Icon type="question-circle-o" />
</Tooltip>
</span>
</Checkbox>,
)}
</Form.Item>
);
case 'textArea':
return (
<Form.Item
key={k}
label={
<span>
{item.label}&nbsp;
<Tooltip title={item.tooltip} placement="right">
<Icon type="question-circle-o" />
</Tooltip>
</span>
}
style={{ display: 'block' }}
>
{getFieldDecorator(`${item.id}`, {
initialValue: null,
})(
<TextArea
placeholder={item.optional.placeholder}
rows={item.optional.row}
/>,
)}
</Form.Item>
);
case 'radioGroup':
initialRadioOptions.push(item.optional.initialValue);
initialRadioPanels[panelId] = initialRadioOptions;
return (
<div>
<Form.Item
key={k}
label={
<span>
{item.label}&nbsp;
<Tooltip title={item.tooltip} placement="right">
<Icon type="question-circle-o" />
</Tooltip>
</span>
}
style={{ display: 'block' }}
>
{getFieldDecorator(`${item.id}`, {
initialValue: `${item.optional.initialValue}`,
})(
<Radio.Group
onChange={e =>
this.handleRadioPanel(e, item.optional.radio)
}
>
{item.optional.radio.map((option, i) => {
return (
<Radio key={i} value={option.value}>
{option.name}
</Radio>
);
})}
</Radio.Group>,
)}
</Form.Item>
<div className={'sub-panel-container'}>
{item.optional.subPanel.map((panel, i) => {
radioSubPanelSwitches.push(`${panelId}-${panel.id}`);
radioPanelStatusList[panel.id] = false;
return (
<div
key={i}
id={panel.id}
ref={panel.id}
style={
this.state.subPanelRadio[panel.id]
? { display: 'block' }
: { display: 'none' }
}
>
{this.getPanelItems(panel.panelItem)}
</div>
);
})}
</div>
</div>
);
case 'title':
return (
<Title key={k} level={4}>
{item.label}{' '}
</Title>
);
case 'paragraph':
return (
<Paragraph key={k} style={{ marginTop: 20 }}>
{item.label}{' '}
</Paragraph>
);
case 'alert':
return (
<Alert key={k} description={item.label} type="warning" showIcon />
);
case 'upload':
return (
<Form.Item
key={k}
label={
<span>
{item.label}&nbsp;
<Tooltip title={item.tooltip} placement="right">
<Icon type="question-circle-o" />
</Tooltip>
</span>
}
>
{getFieldDecorator('upload', {})(
<Upload>
<Button>
<Icon type="upload" /> Click to upload
</Button>
</Upload>,
)}
</Form.Item>
);
case 'inputTable':
if (
!(`${item.optional.dataSource}` in this.state.inputTableDataSources)
) {
Object.defineProperty(
this.state.inputTableDataSources,
`${item.optional.dataSource}`,
{ value: [], writable: true },
);
}
return (
<div key={k}>
<Button
onClick={() => this.handleAdd(item.optional.dataSource)}
type="primary"
style={{ marginBottom: 16 }}
>
<Icon type="plus-circle" />
{item.optional.button.name}
</Button>
<Table
id={item.id}
dataSource={
this.state.inputTableDataSources[item.optional.dataSource]
}
columns={this.getColumns(
{ getFieldDecorator },
item.optional.columns,
)}
/>
</div>
);
case 'customInputTable':
return (
<div key={k}>
<Upload onChange={this.handleCustomInputTable}>
<Button type="primary" style={{ marginBottom: 16 }}>
<Icon type="plus-circle" />
{item.optional.button.name}
</Button>
</Upload>
<Table
id={item.id}
dataSource={this.state.customInputDataArray}
columns={this.getColumns(
{ getFieldDecorator },
item.optional.columns,
)}
/>
</div>
);
default:
return null;
}
});
};
render() {
const { policyUIConfigurationsList } = this.props;
const { getFieldDecorator } = this.props.form;
return (
<div className="tab-container">
<Tabs
tabPosition={'left'}
size={'large'}
onTabClick={this.setProfileInfo}
>
{policyUIConfigurationsList.map((element, i) => {
return (
<TabPane tab={<span>{element.name}</span>} key={i}>
{Object.values(element.panels).map((panel, j) => {
panel = panel.panel;
return (
<div key={j}>
<Collapse
bordered={false}
activeKey={this.state.activePanelKeys}
>
<Collapse.Panel
key={panel.panelId}
showArrow={false}
style={{ border: 0 }}
header={
<div>
<Row>
<Col offset={0} span={14}>
<Title level={4}> {panel.title} </Title>
</Col>
<Col offset={8} span={1}>
<Switch
id={`${panel.panelId}_SWITCH`}
checkedChildren="ON"
unCheckedChildren="OFF"
onChange={e =>
this.handleMainPanel(
e,
`${panel.panelId}`,
)
}
/>
</Col>
</Row>
<Row>{panel.description}</Row>
</div>
}
>
{panel.hasOwnProperty('panelItem') && (
<div>
<Form name={panel.panelId}>
<Form.Item style={{ display: 'none' }}>
{getFieldDecorator(`${panel.panelId}`, {
initialValue: ' ',
})(<Input />)}
</Form.Item>
{this.getPanelItems(
panel.panelItem,
panel.panelId,
)}
</Form>
</div>
)}
{panel.hasOwnProperty('subFormLists') && (
<div>
{Object.values(panel.subFormLists).map(
(form, i) => {
subFormContainer[`${form.id}`] =
panel.panelId;
return (
<Form name={form.id} key={i}>
<Form.Item style={{ display: 'none' }}>
{getFieldDecorator(`${form.id}`, {
initialValue: ' ',
})(<Input />)}
</Form.Item>
{this.getPanelItems(form.panelItem)}
</Form>
);
},
)}
</div>
)}
</Collapse.Panel>
</Collapse>
</div>
);
})}
</TabPane>
);
})}
</Tabs>
<Col span={16} offset={20}>
<div style={{ marginTop: 24 }}>
<Button style={{ marginRight: 8 }} onClick={this.props.getPrevStep}>
Back
</Button>
<Button
type="primary"
onClick={e => this.onHandleContinue(e, 'configureProfileData')}
>
Continue
</Button>
</div>
</Col>
</div>
);
}
}
export default withConfigContext(Form.create()(ConfigureProfile));

View File

@ -1,86 +0,0 @@
/*
* Copyright (c) 2020, Entgra (pvt) Ltd. (http://entgra.io) All Rights Reserved.
*
* Entgra (pvt) Ltd. licenses this file to you 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.
*/
import React from 'react';
import { withConfigContext } from '../../../../../../../../components/ConfigContext';
import { Button, Col, Form, Input } from 'antd';
const { TextArea } = Input;
class PublishDevices extends React.Component {
constructor(props) {
super(props);
this.config = this.props.context;
}
onClickSavePolicy = (event, formName) => {
this.props.form.validateFields((err, values) => {
if (!err) {
this.props.getPolicyPayloadData(formName, values);
}
});
};
render() {
const { policyData } = this.props;
const { getFieldDecorator } = this.props.form;
return (
<div>
<Form.Item
label={'Set a name to your policy *'}
style={{ display: 'block' }}
>
{getFieldDecorator('policyName', {
initialValue: policyData.policyName,
rules: [
{
pattern: new RegExp('^.{1,30}$'),
message: 'Should be 1-to-30 characters long',
},
],
})(<Input placeholder={'Should be 1 to 30 characters long'} />)}
</Form.Item>
<Form.Item label={'Add a Description'} style={{ display: 'block' }}>
{getFieldDecorator('description', {
initialValue: policyData.description,
})(<TextArea rows={8} />)}
</Form.Item>
<Col span={16} offset={18}>
<div style={{ marginTop: 24 }}>
<Button style={{ marginRight: 8 }} onClick={this.props.getPrevStep}>
Back
</Button>
<Button
type="primary"
style={{ marginRight: 8 }}
onClick={e => this.onClickSavePolicy(e, 'publishDevicesData')}
>
Save & Publish
</Button>
<Button
type="primary"
onClick={e => this.onClickSavePolicy(e, 'publishDevicesData')}
>
Save
</Button>
</div>
</Col>
</div>
);
}
}
export default withConfigContext(Form.create()(PublishDevices));

View File

@ -1,182 +0,0 @@
/*
* Copyright (c) 2020, Entgra (pvt) Ltd. (http://entgra.io) All Rights Reserved.
*
* Entgra (pvt) Ltd. licenses this file to you 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.
*/
import React from 'react';
import {
Button,
Col,
Form,
Icon,
message,
notification,
Radio,
Select,
Tooltip,
} from 'antd';
import { withConfigContext } from '../../../../../../../../components/ConfigContext';
import axios from 'axios';
const { Option } = Select;
class SelectPolicyType extends React.Component {
constructor(props) {
super(props);
this.config = this.props.context;
this.state = {
correctivePoliciesList: [],
};
}
componentDidMount() {
this.fetchPolicies();
}
// generate payload using Select policy type
onHandleContinue = (e, formName) => {
this.props.form.validateFields((err, values) => {
if (!err) {
if (
values.policyType === 'CORRECTIVE' ||
values.correctiveActions === 'NONE'
) {
values.correctiveActions = [];
}
this.props.getPolicyPayloadData(formName, values);
this.props.getNextStep();
}
});
};
fetchPolicies = () => {
let apiUrl =
window.location.origin +
this.config.serverConfig.invoker.uri +
this.config.serverConfig.invoker.deviceMgt +
'/policies';
// send request to the invokerss
axios
.get(apiUrl)
.then(res => {
if (res.status === 200) {
let policies = res.data.data.policies;
let correctivePolicies = [];
for (let i = 0; i < policies.length; i++) {
if (policies[i].policyType === 'CORRECTIVE') {
correctivePolicies.push(
<Option key={policies[i].profileId}>
{policies[i].policyName}
</Option>,
);
}
}
this.setState({
correctivePoliciesList: correctivePolicies,
});
}
})
.catch(error => {
if (error.hasOwnProperty('response') && error.response.status === 401) {
// todo display a popop with error
message.error('You are not logged in');
window.location.href = window.location.origin + '/entgra/login';
} else {
notification.error({
message: 'There was a problem',
duration: 0,
description: 'Error occurred while trying to load policies.',
});
}
this.setState({ loading: false });
});
};
handlePolicyTypes = event => {
if (event.target.value === 'GENERAL') {
document.getElementById('generalPolicySubPanel').style.display = 'block';
} else {
document.getElementById('generalPolicySubPanel').style.display = 'none';
}
};
render() {
const { policyData } = this.props;
const { getFieldDecorator } = this.props.form;
let correctiveActions = null;
if (policyData.correctiveActions.length > 0) {
correctiveActions = '';
} else {
correctiveActions = 'NONE';
}
return (
<div>
<Form.Item style={{ display: 'block' }}>
{getFieldDecorator('policyType', {
initialValue: policyData.policyType,
})(
<Radio.Group onChange={this.handlePolicyTypes}>
<Radio value="GENERAL">General Policy</Radio>
<Radio value="CORRECTIVE">Corrective Policy</Radio>
</Radio.Group>,
)}
</Form.Item>
<div id="generalPolicySubPanel" style={{ display: 'block' }}>
<Form.Item
label={
<span>
Select Corrective Policy&nbsp;
<Tooltip
title={
'Select the corrective policy to be applied when this general policy is violated'
}
placement="right"
>
<Icon type="question-circle-o" />
</Tooltip>
</span>
}
>
{getFieldDecorator('correctiveActions', {
initialValue: correctiveActions,
})(
<Select style={{ width: '100%' }}>
<Option value="NONE">None</Option>
{this.state.correctivePoliciesList}
</Select>,
)}
</Form.Item>
</div>
<Col span={16} offset={20}>
<div style={{ marginTop: 24 }}>
<Button style={{ marginRight: 8 }} onClick={this.props.getPrevStep}>
Back
</Button>
<Button
type="primary"
onClick={e => this.onHandleContinue(e, 'policyTypeData')}
>
Continue
</Button>
</div>
</Col>
</div>
);
}
}
export default withConfigContext(Form.create()(SelectPolicyType));

View File

@ -1,273 +0,0 @@
/*
* Copyright (c) 2020, Entgra (pvt) Ltd. (http://entgra.io) All Rights Reserved.
*
* Entgra (pvt) Ltd. licenses this file to you 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.
*/
import React from 'react';
import { Form, Row, Col, Card, Steps, message, notification } from 'antd';
import { withConfigContext } from '../../../../../../components/ConfigContext';
import ConfigureProfile from './components/ConfigureProfile';
import SelectPolicyType from './components/SelectPolicyType';
import AssignGroups from './components/AssignGroups';
import PublishDevices from './components/PublishDevices';
import axios from 'axios';
const { Step } = Steps;
class EditPolicy extends React.Component {
constructor(props) {
super(props);
this.config = this.props.context;
this.state = {
currentStepIndex: 0,
policyUIConfigurationsList: null,
newPolicyPayload: { compliance: 'enforce' },
policyProfile: {},
payloadData: {},
policyFeatureList: [],
policyData: {},
deviceType: null,
};
}
componentDidMount() {
this.getSelectedPolicy(this.props.policyId);
}
getPolicyPayloadData = (dataName, dataValue) => {
Object.defineProperty(this.state.payloadData, dataName, {
value: dataValue,
writable: true,
});
if (dataName === 'publishDevicesData') {
this.createPayload();
}
};
createPayload = () => {
const {
publishDevicesData,
configureProfileData,
policyTypeData,
groupData,
} = this.state.payloadData;
const profile = {
profileName: publishDevicesData.policyName,
deviceType: this.state.deviceType,
profileFeaturesList: configureProfileData,
};
const payload = {
...publishDevicesData,
compliance: 'enforce',
ownershipType: null,
...policyTypeData,
profile: profile,
...groupData,
};
this.onEditPolicy(JSON.stringify(payload));
};
getSelectedPolicy = policyId => {
let apiUrl =
window.location.origin +
this.config.serverConfig.invoker.uri +
this.config.serverConfig.invoker.deviceMgt +
'/policies/' +
policyId;
// send request to the invokers
axios
.get(apiUrl)
.then(res => {
if (res.status === 200) {
this.setState({
isLoading: true,
policyData: res.data.data,
deviceType: res.data.data.profile.deviceType,
policyFeatureList: res.data.data.profile.profileFeaturesList,
});
this.getPolicyConfigJson(res.data.data.profile.deviceType);
}
})
.catch(error => {
if (error.hasOwnProperty('response') && error.response.status === 401) {
// todo display a popop with error
message.error('You are not logged in');
window.location.href = window.location.origin + '/entgra/login';
} else {
notification.error({
message: 'There was a problem',
duration: 0,
description: 'Error occurred while trying to load selected policy.',
});
}
});
};
getPolicyConfigJson = type => {
this.setState({ isLoading: true });
let apiUrl =
window.location.origin +
this.config.serverConfig.invoker.uri +
this.config.serverConfig.invoker.deviceMgt +
'/device-types/' +
type +
'/ui-policy-configurations';
// send request to the invokers
axios
.get(apiUrl)
.then(res => {
if (res.status === 200) {
this.setState({
policyUIConfigurationsList: JSON.parse(res.data.data),
});
}
})
.catch(error => {
if (error.hasOwnProperty('response') && error.response.status === 401) {
// todo display a popop with error
message.error('You are not logged in');
window.location.href = window.location.origin + '/entgra/login';
} else {
notification.error({
message: 'There was a problem',
duration: 0,
description: 'Error occurred while trying to load Policy details.',
});
}
});
};
onEditPolicy = value => {
axios
.put(
window.location.origin +
this.config.serverConfig.invoker.uri +
this.config.serverConfig.invoker.deviceMgt +
'/policies/' +
this.props.policyId,
value,
{ headers: { 'Content-Type': 'application-json' } },
)
.then(res => {
if (res.status === 200) {
notification.success({
message: 'Done',
duration: 4,
description: 'Successfully Updated the Policy.',
});
}
})
.catch(error => {
if (error.hasOwnProperty('response') && error.response.status === 401) {
// todo display a popop with error
message.error('You are not logged in');
window.location.href = window.location.origin + '/entgra/login';
} else {
notification.error({
message: 'There was a problem',
duration: 0,
description: 'Error occurred while trying to Updated the Policy.',
});
}
});
};
getNextStep = () => {
const currentStepIndex = this.state.currentStepIndex + 1;
this.setState({ currentStepIndex });
};
getPrevStep = () => {
const currentStepIndex = this.state.currentStepIndex - 1;
this.setState({ currentStepIndex });
};
render() {
const {
currentStepIndex,
policyUIConfigurationsList,
policyFeatureList,
policyData,
deviceType,
} = this.state;
return (
<div>
{policyUIConfigurationsList != null && (
<Row>
<Col span={20} offset={2}>
<Steps style={{ minHeight: 32 }} current={currentStepIndex}>
<Step key="ProfileConfigure" title="Configure profile" />
<Step key="PolicyType" title="Select policy type" />
<Step key="AssignGroups" title="Assign to groups" />
<Step key="Publish" title="Publish to devices" />
</Steps>
</Col>
<Col span={16} offset={4}>
<Card style={{ marginTop: 24 }}>
<div
style={{ display: currentStepIndex === 0 ? 'unset' : 'none' }}
>
<ConfigureProfile
policyUIConfigurationsList={policyUIConfigurationsList}
getPolicyPayloadData={this.getPolicyPayloadData}
getNextStep={this.getNextStep}
policyFeatureList={policyFeatureList}
deviceType={deviceType}
/>
</div>
<div
style={{ display: currentStepIndex === 1 ? 'unset' : 'none' }}
>
<SelectPolicyType
getPolicyPayloadData={this.getPolicyPayloadData}
policyData={policyData}
getPrevStep={this.getPrevStep}
getNextStep={this.getNextStep}
/>
</div>
<div
style={{ display: currentStepIndex === 2 ? 'unset' : 'none' }}
>
<AssignGroups
getPolicyPayloadData={this.getPolicyPayloadData}
policyData={policyData}
getPrevStep={this.getPrevStep}
getNextStep={this.getNextStep}
/>
</div>
<div
style={{ display: currentStepIndex === 3 ? 'unset' : 'none' }}
>
<PublishDevices
policyData={policyData}
getPolicyPayloadData={this.getPolicyPayloadData}
getPrevStep={this.getPrevStep}
/>
</div>
</Card>
</Col>
</Row>
)}
</div>
);
}
}
export default withConfigContext(
Form.create({ name: 'edit-policy' })(EditPolicy),
);

View File

@ -1,55 +0,0 @@
/*
* Copyright (c) 2020, Entgra (pvt) Ltd. (http://entgra.io) All Rights Reserved.
*
* Entgra (pvt) Ltd. licenses this file to you 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.
*/
import React from 'react';
import { Divider, Icon, Tooltip } from 'antd';
import { withConfigContext } from '../../../../../../../../components/ConfigContext';
import { Link } from 'react-router-dom';
class PolicyAction extends React.Component {
constructor(props) {
super(props);
this.config = this.props.context;
}
render() {
return (
<div>
<div>
<Tooltip placement="top" title={'Edit Policy'}>
<Link
to={`/entgra/policy/edit/${this.props.selectedPolicyData.id}`}
>
<Icon type="edit" />
</Link>
</Tooltip>
<Divider type="vertical" />
<Tooltip placement="top" title={''}>
<Link
to={`/entgra/policy/view/${this.props.selectedPolicyData.id}`}
>
<Icon type="eye" />
</Link>
</Tooltip>
</div>
</div>
);
}
}
export default withConfigContext(PolicyAction);

View File

@ -1,178 +0,0 @@
/*
* Copyright (c) 2020, Entgra (pvt) Ltd. (http://entgra.io) All Rights Reserved.
*
* Entgra (pvt) Ltd. licenses this file to you 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.
*/
import React from 'react';
import { Button, Tooltip, Popconfirm, Divider } from 'antd';
class BulkActionBar extends React.Component {
constructor(props) {
super(props);
this.state = {
selectedMultiple: false,
selectedSingle: false,
isPolicyActive: true,
};
}
// This method checks whether active devices are selected
onCheckPolicyStatus = () => {
let tempIsPolicyActive;
for (let i = 0; i < this.props.selectedRows.length; i++) {
if (this.props.selectedRows[i].active) {
tempIsPolicyActive = true;
break;
}
tempIsPolicyActive = false;
}
this.setState({ isPolicyActive: tempIsPolicyActive });
};
onConfirmRemove = () => {
if (!this.state.isPolicyActive) {
this.props.removePolicy();
}
};
onConfirmPublish = () => {
if (!this.state.isPolicyActive) {
this.props.publishPolicy();
}
};
onConfirmUnpublish = () => {
if (this.state.isPolicyActive) {
this.props.unpublishPolicy();
}
};
render() {
const isSelected = this.props.selectedRows.length > 0;
return (
<div>
<div style={{ padding: '5px' }}>
<Tooltip placement="bottom" title={'Apply Changes to Device'}>
<Popconfirm
placement="topLeft"
title={'Do you really want to apply changes to all policies?'}
onConfirm={this.props.applyChanges}
okText="Yes"
cancelText="No"
>
<Button
type="link"
shape="circle"
icon="check-circle"
size={'default'}
style={{ margin: '2px' }}
>
APPLY CHANGES TO DEVICES
</Button>
</Popconfirm>
</Tooltip>
</div>
<div
style={{ display: isSelected ? 'inline' : 'none', padding: '8px' }}
>
<Tooltip
placement="bottom"
title={'Remove'}
autoAdjustOverflow={true}
>
<Popconfirm
placement="topLeft"
title={
!this.state.isPolicyActive
? 'Do you really want to remove the selected policy(s)?'
: 'You cannot select already active policies. Please deselect active policies and try again.'
}
onConfirm={this.onConfirmRemove}
okText="Yes"
cancelText="No"
>
<Button
type="link"
shape="circle"
icon="delete"
size={'default'}
onClick={this.onCheckPolicyStatus}
style={{ margin: '2px' }}
>
Remove
</Button>
</Popconfirm>
</Tooltip>
<Divider type="vertical" />
<Tooltip placement="bottom" title={'Publish'}>
<Popconfirm
placement="topLeft"
title={
!this.state.isPolicyActive
? 'Do you really want to publish the selected policy(s)??'
: 'You cannot select already active policies. Please deselect active policies and try again.'
}
okText="Yes"
onConfirm={this.onConfirmPublish}
cancelText="No"
>
<Button
type="link"
shape="circle"
icon="import"
onClick={this.onCheckPolicyStatus}
size={'default'}
style={{
margin: '2px',
}}
>
Publish
</Button>
</Popconfirm>
</Tooltip>
<Divider type="vertical" />
<Tooltip placement="bottom" title={'Unpublish'}>
<Popconfirm
placement="topLeft"
title={
this.state.isPolicyActive
? 'Do you really want to unpublish the selected policy(s)?'
: 'You cannot select already inactive policies to be unpublished. Please deselect inactive policies and try again.'
}
okText="Yes"
onConfirm={this.onConfirmUnpublish}
cancelText="No"
>
<Button
type="link"
shape="circle"
icon="export"
onClick={this.onCheckPolicyStatus}
size={'default'}
style={{ margin: '2px' }}
>
Unpublish
</Button>
</Popconfirm>
</Tooltip>
</div>
</div>
);
}
}
export default BulkActionBar;

View File

@ -1,344 +0,0 @@
/*
* Copyright (c) 2020, Entgra (pvt) Ltd. (http://entgra.io) All Rights Reserved.
*
* Entgra (pvt) Ltd. licenses this file to you 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.
*/
import React from 'react';
import axios from 'axios';
import { message, notification, Table } from 'antd';
import TimeAgo from 'javascript-time-ago';
// Load locale-specific relative date/time formatting rules.
import en from 'javascript-time-ago/locale/en';
import { withConfigContext } from '../../../../../../components/ConfigContext';
import PolicyAction from './component/PolicyAction';
import PolicyBulkActionBar from './component/PolicyBulkActionBar';
let apiUrl;
class PoliciesTable extends React.Component {
constructor(props) {
super(props);
TimeAgo.addLocale(en);
this.config = this.props.context;
this.state = {
data: [],
pagination: {},
loading: false,
selectedRows: [],
};
}
rowSelection = {
onChange: (selectedRowKeys, selectedRows) => {
this.setState({
selectedRows: selectedRows,
});
},
};
componentDidMount() {
this.fetchGroups();
}
// fetch data from api
fetchGroups = (params = {}) => {
const config = this.props.context;
this.setState({ loading: true });
// get current page
const currentPage = params.hasOwnProperty('page') ? params.page : 1;
const extraParams = {
offset: 10 * (currentPage - 1), // calculate the offset
limit: 10,
};
const encodedExtraParams = Object.keys(extraParams)
.map(key => key + '=' + extraParams[key])
.join('&');
apiUrl =
window.location.origin +
config.serverConfig.invoker.uri +
config.serverConfig.invoker.deviceMgt +
'/policies?' +
encodedExtraParams;
// send request to the invokerss
axios
.get(apiUrl)
.then(res => {
if (res.status === 200) {
const pagination = { ...this.state.pagination };
this.setState({
loading: false,
data: res.data.data.policies,
pagination,
});
}
})
.catch(error => {
if (error.hasOwnProperty('response') && error.response.status === 401) {
// todo display a popop with error
message.error('You are not logged in');
window.location.href = window.location.origin + '/entgra/login';
} else {
notification.error({
message: 'There was a problem',
duration: 0,
description: 'Error occurred while trying to load policies.',
});
}
this.setState({ loading: false });
});
};
handleTableChange = (pagination, filters, sorter) => {
const pager = { ...this.state.pagination };
pager.current = pagination.current;
this.setState({
pagination: pager,
});
this.fetch({
results: pagination.pageSize,
page: pagination.current,
sortField: sorter.field,
sortOrder: sorter.order,
...filters,
});
};
unpublishPolicy = () => {
const policyIDs = this.state.selectedRows.map(obj => obj.id);
// send request to the invoker
axios
.post(
window.location.origin +
this.config.serverConfig.invoker.uri +
this.config.serverConfig.invoker.deviceMgt +
'/policies/deactivate-policy',
policyIDs,
{ headers: { 'Content-Type': 'application/json' } },
)
.then(res => {
if (res.status === 200) {
this.fetchGroups();
notification.success({
message: 'Done',
duration: 4,
description: 'Selected policy(s) was successfully unpublished',
});
}
})
.catch(error => {
if (error.hasOwnProperty('response') && error.response.status === 401) {
// todo display a popop with error
message.error('You are not logged in');
window.location.href = window.location.origin + '/entgra/login';
} else {
notification.error({
message: 'There was a problem',
duration: 0,
description: 'Error occurred while trying to unpublish policy(s).',
});
}
});
};
publishPolicy = () => {
const policyIDs = this.state.selectedRows.map(obj => obj.id);
// send request to the invoker
axios
.post(
window.location.origin +
this.config.serverConfig.invoker.uri +
this.config.serverConfig.invoker.deviceMgt +
'/policies/activate-policy',
policyIDs,
{ headers: { 'Content-Type': 'application/json' } },
)
.then(res => {
if (res.status === 200) {
this.fetchGroups();
notification.success({
message: 'Done',
duration: 4,
description: 'Selected policy(s) was successfully unpublished',
});
}
})
.catch(error => {
if (error.hasOwnProperty('response') && error.response.status === 401) {
// todo display a popop with error
message.error('You are not logged in');
window.location.href = window.location.origin + '/entgra/login';
} else {
notification.error({
message: 'There was a problem',
duration: 0,
description: 'Error occurred while trying to unpublish policy(s).',
});
}
});
};
removePolicy = () => {
const policyIDs = this.state.selectedRows.map(obj => obj.id);
// send request to the invoker
axios
.post(
window.location.origin +
this.config.serverConfig.invoker.uri +
this.config.serverConfig.invoker.deviceMgt +
'/policies/remove-policy',
policyIDs,
{ headers: { 'Content-Type': 'application/json' } },
)
.then(res => {
if (res.status === 200) {
this.fetchGroups();
notification.success({
message: 'Done',
duration: 4,
description: 'Selected policy(s) was successfully removed.',
});
}
})
.catch(error => {
if (error.hasOwnProperty('response') && error.response.status === 401) {
// todo display a popop with error
message.error('You are not logged in');
window.location.href = window.location.origin + '/entgra/login';
} else {
notification.error({
message: 'There was a problem',
duration: 0,
description: 'Error occurred while trying to remove policy(s).',
});
}
});
};
applyChanges = () => {
// send request to the invoker
axios
.put(
window.location.origin +
this.config.serverConfig.invoker.uri +
this.config.serverConfig.invoker.deviceMgt +
'/policies/apply-changes',
'null',
{ headers: { 'Content-Type': 'application/json' } },
)
.then(res => {
if (res.status === 200) {
this.fetchGroups();
notification.success({
message: 'Done',
duration: 4,
description: 'Changes applied successfully.',
});
}
})
.catch(error => {
if (error.hasOwnProperty('response') && error.response.status === 401) {
// todo display a popop with error
message.error('You are not logged in');
window.location.href = window.location.origin + '/entgra/login';
} else {
notification.error({
message: 'There was a problem',
duration: 0,
description:
'Error occurred while trying to apply changes to device.',
});
}
});
};
render() {
const { data, pagination, loading, selectedRows } = this.state;
const columns = [
{
title: 'Policy Name',
dataIndex: 'policyName',
width: 100,
},
{
title: 'Description',
dataIndex: 'description',
key: 'description',
// render: enrolmentInfo => enrolmentInfo.owner
// todo add filtering options
},
{
title: 'Compilance',
dataIndex: 'compliance',
key: 'compliance',
// render: enrolmentInfo => enrolmentInfo.ownership
// todo add filtering options
},
{
title: 'Policy Type',
dataIndex: 'policyType',
key: 'policyType',
// render: enrolmentInfo => enrolmentInfo.ownership
// todo add filtering options
},
{
title: 'Action',
dataIndex: 'id',
key: 'action',
render: (id, row) => (
<span>
<PolicyAction selectedPolicyData={row} />
</span>
),
},
];
return (
<div>
<PolicyBulkActionBar
selectedRows={selectedRows}
unpublishPolicy={this.unpublishPolicy}
publishPolicy={this.publishPolicy}
removePolicy={this.removePolicy}
applyChanges={this.applyChanges}
/>
<Table
columns={columns}
rowKey={record => record.id}
dataSource={data}
pagination={{
...pagination,
size: 'small',
// position: "top",
showTotal: (total, range) =>
`showing ${range[0]}-${range[1]} of ${total} groups`,
// showQuickJumper: true
}}
loading={loading}
onChange={this.handleTableChange}
rowSelection={this.rowSelection}
/>
</div>
);
}
}
export default withConfigContext(PoliciesTable);

View File

@ -1,831 +0,0 @@
/*
* Copyright (c) 2020, Entgra (pvt) Ltd. (http://entgra.io) All Rights Reserved.
*
* Entgra (pvt) Ltd. licenses this file to you 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.
*/
import React from 'react';
import {
Alert,
Button,
Checkbox,
Col,
Collapse,
Form,
Icon,
Input,
Popconfirm,
Radio,
Row,
Select,
Table,
Tabs,
Tooltip,
Typography,
Upload,
} from 'antd';
import { withConfigContext } from '../../../../../../../../components/ConfigContext';
import moment from 'moment';
const { Text, Title, Paragraph } = Typography;
const { TabPane } = Tabs;
const { Option } = Select;
const { TextArea } = Input;
const subPanelpayloadAttributes = {};
const fieldKeys = [];
let subFormContainer = {};
let radioSubPanelSwitches = [];
let radioPanelStatusList = {};
class PolicyInfo extends React.Component {
constructor(props) {
super(props);
this.config = this.props.context;
this.state = {
data: {},
policyFeatureList: [],
activePanelKeys: [],
activeSubPanelKeys: [],
profilePreviewKey: '',
customInputDataArray: [],
inputTableDataSources: {},
isInfoPreview: false,
subPanelRadio: {},
};
}
setProfileInfo = e => {
let activePolicies = [];
let activePolicyFields = {};
let activeSubPanels = [];
let subPanelRadio = this.state.subPanelRadio;
this.setState({
subPanelRadio: radioPanelStatusList,
});
const allFields = this.props.form.getFieldsValue();
this.props.policyFeatureList.map(element => {
activePolicies.push(element.featureCode);
let featureData = JSON.parse(element.content);
Object.keys(featureData).map(key => {
if (element.featureCode in subPanelpayloadAttributes) {
Object.entries(subPanelpayloadAttributes[element.featureCode]).map(
([panelKey, payloadAttr]) => {
if (key === payloadAttr) {
activeSubPanels.push(`${element.featureCode}-${panelKey}`);
}
},
);
let regex = new RegExp(`${element.featureCode}.+${key}`, 'g');
Object.keys(allFields).map(fieldName => {
if (fieldName.match(regex) != null) {
activePolicyFields[fieldName] = featureData[key];
}
});
} else if (element.featureCode in subFormContainer) {
let regex = new RegExp(`.+${element.featureCode}-${key}`, 'g');
Object.keys(allFields).map(fieldName => {
if (fieldName.match(regex) != null) {
activePolicyFields[fieldName] = featureData[key];
if (
!activePolicies.includes(
fieldName.replace(`-${element.featureCode}-${key}`, ''),
)
) {
activePolicies.push(
fieldName.replace(`-${element.featureCode}-${key}`, ''),
);
}
}
});
} else {
let regex = new RegExp(`${element.featureCode}.+${key}`, 'g');
Object.keys(allFields).map(fieldName => {
if (fieldName.match(regex) != null) {
activePolicyFields[fieldName] = featureData[key];
}
if (
radioSubPanelSwitches.includes(
`${element.featureCode}-${featureData[key]}`,
)
) {
let subPanelViewStatus = {
[featureData[key]]: true,
};
Object.assign(subPanelRadio, subPanelViewStatus);
}
});
}
});
});
this.props.form.setFieldsValue(activePolicyFields);
this.setState({
activePanelKeys: activePolicies,
activeSubPanelKeys: activeSubPanels,
subPanelRadio,
});
};
// convert time from 24h format to 12h format
timeConverter = time => {
time = time
.toString()
.match(/^([01]\d|2[0-3])(:)([0-5]\d)(:[0-5]\d)?$/) || [time];
if (time.length > 1) {
time = time.slice(1);
time[5] = +time[0] < 12 ? ' AM' : ' PM';
time[0] = +time[0] % 12 || 12;
}
return time.join('');
};
// get Option value from start Time, end Time and time difference between 2 values
getOptionForTimeSelectors = (startTimeValue, endTimeValue, timeIncrement) => {
let timeOptions = [];
let time = new Date(
moment()
.startOf('day')
.format('YYYY/MM/DD'),
);
let tempValue = startTimeValue;
time.setMinutes(time.getMinutes() + tempValue);
let startOption = (
<Option value={String(tempValue)}>
{this.timeConverter(
`${String(time)
.split(' ')[4]
.substring(0, 5)}`,
)}
</Option>
);
timeOptions.push(startOption);
while (tempValue !== endTimeValue) {
time = new Date(
moment()
.startOf('day')
.format('YYYY/MM/DD'),
);
tempValue += timeIncrement;
if (tempValue > 1440) {
tempValue = 0;
continue;
}
time.setMinutes(time.getMinutes() + tempValue);
let option = (
<Option value={String(tempValue)}>
{this.timeConverter(
`${String(time)
.split(' ')[4]
.substring(0, 5)}`,
)}
</Option>
);
timeOptions.push(option);
}
return timeOptions;
};
// handle items which handle from radio buttons
handleRadioPanel = (e, subPanel) => {
{
subPanel.map((panel, i) => {
if (panel.value === e.target.value) {
document.getElementById(panel.value).style.display = 'block';
} else {
document.getElementById(panel.value).style.display = 'none';
}
});
}
};
// handle items which handle from select options
handleSelectedPanel = (e, subPanel) => {
{
subPanel.map((panel, i) => {
if (panel.id === e) {
document.getElementById(panel.id).style.display = 'block';
} else {
document.getElementById(panel.id).style.display = 'none';
}
});
}
};
getColumns = ({ getFieldDecorator }, arr) => {
const columnArray = [];
const actionColumn = [
{
title: '',
dataIndex: 'operation',
render: (name, row) => (
<Form.Item>
<Popconfirm title="Sure to delete?">
<a>
<Text type="danger">
<Icon type="delete" />
</Text>
</a>
</Popconfirm>
</Form.Item>
),
},
];
Object.values(arr).map((columnData, c) => {
if (columnData.type === 'input') {
const column = {
title: `${columnData.name}`,
dataIndex: `${columnData.key}`,
key: `${columnData.key}`,
render: (name, row, i) => (
<Form.Item>
{getFieldDecorator(`${columnData.key}${i}`, {})(
<Input
type={columnData.others.inputType}
placeholder={columnData.others.placeholder}
/>,
)}
</Form.Item>
),
};
columnArray.push(column);
} else if (columnData.type === 'upload') {
const column = {
title: `${columnData.name}`,
dataIndex: `${columnData.key}`,
key: `${columnData.key}`,
render: (name, row, i) => (
<Form.Item>
{getFieldDecorator(`${columnData.key}${i}`, {})(
<Upload>
<Button>
<Icon type="upload" /> Choose file
</Button>
</Upload>,
)}
</Form.Item>
),
};
columnArray.push(column);
} else if (columnData.type === 'select') {
const column = {
title: `${columnData.name}`,
dataIndex: `${columnData.key}`,
key: `${columnData.key}`,
render: (name, row, i) => (
<Form.Item>
{getFieldDecorator(`${columnData.key}${i}`, {
initialValue: columnData.others.initialDataIndex,
})(
<Select disabled>
{columnData.others.option.map((option, i) => {
return (
<Option key={i} value={option.key}>
{option.value}
</Option>
);
})}
</Select>,
)}
</Form.Item>
),
};
columnArray.push(column);
}
});
const columns = columnArray.concat(actionColumn);
return columns;
};
// generate form items
getPanelItems = (panel, panelId) => {
const { getFieldDecorator } = this.props.form;
const subPanelList = {};
return panel.map((item, k) => {
fieldKeys.push(item.id);
switch (item.type) {
case 'select':
if (item.optional.hasOwnProperty('subPanel')) {
return (
<div>
<Form.Item
key={k}
label={
<span>
{item.label}&nbsp;
<Tooltip title={item.tooltip} placement="right">
<Icon type="question-circle-o" />
</Tooltip>
</span>
}
style={{ display: 'block' }}
>
{getFieldDecorator(`${item.id}`, {
initialValue: `${item.optional.option[0].name}`,
})(
<Select
onChange={e =>
this.handleSelectedPanel(e, item.optional.subPanel)
}
disabled
>
{item.optional.option.map((option, i) => {
return (
<Option key={i} value={option.value}>
{option.name}
</Option>
);
})}
</Select>,
)}
</Form.Item>
<div className={'sub-panel-container'}>
{item.optional.subPanel.map((panel, i) => {
return (
<div
id={panel.id}
key={i}
style={
panel.id === item.optional.initialDataIndex
? { display: 'block' }
: { display: 'none' }
}
>
{this.getPanelItems(panel.panelItem)}
</div>
);
})}
</div>
</div>
);
}
return (
<Form.Item
key={k}
label={
<span>
{item.label}&nbsp;
<Tooltip title={item.tooltip} placement="right">
<Icon type="question-circle-o" />
</Tooltip>
</span>
}
style={{ display: 'block' }}
>
{getFieldDecorator(`${item.id}`, {
initialValue: `${item.optional.option[0].name}`,
})(
<Select disabled>
{item.optional.option.map((option, i) => {
return (
<Option key={i} value={option.value}>
{option.name}
</Option>
);
})}
</Select>,
)}
</Form.Item>
);
case 'timeSelector':
return (
<Form.Item
key={k}
label={
<span>
{item.label}&nbsp;
<Tooltip title={item.tooltip} placement="right">
<Icon type="question-circle-o" />
</Tooltip>
</span>
}
style={{ display: 'block' }}
>
{getFieldDecorator(`${item.id}`, {
initialValue: item.optional.initialDataIndex,
})(
<Select disabled>
{this.getOptionForTimeSelectors(
item.optional.firstOptionValue,
item.optional.lastOptionValue,
item.optional.valueDifference,
)}
</Select>,
)}
</Form.Item>
);
case 'input':
return (
<Form.Item
key={k}
label={
<span>
{item.label}&nbsp;
<Tooltip title={item.tooltip} placement="right">
<Icon type="question-circle-o" />
</Tooltip>
</span>
}
style={{ display: 'block' }}
>
{getFieldDecorator(`${item.id}`, {
rules: [
{
pattern: new RegExp(`${item.optional.rules.regex}`),
message: `${item.optional.rules.validationMsg}`,
},
],
})(<Input placeholder={item.optional.placeholder} disabled />)}
</Form.Item>
);
case 'checkbox':
if (item.optional.hasOwnProperty('subPanel')) {
return (
<div key={k}>
<Collapse
bordered={false}
activeKey={this.state.activeSubPanelKeys}
>
<Collapse.Panel
key={item.id}
showArrow={false}
style={{ border: 0 }}
header={
<Form.Item key={k}>
{getFieldDecorator(`${item.id}`, {
valuePropName: 'checked',
initialValue: item.optional.ischecked,
})(
<Checkbox disabled>
<span>
{item.label}&nbsp;
<Tooltip title={item.tooltip} placement="right">
<Icon type="question-circle-o" />
</Tooltip>
</span>
</Checkbox>,
)}
</Form.Item>
}
>
<div>
<div>
{item.optional.subPanel.map((panel, i) => {
subPanelList[panel.others.itemSwitch] =
panel.others.itemPayload;
if (
subPanelpayloadAttributes.hasOwnProperty(panelId)
) {
Object.assign(
subPanelpayloadAttributes[panelId],
subPanelList,
);
} else {
subPanelpayloadAttributes[panelId] = subPanelList;
}
return (
<div key={i}>
{this.getPanelItems(panel.panelItem, panelId)}
</div>
);
})}
</div>
</div>
</Collapse.Panel>
</Collapse>
</div>
);
}
return (
<Form.Item key={k}>
{getFieldDecorator(`${item.id}`, {
valuePropName: 'checked',
initialValue: item.optional.ischecked,
})(
<Checkbox disabled>
<span>
{item.label}&nbsp;
<Tooltip title={item.tooltip} placement="right">
<Icon type="question-circle-o" />
</Tooltip>
</span>
</Checkbox>,
)}
</Form.Item>
);
case 'textArea':
return (
<Form.Item
key={k}
label={
<span>
{item.label}&nbsp;
<Tooltip title={item.tooltip} placement="right">
<Icon type="question-circle-o" />
</Tooltip>
</span>
}
style={{ display: 'block' }}
>
{getFieldDecorator(`${item.id}`, {
initialValue: null,
})(
<TextArea
placeholder={item.optional.placeholder}
rows={item.optional.row}
disabled
/>,
)}
</Form.Item>
);
case 'radioGroup':
return (
<div>
<Form.Item
key={k}
label={
<span>
{item.label}&nbsp;
<Tooltip title={item.tooltip} placement="right">
<Icon type="question-circle-o" />
</Tooltip>
</span>
}
style={{ display: 'block' }}
>
{getFieldDecorator(`${item.id}`, {
initialValue: `${item.optional.initialValue}`,
})(
<Radio.Group
onChange={e =>
this.handleRadioPanel(e, item.optional.radio)
}
disabled
>
{item.optional.radio.map((option, i) => {
return (
<Radio key={i} value={option.value}>
{option.name}
</Radio>
);
})}
</Radio.Group>,
)}
</Form.Item>
<div className={'sub-panel-container'}>
{item.optional.subPanel.map((panel, i) => {
radioSubPanelSwitches.push(`${panelId}-${panel.id}`);
radioPanelStatusList[panel.id] = false;
return (
<div
key={i}
id={panel.id}
style={
this.state.subPanelRadio[panel.id]
? { display: 'block' }
: { display: 'none' }
}
>
{this.getPanelItems(panel.panelItem)}
</div>
);
})}
</div>
</div>
);
case 'title':
return (
<Title key={k} level={4}>
{item.label}{' '}
</Title>
);
case 'paragraph':
return (
<Paragraph key={k} style={{ marginTop: 20 }}>
{item.label}{' '}
</Paragraph>
);
case 'alert':
return (
<Alert key={k} description={item.label} type="warning" showIcon />
);
case 'upload':
return (
<Form.Item
key={k}
label={
<span>
{item.label}&nbsp;
<Tooltip title={item.tooltip} placement="right">
<Icon type="question-circle-o" />
</Tooltip>
</span>
}
>
{getFieldDecorator('upload', {})(
<Upload>
<Button>
<Icon type="upload" /> Click to upload
</Button>
</Upload>,
)}
</Form.Item>
);
case 'inputTable':
if (
!(`${item.optional.dataSource}` in this.state.inputTableDataSources)
) {
Object.defineProperty(
this.state.inputTableDataSources,
`${item.optional.dataSource}`,
{ value: [], writable: true },
);
}
return (
<div key={k}>
<Table
id={item.id}
dataSource={
this.state.inputTableDataSources[item.optional.dataSource]
}
columns={this.getColumns(
{ getFieldDecorator },
item.optional.columns,
)}
/>
</div>
);
case 'customInputTable':
return (
<div key={k}>
<Table
id={item.id}
dataSource={this.state.customInputDataArray}
columns={this.getColumns(
{ getFieldDecorator },
item.optional.columns,
)}
/>
</div>
);
default:
return null;
}
});
};
onPreview = e => {
this.setProfileInfo();
this.setState({
profilePreviewKey: 'profileInfo',
isInfoPreview: true,
});
};
onCancelPreview = e => {
this.setState({
profilePreviewKey: 'profileInfo',
isInfoPreview: false,
});
};
render() {
const { policyUIConfigurationsList } = this.props;
const { getFieldDecorator } = this.props.form;
return (
<div style={{ marginTop: 20 }}>
<Row>
<Col span={4}>
<Title level={4}>Profile Information</Title>
</Col>
<Col span={16}>
<Button
type="link"
icon="eye"
onClick={this.onPreview}
style={{ display: this.state.isInfoPreview ? 'none' : 'inline' }}
>
<Text
style={{
fontSize: 'small',
}}
>
(Click to view policy information)
</Text>
</Button>
<Button
type="link"
icon="eye-invisible"
onClick={this.onCancelPreview}
style={{ display: this.state.isInfoPreview ? 'inline' : 'none' }}
/>
</Col>
</Row>
<Collapse
bordered={false}
activeKey={this.state.profilePreviewKey}
style={{ display: this.state.isInfoPreview ? 'block' : 'none' }}
>
<Collapse.Panel
key={'profileInfo'}
showArrow={false}
style={{
border: 0,
}}
>
<div className="tab-container">
<Tabs tabPosition={'left'} size={'large'}>
{policyUIConfigurationsList.map((element, i) => {
return (
<TabPane tab={<span>{element.name}</span>} key={i}>
{Object.values(element.panels).map((panel, j) => {
panel = panel.panel;
return (
<div key={j}>
<Collapse
bordered={false}
activeKey={this.state.activePanelKeys}
>
<Collapse.Panel
key={panel.panelId}
showArrow={false}
style={{ border: 0 }}
header={
<div>
<Row>
<Col offset={0} span={14}>
<Title level={4}> {panel.title} </Title>
</Col>
</Row>
<Row>{panel.description}</Row>
</div>
}
>
{panel.hasOwnProperty('panelItem') && (
<div>
<Form name={panel.panelId}>
<Form.Item style={{ display: 'none' }}>
{getFieldDecorator(`${panel.panelId}`, {
initialValue: ' ',
})(<Input />)}
</Form.Item>
{this.getPanelItems(
panel.panelItem,
panel.panelId,
)}
</Form>
</div>
)}
{panel.hasOwnProperty('subFormLists') && (
<div>
{Object.values(panel.subFormLists).map(
(form, i) => {
subFormContainer[`${form.id}`] =
panel.panelId;
return (
<Form name={form.id} key={i}>
<Form.Item
style={{ display: 'none' }}
>
{getFieldDecorator(`${form.id}`, {
initialValue: ' ',
})(<Input />)}
</Form.Item>
{this.getPanelItems(form.panelItem)}
</Form>
);
},
)}
</div>
)}
</Collapse.Panel>
</Collapse>
</div>
);
})}
</TabPane>
);
})}
</Tabs>
</div>
</Collapse.Panel>
</Collapse>
</div>
);
}
}
export default withConfigContext(Form.create()(PolicyInfo));

View File

@ -1,131 +0,0 @@
/*
* Copyright (c) 2020, Entgra (pvt) Ltd. (http://entgra.io) All Rights Reserved.
*
* Entgra (pvt) Ltd. licenses this file to you 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.
*/
import React from 'react';
import { Card, Col, Icon, Row, Typography } from 'antd';
import { withConfigContext } from '../../../../../../../../components/ConfigContext';
const { Title, Text } = Typography;
class ProfileOverview extends React.Component {
constructor(props) {
super(props);
this.config = this.props.context;
this.state = {
data: {},
};
}
render() {
const { policyData } = this.props;
return (
<div>
<div style={{ marginTop: 20 }}>
<div>
<Title level={4}>Policy Overview</Title>
</div>
<Card>
<div style={{ paddingLeft: 4 }}>
<Row style={{ marginTop: 8 }}>
<Col span={8}>
<Text>Platform</Text>
</Col>
<Col span={8}>
<Text>{policyData.profile.deviceType.toUpperCase()}</Text>
</Col>
</Row>
<hr style={{ backgroundColor: 'grey' }} />
<Row style={{ marginTop: 15 }}>
<Col span={8}>
<Text>Groups</Text>
</Col>
<Col span={8}>
<Text>{policyData.deviceGroups}</Text>
</Col>
</Row>
<hr />
<Row style={{ marginTop: 15 }}>
<Col span={8}>
<Text>Action upon non-compliance</Text>
</Col>
<Col span={8}>
<Text>{policyData.compliance.toUpperCase()}</Text>
</Col>
</Row>
<hr />
<Row style={{ marginTop: 15 }}>
<Col span={8}>
<Text>Status</Text>
</Col>
<Col span={8}>
<Text>
{policyData.active ? (
<span>
<Icon
type="exclamation-circle"
style={{ color: '#6ab04c' }}
/>
Active
</span>
) : (
<span>
<Icon
type="exclamation-circle"
style={{ color: '#f9ca24' }}
/>
Inactive
</span>
)}
{policyData.updated ? <span>/Updated</span> : <span></span>}
</Text>
</Col>
</Row>
<hr />
<Row style={{ marginTop: 15 }}>
<Col span={8}>
<Text>Assigned Roles</Text>
</Col>
<Col span={8}>{policyData.roles}</Col>
</Row>
<hr />
<Row style={{ marginTop: 15 }}>
<Col span={8}>
<Text type={8}>Policy Type</Text>
</Col>
<Col span={8}>
<Text>{policyData.policyType}</Text>
</Col>
</Row>
</div>
</Card>
</div>
<div style={{ marginTop: 20 }}>
<Title level={4}>Description</Title>
<Card>
<Row>
<Col span={4}>
<Text>{policyData.description}</Text>
</Col>
</Row>
</Card>
</div>
</div>
);
}
}
export default withConfigContext(ProfileOverview);

View File

@ -1,147 +0,0 @@
/*
* Copyright (c) 2020, Entgra (pvt) Ltd. (http://entgra.io) All Rights Reserved.
*
* Entgra (pvt) Ltd. licenses this file to you 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.
*/
import React from 'react';
import { Col, message, notification } from 'antd';
import { withConfigContext } from '../../../../../../components/ConfigContext';
import PolicyInfo from './component/PolicyInfo';
import ProfileOverview from './component/ProfileOverview';
import axios from 'axios';
class PolicyProfile extends React.Component {
constructor(props) {
super(props);
this.config = this.props.context;
this.state = {
policyId: this.props.policyId,
policyData: null,
policyUIConfigurationsList: [],
policyFeatureList: [],
};
}
componentDidMount() {
this.getSelectedPolicy(this.props.policyId);
}
getSelectedPolicy = policyId => {
let apiUrl =
window.location.origin +
this.config.serverConfig.invoker.uri +
this.config.serverConfig.invoker.deviceMgt +
'/policies/' +
policyId;
// send request to the invokers
axios
.get(apiUrl)
.then(res => {
if (res.status === 200) {
this.setState({
policyData: res.data.data,
policyFeatureList: res.data.data.profile.profileFeaturesList,
});
this.getPolicyConfigJson(res.data.data.profile.deviceType);
}
})
.catch(error => {
if (error.hasOwnProperty('response') && error.response.status === 401) {
// todo display a popop with error
message.error('You are not logged in');
window.location.href = window.location.origin + '/entgra/login';
} else {
notification.error({
message: 'There was a problem',
duration: 0,
description: 'Error occurred while trying to load selected policy.',
});
}
});
};
getPolicyConfigJson = type => {
this.setState({ isLoading: true });
let apiUrl =
window.location.origin +
this.config.serverConfig.invoker.uri +
this.config.serverConfig.invoker.deviceMgt +
'/device-types/' +
type +
'/ui-policy-configurations';
// send request to the invokers
axios
.get(apiUrl)
.then(res => {
if (res.status === 200) {
this.setState({
isLoading: false,
policyUIConfigurationsList: JSON.parse(res.data.data),
});
}
})
.catch(error => {
if (error.hasOwnProperty('response') && error.response.status === 401) {
// todo display a popop with error
message.error('You are not logged in');
window.location.href = window.location.origin + '/entgra/login';
} else {
notification.error({
message: 'There was a problem',
duration: 0,
description: 'Error occurred while trying to load Policy details.',
});
}
this.setState({ isLoading: false });
});
};
render() {
const {
policyData,
policyUIConfigurationsList,
policyFeatureList,
} = this.state;
return (
<div>
<Col span={16} offset={4}>
{/* <Card style={{ marginTop: 24 }}>*/}
<div>
{policyData != null && (
<ProfileOverview
policyId={this.props.policyId}
policyData={policyData}
/>
)}
</div>
<div>
{policyData != null && (
<PolicyInfo
policyId={this.state.policyId}
policyFeatureList={policyFeatureList}
policyUIConfigurationsList={policyUIConfigurationsList}
/>
)}
</div>
</Col>
</div>
);
}
}
export default withConfigContext(PolicyProfile);

View File

@ -1,62 +0,0 @@
/*
* Copyright (c) 2020, Entgra (pvt) Ltd. (http://entgra.io) All Rights Reserved.
*
* Entgra (pvt) Ltd. licenses this file to you 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.
*/
import React from 'react';
import { PageHeader, Typography, Breadcrumb, Icon } from 'antd';
import { Link } from 'react-router-dom';
import PoliciesTable from './components/PoliciesTable';
const { Paragraph } = Typography;
class Policies extends React.Component {
routes;
constructor(props) {
super(props);
this.routes = props.routes;
}
render() {
return (
<div>
<PageHeader style={{ paddingTop: 0 }}>
<Breadcrumb style={{ paddingBottom: 16 }}>
<Breadcrumb.Item>
<Link to="/entgra">
<Icon type="home" /> Home
</Link>
</Breadcrumb.Item>
<Breadcrumb.Item>Policies</Breadcrumb.Item>
</Breadcrumb>
<div className="wrap">
<h3>Policies</h3>
<Paragraph>All policies for device management</Paragraph>
</div>
<div style={{ backgroundColor: '#ffffff', borderRadius: 5 }}>
<PoliciesTable />
</div>
</PageHeader>
<div
style={{ background: '#f0f2f5', padding: 24, minHeight: 720 }}
></div>
</div>
);
}
}
export default Policies;

View File

@ -1,62 +0,0 @@
/*
* Copyright (c) 2020, Entgra (pvt) Ltd. (http://entgra.io) All Rights Reserved.
*
* Entgra (pvt) Ltd. licenses this file to you 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.
*/
import React from 'react';
import { PageHeader, Typography, Breadcrumb, Icon } from 'antd';
import { Link } from 'react-router-dom';
import AddPolicy from '../../components/AddPolicy';
const { Paragraph } = Typography;
class AddNewPolicy extends React.Component {
routes;
constructor(props) {
super(props);
this.routes = props.routes;
}
render() {
return (
<div>
<PageHeader style={{ paddingTop: 0 }}>
<Breadcrumb style={{ paddingBottom: 16 }}>
<Breadcrumb.Item>
<Link to="/entgra">
<Icon type="home" /> Home
</Link>
</Breadcrumb.Item>
<Breadcrumb.Item>Policies</Breadcrumb.Item>
</Breadcrumb>
<div className="wrap">
<h3>Policies</h3>
<Paragraph>Create new policy on IoT Server.</Paragraph>
</div>
<div style={{ borderRadius: 5 }}>
<AddPolicy />
</div>
</PageHeader>
<div
style={{ background: '#f0f2f5', padding: 24, minHeight: 720 }}
></div>
</div>
);
}
}
export default AddNewPolicy;

View File

@ -1,69 +0,0 @@
/*
* Copyright (c) 2020, Entgra (pvt) Ltd. (http://entgra.io) All Rights Reserved.
*
* Entgra (pvt) Ltd. licenses this file to you 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.
*/
import React, { Component } from 'react';
import { Breadcrumb, Icon, PageHeader } from 'antd';
import { Link } from 'react-router-dom';
import EditPolicy from '../../components/EditPolicy';
import { withConfigContext } from '../../../../../../components/ConfigContext';
class EditSelectedPolicy extends Component {
routes;
constructor(props) {
super(props);
this.routes = props.routes;
this.config = this.props.context;
this.state = {
data: {},
policyOverview: {},
policyId: '',
};
}
render() {
const {
match: { params },
} = this.props;
return (
<div>
<PageHeader style={{ paddingTop: 0 }}>
<Breadcrumb style={{ paddingBottom: 16 }}>
<Breadcrumb.Item>
<Link to="/entgra">
<Icon type="home" /> Home
</Link>
</Breadcrumb.Item>
<Breadcrumb.Item>Policies</Breadcrumb.Item>
</Breadcrumb>
<div className="wrap">
{/* <h3>Policies</h3>*/}
{/* <Paragraph>Create new policy on IoT Server.</Paragraph>*/}
</div>
<div style={{ borderRadius: 5 }}>
<EditPolicy policyId={params.policyId} />
</div>
</PageHeader>
<div
style={{ background: '#f0f2f5', padding: 24, minHeight: 720 }}
></div>
</div>
);
}
}
export default withConfigContext(EditSelectedPolicy);

View File

@ -1,68 +0,0 @@
/*
* Copyright (c) 2020, Entgra (pvt) Ltd. (http://entgra.io) All Rights Reserved.
*
* Entgra (pvt) Ltd. licenses this file to you 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.
*/
import React, { Component } from 'react';
import { Breadcrumb, Icon, PageHeader } from 'antd';
import { Link } from 'react-router-dom';
import { withConfigContext } from '../../../../../../components/ConfigContext';
import PolicyProfile from '../../components/PolicyProfile';
class ViewPolicy extends Component {
routes;
constructor(props) {
super(props);
this.routes = props.routes;
this.config = this.props.context;
this.state = {
data: {},
policyOverview: {},
policyId: '',
};
}
render() {
const {
match: { params },
} = this.props;
return (
<div>
<PageHeader style={{ paddingTop: 0 }}>
<Breadcrumb style={{ paddingBottom: 16 }}>
<Breadcrumb.Item>
<Link to="/entgra">
<Icon type="home" /> Home
</Link>
</Breadcrumb.Item>
<Breadcrumb.Item>Policies</Breadcrumb.Item>
</Breadcrumb>
<div className="wrap">
{/* <h3>Policies</h3>*/}
{/* <Paragraph>Create new policy on IoT Server.</Paragraph>*/}
</div>
<div style={{ borderRadius: 5 }}>
<PolicyProfile policyId={params.policyId} />
</div>
</PageHeader>
<div
style={{ background: '#f0f2f5', padding: 24, minHeight: 720 }}
></div>
</div>
);
}
}
export default withConfigContext(ViewPolicy);

View File

@ -1,32 +0,0 @@
/*
* Copyright (c) 2020, Entgra (pvt) Ltd. (http://entgra.io) All Rights Reserved.
*
* Entgra (pvt) Ltd. licenses this file to you 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.
*/
.tab-container > .ant-tabs > .ant-tabs-bar {
border-color: #aaaaaa;
}
.tab-container > .ant-tabs > .ant-tabs-bar .ant-tabs-tab {
height: 50px;
margin: 0;
}
.tab-container > .ant-tabs > .ant-tabs-bar .ant-tabs-tab-active {
border-color: transparent;
background: #4b92db;
color: #fff;
}

View File

@ -1,410 +0,0 @@
/*
* Copyright (c) 2020, Entgra (pvt) Ltd. (http://entgra.io) All Rights Reserved.
*
* Entgra (pvt) Ltd. licenses this file to you 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.
*/
import React from 'react';
import {
Button,
Form,
Input,
message,
Modal,
notification,
Select,
Tree,
} from 'antd';
import { withConfigContext } from '../../../../../../../../components/ConfigContext';
import axios from 'axios';
const { Option } = Select;
const { TreeNode } = Tree;
class AddRole extends React.Component {
constructor(props) {
super(props);
this.config = this.props.context;
this.expandKeys = [];
this.state = {
isAddRoleModalVisible: false,
isAddPermissionModalVisible: false,
roleName: '',
users: [],
nodeList: [],
expandedKeys: [],
autoExpandParent: true,
checkedKeys: [],
isNodeList: false,
};
}
openAddModal = () => {
this.setState({
isAddRoleModalVisible: true,
});
};
onCancelHandler = e => {
this.setState({
isAddRoleModalVisible: false,
isAddPermissionModalVisible: false,
});
};
getCheckedPermissionsList = data => {
data.forEach(item => {
if (item !== null) {
this.expandKeys.push(item.resourcePath);
this.getCheckedPermissionsList(item.nodeList);
} else {
return null;
}
});
};
onAddRole = e => {
this.props.form.validateFields((err, values) => {
if (!err) {
this.onConfirmAddRole(values);
}
console.log(values);
});
};
onExpand = expandedKeys => {
this.setState({
expandedKeys,
autoExpandParent: false,
});
};
onCheck = checkedKeys => {
this.setState({ checkedKeys });
};
onConfirmAddRole = value => {
const roleData = {
roleName: value.roleName,
users: value.users,
};
this.setState({
roleName: value.roleName,
});
axios
.post(
window.location.origin +
this.config.serverConfig.invoker.uri +
this.config.serverConfig.invoker.deviceMgt +
'/roles',
roleData,
{ headers: { 'Content-Type': 'application-json' } },
)
.then(res => {
if (res.status === 201) {
this.props.fetchUsers();
this.setState({
isAddRoleModalVisible: false,
isAddPermissionModalVisible: true,
});
notification.success({
message: 'Done',
duration: 4,
description: 'Successfully added the role.',
});
this.loadPermissionList();
}
})
.catch(error => {
if (error.hasOwnProperty('response') && error.response.status === 401) {
// todo display a popop with error
message.error('You are not logged in');
window.location.href = window.location.origin + '/entgra/login';
} else {
notification.error({
message: 'There was a problem',
duration: 0,
description: 'Error occurred while trying to add role.',
});
}
});
};
renderTreeNodes = data => {
return data.map(item => {
if (item !== null) {
if (item.hasOwnProperty('nodeList')) {
return (
<TreeNode
title={item.displayName}
key={item.resourcePath}
dataRef={item}
>
{this.renderTreeNodes(item.nodeList)}
</TreeNode>
);
}
return <TreeNode key={item.resourcePath} {...item} />;
}
// eslint-disable-next-line react/jsx-key
return <TreeNode />;
});
};
onAssignPermissions = () => {
const roleData = {
roleName: this.state.roleName,
permissions: this.state.checkedKeys,
};
axios
.put(
window.location.origin +
this.config.serverConfig.invoker.uri +
this.config.serverConfig.invoker.deviceMgt +
'/roles/' +
this.state.roleName,
roleData,
{ headers: { 'Content-Type': 'application-json' } },
)
.then(res => {
if (res.status === 200) {
this.props.fetchUsers();
notification.success({
message: 'Done',
duration: 4,
description: 'Successfully Updated the Permissions.',
});
this.setState({
isAddPermissionModalVisible: false,
});
}
})
.catch(error => {
if (error.hasOwnProperty('response') && error.response.status === 401) {
// todo display a popop with error
message.error('You are not logged in');
window.location.href = window.location.origin + '/entgra/login';
} else {
notification.error({
message: 'There was a problem',
duration: 0,
description: 'Error occurred while trying to add permissions.',
});
}
});
};
loadPermissionList = () => {
let apiURL =
window.location.origin +
this.config.serverConfig.invoker.uri +
this.config.serverConfig.invoker.deviceMgt +
'/roles/' +
this.state.roleName +
'/permissions';
axios
.get(apiURL)
.then(res => {
if (res.status === 200) {
this.getCheckedPermissionsList(res.data.data.nodeList);
this.setState({
nodeList: res.data.data.nodeList,
isNodeList: true,
expandedKeys: this.expandKeys,
});
}
})
.catch(error => {
if (error.hasOwnProperty('response') && error.response.status === 401) {
// todo display a popop with error
message.error('You are not logged in');
window.location.href = window.location.origin + '/entgra/login';
} else {
notification.error({
message: 'There was a problem',
duration: 0,
description: 'Error occurred while trying to load permission.',
});
}
});
};
loadUsersList = value => {
let apiURL =
window.location.origin +
this.config.serverConfig.invoker.uri +
this.config.serverConfig.invoker.deviceMgt +
'/users/search/usernames?filter=' +
value +
'&domain=Primary';
axios
.get(apiURL)
.then(res => {
if (res.status === 200) {
let user = JSON.parse(res.data.data);
let users = [];
for (let i = 0; i < user.length; i++) {
users.push(
<Option key={user[i].username}>{user[i].username}</Option>,
);
}
this.setState({
users: users,
});
}
})
.catch(error => {
if (error.hasOwnProperty('response') && error.response.status === 401) {
// todo display a popop with error
message.error('You are not logged in');
window.location.href = window.location.origin + '/entgra/login';
} else {
notification.error({
message: 'There was a problem',
duration: 0,
description: 'Error occurred while trying to load users.',
});
}
});
};
render() {
const { getFieldDecorator } = this.props.form;
return (
<div>
<div>
<Button
type="primary"
icon="plus"
size={'default'}
onClick={this.openAddModal}
style={{ marginBottom: '10px' }}
>
Add Role
</Button>
</div>
<div>
<Modal
title="ADD NEW ROLE"
width="40%"
visible={this.state.isAddRoleModalVisible}
onOk={this.onAddRole}
onCancel={this.onCancelHandler}
footer={[
<Button key="cancel" onClick={this.onCancelHandler}>
Cancel
</Button>,
<Button key="submit" type="primary" onClick={this.onAddRole}>
Add Role
</Button>,
]}
>
<div style={{ alignItems: 'center' }}>
<p>Create new user on IoT Server.</p>
<Form labelCol={{ span: 5 }} wrapperCol={{ span: 18 }}>
<Form.Item
label="User Store Domain"
style={{ display: 'block' }}
>
{getFieldDecorator('userStoreDomain', {
initialValue: 'PRIMARY',
})(
<Select>
<Option key="PRIMARY">PRIMARY</Option>
</Select>,
)}
</Form.Item>
<Form.Item label="Role Name" style={{ display: 'block' }}>
{getFieldDecorator('roleName', {
rules: [
{
pattern: new RegExp('^(((?!(\\@|\\/|\\s)).){3,})*$'),
message:
'Role name should be in minimum 3 characters long and not ' +
'include any whitespaces or @ or /',
},
{
required: true,
message: 'This field is required.',
},
],
})(<Input />)}
</Form.Item>
<Form.Item label="User List" style={{ display: 'block' }}>
{getFieldDecorator('users', {})(
<Select
mode="multiple"
style={{ width: '100%' }}
onSearch={this.loadUsersList}
>
{this.state.users}
</Select>,
)}
</Form.Item>
</Form>
</div>
</Modal>
</div>
<div>
<Modal
title="CHANGE ROLE PERMISSION"
width="40%"
visible={this.state.isAddPermissionModalVisible}
onOk={this.onAssignPermissions}
onCancel={this.onCancelHandler}
bodyStyle={{
overflowY: 'scroll',
maxHeight: '500px',
marginLeft: '10px',
}}
footer={[
<Button key="cancel" onClick={this.onCancelHandler}>
Cancel
</Button>,
<Button
key="submit"
type="primary"
onClick={this.onAssignPermissions}
>
Assign
</Button>,
]}
>
<div style={{ alignItems: 'center' }}>
<div>
{this.state.isNodeList && (
<Tree
checkable
onExpand={this.onExpand}
expandedKeys={this.state.expandedKeys}
autoExpandParent={this.state.autoExpandParent}
onCheck={this.onCheck}
checkedKeys={this.state.checkedKeys}
>
{this.renderTreeNodes(this.state.nodeList)}
</Tree>
)}
</div>
</div>
</Modal>
</div>
</div>
);
}
}
export default withConfigContext(Form.create({ name: 'add-role' })(AddRole));

Some files were not shown because too many files have changed in this diff Show More