Remove APPM store and publisher UIs from device-mgt core

revert-70ac1926
tcdlpds@gmail.com 4 years ago
parent 4943635a61
commit 52761c673d

14
.gitignore vendored

@ -26,17 +26,3 @@ target
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
hs_err_pid*
components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/node_modules/
components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/package-lock.json
components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/npm-debug.log
components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/dist/
components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/tmp/
components/application-mgt/org.wso2.carbon.device.application.mgt.store.ui/react-app/node_modules/
components/application-mgt/org.wso2.carbon.device.application.mgt.store.ui/react-app/dist/
components/application-mgt/org.wso2.carbon.device.application.mgt.store.ui/react-app/package-lock.json
components/application-mgt/org.wso2.carbon.device.application.mgt.store.ui/react-app/tmp/
components/device-mgt/io.entgra.device.mgt.ui/react-app/node_modules/
components/device-mgt/io.entgra.device.mgt.ui/react-app/dist/
components/device-mgt/io.entgra.device.mgt.ui/react-app/package-lock.json
components/device-mgt/io.entgra.device.mgt.ui/react-app/tmp/

@ -1,128 +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>application-mgt</artifactId>
<version>4.1.11-SNAPSHOT</version>
</parent>
<artifactId>org.wso2.carbon.device.application.mgt.publisher.ui</artifactId>
<version>4.1.11-SNAPSHOT</version>
<packaging>war</packaging>
<name>WSO2 Carbon - Application Management Publisher UI Component</name>
<url>https://entgra.io</url>
<description>This Component contains Application Management publisher UI</description>
<build>
<plugins>
<plugin>
<artifactId>maven-war-plugin</artifactId>
<configuration>
<packagingExcludes>WEB-INF/lib/*cxf*.jar</packagingExcludes>
<warName>publisher</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>
</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>

@ -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, 100, 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
}
}

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

@ -1,28 +0,0 @@
/*
* 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.
*/
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,
};
};

@ -1,112 +0,0 @@
{
"name": "publisher",
"version": "1.0.0",
"description": "WSO2 IoT Server App Publisher",
"main": "App.js",
"proxy": "http://localhost:3001",
"repository": {
"type": "git",
"url": "git://github.com/wso2/carbon-devicemgt"
},
"license": "Apache License 2.0",
"dependencies": {
"@ant-design/compatible": "^1.0.0",
"@ant-design/dark-theme": "^0.2.2",
"@ant-design/icons": "^4.0.6",
"@babel/polyfill": "^7.6.0",
"acorn": "^6.2.0",
"antd": "^4.0.0",
"axios": "^0.19.0",
"babel-eslint": "^10.1.0",
"d3": "^5.9.7",
"dagre": "^0.8.4",
"entgra-icons-react": "^1.0.0",
"fetch": "^1.1.0",
"imagemin": "^6.1.0",
"keymirror": "^0.1.1",
"lodash.debounce": "latest",
"rc-tween-one": "^2.4.1",
"react-d3-graph": "^2.1.0",
"react-highlight-words": "^0.16.0",
"react-html-parser": "^2.0.2",
"react-infinite-scroller": "^1.2.4",
"react-quill": "^1.3.3",
"react-router": "latest",
"react-router-config": "^5.0.1",
"react-router-dom": "^5.0.1",
"react-scripts": "2.1.8",
"react-star-ratings": "^2.3.0",
"redux-thunk": "^2.3.0",
"shade-blend-color": "^1.0.0",
"storm-react-diagrams": "^5.2.1",
"typescript": "^3.6.4"
},
"devDependencies": {
"@babel/core": "^7.5.0",
"@babel/plugin-proposal-class-properties": "^7.5.0",
"@babel/preset-env": "^7.5.0",
"@babel/preset-react": "^7.0.0",
"@babel/register": "^7.4.4",
"babel-core": "^6.26.3",
"babel-loader": "^8.0.6",
"babel-polyfill": "^6.26.0",
"babel-preset-es2015": "^6.24.1",
"babel-preset-stage-0": "^6.24.1",
"body-parser": "^1.19.0",
"chai": "^4.1.2",
"cross-env": "^7.0.0",
"css-loader": "^0.28.11",
"entgra-icons": "^1.4.0",
"eslint": "^5.16.0",
"eslint-config-prettier": "4.3.0",
"eslint-plugin-babel": "5.3.0",
"eslint-plugin-import": "^2.21.2",
"eslint-plugin-jsx": "0.0.2",
"eslint-plugin-jsx-a11y": "^6.3.1",
"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.10.3",
"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",
"react": "^16.8.6",
"react-dom": "^16.8.6",
"react-intl": "^2.9.0",
"react-redux": "^7.1.0",
"redux": "^4.0.1",
"sass-loader": "^6.0.7",
"style-loader": "^0.18.2",
"url-loader": "^1.1.2",
"webpack": "^4.39.3",
"webpack-cli": "^3.3.7",
"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\""
}
}

@ -1,100 +0,0 @@
{
"appName": "publisher",
"theme": {
"logo": "https://entgra.io/assets/images/svg/logo.svg",
"primaryColor": "rgb(24, 144, 255)",
"footerText": "©2020 entgra.io"
},
"serverConfig": {
"invoker": {
"contextPath" : "/publisher-ui-request-handler",
"uri": "/publisher-ui-request-handler/invoke",
"publisher": "/application-mgt-publisher/v1.0",
"store": "/application-mgt-store/v1.0",
"deviceMgt": "/device-mgt/v1.0"
},
"loginUri": "/publisher-ui-request-handler/login",
"logoutUri": "/publisher-ui-request-handler/logout",
"ssoLoginUri": "/publisher-ui-request-handler/ssoLogin",
"ssoLogoutUri": "/publisher-ui-request-handler/ssoLogout",
"platform": "publisher",
"appUiConfigUri": "/api/device-mgt-config/v1.0/configurations/ui-config"
},
"defaultPlatformIcons": {
"default": {
"icon": "fw-globe",
"color": "#535c68"
},
"android": {
"icon": "fw-android",
"color": "#7db343"
},
"ios": {
"icon": "fw-apple",
"color": "#535c68"
},
"windows": {
"icon": "fw-windows",
"color": "#008cc4"
}
},
"lifecycle": {
"CREATED": {
"title": "Created",
"text": "The initial most state of an application.\n You can only proceed to one of the following states:",
"icon": "fw-document",
"step": 0
},
"IN-REVIEW": {
"title": "In-Review",
"text": "In this state the application is being reviewed by approvers. You can move from this state to APPROVED state if it is approved by reviewers. Otherwise, you can move the application into either REJECTED state or CREATED state based on the feedback getting by reviewers.",
"icon": "fw-throttling-policy",
"step": 1
},
"APPROVED": {
"title": "Approved",
"text": "The approved state is a compulsory state prior to publishing the application.\n You can only proceed to one of the following states:",
"icon": "fw-check",
"step": 2
},
"PUBLISHED": {
"title": "Published",
"text": "The state which is applied for applications which are qualified for your Corporate App Store. Only the applications of Published state can be installed to your corporate devices.\n You can only proceed to one of the following states:",
"icon": "fw-globe",
"step": 3
},
"BLOCKED": {
"title": "Blocked",
"text": "This state allows you to block your application either to publish or deprecate at a future date.\n You can only proceed to one of the following states:",
"icon": "fw-lock",
"step": 4
},
"DEPRECATED": {
"title": "Deprecated",
"text": "The applications which are outdated and no longer suit your app store.\n You can only proceed to one of the following states:",
"icon": "fw-warning",
"step": 5
},
"REJECTED": {
"title": "Rejected",
"text": "The Approvers can reject an application due to a faulty of the app or not being in compliance with company policies.\n You can only proceed to one of the following states:",
"icon": "fw-error",
"step": 6
},
"RETIRED": {
"title": "Retired",
"text": "The final state of an application, where no transition of states will be allowed after this.",
"icon": "fw-delete",
"step": 7
}
},
"deviceTypes": {
"mobileTypes": ["android", "ios", "windows"]
},
"windowsDeviceType": {
"appType": ["msi", "appx"]
},
"windowsAppxMsiKeyValueForMetaData": {
"metaKeyArray": ["Package_Url", "Dependency_Package_Url", "Certificate_Hash", "Encoded_Cert_Content", "Package_Family_Name", "Product_Id", "Content_Uri", "File_Hash"]
}
}

@ -1,15 +0,0 @@
{
"short_name": "App Publisher",
"name": "WSO2 IoT App Publisher",
"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 it is too large Load Diff

Before

Width:  |  Height:  |  Size: 443 KiB

@ -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: 44 KiB

@ -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"
}

@ -1,903 +0,0 @@
/*
* Copyright (c) 2017, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
*
* WSO2 Inc. 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 publisher pages. */
.publisher-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;
}
*/

@ -1,17 +0,0 @@
const express = require('express');
const bodyParser = require('body-parser');
const pino = require('express-pino-logger')();
const app = express();
app.use(bodyParser.urlencoded({ extended: false }));
app.use(pino);
app.get('/api/greeting', (req, res) => {
const name = req.query.name || 'World';
res.setHeader('Content-Type', 'application/json');
res.send(JSON.stringify({ greeting: `Hello ${name}!` }));
});
app.listen(3001, () =>
console.log('Express server is running on localhost:3001')
);

@ -1,78 +0,0 @@
/*
* 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.
*/
.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{
height:450px;
border-radius: 25px;
padding: 15px;
}
.release .release-images{
overflow-x: auto;
overflow-y: hidden;
}
.release .release-images::-webkit-scrollbar-track{
-webkit-box-shadow: inset 0 0 6px rgba(0,0,0,0.3);
background-color: #F5F5F5;
border-radius: 10px;
}
.release .release-images::-webkit-scrollbar{
height: 8px;
background-color: #F5F5F5;
}
.release .release-images::-webkit-scrollbar-thumb{
background-color: #aaaaaa;
border-radius: 10px;
}
.release .release-screenshot{
display: table-cell;
}
.main-container{
background: #f0f2f5;
min-height: 780px
}
@media only screen and (min-width: 768px) {
.main-container{
padding: 24px;
}
}

@ -1,224 +0,0 @@
/*
* 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.
*/
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 } from 'antd';
import ConfigContext from './components/ConfigContext';
import { getUiConfig } from './services/utils/uiConfigHandler';
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() {
this.updateFavicon();
axios
.get(window.location.origin + '/publisher/public/conf/config.json')
.then(res => {
const config = res.data;
this.checkUserLoggedIn(config);
})
.catch(error => {
this.setState({
loading: false,
error: true,
});
});
}
getAndroidEnterpriseToken = config => {
axios
.get(
window.location.origin +
config.serverConfig.invoker.uri +
'/device-mgt/android/v1.0/enterprise/store-url?approveApps=true' +
'&searchEnabled=true&isPrivateAppsEnabled=true&isWebAppEnabled=true&isOrganizeAppPageVisible=true&isManagedConfigEnabled=true' +
'&host=' +
window.location.origin,
)
.then(res => {
config.androidEnterpriseToken = res.data.data.token;
this.setState({
loading: false,
config: config,
});
})
.catch(error => {
config.androidEnterpriseToken = null;
this.setState({
loading: false,
config: config,
});
});
};
checkUserLoggedIn = config => {
axios
.post(
window.location.origin +
config.serverConfig.invoker.contextPath +
'/user',
)
.then(res => {
config.user = {
username: res.data.data,
};
const pageURL = window.location.pathname;
const lastURLSegment = pageURL.substr(pageURL.lastIndexOf('/') + 1);
if (lastURLSegment === 'login') {
window.location.href = window.location.origin + '/publisher/';
} else {
this.getUserPermissions(config);
}
})
.catch(error => {
this.handleApiError(error, config);
});
};
updateFavicon = () => {
const link =
document.querySelector("link[rel*='icon']") ||
document.createElement('link');
link.type = 'image/x-icon';
link.rel = 'shortcut icon';
link.href =
window.location.origin +
'/devicemgt/public/uuf.unit.favicon/img/favicon.png';
document.getElementsByTagName('head')[0].appendChild(link);
};
getUserPermissions = config => {
axios
.get(
window.location.origin +
config.serverConfig.invoker.uri +
config.serverConfig.invoker.deviceMgt +
'/users/current-user/permissions',
)
.then(res => {
config.user.permissions = res.data.data.permissions;
this.getAndroidEnterpriseToken(config);
})
.catch(error => {
this.handleApiError(error, config);
});
};
handleApiError = (error, config) => {
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);
getUiConfig(config).then(uiConfig => {
if (uiConfig !== undefined) {
if (uiConfig.isSsoEnable) {
window.location =
window.location.origin +
config.serverConfig.ssoLoginUri +
'?redirect=' +
window.location.origin +
pageURL;
} else if (lastURLSegment !== 'login') {
window.location.href =
window.location.origin +
`/${config.appName}/login?redirect=${redirectUrl}`;
} else {
this.getAndroidEnterpriseToken(config);
}
} else {
this.setState({
loading: false,
error: true,
});
}
});
} else {
this.setState({
loading: false,
error: true,
});
}
};
render() {
const { loading, error } = this.state;
const applicationView = (
<Router>
<ConfigContext.Provider value={this.state.config}>
<div>
<Switch>
<Redirect exact from="/publisher" to="/publisher/apps" />
{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;

@ -1,27 +0,0 @@
/*
* 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.
*/
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);
});

@ -1,39 +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 '../ConfigContext';
import { isAuthorized } from '../../services/utils/authorizationHandler';
class Authorized extends react.Component {
constructor(props) {
super(props);
}
render() {
return isAuthorized(this.props.context.user, this.props.permission)
? this.props.yes
: this.props.no;
}
}
Authorized.defaultProps = {
yes: null,
no: null,
};
export default withConfigContext(Authorized);

@ -1,34 +0,0 @@
/*
* 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.
*/
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;

@ -1,40 +0,0 @@
/*
* 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.
*/
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} routes={this.props.routes} />
)}
/>
);
}
}
export default RouteWithSubRoutes;

@ -1,45 +0,0 @@
/*
* 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.
*/
.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;
}

@ -1,28 +0,0 @@
<!--
~ 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.
-->
<!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 App Publisher</title>
<script src="https://apis.google.com/js/client.js"></script>
</head>
<div id="root"></div>
</html>

@ -1,107 +0,0 @@
/*
* 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.
*/
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 Dashboard from './scenes/Home';
import Apps from './scenes/Home/scenes/Apps';
import Release from './scenes/Home/scenes/Apps/scenes/Release';
import AddNewEnterpriseApp from './scenes/Home/scenes/AddNewApp/scenes/Enterprise';
import Mange from './scenes/Home/scenes/Manage';
import './index.css';
import AddNewPublicApp from './scenes/Home/scenes/AddNewApp/scenes/Public';
import AddNewWebClip from './scenes/Home/scenes/AddNewApp/scenes/WebClip';
import AddNewRelease from './scenes/Home/scenes/AddNewRelease';
import AddNewCustomApp from './scenes/Home/scenes/AddNewApp/scenes/Custom';
import ManageAndroidEnterprise from './scenes/Home/scenes/Manage/scenes/AndroidEnterprise';
import Page from './scenes/Home/scenes/Manage/scenes/AndroidEnterprise/scenes/Page';
const routes = [
{
path: '/publisher/login',
exact: true,
component: Login,
},
{
path: '/publisher/',
exact: false,
component: Dashboard,
routes: [
{
path: '/publisher/apps',
component: Apps,
exact: true,
},
{
path: '/publisher/apps/releases/:uuid',
exact: true,
component: Release,
},
{
path: '/publisher/apps/:deviceType/:appId/add-release',
component: AddNewRelease,
exact: true,
},
{
path: '/publisher/add-new-app/enterprise',
component: AddNewEnterpriseApp,
exact: true,
},
{
path: '/publisher/add-new-app/public',
component: AddNewPublicApp,
exact: true,
},
{
path: '/publisher/add-new-app/web-clip',
component: AddNewWebClip,
exact: true,
},
{
path: '/publisher/add-new-app/custom-app',
component: AddNewCustomApp,
exact: true,
},
{
path: '/publisher/manage',
component: Mange,
exact: true,
},
{
path: '/publisher/manage/android-enterprise',
component: ManageAndroidEnterprise,
exact: true,
},
{
path: '/publisher/manage/android-enterprise/pages/:pageName/:pageId',
component: Page,
exact: true,
},
],
},
];
ReactDOM.render(<App routes={routes} />, document.getElementById('root'));
// If you want your app to work offline 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();

@ -1,7 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 841.9 595.3">
<g fill="#61DAFB">
<path d="M666.3 296.5c0-32.5-40.7-63.3-103.1-82.4 14.4-63.6 8-114.2-20.2-130.4-6.5-3.8-14.1-5.6-22.4-5.6v22.3c4.6 0 8.3.9 11.4 2.6 13.6 7.8 19.5 37.5 14.9 75.7-1.1 9.4-2.9 19.3-5.1 29.4-19.6-4.8-41-8.5-63.5-10.9-13.5-18.5-27.5-35.3-41.6-50 32.6-30.3 63.2-46.9 84-46.9V78c-27.5 0-63.5 19.6-99.9 53.6-36.4-33.8-72.4-53.2-99.9-53.2v22.3c20.7 0 51.4 16.5 84 46.6-14 14.7-28 31.4-41.3 49.9-22.6 2.4-44 6.1-63.6 11-2.3-10-4-19.7-5.2-29-4.7-38.2 1.1-67.9 14.6-75.8 3-1.8 6.9-2.6 11.5-2.6V78.5c-8.4 0-16 1.8-22.6 5.6-28.1 16.2-34.4 66.7-19.9 130.1-62.2 19.2-102.7 49.9-102.7 82.3 0 32.5 40.7 63.3 103.1 82.4-14.4 63.6-8 114.2 20.2 130.4 6.5 3.8 14.1 5.6 22.5 5.6 27.5 0 63.5-19.6 99.9-53.6 36.4 33.8 72.4 53.2 99.9 53.2 8.4 0 16-1.8 22.6-5.6 28.1-16.2 34.4-66.7 19.9-130.1 62-19.1 102.5-49.9 102.5-82.3zm-130.2-66.7c-3.7 12.9-8.3 26.2-13.5 39.5-4.1-8-8.4-16-13.1-24-4.6-8-9.5-15.8-14.4-23.4 14.2 2.1 27.9 4.7 41 7.9zm-45.8 106.5c-7.8 13.5-15.8 26.3-24.1 38.2-14.9 1.3-30 2-45.2 2-15.1 0-30.2-.7-45-1.9-8.3-11.9-16.4-24.6-24.2-38-7.6-13.1-14.5-26.4-20.8-39.8 6.2-13.4 13.2-26.8 20.7-39.9 7.8-13.5 15.8-26.3 24.1-38.2 14.9-1.3 30-2 45.2-2 15.1 0 30.2.7 45 1.9 8.3 11.9 16.4 24.6 24.2 38 7.6 13.1 14.5 26.4 20.8 39.8-6.3 13.4-13.2 26.8-20.7 39.9zm32.3-13c5.4 13.4 10 26.8 13.8 39.8-13.1 3.2-26.9 5.9-41.2 8 4.9-7.7 9.8-15.6 14.4-23.7 4.6-8 8.9-16.1 13-24.1zM421.2 430c-9.3-9.6-18.6-20.3-27.8-32 9 .4 18.2.7 27.5.7 9.4 0 18.7-.2 27.8-.7-9 11.7-18.3 22.4-27.5 32zm-74.4-58.9c-14.2-2.1-27.9-4.7-41-7.9 3.7-12.9 8.3-26.2 13.5-39.5 4.1 8 8.4 16 13.1 24 4.7 8 9.5 15.8 14.4 23.4zM420.7 163c9.3 9.6 18.6 20.3 27.8 32-9-.4-18.2-.7-27.5-.7-9.4 0-18.7.2-27.8.7 9-11.7 18.3-22.4 27.5-32zm-74 58.9c-4.9 7.7-9.8 15.6-14.4 23.7-4.6 8-8.9 16-13 24-5.4-13.4-10-26.8-13.8-39.8 13.1-3.1 26.9-5.8 41.2-7.9zm-90.5 125.2c-35.4-15.1-58.3-34.9-58.3-50.6 0-15.7 22.9-35.6 58.3-50.6 8.6-3.7 18-7 27.7-10.1 5.7 19.6 13.2 40 22.5 60.9-9.2 20.8-16.6 41.1-22.2 60.6-9.9-3.1-19.3-6.5-28-10.2zM310 490c-13.6-7.8-19.5-37.5-14.9-75.7 1.1-9.4 2.9-19.3 5.1-29.4 19.6 4.8 41 8.5 63.5 10.9 13.5 18.5 27.5 35.3 41.6 50-32.6 30.3-63.2 46.9-84 46.9-4.5-.1-8.3-1-11.3-2.7zm237.2-76.2c4.7 38.2-1.1 67.9-14.6 75.8-3 1.8-6.9 2.6-11.5 2.6-20.7 0-51.4-16.5-84-46.6 14-14.7 28-31.4 41.3-49.9 22.6-2.4 44-6.1 63.6-11 2.3 10.1 4.1 19.8 5.2 29.1zm38.5-66.7c-8.6 3.7-18 7-27.7 10.1-5.7-19.6-13.2-40-22.5-60.9 9.2-20.8 16.6-41.1 22.2-60.6 9.9 3.1 19.3 6.5 28.1 10.2 35.4 15.1 58.3 34.9 58.3 50.6-.1 15.7-23 35.6-58.4 50.6zM320.8 78.4z"/>
<circle cx="420.9" cy="296.5" r="45.7"/>
<path d="M520.5 78.1z"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 2.6 KiB

@ -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 { LogoutOutlined } from '@ant-design/icons';
import { notification, Menu } from 'antd';
import axios from 'axios';
import { withConfigContext } from '../../../../components/ConfigContext';
import { getUiConfig } from '../../../../services/utils/uiConfigHandler';
/*
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,
});
let logoutUri;
getUiConfig(config).then(uiConfig => {
if (uiConfig !== undefined) {
if (uiConfig.isSsoEnable) {
logoutUri = window.location.origin + config.serverConfig.ssoLogoutUri;
} else {
logoutUri = window.location.origin + config.serverConfig.logoutUri;
}
axios
.post(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 + `/${config.appName}/login`;
}
})
.catch(function(error) {
notification.error({
message: 'There was a problem',
duration: 0,
description: 'Error occurred while trying to logout.',
});
});
} else {
this.setState({
loading: false,
error: true,
});
}
});
};
render() {
return (
<Menu>
<Menu.Item key="1" onClick={this.handleSubmit}>
<LogoutOutlined />
Logout
</Menu.Item>
</Menu>
);
}
}
export default withConfigContext(Logout);

@ -1,278 +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 {
AndroidFilled,
AppstoreOutlined,
ControlOutlined,
PlusOutlined,
SettingOutlined,
UserOutlined,
MenuFoldOutlined,
MenuUnfoldOutlined,
} from '@ant-design/icons';
import { Layout, Menu, Drawer, Button } from 'antd';
import { Switch, Link } from 'react-router-dom';
import RouteWithSubRoutes from '../../components/RouteWithSubRoutes';
import { Redirect } from 'react-router';
import './styles.css';
import { withConfigContext } from '../../components/ConfigContext';
import Logout from './components/Logout';
import { isAuthorized } from '../../services/utils/authorizationHandler';
const { Header, Content, Footer } = Layout;
const { SubMenu } = Menu;
class Dashboard extends React.Component {
constructor(props) {
super(props);
this.state = {
routes: props.routes,
visible: false,
collapsed: false,
};
this.config = this.props.context;
this.Logo = this.config.theme.logo;
this.footerText = this.config.theme.footerText;
}
showMobileNavigationBar = () => {
this.setState({
visible: true,
collapsed: !this.state.collapsed,
});
};
onCloseMobileNavigationBar = () => {
this.setState({
visible: false,
});
};
render() {
return (
<div>
<Layout>
<Header
style={{
paddingLeft: 0,
paddingRight: 0,
backgroundColor: 'white',
}}
>
<div className="logo-image">
<Link to="/publisher/apps">
<img alt="logo" src={this.Logo} />
</Link>
</div>
<div className="web-layout">
<Menu
theme="light"
mode="horizontal"
defaultSelectedKeys={['1']}
style={{ lineHeight: '64px' }}
>
<Menu.Item key="1">
<Link to="/publisher/apps">
<AppstoreOutlined />
Apps
</Link>
</Menu.Item>
{isAuthorized(
this.props.context.user,
'/permission/admin/app-mgt/publisher/application/update',
) && (
<SubMenu
title={
<span className="submenu-title-wrapper">
<PlusOutlined />
Add New App
</span>
}
>
<Menu.Item key="add-new-public-app">
<Link to="/publisher/add-new-app/public">Public App</Link>
</Menu.Item>
<Menu.Item key="add-new-enterprise-app">
<Link to="/publisher/add-new-app/enterprise">
Enterprise App
</Link>
</Menu.Item>
<Menu.Item key="add-new-web-clip">
<Link to="/publisher/add-new-app/web-clip">Web Clip</Link>
</Menu.Item>
<Menu.Item key="add-new-custom-app">
<Link to="/publisher/add-new-app/custom-app">
Custom App
</Link>
</Menu.Item>
</SubMenu>
)}
<SubMenu
title={
<span className="submenu-title-wrapper">
<ControlOutlined />
Manage
</span>
}
>
<Menu.Item key="manage">
<Link to="/publisher/manage">
<SettingOutlined /> General
</Link>
</Menu.Item>
{this.config.androidEnterpriseToken != null && (
<Menu.Item key="manage-android-enterprise">
<Link to="/publisher/manage/android-enterprise">
<AndroidFilled /> Android Enterprise
</Link>
</Menu.Item>
)}
</SubMenu>
<SubMenu
className="profile"
title={
<span className="submenu-title-wrapper">
<UserOutlined />
{this.config.username}
</span>
}
>
<Logout />
</SubMenu>
</Menu>
</div>
</Header>
</Layout>
<Layout className="mobile-layout">
<div className="mobile-menu-button">
<Button type="link" onClick={this.showMobileNavigationBar}>
{this.state.collapsed ? (
<MenuFoldOutlined />
) : (
<MenuUnfoldOutlined />
)}
</Button>
</div>
</Layout>
<Drawer
title={
<Link
to="/publisher/apps"
onClick={this.onCloseMobileNavigationBar}
>
<img
alt="logo"
src={this.Logo}
style={{ marginLeft: 30 }}
width={'60%'}
/>
</Link>
}
placement="left"
closable={false}
onClose={this.onCloseMobileNavigationBar}
visible={this.state.visible}
getContainer={false}
style={{ position: 'absolute' }}
>
<Menu
theme="light"
mode="inline"
defaultSelectedKeys={['1']}
style={{ lineHeight: '64px', width: 231 }}
onClick={this.onCloseMobileNavigationBar}
>
<Menu.Item key="1">
<Link to="/publisher/apps">
<AppstoreOutlined />
Apps
</Link>
</Menu.Item>
<SubMenu
title={
<span className="submenu-title-wrapper">
<PlusOutlined />
Add New App
</span>
}
>
<Menu.Item key="setting:1">
<Link to="/publisher/add-new-app/public">Public APP</Link>
</Menu.Item>
<Menu.Item key="setting:2">
<Link to="/publisher/add-new-app/enterprise">
Enterprise APP
</Link>
</Menu.Item>
<Menu.Item key="setting:3">
<Link to="/publisher/add-new-app/web-clip">Web Clip</Link>
</Menu.Item>
<Menu.Item key="setting:4">
<Link to="/publisher/add-new-app/custom-app">Custom App</Link>
</Menu.Item>
</SubMenu>
<Menu.Item key="2">
<Link to="/publisher/manage">
<ControlOutlined />
Manage
</Link>
</Menu.Item>
</Menu>
</Drawer>
<Layout className="mobile-layout">
<Menu
mode="horizontal"
defaultSelectedKeys={['1']}
style={{ lineHeight: '63px', position: 'fixed', marginLeft: '80%' }}
>
<SubMenu
title={
<span className="submenu-title-wrapper">
<UserOutlined />
</span>
}
>
<Logout />
</SubMenu>
</Menu>
</Layout>
<Layout className="dashboard-body">
<Content style={{ marginTop: 2 }}>
<Switch>
<Redirect exact from="/publisher" to="/publisher/apps" />
{this.state.routes.map(route => (
<RouteWithSubRoutes key={route.path} {...route} />
))}
</Switch>
</Content>
<Footer style={{ textAlign: 'center' }}>{this.footerText}</Footer>
</Layout>
</div>
);
}
}
export default withConfigContext(Dashboard);

@ -1,554 +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 } from '@ant-design/compatible';
import '@ant-design/compatible/assets/index.css';
import { Alert, Button, Col, Input, Row, Select, Spin } from 'antd';
import axios from 'axios';
import { withConfigContext } from '../../../../../../../../components/ConfigContext';
import { handleApiError } from '../../../../../../../../services/utils/errorHandler';
import debounce from 'lodash.debounce';
import Authorized from '../../../../../../../../components/Authorized/Authorized';
const formItemLayout = {
labelCol: {
xs: { span: 24 },
sm: { span: 5 },
},
wrapperCol: {
xs: { span: 24 },
sm: { span: 19 },
},
};
const { Option } = Select;
const { TextArea } = Input;
class NewAppDetailsForm extends React.Component {
constructor(props) {
super(props);
this.state = {
categories: [],
tags: [],
deviceTypes: [],
selectedValue: null,
fetching: false,
roleSearchValue: [],
unrestrictedRoles: [],
};
this.lastFetchId = 0;
this.fetchRoles = debounce(this.fetchRoles, 800);
}
handleSubmit = e => {
e.preventDefault();
const { formConfig } = this.props;
this.props.form.validateFields((err, values) => {
if (!err) {
this.setState({
loading: true,
});
const {
name,
description,
categories,
tags,
unrestrictedRoles,
} = values;
const unrestrictedRolesData = [];
unrestrictedRoles.map(val => {
unrestrictedRolesData.push(val.key);
});
const application = {
name,
description,
categories,
tags,
unrestrictedRoles: unrestrictedRolesData,
};
if (formConfig.installationType !== 'WEB_CLIP') {
application.deviceType = values.deviceType;
} else {
application.type = 'WEB_CLIP';
application.deviceType = 'ALL';
}
this.validateAppName(name, application.deviceType, application);
}
});
};
validateAppName = (name, deviceType, application) => {
const config = this.props.context;
axios
.get(
window.location.origin +
config.serverConfig.invoker.uri +
config.serverConfig.invoker.publisher +
`/applications/device-type/${deviceType}/app-name/${name}`,
)
.then(res => {
if (res.status === 200) {
this.props.onSuccessApplicationData(application);
}
})
.catch(error => {
if (error.hasOwnProperty('response') && error.response.status === 403) {
this.setState({
loading: false,
});
} else if (
error.hasOwnProperty('response') &&
error.response.status === 409
) {
this.props.form.setFields({
name: {
value: name,
errors: [
new Error('App name already exists, Please try another'),
],
},
});
} else {
handleApiError(
error,
'Error occurred while trying to validate app name.',
true,
);
}
});
};
componentDidMount() {
this.getCategories();
this.getTags();
this.getDeviceTypes();
}
getCategories = () => {
const config = this.props.context;
axios
.get(
window.location.origin +
config.serverConfig.invoker.uri +
config.serverConfig.invoker.publisher +
'/applications/categories',
)
.then(res => {
if (res.status === 200) {
let categories = JSON.parse(res.data.data);
this.setState({
categories: categories,
loading: false,
});
}
})
.catch(error => {
handleApiError(
error,
'Error occurred while trying to load categories.',
true,
);
this.setState({
loading: false,
});
});
};
getTags = () => {
const config = this.props.context;
axios
.get(
window.location.origin +
config.serverConfig.invoker.uri +
config.serverConfig.invoker.publisher +
'/applications/tags',
)
.then(res => {
if (res.status === 200) {
let tags = JSON.parse(res.data.data);
this.setState({
tags: tags,
loading: false,
});
}
})
.catch(error => {
handleApiError(
error,
'Error occurred while trying to load tags.',
true,
);
this.setState({
loading: false,
});
});
};
getDeviceTypes = () => {
const config = this.props.context;
const { formConfig } = this.props;
const { installationType } = formConfig;
axios
.get(
window.location.origin +
config.serverConfig.invoker.uri +
config.serverConfig.invoker.deviceMgt +
'/device-types',
)
.then(res => {
if (res.status === 200) {
const allDeviceTypes = JSON.parse(res.data.data);
const mobileDeviceTypes = config.deviceTypes.mobileTypes;
const allowedDeviceTypes = [];
// exclude mobile device types if installation type is custom
if (installationType === 'CUSTOM') {
allDeviceTypes.forEach(deviceType => {
if (!mobileDeviceTypes.includes(deviceType.name)) {
allowedDeviceTypes.push(deviceType);
}
});
} else {
allDeviceTypes.forEach(deviceType => {
if (mobileDeviceTypes.includes(deviceType.name)) {
allowedDeviceTypes.push(deviceType);
}
});
}
this.setState({
deviceTypes: allowedDeviceTypes,
loading: false,
});
}
})
.catch(error => {
handleApiError(
error,
'Error occurred while trying to load device types.',
true,
);
if (error.hasOwnProperty('response') && error.response.status === 403) {
const { forbiddenErrors } = this.state;
forbiddenErrors.deviceTypes = true;
this.setState({
forbiddenErrors,
loading: false,
});
} else {
this.setState({
loading: false,
});
}
});
};
fetchRoles = value => {
const config = this.props.context;
this.lastFetchId += 1;
const fetchId = this.lastFetchId;
this.setState({ data: [], fetching: true });
axios
.get(
window.location.origin +
config.serverConfig.invoker.uri +
config.serverConfig.invoker.deviceMgt +
'/roles?filter=' +
value,
)
.then(res => {
if (res.status === 200) {
if (fetchId !== this.lastFetchId) {
// for fetch callback order
return;
}
const data = res.data.data.roles.map(role => ({
text: role,
value: role,
}));
this.setState({
unrestrictedRoles: data,
fetching: false,
});
}
})
.catch(error => {
handleApiError(
error,
'Error occurred while trying to load roles.',
true,
);
this.setState({
fetching: false,
});
});
};
handleRoleSearch = roleSearchValue => {
this.setState({
roleSearchValue,
unrestrictedRoles: [],
fetching: false,
});
};
// Event handler for selecting the device type
handleSelect = event => {
this.setState({
selectedValue: event,
});
if (this.props.selectedValueHandler) {
this.props.selectedValueHandler(event);
}
};
// Event handler for selecting the windows app type
handleSelectForAppType = event => {
if (this.props.selectedAppTypeHandler) {
this.props.selectedAppTypeHandler(event);
}
};
render() {
const config = this.props.context;
// Windows installation app types
const appTypes = config.windowsDeviceType.appType;
const { formConfig } = this.props;
const {
categories,
tags,
deviceTypes,
selectedValue,
fetching,
unrestrictedRoles,
} = this.state;
const { getFieldDecorator } = this.props.form;
return (
<div>
<Row>
<Col md={5}></Col>
<Col md={14}>
<Form
labelAlign="right"
layout="horizontal"
onSubmit={this.handleSubmit}
>
{formConfig.installationType !== 'WEB_CLIP' && (
<div>
<Authorized
permission="/permission/admin/device-mgt/admin/device-type/view"
no={
<Alert
message="You don't have permission to view device types."
type="warning"
banner
closable
/>
}
/>
<Form.Item {...formItemLayout} label="Device Type">
{getFieldDecorator('deviceType', {
rules: [
{
required: true,
message: 'Please select device type',
},
],
})(
<Select
style={{ width: '100%' }}
placeholder="select device type"
onSelect={this.handleSelect.bind(this)}
>
{deviceTypes.map(deviceType => {
return (
<Option key={deviceType.name}>
{deviceType.name}
</Option>
);
})}
</Select>,
)}
</Form.Item>
</div>
)}
{/* app name*/}
<Authorized
permission="/permission/admin/app-mgt/publisher/application/view"
no={
<Alert
message="You don't have permission to compare application names. If you have update permission,
you still can add application but app name should be unique."
type="warning"
banner
closable
/>
}
/>
<Form.Item {...formItemLayout} label="App Name">
{getFieldDecorator('name', {
rules: [
{
required: true,
message: 'Please input a name',
},
],
})(<Input placeholder="ex: Lorem App" />)}
</Form.Item>
{/* App Type only shown for windows device types for enterprise apps */}
{selectedValue === 'windows' &&
this.props.formConfig.installationType === 'ENTERPRISE' && (
<Form.Item {...formItemLayout} label="App Type">
{getFieldDecorator('appType', {
rules: [
{
required: true,
message: 'Please select app type',
},
],
})(
<Select
style={{ width: '100%' }}
placeholder="select application type"
onSelect={this.handleSelectForAppType}
>
{appTypes.map(appType => {
return <Option key={appType}>{appType}</Option>;
})}
</Select>,
)}
</Form.Item>
)}
{/* description*/}
<Form.Item {...formItemLayout} label="Description">
{getFieldDecorator('description', {
rules: [
{
required: true,
message: 'Please enter a description',
},
],
})(
<TextArea placeholder="Enter the description..." rows={7} />,
)}
</Form.Item>
{/* Unrestricted Roles*/}
<Authorized
permission="/permission/admin/device-mgt/roles/view"
no={
<Alert
message="You don't have permission to view roles."
type="warning"
banner
/>
}
/>
<Form.Item {...formItemLayout} label="Visible Roles">
{getFieldDecorator('unrestrictedRoles', {
rules: [],
initialValue: [],
})(
<Select
mode="multiple"
labelInValue
// value={roleSearchValue}
placeholder="Search roles"
notFoundContent={fetching ? <Spin size="small" /> : null}
filterOption={false}
onSearch={this.fetchRoles}
onChange={this.handleRoleSearch}
style={{ width: '100%' }}
>
{unrestrictedRoles.map(d => (
<Option key={d.value}>{d.text}</Option>
))}
</Select>,
)}
</Form.Item>
<Form.Item {...formItemLayout} label="Categories">
{getFieldDecorator('categories', {
rules: [
{
required: true,
message: 'Please select categories',
},
],
})(
<Select
mode="multiple"
style={{ width: '100%' }}
placeholder="Select a Category"
onChange={this.handleCategoryChange}
>
{categories.map(category => {
return (
<Option key={category.categoryName}>
{category.categoryName}
</Option>
);
})}
</Select>,
)}
</Form.Item>
<Form.Item {...formItemLayout} label="Tags">
{getFieldDecorator('tags', {
rules: [
{
required: true,
message: 'Please select tags',
},
],
})(
<Select
mode="tags"
style={{ width: '100%' }}
placeholder="Tags"
>
{tags.map(tag => {
return <Option key={tag.tagName}>{tag.tagName}</Option>;
})}
</Select>,
)}
</Form.Item>
<Form.Item style={{ float: 'right' }}>
<Button type="primary" htmlType="submit">
Next
</Button>
</Form.Item>
</Form>
</Col>
</Row>
</div>
);
}
}
export default withConfigContext(
Form.create({ name: 'app-details-form' })(NewAppDetailsForm),
);

@ -1,926 +0,0 @@
/*
* 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.
*/
import React from 'react';
import { MinusOutlined, PlusOutlined, UploadOutlined } from '@ant-design/icons';
import { Form } from '@ant-design/compatible';
import '@ant-design/compatible/assets/index.css';
import {
Button,
Col,
Input,
Row,
Select,
Upload,
Typography,
Modal,
Alert,
} from 'antd';
import '@babel/polyfill';
import Authorized from '../../../../../../../../components/Authorized/Authorized';
import { withConfigContext } from '../../../../../../../../components/ConfigContext';
const { Text } = Typography;
const formItemLayout = {
labelCol: {
xs: { span: 24 },
sm: { span: 8 },
},
wrapperCol: {
xs: { span: 24 },
sm: { span: 16 },
},
};
const { Option } = Select;
const { TextArea } = Input;
const InputGroup = Input.Group;
function getBase64(file) {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.readAsDataURL(file);
reader.onload = () => resolve(reader.result);
reader.onerror = error => reject(error);
});
}
// function for access the full name of the binary file using the installation path
function extractBinaryFileName(installationPath) {
let UploadedBinaryName = installationPath.split('/');
return UploadedBinaryName[UploadedBinaryName.length - 1];
}
class NewAppUploadForm extends React.Component {
constructor(props) {
super(props);
this.state = {
icons: [],
screenshots: [],
loading: false,
binaryFiles: [],
application: null,
isFree: true,
previewVisible: false,
previewImage: '',
binaryFileHelperText: '',
iconHelperText: '',
screenshotHelperText: '',
osVersionsHelperText: '',
osVersionsValidateStatus: 'validating',
metaData: [],
appType: null,
};
this.lowerOsVersion = null;
this.upperOsVersion = null;
}
normFile = e => {
if (Array.isArray(e)) {
return e;
}
return e && e.fileList;
};
handleSubmit = e => {
e.preventDefault();
const { formConfig } = this.props;
const { specificElements } = formConfig;
let windowsAppTypeMetaArray = [];
let metaValue = [];
this.props.form.validateFields((err, values) => {
if (!err) {
this.setState({
loading: true,
});
const {
binaryFile,
icon,
screenshots,
releaseDescription,
releaseType,
} = values;
/**
* To save the metaData value that receive from
* metaData UI In an windows app type creation
*/
if (
((this.props.formConfig.installationType === 'ENTERPRISE' &&
this.props.selectedValue === 'windows') ||
this.props.deviceType === 'windows') &&
this.state.metaData.length !== 0
) {
metaValue = [...this.state.metaData];
}
// add release data
const release = {
description: releaseDescription,
price: 0,
isSharedWithAllTenants: false,
metaData: JSON.stringify(this.state.metaData),
releaseType: releaseType,
};
const data = new FormData();
const config = this.props.context;
// Accessing the Meta Key value for windows device type from the config.json file
const metaKeyValues =
config.windowsAppxMsiKeyValueForMetaData.metaKeyArray;
/*
Setting up the app type specific values to the
metaData state field and Setting up the version
and the packageName for windows type
*/
if (
(this.props.formConfig.installationType === 'ENTERPRISE' &&
this.props.selectedValue === 'windows') ||
this.props.deviceType === 'windows'
) {
// Setting up the version and packageName
release.version = values.version;
release.packageName = values.packageName;
// setting the metaData value for appx type
if (
this.props.selectedAppType === 'appx' ||
this.state.appType === 'appx'
) {
windowsAppTypeMetaArray = [
{
key: metaKeyValues[0],
value: values.packageUrl,
},
{
key: metaKeyValues[1],
value: values.dependencyPackageUrl,
},
{
key: metaKeyValues[2],
value: values.certificateHash,
},
{
key: metaKeyValues[3],
value: values.encodedCertContent,
},
{
key: metaKeyValues[4],
value: values.packageName,
},
];
windowsAppTypeMetaArray = [
...windowsAppTypeMetaArray,
...metaValue,
];
} else if (
this.props.selectedAppType === 'msi' ||
this.state.appType === 'msi'
) {
windowsAppTypeMetaArray = [
{
key: metaKeyValues[5],
value: values.productId,
},
{
key: metaKeyValues[6],
value: values.contentUri,
},
{
key: metaKeyValues[7],
value: values.fileHash,
},
];
windowsAppTypeMetaArray = [
...windowsAppTypeMetaArray,
...metaValue,
];
}
this.setState(
{
metaData: windowsAppTypeMetaArray,
},
() => {
release.metaData = JSON.stringify(this.state.metaData);
this.props.onSuccessReleaseData({ data, release });
},
);
}
if (specificElements.hasOwnProperty('version')) {
release.version = values.version;
}
if (specificElements.hasOwnProperty('url')) {
release.url = values.url;
}
if (specificElements.hasOwnProperty('packageName')) {
release.packageName = values.packageName;
}
let isFormValid = true; // flag to check if this form is valid
if (
formConfig.installationType !== 'WEB_CLIP' &&
formConfig.installationType !== 'CUSTOM'
) {
if (this.lowerOsVersion == null || this.upperOsVersion == null) {
isFormValid = false;
this.setState({
osVersionsHelperText: 'Please select supported OS versions',
osVersionsValidateStatus: 'error',
});
} else if (
parseFloat(this.lowerOsVersion) >= parseFloat(this.upperOsVersion)
) {
isFormValid = false;
this.setState({
osVersionsHelperText: 'Please select valid range',
osVersionsValidateStatus: 'error',
});
} else {
release.supportedOsVersions = `${this.lowerOsVersion}-${this.upperOsVersion}`;
}
}
if (
specificElements.hasOwnProperty('binaryFile') &&
this.state.binaryFiles.length !== 1
) {
isFormValid = false;
this.setState({
binaryFileHelperText: 'Please select the application',
});
}
if (this.state.icons.length !== 1) {
isFormValid = false;
this.setState({
iconHelperText: 'Please select an icon',
});
}
if (this.state.screenshots.length !== 3) {
isFormValid = false;
this.setState({
screenshotHelperText: 'Please select 3 screenshots',
});
}
if (this.state.screenshots.length !== 3) {
isFormValid = false;
this.setState({
screenshotHelperText: 'Please select 3 screenshots',
});
}
if (isFormValid) {
data.append('icon', icon[0].originFileObj);
data.append('screenshot1', screenshots[0].originFileObj);
data.append('screenshot2', screenshots[1].originFileObj);
data.append('screenshot3', screenshots[2].originFileObj);
if (specificElements.hasOwnProperty('binaryFile')) {
data.append('binaryFile', binaryFile[0].originFileObj);
}
// Condition to check is it not an Enterprise windows app creation or release
if (
!(
this.props.selectedValue === 'windows' &&
this.props.formConfig.installationType === 'ENTERPRISE'
) &&
this.props.deviceType !== 'windows'
) {
this.props.onSuccessReleaseData({ data, release });
}
}
}
});
};
handleIconChange = ({ fileList }) => {
if (fileList.length === 1) {
this.setState({
iconHelperText: '',
});
}
this.setState({
icons: fileList,
});
};
handleBinaryFileChange = ({ fileList }) => {
let validity = true;
// To set the app type of windows by using the binary file in an new app release
if (this.props.formConfig.isNewRelease && fileList.length !== 0) {
let firstUploadedBinaryFileName = extractBinaryFileName(
this.props.uploadedInstalltionAppType,
);
let FirstFileExtension = firstUploadedBinaryFileName.substr(
firstUploadedBinaryFileName.lastIndexOf('.') + 1,
);
let LastFileExtension = fileList[0].name.substr(
fileList[0].name.lastIndexOf('.') + 1,
);
if (FirstFileExtension !== LastFileExtension) {
validity = false;
} else if (LastFileExtension === 'msi' || LastFileExtension === 'appx') {
this.setState({
appType: LastFileExtension,
});
}
}
if (fileList.length === 1) {
this.setState({
binaryFileHelperText: '',
});
}
if (validity) {
this.setState({
binaryFiles: fileList,
});
} else {
this.setState({
binaryFileHelperText: 'Upload Correct Binary File extension',
});
}
};
handleScreenshotChange = ({ fileList }) => {
if (fileList.length === 3) {
this.setState({
screenshotHelperText: '',
});
}
this.setState({
screenshots: fileList,
});
};
handlePreviewCancel = () => this.setState({ previewVisible: false });
handlePreview = async file => {
if (!file.url && !file.preview) {
file.preview = await getBase64(file.originFileObj);
}
this.setState({
previewImage: file.url || file.preview,
previewVisible: true,
});
};
addNewMetaData = () => {
this.setState({
metaData: this.state.metaData.concat({ key: '', value: '' }),
});
};
handleLowerOsVersionChange = lowerOsVersion => {
this.lowerOsVersion = lowerOsVersion;
this.setState({
osVersionsValidateStatus: 'validating',
osVersionsHelperText: '',
});
};
handleUpperOsVersionChange = upperOsVersion => {
if (upperOsVersion === 'all') {
this.upperOsVersion = this.props.supportedOsVersions[
this.props.supportedOsVersions.length - 1
].versionName;
} else {
this.upperOsVersion = upperOsVersion;
}
this.setState({
osVersionsValidateStatus: 'validating',
osVersionsHelperText: '',
});
};
render() {
const { formConfig, supportedOsVersions } = this.props;
const { getFieldDecorator } = this.props.form;
const config = this.props.context;
const {
icons,
screenshots,
binaryFiles,
previewImage,
previewVisible,
binaryFileHelperText,
iconHelperText,
screenshotHelperText,
metaData,
osVersionsHelperText,
osVersionsValidateStatus,
} = this.state;
const uploadButton = (
<div>
<PlusOutlined />
<div className="ant-upload-text">Select</div>
</div>
);
return (
<div>
<Row>
<Col md={5}></Col>
<Col md={14}>
<Form
labelAlign="right"
layout="horizontal"
onSubmit={this.handleSubmit}
>
{formConfig.specificElements.hasOwnProperty('binaryFile') && (
<Form.Item
{...formItemLayout}
label="Application"
validateStatus="error"
help={binaryFileHelperText}
>
{getFieldDecorator('binaryFile', {
valuePropName: 'binaryFile',
getValueFromEvent: this.normFile,
required: true,
message: 'Please select application',
})(
<Upload
name="binaryFile"
onChange={this.handleBinaryFileChange}
beforeUpload={() => false}
>
{binaryFiles.length !== 1 && (
<Button>
<UploadOutlined /> Click to upload
</Button>
)}
</Upload>,
)}
</Form.Item>
)}
<Form.Item
{...formItemLayout}
label="Icon"
validateStatus="error"
help={iconHelperText}
style={{
marginBottom: 0,
}}
>
{getFieldDecorator('icon', {
valuePropName: 'icon',
getValueFromEvent: this.normFile,
required: true,
message: 'Please select a icon',
})(
<Upload
name="logo"
listType="picture-card"
onChange={this.handleIconChange}
beforeUpload={() => false}
onPreview={this.handlePreview}
>
{icons.length === 1 ? null : uploadButton}
</Upload>,
)}
</Form.Item>
<Row
style={{
marginBottom: 24,
}}
>
<Col xs={24} sm={8}></Col>
<Col xs={24} sm={16}>
<Text type="secondary">Recommended : 240px x 240px</Text>
</Col>
</Row>
<Form.Item
{...formItemLayout}
label="Screenshots"
validateStatus="error"
help={screenshotHelperText}
style={{
marginBottom: 0,
}}
>
{getFieldDecorator('screenshots', {
valuePropName: 'icon',
getValueFromEvent: this.normFile,
required: true,
message: 'Please select a icon',
})(
<Upload
name="screenshots"
listType="picture-card"
onChange={this.handleScreenshotChange}
beforeUpload={() => false}
onPreview={this.handlePreview}
>
{screenshots.length >= 3 ? null : uploadButton}
</Upload>,
)}
</Form.Item>
<Row
style={{
marginBottom: 24,
}}
>
<Col xs={24} sm={8}></Col>
<Col xs={24} sm={16}>
<Text type="secondary">
It is mandatory to upload three screenshots and the
recommended screenshot size - min. 320px max. 3840px.
</Text>
</Col>
</Row>
{/* Package Name field for windows device type and other specific scene using it */}
{(formConfig.specificElements.hasOwnProperty('packageName') ||
(this.props.formConfig.installationType === 'ENTERPRISE' &&
this.props.selectedValue === 'windows') ||
this.props.deviceType === 'windows') && (
<Form.Item {...formItemLayout} label="Package Name">
{getFieldDecorator('packageName', {
rules: [
{
required: true,
message: 'Please input the package name',
},
],
})(<Input placeholder="Package Name" />)}
</Form.Item>
)}
{formConfig.specificElements.hasOwnProperty('url') && (
<Form.Item {...formItemLayout} label="URL">
{getFieldDecorator('url', {
rules: [
{
required: true,
message: 'Please input the url',
},
],
})(<Input placeholder="url" />)}
</Form.Item>
)}
{/* Version field for windows device type and other specific scene using it */}
{(formConfig.specificElements.hasOwnProperty('version') ||
(this.props.formConfig.installationType === 'ENTERPRISE' &&
this.props.selectedValue === 'windows') ||
this.props.deviceType === 'windows') && (
<Form.Item {...formItemLayout} label="Version">
{getFieldDecorator('version', {
rules: [
{
required: true,
message: 'Please input the version',
},
],
})(<Input placeholder="Version" />)}
</Form.Item>
)}
{/* Windows Appx App Type Fields */}
{/* For Windows appx app type only -> Package Url */}
{((this.props.formConfig.installationType === 'ENTERPRISE' &&
this.props.selectedValue === 'windows' &&
this.props.selectedAppType === 'appx') ||
this.state.appType === 'appx') && (
<Form.Item {...formItemLayout} label="Package Url">
{getFieldDecorator('packageUrl', {
rules: [
{
required: true,
message: 'Please input the package url',
},
],
})(<Input placeholder="Package Url" />)}
</Form.Item>
)}
{/* For Windows appx app type only -> Dependency Package Url */}
{((this.props.formConfig.installationType === 'ENTERPRISE' &&
this.props.selectedValue === 'windows' &&
this.props.selectedAppType === 'appx') ||
this.state.appType === 'appx') && (
<Form.Item {...formItemLayout} label="Dependency Package Url">
{getFieldDecorator('dependencyPackageUrl', {})(
<Input placeholder="Dependency Package Url" />,
)}
</Form.Item>
)}
{/* For Windows appx app type only -> Certificate Hash */}
{((this.props.formConfig.installationType === 'ENTERPRISE' &&
this.props.selectedValue === 'windows' &&
this.props.selectedAppType === 'appx') ||
this.state.appType === 'appx') && (
<Form.Item {...formItemLayout} label="Certificate Hash">
{getFieldDecorator('certificateHash', {
rules: [
{
required: true,
message: 'Please input the certificate hash',
},
],
})(<Input placeholder="Certificate Hash" />)}
</Form.Item>
)}
{/* For Windows appx app type only -> Encoded Certificate Content */}
{((this.props.formConfig.installationType === 'ENTERPRISE' &&
this.props.selectedValue === 'windows' &&
this.props.selectedAppType === 'appx') ||
this.state.appType === 'appx') && (
<Form.Item {...formItemLayout} label="Encoded Cert Content">
{getFieldDecorator('encodedCertContent', {
rules: [
{
required: true,
message: 'Give the encoded cert content',
},
],
})(
<TextArea
placeholder="Enter a encoded certificate content"
rows={5}
/>,
)}
</Form.Item>
)}
{/* Windows MSI App Type Fields */}
{/* For Windows msi app type only -> Product Id */}
{((this.props.formConfig.installationType === 'ENTERPRISE' &&
this.props.selectedValue === 'windows' &&
this.props.selectedAppType === 'msi') ||
this.state.appType === 'msi') && (
<Form.Item {...formItemLayout} label="Product Id">
{getFieldDecorator('productId', {
rules: [
{
required: true,
message: 'Please input the product id',
},
],
})(<Input placeholder="Product Id" />)}
</Form.Item>
)}
{/* For Windows msi app type only -> Content URI */}
{((this.props.formConfig.installationType === 'ENTERPRISE' &&
this.props.selectedValue === 'windows' &&
this.props.selectedAppType === 'msi') ||
this.state.appType === 'msi') && (
<Form.Item {...formItemLayout} label="Content URI">
{getFieldDecorator('contentUri', {
rules: [
{
required: true,
message: 'Please input the content uri',
},
],
})(<Input placeholder="Content Uri" />)}
</Form.Item>
)}
{/* For Windows msi app type only -> File Hash */}
{((this.props.formConfig.installationType === 'ENTERPRISE' &&
this.props.selectedValue === 'windows' &&
this.props.selectedAppType === 'msi') ||
this.state.appType === 'msi') && (
<Form.Item {...formItemLayout} label="File Hash">
{getFieldDecorator('fileHash', {
rules: [
{
required: true,
message: 'Please input the file hash',
},
],
})(<Input placeholder="File Hash" />)}
</Form.Item>
)}
<Form.Item {...formItemLayout} label="Release Type">
{getFieldDecorator('releaseType', {
rules: [
{
required: true,
message: 'Please input the Release Type',
},
],
})(<Input placeholder="Release Type" />)}
</Form.Item>
<Form.Item {...formItemLayout} label="Description">
{getFieldDecorator('releaseDescription', {
rules: [
{
required: true,
message: 'Please enter a description for release',
},
],
})(
<TextArea
placeholder="Enter a description for release"
rows={5}
/>,
)}
</Form.Item>
{formConfig.installationType !== 'WEB_CLIP' &&
formConfig.installationType !== 'CUSTOM' && (
<div>
<Authorized
permission="/permission/admin/device-mgt/admin/device-type"
no={
<Alert
message="You don't have permission to view supported OS versions."
type="warning"
banner
/>
}
/>
<Form.Item
{...formItemLayout}
label="Supported OS Versions"
validateStatus={osVersionsValidateStatus}
help={osVersionsHelperText}
>
{getFieldDecorator('supportedOS', {
rules: [
{
required: true,
},
],
initialValue: false,
})(
<div>
<InputGroup>
<Row gutter={8}>
<Col span={11}>
<Select
placeholder="Lower version"
style={{ width: '100%' }}
onChange={this.handleLowerOsVersionChange}
>
{supportedOsVersions.map(version => (
<Option
key={version.versionName}
value={version.versionName}
>
{version.versionName}
</Option>
))}
</Select>
</Col>
<Col span={2}>
<p> - </p>
</Col>
<Col span={11}>
<Select
style={{ width: '100%' }}
placeholder="Upper version"
defaultActiveFirstOption={true}
onChange={this.handleUpperOsVersionChange}
>
{supportedOsVersions.length > 0 && (
<Option key="all" value="all">
All
</Option>
)}
{supportedOsVersions.map(version => (
<Option
key={version.versionName}
value={version.versionName}
>
{version.versionName}
</Option>
))}
</Select>
</Col>
</Row>
</InputGroup>
</div>,
)}
</Form.Item>
</div>
)}
<Form.Item {...formItemLayout} label="Meta Data">
{getFieldDecorator('meta', {})(
<div>
{metaData.map((data, index) => {
/*
Exclude showing the values related to
windows app type variables in meta Data UI
*/
if (
!config.windowsAppxMsiKeyValueForMetaData.metaKeyArray.includes(
data.key,
)
) {
return (
<InputGroup key={index}>
<Row gutter={8}>
<Col span={5}>
<Input
placeholder="key"
value={data.key}
onChange={e => {
metaData[index].key = e.currentTarget.value;
this.setState({
metaData,
});
}}
/>
</Col>
<Col span={8}>
<Input
placeholder="value"
value={data.value}
onChange={e => {
metaData[index].value =
e.currentTarget.value;
this.setState({
metaData,
});
}}
/>
</Col>
<Col span={3}>
<Button
type="dashed"
shape="circle"
icon={<MinusOutlined />}
onClick={() => {
metaData.splice(index, 1);
this.setState({
metaData,
});
}}
/>
</Col>
</Row>
</InputGroup>
);
}
})}
<Button
type="dashed"
icon={<PlusOutlined />}
onClick={this.addNewMetaData}
>
Add
</Button>
</div>,
)}
</Form.Item>
<Form.Item style={{ float: 'right', marginLeft: 8 }}>
<Button type="primary" htmlType="submit">
Submit
</Button>
</Form.Item>
<Form.Item style={{ float: 'right' }}>
<Button
htmlType="button"
onClick={this.props.onClickBackButton}
>
Back
</Button>
</Form.Item>
</Form>
</Col>
</Row>
<Modal
visible={previewVisible}
footer={null}
onCancel={this.handlePreviewCancel}
>
<img
alt="Preview Image"
style={{ width: '100%' }}
src={previewImage}
/>
</Modal>
</div>
);
}
}
export default withConfigContext(
Form.create({ name: 'app-upload-form' })(NewAppUploadForm),
);

@ -1,258 +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 } from '@ant-design/compatible';
import '@ant-design/compatible/assets/index.css';
import { Card, Button, Steps, Row, Col, Result, Spin } from 'antd';
import axios from 'axios';
import { withRouter } from 'react-router-dom';
import NewAppDetailsForm from './components/NewAppDetailsForm';
import NewAppUploadForm from './components/NewAppUploadForm';
import { withConfigContext } from '../../../../../../components/ConfigContext';
import { handleApiError } from '../../../../../../services/utils/errorHandler';
const { Step } = Steps;
class AddNewAppFormComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
current: 0,
categories: [],
tags: [],
icons: [],
screenshots: [],
loading: false,
binaryFiles: [],
application: null,
release: null,
isError: false,
deviceType: null,
selectedValue: null,
selectedAppType: null,
supportedOsVersions: [],
errorText: '',
forbiddenErrors: {
supportedOsVersions: false,
},
};
}
onSuccessApplicationData = application => {
const { formConfig } = this.props;
if (
application.hasOwnProperty('deviceType') &&
formConfig.installationType !== 'WEB_CLIP' &&
formConfig.installationType !== 'CUSTOM'
) {
this.getSupportedOsVersions(application.deviceType);
}
this.setState({
application,
current: 1,
});
};
onSuccessReleaseData = releaseData => {
const config = this.props.context;
this.setState({
loading: true,
isError: false,
});
const { application } = this.state;
const { data, release } = releaseData;
const { formConfig } = this.props;
const { price } = release;
application.subMethod = price === 0 ? 'FREE' : 'PAID';
// add release wrapper
application[formConfig.releaseWrapperName] = [release];
const json = JSON.stringify(application);
const blob = new Blob([json], {
type: 'application/json',
});
data.append(formConfig.jsonPayloadName, blob);
const url =
window.location.origin +
config.serverConfig.invoker.uri +
config.serverConfig.invoker.publisher +
'/applications' +
formConfig.endpoint;
axios
.post(url, data)
.then(res => {
if (res.status === 201) {
this.setState({
loading: false,
current: 2,
});
} else {
this.setState({
loading: false,
isError: true,
current: 2,
});
}
})
.catch(error => {
handleApiError(error, error.response.data.data);
this.setState({
loading: false,
isError: true,
current: 2,
errorText: error.response.data.data,
});
});
};
onClickBackButton = () => {
const current = this.state.current - 1;
this.setState({ current });
};
getSupportedOsVersions = deviceType => {
const config = this.props.context;
axios
.get(
window.location.origin +
config.serverConfig.invoker.uri +
config.serverConfig.invoker.deviceMgt +
`/admin/device-types/${deviceType}/versions`,
)
.then(res => {
if (res.status === 200) {
let supportedOsVersions = JSON.parse(res.data.data);
this.setState({
supportedOsVersions,
loading: false,
});
}
})
.catch(error => {
handleApiError(
error,
'Error occurred while trying to load supported OS versions.',
true,
);
this.setState({
loading: false,
});
});
};
// For passing the device type as the prop for other component
selectedValueHandler = selectedValue => {
this.setState({
selectedValue,
});
};
// For passing the app type as the prop for other component
selectedAppTypeHandler = selectedAppType => {
this.setState({
selectedAppType,
});
};
render() {
const {
loading,
current,
isError,
supportedOsVersions,
errorText,
selectedValue,
selectedAppType,
} = this.state;
const { formConfig } = this.props;
return (
<div>
<Spin tip="Uploading..." spinning={loading}>
<Row>
<Col span={16} offset={4}>
<Steps style={{ minHeight: 32 }} current={current}>
<Step key="Application" title="Application" />
<Step key="Release" title="Release" />
<Step key="Result" title="Result" />
</Steps>
<Card style={{ marginTop: 24 }}>
<div style={{ display: current === 0 ? 'unset' : 'none' }}>
<NewAppDetailsForm
formConfig={formConfig}
selectedValueHandler={this.selectedValueHandler}
selectedAppTypeHandler={this.selectedAppTypeHandler}
onSuccessApplicationData={this.onSuccessApplicationData}
/>
</div>
<div style={{ display: current === 1 ? 'unset' : 'none' }}>
<NewAppUploadForm
formConfig={formConfig}
selectedValue={selectedValue}
selectedAppType={selectedAppType}
supportedOsVersions={supportedOsVersions}
onSuccessReleaseData={this.onSuccessReleaseData}
onClickBackButton={this.onClickBackButton}
/>
</div>
<div style={{ display: current === 2 ? 'unset' : 'none' }}>
{!isError && (
<Result
status="success"
title="Application created successfully!"
extra={[
<Button
type="primary"
key="console"
onClick={() =>
this.props.history.push('/publisher/apps')
}
>
Go to applications
</Button>,
]}
/>
)}
{isError && (
<Result
status="500"
title={errorText}
subTitle="Go back to edit the details and submit again."
extra={
<Button onClick={this.onClickBackButton}>Back</Button>
}
/>
)}
</div>
</Card>
</Col>
</Row>
</Spin>
</div>
);
}
}
const AddNewAppForm = withRouter(
Form.create({ name: 'add-new-app' })(AddNewAppFormComponent),
);
export default withConfigContext(AddNewAppForm);

@ -1,92 +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 { HomeOutlined } from '@ant-design/icons';
import { PageHeader, Typography, Breadcrumb, Result } from 'antd';
import AddNewAppForm from '../../components/AddNewAppForm';
import { Link } from 'react-router-dom';
import Authorized from '../../../../../../components/Authorized/Authorized';
const { Paragraph } = Typography;
const formConfig = {
installationType: 'CUSTOM',
endpoint: '/custom-app',
jsonPayloadName: 'application',
releaseWrapperName: 'customAppReleaseWrappers',
specificElements: {
binaryFile: {
required: true,
},
packageName: {
required: true,
},
version: {
required: true,
},
},
};
class AddNewCustomApp extends React.Component {
constructor(props) {
super(props);
this.state = {
current: 0,
categories: [],
};
}
render() {
return (
<div>
<PageHeader style={{ paddingTop: 0, backgroundColor: '#fff' }}>
<Breadcrumb style={{ paddingBottom: 16 }}>
<Breadcrumb.Item>
<Link to="/publisher/apps">
<HomeOutlined /> Home
</Link>
</Breadcrumb.Item>
<Breadcrumb.Item>Add New Custom App</Breadcrumb.Item>
</Breadcrumb>
<div className="wrap">
<h3>Add New Custom App</h3>
<Paragraph>
Submit and share your own application to the corporate app store.
</Paragraph>
</div>
</PageHeader>
<div style={{ background: '#f0f2f5', padding: 24, minHeight: 720 }}>
<Authorized
permission="/permission/admin/app-mgt/publisher/application/update"
yes={<AddNewAppForm formConfig={formConfig} />}
no={
<Result
status="403"
title="You don't have permission to add new apps."
subTitle="Please contact system administrator"
/>
}
/>
</div>
</div>
);
}
}
export default AddNewCustomApp;

@ -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 { HomeOutlined } from '@ant-design/icons';
import { PageHeader, Typography, Breadcrumb, Result } from 'antd';
import AddNewAppForm from '../../components/AddNewAppForm';
import { Link } from 'react-router-dom';
import Authorized from '../../../../../../components/Authorized/Authorized';
const { Paragraph } = Typography;
const formConfig = {
installationType: 'ENTERPRISE',
endpoint: '/ent-app',
jsonPayloadName: 'application',
releaseWrapperName: 'entAppReleaseWrappers',
specificElements: {
binaryFile: {
required: true,
},
},
};
class AddNewEnterpriseApp extends React.Component {
constructor(props) {
super(props);
this.state = {
current: 0,
categories: [],
};
}
render() {
return (
<div>
<PageHeader style={{ paddingTop: 0, backgroundColor: '#fff' }}>
<Breadcrumb style={{ paddingBottom: 16 }}>
<Breadcrumb.Item>
<Link to="/publisher/apps">
<HomeOutlined /> Home
</Link>
</Breadcrumb.Item>
<Breadcrumb.Item>Add New Enterprise App</Breadcrumb.Item>
</Breadcrumb>
<div className="wrap">
<h3>Add New Enterprise App</h3>
<Paragraph>
Submit and share your own application to the corporate app store.
</Paragraph>
</div>
</PageHeader>
<div style={{ background: '#f0f2f5', padding: 24, minHeight: 720 }}>
<Authorized
permission="/permission/admin/app-mgt/publisher/application/update"
yes={<AddNewAppForm formConfig={formConfig} />}
no={
<Result
status="403"
title="You don't have permission to add new apps."
subTitle="Please contact system administrator"
/>
}
/>
</div>
</div>
);
}
}
export default AddNewEnterpriseApp;

@ -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 { HomeOutlined } from '@ant-design/icons';
import { PageHeader, Typography, Breadcrumb, Result } from 'antd';
import AddNewAppForm from '../../components/AddNewAppForm';
import { Link } from 'react-router-dom';
import Authorized from '../../../../../../components/Authorized/Authorized';
const { Paragraph } = Typography;
const formConfig = {
installationType: 'PUBLIC',
endpoint: '/public-app',
jsonPayloadName: 'public-app',
releaseWrapperName: 'publicAppReleaseWrappers',
specificElements: {
packageName: {
required: true,
},
version: {
required: true,
},
},
};
class AddNewPublicApp extends React.Component {
constructor(props) {
super(props);
this.state = {
current: 0,
categories: [],
};
}
componentDidMount() {
// this.getCategories();
}
render() {
return (
<div>
<PageHeader style={{ paddingTop: 0, backgroundColor: '#fff' }}>
<Breadcrumb style={{ paddingBottom: 16 }}>
<Breadcrumb.Item>
<Link to="/publisher/apps">
<HomeOutlined /> Home
</Link>
</Breadcrumb.Item>
<Breadcrumb.Item>Add New Public App</Breadcrumb.Item>
</Breadcrumb>
<div className="wrap">
<h3>Add New Public App</h3>
<Paragraph>
Share a public application in google play or apple store to your
corporate app store.
</Paragraph>
</div>
</PageHeader>
<div style={{ background: '#f0f2f5', padding: 24, minHeight: 720 }}>
<Authorized
permission="/permission/admin/app-mgt/publisher/application/update"
yes={<AddNewAppForm formConfig={formConfig} />}
no={
<Result
status="403"
title="You don't have permission to add new apps."
subTitle="Please contact system administrator"
/>
}
/>
</div>
</div>
);
}
}
export default AddNewPublicApp;

@ -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 { HomeOutlined } from '@ant-design/icons';
import { PageHeader, Typography, Breadcrumb, Result } from 'antd';
import AddNewAppForm from '../../components/AddNewAppForm';
import { Link } from 'react-router-dom';
import Authorized from '../../../../../../components/Authorized/Authorized';
const { Paragraph } = Typography;
const formConfig = {
installationType: 'WEB_CLIP',
endpoint: '/web-app',
jsonPayloadName: 'webapp',
releaseWrapperName: 'webAppReleaseWrappers',
specificElements: {
url: {
required: true,
},
version: {
required: true,
},
},
};
class AddNewEnterpriseApp extends React.Component {
constructor(props) {
super(props);
this.state = {
current: 0,
categories: [],
};
}
render() {
return (
<div>
<PageHeader style={{ paddingTop: 0, backgroundColor: '#fff' }}>
<Breadcrumb style={{ paddingBottom: 16 }}>
<Breadcrumb.Item>
<Link to="/publisher/apps">
<HomeOutlined /> Home
</Link>
</Breadcrumb.Item>
<Breadcrumb.Item>Add New Web Clip</Breadcrumb.Item>
</Breadcrumb>
<div className="wrap">
<h3>Add New Web Clip</h3>
<Paragraph>Share a Web Clip to your corporate app store.</Paragraph>
</div>
</PageHeader>
<div style={{ background: '#f0f2f5', padding: 24, minHeight: 720 }}>
<Authorized
permission="/permission/admin/app-mgt/publisher/application/update"
yes={<AddNewAppForm formConfig={formConfig} />}
no={
<Result
status="403"
title="You don't have permission to add new apps."
subTitle="Please contact system administrator"
/>
}
/>
</div>
</div>
);
}
}
export default AddNewEnterpriseApp;

@ -1,22 +0,0 @@
/*
* 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.
*/
.stepForm {
max-width: 500px;
margin: 40px auto 0;
}

@ -1,188 +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 } from '@ant-design/compatible';
import '@ant-design/compatible/assets/index.css';
import { notification, Spin, Card, Row, Col } from 'antd';
import axios from 'axios';
import { withRouter } from 'react-router-dom';
import { withConfigContext } from '../../../../../../components/ConfigContext';
import { handleApiError } from '../../../../../../services/utils/errorHandler';
import NewAppUploadForm from '../../../AddNewApp/components/AddNewAppForm/components/NewAppUploadForm';
const formConfig = {
isNewRelease: true,
specificElements: {
binaryFile: {
required: true,
},
},
};
class AddNewReleaseFormComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
loading: false,
supportedOsVersions: [],
application: null,
release: null,
deviceType: null,
forbiddenErrors: {
supportedOsVersions: false,
},
};
}
componentDidMount() {
this.getSupportedOsVersions(this.props.deviceType);
}
getSupportedOsVersions = deviceType => {
const config = this.props.context;
axios
.get(
window.location.origin +
config.serverConfig.invoker.uri +
config.serverConfig.invoker.deviceMgt +
`/admin/device-types/${deviceType}/versions`,
)
.then(res => {
if (res.status === 200) {
let supportedOsVersions = JSON.parse(res.data.data);
this.setState({
supportedOsVersions,
loading: false,
});
}
})
.catch(error => {
handleApiError(
error,
'Error occurred while trying to load supported OS versions.',
true,
);
if (error.hasOwnProperty('response') && error.response.status === 403) {
const { forbiddenErrors } = this.state;
forbiddenErrors.supportedOsVersions = true;
this.setState({
forbiddenErrors,
loading: false,
});
} else {
this.setState({
loading: false,
});
}
});
};
onSuccessReleaseData = releaseData => {
const config = this.props.context;
const { appId, deviceType } = this.props;
this.setState({
loading: true,
});
const { data, release } = releaseData;
const json = JSON.stringify(release);
const blob = new Blob([json], {
type: 'application/json',
});
data.append('applicationRelease', blob);
const url =
window.location.origin +
config.serverConfig.invoker.uri +
config.serverConfig.invoker.publisher +
'/applications/' +
deviceType +
'/ent-app/' +
appId;
axios
.post(url, data)
.then(res => {
if (res.status === 201) {
this.setState({
loading: false,
});
notification.success({
message: 'Done!',
description: 'New release was added successfully',
});
const uuid = res.data.data.uuid;
this.props.history.push({
pathname: '/publisher/apps/releases/' + uuid,
state: { fullAppDetails: this.props.location.state.fullAppDetails },
});
} else {
this.setState({
loading: false,
});
}
})
.catch(error => {
handleApiError(
error,
'Sorry, we were unable to complete your request.',
);
this.setState({
loading: false,
});
});
};
onClickBackButton = () => {
this.props.history.push('/publisher/apps/');
};
render() {
const { loading, supportedOsVersions, forbiddenErrors } = this.state;
return (
<div>
<Spin tip="Uploading..." spinning={loading}>
<Row>
<Col span={17} offset={4}>
<Card>
<NewAppUploadForm
forbiddenErrors={forbiddenErrors}
formConfig={formConfig}
deviceType={this.props.deviceType}
// Takes the first upload app type installation path
uploadedInstalltionAppType={
this.props.location.state.appDetails.installerPath
}
supportedOsVersions={supportedOsVersions}
onSuccessReleaseData={this.onSuccessReleaseData}
onClickBackButton={this.onClickBackButton}
/>
</Card>
</Col>
</Row>
</Spin>
</div>
);
}
}
const AddReleaseForm = withRouter(
Form.create({ name: 'add-new-release' })(AddNewReleaseFormComponent),
);
export default withConfigContext(AddReleaseForm);

@ -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 { HomeOutlined } from '@ant-design/icons';
import { PageHeader, Typography, Breadcrumb } from 'antd';
import AddNewReleaseForm from './components/AddNewReleaseForm';
import { Link } from 'react-router-dom';
const Paragraph = Typography;
class AddNewRelease extends React.Component {
constructor(props) {
super(props);
this.state = {
current: 0,
categories: [],
};
}
render() {
const { appId, deviceType } = this.props.match.params;
return (
<div>
<PageHeader style={{ paddingTop: 0, backgroundColor: '#fff' }}>
<Breadcrumb style={{ paddingBottom: 16 }}>
<Breadcrumb.Item>
<Link to="/publisher/apps">
<HomeOutlined /> Home
</Link>
</Breadcrumb.Item>
<Breadcrumb.Item>Add New Release</Breadcrumb.Item>
</Breadcrumb>
<div className="wrap">
<h3>Add New Release</h3>
<Paragraph>Add new release for the application</Paragraph>
</div>
</PageHeader>
<div style={{ background: '#f0f2f5', padding: 24, minHeight: 720 }}>
<AddNewReleaseForm deviceType={deviceType} appId={appId} />
</div>
</div>
);
}
}
export default AddNewRelease;

@ -1,102 +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 { DeleteOutlined, ExclamationCircleOutlined } from '@ant-design/icons';
import { Button, Modal, notification, Tooltip } from 'antd';
import axios from 'axios';
import { handleApiError } from '../../../../../../../../../../../services/utils/errorHandler';
import { withConfigContext } from '../../../../../../../../../../../components/ConfigContext';
import { withRouter } from 'react-router-dom';
import '../../styles.css';
const { confirm } = Modal;
class DeleteApp extends React.Component {
showModal = () => {
confirm({
title: 'Are you sure you want to delete this app?',
icon: <ExclamationCircleOutlined style={{ color: 'red' }} />,
content:
'You are trying to delete the entire application, by performing this operation all ' +
'app data, app release data, and all release artifacts will be deleted ' +
'permanently. Further, please note, this process cannot be undone.',
okText: 'Yes',
okType: 'danger',
cancelText: 'No',
onOk: this.deleteRelease,
});
};
deleteRelease = () => {
const config = this.props.context;
const apiUrl =
window.location.origin +
config.serverConfig.invoker.uri +
config.serverConfig.invoker.publisher +
'/admin/applications/' +
this.props.id;
axios
.delete(apiUrl)
.then(res => {
if (res.status === 200) {
notification.success({
message: 'Successfully deleted the app',
});
this.props.history.push('/publisher');
}
})
.catch(error => {
handleApiError(
error,
'Something Went wrong when trying to delete the app, Please contact the administrator',
);
this.setState({
loading: false,
});
});
};
render() {
return (
<div className="delete-app">
{this.props.isDeletableApp && (
<Button
type="link"
className="btn-view-more"
onClick={this.showModal}
>
<DeleteOutlined /> Delete
</Button>
)}
{!this.props.isDeletableApp && (
<Tooltip
placement="leftTop"
title="All application releases should be in a deletable state."
>
<Button type="link" disabled={true} className="btn-view-more">
<DeleteOutlined /> Delete
</Button>
</Tooltip>
)}
</div>
);
}
}
export default withConfigContext(withRouter(DeleteApp));

@ -1,276 +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, notification, Popover, Spin, Tooltip } from 'antd';
import axios from 'axios';
import { withConfigContext } from '../../../../../../../../../../../components/ConfigContext';
import { handleApiError } from '../../../../../../../../../../../services/utils/errorHandler';
import { SettingOutlined, QuestionCircleOutlined } from '@ant-design/icons';
import '../../styles.css';
class ManagedConfigurationsIframe extends React.Component {
constructor(props) {
super(props);
this.config = this.props.context;
this.state = {
visible: false,
loading: false,
isHintVisible: false,
};
}
showModal = () => {
this.getMcm();
this.setState({
visible: true,
});
};
handleOk = e => {
this.setState({
visible: false,
});
};
handleCancel = e => {
this.setState({
visible: false,
});
};
getMcm = () => {
const { packageName } = this.props;
this.setState({ loading: true });
// send request to the invoker
axios
.get(
window.location.origin +
this.config.serverConfig.invoker.uri +
'/device-mgt/android/v1.0/enterprise/managed-configs/package/' +
packageName,
)
.then(res => {
if (res.status === 200) {
let mcmId = null;
if (res.data.hasOwnProperty('data')) {
mcmId = res.data.data.mcmId;
}
this.loadIframe(mcmId);
this.setState({ loading: false });
}
})
.catch(error => {
handleApiError(
error,
'Error occurred while trying to load configurations.',
);
this.setState({ loading: false, visible: false });
});
};
loadIframe = mcmId => {
const { packageName } = this.props;
let method = 'post';
// eslint-disable-next-line no-undef
gapi.load('gapi.iframes', () => {
const parameters = {
token: this.config.androidEnterpriseToken,
packageName: packageName,
};
if (mcmId != null) {
parameters.mcmId = mcmId;
parameters.canDelete = true;
method = 'put';
}
const queryString = Object.keys(parameters)
.map(key => key + '=' + parameters[key])
.join('&');
var options = {
url: 'https://play.google.com/managed/mcm?' + queryString,
where: document.getElementById('manage-config-iframe-container'),
attributes: { style: 'height:720px', scrolling: 'yes' },
};
// eslint-disable-next-line no-undef
var iframe = gapi.iframes.getContext().openChild(options);
iframe.register(
'onconfigupdated',
event => {
this.updateConfig(method, event);
},
// eslint-disable-next-line no-undef
gapi.iframes.CROSS_ORIGIN_IFRAMES_FILTER,
);
iframe.register(
'onconfigdeleted',
event => {
this.deleteConfig(event);
},
// eslint-disable-next-line no-undef
gapi.iframes.CROSS_ORIGIN_IFRAMES_FILTER,
);
});
};
updateConfig = (method, event) => {
const { packageName } = this.props;
this.setState({ loading: true });
const data = {
mcmId: event.mcmId,
profileName: event.name,
packageName,
};
// send request to the invoker
axios({
method,
url:
window.location.origin +
this.config.serverConfig.invoker.uri +
'/device-mgt/android/v1.0/enterprise/managed-configs',
data,
})
.then(res => {
if (res.status === 200 || res.status === 201) {
notification.success({
message: 'Saved!',
description: 'Configuration Profile updated Successfully',
});
this.setState({
loading: false,
visible: false,
});
}
})
.catch(error => {
handleApiError(
error,
'Error occurred while trying to update configurations.',
);
this.setState({ loading: false });
});
};
deleteConfig = event => {
this.setState({ loading: true });
// send request to the invoker
axios
.delete(
window.location.origin +
this.config.serverConfig.invoker.uri +
'/device-mgt/android/v1.0/enterprise/managed-configs/mcm/' +
event.mcmId,
)
.then(res => {
if (res.status === 200 || res.status === 201) {
notification.success({
message: 'Saved!',
description: 'Configuration Profile removed Successfully',
});
this.setState({
loading: false,
visible: false,
});
}
})
.catch(error => {
handleApiError(
error,
'Error occurred while trying to remove configurations.',
);
this.setState({ loading: false });
});
};
handleHintVisibleChange = visible => {
this.setState({ isHintVisible: visible });
};
render() {
return (
<div>
{this.props.isEnabled && (
<Button
type="link"
className="btn-view-more"
onClick={this.showModal}
>
<SettingOutlined /> Managed Configurations
</Button>
)}
{!this.props.isEnabled && (
<Tooltip
placement="leftTop"
title="Managed configurations are available only with android enterprise apps."
>
<Button type="link" className="btn-view-more" disabled={true}>
<SettingOutlined /> Managed Configurations
</Button>
</Tooltip>
)}
<Modal
visible={this.state.visible}
onOk={this.handleOk}
onCancel={this.handleCancel}
footer={null}
>
<div>
<Popover
title={null}
trigger="click"
visible={this.state.isHintVisible}
content={
<p>
If you are developing apps for the enterprise market, you may
need to satisfy particular requirements set by a
organization&quot;s policies. Managed configurations,
previously previously known as application restrictions, allow
the organization&quot;s IT admin to remotely specify settings
for apps. This capability is particularly useful for
organization-approved apps deployed to a work profile.
</p>
}
onVisibleChange={this.handleHintVisibleChange}
overlayStyle={{ width: 300 }}
>
<Button
size="large"
type="link"
style={{ marginTop: -56, fontSize: '1.2em' }}
>
<QuestionCircleOutlined />
</Button>
</Popover>
<Spin spinning={this.state.loading}>
<div id="manage-config-iframe-container"></div>
</Spin>
</div>
</Modal>
</div>
);
}
}
export default withConfigContext(ManagedConfigurationsIframe);

@ -1,106 +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 {
EyeInvisibleOutlined,
ExclamationCircleOutlined,
} from '@ant-design/icons';
import { Button, Modal, notification, Tooltip } from 'antd';
import axios from 'axios';
import { handleApiError } from '../../../../../../../../../../../services/utils/errorHandler';
import { withConfigContext } from '../../../../../../../../../../../components/ConfigContext';
import { withRouter } from 'react-router-dom';
import '../../styles.css';
const { confirm } = Modal;
class RetireApp extends React.Component {
showModal = () => {
confirm({
title: 'Are you sure you want to retire this app?',
icon: <ExclamationCircleOutlined style={{ color: 'red' }} />,
content:
'You are trying to retire the entire application, by performing this operation, ' +
'you will not see the app data or app release data on either publisher or store. ' +
'Further, please note, this process cannot be undone.',
okText: 'Yes',
okType: 'danger',
cancelText: 'No',
onOk: this.hideApp,
});
};
hideApp = () => {
const config = this.props.context;
const apiUrl =
window.location.origin +
config.serverConfig.invoker.uri +
config.serverConfig.invoker.publisher +
'/admin/applications/retire/' +
this.props.id;
axios
.put(apiUrl)
.then(res => {
if (res.status === 200) {
notification.success({
message: 'Successfully hided the app',
});
this.props.history.push('/publisher');
}
})
.catch(error => {
console.log(error);
handleApiError(
error,
'Something Went wrong when trying to reitre the app, Please contact the administrator',
);
this.setState({
loading: false,
});
});
};
render() {
return (
<div>
{this.props.isHideableApp && (
<Button
type="link"
onClick={this.showModal}
className="btn-view-more"
>
<EyeInvisibleOutlined /> Retire
</Button>
)}
{!this.props.isHideableApp && (
<Tooltip
placement="leftTop"
title="All releases should be in retired state"
>
<Button type="link" disabled={true} className="btn-view-more">
<EyeInvisibleOutlined /> Retire
</Button>
</Tooltip>
)}
</div>
);
}
}
export default withConfigContext(withRouter(RetireApp));

@ -1,45 +0,0 @@
/*
* 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.
*/
.release-card{
margin-top: 16px;
}
.release-card:hover {
background-color: rgba(15, 188, 249,0.1);
}
@media screen and (max-width: 700px) {
.release-card{
width: 210%;
}
.app-release-cards{
display: grid;
}
}
.app-details-drawer {
text-align: right
}
.btn-view-more {
color: rgba(0, 0, 0, 0.65) !important;
}

@ -1,335 +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 { CheckCircleOutlined } from '@ant-design/icons';
import { Avatar, Table, Tag, Badge, Alert, Tooltip } from 'antd';
import axios from 'axios';
import pSBC from 'shade-blend-color';
import './styles.css';
import { withConfigContext } from '../../../../../../../../components/ConfigContext';
import AppDetailsDrawer from './AppDetailsDrawer';
import { handleApiError } from '../../../../../../../../services/utils/errorHandler';
import { EntgraIcon } from 'entgra-icons-react';
let config = null;
const columns = [
{
title: '',
dataIndex: 'name',
// eslint-disable-next-line react/display-name
render: (name, row) => {
let avatar = null;
if (row.applicationReleases.length === 0) {
const avatarLetter = name.charAt(0).toUpperCase();
avatar = (
<Avatar
shape="square"
size="large"
style={{
marginRight: 20,
borderRadius: '28%',
border: '1px solid #ddd',
backgroundColor: pSBC(0.5, config.theme.primaryColor),
}}
>
{avatarLetter}
</Avatar>
);
} else {
const { applicationReleases } = row;
let hasPublishedRelease = false;
for (let i = 0; i < applicationReleases.length; i++) {
if (applicationReleases[i].currentStatus === 'PUBLISHED') {
hasPublishedRelease = true;
break;
}
}
avatar = hasPublishedRelease ? (
<Badge
title="Published"
style={{
backgroundColor: '#52c41a',
borderRadius: '50%',
color: 'white',
}}
count={
<Tooltip title="Published">
<CheckCircleOutlined
style={{
backgroundColor: '#52c41a',
borderRadius: '50%',
color: 'white',
}}
/>
</Tooltip>
}
>
<Avatar
shape="square"
size="large"
style={{
borderRadius: '28%',
border: '1px solid #ddd',
}}
src={row.applicationReleases[0].iconPath}
/>
</Badge>
) : (
<Avatar
shape="square"
size="large"
style={{
borderRadius: '28%',
border: '1px solid #ddd',
}}
src={row.applicationReleases[0].iconPath}
/>
);
}
return (
<div>
{avatar}
<span style={{ marginLeft: 20 }}>{name}</span>
</div>
);
},
},
{
title: 'Categories',
dataIndex: 'categories',
// eslint-disable-next-line react/display-name
render: categories => (
<span>
{categories.map(category => {
return (
<Tag
style={{ marginBottom: 8 }}
color={pSBC(0.3, config.theme.primaryColor)}
key={category}
>
{category}
</Tag>
);
})}
</span>
),
},
{
title: 'Platform',
dataIndex: 'deviceType',
// eslint-disable-next-line react/display-name
render: platform => {
const defaultPlatformIcons = config.defaultPlatformIcons;
let icon = defaultPlatformIcons.default.icon;
let color = defaultPlatformIcons.default.color;
if (defaultPlatformIcons.hasOwnProperty(platform)) {
icon = defaultPlatformIcons[platform].icon;
color = defaultPlatformIcons[platform].color;
}
return (
<span style={{ fontSize: 20, color: color, textAlign: 'center' }}>
<EntgraIcon type={icon} />
</span>
);
},
},
{
title: 'Type',
dataIndex: 'type',
},
{
title: 'Subscription',
dataIndex: 'subMethod',
},
];
class AppsTable extends React.Component {
constructor(props) {
super(props);
this.state = {
pagination: {},
apps: [],
filters: {},
isDrawerVisible: false,
selectedApp: null,
selectedAppIndex: -1,
loading: false,
isForbiddenErrorVisible: false,
};
config = this.props.context;
}
componentDidMount() {
const { filters } = this.props;
this.setState({
filters,
});
this.fetch(filters);
}
componentDidUpdate(prevProps, prevState, snapshot) {
const { filters } = this.props;
if (prevProps.filters !== this.props.filters) {
this.setState({
filters,
});
this.fetch(filters);
}
}
// handler to show app drawer
showDrawer = (app, appIndex) => {
this.setState({
isDrawerVisible: true,
selectedApp: app,
selectedAppIndex: appIndex,
});
};
// handler to close the app drawer
closeDrawer = () => {
this.setState({
isDrawerVisible: false,
});
};
handleTableChange = (pagination, filters, sorter) => {
const pager = { ...this.state.pagination };
pager.current = pagination.current;
this.setState({
pagination: pager,
});
this.fetch(this.state.filters, {
results: pagination.pageSize,
page: pagination.current,
sortField: sorter.field,
sortOrder: sorter.order,
...filters,
});
};
fetch = (filters, params = {}) => {
this.setState({ loading: true });
const config = this.props.context;
if (!params.hasOwnProperty('page')) {
params.page = 1;
}
const data = {
offset: 10 * (params.page - 1),
limit: 10,
...filters,
};
axios
.post(
window.location.origin +
config.serverConfig.invoker.uri +
config.serverConfig.invoker.publisher +
'/applications',
data,
)
.then(res => {
if (res.status === 200) {
const data = res.data.data;
let apps = [];
if (res.data.data.hasOwnProperty('applications')) {
apps = data.applications;
}
const pagination = { ...this.state.pagination };
// Read total count from server
// pagination.total = data.totalCount;
pagination.total = data.pagination.count;
this.setState({
loading: false,
apps: apps,
pagination,
});
}
})
.catch(error => {
handleApiError(
error,
'Error occurred while trying to load apps.',
true,
);
if (error.hasOwnProperty('response') && error.response.status === 403) {
this.setState({
isForbiddenErrorVisible: true,
});
}
this.setState({ loading: false });
});
};
onUpdateApp = (key, value) => {
const apps = [...this.state.apps];
apps[this.state.selectedAppIndex][key] = value;
this.setState({
apps,
});
};
render() {
const { isDrawerVisible, loading } = this.state;
return (
<div>
{this.state.isForbiddenErrorVisible && (
<Alert
message="You don't have permission to view apps."
type="warning"
banner
closable
/>
)}
<div className="apps-table">
<Table
rowKey={record => record.id}
dataSource={this.state.apps}
columns={columns}
pagination={this.state.pagination}
onChange={this.handleTableChange}
rowClassName="app-row"
loading={loading}
onRow={(record, rowIndex) => {
return {
onClick: event => {
this.showDrawer(record, rowIndex);
},
};
}}
/>
<AppDetailsDrawer
visible={isDrawerVisible}
onClose={this.closeDrawer}
app={this.state.selectedApp}
onUpdateApp={this.onUpdateApp}
/>
</div>
</div>
);
}
}
export default withConfigContext(AppsTable);

@ -1,27 +0,0 @@
/*
* 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.
*/
.app-row{
cursor: pointer;
}
.apps-table{
display: block;
overflow: auto;
width: 100%;
}

@ -1,293 +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 } from '@ant-design/compatible';
import '@ant-design/compatible/assets/index.css';
import { Card, Col, Row, Typography, Divider, Select, Button } from 'antd';
import axios from 'axios';
import { withConfigContext } from '../../../../../../../../components/ConfigContext';
import { handleApiError } from '../../../../../../../../services/utils/errorHandler';
import Authorized from '../../../../../../../../components/Authorized/Authorized';
const { Option } = Select;
const { Title } = Typography;
class FiltersForm extends React.Component {
constructor(props) {
super(props);
this.state = {
categories: [],
tags: [],
deviceTypes: [],
};
}
handleSubmit = e => {
e.preventDefault();
this.props.form.validateFields((err, values) => {
for (const [key, value] of Object.entries(values)) {
if (value === undefined) {
delete values[key];
}
}
if (values.hasOwnProperty('deviceType') && values.deviceType === 'ALL') {
delete values.deviceType;
}
if (values.hasOwnProperty('appType') && values.appType === 'ALL') {
delete values.appType;
}
this.props.setFilters(values);
});
};
componentDidMount() {
this.getCategories();
this.getTags();
this.getDeviceTypes();
}
getCategories = () => {
const config = this.props.context;
axios
.get(
window.location.origin +
config.serverConfig.invoker.uri +
config.serverConfig.invoker.publisher +
'/applications/categories',
)
.then(res => {
if (res.status === 200) {
let categories = JSON.parse(res.data.data);
this.setState({
categories: categories,
loading: false,
});
}
})
.catch(error => {
handleApiError(
error,
'Error occurred while trying to load categories.',
true,
);
this.setState({
loading: false,
});
});
};
getTags = () => {
const config = this.props.context;
axios
.get(
window.location.origin +
config.serverConfig.invoker.uri +
config.serverConfig.invoker.publisher +
'/applications/tags',
)
.then(res => {
if (res.status === 200) {
let tags = JSON.parse(res.data.data);
this.setState({
tags: tags,
loading: false,
});
}
})
.catch(error => {
handleApiError(
error,
'Error occurred while trying to load tags.',
true,
);
this.setState({
loading: false,
});
});
};
getDeviceTypes = () => {
const config = this.props.context;
axios
.get(
window.location.origin +
config.serverConfig.invoker.uri +
config.serverConfig.invoker.deviceMgt +
'/device-types',
)
.then(res => {
if (res.status === 200) {
const deviceTypes = JSON.parse(res.data.data);
this.setState({
deviceTypes,
loading: false,
});
}
})
.catch(error => {
handleApiError(
error,
'Error occurred while trying to load device types.',
true,
);
this.setState({
loading: false,
});
});
};
render() {
const { categories, tags, deviceTypes } = this.state;
const { getFieldDecorator } = this.props.form;
return (
<Card>
<Form
labelAlign="left"
layout="horizontal"
hideRequiredMark
onSubmit={this.handleSubmit}
>
<Row>
<Col span={12}>
<Title level={4}>Filter</Title>
</Col>
<Col span={12}>
<Form.Item
style={{
float: 'right',
marginBottom: 0,
marginTop: -5,
}}
>
<Button size="small" type="primary" htmlType="submit">
Submit
</Button>
</Form.Item>
</Col>
</Row>
<Authorized
permission="/permission/admin/app-mgt/publisher/application/update"
yes={
<div>
<p>Categories:</p>
<Form.Item>
{getFieldDecorator('categories', {
rules: [
{
required: false,
message: 'Please select categories',
},
],
})(
<Select
mode="multiple"
style={{ width: '100%' }}
placeholder="Select a Category"
onChange={this.handleCategoryChange}
>
{categories.map(category => {
return (
<Option key={category.categoryName}>
{category.categoryName}
</Option>
);
})}
</Select>,
)}
</Form.Item>
<p>Tags:</p>
<Form.Item>
{getFieldDecorator('tags', {
rules: [
{
required: false,
message: 'Please select tags',
},
],
})(
<Select
mode="multiple"
style={{ width: '100%' }}
placeholder="Select tags"
>
{tags.map(tag => {
return <Option key={tag.tagName}>{tag.tagName}</Option>;
})}
</Select>,
)}
</Form.Item>
</div>
}
/>
<p>Device Type:</p>
<Authorized
permission="/permission/admin/device-mgt/admin/device-type/view"
yes={
<Form.Item>
{getFieldDecorator('deviceType', {
rules: [
{
required: false,
message: 'Please select device types',
},
],
})(
<Select
style={{ width: '100%' }}
placeholder="Select device types"
>
{deviceTypes.map(deviceType => {
return (
<Option key={deviceType.name}>{deviceType.name}</Option>
);
})}
<Option key="ALL">All</Option>
</Select>,
)}
</Form.Item>
}
/>
<p>App Type:</p>
<Form.Item>
{getFieldDecorator('appType', {})(
<Select style={{ width: '100%' }} placeholder="Select app type">
<Option value="ENTERPRISE">Enterprise</Option>
<Option value="PUBLIC">Public</Option>
<Option value="WEB_CLIP">Web APP</Option>
<Option value="CUSTOM">Custom</Option>
<Option value="ALL">All</Option>
</Select>,
)}
</Form.Item>
<Divider />
</Form>
</Card>
);
}
}
const Filters = withConfigContext(
Form.create({ name: 'filter-apps' })(FiltersForm),
);
export default withConfigContext(Filters);

@ -1,102 +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, Row, Typography, Input, Divider } from 'antd';
import AppsTable from './components/ApssTable';
import Filters from './components/Filters';
const { Title } = Typography;
const Search = Input.Search;
class Apps extends React.Component {
constructor(props) {
super(props);
this.state = {
filters: {},
};
this.appName = '';
}
setFilters = filters => {
if (this.appName === '' && filters.hasOwnProperty('appName')) {
delete filters.appName;
} else {
filters.appName = this.appName;
}
this.setState({
filters,
});
};
setSearchText = appName => {
const filters = { ...this.state.filters };
this.appName = appName;
if (appName === '' && filters.hasOwnProperty('appName')) {
delete filters.appName;
} else {
filters.appName = appName;
}
this.setState({
filters,
});
};
onChangeSearchText = e => {
const filters = { ...this.state.filters };
const appName = e.target.value;
if (appName === '' && filters.hasOwnProperty('appName')) {
delete filters.appName;
this.setState({
filters,
});
}
};
render() {
const { filters } = this.state;
return (
<Card>
<Row gutter={28}>
<Col md={6}>
<Filters setFilters={this.setFilters} />
</Col>
<Col md={18}>
<Row>
<Col span={6}>
<Title level={4}>Apps</Title>
</Col>
<Col span={18} style={{ textAlign: 'right' }}>
<Search
placeholder="Search by app name"
onSearch={this.setSearchText}
onChange={this.onChangeSearchText}
style={{ width: 240, zIndex: 0 }}
/>
</Col>
</Row>
<Divider dashed={true} />
<AppsTable filters={filters} />
</Col>
</Row>
</Card>
);
}
}
export default Apps;

@ -1,177 +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 { TeamOutlined } from '@ant-design/icons';
import { Row, Typography } from 'antd';
import StarRatings from 'react-star-ratings';
import './styles.css';
import axios from 'axios';
import { withConfigContext } from '../../../../../../components/ConfigContext';
import { handleApiError } from '../../../../../../services/utils/errorHandler';
const { Text } = Typography;
class DetailedRating extends React.Component {
constructor(props) {
super(props);
this.state = {
detailedRating: null,
};
}
componentDidMount() {
const { type, uuid } = this.props;
this.getData(type, uuid);
}
componentDidUpdate(prevProps, prevState) {
if (prevProps.uuid !== this.props.uuid) {
const { type, uuid } = this.props;
this.getData(type, uuid);
}
}
getData = (type, uuid) => {
const config = this.props.context;
return axios
.get(
window.location.origin +
config.serverConfig.invoker.uri +
config.serverConfig.invoker.publisher +
'/admin/reviews/' +
uuid +
'/' +
type +
'-rating',
)
.then(res => {
if (res.status === 200) {
let detailedRating = res.data.data;
this.setState({
detailedRating,
});
}
})
.catch(function(error) {
handleApiError(
error,
'Error occurred while trying to load rating for the release.',
true,
);
});
};
render() {
const detailedRating = this.state.detailedRating;
if (detailedRating == null) {
return null;
}
const totalCount = detailedRating.noOfUsers;
const ratingVariety = detailedRating.ratingVariety;
const ratingArray = [];
// eslint-disable-next-line no-unused-vars
for (let [key, value] of Object.entries(ratingVariety)) {
ratingArray.push(value);
}
const maximumRating = Math.max(...ratingArray);
const ratingBarPercentages = [0, 0, 0, 0, 0];
if (maximumRating > 0) {
for (let i = 0; i < 5; i++) {
ratingBarPercentages[i] =
(ratingVariety[(i + 1).toString()] / maximumRating) * 100;
}
}
return (
<Row className="d-rating">
<div className="numeric-data">
<div className="rate">{detailedRating.ratingValue.toFixed(1)}</div>
<StarRatings
rating={detailedRating.ratingValue}
starRatedColor="#777"
starDimension="16px"
starSpacing="2px"
numberOfStars={5}
name="rating"
/>
<br />
<Text type="secondary" className="people-count">
<TeamOutlined /> {totalCount} total
</Text>
</div>
<div className="bar-containers">
<div className="bar-container">
<span className="number">5</span>
<span
className="bar rate-5"
style={{ width: ratingBarPercentages[4] + '%' }}
>
{' '}
</span>
</div>
<div className="bar-container">
<span className="number">4</span>
<span
className="bar rate-4"
style={{ width: ratingBarPercentages[3] + '%' }}
>
{' '}
</span>
</div>
<div className="bar-container">
<span className="number">3</span>
<span
className="bar rate-3"
style={{ width: ratingBarPercentages[2] + '%' }}
>
{' '}
</span>
</div>
<div className="bar-container">
<span className="number">2</span>
<span
className="bar rate-2"
style={{ width: ratingBarPercentages[1] + '%' }}
>
{' '}
</span>
</div>
<div className="bar-container">
<span className="number">1</span>
<span
className="bar rate-1"
style={{ width: ratingBarPercentages[0] + '%' }}
>
{' '}
</span>
</div>
</div>
</Row>
);
}
}
export default withConfigContext(DetailedRating);

@ -1,102 +0,0 @@
/*
* 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.
*/
.d-rating .numeric-data{
box-sizing: border-box;
display: inline-block;
padding: 20px 0 20px 0;
vertical-align: top;
text-align: center;
width: 30%;
}
.d-rating .bar-containers{
box-sizing: border-box;
display: inline-block;
padding: 20px 20px 20px 30px;
vertical-align: top;
width: 70%;
}
@media screen and (max-width: 768px) {
.d-rating .bar-containers{
width: 100%;
}
.d-rating .numeric-data{
width: 50%;
}
}
.d-rating .bar-containers .bar-container{
color: #737373;
font-weight: 400;
height: 20px;
margin-bottom: 4px;
position: relative;
width: 100%;
}
.d-rating .bar-containers .bar-container .number{
font-size: 11px;
left: -16px;
letter-spacing: 1px;
position: absolute;
}
.d-rating .bar-containers .bar-container .bar{
transition: width .25s ease;
display: inline-block;
height: 100%;
opacity: .8;
border-radius: 5px;
}
.bar-container .rate-5{
background: #57bb8a;
}
.bar-container .rate-4{
background: #9ace6a;
}
.bar-container .rate-3{
background: #ffcf02;
}
.bar-container .rate-2{
background: #ff9f02;
}
.bar-container .rate-1{
background: #ff6f31;
}
.d-rating .numeric-data .rate{
color: #333;
font-size: 64px;
font-weight: 100;
line-height: 64px;
padding-bottom: 6px;
}
.d-rating .numeric-data .people-count{
padding-top: 6px;
}

@ -1,52 +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 AppList from './components/AppList';
import Authorized from '../../../../components/Authorized/Authorized';
import { Result } from 'antd';
class Apps extends React.Component {
routes;
constructor(props) {
super(props);
this.routes = props.routes;
}
render() {
return (
<div>
<div style={{ background: '#f0f2f5', padding: 24, minHeight: 780 }}>
<Authorized
permission="/permission/admin/app-mgt/publisher/application/view"
yes={<AppList />}
no={
<Result
status="403"
title="You don't have permission to view apps."
subTitle="Please contact system administrator"
/>
}
/>
</div>
</div>
);
}
}
export default Apps;

@ -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 { Tag, Timeline, Card } from 'antd';
import { withConfigContext } from '../../../../../../../../../../components/ConfigContext';
class LifeCycleHistory extends React.Component {
constructor(props) {
super(props);
}
render() {
const { lifeCycleStates } = this.props;
return (
<div className="scroll" style={{ height: 500, overflowY: 'auto' }}>
<Timeline mode={'alternate'} style={{ marginTop: 10 }}>
{lifeCycleStates.map(
(state, index) =>
state && (
<Timeline.Item key={index} label={state.updatedAt}>
<Card>
<div style={{ textAlign: 'center' }}>
{state.previousState === state.currentState ? (
'Application Created'
) : (
<div>
State changed from <br />
<div style={{ marginTop: 5 }}>
<Tag color="blue">{state.previousState}</Tag> to{' '}
<Tag color="blue">{state.currentState}</Tag>
</div>
</div>
)}
<Tag style={{ marginTop: 5 }}>{state.updatedAt}</Tag>
</div>
</Card>
</Timeline.Item>
),
)}
</Timeline>
</div>
);
}
}
export default withConfigContext(LifeCycleHistory);

@ -1,113 +0,0 @@
/*
* 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.
*/
import React from 'react';
import { QuestionCircleOutlined } from '@ant-design/icons';
import { Modal, Button, Tag, List, Typography } from 'antd';
import pSBC from 'shade-blend-color';
import { withConfigContext } from '../../../../../../../../../../components/ConfigContext';
const { Text } = Typography;
class LifeCycleDetailsModal extends React.Component {
constructor(props) {
super(props);
this.state = { visible: false };
}
showModal = () => {
this.setState({
visible: true,
});
};
handleCancel = e => {
this.setState({
visible: false,
});
};
render() {
const config = this.props.context;
const lifeCycleConfig = config.lifecycle;
const { lifecycle } = this.props;
return (
<div>
<Button
size="small"
icon={<QuestionCircleOutlined />}
onClick={this.showModal}
>
Learn more
</Button>
<Modal
title="Lifecycle"
visible={this.state.visible}
footer={null}
onCancel={this.handleCancel}
>
<List
itemLayout="horizontal"
dataSource={Object.keys(lifecycle)}
renderItem={lifecycleState => {
let text = '';
let footerText = '';
let nextProceedingStates = [];
if (lifeCycleConfig.hasOwnProperty(lifecycleState)) {
text = lifeCycleConfig[lifecycleState].text;
}
if (
lifecycle[lifecycleState].hasOwnProperty('proceedingStates')
) {
nextProceedingStates =
lifecycle[lifecycleState].proceedingStates;
footerText =
'You can only proceed to one of the following states:';
}
return (
<List.Item>
<List.Item.Meta title={lifecycleState} />
{text}
<br />
<Text type="secondary">{footerText}</Text>
<div>
{nextProceedingStates.map(lifecycleState => {
return (
<Tag
key={lifecycleState}
style={{ margin: 5 }}
color={pSBC(0.3, config.theme.primaryColor)}
>
{lifecycleState}
</Tag>
);
})}
</div>
</List.Item>
);
}}
/>
</Modal>
</div>
);
}
}
export default withConfigContext(LifeCycleDetailsModal);

@ -1,354 +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 {
Typography,
Tag,
Divider,
Button,
Modal,
notification,
Steps,
Alert,
Tabs,
Tooltip,
} from 'antd';
import axios from 'axios';
import ReactQuill from 'react-quill';
import 'react-quill/dist/quill.snow.css';
import './styles.css';
import { withConfigContext } from '../../../../../../../../components/ConfigContext';
import { handleApiError } from '../../../../../../../../services/utils/errorHandler';
import LifeCycleHistory from './components/LifeCycleHistory';
import { EntgraIcon } from 'entgra-icons-react';
const { Text, Title, Paragraph } = Typography;
const { TabPane } = Tabs;
const modules = {
toolbar: [
[{ header: [1, 2, false] }],
['bold', 'italic', 'underline', 'strike', 'blockquote', 'code-block'],
[{ list: 'ordered' }, { list: 'bullet' }],
['link', 'image'],
],
};
const formats = [
'header',
'bold',
'italic',
'underline',
'strike',
'blockquote',
'code-block',
'list',
'bullet',
'link',
'image',
];
const { Step } = Steps;
class LifeCycle extends React.Component {
constructor(props) {
super(props);
this.state = {
currentStatus: props.currentStatus,
selectedStatus: null,
reasonText: '',
isReasonModalVisible: false,
isConfirmButtonLoading: false,
current: 0,
lifecycleSteps: [],
lifeCycleStates: [],
isPublished: false,
};
}
componentDidMount() {
const config = this.props.context;
const lifeCycleConfig = config.lifecycle;
const lifecycleSteps = Object.keys(lifeCycleConfig).map(config => {
return lifeCycleConfig[config];
});
let isPublished = this.checkReleaseLifeCycleStatus();
this.setState({
current: lifeCycleConfig[this.props.currentStatus].step,
lifecycleSteps,
isPublished,
});
this.getLifeCycleHistory();
}
componentDidUpdate(prevProps, prevState, snapshot) {
if (
prevProps.currentStatus !== this.props.currentStatus ||
prevProps.uuid !== this.props.uuid
) {
this.setState({
currentStatus: this.props.currentStatus,
});
}
}
handleChange = value => {
this.setState({ reasonText: value });
};
showReasonModal = lifecycleState => {
this.setState({
selectedStatus: lifecycleState,
isReasonModalVisible: true,
});
};
closeReasonModal = () => {
this.setState({
isReasonModalVisible: false,
});
};
addLifeCycle = () => {
const config = this.props.context;
const lifeCycleConfig = config.lifecycle;
const { selectedStatus, reasonText } = this.state;
const { uuid } = this.props;
const data = {
action: selectedStatus,
reason: reasonText,
};
this.setState({
isConfirmButtonLoading: true,
});
axios
.post(
window.location.origin +
config.serverConfig.invoker.uri +
config.serverConfig.invoker.publisher +
'/applications/life-cycle/' +
uuid,
data,
)
.then(res => {
if (res.status === 201) {
this.setState({
current: lifeCycleConfig[selectedStatus].step,
isReasonModalVisible: false,
isConfirmButtonLoading: false,
currentStatus: selectedStatus,
selectedStatus: null,
reasonText: '',
});
this.props.changeCurrentLifecycleStatus(selectedStatus);
notification.success({
message: 'Done!',
description: 'Lifecycle state updated successfully!',
});
this.getLifeCycleHistory();
}
})
.catch(error => {
handleApiError(error, 'Error occurred while trying to add lifecycle');
this.setState({
isConfirmButtonLoading: false,
});
});
};
getLifeCycleHistory = () => {
const config = this.props.context;
const { uuid } = this.props;
axios
.get(
window.location.origin +
config.serverConfig.invoker.uri +
config.serverConfig.invoker.publisher +
'/applications/life-cycle/state-changes/' +
uuid,
)
.then(res => {
if (res.status === 200) {
this.setState({ lifeCycleStates: JSON.parse(res.data.data) });
}
})
.catch(error => {
handleApiError(
error,
'Error occurred while trying to get lifecycle history',
);
});
};
onChange = current => {
this.setState({ current });
};
/*
Function to check if the same app releases are in published
state or not and assigned a boolean value to disable
the publish button if an app release is already published
*/
checkReleaseLifeCycleStatus = () => {
if (typeof this.props.appReleases !== 'undefined') {
let appReleases = this.props.appReleases.fullAppDetails;
for (let i = 0; i < appReleases.length; i++) {
if (
this.props.uuid !== appReleases[i].uuid &&
appReleases[i].currentStatus === 'PUBLISHED'
) {
return true;
}
}
return false;
}
return false;
};
render() {
const {
currentStatus,
selectedStatus,
current,
lifecycleSteps,
lifeCycleStates,
} = this.state;
const { lifecycle } = this.props;
const text = <span>Already an app is in publish state</span>;
let proceedingStates = [];
if (
lifecycle !== null &&
lifecycle.hasOwnProperty(currentStatus) &&
lifecycle[currentStatus].hasOwnProperty('proceedingStates')
) {
proceedingStates = lifecycle[currentStatus].proceedingStates;
}
return (
<div>
<Title level={4}>Manage Lifecycle</Title>
<Divider />
<Paragraph>
Ensure that your security policies are not violated by the
application. Have a thorough review and approval process before
directly publishing it to your app store. You can easily transition
from one state to another. <br />
</Paragraph>
<Tabs defaultActiveKey="1" type="card">
<TabPane tab="Change Lifecycle" key="1">
<div>
<Steps
direction={'vertical'}
current={current}
onChange={this.onChange}
size="small"
>
{lifecycleSteps.map((step, index) => {
return (
<Step
key={index}
icon={<EntgraIcon type={step.icon} />}
title={step.title}
disabled={current !== step.step}
description={
current === step.step && (
<div style={{ width: 400 }}>
<p>{step.text}</p>
{proceedingStates.map(lifecycleState => {
return (
<Tooltip
key={lifecycleState}
title={
lifecycleState === 'PUBLISHED' &&
this.state.isPublished
? text
: ''
}
>
<Button
size={'small'}
style={{ marginRight: 3 }}
disabled={
lifecycleState === 'PUBLISHED' &&
this.state.isPublished
}
onClick={() =>
this.showReasonModal(lifecycleState)
}
key={lifecycleState}
type={'primary'}
>
{lifecycleState}
</Button>
</Tooltip>
);
})}
</div>
)
}
/>
);
})}
</Steps>
</div>
</TabPane>
<TabPane tab="Lifecycle History" key="2">
<LifeCycleHistory lifeCycleStates={lifeCycleStates} />
</TabPane>
</Tabs>
<Divider />
<Modal
title="Confirm changing lifecycle state"
visible={this.state.isReasonModalVisible}
onOk={this.addLifeCycle}
onCancel={this.closeReasonModal}
okText="Confirm"
>
<Text>
You are going to change the lifecycle state from,
<br />
<Tag color="blue">{currentStatus}</Tag>to{' '}
<Tag color="blue">{selectedStatus}</Tag>
</Text>
<br />
{lifecycle && selectedStatus && lifecycle[selectedStatus].isEndState && (
<Alert
message="In this state application becomes completely obsolete. Be careful,
this process cannot be undone."
banner
style={{ marginTop: 5 }}
/>
)}
<Divider orientation="left">Reason</Divider>
<ReactQuill
theme="snow"
value={this.state.reasonText}
onChange={this.handleChange}
modules={modules}
formats={formats}
placeholder="Leave a comment (optional)"
/>
</Modal>
</div>
);
}
}
export default withConfigContext(LifeCycle);

@ -1,35 +0,0 @@
/*
* 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.
*/
.ql-editor{
min-height: 100px !important;
max-height: 400px;
overflow: hidden;
overflow-y: scroll;
overflow-x: scroll;
}
.ql-toolbar.ql-snow{
border-radius: 10px 10px 0 0;
}
.ql-toolbar.ql-snow + .ql-container.ql-snow{
border-radius: 0 0 10px 10px;
}
.ql-editor.ql-blank::before {
font-style: unset;
}

@ -1,88 +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 { DeleteOutlined } from '@ant-design/icons';
import { Modal, Button, notification } from 'antd';
import axios from 'axios';
import { handleApiError } from '../../../../../../../../../../services/utils/errorHandler';
import { withConfigContext } from '../../../../../../../../../../components/ConfigContext';
import { withRouter } from 'react-router-dom';
const { confirm } = Modal;
class DeleteRelease extends React.Component {
showModal = () => {
confirm({
title: 'Are you sure you want to delete the application release?',
content:
'If you have multiple application releases, only the selected app release will be ' +
'deleted. Otherwise, the whole application will be deleted. Further note, this will ' +
'delete application artifacts permanently.\n' +
'Be careful, this process cannot be undone.',
okText: 'Yes',
okType: 'danger',
cancelText: 'No',
onOk: this.deleteRelease,
});
};
deleteRelease = () => {
const config = this.props.context;
const apiUrl =
window.location.origin +
config.serverConfig.invoker.uri +
config.serverConfig.invoker.publisher +
'/admin/applications/release/' +
this.props.uuid;
axios
.delete(apiUrl)
.then(res => {
if (res.status === 200) {
notification.success({
message: 'Successfully deleted the release',
});
this.props.history.push('/publisher');
}
})
.catch(error => {
handleApiError(
'Something Went wrong when trying to delete the release, Please contact the administrator',
);
this.setState({
loading: false,
});
});
};
render() {
return (
<>
<Button
disabled={!this.props.isDeletableState}
size="small"
type="danger"
onClick={this.showModal}
>
<DeleteOutlined /> Delete
</Button>
</>
);
}
}
export default withConfigContext(withRouter(DeleteRelease));

@ -1,815 +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 {
EditOutlined,
MinusOutlined,
PlusOutlined,
UploadOutlined,
} from '@ant-design/icons';
import { Form } from '@ant-design/compatible';
import '@ant-design/compatible/assets/index.css';
import {
Modal,
Button,
notification,
Spin,
Tooltip,
Upload,
Input,
Divider,
Row,
Col,
Select,
Alert,
} from 'antd';
import axios from 'axios';
import '@babel/polyfill';
import { withConfigContext } from '../../../../../../../../../../components/ConfigContext';
import Authorized from '../../../../../../../../../../components/Authorized/Authorized';
const { TextArea } = Input;
const InputGroup = Input.Group;
const { Option } = Select;
const formItemLayout = {
labelCol: {
span: 8,
},
wrapperCol: {
span: 16,
},
};
function getBase64(file) {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.readAsDataURL(file);
reader.onload = () => resolve(reader.result);
reader.onerror = error => reject(error);
});
}
// function for access the name of the binary file using the installation path
function extractBinaryFileName(installationPath) {
let UploadedBinaryName = installationPath.split('/');
let binaryFileName = UploadedBinaryName[UploadedBinaryName.length - 1];
return binaryFileName.substr(binaryFileName.lastIndexOf('.') + 1);
}
class EditReleaseModal extends React.Component {
// To add subscription type & tenancy sharing, refer https://gitlab.com/entgra/carbon-device-mgt/merge_requests/331
constructor(props) {
super(props);
this.state = {
visible: false,
current: 0,
categories: [],
tags: [],
icons: [],
screenshots: [],
loading: false,
binaryFiles: [],
metaData: [],
formConfig: {
specificElements: {},
},
};
this.lowerOsVersion = null;
this.upperOsVersion = null;
}
componentDidMount = () => {
this.generateConfig();
};
generateConfig = () => {
const { type } = this.props;
const formConfig = {
type,
};
switch (type) {
case 'ENTERPRISE':
formConfig.endpoint = '/ent-app-release';
formConfig.specificElements = {
binaryFile: {
required: true,
},
};
break;
case 'PUBLIC':
formConfig.endpoint = '/public-app-release';
formConfig.specificElements = {
packageName: {
required: true,
},
version: {
required: true,
},
};
break;
case 'WEB_CLIP':
formConfig.endpoint = '/web-app-release';
formConfig.specificElements = {
version: {
required: true,
},
url: {
required: true,
},
};
break;
case 'CUSTOM':
formConfig.endpoint = '/custom-app-release';
formConfig.specificElements = {
binaryFile: {
required: true,
},
packageName: {
required: true,
},
version: {
required: true,
},
};
break;
}
this.setState({
formConfig,
});
};
showModal = () => {
const config = this.props.context;
const { release } = this.props;
const { formConfig } = this.state;
const { specificElements } = formConfig;
let metaData = [];
try {
metaData = JSON.parse(release.metaData);
} catch (e) {
console.log(e);
}
this.props.form.setFields({
releaseType: {
value: release.releaseType,
},
releaseDescription: {
value: release.description,
},
});
if (config.deviceTypes.mobileTypes.includes(this.props.deviceType)) {
const osVersions = release.supportedOsVersions.split('-');
this.lowerOsVersion = osVersions[0];
this.upperOsVersion = osVersions[1];
this.props.form.setFields({
lowerOsVersion: {
value: osVersions[0],
},
upperOsVersion: {
value: osVersions[1],
},
});
}
// Showing the packageName value in the edit form UI
if (
formConfig.specificElements.hasOwnProperty('packageName') ||
(this.props.type === 'ENTERPRISE' && this.props.deviceType === 'windows')
) {
this.props.form.setFields({
packageName: {
value: release.packageName,
},
});
}
// Showing the version value in the edit form UI
if (
formConfig.specificElements.hasOwnProperty('version') ||
(this.props.type === 'ENTERPRISE' && this.props.deviceType === 'windows')
) {
this.props.form.setFields({
version: {
value: release.version,
},
});
}
if (specificElements.hasOwnProperty('url')) {
this.props.form.setFields({
url: {
value: release.url,
},
});
}
this.setState({
visible: true,
metaData,
});
};
handleOk = e => {
this.setState({
visible: false,
});
};
handleCancel = e => {
this.setState({
visible: false,
});
};
normFile = e => {
if (Array.isArray(e)) {
return e;
}
return e && e.fileList;
};
handleIconChange = ({ fileList }) => this.setState({ icons: fileList });
handleBinaryFileChange = ({ fileList }) =>
this.setState({ binaryFiles: fileList });
handleScreenshotChange = ({ fileList }) =>
this.setState({ screenshots: fileList });
handleSubmit = e => {
e.preventDefault();
const { uuid } = this.props.release;
const config = this.props.context;
const { formConfig } = this.state;
const { specificElements } = formConfig;
// Accessing the extension type of the current uploaded binary file
const appTypeExtension = extractBinaryFileName(
this.props.release.installerPath,
);
this.props.form.validateFields((err, values) => {
if (!err) {
this.setState({
loading: true,
});
const { releaseDescription, releaseType } = values;
const { icons, screenshots, binaryFiles } = this.state;
const data = new FormData();
// add release data
const release = {
description: releaseDescription,
price: 0,
isSharedWithAllTenants: false,
metaData: JSON.stringify(this.state.metaData),
releaseType: releaseType,
};
if (config.deviceTypes.mobileTypes.includes(this.props.deviceType)) {
release.supportedOsVersions = `${this.lowerOsVersion}-${this.upperOsVersion}`;
}
if (
specificElements.hasOwnProperty('binaryFile') &&
binaryFiles.length === 1
) {
data.append('binaryFile', binaryFiles[0].originFileObj);
}
if (
specificElements.hasOwnProperty('version') ||
(this.props.type === 'ENTERPRISE' &&
this.props.deviceType === 'windows')
) {
release.version = values.version;
}
// Accessing the Meta Key value for windows device type from the config.json file
const metaKeyValues =
config.windowsAppxMsiKeyValueForMetaData.metaKeyArray;
if (
specificElements.hasOwnProperty('packageName') ||
(this.props.type === 'ENTERPRISE' &&
this.props.deviceType === 'windows')
) {
release.packageName = values.packageName;
// Setting up the packageName to the package_Family_Name key in an appx app type instance
if (appTypeExtension === config.windowsDeviceType.appType[1]) {
let metaDataArray = this.state.metaData;
let filterMetaArray = metaDataArray.filter(
obj => obj.key !== metaKeyValues[4],
);
filterMetaArray.push({
key: metaKeyValues[4],
value: values.packageName,
});
release.metaData = JSON.stringify(filterMetaArray);
}
}
if (specificElements.hasOwnProperty('url')) {
release.url = values.url;
}
if (icons.length === 1) {
data.append('icon', icons[0].originFileObj);
}
if (screenshots.length > 0) {
data.append('screenshot1', screenshots[0].originFileObj);
}
if (screenshots.length > 1) {
data.append('screenshot2', screenshots[1].originFileObj);
}
if (screenshots.length > 2) {
data.append('screenshot3', screenshots[2].originFileObj);
}
const json = JSON.stringify(release);
const blob = new Blob([json], {
type: 'application/json',
});
data.append('applicationRelease', blob);
const url =
window.location.origin +
config.serverConfig.invoker.uri +
config.serverConfig.invoker.publisher +
'/applications' +
formConfig.endpoint +
'/' +
uuid;
axios
.put(url, data)
.then(res => {
if (res.status === 200) {
const updatedRelease = res.data.data;
this.setState({
loading: false,
visible: false,
});
notification.success({
message: 'Done!',
description: 'Saved!',
});
this.props.updateRelease(updatedRelease);
}
})
.catch(error => {
if (
error.hasOwnProperty('response') &&
error.response.status === 401
) {
window.location.href =
window.location.origin + '/publisher/login';
} else {
notification.error({
message: 'Something went wrong!',
description: 'Sorry, we were unable to complete your request.',
});
}
this.setState({
loading: false,
});
});
}
});
};
addNewMetaData = () => {
this.setState({
metaData: this.state.metaData.concat({ key: '', value: '' }),
});
};
handlePreviewCancel = () => this.setState({ previewVisible: false });
handlePreview = async file => {
if (!file.url && !file.preview) {
file.preview = await getBase64(file.originFileObj);
}
this.setState({
previewImage: file.url || file.preview,
previewVisible: true,
});
};
handleLowerOsVersionChange = lowerOsVersion => {
this.lowerOsVersion = lowerOsVersion;
};
handleUpperOsVersionChange = upperOsVersion => {
this.upperOsVersion = upperOsVersion;
};
render() {
const {
formConfig,
icons,
screenshots,
loading,
binaryFiles,
metaData,
previewImage,
previewVisible,
} = this.state;
const { getFieldDecorator } = this.props.form;
const { isAppUpdatable, supportedOsVersions, deviceType } = this.props;
const config = this.props.context;
const uploadButton = (
<div>
<PlusOutlined />
<div className="ant-upload-text">Select</div>
</div>
);
return (
<>
<Tooltip
title={
isAppUpdatable
? 'Edit this release'
: "This release isn't in an editable state"
}
>
<Button
disabled={!isAppUpdatable}
size="small"
type="primary"
onClick={this.showModal}
>
<EditOutlined /> Edit
</Button>
</Tooltip>
<Modal
title="Edit release"
visible={this.state.visible}
footer={null}
width={580}
onCancel={this.handleCancel}
>
<div>
<Spin tip="Uploading..." spinning={loading}>
<Form
labelAlign="left"
layout="horizontal"
hideRequiredMark
onSubmit={this.handleSubmit}
>
{formConfig.specificElements.hasOwnProperty('binaryFile') && (
<Form.Item {...formItemLayout} label="Application">
{getFieldDecorator('binaryFile', {
valuePropName: 'binaryFile',
getValueFromEvent: this.normFile,
required: true,
message: 'Please select application',
})(
<Upload
name="binaryFile"
onChange={this.handleBinaryFileChange}
beforeUpload={() => false}
>
{binaryFiles.length !== 1 && (
<Button>
<UploadOutlined /> Change
</Button>
)}
</Upload>,
)}
</Form.Item>
)}
{formConfig.specificElements.hasOwnProperty('url') && (
<Form.Item {...formItemLayout} label="URL">
{getFieldDecorator('url', {
rules: [
{
required: true,
message: 'Please input the url',
},
],
})(<Input placeholder="url" />)}
</Form.Item>
)}
<Form.Item {...formItemLayout} label="Icon">
{getFieldDecorator('icon', {
valuePropName: 'icon',
getValueFromEvent: this.normFile,
required: true,
message: 'Please select a icon',
})(
<Upload
name="logo"
listType="picture-card"
onChange={this.handleIconChange}
beforeUpload={() => false}
onPreview={this.handlePreview}
>
{icons.length === 1 ? null : uploadButton}
</Upload>,
)}
</Form.Item>
<Form.Item {...formItemLayout} label="Screenshots">
{getFieldDecorator('screenshots', {
valuePropName: 'icon',
getValueFromEvent: this.normFile,
required: true,
message: 'Please select a icon',
})(
<Upload
name="screenshots"
listType="picture-card"
onChange={this.handleScreenshotChange}
beforeUpload={() => false}
onPreview={this.handlePreview}
>
{screenshots.length >= 3 ? null : uploadButton}
</Upload>,
)}
</Form.Item>
{/* Package Name field for windows device type and other specific scene using it */}
{(formConfig.specificElements.hasOwnProperty('packageName') ||
(this.props.type === 'ENTERPRISE' &&
this.props.deviceType === 'windows')) && (
<Form.Item {...formItemLayout} label="Package Name">
{getFieldDecorator('packageName', {
rules: [
{
required: true,
message: 'Please input the package name',
},
],
})(<Input placeholder="Package Name" />)}
</Form.Item>
)}
{/* Version field for windows device type and other specific scene using it */}
{(formConfig.specificElements.hasOwnProperty('version') ||
(this.props.type === 'ENTERPRISE' &&
this.props.deviceType === 'windows')) && (
<Form.Item {...formItemLayout} label="Version">
{getFieldDecorator('version', {
rules: [
{
required: true,
message: 'Please input the version',
},
],
})(<Input placeholder="Version" />)}
</Form.Item>
)}
<Form.Item {...formItemLayout} label="Release Type">
{getFieldDecorator('releaseType', {
rules: [
{
required: true,
message: 'Please input the Release Type',
},
],
})(<Input placeholder="Release Type" />)}
</Form.Item>
<Form.Item {...formItemLayout} label="Description">
{getFieldDecorator('releaseDescription', {
rules: [
{
required: true,
message: 'Please enter a description for release',
},
],
})(
<TextArea
placeholder="Enter a description for release"
rows={5}
/>,
)}
</Form.Item>
{config.deviceTypes.mobileTypes.includes(deviceType) && (
<div>
<Authorized
permission="/permission/admin/device-mgt/admin/device-type"
no={
<Alert
message="You don't have permission to view supported OS versions."
type="warning"
banner
/>
}
/>
<Form.Item
{...formItemLayout}
label="Supported OS Versions"
>
{getFieldDecorator('supportedOS')(
<div>
<InputGroup>
<Row gutter={8}>
<Col span={11}>
<Form.Item>
{getFieldDecorator('lowerOsVersion', {
rules: [
{
required: true,
message: 'Please select Value',
},
],
})(
<Select
placeholder="Lower version"
style={{ width: '100%' }}
onChange={this.handleLowerOsVersionChange}
>
{supportedOsVersions.map(version => (
<Option
key={version.versionName}
value={version.versionName}
>
{version.versionName}
</Option>
))}
</Select>,
)}
</Form.Item>
</Col>
<Col span={2}>
<p> - </p>
</Col>
<Col span={11}>
<Form.Item>
{getFieldDecorator('upperOsVersion', {
rules: [
{
required: true,
message: 'Please select Value',
},
],
})(
<Select
style={{ width: '100%' }}
placeholder="Upper version"
onChange={this.handleUpperOsVersionChange}
>
{supportedOsVersions.map(version => (
<Option
key={version.versionName}
value={version.versionName}
>
{version.versionName}
</Option>
))}
</Select>,
)}
</Form.Item>
</Col>
</Row>
</InputGroup>
</div>,
)}
</Form.Item>
</div>
)}
<Form.Item {...formItemLayout} label="Meta Data">
{getFieldDecorator('meta', {
rules: [
{
required: true,
message: 'Please fill empty fields',
},
],
initialValue: false,
})(
<div>
{metaData.map((data, index) => {
if (
!(
data.key ===
config.windowsAppxMsiKeyValueForMetaData
.metaKeyArray[4]
)
) {
return (
<InputGroup key={index}>
<Row gutter={8}>
<Col span={10}>
<Input
placeholder="key"
value={data.key}
onChange={e => {
metaData[index].key =
e.currentTarget.value;
this.setState({
metaData,
});
}}
/>
</Col>
<Col span={10}>
<Input
placeholder="value"
value={data.value}
onChange={e => {
metaData[index].value =
e.currentTarget.value;
this.setState({
metaData,
});
}}
/>
</Col>
<Col span={3}>
<Button
type="dashed"
shape="circle"
icon={<MinusOutlined />}
onClick={() => {
metaData.splice(index, 1);
this.setState({
metaData,
});
}}
/>
</Col>
</Row>
</InputGroup>
);
}
})}
<Button
type="dashed"
icon={<PlusOutlined />}
onClick={this.addNewMetaData}
>
Add
</Button>
</div>,
)}
</Form.Item>
<Divider />
<Form.Item style={{ float: 'right', marginLeft: 8 }}>
<Button type="primary" htmlType="submit">
Update
</Button>
</Form.Item>
<Form.Item style={{ float: 'right' }}>
<Button htmlType="button" onClick={this.handleCancel}>
Back
</Button>
</Form.Item>
<br />
</Form>
</Spin>
</div>
<Modal
visible={previewVisible}
footer={null}
onCancel={this.handlePreviewCancel}
>
<img
alt="Preview Image"
style={{ width: '100%' }}
src={previewImage}
/>
</Modal>
</Modal>
</>
);
}
}
const EditRelease = withConfigContext(
Form.create({ name: 'add-new-release' })(EditReleaseModal),
);
export default EditRelease;

@ -1,84 +0,0 @@
/*
* 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.
*/
import React from 'react';
import { Avatar } from 'antd';
import { List, Typography } from 'antd';
import StarRatings from 'react-star-ratings';
const { Text, Paragraph } = Typography;
const colorList = [
'#f0932b',
'#badc58',
'#6ab04c',
'#eb4d4b',
'#0abde3',
'#9b59b6',
'#3498db',
'#22a6b3',
];
class SingleReview extends React.Component {
render() {
const review = this.props.review;
const randomColor = colorList[Math.floor(Math.random() * colorList.length)];
const avatarLetter = review.username.charAt(0).toUpperCase();
const content = (
<div style={{ marginTop: -5 }}>
<StarRatings
rating={review.rating}
starRatedColor="#777"
starDimension="12px"
starSpacing="2px"
numberOfStars={5}
name="rating"
/>
<Text style={{ fontSize: 12, color: '#aaa' }} type="secondary">
{' '}
{review.createdAt}
</Text>
<br />
<Paragraph
ellipsis={{ rows: 3, expandable: true }}
style={{ color: '#777' }}
>
{review.content}
</Paragraph>
</div>
);
return (
<div style={{ width: '100%' }}>
<List.Item.Meta
avatar={
<Avatar
style={{ backgroundColor: randomColor, verticalAlign: 'middle' }}
size="large"
>
{avatarLetter}
</Avatar>
}
title={review.username}
description={content}
/>
</div>
);
}
}
export default SingleReview;

@ -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 { List, Spin, Button } from 'antd';
import './styles.css';
import InfiniteScroll from 'react-infinite-scroller';
import SingleReview from './components/Review';
import axios from 'axios';
import { withConfigContext } from '../../../../../../../../../../components/ConfigContext';
import { handleApiError } from '../../../../../../../../../../services/utils/errorHandler';
const limit = 5;
class Reviews extends React.Component {
state = {
data: [],
loading: false,
hasMore: false,
loadMore: false,
forbiddenErrors: {
reviews: false,
},
};
componentDidMount() {
this.fetchData(0, limit, res => {
this.setState({
data: res,
});
});
}
fetchData = (offset, limit, callback) => {
const config = this.props.context;
const { uuid, type } = this.props;
this.setState({
loading: true,
});
axios
.get(
window.location.origin +
config.serverConfig.invoker.uri +
config.serverConfig.invoker.publisher +
'/admin/reviews/' +
type +
'/' +
uuid,
)
.then(res => {
if (res.status === 200) {
let reviews = res.data.data.data;
callback(reviews);
}
})
.catch(error => {
handleApiError(
error,
'Error occurred while trying to load reviews.',
true,
);
if (error.hasOwnProperty('response') && error.response.status === 403) {
const { forbiddenErrors } = this.state;
forbiddenErrors.reviews = true;
this.setState({
forbiddenErrors,
loading: false,
});
} else {
this.setState({
loading: false,
});
}
});
};
handleInfiniteOnLoad = count => {
const offset = count * limit;
let data = this.state.data;
this.setState({
loading: true,
});
if (data.length > 149) {
this.setState({
hasMore: false,
loading: false,
});
return;
}
this.fetchData(offset, limit, res => {
if (res.length > 0) {
data = data.concat(res);
this.setState({
data,
loading: false,
});
} else {
this.setState({
hasMore: false,
loading: false,
});
}
});
};
enableLoading = () => {
this.setState({
hasMore: true,
loadMore: true,
});
};
render() {
return (
<div>
<div className="demo-infinite-container">
<InfiniteScroll
initialLoad={false}
pageStart={0}
loadMore={this.handleInfiniteOnLoad}
hasMore={!this.state.loading && this.state.hasMore}
useWindow={true}
>
<List
dataSource={this.state.data}
renderItem={item => (
<List.Item key={item.id}>
<SingleReview review={item} />
</List.Item>
)}
>
{this.state.loading && this.state.hasMore && (
<div className="demo-loading-container">
<Spin />
</div>
)}
</List>
</InfiniteScroll>
{!this.state.loadMore && this.state.data.length >= limit && (
<div style={{ textAlign: 'center' }}>
<Button
type="dashed"
htmlType="button"
onClick={this.enableLoading}
>
Read All Reviews
</Button>
</div>
)}
</div>
</div>
);
}
}
export default withConfigContext(Reviews);

@ -1,34 +0,0 @@
/*
* 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.
*/
.demo-infinite-container {
overflow: auto;
padding: 8px 24px;
}
.demo-loading-container {
position: absolute;
bottom: 40px;
width: 100%;
text-align: center;
}
.demo-loading {
position: absolute;
bottom: -40px;
left: 50%;
}

@ -1,231 +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 { ShopOutlined } from '@ant-design/icons';
import { Divider, Row, Col, Typography, Button, Tooltip, Alert } from 'antd';
import StarRatings from 'react-star-ratings';
import Reviews from './components/Reviews';
import '../../../../../../../../App.css';
import DetailedRating from '../../../../components/DetailedRating';
import EditRelease from './components/EditRelease';
import { withConfigContext } from '../../../../../../../../components/ConfigContext';
import Authorized from '../../../../../../../../components/Authorized/Authorized';
import DeleteRelease from './components/DeleteRelease';
import { EntgraIcon } from 'entgra-icons-react';
const { Title, Text, Paragraph } = Typography;
class ReleaseView extends React.Component {
constructor(props) {
super(props);
this.state = {};
}
render() {
const { app, release } = this.props;
const config = this.props.context;
const { lifecycle, currentLifecycleStatus } = this.props;
let isKeyInclude = false;
let metaArrayWithOutWindowsKey = [];
if (release == null) {
return null;
}
let isAppUpdatable,
isAppInstallable,
isDeletableState = false;
if (lifecycle != null) {
isAppUpdatable = lifecycle[currentLifecycleStatus].isAppUpdatable;
isAppInstallable = lifecycle[currentLifecycleStatus].isAppInstallable;
isDeletableState = lifecycle[currentLifecycleStatus].isDeletableState;
}
const platform = app.deviceType;
const defaultPlatformIcons = config.defaultPlatformIcons;
let icon = defaultPlatformIcons.default.icon;
let color = defaultPlatformIcons.default.color;
if (defaultPlatformIcons.hasOwnProperty(platform)) {
icon = defaultPlatformIcons[platform].icon;
color = defaultPlatformIcons[platform].color;
}
let metaData = [];
try {
metaData = JSON.parse(release.metaData);
} catch (e) {
console.log(e);
}
return (
<div>
<div className="release">
<Row>
<Col xl={4} sm={6} xs={8} className="release-icon">
<img src={release.iconPath} alt="icon" />
</Col>
<Col xl={10} sm={11} className="release-title">
<Title level={2}>{app.name}</Title>
<StarRatings
rating={release.rating}
starRatedColor="#777"
starDimension="20px"
starSpacing="2px"
numberOfStars={5}
name="rating"
/>
<br />
<Text>Platform : </Text>
<span style={{ fontSize: 20, color: color, textAlign: 'center' }}>
<EntgraIcon type={icon} />
</span>
<Divider type="vertical" />
<Text>Version : {release.version}</Text>
<br />
<Authorized
permission="/permission/admin/app-mgt/publisher/application/update"
yes={
<>
<EditRelease
isAppUpdatable={isAppUpdatable}
type={app.type}
deviceType={app.deviceType}
release={release}
updateRelease={this.props.updateRelease}
supportedOsVersions={[...this.props.supportedOsVersions]}
/>
<Divider type="vertical" />
<DeleteRelease
uuid={release.uuid}
isDeletableState={isDeletableState}
/>
</>
}
/>
</Col>
<Col xl={8} md={10} sm={24} xs={24} style={{ float: 'right' }}>
<div>
<Authorized
permission="/permission/admin/app-mgt/publisher/application/update"
yes={
<Tooltip
title={
isAppInstallable
? 'Open this app in store'
: "This release isn't in an installable state"
}
>
<Button
style={{ float: 'right' }}
htmlType="button"
type="primary"
icon={<ShopOutlined />}
disabled={!isAppInstallable}
onClick={() => {
window.open(
window.location.origin +
'/store/' +
app.deviceType +
'/apps/' +
release.uuid,
);
}}
>
Open in store
</Button>
</Tooltip>
}
/>
</div>
</Col>
</Row>
<Divider />
<Row className="release-images" style={{ flexFlow: 'nowrap' }}>
{release.screenshots.map((screenshotUrl, index) => {
return (
<div key={index} className="release-screenshot">
<img key={screenshotUrl} src={screenshotUrl} />
</div>
);
})}
</Row>
<Divider />
<Paragraph type="secondary" ellipsis={{ rows: 3, expandable: true }}>
{release.description}
</Paragraph>
<Divider />
<Text>META DATA</Text>
<Row>
{metaData.map((data, index) => {
// Exclude showing the values related to windows app type variables in the metaData UI
if (
!config.windowsAppxMsiKeyValueForMetaData.metaKeyArray.includes(
data.key,
)
) {
isKeyInclude = false;
metaArrayWithOutWindowsKey.push(data);
return (
<Col
key={index}
lg={8}
md={6}
xs={24}
style={{ marginTop: 15 }}
>
<Text>{data.key}</Text>
<br />
<Text type="secondary">{data.value}</Text>
</Col>
);
}
})}
{(metaData.length === 0 ||
(!isKeyInclude && metaArrayWithOutWindowsKey.length === 0)) && (
<Text type="secondary">No meta data available.</Text>
)}
</Row>
<Divider />
<Text>REVIEWS</Text>
<Authorized
permission="/permission/admin/app-mgt/publisher/admin/review/view"
yes={
<div>
<Row>
<Col lg={18}>
<DetailedRating type="release" uuid={release.uuid} />
</Col>
</Row>
<Reviews type="release" uuid={release.uuid} />
</div>
}
no={
<Alert
message="You don't have permission to view reviews."
type="warning"
banner
closable
/>
}
/>
</div>
</div>
);
}
}
export default withConfigContext(ReleaseView);

@ -1,250 +0,0 @@
/*
* 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.
*/
import React from 'react';
import '../../../../../../App.css';
import { Typography, Row, Col, Card, Skeleton } from 'antd';
import axios from 'axios';
import ReleaseView from './components/ReleaseView';
import LifeCycle from './components/LifeCycle';
import { withConfigContext } from '../../../../../../components/ConfigContext';
import { handleApiError } from '../../../../../../services/utils/errorHandler';
import Authorized from '../../../../../../components/Authorized/Authorized';
import { isAuthorized } from '../../../../../../services/utils/authorizationHandler';
const { Title } = Typography;
class Release extends React.Component {
routes;
constructor(props) {
super(props);
this.routes = props.routes;
this.state = {
loading: true,
app: null,
uuid: null,
release: null,
currentLifecycleStatus: null,
lifecycle: null,
supportedOsVersions: [],
};
}
componentDidMount() {
const { uuid } = this.props.match.params;
this.fetchData(uuid);
if (
isAuthorized(
this.props.context.user,
'/permission/admin/app-mgt/publisher/application/update',
)
) {
this.getLifecycle();
}
}
changeCurrentLifecycleStatus = status => {
this.setState({
currentLifecycleStatus: status,
});
};
updateRelease = release => {
this.setState({
release,
});
};
fetchData = uuid => {
const config = this.props.context;
// send request to the invoker
axios
.get(
window.location.origin +
config.serverConfig.invoker.uri +
config.serverConfig.invoker.publisher +
'/applications/release/' +
uuid,
)
.then(res => {
if (res.status === 200) {
const app = res.data.data;
const release = app !== null ? app.applicationReleases[0] : null;
const currentLifecycleStatus =
release !== null ? release.currentStatus : null;
this.setState({
app: app,
release: release,
currentLifecycleStatus: currentLifecycleStatus,
loading: false,
uuid: uuid,
});
if (config.deviceTypes.mobileTypes.includes(app.deviceType)) {
if (
isAuthorized(
config.user,
'/permission/admin/device-mgt/admin/device-type',
)
) {
this.getSupportedOsVersions(app.deviceType);
} else {
this.setState({ loading: false });
}
}
}
})
.catch(error => {
handleApiError(
error,
'Error occurred while trying to load the release.',
);
this.setState({ loading: false });
});
};
getLifecycle = () => {
const config = this.props.context;
axios
.get(
window.location.origin +
config.serverConfig.invoker.uri +
config.serverConfig.invoker.publisher +
'/applications/lifecycle-config',
)
.then(res => {
if (res.status === 200) {
const lifecycle = res.data.data;
this.setState({
lifecycle: lifecycle,
});
}
})
.catch(error => {
handleApiError(
error,
'Error occurred while trying to load lifecycle configuration.',
true,
);
});
};
getSupportedOsVersions = deviceType => {
const config = this.props.context;
axios
.get(
window.location.origin +
config.serverConfig.invoker.uri +
config.serverConfig.invoker.deviceMgt +
`/admin/device-types/${deviceType}/versions`,
)
.then(res => {
if (res.status === 200) {
let supportedOsVersions = JSON.parse(res.data.data);
this.setState({
supportedOsVersions,
});
}
})
.catch(error => {
handleApiError(
error,
'Error occurred while trying to load supported OS versions.',
true,
);
this.setState({
loading: false,
});
});
};
render() {
const {
app,
release,
currentLifecycleStatus,
lifecycle,
loading,
forbiddenErrors,
} = this.state;
if (release == null && loading === false) {
return (
<div style={{ background: '#f0f2f5', padding: 24, minHeight: 780 }}>
<Title level={3}>No Apps Found</Title>
</div>
);
}
// todo remove uppercase
return (
<div>
<div className="main-container">
<Row style={{ padding: 10 }}>
<Col lg={16} md={24} style={{ padding: 3 }}>
<Card>
<Skeleton
loading={loading}
avatar={{ size: 'large' }}
active
paragraph={{ rows: 18 }}
>
{release !== null && (
<ReleaseView
forbiddenErrors={forbiddenErrors}
app={app}
release={release}
currentLifecycleStatus={currentLifecycleStatus}
lifecycle={lifecycle}
updateRelease={this.updateRelease}
supportedOsVersions={[...this.state.supportedOsVersions]}
/>
)}
</Skeleton>
</Card>
</Col>
<Authorized
permission="/permission/admin/app-mgt/publisher/application/update"
yes={
<Col lg={8} md={24} style={{ padding: 3 }}>
<Card lg={8} md={24}>
<Skeleton loading={loading} active paragraph={{ rows: 8 }}>
{release !== null && (
<LifeCycle
uuid={release.uuid}
currentStatus={release.currentStatus.toUpperCase()}
changeCurrentLifecycleStatus={
this.changeCurrentLifecycleStatus
}
lifecycle={lifecycle}
appReleases={this.props.location.state}
/>
)}
</Skeleton>
</Card>
</Col>
}
/>
</Row>
</div>
</div>
);
}
}
export default withConfigContext(Release);

@ -1,520 +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 { DeleteOutlined, EditOutlined, PlusOutlined } from '@ant-design/icons';
import {
Card,
Tag,
message,
Input,
notification,
Divider,
Button,
Spin,
Tooltip,
Popconfirm,
Modal,
Row,
Col,
Typography,
Alert,
} from 'antd';
import axios from 'axios';
import { TweenOneGroup } from 'rc-tween-one';
import pSBC from 'shade-blend-color';
import { withConfigContext } from '../../../../../../components/ConfigContext';
import { handleApiError } from '../../../../../../services/utils/errorHandler';
import { isAuthorized } from '../../../../../../services/utils/authorizationHandler';
const { Title } = Typography;
class ManageCategories extends React.Component {
state = {
loading: false,
searchText: '',
categories: [],
tempElements: [],
inputVisible: false,
inputValue: '',
isAddNewVisible: false,
isEditModalVisible: false,
currentlyEditingId: null,
editingValue: null,
forbiddenErrors: {
categories: false,
},
};
componentDidMount() {
const config = this.props.context;
this.hasPermissionToManage = isAuthorized(
config.user,
'/permission/admin/app-mgt/publisher/admin/application/update',
);
axios
.get(
window.location.origin +
config.serverConfig.invoker.uri +
config.serverConfig.invoker.publisher +
'/applications/categories',
)
.then(res => {
if (res.status === 200) {
let categories = JSON.parse(res.data.data);
this.setState({
categories: categories,
loading: false,
});
}
})
.catch(error => {
handleApiError(
error,
'Error occured while trying to load categories',
true,
);
this.setState({
loading: false,
});
});
}
handleCloseButton = () => {
this.setState({
tempElements: [],
isAddNewVisible: false,
});
};
deleteCategory = id => {
const config = this.props.context;
this.setState({
loading: true,
});
axios
.delete(
window.location.origin +
config.serverConfig.invoker.uri +
config.serverConfig.invoker.publisher +
'/admin/applications/categories/' +
id,
)
.then(res => {
if (res.status === 200) {
notification.success({
message: 'Done!',
description: 'Category Removed Successfully!',
});
const { categories } = this.state;
const remainingElements = categories.filter(function(value) {
return value.categoryName !== id;
});
this.setState({
loading: false,
categories: remainingElements,
});
}
})
.catch(error => {
handleApiError(
error,
'Error occurred while trying to load categories.',
);
this.setState({
loading: false,
});
});
};
renderElement = category => {
const config = this.props.context;
const categoryName = category.categoryName;
const tagElem = (
<Tag
color={pSBC(0.3, config.theme.primaryColor)}
style={{ marginTop: 8 }}
>
{categoryName}
{this.hasPermissionToManage && (
<>
<Divider type="vertical" />
<Tooltip title="edit">
<EditOutlined
onClick={() => {
this.openEditModal(categoryName);
}}
/>
</Tooltip>
<Divider type="vertical" />
<Tooltip title="delete">
<Popconfirm
title="Are you sure delete this category?"
onConfirm={() => {
if (category.isCategoryDeletable) {
this.deleteCategory(categoryName);
} else {
notification.error({
message: 'Cannot delete "' + categoryName + '"',
description:
'This category is currently used. Please unassign the category from apps.',
});
}
}}
okText="Yes"
cancelText="No"
>
<DeleteOutlined />
</Popconfirm>
</Tooltip>
</>
)}
</Tag>
);
return (
<span key={category.categoryName} style={{ display: 'inline-block' }}>
{tagElem}
</span>
);
};
renderTempElement = category => {
const tagElem = (
<Tag
style={{ marginTop: 8 }}
closable
onClose={e => {
e.preventDefault();
const { tempElements } = this.state;
const remainingElements = tempElements.filter(function(value) {
return value.categoryName !== category.categoryName;
});
this.setState({
tempElements: remainingElements,
});
}}
>
{category.categoryName}
</Tag>
);
return (
<span key={category.categoryName} style={{ display: 'inline-block' }}>
{tagElem}
</span>
);
};
showInput = () => {
this.setState({ inputVisible: true }, () => this.input.focus());
};
handleInputChange = e => {
this.setState({ inputValue: e.target.value });
};
handleInputConfirm = () => {
const { inputValue, categories } = this.state;
let { tempElements } = this.state;
if (inputValue) {
if (
categories.findIndex(i => i.categoryName === inputValue) === -1 &&
tempElements.findIndex(i => i.categoryName === inputValue) === -1
) {
tempElements = [
...tempElements,
{ categoryName: inputValue, isCategoryDeletable: true },
];
} else {
message.warning('Category already exists');
}
}
this.setState({
tempElements,
inputVisible: false,
inputValue: '',
});
};
handleSave = () => {
const config = this.props.context;
const { tempElements, categories } = this.state;
this.setState({
loading: true,
});
const data = tempElements.map(category => category.categoryName);
axios
.post(
window.location.origin +
config.serverConfig.invoker.uri +
config.serverConfig.invoker.publisher +
'/admin/applications/categories',
data,
)
.then(res => {
if (res.status === 200) {
notification.success({
message: 'Done!',
description: 'New Categories were added successfully',
});
this.setState({
categories: [...categories, ...tempElements],
tempElements: [],
inputVisible: false,
inputValue: '',
loading: false,
isAddNewVisible: false,
});
}
})
.catch(error => {
handleApiError(error, 'Error occurred while trying to add categories.');
this.setState({
loading: false,
});
});
};
saveInputRef = input => (this.input = input);
closeEditModal = e => {
this.setState({
isEditModalVisible: false,
currentlyEditingId: null,
});
};
openEditModal = id => {
this.setState({
isEditModalVisible: true,
currentlyEditingId: id,
editingValue: id,
});
};
editItem = () => {
const config = this.props.context;
const { editingValue, currentlyEditingId, categories } = this.state;
this.setState({
loading: true,
isEditModalVisible: false,
});
axios
.put(
window.location.origin +
config.serverConfig.invoker.uri +
config.serverConfig.invoker.publisher +
'/admin/applications/categories/rename?from=' +
currentlyEditingId +
'&to=' +
editingValue,
{},
)
.then(res => {
if (res.status === 200) {
notification.success({
message: 'Done!',
description: 'Category was edited successfully',
});
categories[
categories.findIndex(i => i.categoryName === currentlyEditingId)
].categoryName = editingValue;
this.setState({
categories: categories,
loading: false,
editingValue: null,
});
}
})
.catch(error => {
handleApiError(
error,
'Error occurred while trying to delete the category.',
);
this.setState({
loading: false,
editingValue: null,
});
});
};
handleEditInputChange = e => {
this.setState({
editingValue: e.target.value,
});
};
render() {
const {
categories,
inputVisible,
inputValue,
tempElements,
isAddNewVisible,
} = this.state;
const categoriesElements = categories.map(this.renderElement);
const temporaryElements = tempElements.map(this.renderTempElement);
return (
<div style={{ marginBottom: 16 }}>
{!this.hasPermissionToManage && (
<Alert
message="You don't have permission to add / edit / delete categories."
type="warning"
banner
/>
)}
<Card>
<Spin tip="Working on it..." spinning={this.state.loading}>
<Row>
<Col span={16}>
<Title level={4}>Categories</Title>
</Col>
<Col span={8}>
{!isAddNewVisible && (
<div style={{ float: 'right' }}>
<Button
icon={<PlusOutlined />}
// type="primary"
size="small"
onClick={() => {
this.setState(
{
isAddNewVisible: true,
inputVisible: true,
},
() => this.input.focus(),
);
}}
htmlType="button"
disabled={!this.hasPermissionToManage}
>
Add
</Button>
</div>
)}
</Col>
</Row>
{isAddNewVisible && (
<div>
<Divider />
<div style={{ marginBottom: 16 }}>
<TweenOneGroup
enter={{
scale: 0.8,
opacity: 0,
type: 'from',
duration: 100,
onComplete: e => {
e.target.style = '';
},
}}
leave={{ opacity: 0, width: 0, scale: 0, duration: 200 }}
appear={false}
>
{temporaryElements}
{inputVisible && (
<Input
ref={this.saveInputRef}
type="text"
size="small"
style={{ width: 120 }}
value={inputValue}
onChange={this.handleInputChange}
onBlur={this.handleInputConfirm}
onPressEnter={this.handleInputConfirm}
/>
)}
{!inputVisible && (
<Tag
onClick={this.showInput}
style={{ background: '#fff', borderStyle: 'dashed' }}
>
<PlusOutlined /> New Category
</Tag>
)}
</TweenOneGroup>
</div>
<div>
{tempElements.length > 0 && (
<span>
<Button
onClick={this.handleSave}
htmlType="button"
type="primary"
size="small"
>
Save
</Button>
<Divider type="vertical" />
</span>
)}
<Button onClick={this.handleCloseButton} size="small">
Cancel
</Button>
</div>
</div>
)}
<Divider dashed="true" />
<div style={{ marginTop: 8 }}>
<TweenOneGroup
enter={{
scale: 0.8,
opacity: 0,
type: 'from',
duration: 100,
onComplete: e => {
e.target.style = '';
},
}}
leave={{ opacity: 0, width: 0, scale: 0, duration: 200 }}
appear={false}
>
{categoriesElements}
</TweenOneGroup>
</div>
</Spin>
</Card>
<Modal
title="Edit"
visible={this.state.isEditModalVisible}
onCancel={this.closeEditModal}
onOk={this.editItem}
>
<Input
value={this.state.editingValue}
ref={input => (this.editingInput = input)}
onChange={this.handleEditInputChange}
/>
</Modal>
</div>
);
}
}
export default withConfigContext(ManageCategories);

@ -1,512 +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 { DeleteOutlined, EditOutlined, PlusOutlined } from '@ant-design/icons';
import {
Card,
Tag,
message,
Input,
notification,
Divider,
Button,
Spin,
Tooltip,
Popconfirm,
Modal,
Row,
Col,
Typography,
Alert,
} from 'antd';
import axios from 'axios';
import { TweenOneGroup } from 'rc-tween-one';
import { withConfigContext } from '../../../../../../components/ConfigContext';
import { handleApiError } from '../../../../../../services/utils/errorHandler';
import { isAuthorized } from '../../../../../../services/utils/authorizationHandler';
const { Title } = Typography;
class ManageTags extends React.Component {
state = {
loading: false,
searchText: '',
tags: [],
tempElements: [],
inputVisible: false,
inputValue: '',
isAddNewVisible: false,
isEditModalVisible: false,
currentlyEditingId: null,
editingValue: null,
forbiddenErrors: {
tags: false,
},
};
componentDidMount() {
const config = this.props.context;
this.hasPermissionToManage = isAuthorized(
config.user,
'/permission/admin/app-mgt/publisher/admin/application/update',
);
axios
.get(
window.location.origin +
config.serverConfig.invoker.uri +
config.serverConfig.invoker.publisher +
'/applications/tags',
)
.then(res => {
if (res.status === 200) {
let tags = JSON.parse(res.data.data);
this.setState({
tags: tags,
loading: false,
});
}
})
.catch(error => {
handleApiError(
error,
'Error occurred while trying to load tags.',
true,
);
this.setState({
loading: false,
});
});
}
handleCloseButton = () => {
this.setState({
tempElements: [],
isAddNewVisible: false,
});
};
deleteTag = id => {
const config = this.props.context;
this.setState({
loading: true,
});
axios
.delete(
window.location.origin +
config.serverConfig.invoker.uri +
config.serverConfig.invoker.publisher +
'/admin/applications/tags/' +
id,
)
.then(res => {
if (res.status === 200) {
notification.success({
message: 'Done!',
description: 'Tag Removed Successfully!',
});
const { tags } = this.state;
const remainingElements = tags.filter(function(value) {
return value.tagName !== id;
});
this.setState({
loading: false,
tags: remainingElements,
});
}
})
.catch(error => {
handleApiError(error, 'Error occurred while trying to delete the tag.');
this.setState({
loading: false,
});
});
};
renderElement = tag => {
const tagName = tag.tagName;
const tagElem = (
<Tag color="#34495e" style={{ marginTop: 8 }}>
{tagName}
{this.hasPermissionToManage && (
<>
<Divider type="vertical" />
<Tooltip title="edit">
<EditOutlined
onClick={() => {
this.openEditModal(tagName);
}}
/>
</Tooltip>
<Divider type="vertical" />
<Tooltip title="delete">
<Popconfirm
title="Are you sure delete this tag?"
onConfirm={() => {
if (tag.isTagDeletable) {
this.deleteTag(tagName);
} else {
notification.error({
message: 'Cannot delete "' + tagName + '"',
description:
'This tag is currently used. Please unassign the tag from apps.',
});
}
}}
okText="Yes"
cancelText="No"
>
<DeleteOutlined />
</Popconfirm>
</Tooltip>
</>
)}
</Tag>
);
return (
<span key={tag.tagName} style={{ display: 'inline-block' }}>
{tagElem}
</span>
);
};
renderTempElement = tag => {
const { tempElements } = this.state;
const tagElem = (
<Tag
style={{ marginTop: 8 }}
closable
onClose={e => {
e.preventDefault();
const remainingElements = tempElements.filter(function(value) {
return value.tagName !== tag.tagName;
});
this.setState({
tempElements: remainingElements,
});
}}
>
{tag.tagName}
</Tag>
);
return (
<span key={tag.tagName} style={{ display: 'inline-block' }}>
{tagElem}
</span>
);
};
showInput = () => {
this.setState({ inputVisible: true }, () => this.input.focus());
};
handleInputChange = e => {
this.setState({ inputValue: e.target.value });
};
handleInputConfirm = () => {
const { inputValue, tags } = this.state;
let { tempElements } = this.state;
if (inputValue) {
if (
tags.findIndex(i => i.tagName === inputValue) === -1 &&
tempElements.findIndex(i => i.tagName === inputValue) === -1
) {
tempElements = [
...tempElements,
{ tagName: inputValue, isTagDeletable: true },
];
} else {
message.warning('Tag already exists');
}
}
this.setState({
tempElements,
inputVisible: false,
inputValue: '',
});
};
handleSave = () => {
const config = this.props.context;
const { tempElements, tags } = this.state;
this.setState({
loading: true,
});
const data = tempElements.map(tag => tag.tagName);
axios
.post(
window.location.origin +
config.serverConfig.invoker.uri +
config.serverConfig.invoker.publisher +
'/applications/tags',
data,
)
.then(res => {
if (res.status === 200) {
notification.success({
message: 'Done!',
description: 'New tags were added successfully',
});
this.setState({
tags: [...tags, ...tempElements],
tempElements: [],
inputVisible: false,
inputValue: '',
loading: false,
isAddNewVisible: false,
});
}
})
.catch(error => {
handleApiError(error, 'Error occurred while trying to delete tag.');
this.setState({
loading: false,
});
});
};
saveInputRef = input => (this.input = input);
closeEditModal = e => {
this.setState({
isEditModalVisible: false,
currentlyEditingId: null,
});
};
openEditModal = id => {
this.setState({
isEditModalVisible: true,
currentlyEditingId: id,
editingValue: id,
});
};
editItem = () => {
const config = this.props.context;
const { editingValue, currentlyEditingId, tags } = this.state;
this.setState({
loading: true,
isEditModalVisible: false,
});
axios
.put(
window.location.origin +
config.serverConfig.invoker.uri +
config.serverConfig.invoker.publisher +
'/applications/tags/rename?from=' +
currentlyEditingId +
'&to=' +
editingValue,
{},
)
.then(res => {
if (res.status === 200) {
notification.success({
message: 'Done!',
description: 'Tag was edited successfully',
});
tags[
tags.findIndex(i => i.tagName === currentlyEditingId)
].tagName = editingValue;
this.setState({
tags: tags,
loading: false,
editingValue: null,
});
}
})
.catch(error => {
handleApiError(error, 'Error occurred while trying to edit tag.');
this.setState({
loading: false,
editingValue: null,
});
});
};
handleEditInputChange = e => {
this.setState({
editingValue: e.target.value,
});
};
render() {
const {
tags,
inputVisible,
inputValue,
tempElements,
isAddNewVisible,
} = this.state;
const tagsElements = tags.map(this.renderElement);
const temporaryElements = tempElements.map(this.renderTempElement);
return (
<div style={{ marginBottom: 16 }}>
{!this.hasPermissionToManage && (
<Alert
message="You don't have permission to add / edit / delete tags."
type="warning"
banner
/>
)}
<Card>
<Spin tip="Working on it..." spinning={this.state.loading}>
<Row>
<Col span={16}>
<Title level={4}>Tags</Title>
</Col>
<Col span={8}>
{!isAddNewVisible && (
<div style={{ float: 'right' }}>
<Button
icon={<PlusOutlined />}
// type="primary"
size="small"
onClick={() => {
this.setState(
{
isAddNewVisible: true,
inputVisible: true,
},
() => this.input.focus(),
);
}}
htmlType="button"
disabled={!this.hasPermissionToManage}
>
Add
</Button>
</div>
)}
</Col>
</Row>
{isAddNewVisible && (
<div>
<Divider />
<div style={{ marginBottom: 16 }}>
<TweenOneGroup
enter={{
scale: 0.8,
opacity: 0,
type: 'from',
duration: 100,
onComplete: e => {
e.target.style = '';
},
}}
leave={{ opacity: 0, width: 0, scale: 0, duration: 200 }}
appear={false}
>
{temporaryElements}
{inputVisible && (
<Input
ref={this.saveInputRef}
type="text"
size="small"
style={{ width: 120 }}
value={inputValue}
onChange={this.handleInputChange}
onBlur={this.handleInputConfirm}
onPressEnter={this.handleInputConfirm}
/>
)}
{!inputVisible && (
<Tag
onClick={this.showInput}
style={{ background: '#fff', borderStyle: 'dashed' }}
>
<PlusOutlined /> New Tag
</Tag>
)}
</TweenOneGroup>
</div>
<div>
{tempElements.length > 0 && (
<span>
<Button
onClick={this.handleSave}
htmlType="button"
type="primary"
size="small"
disabled={tempElements.length === 0}
>
Save
</Button>
<Divider type="vertical" />
</span>
)}
<Button onClick={this.handleCloseButton} size="small">
Cancel
</Button>
</div>
</div>
)}
<Divider dashed="true" />
<div style={{ marginTop: 8 }}>
<TweenOneGroup
enter={{
scale: 0.8,
opacity: 0,
type: 'from',
duration: 100,
onComplete: e => {
e.target.style = '';
},
}}
leave={{ opacity: 0, width: 0, scale: 0, duration: 200 }}
appear={false}
>
{tagsElements}
</TweenOneGroup>
</div>
</Spin>
</Card>
<Modal
title="Edit"
visible={this.state.isEditModalVisible}
onCancel={this.closeEditModal}
onOk={this.editItem}
>
<Input
value={this.state.editingValue}
ref={input => (this.editingInput = input)}
onChange={this.handleEditInputChange}
/>
</Modal>
</div>
);
}
}
export default withConfigContext(ManageTags);

@ -1,79 +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 { HomeOutlined } from '@ant-design/icons';
import { PageHeader, Typography, Breadcrumb, Row, Col } from 'antd';
import ManageCategories from './components/Categories';
import ManageTags from './components/Tags';
import { Link } from 'react-router-dom';
import Authorized from '../../../../components/Authorized/Authorized';
const { Paragraph } = Typography;
class Manage extends React.Component {
routes;
constructor(props) {
super(props);
this.routes = props.routes;
}
render() {
return (
<div>
<PageHeader style={{ paddingTop: 0, backgroundColor: '#fff' }}>
<Breadcrumb style={{ paddingBottom: 16 }}>
<Breadcrumb.Item>
<Link to="/publisher/apps">
<HomeOutlined /> Home
</Link>
</Breadcrumb.Item>
<Breadcrumb.Item>Manage</Breadcrumb.Item>
<Breadcrumb.Item>General</Breadcrumb.Item>
</Breadcrumb>
<div className="wrap">
<h3>Manage General Settings</h3>
<Paragraph>
Maintain and manage categories and tags here..
</Paragraph>
</div>
</PageHeader>
<div style={{ background: '#f0f2f5', padding: 24, minHeight: 780 }}>
<Row gutter={16}>
<Authorized
permission="/permission/admin/app-mgt/publisher/application/update"
yes={
<>
<Col sm={24} md={12}>
<ManageCategories />
</Col>
<Col sm={24} md={12}>
<ManageTags />
</Col>
</>
}
/>
</Row>
</div>
</div>
);
}
}
export default Manage;

@ -1,83 +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 { Modal, Button } from 'antd';
import { withConfigContext } from '../../../../../../../../components/ConfigContext';
class GooglePlayIframe extends React.Component {
constructor(props) {
super(props);
this.config = this.props.context;
this.state = {
visible: false,
};
}
showModal = () => {
this.setState({
visible: true,
});
};
handleOk = e => {
this.setState({
visible: false,
});
};
handleCancel = e => {
this.setState({
visible: false,
});
};
render() {
return (
<div style={{ display: 'inline-block', padding: 4 }}>
<Button type="primary" onClick={this.showModal}>
Approve Applications
</Button>
<Modal
title={null}
visible={this.state.visible}
onOk={this.handleOk}
onCancel={this.handleCancel}
width={740}
footer={null}
>
<iframe
style={{
height: 720,
border: 0,
width: '100%',
}}
src={
'https://play.google.com/work/embedded/search?token=' +
this.config.androidEnterpriseToken +
'&mode=APPROVE&showsearchbox=TRUE'
}
/>
</Modal>
</div>
);
}
}
export default withConfigContext(GooglePlayIframe);

@ -1,127 +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, Input, Modal, notification, Spin } from 'antd';
import axios from 'axios';
import { withConfigContext } from '../../../../../../../../../../components/ConfigContext';
import { withRouter } from 'react-router';
import { handleApiError } from '../../../../../../../../../../services/utils/errorHandler';
class AddNewPage extends React.Component {
state = {
visible: false,
pageName: '',
};
showModal = () => {
this.setState({
visible: true,
loading: false,
});
};
handleCancel = e => {
this.setState({
visible: false,
});
};
handlePageName = e => {
this.setState({
pageName: e.target.value,
});
};
createNewPage = () => {
const config = this.props.context;
this.setState({ loading: true });
axios
.post(
window.location.origin +
config.serverConfig.invoker.uri +
'/device-mgt/android/v1.0/enterprise/store-layout/page',
{
locale: 'en',
pageName: this.state.pageName,
},
)
.then(res => {
if (res.status === 200) {
const { pageId, pageName } = res.data.data;
notification.success({
message: 'Saved!',
description: 'Page created successfully!',
});
this.setState({ loading: false });
this.props.history.push(
`/publisher/manage/android-enterprise/pages/${pageName}/${pageId}`,
);
}
})
.catch(error => {
handleApiError(
error,
'Error occurred while trying to update the cluster.',
);
this.setState({ loading: false });
});
};
render() {
return (
<div style={{ marginTop: 24, marginBottom: 24 }}>
<Button type="dashed" onClick={this.showModal}>
Add new page
</Button>
<Modal
title="Add new page"
visible={this.state.visible}
onOk={this.createNewPage}
onCancel={this.handleCancel}
okText="Create Page"
footer={null}
>
<Spin spinning={this.state.loading}>
<p>Choose a name for the page</p>
<Input onChange={this.handlePageName} />
<Divider />
<div>
<Button onClick={this.handleCancel}>Cancel</Button>
<Divider type="vertical" />
<Button
onClick={this.createNewPage}
htmlType="button"
type="primary"
disabled={this.state.pageName.length === 0}
>
Create Page
</Button>
</div>
</Spin>
</Modal>
</div>
);
}
}
export default withConfigContext(withRouter(AddNewPage));

@ -1,303 +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 { DeleteOutlined, HomeOutlined } from '@ant-design/icons';
import {
Tag,
notification,
Table,
Typography,
Divider,
Popconfirm,
Button,
} from 'antd';
import { withConfigContext } from '../../../../../../../../components/ConfigContext';
import './styles.css';
import { Link } from 'react-router-dom';
import AddNewPage from './components/AddNewPage';
import { handleApiError } from '../../../../../../../../services/utils/errorHandler';
import { isAuthorized } from '../../../../../../../../services/utils/authorizationHandler';
const { Text, Title } = Typography;
class Pages extends React.Component {
constructor(props) {
super(props);
this.state = {
data: [],
pagination: {},
loading: false,
selectedRows: [],
homePageId: null,
};
this.hasPermissionToManage = isAuthorized(
this.props.context.user,
'/permission/admin/device-mgt/enterprise/user/modify',
);
}
rowSelection = {
onChange: (selectedRowKeys, selectedRows) => {
this.setState({
selectedRows: selectedRows,
});
},
};
componentDidMount() {
if (this.hasPermissionToView) {
this.setHomePage();
}
this.fetch();
}
// fetch data from api
fetch = (params = {}) => {
const config = this.props.context;
this.setState({ loading: true });
// send request to the invoker
axios
.get(
window.location.origin +
config.serverConfig.invoker.uri +
'/device-mgt/android/v1.0/enterprise/store-layout/page',
)
.then(res => {
if (res.status === 200) {
const pagination = { ...this.state.pagination };
this.setState({
loading: false,
data: res.data.data.page,
pagination,
});
}
})
.catch(error => {
handleApiError(error, 'Error occurred while trying to load pages.');
this.setState({ loading: false });
});
};
setHomePage = () => {
const config = this.props.context;
// send request to the invoker
axios
.get(
window.location.origin +
config.serverConfig.invoker.uri +
'/device-mgt/android/v1.0/enterprise/store-layout/home-page',
)
.then(res => {
if (res.status === 200) {
this.setState({
homePageId: res.data.data.homepageId,
});
}
})
.catch(error => {
handleApiError(error, 'Error occurred while trying to get home page.');
this.setState({ loading: false });
});
};
updateHomePage = pageId => {
const config = this.props.context;
this.setState({
loading: true,
});
// send request to the invoker
axios
.put(
window.location.origin +
config.serverConfig.invoker.uri +
'/device-mgt/android/v1.0/enterprise/store-layout/home-page/' +
pageId,
{},
)
.then(res => {
if (res.status === 200) {
notification.success({
message: 'Done!',
description: 'Home page was updated successfully!',
});
this.setState({
homePageId: res.data.data.homepageId,
loading: false,
});
}
})
.catch(error => {
handleApiError(
error,
'Error occurred while trying to update the home page.',
);
this.setState({ loading: false });
});
};
deletePage = pageId => {
const { data } = this.state;
const config = this.props.context;
this.setState({
loading: true,
});
// send request to the invoker
axios
.delete(
window.location.origin +
config.serverConfig.invoker.uri +
'/device-mgt/android/v1.0/enterprise/store-layout/page/' +
pageId,
)
.then(res => {
if (res.status === 200) {
notification.success({
message: 'Done!',
description: 'Home page was updated successfully!',
});
for (let i = 0; i < data.length; i++) {
if (data[i].id === pageId) {
data.splice(i, 1);
}
}
this.setState({
loading: false,
data: data,
});
}
})
.catch(error => {
handleApiError(
error,
'Error occurred while trying to delete the page.',
);
this.setState({ loading: false });
});
};
handleTableChange = (pagination, filters, sorter) => {
const pager = { ...this.state.pagination };
pager.current = pagination.current;
this.setState({
pagination: pager,
});
};
columns = [
{
title: 'Page',
dataIndex: 'name',
key: 'name',
width: 300,
render: (name, page) => {
const pageName = name[0].text;
return (
<div>
<Link
to={`/publisher/manage/android-enterprise/pages/${pageName}/${page.id}`}
>
{' '}
{pageName + ' '}
</Link>
{page.id === this.state.homePageId && (
<Tag color="#badc58">Home Page</Tag>
)}
</div>
);
},
},
{
title: 'Actions',
key: 'actions',
render: (name, page) => (
<div>
{this.hasPermissionToManage && (
<>
<span className="action">
<Button
disabled={page.id === this.state.homePageId}
className="btn-warning"
icon={<HomeOutlined />}
type="link"
onClick={() => {
this.updateHomePage(page.id);
}}
>
set as homepage
</Button>
</span>
<Divider type="vertical" />
<Popconfirm
title="Are you sure"
okText="Yes"
cancelText="No"
onConfirm={() => {
this.deletePage(page.id);
}}
>
<span className="action">
<Text type="danger">
<DeleteOutlined /> delete
</Text>
</span>
</Popconfirm>
</>
)}
</div>
),
},
];
render() {
const { data, pagination, loading } = this.state;
return (
<div className="layout-pages">
<Title level={4}>Pages</Title>
{this.hasPermissionToManage && <AddNewPage />}
<div style={{ backgroundColor: '#ffffff', borderRadius: 5 }}>
<Table
columns={this.columns}
rowKey={record => record.id}
dataSource={data}
pagination={{
...pagination,
size: 'small',
// position: "top",
showTotal: (total, range) =>
`showing ${range[0]}-${range[1]} of ${total} pages`,
showQuickJumper: true,
}}
loading={loading}
onChange={this.handleTableChange}
// rowSelection={this.rowSelection}
scroll={{ x: 1000 }}
/>
</div>
</div>
);
}
}
export default withConfigContext(Pages);

@ -1,33 +0,0 @@
/*
* 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.
*/
.layout-pages .action {
cursor: pointer;
}
.layout-pages .edit {
color: #008dff;
}
.layout-pages .btn-warning {
color: #faad14;
}
.layout-pages .btn-warning:hover {
color: #fa8905;
}

@ -1,82 +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 { SyncOutlined } from '@ant-design/icons';
import { Button, notification } from 'antd';
import axios from 'axios';
import { withConfigContext } from '../../../../../../../../components/ConfigContext';
import { handleApiError } from '../../../../../../../../services/utils/errorHandler';
class SyncAndroidApps extends React.Component {
constructor(props) {
super(props);
this.state = {
loading: false,
};
}
syncApps = () => {
const config = this.props.context;
this.setState({
loading: true,
});
axios
.get(
window.location.origin +
config.serverConfig.invoker.uri +
'/device-mgt/android/v1.0/enterprise/products/sync',
)
.then(res => {
notification.success({
message: 'Done!',
description: 'Apps synced successfully!',
});
this.setState({
loading: false,
});
})
.catch(error => {
handleApiError(error, 'Error occurred while syncing the apps.');
this.setState({
loading: false,
});
});
};
render() {
const { loading } = this.state;
return (
<div style={{ display: 'inline-block', padding: 4 }}>
<Button
onClick={this.syncApps}
loading={loading}
style={{ marginTop: 16 }}
type="primary"
icon={<SyncOutlined />}
>
Sync{loading && 'ing...'}
</Button>
</div>
);
}
}
export default withConfigContext(SyncAndroidApps);

@ -1,84 +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 { HomeOutlined } from '@ant-design/icons';
import { PageHeader, Breadcrumb, Divider, Result } from 'antd';
import { Link } from 'react-router-dom';
import SyncAndroidApps from './components/SyncAndroidApps';
import { withConfigContext } from '../../../../../../components/ConfigContext';
import GooglePlayIframe from './components/GooglePlayIframe';
import Pages from './components/Pages';
import Authorized from '../../../../../../components/Authorized/Authorized';
class ManageAndroidEnterprise extends React.Component {
routes;
constructor(props) {
super(props);
this.routes = props.routes;
this.config = this.props.context;
}
render() {
return (
<div>
<PageHeader style={{ paddingTop: 0, backgroundColor: '#fff' }}>
<Breadcrumb style={{ paddingBottom: 16 }}>
<Breadcrumb.Item>
<Link to="/publisher/apps">
<HomeOutlined /> Home
</Link>
</Breadcrumb.Item>
<Breadcrumb.Item>Manage</Breadcrumb.Item>
<Breadcrumb.Item>Android Enterprise</Breadcrumb.Item>
</Breadcrumb>
<div className="wrap">
<h3>Manage Android Enterprise</h3>
{/* <Paragraph>Lorem ipsum</Paragraph>*/}
</div>
</PageHeader>
<div style={{ background: '#f0f2f5', padding: 24, minHeight: 720 }}>
<Authorized
permission="/permission/admin/device-mgt/enterprise/user/view"
yes={
<>
<SyncAndroidApps />
<Authorized
permission="/permission/admin/device-mgt/enterprise/user/modify"
yes={<GooglePlayIframe />}
/>
<Divider />
<Pages />
</>
}
no={
<Result
status="403"
title="You don't have permission to view android enterprise configurations."
subTitle="Please contact system administrator"
/>
}
/>
</div>
</div>
);
}
}
export default withConfigContext(ManageAndroidEnterprise);

@ -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 { PlusOutlined } from '@ant-design/icons';
import { Modal, Table, Avatar } from 'antd';
import '../../styles.css';
import { withConfigContext } from '../../../../../../../../../../../../components/ConfigContext';
const columns = [
{
title: '',
dataIndex: 'iconUrl',
key: 'iconUrl',
// eslint-disable-next-line react/display-name
render: iconUrl => <Avatar shape="square" src={iconUrl} />,
},
{
title: 'Name',
dataIndex: 'name',
key: 'name',
},
{
title: 'Page',
dataIndex: 'packageId',
key: 'packageId',
},
];
class AddAppsToClusterModal extends React.Component {
constructor(props) {
super(props);
this.state = {
visible: false,
loading: false,
selectedProducts: [],
homePageId: null,
};
}
showModal = () => {
this.setState({
visible: true,
});
};
handleOk = () => {
this.props.addSelectedProducts(this.state.selectedProducts);
this.handleCancel();
};
handleCancel = () => {
this.setState({
visible: false,
});
};
rowSelection = {
onChange: (selectedRowKeys, selectedRows) => {
this.setState({
selectedProducts: selectedRows,
});
},
};
render() {
const { pagination, loading } = this.state;
return (
<div>
<div className="btn-add-new-wrapper">
<div className="btn-add-new">
<button className="btn" onClick={this.showModal}>
<PlusOutlined style={{ position: 'relative' }} />
</button>
</div>
<div className="title">Add app</div>
</div>
<Modal
title="Select Apps"
width={640}
visible={this.state.visible}
onOk={this.handleOk}
onCancel={this.handleCancel}
>
<Table
columns={columns}
rowKey={record => record.packageId}
dataSource={this.props.unselectedProducts}
scroll={{ x: 300 }}
pagination={{
...pagination,
size: 'small',
// position: "top",
showTotal: (total, range) =>
`showing ${range[0]}-${range[1]} of ${total} pages`,
showQuickJumper: true,
}}
loading={loading}
onChange={this.handleTableChange}
rowSelection={this.rowSelection}
/>
</Modal>
</div>
);
}
}
export default withConfigContext(AddAppsToClusterModal);

@ -1,483 +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 {
CaretDownOutlined,
CaretLeftFilled,
CaretRightFilled,
CaretUpOutlined,
CloseCircleFilled,
DeleteOutlined,
} from '@ant-design/icons';
import {
Button,
Col,
Divider,
message,
notification,
Popconfirm,
Row,
Spin,
Tooltip,
Typography,
} from 'antd';
import './styles.css';
import axios from 'axios';
import { withConfigContext } from '../../../../../../../../../../components/ConfigContext';
import AddAppsToClusterModal from './components/AddAppsToClusterModal';
import { handleApiError } from '../../../../../../../../../../services/utils/errorHandler';
const { Title } = Typography;
class Cluster extends React.Component {
constructor(props) {
super(props);
const { cluster, pageId } = this.props;
this.originalCluster = Object.assign({}, cluster);
const { name, products, clusterId } = cluster;
this.clusterId = clusterId;
this.pageId = pageId;
this.state = {
name,
products,
isSaveable: false,
loading: false,
};
}
handleNameChange = name => {
this.setState({
name,
});
if (name !== this.originalCluster.name) {
this.setState({
isSaveable: true,
});
}
};
isProductsChanged = currentProducts => {
let isChanged = false;
const originalProducts = this.originalCluster.products;
if (currentProducts.length === originalProducts.length) {
for (let i = 0; i < currentProducts.length; i++) {
if (currentProducts[i].packageId !== originalProducts[i].packageId) {
isChanged = true;
break;
}
}
} else {
isChanged = true;
}
return isChanged;
};
swapProduct = (index, swapIndex) => {
const products = [...this.state.products];
if (swapIndex !== -1 && index < products.length) {
// swap elements
[products[index], products[swapIndex]] = [
products[swapIndex],
products[index],
];
this.setState({
products,
});
this.setState({
isSaveable: this.isProductsChanged(products),
});
}
};
removeProduct = index => {
const products = [...this.state.products];
products.splice(index, 1);
this.setState({
products,
isSaveable: true,
});
};
getCurrentCluster = () => {
const { products, name } = this.state;
return {
pageId: this.pageId,
clusterId: this.clusterId,
name: name,
products: products,
orderInPage: this.props.orderInPage,
};
};
resetChanges = () => {
const cluster = this.originalCluster;
const { name, products } = cluster;
this.setState({
loading: false,
name,
products,
isSaveable: false,
});
};
updateCluster = () => {
const config = this.props.context;
const cluster = this.getCurrentCluster();
this.setState({ loading: true });
axios
.put(
window.location.origin +
config.serverConfig.invoker.uri +
'/device-mgt/android/v1.0/enterprise/store-layout/cluster',
cluster,
)
.then(res => {
if (res.status === 200) {
notification.success({
message: 'Saved!',
description: 'Cluster updated successfully!',
});
const cluster = res.data.data;
this.originalCluster = Object.assign({}, cluster);
this.resetChanges();
if (this.props.toggleAddNewClusterVisibility !== undefined) {
this.props.toggleAddNewClusterVisibility(false);
}
}
})
.catch(error => {
handleApiError(
error,
'Error occurred while trying to update the cluster.',
);
this.setState({ loading: false });
});
};
deleteCluster = () => {
const config = this.props.context;
this.setState({ loading: true });
axios
.delete(
window.location.origin +
config.serverConfig.invoker.uri +
`/device-mgt/android/v1.0/enterprise/store-layout/cluster/${this.clusterId}/page/` +
this.pageId,
)
.then(res => {
if (res.status === 200) {
notification.success({
message: 'Done!',
description: 'Cluster deleted successfully!',
});
this.setState({
loading: false,
});
this.props.removeLoadedCluster(this.clusterId);
}
})
.catch(error => {
handleApiError(
error,
'Error occurred while trying to update the cluster.',
);
this.setState({ loading: false });
});
};
getUnselectedProducts = () => {
const { applications } = this.props;
const selectedProducts = this.state.products;
// get a copy from all products
const unSelectedProducts = [...applications];
// remove selected products from unselected products
selectedProducts.forEach(selectedProduct => {
for (let i = 0; i < unSelectedProducts.length; i++) {
if (selectedProduct.packageId === unSelectedProducts[i].packageId) {
// remove item from array
unSelectedProducts.splice(i, 1);
}
}
});
return unSelectedProducts;
};
addSelectedProducts = products => {
this.setState({
products: [...this.state.products, ...products],
isSaveable: products.length > 0,
});
};
cancelAddingNewCluster = () => {
this.resetChanges();
this.props.toggleAddNewClusterVisibility(false);
};
saveNewCluster = () => {
const config = this.props.context;
const cluster = this.getCurrentCluster();
this.setState({ loading: true });
axios
.post(
window.location.origin +
config.serverConfig.invoker.uri +
'/device-mgt/android/v1.0/enterprise/store-layout/cluster',
cluster,
)
.then(res => {
if (res.status === 200) {
notification.success({
message: 'Saved!',
description: 'Cluster updated successfully!',
});
const cluster = res.data.data;
this.resetChanges();
this.props.addSavedClusterToThePage(cluster);
}
})
.catch(error => {
if (error.hasOwnProperty('response') && error.response.status === 401) {
message.error('You are not logged in');
window.location.href = window.location.origin + '/publisher/login';
} else {
notification.error({
message: 'There was a problem',
duration: 0,
description: 'Error occurred while trying to update the cluster.',
});
}
this.setState({ loading: false });
});
};
render() {
const { name, products, loading } = this.state;
const unselectedProducts = this.getUnselectedProducts();
const { isTemporary, index } = this.props;
const Product = ({ product, index }) => {
const { packageId } = product;
let imageSrc = '';
const iconUrl = product.iconUrl;
// check if the icon url is an url or google image id
if (iconUrl.startsWith('http')) {
imageSrc = iconUrl;
} else {
imageSrc = `https://lh3.googleusercontent.com/${iconUrl}=s240-rw`;
}
return (
<div className="product">
<div className="arrow">
{this.props.hasPermissionToManage && (
<button
disabled={index === 0}
className="btn"
onClick={() => {
this.swapProduct(index, index - 1);
}}
>
<CaretLeftFilled />
</button>
)}
</div>
<div className="product-icon">
<img src={imageSrc} />
<Tooltip title={packageId}>
<div className="title">{packageId}</div>
</Tooltip>
</div>
{this.props.hasPermissionToManage && (
<>
<div className="arrow">
<button
disabled={index === products.length - 1}
onClick={() => {
this.swapProduct(index, index + 1);
}}
className="btn btn-right"
>
<CaretRightFilled />
</button>
</div>
<div className="delete-btn">
<button
className="btn"
onClick={() => {
this.removeProduct(index);
}}
>
<CloseCircleFilled />
</button>
</div>
</>
)}
</div>
);
};
return (
<div className="cluster" id={this.props.orderInPage}>
<Spin spinning={loading}>
<Row>
<Col span={16}>
{this.props.hasPermissionToManage && (
<Title editable={{ onChange: this.handleNameChange }} level={4}>
{name}
</Title>
)}
{!this.props.hasPermissionToManage && (
<Title level={4}>{name}</Title>
)}
</Col>
<Col span={8}>
{!isTemporary && this.props.hasPermissionToManage && (
<div style={{ float: 'right' }}>
<Tooltip title="Move Up">
<Button
type="link"
icon={<CaretUpOutlined />}
size="large"
onClick={() => {
this.props.swapClusters(index, index - 1);
}}
htmlType="button"
/>
</Tooltip>
<Tooltip title="Move Down">
<Button
type="link"
icon={<CaretDownOutlined />}
size="large"
onClick={() => {
this.props.swapClusters(index, index + 1);
}}
htmlType="button"
/>
</Tooltip>
<Tooltip title="Delete Cluster">
<Popconfirm
title="Are you sure?"
okText="Yes"
cancelText="No"
onConfirm={this.deleteCluster}
>
<Button
type="danger"
icon={<DeleteOutlined />}
shape="circle"
htmlType="button"
/>
</Popconfirm>
</Tooltip>
</div>
)}
</Col>
</Row>
<div className="products-row">
{this.props.hasPermissionToManage && (
<AddAppsToClusterModal
addSelectedProducts={this.addSelectedProducts}
unselectedProducts={unselectedProducts}
/>
)}
{products.map((product, index) => {
return (
<Product
key={product.packageId}
product={product}
index={index}
/>
);
})}
</div>
{this.props.hasPermissionToManage && (
<Row>
<Col>
{isTemporary && (
<div>
<Button onClick={this.cancelAddingNewCluster}>
Cancel
</Button>
<Divider type="vertical" />
<Tooltip
title={
products.length === 0
? 'You must add applications to the cluster before saving'
: ''
}
>
<Button
disabled={products.length === 0}
onClick={this.saveNewCluster}
htmlType="button"
type="primary"
>
Save
</Button>
</Tooltip>
</div>
)}
{!isTemporary && (
<div>
<Button
onClick={this.resetChanges}
disabled={!this.state.isSaveable}
>
Cancel
</Button>
<Divider type="vertical" />
<Button
onClick={this.updateCluster}
htmlType="button"
type="primary"
disabled={!this.state.isSaveable}
>
Save
</Button>
</div>
)}
</Col>
</Row>
)}
</Spin>
</div>
);
}
}
export default withConfigContext(Cluster);

@ -1,168 +0,0 @@
/*
* 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.
*/
.cluster{
border-radius: 5px;
background-color: white;
padding: 24px;
margin: 14px 0 14px 0;
}
.cluster .products-row{
-webkit-align-items: center;
align-items: center;
display: -webkit-box;
display: -webkit-flex;
display: flex;
-webkit-flex-wrap: wrap;
flex-wrap: wrap;
margin-top: 24px;
}
.cluster .product{
display: -webkit-box;
display: -webkit-flex;
display: flex;
padding: 20px 0;
position: relative;
}
.cluster .product .product-icon{
display: -webkit-box;
display: -webkit-flex;
display: flex;
-webkit-flex-direction: column;
flex-direction: column;
height: 180px;
width: 90px;
}
.cluster .product .title, .cluster .btn-add-new-wrapper .title {
color: #202124;
overflow: hidden;
position: relative;
text-overflow: ellipsis;
white-space: nowrap;
margin-top: 16px;
}
.cluster .product .product-icon img{
-webkit-align-self: center;
align-self: center;
height: 90px;
/*padding-bottom: 16px;*/
width: 90px;
border-radius: 28%;
}
.cluster .product .arrow {
color: #80868b;
font-size: 20px;
position: relative;
top: 20px;
width: 36px;
}
.cluster .product .arrow .btn {
width: 48px;
height: 48px;
padding: 12px;
font-size: 24px;
display: inline-block;
position: relative;
-webkit-box-sizing: border-box;
box-sizing: border-box;
border: none;
outline: none;
background-color: transparent;
fill: currentColor;
color: inherit;
text-decoration: none;
cursor: pointer;
-webkit-user-select: none;
}
.cluster .product .arrow .btn-right {
left: -12px;
}
.cluster .product .delete-btn {
color: #80868b;
font-size: 20px;
position: absolute;
right: 0;
top: -10px;
}
.cluster .product .delete-btn .btn {
width: 48px;
height: 48px;
padding: 16px 28px 0 0;
font-size: 24px;
display: inline-block;
position: relative;
-webkit-box-sizing: border-box;
box-sizing: border-box;
border: none;
outline: none;
background-color: transparent;
fill: currentColor;
color: inherit;
text-decoration: none;
cursor: pointer;
-webkit-user-select: none;
}
.cluster .btn-add-new-wrapper{
display: -webkit-box;
display: -webkit-flex;
display: flex;
-webkit-flex-direction: column;
flex-direction: column;
height: 180px;
width: 90px;
margin: 0 33px;
}
.cluster .btn-add-new{
-webkit-align-self: center;
align-self: center;
height: 90px;
padding-bottom: 16px;
width: 90px;
border-radius: 28%;
}
.cluster .btn-add-new .btn{
height: 36px;
padding: 0 23px 0 23px;
border-width: 1px;
min-height: 90px;
width: 90px;
background-color: transparent;
border-radius: 28%;
}
.cluster .btn-add-new :hover{
background-color: rgba(250, 159, 0, 0.2);
cursor: pointer;
}
.cluster .ant-typography-edit-content{
width: 200px;
}

@ -1,125 +0,0 @@
/*
* 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.
*/
import React from 'react';
import { Button, Modal, notification, Select, Spin } from 'antd';
import axios from 'axios';
import { withConfigContext } from '../../../../../../../../../../components/ConfigContext';
import { handleApiError } from '../../../../../../../../../../services/utils/errorHandler';
const { Option } = Select;
class EditLinks extends React.Component {
constructor(props) {
super(props);
this.selectedLinks = [];
this.state = {
visible: false,
};
}
showModal = () => {
this.setState({
visible: true,
loading: false,
});
};
handleCancel = e => {
this.setState({
visible: false,
});
};
updateLinks = () => {
const config = this.props.context;
this.setState({ loading: true });
axios
.put(
window.location.origin +
config.serverConfig.invoker.uri +
'/device-mgt/android/v1.0/enterprise/store-layout/page-link',
{
pageId: this.props.pageId,
links: this.selectedLinks,
},
)
.then(res => {
if (res.status === 200) {
notification.success({
message: 'Saved!',
description: 'Links updated successfully!',
});
this.props.updateLinks(this.selectedLinks);
this.setState({
loading: false,
visible: false,
});
}
})
.catch(error => {
handleApiError(
error,
'Error occurred while trying to update the cluster.',
);
this.setState({ loading: false });
});
};
handleChange = selectedLinks => {
this.selectedLinks = selectedLinks;
};
render() {
return (
<div>
<Button onClick={this.showModal} type="link">
[add / remove links]
</Button>
<Modal
title="Add / Remove Links"
visible={this.state.visible}
onOk={this.updateLinks}
onCancel={this.handleCancel}
okText="Update"
>
<Spin spinning={this.state.loading}>
<Select
mode="multiple"
style={{ width: '100%' }}
placeholder="Please select links"
defaultValue={this.props.selectedLinks}
onChange={this.handleChange}
>
{this.props.pages.map(page => (
<Option disabled={page.id === this.props.pageId} key={page.id}>
{page.name[0].text}
</Option>
))}
</Select>
</Spin>
</Modal>
</div>
);
}
}
export default withConfigContext(EditLinks);

@ -1,471 +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 { HomeOutlined, PlusOutlined } from '@ant-design/icons';
import {
PageHeader,
Typography,
Breadcrumb,
Button,
Col,
Row,
notification,
message,
Spin,
Tag,
Divider,
Result,
} from 'antd';
import { Link, withRouter } from 'react-router-dom';
import { withConfigContext } from '../../../../../../../../components/ConfigContext';
import axios from 'axios';
import Cluster from './components/Cluster';
import EditLinks from './components/EditLinks';
import { handleApiError } from '../../../../../../../../services/utils/errorHandler';
import Authorized from '../../../../../../../../components/Authorized/Authorized';
import { isAuthorized } from '../../../../../../../../services/utils/authorizationHandler';
const { Title } = Typography;
class Page extends React.Component {
routes;
constructor(props) {
super(props);
const { pageName, pageId } = this.props.match.params;
this.pageId = pageId;
this.routes = props.routes;
this.config = this.props.context;
this.pages = [];
this.pageNames = {};
this.state = {
pageName,
clusters: [],
loading: false,
applications: [],
isAddNewClusterVisible: false,
links: [],
};
this.hasPermissionToManage = isAuthorized(
this.props.context.user,
'/permission/admin/device-mgt/enterprise/user/modify',
);
this.hasPermissionToView = isAuthorized(
this.props.context.user,
'/permission/admin/device-mgt/enterprise/user/view',
);
}
componentDidMount() {
if (this.hasPermissionToView) {
this.fetchClusters();
this.fetchApplications();
this.fetchPages();
}
}
removeLoadedCluster = clusterId => {
const clusters = [...this.state.clusters];
let index = -1;
for (let i = 0; i < clusters.length; i++) {
if (clusters[i].clusterId === clusterId) {
index = i;
break;
}
}
clusters.splice(index, 1);
this.setState({
clusters,
});
};
updatePageName = pageName => {
const config = this.props.context;
if (pageName !== this.state.pageName && pageName !== '') {
const data = {
locale: 'en',
pageName: pageName,
pageId: this.pageId,
};
axios
.put(
window.location.origin +
config.serverConfig.invoker.uri +
'/device-mgt/android/v1.0/enterprise/store-layout/page',
data,
)
.then(res => {
if (res.status === 200) {
notification.success({
message: 'Saved!',
description: 'Page name updated successfully!',
});
this.setState({
loading: false,
pageName: res.data.data.pageName,
});
this.props.history.push(
`/publisher/manage/android-enterprise/pages/${pageName}/${this.pageId}`,
);
}
})
.catch(error => {
handleApiError(
error,
'Error occurred while trying to save the page name.',
);
this.setState({ loading: false });
});
}
};
swapClusters = (index, swapIndex) => {
const clusters = [...this.state.clusters];
if (swapIndex !== -1 && index < clusters.length) {
// swap elements
[clusters[index], clusters[swapIndex]] = [
clusters[swapIndex],
clusters[index],
];
this.setState({
clusters,
});
}
};
fetchPages = () => {
const config = this.props.context;
this.setState({ loading: true });
// send request to the invoker
axios
.get(
window.location.origin +
config.serverConfig.invoker.uri +
'/device-mgt/android/v1.0/enterprise/store-layout/page',
)
.then(res => {
if (res.status === 200) {
this.pages = res.data.data.page;
let links = [];
this.pages.forEach(page => {
this.pageNames[page.id.toString()] = page.name[0].text;
if (page.id === this.pageId && page.hasOwnProperty('link')) {
links = page.link;
}
});
this.setState({
loading: false,
links,
});
}
})
.catch(error => {
if (error.hasOwnProperty('response') && error.response.status === 401) {
message.error('You are not logged in');
window.location.href = window.location.origin + '/publisher/login';
} else {
notification.error({
message: 'There was a problem',
duration: 0,
description: 'Error occurred while trying to load pages.',
});
}
this.setState({ loading: false });
});
};
fetchClusters = () => {
const config = this.props.context;
axios
.get(
window.location.origin +
config.serverConfig.invoker.uri +
`/device-mgt/android/v1.0/enterprise/store-layout/page/${this.pageId}/clusters`,
)
.then(res => {
if (res.status === 200) {
let clusters = JSON.parse(res.data.data);
// sort according to the orderInPage value
clusters.sort((a, b) => (a.orderInPage > b.orderInPage ? 1 : -1));
this.setState({
clusters,
loading: false,
});
}
})
.catch(error => {
if (error.hasOwnProperty('response') && error.response.status === 401) {
window.location.href = window.location.origin + '/publisher/login';
} else if (
!(error.hasOwnProperty('response') && error.response.status === 404)
) {
// API sends 404 when no apps
notification.error({
message: 'There was a problem',
duration: 0,
description: 'Error occurred while trying to load clusters.',
});
}
this.setState({
loading: false,
});
});
};
// fetch applications
fetchApplications = () => {
const config = this.props.context;
this.setState({ loading: true });
const filters = {
appType: 'PUBLIC',
deviceType: 'android',
};
// send request to the invoker
axios
.post(
window.location.origin +
config.serverConfig.invoker.uri +
config.serverConfig.invoker.publisher +
'/applications',
filters,
)
.then(res => {
if (res.status === 200) {
const applications = res.data.data.applications.map(application => {
const release = application.applicationReleases[0];
return {
packageId: `app:${application.packageName}`,
iconUrl: release.iconPath,
name: application.name,
};
});
this.setState({
loading: false,
applications,
});
}
})
.catch(error => {
if (error.hasOwnProperty('response') && error.response.status === 401) {
message.error('You are not logged in');
window.location.href = window.location.origin + '/publisher/login';
} else {
notification.error({
message: 'There was a problem',
duration: 0,
description: 'Error occurred while trying to load pages.',
});
}
this.setState({ loading: false });
});
};
toggleAddNewClusterVisibility = isAddNewClusterVisible => {
this.setState({
isAddNewClusterVisible,
});
};
addSavedClusterToThePage = cluster => {
this.setState({
clusters: [...this.state.clusters, cluster],
isAddNewClusterVisible: false,
});
window.scrollTo(0, document.body.scrollHeight);
};
updateLinks = links => {
this.setState({
links,
});
};
render() {
const {
pageName,
loading,
clusters,
applications,
isAddNewClusterVisible,
links,
} = this.state;
return (
<div>
<PageHeader style={{ paddingTop: 0, backgroundColor: '#fff' }}>
<Breadcrumb style={{ paddingBottom: 16 }}>
<Breadcrumb.Item>
<Link to="/publisher/apps">
<HomeOutlined /> Home
</Link>
</Breadcrumb.Item>
<Breadcrumb.Item>Manage</Breadcrumb.Item>
<Breadcrumb.Item>
<Link to="/publisher/manage/android-enterprise">
Android Enterprise
</Link>
</Breadcrumb.Item>
<Breadcrumb.Item>Manage Page</Breadcrumb.Item>
</Breadcrumb>
<div className="wrap">
<h3>Manage Android Enterprise</h3>
{/* <Paragraph>Lorem ipsum</Paragraph>*/}
</div>
</PageHeader>
<Authorized
permission="/permission/admin/device-mgt/enterprise/user/view"
yes={
<Spin spinning={loading}>
<div
style={{ background: '#f0f2f5', padding: 24, minHeight: 720 }}
>
<Row>
<Col md={8} sm={18} xs={24}>
{this.hasPermissionToManage && (
<Title
editable={{ onChange: this.updatePageName }}
level={2}
>
{pageName}
</Title>
)}
{!this.hasPermissionToManage && (
<Title level={2}>{pageName}</Title>
)}
</Col>
</Row>
<Row>
<Col>
<Title level={4}>Links</Title>
{links.map(link => {
if (this.pageNames.hasOwnProperty(link.toString())) {
return (
<Tag key={link} color="#87d068">
{this.pageNames[link.toString()]}
</Tag>
);
}
return null;
})}
<Authorized
permission="/permission/admin/device-mgt/enterprise/user/modify"
yes={
<EditLinks
updateLinks={this.updateLinks}
pageId={this.pageId}
selectedLinks={links}
pages={this.pages}
/>
}
/>
</Col>
{/* <Col>*/}
{/* </Col>*/}
</Row>
<Divider dashed={true} />
<Title level={4}>Clusters</Title>
<Authorized
permission="/permission/admin/device-mgt/enterprise/user/modify"
yes={
<div
hidden={isAddNewClusterVisible}
style={{ textAlign: 'center' }}
>
<Button
type="dashed"
shape="round"
icon={<PlusOutlined />}
size="large"
onClick={() => {
this.toggleAddNewClusterVisibility(true);
}}
>
Add new cluster
</Button>
</div>
}
/>
<div hidden={!isAddNewClusterVisible}>
<Cluster
cluster={{
clusterId: 0,
name: 'New Cluster',
products: [],
}}
hasPermissionToManage={true}
orderInPage={clusters.length}
isTemporary={true}
pageId={this.pageId}
applications={applications}
addSavedClusterToThePage={this.addSavedClusterToThePage}
toggleAddNewClusterVisibility={
this.toggleAddNewClusterVisibility
}
/>
</div>
{clusters.map((cluster, index) => {
return (
<Cluster
hasPermissionToManage={this.hasPermissionToManage}
key={cluster.clusterId}
index={index}
orderInPage={cluster.orderInPage}
isTemporary={false}
cluster={cluster}
pageId={this.pageId}
applications={applications}
swapClusters={this.swapClusters}
removeLoadedCluster={this.removeLoadedCluster}
/>
);
})}
</div>
</Spin>
}
no={
<Result
status="403"
title="You don't have permission to view android enterprise configurations."
subTitle="Please contact system administrator"
/>
}
/>
</div>
);
}
}
export default withConfigContext(withRouter(Page));

@ -1,89 +0,0 @@
/*
* 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.
*/
.logo-image {
height: 31px;
margin: 0 5px 16px 24px;
float: left;
}
.logo-image img{
height: 35px;
}
.profile{
float:right;
margin-right: 2%;
}
.mobile-layout{
visibility: hidden;
position: absolute;
}
.mobile-menu-button{
margin-left: 4%;
position: fixed;
}
.bar-icon{
margin-top: 10%;
font-size: 27px;
}
@media screen and (max-width: 660px) {
.web-layout{
visibility: hidden;
}
.mobile-layout{
visibility: visible;
z-index: 1;
}
.mobile-menu-button{
margin-top: 15px;
}
Header{
position: fixed;
z-index: 1;
width: 100%;
}
.dashboard-body{
margin-top: 50px;
}
.logo-image {
margin-left: 20%;
}
}
@media screen and (max-height: 350px) {
.mobile-menu-button{
margin-top: 15px;
}
.dashboard-body{
margin-top: 9%;
}
}

@ -1,199 +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 { LockOutlined, UserOutlined } from '@ant-design/icons';
import { Form } from '@ant-design/compatible';
import '@ant-design/compatible/assets/index.css';
import {
Typography,
Row,
Col,
Input,
Button,
message,
notification,
} from 'antd';
import './styles.css';
import axios from 'axios';
import './styles.css';
import { withConfigContext } from '../../components/ConfigContext';
const { Title } = Typography;
const { Text } = Typography;
class Login extends React.Component {
render() {
const config = this.props.context;
return (
<div className="login">
<div className="background"></div>
<div className="content">
<Row>
<Col xs={3} sm={3} md={10}></Col>
<Col xs={18} sm={18} md={4}>
<Row style={{ marginBottom: 20 }}>
<Col style={{ textAlign: 'center' }}>
<img
style={{
marginTop: 36,
height: 60,
}}
src={config.theme.logo}
/>
</Col>
</Row>
<Title level={2}>Login</Title>
<WrappedNormalLoginForm />
</Col>
</Row>
<Row>
<Col span={4} offset={10}></Col>
</Row>
</div>
</div>
);
}
}
class NormalLoginForm extends React.Component {
constructor(props) {
super(props);
this.state = {
inValid: false,
loading: false,
};
}
handleSubmit = e => {
const config = this.props.context;
const thisForm = this;
e.preventDefault();
this.props.form.validateFields((err, values) => {
thisForm.setState({
inValid: false,
});
if (!err) {
thisForm.setState({
loading: true,
});
const parameters = {
username: values.username,
password: values.password,
platform: 'publisher',
};
const request = Object.keys(parameters)
.map(key => key + '=' + parameters[key])
.join('&');
axios
.post(window.location.origin + config.serverConfig.loginUri, request)
.then(res => {
if (res.status === 200) {
let redirectUrl = window.location.origin + '/publisher';
const searchParams = new URLSearchParams(window.location.search);
if (searchParams.has('redirect')) {
redirectUrl = searchParams.get('redirect');
}
window.location = redirectUrl;
}
})
.catch(function(error) {
if (
error.hasOwnProperty('response') &&
error.response.status === 401
) {
thisForm.setState({
loading: false,
inValid: true,
});
} else {
notification.error({
message: 'There was a problem',
duration: 10,
description: message,
});
thisForm.setState({
loading: false,
inValid: false,
});
}
});
}
});
};
render() {
const { getFieldDecorator } = this.props.form;
let errorMsg = '';
if (this.state.inValid) {
errorMsg = <Text type="danger">Invalid Login Details</Text>;
}
let loading = '';
if (this.state.loading) {
loading = <Text type="secondary">Loading..</Text>;
}
return (
<Form onSubmit={this.handleSubmit} className="login-form">
<Form.Item>
{getFieldDecorator('username', {
rules: [{ required: true, message: 'Please enter your username' }],
})(
<Input
style={{ height: 32 }}
prefix={<UserOutlined style={{ color: 'rgba(0,0,0,.25)' }} />}
placeholder="Username"
/>,
)}
</Form.Item>
<Form.Item>
{getFieldDecorator('password', {
rules: [{ required: true, message: 'Please enter your password' }],
})(
<Input
style={{ height: 32 }}
prefix={<LockOutlined style={{ color: 'rgba(0,0,0,.25)' }} />}
type="password"
placeholder="Password"
/>,
)}
</Form.Item>
{loading}
{errorMsg}
<Form.Item>
<Button
loading={this.state.loading}
block
type="primary"
htmlType="submit"
className="login-form-button"
>
Log in
</Button>
</Form.Item>
</Form>
);
}
}
const WrappedNormalLoginForm = Form.create({ name: 'normal_login' })(
withConfigContext(NormalLoginForm),
);
export default withConfigContext(Login);

@ -1,62 +0,0 @@
/*
* 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.
*/
@-moz-keyframes spin {
0% {
-moz-transform: rotate(0deg) scale(1.0);
}
100% {
-moz-transform: rotate(360deg) scale(0.1);
}
}
@-webkit-keyframes spin {
0% {
-webkit-transform: rotate(0deg) scale(1.0);
}
100% {
-webkit-transform: rotate(360deg) scale(0.1);
}
}
@keyframes spin {
0% {
-webkit-transform: rotate(0deg) scale(1.0);
}
100% {
-webkit-transform: rotate(360deg) scale(0.1);
transform: rotate(360deg) scale(0.1);
}
}
.login .background {
position: absolute;
height: 100%;
width: 100%;
z-index: 0;
background-image: url('https://gw.alipayobjects.com/zos/rmsportal/TVYTbAXWheQpRcWDaDMu.svg');
background-repeat: no-repeat;
background-position: center 110px;
background-size: 100%;
animation: spin 200s infinite linear;
}
.login .content {
position: relative;
z-index: 1;
}

@ -1,153 +0,0 @@
/*
* 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.
*/
// This optional code is used to register a service worker.
// register() is not called by default.
// This lets the app load faster on subsequent visits in production, and gives
// it offline capabilities. However, it also means that developers (and users)
// will only see deployed updates on subsequent visits to a page, after all the
// existing tabs open on the page have been closed, since previously cached
// resources are updated in the background.
// To learn more about the benefits of this model and instructions on how to
// opt-in, read https://bit.ly/CRA-PWA
const isLocalhost = Boolean(
window.location.hostname === 'localhost' ||
// [::1] is the IPv6 localhost address.
window.location.hostname === '[::1]' ||
// 127.0.0.1/8 is considered localhost for IPv4.
window.location.hostname.match(
/^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/,
),
);
export function register(config) {
if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
// The URL constructor is available in all browsers that support SW.
const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href);
if (publicUrl.origin !== window.location.origin) {
// Our service worker won't work if PUBLIC_URL is on a different origin
// from what our page is served on. This might happen if a CDN is used to
// serve assets; see https://github.com/facebook/create-react-app/issues/2374
return;
}
window.addEventListener('load', () => {
const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
if (isLocalhost) {
// This is running on localhost. Let's check if a service worker still exists or not.
checkValidServiceWorker(swUrl, config);
// Add some additional logging to localhost, pointing developers to the
// service worker/PWA documentation.
navigator.serviceWorker.ready.then(() => {
console.log(
'This web app is being served cache-first by a service ' +
'worker. To learn more, visit https://bit.ly/CRA-PWA',
);
});
} else {
// Is not localhost. Just register service worker
registerValidSW(swUrl, config);
}
});
}
}
function registerValidSW(swUrl, config) {
navigator.serviceWorker
.register(swUrl)
.then(registration => {
registration.onupdatefound = () => {
const installingWorker = registration.installing;
if (installingWorker == null) {
return;
}
installingWorker.onstatechange = () => {
if (installingWorker.state === 'installed') {
if (navigator.serviceWorker.controller) {
// At this point, the updated precached content has been fetched,
// but the previous service worker will still serve the older
// content until all client tabs are closed.
console.log(
'New content is available and will be used when all ' +
'tabs for this page are closed. See https://bit.ly/CRA-PWA.',
);
// Execute callback
if (config && config.onUpdate) {
config.onUpdate(registration);
}
} else {
// At this point, everything has been precached.
// It's the perfect time to display a
// "Content is cached for offline use." message.
console.log('Content is cached for offline use.');
// Execute callback
if (config && config.onSuccess) {
config.onSuccess(registration);
}
}
}
};
};
})
.catch(error => {
console.error('Error during service worker registration:', error);
});
}
function checkValidServiceWorker(swUrl, config) {
// Check if the service worker can be found. If it can't reload the page.
fetch(swUrl)
.then(response => {
// Ensure service worker exists, and that we really are getting a JS file.
const contentType = response.headers.get('content-type');
if (
response.status === 404 ||
(contentType != null && contentType.indexOf('javascript') === -1)
) {
// No service worker found. Probably a different app. Reload the page.
navigator.serviceWorker.ready.then(registration => {
registration.unregister().then(() => {
window.location.reload();
});
});
} else {
// Service worker found. Proceed as normal.
registerValidSW(swUrl, config);
}
})
.catch(() => {
console.log(
'No internet connection found. App is running in offline mode.',
);
});
}
export function unregister() {
if ('serviceWorker' in navigator) {
navigator.serviceWorker.ready.then(registration => {
registration.unregister();
});
}
}

@ -1,24 +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.
*/
export const isAuthorized = (user, permission) => {
if (!user || !permission) {
return false;
}
return user.permissions.includes(permission);
};

@ -1,44 +0,0 @@
/*
* 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.
*/
import { notification } from 'antd';
export const handleApiError = (
error,
message,
isForbiddenMessageSilent = false,
) => {
if (error.hasOwnProperty('response') && error.response.status === 401) {
const redirectUrl = encodeURI(window.location.href);
window.location.href =
window.location.origin + `/publisher/login?redirect=${redirectUrl}`;
// silence 403 forbidden message
} else if (
!(
isForbiddenMessageSilent &&
error.hasOwnProperty('response') &&
error.response.status === 403
)
) {
notification.error({
message: 'There was a problem',
duration: 10,
description: message,
});
}
};

@ -1,40 +0,0 @@
/*
* Copyright (C) 2020. Entgra (Pvt) Ltd, https://entgra.io
* All Rights Reserved.
*
* Unauthorized copying/redistribution of this file, via any medium
* is strictly prohibited.
* Proprietary and confidential.
*
* Licensed under the Entgra Commercial License,
* Version 1.0 (the "License");
* you may not use this file except in compliance with the License.
*
* 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.
*
* You may obtain a copy of the License at
* https://entgra.io/licenses/entgra-commercial/1.0
*/
import axios from 'axios';
import { notification } from 'antd';
export const getUiConfig = config => {
return axios
.get(window.location.origin + config.serverConfig.appUiConfigUri)
.then(res => {
return res.data;
})
.catch(error => {
notification.error({
message: 'There was a problem',
duration: 0,
description: 'Error occurred while trying to load UI configurations.',
});
});
};

@ -1,135 +0,0 @@
/*
* 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.
*/
var path = require('path');
const HtmlWebPackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const configurations = require('./public/conf/config.json');
const config = {
devtool: 'source-map',
output: {
publicPath: '/publisher/',
},
watch: false,
resolve: {
alias: {
AppData: path.resolve(__dirname, 'source/src/app/common/'),
AppComponents: path.resolve(__dirname, 'source/src/app/components/'),
},
extensions: ['.jsx', '.js', '.ttf', '.woff', '.woff2', '.svg'],
},
module: {
rules: [
{
test: /\.(js|jsx)$/,
exclude: /node_modules/,
use: [
{
loader: 'babel-loader',
},
],
},
{
test: /\.html$/,
use: [
{
loader: 'html-loader',
options: { minimize: true },
},
],
},
{
test: /\.css$/,
use: [MiniCssExtractPlugin.loader, 'css-loader'],
},
{
test: /\.scss$/,
use: [
MiniCssExtractPlugin.loader,
'css-loader',
'postcss-loader',
'sass-loader',
],
},
{
test: /\.scss$/,
use: ['style-loader', 'scss-loader'],
},
{
test: /\.less$/,
use: [
{
loader: 'style-loader',
},
{
loader: 'css-loader',
},
{
loader: 'less-loader',
options: {
modifyVars: {
'primary-color': configurations.theme.primaryColor,
'link-color': configurations.theme.primaryColor,
},
javascriptEnabled: true,
},
},
],
},
{
test: /\.(woff|woff2|eot|ttf|svg)$/,
loader: 'url-loader?limit=100000',
},
{
test: /\.(png|jpe?g)/i,
use: [
{
loader: 'url-loader',
options: {
name: './img/[name].[ext]',
limit: 10000,
},
},
{
loader: 'img-loader',
},
],
},
],
},
plugins: [
new HtmlWebPackPlugin({
template: './src/index.html',
filename: './index.html',
}),
new MiniCssExtractPlugin({
filename: '[name].css',
chunkFilename: '[id].css',
}),
],
externals: {
Config: JSON.stringify(require('./public/conf/config.json')),
},
};
if (process.env.NODE_ENV === 'development') {
config.watch = true;
}
module.exports = config;

@ -1,25 +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.
-->
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" version="2.5">
<display-name>Publisher-Webapp</display-name>
<error-page>
<error-code>404</error-code>
<location>/index.html</location>
</error-page>
</web-app>

@ -1,171 +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>application-mgt</artifactId>
<version>4.1.11-SNAPSHOT</version>
</parent>
<artifactId>org.wso2.carbon.device.application.mgt.store.ui</artifactId>
<version>4.1.11-SNAPSHOT</version>
<packaging>war</packaging>
<name>WSO2 Carbon - Application Management Store UI Component</name>
<url>https://entgra.io</url>
<description>This Component contains Application Management store UI</description>
<dependencies>
</dependencies>
<build>
<plugins>
<plugin>
<artifactId>maven-war-plugin</artifactId>
<configuration>
<packagingExcludes>WEB-INF/lib/*cxf*.jar</packagingExcludes>
<warName>store</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>
</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>
<!-- <plugin>-->
<!-- <groupId>org.codehaus.mojo</groupId>-->
<!-- <artifactId>exec-maven-plugin</artifactId>-->
<!-- <version>1.5.0</version>-->
<!-- <executions>-->
<!-- <execution>-->
<!-- <id>npm install (initialize)</id>-->
<!-- <goals>-->
<!-- <goal>exec</goal>-->
<!-- </goals>-->
<!-- <phase>initialize</phase>-->
<!-- <configuration>-->
<!-- <workingDirectory>react-app</workingDirectory>-->
<!-- <executable>${npm.executable}</executable>-->
<!-- <arguments>-->
<!-- <argument>install</argument>-->
<!-- <argument>&#45;&#45;silent</argument>-->
<!-- </arguments>-->
<!-- </configuration>-->
<!-- </execution>-->
<!-- <execution>-->
<!-- <id>npm run build (compile)</id>-->
<!-- <goals>-->
<!-- <goal>exec</goal>-->
<!-- </goals>-->
<!-- <phase>compile</phase>-->
<!-- <configuration>-->
<!-- <workingDirectory>react-app</workingDirectory>-->
<!-- <executable>${npm.executable}</executable>-->
<!-- <arguments>-->
<!-- <argument>run</argument>-->
<!-- <argument>${npm.build.command}</argument>-->
<!-- </arguments>-->
<!-- </configuration>-->
<!-- </execution>-->
<!-- </executions>-->
<!-- <configuration>-->
<!-- <workingDirectory>${npm.working.dir}</workingDirectory>-->
<!-- </configuration>-->
<!-- </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>

@ -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, 100, 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
}
}

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

@ -1,28 +0,0 @@
/*
* 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.
*/
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,
};
};

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

Loading…
Cancel
Save