mirror of
https://repository.entgra.net/community/device-mgt-core.git
synced 2025-10-06 02:01:45 +00:00
Merge branch 'appm-publisher/feature/eslint' into 'master'
Add ESLint to APPM publisher ui Closes product-iots#294 See merge request entgra/carbon-device-mgt!421
This commit is contained in:
commit
751f2b4bb8
@ -79,6 +79,16 @@
|
|||||||
<arguments>install</arguments>
|
<arguments>install</arguments>
|
||||||
</configuration>
|
</configuration>
|
||||||
</execution>
|
</execution>
|
||||||
|
<execution>
|
||||||
|
<id>lint</id>
|
||||||
|
<goals>
|
||||||
|
<goal>npm</goal>
|
||||||
|
</goals>
|
||||||
|
<configuration>
|
||||||
|
<arguments>run-script lint</arguments>
|
||||||
|
</configuration>
|
||||||
|
<phase>generate-resources</phase>
|
||||||
|
</execution>
|
||||||
<execution>
|
<execution>
|
||||||
<id>prod</id>
|
<id>prod</id>
|
||||||
<goals>
|
<goals>
|
||||||
|
|||||||
@ -0,0 +1,325 @@
|
|||||||
|
{
|
||||||
|
"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
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"singleQuote": true,
|
||||||
|
"trailingComma": "all",
|
||||||
|
"parser": "flow"
|
||||||
|
}
|
||||||
@ -16,14 +16,13 @@
|
|||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
module.exports = function (api) {
|
module.exports = function(api) {
|
||||||
api.cache(true);
|
api.cache(true);
|
||||||
const presets = [ "@babel/preset-env",
|
const presets = ['@babel/preset-env', '@babel/preset-react'];
|
||||||
"@babel/preset-react" ];
|
const plugins = ['@babel/plugin-proposal-class-properties'];
|
||||||
const plugins = ["@babel/plugin-proposal-class-properties"];
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
presets,
|
presets,
|
||||||
plugins
|
plugins,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
@ -56,6 +56,12 @@
|
|||||||
"body-parser": "^1.19.0",
|
"body-parser": "^1.19.0",
|
||||||
"chai": "^4.1.2",
|
"chai": "^4.1.2",
|
||||||
"css-loader": "^0.28.11",
|
"css-loader": "^0.28.11",
|
||||||
|
"eslint": "^5.16.0",
|
||||||
|
"eslint-config-prettier": "4.3.0",
|
||||||
|
"eslint-plugin-babel": "5.3.0",
|
||||||
|
"eslint-plugin-jsx": "0.0.2",
|
||||||
|
"eslint-plugin-prettier": "3.1.0",
|
||||||
|
"eslint-plugin-react": "7.14.2",
|
||||||
"express": "^4.17.1",
|
"express": "^4.17.1",
|
||||||
"express-pino-logger": "^4.0.0",
|
"express-pino-logger": "^4.0.0",
|
||||||
"file-loader": "^2.0.0",
|
"file-loader": "^2.0.0",
|
||||||
@ -74,6 +80,7 @@
|
|||||||
"npm-run-all": "^4.1.5",
|
"npm-run-all": "^4.1.5",
|
||||||
"pino-colada": "^1.4.5",
|
"pino-colada": "^1.4.5",
|
||||||
"postcss-loader": "^3.0.0",
|
"postcss-loader": "^3.0.0",
|
||||||
|
"prettier": "1.18.1",
|
||||||
"react": "^16.8.6",
|
"react": "^16.8.6",
|
||||||
"react-dom": "^16.8.6",
|
"react-dom": "^16.8.6",
|
||||||
"react-intl": "^2.9.0",
|
"react-intl": "^2.9.0",
|
||||||
@ -96,6 +103,7 @@
|
|||||||
"build_prod": "NODE_ENV=production NODE_OPTIONS=--max_old_space_size=4096 webpack -p --display errors-only --hide-modules",
|
"build_prod": "NODE_ENV=production NODE_OPTIONS=--max_old_space_size=4096 webpack -p --display errors-only --hide-modules",
|
||||||
"build_dev": "NODE_ENV=development webpack -d --watch ",
|
"build_dev": "NODE_ENV=development webpack -d --watch ",
|
||||||
"server": "node-env-run server --exec nodemon | pino-colada",
|
"server": "node-env-run server --exec nodemon | pino-colada",
|
||||||
"dev2": "run-p server start"
|
"dev2": "run-p server start",
|
||||||
|
"lint": "eslint \"src/**/*.js\""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -16,155 +16,168 @@
|
|||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from "react";
|
import React from 'react';
|
||||||
import "antd/dist/antd.less";
|
import 'antd/dist/antd.less';
|
||||||
import RouteWithSubRoutes from "./components/RouteWithSubRoutes";
|
import RouteWithSubRoutes from './components/RouteWithSubRoutes';
|
||||||
import {
|
import { BrowserRouter as Router, Redirect, Switch } from 'react-router-dom';
|
||||||
BrowserRouter as Router,
|
import axios from 'axios';
|
||||||
Redirect, Switch,
|
import { Layout, Spin, Result } from 'antd';
|
||||||
} from 'react-router-dom';
|
import ConfigContext from './context/ConfigContext';
|
||||||
import axios from "axios";
|
|
||||||
import {Layout, Spin, Result, notification} from "antd";
|
|
||||||
import ConfigContext from "./context/ConfigContext";
|
|
||||||
|
|
||||||
const {Content} = Layout;
|
const { Content } = Layout;
|
||||||
const loadingView = (
|
const loadingView = (
|
||||||
<Layout>
|
<Layout>
|
||||||
<Content style={{
|
<Content
|
||||||
padding: '0 0',
|
style={{
|
||||||
paddingTop: 300,
|
padding: '0 0',
|
||||||
backgroundColor: '#fff',
|
paddingTop: 300,
|
||||||
textAlign: 'center'
|
backgroundColor: '#fff',
|
||||||
}}>
|
textAlign: 'center',
|
||||||
<Spin tip="Loading..."/>
|
}}
|
||||||
</Content>
|
>
|
||||||
</Layout>
|
<Spin tip="Loading..." />
|
||||||
|
</Content>
|
||||||
|
</Layout>
|
||||||
);
|
);
|
||||||
|
|
||||||
const errorView = (
|
const errorView = (
|
||||||
<Result
|
<Result
|
||||||
style={{
|
style={{
|
||||||
paddingTop: 200
|
paddingTop: 200,
|
||||||
}}
|
}}
|
||||||
status="500"
|
status="500"
|
||||||
title="Error occurred while loading the configuration"
|
title="Error occurred while loading the configuration"
|
||||||
subTitle="Please refresh your browser window"
|
subTitle="Please refresh your browser window"
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
class App extends React.Component {
|
class App extends React.Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
loading: true,
|
||||||
|
error: false,
|
||||||
|
config: {},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
constructor(props) {
|
componentDidMount() {
|
||||||
super(props);
|
this.updateFavicon();
|
||||||
this.state = {
|
axios
|
||||||
loading: true,
|
.get(window.location.origin + '/publisher/public/conf/config.json')
|
||||||
error: false,
|
.then(res => {
|
||||||
config: {}
|
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 + '/publisher-ui-request-handler/user',
|
||||||
|
'platform=publisher',
|
||||||
|
)
|
||||||
|
.then(res => {
|
||||||
|
config.user = res.data.data;
|
||||||
|
const pageURL = window.location.pathname;
|
||||||
|
const lastURLSegment = pageURL.substr(pageURL.lastIndexOf('/') + 1);
|
||||||
|
if (lastURLSegment === 'login') {
|
||||||
|
window.location.href = window.location.origin + '/publisher/';
|
||||||
|
} else {
|
||||||
|
this.getAndroidEnterpriseToken(config);
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
|
.catch(error => {
|
||||||
|
if (error.hasOwnProperty('response') && error.response.status === 401) {
|
||||||
|
const redirectUrl = encodeURI(window.location.href);
|
||||||
|
const pageURL = window.location.pathname;
|
||||||
|
const lastURLSegment = pageURL.substr(pageURL.lastIndexOf('/') + 1);
|
||||||
|
if (lastURLSegment !== 'login') {
|
||||||
|
window.location.href =
|
||||||
|
window.location.origin +
|
||||||
|
`/publisher/login?redirect=${redirectUrl}`;
|
||||||
|
} else {
|
||||||
|
this.getAndroidEnterpriseToken(config);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.setState({
|
||||||
|
loading: false,
|
||||||
|
error: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
componentDidMount() {
|
updateFavicon = () => {
|
||||||
this.updateFavicon();
|
const link =
|
||||||
axios.get(
|
document.querySelector("link[rel*='icon']") ||
|
||||||
window.location.origin + "/publisher/public/conf/config.json",
|
document.createElement('link');
|
||||||
).then(res => {
|
link.type = 'image/x-icon';
|
||||||
const config = res.data;
|
link.rel = 'shortcut icon';
|
||||||
this.checkUserLoggedIn(config);
|
link.href =
|
||||||
}).catch((error) => {
|
window.location.origin +
|
||||||
this.setState({
|
'/devicemgt/public/uuf.unit.favicon/img/favicon.png';
|
||||||
loading: false,
|
document.getElementsByTagName('head')[0].appendChild(link);
|
||||||
error: true
|
};
|
||||||
})
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
getAndroidEnterpriseToken = (config) => {
|
render() {
|
||||||
axios.get(
|
const { loading, error } = this.state;
|
||||||
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) => {
|
const applicationView = (
|
||||||
axios.post(
|
<Router>
|
||||||
window.location.origin + "/publisher-ui-request-handler/user",
|
<ConfigContext.Provider value={this.state.config}>
|
||||||
"platform=publisher"
|
<div>
|
||||||
).then(res => {
|
<Switch>
|
||||||
config.user = res.data.data;
|
<Redirect exact from="/publisher" to="/publisher/apps" />
|
||||||
const pageURL = window.location.pathname;
|
{this.props.routes.map(route => (
|
||||||
const lastURLSegment = pageURL.substr(pageURL.lastIndexOf('/') + 1);
|
<RouteWithSubRoutes key={route.path} {...route} />
|
||||||
if (lastURLSegment === "login") {
|
))}
|
||||||
window.location.href = window.location.origin + `/publisher/`;
|
</Switch>
|
||||||
} else {
|
</div>
|
||||||
this.getAndroidEnterpriseToken(config);
|
</ConfigContext.Provider>
|
||||||
}
|
</Router>
|
||||||
}).catch((error) => {
|
);
|
||||||
if (error.hasOwnProperty("response") && error.response.status === 401) {
|
|
||||||
const redirectUrl = encodeURI(window.location.href);
|
|
||||||
const pageURL = window.location.pathname;
|
|
||||||
const lastURLSegment = pageURL.substr(pageURL.lastIndexOf('/') + 1);
|
|
||||||
if (lastURLSegment !== "login") {
|
|
||||||
window.location.href = window.location.origin + `/publisher/login?redirect=${redirectUrl}`;
|
|
||||||
} else {
|
|
||||||
this.getAndroidEnterpriseToken(config);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
this.setState({
|
|
||||||
loading: false,
|
|
||||||
error: true
|
|
||||||
})
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
updateFavicon = () =>{
|
return (
|
||||||
const link = document.querySelector("link[rel*='icon']") || document.createElement('link');
|
<div>
|
||||||
link.type = 'image/x-icon';
|
{loading && loadingView}
|
||||||
link.rel = 'shortcut icon';
|
{!loading && !error && applicationView}
|
||||||
link.href = window.location.origin+'/devicemgt/public/uuf.unit.favicon/img/favicon.png';
|
{error && errorView}
|
||||||
document.getElementsByTagName('head')[0].appendChild(link);
|
</div>
|
||||||
};
|
);
|
||||||
|
}
|
||||||
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;
|
export default App;
|
||||||
|
|||||||
@ -17,21 +17,24 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import {Route} from 'react-router-dom';
|
import { Route } from 'react-router-dom';
|
||||||
class RouteWithSubRoutes extends React.Component{
|
class RouteWithSubRoutes extends React.Component {
|
||||||
props;
|
props;
|
||||||
constructor(props){
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
this.props = props;
|
this.props = props;
|
||||||
}
|
}
|
||||||
render() {
|
render() {
|
||||||
return(
|
return (
|
||||||
<Route path={this.props.path} exact={this.props.exact} render={(props) => (
|
<Route
|
||||||
<this.props.component {...props} routes={this.props.routes}/>
|
path={this.props.path}
|
||||||
)}/>
|
exact={this.props.exact}
|
||||||
);
|
render={props => (
|
||||||
}
|
<this.props.component {...props} routes={this.props.routes} />
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default RouteWithSubRoutes;
|
export default RouteWithSubRoutes;
|
||||||
@ -16,124 +16,161 @@
|
|||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from "react";
|
import React from 'react';
|
||||||
import {Row, Typography, Icon, notification} from "antd";
|
import { Row, Typography, Icon } from 'antd';
|
||||||
import StarRatings from "react-star-ratings";
|
import StarRatings from 'react-star-ratings';
|
||||||
import "./DetailedRating.css";
|
import './DetailedRating.css';
|
||||||
import axios from "axios";
|
import axios from 'axios';
|
||||||
import {withConfigContext} from "../../../context/ConfigContext";
|
import { withConfigContext } from '../../../context/ConfigContext';
|
||||||
import {handleApiError} from "../../../js/Utils";
|
import { handleApiError } from '../../../js/Utils';
|
||||||
|
|
||||||
const { Text } = Typography;
|
const { Text } = Typography;
|
||||||
|
|
||||||
|
class DetailedRating extends React.Component {
|
||||||
class DetailedRating extends React.Component{
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
constructor(props){
|
this.state = {
|
||||||
super(props);
|
detailedRating: null,
|
||||||
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() {
|
componentDidMount() {
|
||||||
const detailedRating = this.state.detailedRating;
|
const { type, uuid } = this.props;
|
||||||
|
this.getData(type, uuid);
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidUpdate(prevProps, prevState) {
|
||||||
if(detailedRating ==null){
|
if (prevProps.uuid !== this.props.uuid) {
|
||||||
return null;
|
const { type, uuid } = this.props;
|
||||||
}
|
this.getData(type, uuid);
|
||||||
|
|
||||||
const totalCount = detailedRating.noOfUsers;
|
|
||||||
const ratingVariety = detailedRating.ratingVariety;
|
|
||||||
|
|
||||||
const ratingArray = [];
|
|
||||||
|
|
||||||
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"><Icon type="team" /> {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>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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">
|
||||||
|
<Icon type="team" /> {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);
|
export default withConfigContext(DetailedRating);
|
||||||
File diff suppressed because it is too large
Load Diff
@ -16,314 +16,327 @@
|
|||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from "react";
|
import React from 'react';
|
||||||
import {
|
import {
|
||||||
Card,
|
Card,
|
||||||
Col,
|
Col,
|
||||||
Row,
|
Row,
|
||||||
Typography,
|
Typography,
|
||||||
Input,
|
Divider,
|
||||||
Divider,
|
Select,
|
||||||
Icon,
|
Button,
|
||||||
Select,
|
Form,
|
||||||
Button,
|
Alert,
|
||||||
Form,
|
} from 'antd';
|
||||||
message,
|
import axios from 'axios';
|
||||||
Radio,
|
import { withConfigContext } from '../../../context/ConfigContext';
|
||||||
notification, Alert
|
import { handleApiError } from '../../../js/Utils';
|
||||||
} from "antd";
|
|
||||||
import axios from "axios";
|
|
||||||
import {withConfigContext} from "../../../context/ConfigContext";
|
|
||||||
import {handleApiError} from "../../../js/Utils";
|
|
||||||
|
|
||||||
const {Option} = Select;
|
|
||||||
const {Title} = Typography;
|
|
||||||
|
|
||||||
|
const { Option } = Select;
|
||||||
|
const { Title } = Typography;
|
||||||
|
|
||||||
class FiltersForm extends React.Component {
|
class FiltersForm extends React.Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
this.state = {
|
this.state = {
|
||||||
categories: [],
|
categories: [],
|
||||||
tags: [],
|
tags: [],
|
||||||
deviceTypes: [],
|
deviceTypes: [],
|
||||||
forbiddenErrors: {
|
forbiddenErrors: {
|
||||||
categories: false,
|
categories: false,
|
||||||
tags: false,
|
tags: false,
|
||||||
deviceTypes: false
|
deviceTypes: false,
|
||||||
}
|
},
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
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() {
|
handleSubmit = e => {
|
||||||
this.getCategories();
|
e.preventDefault();
|
||||||
this.getTags();
|
this.props.form.validateFields((err, values) => {
|
||||||
this.getDeviceTypes();
|
for (const [key, value] of Object.entries(values)) {
|
||||||
}
|
if (value === undefined) {
|
||||||
|
delete values[key];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
getCategories = () => {
|
if (values.hasOwnProperty('deviceType') && values.deviceType === 'ALL') {
|
||||||
const config = this.props.context;
|
delete values.deviceType;
|
||||||
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) => {
|
if (values.hasOwnProperty('appType') && values.appType === 'ALL') {
|
||||||
handleApiError(error, "Error occurred while trying to load categories.", true);
|
delete values.appType;
|
||||||
if (error.hasOwnProperty("response") && error.response.status === 403) {
|
}
|
||||||
const {forbiddenErrors} = this.state;
|
|
||||||
forbiddenErrors.categories = true;
|
|
||||||
this.setState({
|
|
||||||
forbiddenErrors,
|
|
||||||
loading: false
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
this.setState({
|
|
||||||
loading: false
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
getTags = () => {
|
this.props.setFilters(values);
|
||||||
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) => {
|
componentDidMount() {
|
||||||
handleApiError(error, "Error occurred while trying to load tags.", true);
|
this.getCategories();
|
||||||
if (error.hasOwnProperty("response") && error.response.status === 403) {
|
this.getTags();
|
||||||
const {forbiddenErrors} = this.state;
|
this.getDeviceTypes();
|
||||||
forbiddenErrors.tags = true;
|
}
|
||||||
this.setState({
|
|
||||||
forbiddenErrors,
|
|
||||||
loading: false
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
this.setState({
|
|
||||||
loading: false
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
|
getCategories = () => {
|
||||||
getDeviceTypes = () => {
|
const config = this.props.context;
|
||||||
const config = this.props.context;
|
axios
|
||||||
axios.get(
|
.get(
|
||||||
window.location.origin + config.serverConfig.invoker.uri + config.serverConfig.invoker.deviceMgt + "/device-types"
|
window.location.origin +
|
||||||
).then(res => {
|
config.serverConfig.invoker.uri +
|
||||||
if (res.status === 200) {
|
config.serverConfig.invoker.publisher +
|
||||||
const deviceTypes = JSON.parse(res.data.data);
|
'/applications/categories',
|
||||||
this.setState({
|
)
|
||||||
deviceTypes,
|
.then(res => {
|
||||||
loading: false,
|
if (res.status === 200) {
|
||||||
});
|
let categories = JSON.parse(res.data.data);
|
||||||
}
|
this.setState({
|
||||||
|
categories: categories,
|
||||||
}).catch((error) => {
|
loading: false,
|
||||||
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;
|
.catch(error => {
|
||||||
this.setState({
|
handleApiError(
|
||||||
forbiddenErrors,
|
error,
|
||||||
loading: false
|
'Error occurred while trying to load categories.',
|
||||||
})
|
true,
|
||||||
} else {
|
|
||||||
this.setState({
|
|
||||||
loading: false
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const {categories, tags, deviceTypes, forbiddenErrors} = 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>
|
|
||||||
{(forbiddenErrors.categories) && (
|
|
||||||
<Alert
|
|
||||||
message="You don't have permission to view categories."
|
|
||||||
type="warning"
|
|
||||||
banner
|
|
||||||
closable/>
|
|
||||||
)}
|
|
||||||
<Form.Item label="Categories">
|
|
||||||
{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>
|
|
||||||
|
|
||||||
{(forbiddenErrors.deviceTypes) && (
|
|
||||||
<Alert
|
|
||||||
message="You don't have permission to view device types."
|
|
||||||
type="warning"
|
|
||||||
banner
|
|
||||||
closable/>
|
|
||||||
)}
|
|
||||||
<Form.Item label="Device Type">
|
|
||||||
{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>
|
|
||||||
{(forbiddenErrors.tags) && (
|
|
||||||
<Alert
|
|
||||||
message="You don't have permission to view tags."
|
|
||||||
type="warning"
|
|
||||||
banner
|
|
||||||
closable/>
|
|
||||||
)}
|
|
||||||
<Form.Item label="Tags">
|
|
||||||
{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>
|
|
||||||
|
|
||||||
<Form.Item label="App Type">
|
|
||||||
{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>
|
|
||||||
);
|
);
|
||||||
}
|
if (error.hasOwnProperty('response') && error.response.status === 403) {
|
||||||
|
const { forbiddenErrors } = this.state;
|
||||||
|
forbiddenErrors.categories = true;
|
||||||
|
this.setState({
|
||||||
|
forbiddenErrors,
|
||||||
|
loading: false,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
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,
|
||||||
|
);
|
||||||
|
if (error.hasOwnProperty('response') && error.response.status === 403) {
|
||||||
|
const { forbiddenErrors } = this.state;
|
||||||
|
forbiddenErrors.tags = true;
|
||||||
|
this.setState({
|
||||||
|
forbiddenErrors,
|
||||||
|
loading: false,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
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,
|
||||||
|
);
|
||||||
|
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,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { categories, tags, deviceTypes, forbiddenErrors } = 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>
|
||||||
|
{forbiddenErrors.categories && (
|
||||||
|
<Alert
|
||||||
|
message="You don't have permission to view categories."
|
||||||
|
type="warning"
|
||||||
|
banner
|
||||||
|
closable
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<Form.Item label="Categories">
|
||||||
|
{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>
|
||||||
|
|
||||||
|
{forbiddenErrors.deviceTypes && (
|
||||||
|
<Alert
|
||||||
|
message="You don't have permission to view device types."
|
||||||
|
type="warning"
|
||||||
|
banner
|
||||||
|
closable
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<Form.Item label="Device Type">
|
||||||
|
{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>
|
||||||
|
{forbiddenErrors.tags && (
|
||||||
|
<Alert
|
||||||
|
message="You don't have permission to view tags."
|
||||||
|
type="warning"
|
||||||
|
banner
|
||||||
|
closable
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<Form.Item label="Tags">
|
||||||
|
{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>
|
||||||
|
|
||||||
|
<Form.Item label="App Type">
|
||||||
|
{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(
|
||||||
const Filters = withConfigContext(Form.create({name: 'filter-apps'})(FiltersForm));
|
Form.create({ name: 'filter-apps' })(FiltersForm),
|
||||||
|
);
|
||||||
|
|
||||||
export default withConfigContext(Filters);
|
export default withConfigContext(Filters);
|
||||||
@ -16,89 +16,87 @@
|
|||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from "react";
|
import React from 'react';
|
||||||
import {Card, Col, Row, Typography, Input, Divider, notification} from "antd";
|
import { Card, Col, Row, Typography, Input, Divider } from 'antd';
|
||||||
import AppsTable from "./appsTable/AppsTable";
|
import AppsTable from './appsTable/AppsTable';
|
||||||
import Filters from "./Filters";
|
import Filters from './Filters';
|
||||||
import AppDetailsDrawer from "./AppDetailsDrawer/AppDetailsDrawer";
|
|
||||||
import axios from "axios";
|
|
||||||
|
|
||||||
const {Title} = Typography;
|
const { Title } = Typography;
|
||||||
const Search = Input.Search;
|
const Search = Input.Search;
|
||||||
|
|
||||||
class ListApps extends React.Component {
|
class ListApps extends React.Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
this.state = {
|
this.state = {
|
||||||
filters: {}
|
filters: {},
|
||||||
};
|
};
|
||||||
this.appName = '';
|
this.appName = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
setFilters = filters => {
|
||||||
|
if (this.appName === '' && filters.hasOwnProperty('appName')) {
|
||||||
|
delete filters.appName;
|
||||||
|
} else {
|
||||||
|
filters.appName = this.appName;
|
||||||
}
|
}
|
||||||
|
this.setState({
|
||||||
|
filters,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
setFilters = (filters) => {
|
setSearchText = appName => {
|
||||||
if (this.appName === '' && filters.hasOwnProperty("appName")) {
|
const filters = { ...this.state.filters };
|
||||||
delete filters["appName"];
|
this.appName = appName;
|
||||||
} else {
|
if (appName === '' && filters.hasOwnProperty('appName')) {
|
||||||
filters.appName = this.appName;
|
delete filters.appName;
|
||||||
}
|
} else {
|
||||||
this.setState({
|
filters.appName = appName;
|
||||||
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 {isDrawerVisible, 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>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
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 ListApps;
|
export default ListApps;
|
||||||
|
|||||||
@ -16,292 +16,321 @@
|
|||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from "react";
|
import React from 'react';
|
||||||
import {Avatar, Table, Tag, Icon, message, notification, Col, Badge, Alert, Tooltip} from "antd";
|
import { Avatar, Table, Tag, Icon, Badge, Alert, Tooltip } from 'antd';
|
||||||
import axios from "axios";
|
import axios from 'axios';
|
||||||
import pSBC from 'shade-blend-color';
|
import pSBC from 'shade-blend-color';
|
||||||
import "./AppsTable.css";
|
import './AppsTable.css';
|
||||||
import {withConfigContext} from "../../../../context/ConfigContext";
|
import { withConfigContext } from '../../../../context/ConfigContext';
|
||||||
import AppDetailsDrawer from "../AppDetailsDrawer/AppDetailsDrawer";
|
import AppDetailsDrawer from '../AppDetailsDrawer/AppDetailsDrawer';
|
||||||
import {handleApiError} from "../../../../js/Utils";
|
import { handleApiError } from '../../../../js/Utils';
|
||||||
|
|
||||||
let config = null;
|
let config = null;
|
||||||
|
|
||||||
const columns = [
|
const columns = [
|
||||||
{
|
{
|
||||||
title: '',
|
title: '',
|
||||||
dataIndex: 'name',
|
dataIndex: 'name',
|
||||||
render: (name, row) => {
|
// eslint-disable-next-line react/display-name
|
||||||
let avatar = null;
|
render: (name, row) => {
|
||||||
if (row.applicationReleases.length === 0) {
|
let avatar = null;
|
||||||
const avatarLetter = name.charAt(0).toUpperCase();
|
if (row.applicationReleases.length === 0) {
|
||||||
avatar = (
|
const avatarLetter = name.charAt(0).toUpperCase();
|
||||||
<Avatar shape="square" size="large"
|
avatar = (
|
||||||
style={{
|
<Avatar
|
||||||
marginRight: 20,
|
shape="square"
|
||||||
borderRadius: "28%",
|
size="large"
|
||||||
border: "1px solid #ddd",
|
style={{
|
||||||
backgroundColor: pSBC(0.50, config.theme.primaryColor)
|
marginRight: 20,
|
||||||
}}>
|
borderRadius: '28%',
|
||||||
{avatarLetter}
|
border: '1px solid #ddd',
|
||||||
</Avatar>
|
backgroundColor: pSBC(0.5, config.theme.primaryColor),
|
||||||
);
|
}}
|
||||||
} else {
|
>
|
||||||
const {applicationReleases} = row;
|
{avatarLetter}
|
||||||
let hasPublishedRelease = false;
|
</Avatar>
|
||||||
for (let i = 0; i < applicationReleases.length; i++) {
|
);
|
||||||
if (applicationReleases[i].currentStatus === "PUBLISHED") {
|
} else {
|
||||||
hasPublishedRelease = true;
|
const { applicationReleases } = row;
|
||||||
break;
|
let hasPublishedRelease = false;
|
||||||
}
|
for (let i = 0; i < applicationReleases.length; i++) {
|
||||||
}
|
if (applicationReleases[i].currentStatus === 'PUBLISHED') {
|
||||||
avatar = (hasPublishedRelease) ? (
|
hasPublishedRelease = true;
|
||||||
<Badge
|
break;
|
||||||
title="Published"
|
}
|
||||||
style={{backgroundColor: '#52c41a', borderRadius: "50%", color: "white"}}
|
}
|
||||||
count={
|
avatar = hasPublishedRelease ? (
|
||||||
<Tooltip
|
<Badge
|
||||||
title="Published">
|
title="Published"
|
||||||
<Icon
|
style={{
|
||||||
style={{
|
backgroundColor: '#52c41a',
|
||||||
backgroundColor: '#52c41a',
|
borderRadius: '50%',
|
||||||
borderRadius: "50%",
|
color: 'white',
|
||||||
color: "white"
|
}}
|
||||||
}}
|
count={
|
||||||
type="check-circle"/>
|
<Tooltip title="Published">
|
||||||
</Tooltip>
|
<Icon
|
||||||
}>
|
style={{
|
||||||
<Avatar shape="square" size="large"
|
backgroundColor: '#52c41a',
|
||||||
style={{
|
borderRadius: '50%',
|
||||||
borderRadius: "28%",
|
color: 'white',
|
||||||
border: "1px solid #ddd"
|
}}
|
||||||
}}
|
type="check-circle"
|
||||||
src={row.applicationReleases[0].iconPath}
|
/>
|
||||||
/>
|
</Tooltip>
|
||||||
</Badge>
|
|
||||||
) : (
|
|
||||||
<Avatar shape="square" size="large"
|
|
||||||
style={{
|
|
||||||
borderRadius: "28%",
|
|
||||||
border: "1px solid #ddd"
|
|
||||||
}}
|
|
||||||
src={row.applicationReleases[0].iconPath}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
>
|
||||||
|
<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 (
|
return (
|
||||||
<div>
|
<div>
|
||||||
{avatar}
|
{avatar}
|
||||||
<span style={{marginLeft: 20}}>{name}</span>
|
<span style={{ marginLeft: 20 }}>{name}</span>
|
||||||
</div>);
|
</div>
|
||||||
}
|
);
|
||||||
},
|
},
|
||||||
{
|
},
|
||||||
title: 'Categories',
|
{
|
||||||
dataIndex: 'categories',
|
title: 'Categories',
|
||||||
render: categories => (
|
dataIndex: 'categories',
|
||||||
<span>
|
// eslint-disable-next-line react/display-name
|
||||||
{categories.map(category => {
|
render: categories => (
|
||||||
return (
|
<span>
|
||||||
<Tag
|
{categories.map(category => {
|
||||||
style={{marginBottom: 8}}
|
return (
|
||||||
color={pSBC(0.30, config.theme.primaryColor)}
|
<Tag
|
||||||
key={category}>
|
style={{ marginBottom: 8 }}
|
||||||
{category}
|
color={pSBC(0.3, config.theme.primaryColor)}
|
||||||
</Tag>
|
key={category}
|
||||||
);
|
>
|
||||||
})}
|
{category}
|
||||||
</span>
|
</Tag>
|
||||||
)
|
);
|
||||||
},
|
})}
|
||||||
{
|
</span>
|
||||||
title: 'Platform',
|
),
|
||||||
dataIndex: 'deviceType',
|
},
|
||||||
render: platform => {
|
{
|
||||||
const defaultPlatformIcons = config.defaultPlatformIcons;
|
title: 'Platform',
|
||||||
let icon = defaultPlatformIcons.default.icon;
|
dataIndex: 'deviceType',
|
||||||
let color = defaultPlatformIcons.default.color;
|
// eslint-disable-next-line react/display-name
|
||||||
let theme = defaultPlatformIcons.default.theme;
|
render: platform => {
|
||||||
if (defaultPlatformIcons.hasOwnProperty(platform)) {
|
const defaultPlatformIcons = config.defaultPlatformIcons;
|
||||||
icon = defaultPlatformIcons[platform].icon;
|
let icon = defaultPlatformIcons.default.icon;
|
||||||
color = defaultPlatformIcons[platform].color;
|
let color = defaultPlatformIcons.default.color;
|
||||||
theme = defaultPlatformIcons[platform].theme;
|
let theme = defaultPlatformIcons.default.theme;
|
||||||
}
|
if (defaultPlatformIcons.hasOwnProperty(platform)) {
|
||||||
return (
|
icon = defaultPlatformIcons[platform].icon;
|
||||||
<span style={{fontSize: 20, color: color, textAlign: "center"}}>
|
color = defaultPlatformIcons[platform].color;
|
||||||
<Icon type={icon} theme={theme}/>
|
theme = defaultPlatformIcons[platform].theme;
|
||||||
</span>
|
}
|
||||||
);
|
return (
|
||||||
}
|
<span style={{ fontSize: 20, color: color, textAlign: 'center' }}>
|
||||||
},
|
<Icon type={icon} theme={theme} />
|
||||||
{
|
</span>
|
||||||
title: 'Type',
|
);
|
||||||
dataIndex: 'type'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'Subscription',
|
|
||||||
dataIndex: 'subMethod'
|
|
||||||
},
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Type',
|
||||||
|
dataIndex: 'type',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Subscription',
|
||||||
|
dataIndex: 'subMethod',
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
class AppsTable extends React.Component {
|
class AppsTable extends React.Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
this.state = {
|
this.state = {
|
||||||
pagination: {},
|
pagination: {},
|
||||||
apps: [],
|
apps: [],
|
||||||
filters: {},
|
filters: {},
|
||||||
isDrawerVisible: false,
|
isDrawerVisible: false,
|
||||||
selectedApp: null,
|
selectedApp: null,
|
||||||
selectedAppIndex: -1,
|
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,
|
loading: false,
|
||||||
isForbiddenErrorVisible: false
|
apps: apps,
|
||||||
};
|
pagination,
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
|
.catch(error => {
|
||||||
//handler to show app drawer
|
handleApiError(
|
||||||
showDrawer = (app, appIndex) => {
|
error,
|
||||||
this.setState({
|
'Error occurred while trying to load apps.',
|
||||||
isDrawerVisible: true,
|
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>
|
|
||||||
);
|
);
|
||||||
}
|
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);
|
export default withConfigContext(AppsTable);
|
||||||
@ -16,158 +16,173 @@
|
|||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from "react";
|
import React from 'react';
|
||||||
import {Divider, Row, Col, Typography, Button, Drawer, Icon, Tooltip, Empty} from "antd";
|
import { Divider, Row, Col, Typography, Button, Icon, Tooltip } from 'antd';
|
||||||
import StarRatings from "react-star-ratings";
|
import StarRatings from 'react-star-ratings';
|
||||||
import Reviews from "./review/Reviews";
|
import Reviews from './review/Reviews';
|
||||||
import "../../../App.css";
|
import '../../../App.css';
|
||||||
import DetailedRating from "../detailed-rating/DetailedRating";
|
import DetailedRating from '../detailed-rating/DetailedRating';
|
||||||
import EditRelease from "./edit-release/EditRelease";
|
import EditRelease from './edit-release/EditRelease';
|
||||||
import {withConfigContext} from "../../../context/ConfigContext";
|
import { withConfigContext } from '../../../context/ConfigContext';
|
||||||
import NewAppUploadForm from "../../new-app/subForms/NewAppUploadForm";
|
|
||||||
|
|
||||||
const {Title, Text, Paragraph} = Typography;
|
const { Title, Text, Paragraph } = Typography;
|
||||||
|
|
||||||
class ReleaseView extends React.Component {
|
class ReleaseView extends React.Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
this.state = {
|
this.state = {};
|
||||||
|
}
|
||||||
|
|
||||||
}
|
componentDidMount() {
|
||||||
|
console.log('mounted: Release view');
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { app, release } = this.props;
|
||||||
|
const config = this.props.context;
|
||||||
|
const { lifecycle, currentLifecycleStatus } = this.props;
|
||||||
|
|
||||||
|
if (release == null || lifecycle == null) {
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
const { isAppUpdatable, isAppInstallable } = lifecycle[
|
||||||
console.log("mounted: Release view");
|
currentLifecycleStatus
|
||||||
|
];
|
||||||
|
|
||||||
|
const platform = app.deviceType;
|
||||||
|
const defaultPlatformIcons = config.defaultPlatformIcons;
|
||||||
|
let icon = defaultPlatformIcons.default.icon;
|
||||||
|
let color = defaultPlatformIcons.default.color;
|
||||||
|
let theme = defaultPlatformIcons.default.theme;
|
||||||
|
|
||||||
|
if (defaultPlatformIcons.hasOwnProperty(platform)) {
|
||||||
|
icon = defaultPlatformIcons[platform].icon;
|
||||||
|
color = defaultPlatformIcons[platform].color;
|
||||||
|
theme = defaultPlatformIcons[platform].theme;
|
||||||
|
}
|
||||||
|
let metaData = [];
|
||||||
|
try {
|
||||||
|
metaData = JSON.parse(release.metaData);
|
||||||
|
} catch (e) {
|
||||||
|
console.log(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
return (
|
||||||
const {app, release} = this.props;
|
<div>
|
||||||
const config = this.props.context;
|
<div className="release">
|
||||||
const {lifecycle, currentLifecycleStatus} = this.props;
|
<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' }}>
|
||||||
|
<Icon type={icon} theme={theme} />
|
||||||
|
</span>
|
||||||
|
<Divider type="vertical" />
|
||||||
|
<Text>Version : {release.version}</Text>
|
||||||
|
<br />
|
||||||
|
|
||||||
if (release == null || lifecycle == null) {
|
<EditRelease
|
||||||
return null;
|
forbiddenErrors={this.props.forbiddenErrors}
|
||||||
}
|
isAppUpdatable={isAppUpdatable}
|
||||||
|
type={app.type}
|
||||||
const {isAppUpdatable, isAppInstallable} = lifecycle[currentLifecycleStatus];
|
deviceType={app.deviceType}
|
||||||
|
release={release}
|
||||||
const platform = app.deviceType;
|
updateRelease={this.props.updateRelease}
|
||||||
const defaultPlatformIcons = config.defaultPlatformIcons;
|
supportedOsVersions={[...this.props.supportedOsVersions]}
|
||||||
let icon = defaultPlatformIcons.default.icon;
|
/>
|
||||||
let color = defaultPlatformIcons.default.color;
|
</Col>
|
||||||
let theme = defaultPlatformIcons.default.theme;
|
<Col xl={8} md={10} sm={24} xs={24} style={{ float: 'right' }}>
|
||||||
|
<div>
|
||||||
if (defaultPlatformIcons.hasOwnProperty(platform)) {
|
<Tooltip
|
||||||
icon = defaultPlatformIcons[platform].icon;
|
title={
|
||||||
color = defaultPlatformIcons[platform].color;
|
isAppInstallable
|
||||||
theme = defaultPlatformIcons[platform].theme;
|
? 'Open this app in store'
|
||||||
}
|
: "This release isn't in an installable state"
|
||||||
let metaData = [];
|
}
|
||||||
try{
|
>
|
||||||
metaData = JSON.parse(release.metaData);
|
<Button
|
||||||
}catch (e) {
|
style={{ float: 'right' }}
|
||||||
|
htmlType="button"
|
||||||
}
|
type="primary"
|
||||||
|
icon="shop"
|
||||||
return (
|
disabled={!isAppInstallable}
|
||||||
<div>
|
onClick={() => {
|
||||||
<div className="release">
|
window.open(
|
||||||
<Row>
|
window.location.origin +
|
||||||
<Col xl={4} sm={6} xs={8} className="release-icon">
|
'/store/' +
|
||||||
<img src={release.iconPath} alt="icon"/>
|
app.deviceType +
|
||||||
</Col>
|
'/apps/' +
|
||||||
<Col xl={10} sm={11} className="release-title">
|
release.uuid,
|
||||||
<Title level={2}>{app.name}</Title>
|
);
|
||||||
<StarRatings
|
}}
|
||||||
rating={release.rating}
|
>
|
||||||
starRatedColor="#777"
|
Open in store
|
||||||
starDimension="20px"
|
</Button>
|
||||||
starSpacing="2px"
|
</Tooltip>
|
||||||
numberOfStars={5}
|
</div>
|
||||||
name='rating'
|
</Col>
|
||||||
/>
|
</Row>
|
||||||
<br/>
|
<Divider />
|
||||||
<Text>Platform : </Text>
|
<Row className="release-images">
|
||||||
<span style={{fontSize: 20, color: color, textAlign: "center"}}>
|
{release.screenshots.map((screenshotUrl, index) => {
|
||||||
<Icon
|
return (
|
||||||
type={icon}
|
<div key={index} className="release-screenshot">
|
||||||
theme={theme}
|
<img key={screenshotUrl} src={screenshotUrl} />
|
||||||
/>
|
|
||||||
</span>
|
|
||||||
<Divider type="vertical"/>
|
|
||||||
<Text>Version : {release.version}</Text><br/>
|
|
||||||
|
|
||||||
<EditRelease
|
|
||||||
forbiddenErrors={this.props.forbiddenErrors}
|
|
||||||
isAppUpdatable={isAppUpdatable}
|
|
||||||
type={app.type}
|
|
||||||
deviceType={app.deviceType}
|
|
||||||
release={release}
|
|
||||||
updateRelease={this.props.updateRelease}
|
|
||||||
supportedOsVersions={[...this.props.supportedOsVersions]}
|
|
||||||
/>
|
|
||||||
|
|
||||||
</Col>
|
|
||||||
<Col xl={8} md={10} sm={24} xs={24} style={{float: "right"}}>
|
|
||||||
<div>
|
|
||||||
<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="shop"
|
|
||||||
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">
|
|
||||||
{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)=>{
|
|
||||||
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) && (<Text type="secondary">No meta data available.</Text>)}
|
|
||||||
</Row>
|
|
||||||
<Divider/>
|
|
||||||
<Text>REVIEWS</Text>
|
|
||||||
<Row>
|
|
||||||
<Col lg={18}>
|
|
||||||
<DetailedRating type="release" uuid={release.uuid}/>
|
|
||||||
</Col>
|
|
||||||
</Row>
|
|
||||||
<Reviews type="release" uuid={release.uuid}/>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
);
|
||||||
);
|
})}
|
||||||
}
|
</Row>
|
||||||
|
<Divider />
|
||||||
|
<Paragraph type="secondary" ellipsis={{ rows: 3, expandable: true }}>
|
||||||
|
{release.description}
|
||||||
|
</Paragraph>
|
||||||
|
<Divider />
|
||||||
|
<Text>META DATA</Text>
|
||||||
|
<Row>
|
||||||
|
{metaData.map((data, index) => {
|
||||||
|
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 && (
|
||||||
|
<Text type="secondary">No meta data available.</Text>
|
||||||
|
)}
|
||||||
|
</Row>
|
||||||
|
<Divider />
|
||||||
|
<Text>REVIEWS</Text>
|
||||||
|
<Row>
|
||||||
|
<Col lg={18}>
|
||||||
|
<DetailedRating type="release" uuid={release.uuid} />
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
<Reviews type="release" uuid={release.uuid} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default withConfigContext(ReleaseView);
|
export default withConfigContext(ReleaseView);
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@ -16,201 +16,231 @@
|
|||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from "react";
|
import React from 'react';
|
||||||
import {Typography, Tag, Divider, Select, Button, Modal, message, notification, Collapse} from "antd";
|
import {
|
||||||
import axios from "axios";
|
Typography,
|
||||||
|
Tag,
|
||||||
|
Divider,
|
||||||
|
Select,
|
||||||
|
Button,
|
||||||
|
Modal,
|
||||||
|
notification,
|
||||||
|
} from 'antd';
|
||||||
|
import axios from 'axios';
|
||||||
import ReactQuill from 'react-quill';
|
import ReactQuill from 'react-quill';
|
||||||
import 'react-quill/dist/quill.snow.css';
|
import 'react-quill/dist/quill.snow.css';
|
||||||
import './LifeCycle.css';
|
import './LifeCycle.css';
|
||||||
import LifeCycleDetailsModal from "./lifeCycleDetailsModal/lifeCycleDetailsModal";
|
import LifeCycleDetailsModal from './lifeCycleDetailsModal/lifeCycleDetailsModal';
|
||||||
import {withConfigContext} from "../../../../context/ConfigContext";
|
import { withConfigContext } from '../../../../context/ConfigContext';
|
||||||
import {handleApiError} from "../../../../js/Utils";
|
import { handleApiError } from '../../../../js/Utils';
|
||||||
|
|
||||||
const {Text, Title, Paragraph} = Typography;
|
const { Text, Title, Paragraph } = Typography;
|
||||||
const {Option} = Select;
|
const { Option } = Select;
|
||||||
|
|
||||||
const modules = {
|
const modules = {
|
||||||
toolbar: [
|
toolbar: [
|
||||||
[{'header': [1, 2, false]}],
|
[{ header: [1, 2, false] }],
|
||||||
['bold', 'italic', 'underline', 'strike', 'blockquote', 'code-block'],
|
['bold', 'italic', 'underline', 'strike', 'blockquote', 'code-block'],
|
||||||
[{'list': 'ordered'}, {'list': 'bullet'}],
|
[{ list: 'ordered' }, { list: 'bullet' }],
|
||||||
['link', 'image']
|
['link', 'image'],
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
const formats = [
|
const formats = [
|
||||||
'header',
|
'header',
|
||||||
'bold', 'italic', 'underline', 'strike', 'blockquote', 'code-block',
|
'bold',
|
||||||
'list', 'bullet',
|
'italic',
|
||||||
'link', 'image'
|
'underline',
|
||||||
|
'strike',
|
||||||
|
'blockquote',
|
||||||
|
'code-block',
|
||||||
|
'list',
|
||||||
|
'bullet',
|
||||||
|
'link',
|
||||||
|
'image',
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
||||||
class LifeCycle extends React.Component {
|
class LifeCycle extends React.Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
currentStatus: props.currentStatus,
|
||||||
|
selectedStatus: null,
|
||||||
|
reasonText: '',
|
||||||
|
isReasonModalVisible: false,
|
||||||
|
isConfirmButtonLoading: false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
constructor(props) {
|
componentDidUpdate(prevProps, prevState, snapshot) {
|
||||||
super(props);
|
if (
|
||||||
this.state = {
|
prevProps.currentStatus !== this.props.currentStatus ||
|
||||||
currentStatus: props.currentStatus,
|
prevProps.uuid !== this.props.uuid
|
||||||
|
) {
|
||||||
|
this.setState({
|
||||||
|
currentStatus: this.props.currentStatus,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handleChange = value => {
|
||||||
|
this.setState({ reasonText: value });
|
||||||
|
};
|
||||||
|
|
||||||
|
handleSelectChange = value => {
|
||||||
|
this.setState({ selectedStatus: value });
|
||||||
|
};
|
||||||
|
|
||||||
|
showReasonModal = () => {
|
||||||
|
this.setState({
|
||||||
|
isReasonModalVisible: true,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
closeReasonModal = () => {
|
||||||
|
this.setState({
|
||||||
|
isReasonModalVisible: false,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
addLifeCycle = () => {
|
||||||
|
const config = this.props.context;
|
||||||
|
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({
|
||||||
|
isReasonModalVisible: false,
|
||||||
|
isConfirmButtonLoading: false,
|
||||||
|
currentStatus: selectedStatus,
|
||||||
selectedStatus: null,
|
selectedStatus: null,
|
||||||
reasonText: '',
|
reasonText: '',
|
||||||
isReasonModalVisible: false,
|
});
|
||||||
isConfirmButtonLoading: false
|
this.props.changeCurrentLifecycleStatus(selectedStatus);
|
||||||
|
notification.success({
|
||||||
|
message: 'Done!',
|
||||||
|
description: 'Lifecycle state updated successfully!',
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
handleApiError(error, 'Error occurred while trying to add lifecycle');
|
||||||
|
this.setState({
|
||||||
|
isConfirmButtonLoading: false,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const {
|
||||||
|
currentStatus,
|
||||||
|
selectedStatus,
|
||||||
|
isConfirmButtonLoading,
|
||||||
|
} = this.state;
|
||||||
|
const { lifecycle } = this.props;
|
||||||
|
const selectedValue = selectedStatus == null ? [] : selectedStatus;
|
||||||
|
let proceedingStates = [];
|
||||||
|
|
||||||
|
if (
|
||||||
|
lifecycle !== null &&
|
||||||
|
lifecycle.hasOwnProperty(currentStatus) &&
|
||||||
|
lifecycle[currentStatus].hasOwnProperty('proceedingStates')
|
||||||
|
) {
|
||||||
|
proceedingStates = lifecycle[currentStatus].proceedingStates;
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidUpdate(prevProps, prevState, snapshot) {
|
return (
|
||||||
if (prevProps.currentStatus !== this.props.currentStatus || prevProps.uuid !== this.props.uuid) {
|
<div>
|
||||||
this.setState({
|
<Title level={4}>Manage Lifecycle</Title>
|
||||||
currentStatus: this.props.currentStatus
|
<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
|
||||||
handleChange = (value) => {
|
from one state to another. <br />
|
||||||
this.setState({reasonText: value})
|
Note: ‘Change State To’ displays only the next states allowed from the
|
||||||
};
|
current state
|
||||||
|
</Paragraph>
|
||||||
handleSelectChange = (value) => {
|
{lifecycle !== null && <LifeCycleDetailsModal lifecycle={lifecycle} />}
|
||||||
this.setState({selectedStatus: value})
|
<Divider dashed={true} />
|
||||||
};
|
<Text strong={true}>Current State: </Text>{' '}
|
||||||
|
<Tag color="blue">{currentStatus}</Tag>
|
||||||
showReasonModal = () => {
|
<br />
|
||||||
this.setState({
|
<br />
|
||||||
isReasonModalVisible: true
|
<Text>Change State to: </Text>
|
||||||
});
|
<Select
|
||||||
};
|
placeholder="Select state"
|
||||||
|
style={{ width: 120 }}
|
||||||
closeReasonModal = () => {
|
size="small"
|
||||||
this.setState({
|
onChange={this.handleSelectChange}
|
||||||
isReasonModalVisible: false
|
value={selectedValue}
|
||||||
});
|
showSearch={true}
|
||||||
};
|
>
|
||||||
|
{proceedingStates.map(lifecycleState => {
|
||||||
addLifeCycle = () => {
|
return (
|
||||||
const config = this.props.context;
|
<Option key={lifecycleState} value={lifecycleState}>
|
||||||
const {selectedStatus, reasonText} = this.state;
|
{lifecycleState}
|
||||||
const {uuid} = this.props;
|
</Option>
|
||||||
const data = {
|
);
|
||||||
action: selectedStatus,
|
})}
|
||||||
reason: reasonText
|
</Select>
|
||||||
};
|
<Button
|
||||||
|
style={{ marginLeft: 10 }}
|
||||||
this.setState({
|
size="small"
|
||||||
isConfirmButtonLoading: true,
|
type="primary"
|
||||||
});
|
htmlType="button"
|
||||||
|
onClick={this.showReasonModal}
|
||||||
axios.post(
|
loading={isConfirmButtonLoading}
|
||||||
window.location.origin + config.serverConfig.invoker.uri + config.serverConfig.invoker.publisher + "/applications/life-cycle/" + uuid,
|
disabled={selectedStatus == null}
|
||||||
data
|
>
|
||||||
).then(res => {
|
Change
|
||||||
if (res.status === 201) {
|
</Button>
|
||||||
this.setState({
|
<Divider />
|
||||||
isReasonModalVisible: false,
|
<Modal
|
||||||
isConfirmButtonLoading: false,
|
title="Confirm changing lifecycle state"
|
||||||
currentStatus: selectedStatus,
|
visible={this.state.isReasonModalVisible}
|
||||||
selectedStatus: null,
|
onOk={this.addLifeCycle}
|
||||||
reasonText: ''
|
onCancel={this.closeReasonModal}
|
||||||
});
|
okText="Confirm"
|
||||||
this.props.changeCurrentLifecycleStatus(selectedStatus);
|
>
|
||||||
notification["success"]({
|
<Text>
|
||||||
message: "Done!",
|
You are going to change the lifecycle state from,
|
||||||
description:
|
<br />
|
||||||
"Lifecycle state updated successfully!",
|
<Tag color="blue">{currentStatus}</Tag>to{' '}
|
||||||
});
|
<Tag color="blue">{selectedStatus}</Tag>
|
||||||
}
|
</Text>
|
||||||
|
<br />
|
||||||
}).catch((error) => {
|
<br />
|
||||||
handleApiError(error, "Error occurred while trying to add lifecycle");
|
<ReactQuill
|
||||||
this.setState({
|
theme="snow"
|
||||||
isConfirmButtonLoading: false
|
value={this.state.reasonText}
|
||||||
});
|
onChange={this.handleChange}
|
||||||
});
|
modules={modules}
|
||||||
|
formats={formats}
|
||||||
|
placeholder="Leave a comment (optional)"
|
||||||
};
|
/>
|
||||||
|
</Modal>
|
||||||
|
</div>
|
||||||
render() {
|
);
|
||||||
const {currentStatus, selectedStatus, isConfirmButtonLoading} = this.state;
|
}
|
||||||
const {lifecycle} = this.props;
|
|
||||||
const selectedValue = selectedStatus == null ? [] : selectedStatus;
|
|
||||||
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/>Note: ‘Change State To’ displays only the next states allowed from the
|
|
||||||
current state
|
|
||||||
</Paragraph>
|
|
||||||
{lifecycle !== null && (<LifeCycleDetailsModal lifecycle={lifecycle}/>)}
|
|
||||||
<Divider dashed={true}/>
|
|
||||||
<Text strong={true}>Current State: </Text> <Tag color="blue">{currentStatus}</Tag><br/><br/>
|
|
||||||
<Text>Change State to: </Text>
|
|
||||||
<Select
|
|
||||||
placeholder="Select state"
|
|
||||||
style={{width: 120}}
|
|
||||||
size="small"
|
|
||||||
onChange={this.handleSelectChange}
|
|
||||||
value={selectedValue}
|
|
||||||
showSearch={true}
|
|
||||||
>
|
|
||||||
{proceedingStates.map(lifecycleState => {
|
|
||||||
return (
|
|
||||||
<Option
|
|
||||||
key={lifecycleState}
|
|
||||||
value={lifecycleState}>
|
|
||||||
{lifecycleState}
|
|
||||||
</Option>
|
|
||||||
)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
</Select>
|
|
||||||
<Button
|
|
||||||
style={{marginLeft: 10}}
|
|
||||||
size="small"
|
|
||||||
type="primary"
|
|
||||||
htmlType="button"
|
|
||||||
onClick={this.showReasonModal}
|
|
||||||
loading={isConfirmButtonLoading}
|
|
||||||
disabled={selectedStatus == null}>
|
|
||||||
Change
|
|
||||||
</Button>
|
|
||||||
<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/><br/>
|
|
||||||
<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);
|
export default withConfigContext(LifeCycle);
|
||||||
|
|||||||
@ -16,99 +16,93 @@
|
|||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from "react";
|
import React from 'react';
|
||||||
import {Modal, Button, Tag, List, Typography} from 'antd';
|
import { Modal, Button, Tag, List, Typography } from 'antd';
|
||||||
import pSBC from "shade-blend-color";
|
import pSBC from 'shade-blend-color';
|
||||||
import {withConfigContext} from "../../../../../context/ConfigContext";
|
import { withConfigContext } from '../../../../../context/ConfigContext';
|
||||||
|
|
||||||
const {Text} = Typography;
|
const { Text } = Typography;
|
||||||
|
|
||||||
class LifeCycleDetailsModal extends React.Component {
|
class LifeCycleDetailsModal extends React.Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.state = { visible: false };
|
||||||
|
}
|
||||||
|
|
||||||
constructor(props) {
|
showModal = () => {
|
||||||
super(props);
|
this.setState({
|
||||||
this.state = {visible: false};
|
visible: true,
|
||||||
}
|
});
|
||||||
|
};
|
||||||
|
|
||||||
showModal = () => {
|
handleCancel = e => {
|
||||||
this.setState({
|
this.setState({
|
||||||
visible: true,
|
visible: false,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
handleCancel = e => {
|
render() {
|
||||||
this.setState({
|
const config = this.props.context;
|
||||||
visible: false,
|
const lifeCycleConfig = config.lifecycle;
|
||||||
});
|
const { lifecycle } = this.props;
|
||||||
};
|
return (
|
||||||
|
<div>
|
||||||
|
<Button size="small" icon="question-circle" 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 = [];
|
||||||
|
|
||||||
render() {
|
if (lifeCycleConfig.hasOwnProperty(lifecycleState)) {
|
||||||
const config = this.props.context;
|
text = lifeCycleConfig[lifecycleState].text;
|
||||||
const lifeCycleConfig = config.lifecycle;
|
}
|
||||||
const {lifecycle} = this.props;
|
if (
|
||||||
return (
|
lifecycle[lifecycleState].hasOwnProperty('proceedingStates')
|
||||||
<div>
|
) {
|
||||||
<Button
|
nextProceedingStates =
|
||||||
size="small"
|
lifecycle[lifecycleState].proceedingStates;
|
||||||
icon="question-circle"
|
footerText =
|
||||||
onClick={this.showModal}
|
'You can only proceed to one of the following states:';
|
||||||
>
|
}
|
||||||
Learn more
|
|
||||||
</Button>
|
|
||||||
<Modal
|
|
||||||
title="Lifecycle"
|
|
||||||
visible={this.state.visible}
|
|
||||||
footer={null}
|
|
||||||
onCancel={this.handleCancel}
|
|
||||||
>
|
|
||||||
|
|
||||||
<List
|
return (
|
||||||
itemLayout="horizontal"
|
<List.Item>
|
||||||
dataSource={Object.keys(lifecycle)}
|
<List.Item.Meta title={lifecycleState} />
|
||||||
renderItem={lifecycleState => {
|
{text}
|
||||||
let text = "";
|
<br />
|
||||||
let footerText = "";
|
<Text type="secondary">{footerText}</Text>
|
||||||
let nextProceedingStates = [];
|
<div>
|
||||||
|
{nextProceedingStates.map(lifecycleState => {
|
||||||
if (lifeCycleConfig.hasOwnProperty(lifecycleState)) {
|
return (
|
||||||
text = lifeCycleConfig[lifecycleState].text;
|
<Tag
|
||||||
}
|
key={lifecycleState}
|
||||||
if (lifecycle[lifecycleState].hasOwnProperty("proceedingStates")) {
|
style={{ margin: 5 }}
|
||||||
nextProceedingStates = lifecycle[lifecycleState].proceedingStates;
|
color={pSBC(0.3, config.theme.primaryColor)}
|
||||||
footerText = "You can only proceed to one of the following states:"
|
>
|
||||||
}
|
{lifecycleState}
|
||||||
|
</Tag>
|
||||||
return (
|
);
|
||||||
<List.Item>
|
})}
|
||||||
<List.Item.Meta
|
</div>
|
||||||
title={lifecycleState}
|
</List.Item>
|
||||||
/>
|
);
|
||||||
{text}
|
}}
|
||||||
<br/>
|
/>
|
||||||
<Text type="secondary">{footerText}</Text>
|
</Modal>
|
||||||
<div>
|
</div>
|
||||||
{
|
);
|
||||||
nextProceedingStates.map(lifecycleState => {
|
}
|
||||||
return (
|
|
||||||
<Tag
|
|
||||||
key={lifecycleState}
|
|
||||||
style={{margin: 5}}
|
|
||||||
color={pSBC(0.30, config.theme.primaryColor)}
|
|
||||||
>
|
|
||||||
{lifecycleState}
|
|
||||||
</Tag>
|
|
||||||
)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
</List.Item>
|
|
||||||
)
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</Modal>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default withConfigContext(LifeCycleDetailsModal);
|
export default withConfigContext(LifeCycleDetailsModal);
|
||||||
|
|||||||
@ -16,147 +16,166 @@
|
|||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from "react";
|
import React from 'react';
|
||||||
import {List, message, Avatar, Spin, Button, notification, Alert} from 'antd';
|
import { List, Spin, Button, Alert } from 'antd';
|
||||||
import "./Reviews.css";
|
import './Reviews.css';
|
||||||
|
|
||||||
import InfiniteScroll from 'react-infinite-scroller';
|
import InfiniteScroll from 'react-infinite-scroller';
|
||||||
import SingleReview from "./SingleReview";
|
import SingleReview from './SingleReview';
|
||||||
import axios from "axios";
|
import axios from 'axios';
|
||||||
import {withConfigContext} from "../../../../context/ConfigContext";
|
import { withConfigContext } from '../../../../context/ConfigContext';
|
||||||
import {handleApiError} from "../../../../js/Utils";
|
import { handleApiError } from '../../../../js/Utils';
|
||||||
|
|
||||||
const limit = 5;
|
const limit = 5;
|
||||||
|
|
||||||
class Reviews extends React.Component {
|
class Reviews extends React.Component {
|
||||||
state = {
|
state = {
|
||||||
data: [],
|
data: [],
|
||||||
loading: false,
|
loading: false,
|
||||||
hasMore: false,
|
hasMore: false,
|
||||||
loadMore: false,
|
loadMore: false,
|
||||||
forbiddenErrors: {
|
forbiddenErrors: {
|
||||||
reviews: false
|
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(
|
||||||
componentDidMount() {
|
error,
|
||||||
this.fetchData(0, limit, res => {
|
'Error occurred while trying to load reviews.',
|
||||||
this.setState({
|
true,
|
||||||
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>
|
|
||||||
{(this.state.forbiddenErrors.reviews) && (
|
|
||||||
<Alert
|
|
||||||
message="You don't have permission to view reviews."
|
|
||||||
type="warning"
|
|
||||||
banner
|
|
||||||
closable/>
|
|
||||||
)}
|
|
||||||
<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>
|
|
||||||
);
|
);
|
||||||
|
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>
|
||||||
|
{this.state.forbiddenErrors.reviews && (
|
||||||
|
<Alert
|
||||||
|
message="You don't have permission to view reviews."
|
||||||
|
type="warning"
|
||||||
|
banner
|
||||||
|
closable
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<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);
|
export default withConfigContext(Reviews);
|
||||||
|
|||||||
@ -16,49 +16,69 @@
|
|||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from "react";
|
import React from 'react';
|
||||||
import {Avatar} from "antd";
|
import { Avatar } from 'antd';
|
||||||
import {List,Typography} from "antd";
|
import { List, Typography } from 'antd';
|
||||||
import StarRatings from "react-star-ratings";
|
import StarRatings from 'react-star-ratings';
|
||||||
|
|
||||||
const {Text, Paragraph} = Typography;
|
const { Text, Paragraph } = Typography;
|
||||||
const colorList = ['#f0932b','#badc58','#6ab04c','#eb4d4b','#0abde3', '#9b59b6','#3498db','#22a6b3'];
|
const colorList = [
|
||||||
|
'#f0932b',
|
||||||
|
'#badc58',
|
||||||
|
'#6ab04c',
|
||||||
|
'#eb4d4b',
|
||||||
|
'#0abde3',
|
||||||
|
'#9b59b6',
|
||||||
|
'#3498db',
|
||||||
|
'#22a6b3',
|
||||||
|
];
|
||||||
|
|
||||||
class SingleReview extends React.Component {
|
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>
|
||||||
|
);
|
||||||
|
|
||||||
render() {
|
return (
|
||||||
const review = this.props.review;
|
<div>
|
||||||
const randomColor = colorList[Math.floor(Math.random() * (colorList.length))];
|
<List.Item.Meta
|
||||||
const avatarLetter = review.username.charAt(0).toUpperCase();
|
avatar={
|
||||||
const content = (
|
<Avatar
|
||||||
<div style={{marginTop: -5}}>
|
style={{ backgroundColor: randomColor, verticalAlign: 'middle' }}
|
||||||
<StarRatings
|
size="large"
|
||||||
rating={review.rating}
|
>
|
||||||
starRatedColor="#777"
|
{avatarLetter}
|
||||||
starDimension = "12px"
|
</Avatar>
|
||||||
starSpacing = "2px"
|
}
|
||||||
numberOfStars={5}
|
title={review.username}
|
||||||
name='rating'
|
description={content}
|
||||||
/>
|
/>
|
||||||
<Text style={{fontSize: 12, color: "#aaa"}} type="secondary"> {review.createdAt}</Text><br/>
|
</div>
|
||||||
<Paragraph ellipsis={{ rows: 3, expandable: true }} style={{color: "#777"}}>{review.content}</Paragraph>
|
);
|
||||||
</div>
|
}
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<List.Item.Meta
|
|
||||||
avatar={
|
|
||||||
<Avatar style={{ backgroundColor: randomColor, verticalAlign: 'middle' }} size="large">
|
|
||||||
{avatarLetter}
|
|
||||||
</Avatar>
|
|
||||||
}
|
|
||||||
title={review.username}
|
|
||||||
description={content}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default SingleReview;
|
export default SingleReview;
|
||||||
@ -16,109 +16,112 @@
|
|||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from "react";
|
import React from 'react';
|
||||||
import {Button, Divider, Form, Input, message, Modal, notification, Spin} from "antd";
|
import { Button, Divider, Input, Modal, notification, Spin } from 'antd';
|
||||||
import axios from "axios";
|
import axios from 'axios';
|
||||||
import {withConfigContext} from "../../../../context/ConfigContext";
|
import { withConfigContext } from '../../../../context/ConfigContext';
|
||||||
import {withRouter} from "react-router";
|
import { withRouter } from 'react-router';
|
||||||
import {handleApiError} from "../../../../js/Utils";
|
import { handleApiError } from '../../../../js/Utils';
|
||||||
|
|
||||||
class AddNewPage extends React.Component {
|
class AddNewPage extends React.Component {
|
||||||
|
state = {
|
||||||
|
visible: false,
|
||||||
|
pageName: '',
|
||||||
|
};
|
||||||
|
|
||||||
state = {
|
showModal = () => {
|
||||||
visible: false,
|
this.setState({
|
||||||
pageName: ''
|
visible: true,
|
||||||
};
|
loading: false,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
showModal = () => {
|
handleCancel = e => {
|
||||||
this.setState({
|
this.setState({
|
||||||
visible: true,
|
visible: false,
|
||||||
loading: false
|
});
|
||||||
});
|
};
|
||||||
};
|
|
||||||
|
|
||||||
|
handlePageName = e => {
|
||||||
|
this.setState({
|
||||||
|
pageName: e.target.value,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
handleCancel = e => {
|
createNewPage = () => {
|
||||||
this.setState({
|
const config = this.props.context;
|
||||||
visible: false,
|
this.setState({ loading: true });
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
handlePageName = (e) => {
|
axios
|
||||||
this.setState({
|
.post(
|
||||||
pageName: e.target.value,
|
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;
|
||||||
|
|
||||||
createNewPage = () => {
|
notification.success({
|
||||||
const config = this.props.context;
|
message: 'Saved!',
|
||||||
this.setState({loading: true});
|
description: 'Page created successfully!',
|
||||||
|
});
|
||||||
|
|
||||||
axios.post(
|
this.setState({ loading: false });
|
||||||
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;
|
this.props.history.push(
|
||||||
|
`/publisher/manage/android-enterprise/pages/${pageName}/${pageId}`,
|
||||||
notification["success"]({
|
);
|
||||||
message: 'Saved!',
|
}
|
||||||
description: 'Page created successfully!'
|
})
|
||||||
});
|
.catch(error => {
|
||||||
|
handleApiError(
|
||||||
this.setState({loading: false});
|
error,
|
||||||
|
'Error occurred while trying to update the cluster.',
|
||||||
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>
|
|
||||||
);
|
);
|
||||||
}
|
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));
|
export default withConfigContext(withRouter(AddNewPage));
|
||||||
@ -16,64 +16,68 @@
|
|||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from "react";
|
import React from 'react';
|
||||||
import {Modal, Button} from "antd";
|
import { Modal, Button } from 'antd';
|
||||||
import {withConfigContext} from "../../../context/ConfigContext";
|
import { withConfigContext } from '../../../context/ConfigContext';
|
||||||
|
|
||||||
class GooglePlayIframe extends React.Component {
|
class GooglePlayIframe extends React.Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
this.config = this.props.context;
|
this.config = this.props.context;
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
visible: false
|
visible: false,
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
showModal = () => {
|
|
||||||
this.setState({
|
|
||||||
visible: true,
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
}
|
||||||
|
|
||||||
handleOk = e => {
|
showModal = () => {
|
||||||
this.setState({
|
this.setState({
|
||||||
visible: false,
|
visible: true,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
handleCancel = e => {
|
handleOk = e => {
|
||||||
this.setState({
|
this.setState({
|
||||||
visible: false,
|
visible: false,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
render() {
|
handleCancel = e => {
|
||||||
return (
|
this.setState({
|
||||||
<div style={{display: "inline-block", padding: 4}}>
|
visible: false,
|
||||||
<Button type="primary" onClick={this.showModal}>
|
});
|
||||||
Approve Applications
|
};
|
||||||
</Button>
|
|
||||||
<Modal
|
render() {
|
||||||
title={null}
|
return (
|
||||||
visible={this.state.visible}
|
<div style={{ display: 'inline-block', padding: 4 }}>
|
||||||
onOk={this.handleOk}
|
<Button type="primary" onClick={this.showModal}>
|
||||||
onCancel={this.handleCancel}
|
Approve Applications
|
||||||
width = {740}
|
</Button>
|
||||||
footer={null}>
|
<Modal
|
||||||
<iframe
|
title={null}
|
||||||
style={{
|
visible={this.state.visible}
|
||||||
height: 720,
|
onOk={this.handleOk}
|
||||||
border: 0,
|
onCancel={this.handleCancel}
|
||||||
width: "100%"
|
width={740}
|
||||||
}}
|
footer={null}
|
||||||
src={"https://play.google.com/work/embedded/search?token=" + this.config.androidEnterpriseToken +
|
>
|
||||||
"&mode=APPROVE&showsearchbox=TRUE"}
|
<iframe
|
||||||
/>
|
style={{
|
||||||
</Modal>
|
height: 720,
|
||||||
</div>
|
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);
|
export default withConfigContext(GooglePlayIframe);
|
||||||
@ -16,183 +16,215 @@
|
|||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from "react";
|
import React from 'react';
|
||||||
import {Button, message, Modal, notification, Spin} from "antd";
|
import { Button, Modal, notification, Spin } from 'antd';
|
||||||
import axios from "axios";
|
import axios from 'axios';
|
||||||
import {withConfigContext} from "../../../../context/ConfigContext";
|
import { withConfigContext } from '../../../../context/ConfigContext';
|
||||||
import {handleApiError} from "../../../../js/Utils";
|
import { handleApiError } from '../../../../js/Utils';
|
||||||
|
|
||||||
// import gapi from 'gapi-client';
|
|
||||||
|
|
||||||
class ManagedConfigurationsIframe extends React.Component {
|
class ManagedConfigurationsIframe extends React.Component {
|
||||||
|
constructor(props) {
|
||||||
constructor(props) {
|
super(props);
|
||||||
super(props);
|
this.config = this.props.context;
|
||||||
this.config = this.props.context;
|
this.state = {
|
||||||
this.state = {
|
visible: false,
|
||||||
visible: false,
|
loading: false,
|
||||||
loading: false
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
showModal = () => {
|
|
||||||
this.getMcm();
|
|
||||||
this.setState({
|
|
||||||
visible: true,
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
}
|
||||||
|
|
||||||
handleOk = e => {
|
showModal = () => {
|
||||||
this.setState({
|
this.getMcm();
|
||||||
visible: false,
|
this.setState({
|
||||||
});
|
visible: true,
|
||||||
};
|
});
|
||||||
|
};
|
||||||
|
|
||||||
handleCancel = e => {
|
handleOk = e => {
|
||||||
this.setState({
|
this.setState({
|
||||||
visible: false,
|
visible: false,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
getMcm = () => {
|
handleCancel = e => {
|
||||||
const {packageName} = this.props;
|
this.setState({
|
||||||
this.setState({loading: true});
|
visible: false,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
//send request to the invoker
|
getMcm = () => {
|
||||||
axios.get(
|
const { packageName } = this.props;
|
||||||
window.location.origin + this.config.serverConfig.invoker.uri +
|
this.setState({ loading: true });
|
||||||
"/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) => {
|
// send request to the invoker
|
||||||
handleApiError(error, "Error occurred while trying to load configurations.");
|
axios
|
||||||
this.setState({loading: false, visible: false});
|
.get(
|
||||||
});
|
window.location.origin +
|
||||||
};
|
this.config.serverConfig.invoker.uri +
|
||||||
|
'/device-mgt/android/v1.0/enterprise/managed-configs/package/' +
|
||||||
loadIframe = (mcmId) => {
|
packageName,
|
||||||
const {packageName} = this.props;
|
)
|
||||||
let method = "post";
|
.then(res => {
|
||||||
gapi.load('gapi.iframes', () => {
|
if (res.status === 200) {
|
||||||
const parameters = {
|
let mcmId = null;
|
||||||
token: this.config.androidEnterpriseToken,
|
if (res.data.hasOwnProperty('data')) {
|
||||||
packageName: packageName
|
mcmId = res.data.data.mcmId;
|
||||||
};
|
}
|
||||||
if (mcmId != null) {
|
this.loadIframe(mcmId);
|
||||||
parameters.mcmId = mcmId;
|
this.setState({ loading: false });
|
||||||
parameters.canDelete = true;
|
}
|
||||||
method = "put";
|
})
|
||||||
}
|
.catch(error => {
|
||||||
|
handleApiError(
|
||||||
const queryString = Object.keys(parameters).map(key => key + '=' + parameters[key]).join('&');
|
error,
|
||||||
|
'Error occurred while trying to load configurations.',
|
||||||
var options = {
|
|
||||||
'url': "https://play.google.com/managed/mcm?" + queryString,
|
|
||||||
'where': document.getElementById('manage-config-iframe-container'),
|
|
||||||
'attributes': {style: 'height:720px', scrolling: 'yes'}
|
|
||||||
};
|
|
||||||
|
|
||||||
var iframe = gapi.iframes.getContext().openChild(options);
|
|
||||||
iframe.register('onconfigupdated', (event) => {
|
|
||||||
this.updateConfig(method, event);
|
|
||||||
}, gapi.iframes.CROSS_ORIGIN_IFRAMES_FILTER);
|
|
||||||
|
|
||||||
iframe.register('onconfigdeleted', (event) => {
|
|
||||||
this.deleteConfig(event);
|
|
||||||
}, 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) => {
|
|
||||||
const {packageName} = this.props;
|
|
||||||
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});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
render() {
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<Button
|
|
||||||
size="small"
|
|
||||||
type="primary"
|
|
||||||
icon="setting"
|
|
||||||
onClick={this.showModal}>
|
|
||||||
Manage
|
|
||||||
</Button>
|
|
||||||
<Modal
|
|
||||||
visible={this.state.visible}
|
|
||||||
onOk={this.handleOk}
|
|
||||||
onCancel={this.handleCancel}
|
|
||||||
footer={null}>
|
|
||||||
<Spin spinning={this.state.loading}>
|
|
||||||
<div id="manage-config-iframe-container">
|
|
||||||
</div>
|
|
||||||
</Spin>
|
|
||||||
</Modal>
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
}
|
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 });
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Button
|
||||||
|
size="small"
|
||||||
|
type="primary"
|
||||||
|
icon="setting"
|
||||||
|
onClick={this.showModal}
|
||||||
|
>
|
||||||
|
Manage
|
||||||
|
</Button>
|
||||||
|
<Modal
|
||||||
|
visible={this.state.visible}
|
||||||
|
onOk={this.handleOk}
|
||||||
|
onCancel={this.handleCancel}
|
||||||
|
footer={null}
|
||||||
|
>
|
||||||
|
<Spin spinning={this.state.loading}>
|
||||||
|
<div id="manage-config-iframe-container"></div>
|
||||||
|
</Spin>
|
||||||
|
</Modal>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default withConfigContext(ManagedConfigurationsIframe);
|
export default withConfigContext(ManagedConfigurationsIframe);
|
||||||
@ -16,107 +16,107 @@
|
|||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from "react";
|
import React from 'react';
|
||||||
import {Modal, Icon, Table, Avatar} from 'antd';
|
import { Modal, Icon, Table, Avatar } from 'antd';
|
||||||
import "../Cluster.css";
|
import '../Cluster.css';
|
||||||
import {withConfigContext} from "../../../../../../context/ConfigContext";
|
import { withConfigContext } from '../../../../../../context/ConfigContext';
|
||||||
|
|
||||||
const columns = [
|
const columns = [
|
||||||
{
|
{
|
||||||
title: '',
|
title: '',
|
||||||
dataIndex: 'iconUrl',
|
dataIndex: 'iconUrl',
|
||||||
key: 'iconUrl',
|
key: 'iconUrl',
|
||||||
render: (iconUrl) => (<Avatar shape="square" src={iconUrl}/>)
|
// eslint-disable-next-line react/display-name
|
||||||
},
|
render: iconUrl => <Avatar shape="square" src={iconUrl} />,
|
||||||
{
|
},
|
||||||
title: 'Name',
|
{
|
||||||
dataIndex: 'name',
|
title: 'Name',
|
||||||
key: 'name'
|
dataIndex: 'name',
|
||||||
},
|
key: 'name',
|
||||||
{
|
},
|
||||||
title: 'Page',
|
{
|
||||||
dataIndex: 'packageId',
|
title: 'Page',
|
||||||
key: 'packageId'
|
dataIndex: 'packageId',
|
||||||
}
|
key: 'packageId',
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
class AddAppsToClusterModal extends React.Component {
|
class AddAppsToClusterModal extends React.Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
this.state = {
|
this.state = {
|
||||||
visible: false,
|
visible: false,
|
||||||
loading: false,
|
loading: false,
|
||||||
selectedProducts: [],
|
selectedProducts: [],
|
||||||
homePageId: null
|
homePageId: null,
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
showModal = () => {
|
|
||||||
this.setState({
|
|
||||||
visible: true,
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
}
|
||||||
|
|
||||||
handleOk = () => {
|
showModal = () => {
|
||||||
this.props.addSelectedProducts(this.state.selectedProducts);
|
this.setState({
|
||||||
this.handleCancel();
|
visible: true,
|
||||||
};
|
});
|
||||||
|
};
|
||||||
|
|
||||||
handleCancel = () => {
|
handleOk = () => {
|
||||||
this.setState({
|
this.props.addSelectedProducts(this.state.selectedProducts);
|
||||||
visible: false,
|
this.handleCancel();
|
||||||
});
|
};
|
||||||
};
|
|
||||||
|
|
||||||
rowSelection = {
|
handleCancel = () => {
|
||||||
onChange: (selectedRowKeys, selectedRows) => {
|
this.setState({
|
||||||
this.setState({
|
visible: false,
|
||||||
selectedProducts: selectedRows
|
});
|
||||||
})
|
};
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
render() {
|
rowSelection = {
|
||||||
const {pagination, loading} = this.state;
|
onChange: (selectedRowKeys, selectedRows) => {
|
||||||
return (
|
this.setState({
|
||||||
<div>
|
selectedProducts: selectedRows,
|
||||||
<div className="btn-add-new-wrapper">
|
});
|
||||||
<div className="btn-add-new">
|
},
|
||||||
<button className="btn"
|
};
|
||||||
onClick={this.showModal}>
|
|
||||||
<Icon style={{position: "relative"}} type="plus"/>
|
render() {
|
||||||
</button>
|
const { pagination, loading } = this.state;
|
||||||
</div>
|
return (
|
||||||
<div className="title">
|
<div>
|
||||||
Add app
|
<div className="btn-add-new-wrapper">
|
||||||
</div>
|
<div className="btn-add-new">
|
||||||
</div>
|
<button className="btn" onClick={this.showModal}>
|
||||||
<Modal
|
<Icon style={{ position: 'relative' }} type="plus" />
|
||||||
title="Select Apps"
|
</button>
|
||||||
width={640}
|
</div>
|
||||||
visible={this.state.visible}
|
<div className="title">Add app</div>
|
||||||
onOk={this.handleOk}
|
</div>
|
||||||
onCancel={this.handleCancel}>
|
<Modal
|
||||||
<Table
|
title="Select Apps"
|
||||||
columns={columns}
|
width={640}
|
||||||
rowKey={record => record.packageId}
|
visible={this.state.visible}
|
||||||
dataSource={this.props.unselectedProducts}
|
onOk={this.handleOk}
|
||||||
scroll={{ x: 300 }}
|
onCancel={this.handleCancel}
|
||||||
pagination={{
|
>
|
||||||
...pagination,
|
<Table
|
||||||
size: "small",
|
columns={columns}
|
||||||
// position: "top",
|
rowKey={record => record.packageId}
|
||||||
showTotal: (total, range) => `showing ${range[0]}-${range[1]} of ${total} pages`,
|
dataSource={this.props.unselectedProducts}
|
||||||
showQuickJumper: true
|
scroll={{ x: 300 }}
|
||||||
}}
|
pagination={{
|
||||||
loading={loading}
|
...pagination,
|
||||||
onChange={this.handleTableChange}
|
size: 'small',
|
||||||
rowSelection={this.rowSelection}
|
// position: "top",
|
||||||
/>
|
showTotal: (total, range) =>
|
||||||
</Modal>
|
`showing ${range[0]}-${range[1]} of ${total} pages`,
|
||||||
</div>
|
showQuickJumper: true,
|
||||||
);
|
}}
|
||||||
}
|
loading={loading}
|
||||||
|
onChange={this.handleTableChange}
|
||||||
|
rowSelection={this.rowSelection}
|
||||||
|
/>
|
||||||
|
</Modal>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default withConfigContext(AddAppsToClusterModal);
|
export default withConfigContext(AddAppsToClusterModal);
|
||||||
@ -16,395 +16,444 @@
|
|||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from "react";
|
import React from 'react';
|
||||||
import {Button, Col, Divider, Icon, message, notification, Popconfirm, Row, Spin, Tooltip, Typography} from "antd";
|
import {
|
||||||
|
Button,
|
||||||
|
Col,
|
||||||
|
Divider,
|
||||||
|
Icon,
|
||||||
|
message,
|
||||||
|
notification,
|
||||||
|
Popconfirm,
|
||||||
|
Row,
|
||||||
|
Spin,
|
||||||
|
Tooltip,
|
||||||
|
Typography,
|
||||||
|
} from 'antd';
|
||||||
|
|
||||||
import "./Cluster.css";
|
import './Cluster.css';
|
||||||
import axios from "axios";
|
import axios from 'axios';
|
||||||
import {withConfigContext} from "../../../../../context/ConfigContext";
|
import { withConfigContext } from '../../../../../context/ConfigContext';
|
||||||
import AddAppsToClusterModal from "./AddAppsToClusterModal/AddAppsToClusterModal";
|
import AddAppsToClusterModal from './AddAppsToClusterModal/AddAppsToClusterModal';
|
||||||
import {handleApiError} from "../../../../../js/Utils";
|
import { handleApiError } from '../../../../../js/Utils';
|
||||||
|
|
||||||
const {Title} = Typography;
|
const { Title } = Typography;
|
||||||
|
|
||||||
class Cluster extends React.Component {
|
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,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
constructor(props) {
|
handleNameChange = name => {
|
||||||
super(props);
|
this.setState({
|
||||||
const {cluster, pageId} = this.props;
|
name,
|
||||||
this.originalCluster = Object.assign({}, cluster);
|
});
|
||||||
const {name, products, clusterId} = cluster;
|
if (name !== this.originalCluster.name) {
|
||||||
this.clusterId = clusterId;
|
this.setState({
|
||||||
this.pageId = pageId;
|
isSaveable: true,
|
||||||
this.state = {
|
});
|
||||||
name,
|
|
||||||
products,
|
|
||||||
isSaveable: false,
|
|
||||||
loading: false
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
handleNameChange = (name) => {
|
isProductsChanged = currentProducts => {
|
||||||
this.setState({
|
let isChanged = false;
|
||||||
name
|
const originalProducts = this.originalCluster.products;
|
||||||
});
|
if (currentProducts.length === originalProducts.length) {
|
||||||
if (name !== this.originalCluster.name) {
|
for (let i = 0; i < currentProducts.length; i++) {
|
||||||
this.setState({
|
if (currentProducts[i].packageId !== originalProducts[i].packageId) {
|
||||||
isSaveable: true
|
isChanged = true;
|
||||||
});
|
break;
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
} else {
|
||||||
|
isChanged = true;
|
||||||
|
}
|
||||||
|
return isChanged;
|
||||||
|
};
|
||||||
|
|
||||||
isProductsChanged = (currentProducts) => {
|
swapProduct = (index, swapIndex) => {
|
||||||
let isChanged = false;
|
const products = [...this.state.products];
|
||||||
const originalProducts = this.originalCluster.products;
|
if (swapIndex !== -1 && index < products.length) {
|
||||||
if (currentProducts.length === originalProducts.length) {
|
// swap elements
|
||||||
for (let i = 0; i < currentProducts.length; i++) {
|
[products[index], products[swapIndex]] = [
|
||||||
if (currentProducts[i].packageId !== originalProducts[i].packageId) {
|
products[swapIndex],
|
||||||
isChanged = true;
|
products[index],
|
||||||
break;
|
];
|
||||||
}
|
|
||||||
}
|
this.setState({
|
||||||
} else {
|
products,
|
||||||
isChanged = true;
|
});
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return isChanged;
|
})
|
||||||
};
|
.catch(error => {
|
||||||
|
handleApiError(
|
||||||
swapProduct = (index, swapIndex) => {
|
error,
|
||||||
const products = [...this.state.products];
|
'Error occurred while trying to update the cluster.',
|
||||||
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;
|
|
||||||
const {name, products} = cluster;
|
|
||||||
|
|
||||||
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">
|
|
||||||
<button disabled={index === 0} className="btn"
|
|
||||||
onClick={() => {
|
|
||||||
this.swapProduct(index, index - 1);
|
|
||||||
}}>
|
|
||||||
<Icon type="caret-left" theme="filled"/>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<div className="product-icon">
|
|
||||||
<img src={imageSrc}/>
|
|
||||||
<Tooltip title={packageId}>
|
|
||||||
<div className="title">
|
|
||||||
{packageId}
|
|
||||||
</div>
|
|
||||||
</Tooltip>
|
|
||||||
</div>
|
|
||||||
<div className="arrow">
|
|
||||||
<button
|
|
||||||
disabled={index === products.length - 1}
|
|
||||||
onClick={() => {
|
|
||||||
this.swapProduct(index, index + 1);
|
|
||||||
}} className="btn btn-right"><Icon type="caret-right" theme="filled"/></button>
|
|
||||||
</div>
|
|
||||||
<div className="delete-btn">
|
|
||||||
<button className="btn"
|
|
||||||
onClick={() => {
|
|
||||||
this.removeProduct(index)
|
|
||||||
}}>
|
|
||||||
<Icon type="close-circle" theme="filled"/>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="cluster" id={this.props.orderInPage}>
|
|
||||||
<Spin spinning={loading}>
|
|
||||||
<Row>
|
|
||||||
<Col span={16}>
|
|
||||||
<Title editable={{onChange: this.handleNameChange}} level={4}>{name}</Title>
|
|
||||||
</Col>
|
|
||||||
<Col span={8}>
|
|
||||||
{!isTemporary && (
|
|
||||||
<div style={{float: "right"}}>
|
|
||||||
<Tooltip title="Move Up">
|
|
||||||
<Button
|
|
||||||
type="link"
|
|
||||||
icon="caret-up"
|
|
||||||
size="large"
|
|
||||||
onClick={() => {
|
|
||||||
this.props.swapClusters(index, index - 1)
|
|
||||||
}} htmlType="button"/>
|
|
||||||
</Tooltip>
|
|
||||||
<Tooltip title="Move Down">
|
|
||||||
<Button
|
|
||||||
type="link"
|
|
||||||
icon="caret-down"
|
|
||||||
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="delete"
|
|
||||||
shape="circle"
|
|
||||||
htmlType="button"/>
|
|
||||||
</Popconfirm>
|
|
||||||
</Tooltip>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</Col>
|
|
||||||
</Row>
|
|
||||||
<div className="products-row">
|
|
||||||
<AddAppsToClusterModal
|
|
||||||
addSelectedProducts={this.addSelectedProducts}
|
|
||||||
unselectedProducts={unselectedProducts}/>
|
|
||||||
{
|
|
||||||
products.map((product, index) => {
|
|
||||||
return (
|
|
||||||
<Product
|
|
||||||
key={product.packageId}
|
|
||||||
product={product}
|
|
||||||
index={index}/>
|
|
||||||
);
|
|
||||||
})
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
<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>
|
|
||||||
);
|
);
|
||||||
}
|
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">
|
||||||
|
<button
|
||||||
|
disabled={index === 0}
|
||||||
|
className="btn"
|
||||||
|
onClick={() => {
|
||||||
|
this.swapProduct(index, index - 1);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Icon type="caret-left" theme="filled" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div className="product-icon">
|
||||||
|
<img src={imageSrc} />
|
||||||
|
<Tooltip title={packageId}>
|
||||||
|
<div className="title">{packageId}</div>
|
||||||
|
</Tooltip>
|
||||||
|
</div>
|
||||||
|
<div className="arrow">
|
||||||
|
<button
|
||||||
|
disabled={index === products.length - 1}
|
||||||
|
onClick={() => {
|
||||||
|
this.swapProduct(index, index + 1);
|
||||||
|
}}
|
||||||
|
className="btn btn-right"
|
||||||
|
>
|
||||||
|
<Icon type="caret-right" theme="filled" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div className="delete-btn">
|
||||||
|
<button
|
||||||
|
className="btn"
|
||||||
|
onClick={() => {
|
||||||
|
this.removeProduct(index);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Icon type="close-circle" theme="filled" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="cluster" id={this.props.orderInPage}>
|
||||||
|
<Spin spinning={loading}>
|
||||||
|
<Row>
|
||||||
|
<Col span={16}>
|
||||||
|
<Title editable={{ onChange: this.handleNameChange }} level={4}>
|
||||||
|
{name}
|
||||||
|
</Title>
|
||||||
|
</Col>
|
||||||
|
<Col span={8}>
|
||||||
|
{!isTemporary && (
|
||||||
|
<div style={{ float: 'right' }}>
|
||||||
|
<Tooltip title="Move Up">
|
||||||
|
<Button
|
||||||
|
type="link"
|
||||||
|
icon="caret-up"
|
||||||
|
size="large"
|
||||||
|
onClick={() => {
|
||||||
|
this.props.swapClusters(index, index - 1);
|
||||||
|
}}
|
||||||
|
htmlType="button"
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
|
<Tooltip title="Move Down">
|
||||||
|
<Button
|
||||||
|
type="link"
|
||||||
|
icon="caret-down"
|
||||||
|
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="delete"
|
||||||
|
shape="circle"
|
||||||
|
htmlType="button"
|
||||||
|
/>
|
||||||
|
</Popconfirm>
|
||||||
|
</Tooltip>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
<div className="products-row">
|
||||||
|
<AddAppsToClusterModal
|
||||||
|
addSelectedProducts={this.addSelectedProducts}
|
||||||
|
unselectedProducts={unselectedProducts}
|
||||||
|
/>
|
||||||
|
{products.map((product, index) => {
|
||||||
|
return (
|
||||||
|
<Product
|
||||||
|
key={product.packageId}
|
||||||
|
product={product}
|
||||||
|
index={index}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
<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);
|
export default withConfigContext(Cluster);
|
||||||
@ -16,103 +16,110 @@
|
|||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from "react";
|
import React from 'react';
|
||||||
import {Button, message, Modal, notification, Select, Spin} from "antd";
|
import { Button, Modal, notification, Select, Spin } from 'antd';
|
||||||
import axios from "axios";
|
import axios from 'axios';
|
||||||
import {withConfigContext} from "../../../../../context/ConfigContext";
|
import { withConfigContext } from '../../../../../context/ConfigContext';
|
||||||
import {handleApiError} from "../../../../../js/Utils";
|
import { handleApiError } from '../../../../../js/Utils';
|
||||||
|
|
||||||
const {Option} = Select;
|
const { Option } = Select;
|
||||||
|
|
||||||
class EditLinks extends React.Component {
|
class EditLinks extends React.Component {
|
||||||
|
constructor(props) {
|
||||||
constructor(props) {
|
super(props);
|
||||||
super(props);
|
this.selectedLinks = [];
|
||||||
this.selectedLinks = [];
|
this.state = {
|
||||||
this.state = {
|
visible: false,
|
||||||
visible: false
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
showModal = () => {
|
|
||||||
this.setState({
|
|
||||||
visible: true,
|
|
||||||
loading: false
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
showModal = () => {
|
||||||
|
this.setState({
|
||||||
|
visible: true,
|
||||||
|
loading: false,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
handleCancel = e => {
|
handleCancel = e => {
|
||||||
this.setState({
|
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,
|
visible: false,
|
||||||
});
|
});
|
||||||
};
|
}
|
||||||
|
})
|
||||||
updateLinks = () => {
|
.catch(error => {
|
||||||
const config = this.props.context;
|
handleApiError(
|
||||||
this.setState({loading: true});
|
error,
|
||||||
|
'Error occurred while trying to update the cluster.',
|
||||||
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>
|
|
||||||
);
|
);
|
||||||
}
|
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);
|
export default withConfigContext(EditLinks);
|
||||||
@ -16,247 +16,277 @@
|
|||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from "react";
|
import React from 'react';
|
||||||
import axios from "axios";
|
import axios from 'axios';
|
||||||
import {Tag, message, notification, Table, Typography, Divider, Icon, Popconfirm, Button} from "antd";
|
import {
|
||||||
|
Tag,
|
||||||
|
notification,
|
||||||
|
Table,
|
||||||
|
Typography,
|
||||||
|
Divider,
|
||||||
|
Icon,
|
||||||
|
Popconfirm,
|
||||||
|
Button,
|
||||||
|
} from 'antd';
|
||||||
|
|
||||||
import {withConfigContext} from "../../../../context/ConfigContext";
|
import { withConfigContext } from '../../../../context/ConfigContext';
|
||||||
import "./Pages.css";
|
import './Pages.css';
|
||||||
import {Link} from "react-router-dom";
|
import { Link } from 'react-router-dom';
|
||||||
import AddNewPage from "../AddNewPage/AddNewPage";
|
import AddNewPage from '../AddNewPage/AddNewPage';
|
||||||
import {handleApiError} from "../../../../js/Utils";
|
import { handleApiError } from '../../../../js/Utils';
|
||||||
|
|
||||||
const {Text, Title} = Typography;
|
|
||||||
|
|
||||||
let config = null;
|
|
||||||
|
|
||||||
|
const { Text, Title } = Typography;
|
||||||
|
|
||||||
class Pages extends React.Component {
|
class Pages extends React.Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
config = this.props.context;
|
this.state = {
|
||||||
// TimeAgo.addLocale(en);
|
data: [],
|
||||||
this.state = {
|
pagination: {},
|
||||||
data: [],
|
loading: false,
|
||||||
pagination: {},
|
selectedRows: [],
|
||||||
|
homePageId: null,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
rowSelection = {
|
||||||
|
onChange: (selectedRowKeys, selectedRows) => {
|
||||||
|
this.setState({
|
||||||
|
selectedRows: selectedRows,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
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,
|
loading: false,
|
||||||
selectedRows: [],
|
data: res.data.data.page,
|
||||||
homePageId: null
|
pagination,
|
||||||
};
|
});
|
||||||
}
|
|
||||||
|
|
||||||
rowSelection = {
|
|
||||||
onChange: (selectedRowKeys, selectedRows) => {
|
|
||||||
this.setState({
|
|
||||||
selectedRows: selectedRows
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
};
|
})
|
||||||
|
.catch(error => {
|
||||||
|
handleApiError(error, 'Error occurred while trying to load pages.');
|
||||||
|
this.setState({ loading: false });
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
componentDidMount() {
|
setHomePage = () => {
|
||||||
this.setHomePage();
|
const config = this.props.context;
|
||||||
this.fetch();
|
// 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 });
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
//fetch data from api
|
updateHomePage = pageId => {
|
||||||
fetch = (params = {}) => {
|
const config = this.props.context;
|
||||||
const config = this.props.context;
|
this.setState({
|
||||||
this.setState({loading: true});
|
loading: true,
|
||||||
// get current page
|
});
|
||||||
const currentPage = (params.hasOwnProperty("page")) ? params.page : 1;
|
// 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!',
|
||||||
|
});
|
||||||
|
|
||||||
const extraParams = {
|
this.setState({
|
||||||
offset: 10 * (currentPage - 1), //calculate the offset
|
homePageId: res.data.data.homepageId,
|
||||||
limit: 10,
|
loading: false,
|
||||||
};
|
});
|
||||||
|
}
|
||||||
//send request to the invoker
|
})
|
||||||
axios.get(
|
.catch(error => {
|
||||||
window.location.origin + config.serverConfig.invoker.uri +
|
handleApiError(
|
||||||
"/device-mgt/android/v1.0/enterprise/store-layout/page",
|
error,
|
||||||
).then(res => {
|
'Error occurred while trying to update the home page.',
|
||||||
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>
|
|
||||||
<span className="action">
|
|
||||||
<Button disabled={page.id === this.state.homePageId}
|
|
||||||
className="btn-warning"
|
|
||||||
icon="home"
|
|
||||||
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"><Icon type="delete"/> delete</Text>
|
|
||||||
</span>
|
|
||||||
</Popconfirm>
|
|
||||||
</div>
|
|
||||||
),
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const {data, pagination, loading, selectedRows} = this.state;
|
|
||||||
return (
|
|
||||||
<div className="layout-pages">
|
|
||||||
<Title level={4}>Pages</Title>
|
|
||||||
<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>
|
|
||||||
);
|
);
|
||||||
}
|
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>
|
||||||
|
<span className="action">
|
||||||
|
<Button
|
||||||
|
disabled={page.id === this.state.homePageId}
|
||||||
|
className="btn-warning"
|
||||||
|
icon="home"
|
||||||
|
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">
|
||||||
|
<Icon type="delete" /> delete
|
||||||
|
</Text>
|
||||||
|
</span>
|
||||||
|
</Popconfirm>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { data, pagination, loading } = this.state;
|
||||||
|
return (
|
||||||
|
<div className="layout-pages">
|
||||||
|
<Title level={4}>Pages</Title>
|
||||||
|
<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);
|
export default withConfigContext(Pages);
|
||||||
@ -16,64 +16,66 @@
|
|||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from "react";
|
import React from 'react';
|
||||||
import {Button, notification} from "antd";
|
import { Button, notification } from 'antd';
|
||||||
import axios from "axios";
|
import axios from 'axios';
|
||||||
import {withConfigContext} from "../../../context/ConfigContext";
|
import { withConfigContext } from '../../../context/ConfigContext';
|
||||||
import {handleApiError} from "../../../js/Utils";
|
import { handleApiError } from '../../../js/Utils';
|
||||||
|
|
||||||
class SyncAndroidApps extends React.Component {
|
class SyncAndroidApps extends React.Component {
|
||||||
|
constructor(props) {
|
||||||
constructor(props) {
|
super(props);
|
||||||
super(props);
|
this.state = {
|
||||||
this.state = {
|
loading: false,
|
||||||
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() {
|
syncApps = () => {
|
||||||
const {loading} = this.state;
|
const config = this.props.context;
|
||||||
return (
|
this.setState({
|
||||||
<div style={{display: "inline-block", padding: 4}}>
|
loading: true,
|
||||||
<Button
|
});
|
||||||
onClick={this.syncApps}
|
|
||||||
loading={loading}
|
axios
|
||||||
style={{marginTop: 16}}
|
.get(
|
||||||
type="primary"
|
window.location.origin +
|
||||||
icon="sync"
|
config.serverConfig.invoker.uri +
|
||||||
>
|
'/device-mgt/android/v1.0/enterprise/products/sync',
|
||||||
Sync{loading && "ing..."}
|
)
|
||||||
</Button>
|
.then(res => {
|
||||||
</div>
|
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="sync"
|
||||||
|
>
|
||||||
|
Sync{loading && 'ing...'}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default withConfigContext(SyncAndroidApps);
|
export default withConfigContext(SyncAndroidApps);
|
||||||
@ -16,452 +16,507 @@
|
|||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from "react";
|
import React from 'react';
|
||||||
import {
|
import {
|
||||||
Card,
|
Card,
|
||||||
Tag,
|
Tag,
|
||||||
message,
|
message,
|
||||||
Icon,
|
Icon,
|
||||||
Input,
|
Input,
|
||||||
notification,
|
notification,
|
||||||
Divider,
|
Divider,
|
||||||
Button,
|
Button,
|
||||||
Spin,
|
Spin,
|
||||||
Tooltip,
|
Tooltip,
|
||||||
Popconfirm,
|
Popconfirm,
|
||||||
Modal,
|
Modal,
|
||||||
Row,
|
Row,
|
||||||
Col,
|
Col,
|
||||||
Typography, Alert
|
Typography,
|
||||||
} from "antd";
|
Alert,
|
||||||
import axios from "axios";
|
} from 'antd';
|
||||||
import {TweenOneGroup} from 'rc-tween-one';
|
import axios from 'axios';
|
||||||
import pSBC from "shade-blend-color";
|
import { TweenOneGroup } from 'rc-tween-one';
|
||||||
import {withConfigContext} from "../../../context/ConfigContext";
|
import pSBC from 'shade-blend-color';
|
||||||
import {handleApiError} from "../../../js/Utils";
|
import { withConfigContext } from '../../../context/ConfigContext';
|
||||||
|
import { handleApiError } from '../../../js/Utils';
|
||||||
|
|
||||||
const {Title} = Typography;
|
const { Title } = Typography;
|
||||||
|
|
||||||
class ManageCategories extends React.Component {
|
class ManageCategories extends React.Component {
|
||||||
state = {
|
state = {
|
||||||
loading: false,
|
loading: false,
|
||||||
searchText: '',
|
searchText: '',
|
||||||
categories: [],
|
categories: [],
|
||||||
tempElements: [],
|
tempElements: [],
|
||||||
inputVisible: false,
|
inputVisible: false,
|
||||||
inputValue: '',
|
inputValue: '',
|
||||||
isAddNewVisible: false,
|
isAddNewVisible: false,
|
||||||
isEditModalVisible: false,
|
isEditModalVisible: false,
|
||||||
currentlyEditingId: null,
|
currentlyEditingId: null,
|
||||||
editingValue: null,
|
editingValue: null,
|
||||||
forbiddenErrors: {
|
forbiddenErrors: {
|
||||||
categories: false
|
categories: false,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
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 occured while trying to load categories',
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
if (error.hasOwnProperty('response') && error.response.status === 403) {
|
||||||
|
const { forbiddenErrors } = this.state;
|
||||||
|
forbiddenErrors.categories = true;
|
||||||
|
this.setState({
|
||||||
|
forbiddenErrors,
|
||||||
|
loading: false,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
this.setState({
|
||||||
|
loading: false,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
handleCloseButton = () => {
|
||||||
const config = this.props.context;
|
this.setState({
|
||||||
axios.get(
|
tempElements: [],
|
||||||
window.location.origin + config.serverConfig.invoker.uri + config.serverConfig.invoker.publisher + "/applications/categories",
|
isAddNewVisible: false,
|
||||||
).then(res => {
|
});
|
||||||
if (res.status === 200) {
|
};
|
||||||
let categories = JSON.parse(res.data.data);
|
|
||||||
this.setState({
|
|
||||||
categories: categories,
|
|
||||||
loading: false
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
}).catch((error) => {
|
deleteCategory = id => {
|
||||||
handleApiError(error, "Error occured while trying to load categories", true);
|
const config = this.props.context;
|
||||||
if (error.hasOwnProperty("response") && error.response.status === 403) {
|
this.setState({
|
||||||
const {forbiddenErrors} = this.state;
|
loading: true,
|
||||||
forbiddenErrors.categories = true;
|
});
|
||||||
this.setState({
|
axios
|
||||||
forbiddenErrors,
|
.delete(
|
||||||
loading: false
|
window.location.origin +
|
||||||
})
|
config.serverConfig.invoker.uri +
|
||||||
} else {
|
config.serverConfig.invoker.publisher +
|
||||||
this.setState({
|
'/admin/applications/categories/' +
|
||||||
loading: false
|
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}
|
||||||
|
<Divider type="vertical" />
|
||||||
|
<Tooltip title="edit">
|
||||||
|
<Icon
|
||||||
|
onClick={() => {
|
||||||
|
this.openEditModal(categoryName);
|
||||||
|
}}
|
||||||
|
type="edit"
|
||||||
|
/>
|
||||||
|
</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"
|
||||||
|
>
|
||||||
|
<Icon type="delete" />
|
||||||
|
</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');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handleCloseButton = () => {
|
this.setState({
|
||||||
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: [],
|
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.30, config.theme.primaryColor)}
|
|
||||||
style={{marginTop: 8}}>
|
|
||||||
{categoryName}
|
|
||||||
<Divider type="vertical"/>
|
|
||||||
<Tooltip title="edit">
|
|
||||||
<Icon onClick={() => {
|
|
||||||
this.openEditModal(categoryName)
|
|
||||||
}} type="edit"/>
|
|
||||||
</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">
|
|
||||||
<Icon type="delete"/>
|
|
||||||
</Popconfirm>
|
|
||||||
</Tooltip>
|
|
||||||
</Tag>
|
|
||||||
);
|
|
||||||
return (
|
|
||||||
<span key={category.categoryName} style={{display: 'inline-block'}}>
|
|
||||||
{tagElem}
|
|
||||||
</span>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
renderTempElement = (category) => {
|
|
||||||
const config = this.props.context;
|
|
||||||
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,
|
inputVisible: false,
|
||||||
inputValue: '',
|
inputValue: '',
|
||||||
});
|
loading: false,
|
||||||
};
|
isAddNewVisible: false,
|
||||||
|
});
|
||||||
handleSave = () => {
|
}
|
||||||
const config = this.props.context;
|
})
|
||||||
const {tempElements, categories} = this.state;
|
.catch(error => {
|
||||||
|
handleApiError(error, 'Error occurred while trying to add categories.');
|
||||||
this.setState({
|
this.setState({
|
||||||
loading: true
|
loading: false,
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
const data = tempElements.map(category => category.categoryName);
|
saveInputRef = input => (this.input = input);
|
||||||
|
|
||||||
axios.post(
|
closeEditModal = e => {
|
||||||
window.location.origin + config.serverConfig.invoker.uri + config.serverConfig.invoker.publisher + "/admin/applications/categories",
|
this.setState({
|
||||||
data,
|
isEditModalVisible: false,
|
||||||
).then(res => {
|
currentlyEditingId: null,
|
||||||
if (res.status === 200) {
|
});
|
||||||
notification["success"]({
|
};
|
||||||
message: "Done!",
|
|
||||||
description:
|
|
||||||
"New Categories were added successfully",
|
|
||||||
});
|
|
||||||
|
|
||||||
this.setState({
|
openEditModal = id => {
|
||||||
categories: [...categories, ...tempElements],
|
this.setState({
|
||||||
tempElements: [],
|
isEditModalVisible: true,
|
||||||
inputVisible: false,
|
currentlyEditingId: id,
|
||||||
inputValue: '',
|
editingValue: id,
|
||||||
loading: false,
|
});
|
||||||
isAddNewVisible: false
|
};
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
}).catch((error) => {
|
editItem = () => {
|
||||||
handleApiError(error, "Error occurred while trying to add categories.");
|
const config = this.props.context;
|
||||||
this.setState({
|
|
||||||
loading: false
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
|
const { editingValue, currentlyEditingId, categories } = this.state;
|
||||||
|
|
||||||
};
|
this.setState({
|
||||||
|
loading: true,
|
||||||
|
isEditModalVisible: false,
|
||||||
|
});
|
||||||
|
|
||||||
saveInputRef = input => (this.input = input);
|
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',
|
||||||
|
});
|
||||||
|
|
||||||
closeEditModal = e => {
|
categories[
|
||||||
this.setState({
|
categories.findIndex(i => i.categoryName === currentlyEditingId)
|
||||||
isEditModalVisible: false,
|
].categoryName = editingValue;
|
||||||
currentlyEditingId: null
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
openEditModal = (id) => {
|
this.setState({
|
||||||
this.setState({
|
categories: categories,
|
||||||
isEditModalVisible: true,
|
loading: false,
|
||||||
currentlyEditingId: id,
|
editingValue: null,
|
||||||
editingValue: id
|
});
|
||||||
})
|
}
|
||||||
};
|
})
|
||||||
|
.catch(error => {
|
||||||
editItem = () => {
|
handleApiError(
|
||||||
const config = this.props.context;
|
error,
|
||||||
|
'Error occurred while trying to delete the category.',
|
||||||
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, forbiddenErrors} = this.state;
|
|
||||||
const categoriesElements = categories.map(this.renderElement);
|
|
||||||
const temporaryElements = tempElements.map(this.renderTempElement);
|
|
||||||
return (
|
|
||||||
<div style={{marginBottom: 16}}>
|
|
||||||
{(forbiddenErrors.categories) && (
|
|
||||||
<Alert
|
|
||||||
message="You don't have permission to view categories."
|
|
||||||
type="warning"
|
|
||||||
banner
|
|
||||||
closable/>
|
|
||||||
)}
|
|
||||||
<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="plus"
|
|
||||||
// type="primary"
|
|
||||||
size="small"
|
|
||||||
onClick={() => {
|
|
||||||
this.setState({
|
|
||||||
isAddNewVisible: true,
|
|
||||||
inputVisible: true
|
|
||||||
}, () => this.input.focus())
|
|
||||||
}} htmlType="button">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'}}>
|
|
||||||
<Icon type="plus"/> 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>
|
|
||||||
);
|
);
|
||||||
}
|
this.setState({
|
||||||
|
loading: false,
|
||||||
|
editingValue: null,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
handleEditInputChange = e => {
|
||||||
|
this.setState({
|
||||||
|
editingValue: e.target.value,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const {
|
||||||
|
categories,
|
||||||
|
inputVisible,
|
||||||
|
inputValue,
|
||||||
|
tempElements,
|
||||||
|
isAddNewVisible,
|
||||||
|
forbiddenErrors,
|
||||||
|
} = this.state;
|
||||||
|
const categoriesElements = categories.map(this.renderElement);
|
||||||
|
const temporaryElements = tempElements.map(this.renderTempElement);
|
||||||
|
return (
|
||||||
|
<div style={{ marginBottom: 16 }}>
|
||||||
|
{forbiddenErrors.categories && (
|
||||||
|
<Alert
|
||||||
|
message="You don't have permission to view categories."
|
||||||
|
type="warning"
|
||||||
|
banner
|
||||||
|
closable
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<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="plus"
|
||||||
|
// type="primary"
|
||||||
|
size="small"
|
||||||
|
onClick={() => {
|
||||||
|
this.setState(
|
||||||
|
{
|
||||||
|
isAddNewVisible: true,
|
||||||
|
inputVisible: true,
|
||||||
|
},
|
||||||
|
() => this.input.focus(),
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
htmlType="button"
|
||||||
|
>
|
||||||
|
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' }}
|
||||||
|
>
|
||||||
|
<Icon type="plus" /> 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);
|
export default withConfigContext(ManageCategories);
|
||||||
|
|||||||
@ -16,450 +16,499 @@
|
|||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from "react";
|
import React from 'react';
|
||||||
import {
|
import {
|
||||||
Card,
|
Card,
|
||||||
Tag,
|
Tag,
|
||||||
message,
|
message,
|
||||||
Icon,
|
Icon,
|
||||||
Input,
|
Input,
|
||||||
notification,
|
notification,
|
||||||
Divider,
|
Divider,
|
||||||
Button,
|
Button,
|
||||||
Spin,
|
Spin,
|
||||||
Tooltip,
|
Tooltip,
|
||||||
Popconfirm,
|
Popconfirm,
|
||||||
Modal,
|
Modal,
|
||||||
Row, Col,
|
Row,
|
||||||
Typography, Alert
|
Col,
|
||||||
} from "antd";
|
Typography,
|
||||||
import axios from "axios";
|
Alert,
|
||||||
import {TweenOneGroup} from 'rc-tween-one';
|
} from 'antd';
|
||||||
import {withConfigContext} from "../../../context/ConfigContext";
|
import axios from 'axios';
|
||||||
import {handleApiError} from "../../../js/Utils";
|
import { TweenOneGroup } from 'rc-tween-one';
|
||||||
|
import { withConfigContext } from '../../../context/ConfigContext';
|
||||||
|
import { handleApiError } from '../../../js/Utils';
|
||||||
|
|
||||||
const {Title} = Typography;
|
const { Title } = Typography;
|
||||||
|
|
||||||
class ManageTags extends React.Component {
|
class ManageTags extends React.Component {
|
||||||
state = {
|
state = {
|
||||||
loading: false,
|
loading: false,
|
||||||
searchText: '',
|
searchText: '',
|
||||||
tags: [],
|
tags: [],
|
||||||
tempElements: [],
|
tempElements: [],
|
||||||
inputVisible: false,
|
inputVisible: false,
|
||||||
inputValue: '',
|
inputValue: '',
|
||||||
isAddNewVisible: false,
|
isAddNewVisible: false,
|
||||||
isEditModalVisible: false,
|
isEditModalVisible: false,
|
||||||
currentlyEditingId: null,
|
currentlyEditingId: null,
|
||||||
editingValue: null,
|
editingValue: null,
|
||||||
forbiddenErrors: {
|
forbiddenErrors: {
|
||||||
tags: false
|
tags: false,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
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,
|
||||||
|
);
|
||||||
|
if (error.hasOwnProperty('response') && error.response.status === 403) {
|
||||||
|
const { forbiddenErrors } = this.state;
|
||||||
|
forbiddenErrors.tags = true;
|
||||||
|
this.setState({
|
||||||
|
forbiddenErrors,
|
||||||
|
loading: false,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
this.setState({
|
||||||
|
loading: false,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
handleCloseButton = () => {
|
||||||
const config = this.props.context;
|
this.setState({
|
||||||
axios.get(
|
tempElements: [],
|
||||||
window.location.origin + config.serverConfig.invoker.uri + config.serverConfig.invoker.publisher + "/applications/tags",
|
isAddNewVisible: false,
|
||||||
).then(res => {
|
});
|
||||||
if (res.status === 200) {
|
};
|
||||||
let tags = JSON.parse(res.data.data);
|
|
||||||
this.setState({
|
|
||||||
tags: tags,
|
|
||||||
loading: false
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
}).catch((error) => {
|
deleteTag = id => {
|
||||||
handleApiError(error, "Error occurred while trying to load tags.", true);
|
const config = this.props.context;
|
||||||
if (error.hasOwnProperty("response") && error.response.status === 403) {
|
|
||||||
const {forbiddenErrors} = this.state;
|
this.setState({
|
||||||
forbiddenErrors.tags = true;
|
loading: true,
|
||||||
this.setState({
|
});
|
||||||
forbiddenErrors,
|
|
||||||
loading: false
|
axios
|
||||||
})
|
.delete(
|
||||||
} else {
|
window.location.origin +
|
||||||
this.setState({
|
config.serverConfig.invoker.uri +
|
||||||
loading: false
|
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}
|
||||||
|
<Divider type="vertical" />
|
||||||
|
<Tooltip title="edit">
|
||||||
|
<Icon
|
||||||
|
onClick={() => {
|
||||||
|
this.openEditModal(tagName);
|
||||||
|
}}
|
||||||
|
type="edit"
|
||||||
|
/>
|
||||||
|
</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"
|
||||||
|
>
|
||||||
|
<Icon type="delete" />
|
||||||
|
</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');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handleCloseButton = () => {
|
this.setState({
|
||||||
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: [],
|
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}
|
|
||||||
<Divider type="vertical"/>
|
|
||||||
<Tooltip title="edit">
|
|
||||||
<Icon onClick={() => {
|
|
||||||
this.openEditModal(tagName)
|
|
||||||
}} type="edit"/>
|
|
||||||
</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"
|
|
||||||
>
|
|
||||||
<Icon type="delete"/>
|
|
||||||
</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,
|
inputVisible: false,
|
||||||
inputValue: '',
|
inputValue: '',
|
||||||
});
|
loading: false,
|
||||||
};
|
isAddNewVisible: false,
|
||||||
|
});
|
||||||
handleSave = () => {
|
}
|
||||||
const config = this.props.context;
|
})
|
||||||
const {tempElements, tags} = this.state;
|
.catch(error => {
|
||||||
|
handleApiError(error, 'Error occurred while trying to delete tag.');
|
||||||
this.setState({
|
this.setState({
|
||||||
loading: true
|
loading: false,
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
const data = tempElements.map(tag => tag.tagName);
|
saveInputRef = input => (this.input = input);
|
||||||
|
|
||||||
axios.post(window.location.origin + config.serverConfig.invoker.uri + config.serverConfig.invoker.publisher + "/applications/tags",
|
closeEditModal = e => {
|
||||||
data,
|
this.setState({
|
||||||
).then(res => {
|
isEditModalVisible: false,
|
||||||
if (res.status === 200) {
|
currentlyEditingId: null,
|
||||||
notification["success"]({
|
});
|
||||||
message: "Done!",
|
};
|
||||||
description:
|
|
||||||
"New tags were added successfully",
|
|
||||||
});
|
|
||||||
|
|
||||||
this.setState({
|
openEditModal = id => {
|
||||||
tags: [...tags, ...tempElements],
|
this.setState({
|
||||||
tempElements: [],
|
isEditModalVisible: true,
|
||||||
inputVisible: false,
|
currentlyEditingId: id,
|
||||||
inputValue: '',
|
editingValue: id,
|
||||||
loading: false,
|
});
|
||||||
isAddNewVisible: false
|
};
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
}).catch((error) => {
|
editItem = () => {
|
||||||
handleApiError(error, "Error occurred while trying to delete tag.");
|
const config = this.props.context;
|
||||||
this.setState({
|
|
||||||
loading: false
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
|
const { editingValue, currentlyEditingId, tags } = this.state;
|
||||||
|
|
||||||
};
|
this.setState({
|
||||||
|
loading: true,
|
||||||
|
isEditModalVisible: false,
|
||||||
|
});
|
||||||
|
|
||||||
saveInputRef = input => (this.input = input);
|
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',
|
||||||
|
});
|
||||||
|
|
||||||
closeEditModal = e => {
|
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({
|
this.setState({
|
||||||
isEditModalVisible: false,
|
loading: false,
|
||||||
currentlyEditingId: null
|
editingValue: null,
|
||||||
});
|
});
|
||||||
};
|
});
|
||||||
|
};
|
||||||
|
|
||||||
openEditModal = (id) => {
|
handleEditInputChange = e => {
|
||||||
this.setState({
|
this.setState({
|
||||||
isEditModalVisible: true,
|
editingValue: e.target.value,
|
||||||
currentlyEditingId: id,
|
});
|
||||||
editingValue: id
|
};
|
||||||
})
|
|
||||||
};
|
|
||||||
|
|
||||||
editItem = () => {
|
render() {
|
||||||
const config = this.props.context;
|
const {
|
||||||
|
tags,
|
||||||
const {editingValue, currentlyEditingId, tags} = this.state;
|
inputVisible,
|
||||||
|
inputValue,
|
||||||
this.setState({
|
tempElements,
|
||||||
loading: true,
|
isAddNewVisible,
|
||||||
isEditModalVisible: false,
|
forbiddenErrors,
|
||||||
});
|
} = this.state;
|
||||||
|
const tagsElements = tags.map(this.renderElement);
|
||||||
axios.put(
|
const temporaryElements = tempElements.map(this.renderTempElement);
|
||||||
window.location.origin + config.serverConfig.invoker.uri + config.serverConfig.invoker.publisher + "/applications/tags/rename?from=" + currentlyEditingId + "&to=" + editingValue,
|
return (
|
||||||
{},
|
<div style={{ marginBottom: 16 }}>
|
||||||
).then(res => {
|
{forbiddenErrors.tags && (
|
||||||
if (res.status === 200) {
|
<Alert
|
||||||
notification["success"]({
|
message="You don't have permission to view tags."
|
||||||
message: "Done!",
|
type="warning"
|
||||||
description:
|
banner
|
||||||
"Tag was edited successfully",
|
closable
|
||||||
});
|
/>
|
||||||
|
)}
|
||||||
tags[tags.findIndex(i => i.tagName === currentlyEditingId)].tagName = editingValue;
|
<Card>
|
||||||
|
<Spin tip="Working on it..." spinning={this.state.loading}>
|
||||||
this.setState({
|
<Row>
|
||||||
tags: tags,
|
<Col span={16}>
|
||||||
loading: false,
|
<Title level={4}>Tags</Title>
|
||||||
editingValue: null
|
</Col>
|
||||||
});
|
<Col span={8}>
|
||||||
}
|
{!isAddNewVisible && (
|
||||||
|
<div style={{ float: 'right' }}>
|
||||||
}).catch((error) => {
|
<Button
|
||||||
handleApiError(error, "Error occurred while trying to edit tag.");
|
icon="plus"
|
||||||
this.setState({
|
// type="primary"
|
||||||
loading: false,
|
size="small"
|
||||||
editingValue: null
|
onClick={() => {
|
||||||
});
|
this.setState(
|
||||||
});
|
{
|
||||||
|
isAddNewVisible: true,
|
||||||
|
inputVisible: true,
|
||||||
};
|
},
|
||||||
|
() => this.input.focus(),
|
||||||
handleEditInputChange = (e) => {
|
);
|
||||||
this.setState({
|
}}
|
||||||
editingValue: e.target.value
|
htmlType="button"
|
||||||
});
|
>
|
||||||
};
|
Add
|
||||||
|
</Button>
|
||||||
render() {
|
</div>
|
||||||
const {tags, inputVisible, inputValue, tempElements, isAddNewVisible, forbiddenErrors} = this.state;
|
|
||||||
const tagsElements = tags.map(this.renderElement);
|
|
||||||
const temporaryElements = tempElements.map(this.renderTempElement);
|
|
||||||
return (
|
|
||||||
<div style={{marginBottom: 16}}>
|
|
||||||
{(forbiddenErrors.tags) && (
|
|
||||||
<Alert
|
|
||||||
message="You don't have permission to view tags."
|
|
||||||
type="warning"
|
|
||||||
banner
|
|
||||||
closable/>
|
|
||||||
)}
|
)}
|
||||||
<Card>
|
</Col>
|
||||||
<Spin tip="Working on it..." spinning={this.state.loading}>
|
</Row>
|
||||||
<Row>
|
{isAddNewVisible && (
|
||||||
<Col span={16}>
|
<div>
|
||||||
<Title level={4}>Tags</Title>
|
<Divider />
|
||||||
</Col>
|
<div style={{ marginBottom: 16 }}>
|
||||||
<Col span={8}>
|
<TweenOneGroup
|
||||||
{!isAddNewVisible &&
|
enter={{
|
||||||
<div style={{float: "right"}}>
|
scale: 0.8,
|
||||||
<Button
|
opacity: 0,
|
||||||
icon="plus"
|
type: 'from',
|
||||||
// type="primary"
|
duration: 100,
|
||||||
size="small"
|
onComplete: e => {
|
||||||
onClick={() => {
|
e.target.style = '';
|
||||||
this.setState({
|
},
|
||||||
isAddNewVisible: true,
|
}}
|
||||||
inputVisible: true
|
leave={{ opacity: 0, width: 0, scale: 0, duration: 200 }}
|
||||||
}, () => this.input.focus())
|
appear={false}
|
||||||
}} htmlType="button">Add
|
>
|
||||||
</Button>
|
{temporaryElements}
|
||||||
</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 && (
|
{inputVisible && (
|
||||||
<Input
|
<Input
|
||||||
ref={this.saveInputRef}
|
ref={this.saveInputRef}
|
||||||
type="text"
|
type="text"
|
||||||
size="small"
|
size="small"
|
||||||
style={{width: 120}}
|
style={{ width: 120 }}
|
||||||
value={inputValue}
|
value={inputValue}
|
||||||
onChange={this.handleInputChange}
|
onChange={this.handleInputChange}
|
||||||
onBlur={this.handleInputConfirm}
|
onBlur={this.handleInputConfirm}
|
||||||
onPressEnter={this.handleInputConfirm}
|
onPressEnter={this.handleInputConfirm}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{!inputVisible && (
|
{!inputVisible && (
|
||||||
<Tag onClick={this.showInput}
|
<Tag
|
||||||
style={{background: '#fff', borderStyle: 'dashed'}}>
|
onClick={this.showInput}
|
||||||
<Icon type="plus"/> New Tag
|
style={{ background: '#fff', borderStyle: 'dashed' }}
|
||||||
</Tag>
|
>
|
||||||
)}
|
<Icon type="plus" /> New Tag
|
||||||
</TweenOneGroup>
|
</Tag>
|
||||||
</div>
|
)}
|
||||||
<div>
|
</TweenOneGroup>
|
||||||
{tempElements.length > 0 && (
|
</div>
|
||||||
<span>
|
<div>
|
||||||
<Button
|
{tempElements.length > 0 && (
|
||||||
onClick={this.handleSave}
|
<span>
|
||||||
htmlType="button" type="primary"
|
<Button
|
||||||
size="small"
|
onClick={this.handleSave}
|
||||||
disabled={tempElements.length === 0}>
|
htmlType="button"
|
||||||
Save
|
type="primary"
|
||||||
</Button>
|
size="small"
|
||||||
<Divider type="vertical"/>
|
disabled={tempElements.length === 0}
|
||||||
</span>
|
>
|
||||||
)}
|
Save
|
||||||
< Button
|
</Button>
|
||||||
onClick={this.handleCloseButton}
|
<Divider type="vertical" />
|
||||||
size="small">
|
</span>
|
||||||
Cancel
|
)}
|
||||||
</Button>
|
<Button onClick={this.handleCloseButton} size="small">
|
||||||
</div>
|
Cancel
|
||||||
</div>
|
</Button>
|
||||||
}
|
</div>
|
||||||
<Divider dashed="true"/>
|
</div>
|
||||||
<div style={{marginTop: 8}}>
|
)}
|
||||||
<TweenOneGroup
|
<Divider dashed="true" />
|
||||||
enter={{
|
<div style={{ marginTop: 8 }}>
|
||||||
scale: 0.8,
|
<TweenOneGroup
|
||||||
opacity: 0,
|
enter={{
|
||||||
type: 'from',
|
scale: 0.8,
|
||||||
duration: 100,
|
opacity: 0,
|
||||||
onComplete: e => {
|
type: 'from',
|
||||||
e.target.style = '';
|
duration: 100,
|
||||||
},
|
onComplete: e => {
|
||||||
}}
|
e.target.style = '';
|
||||||
leave={{opacity: 0, width: 0, scale: 0, duration: 200}}
|
},
|
||||||
appear={false}
|
}}
|
||||||
>
|
leave={{ opacity: 0, width: 0, scale: 0, duration: 200 }}
|
||||||
{tagsElements}
|
appear={false}
|
||||||
</TweenOneGroup>
|
>
|
||||||
</div>
|
{tagsElements}
|
||||||
</Spin>
|
</TweenOneGroup>
|
||||||
</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>
|
</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);
|
export default withConfigContext(ManageTags);
|
||||||
|
|||||||
@ -16,209 +16,231 @@
|
|||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from "react";
|
import React from 'react';
|
||||||
import {
|
import { Card, Button, Steps, Row, Col, Form, Result, Spin } from 'antd';
|
||||||
Card,
|
import axios from 'axios';
|
||||||
Button,
|
import { withRouter } from 'react-router-dom';
|
||||||
Steps,
|
import NewAppDetailsForm from './subForms/NewAppDetailsForm';
|
||||||
Row,
|
import NewAppUploadForm from './subForms/NewAppUploadForm';
|
||||||
Col,
|
import { withConfigContext } from '../../context/ConfigContext';
|
||||||
Form,
|
import { handleApiError } from '../../js/Utils';
|
||||||
Result,
|
|
||||||
notification,
|
|
||||||
Spin
|
|
||||||
} from "antd";
|
|
||||||
import axios from "axios";
|
|
||||||
import {withRouter} from 'react-router-dom';
|
|
||||||
import NewAppDetailsForm from "./subForms/NewAppDetailsForm";
|
|
||||||
import NewAppUploadForm from "./subForms/NewAppUploadForm";
|
|
||||||
import {withConfigContext} from "../../context/ConfigContext";
|
|
||||||
import {handleApiError} from "../../js/Utils";
|
|
||||||
|
|
||||||
const {Step} = Steps;
|
|
||||||
|
|
||||||
|
const { Step } = Steps;
|
||||||
|
|
||||||
class AddNewAppFormComponent extends React.Component {
|
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,
|
||||||
|
supportedOsVersions: [],
|
||||||
|
errorText: '',
|
||||||
|
forbiddenErrors: {
|
||||||
|
supportedOsVersions: false,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
constructor(props) {
|
onSuccessApplicationData = application => {
|
||||||
super(props);
|
const { formConfig } = this.props;
|
||||||
this.state = {
|
if (
|
||||||
current: 0,
|
application.hasOwnProperty('deviceType') &&
|
||||||
categories: [],
|
formConfig.installationType !== 'WEB_CLIP' &&
|
||||||
tags: [],
|
formConfig.installationType !== 'CUSTOM'
|
||||||
icons: [],
|
) {
|
||||||
screenshots: [],
|
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,
|
loading: false,
|
||||||
binaryFiles: [],
|
current: 2,
|
||||||
application: null,
|
});
|
||||||
release: null,
|
} else {
|
||||||
isError: false,
|
this.setState({
|
||||||
deviceType: null,
|
loading: false,
|
||||||
supportedOsVersions: [],
|
isError: true,
|
||||||
errorText: "",
|
current: 2,
|
||||||
forbiddenErrors: {
|
});
|
||||||
supportedOsVersions: false
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
onSuccessApplicationData = (application) => {
|
|
||||||
const {formConfig} = this.props;
|
|
||||||
if (application.hasOwnProperty("deviceType") &&
|
|
||||||
formConfig.installationType !== "WEB_CLIP" &&
|
|
||||||
formConfig.installationType !== "CUSTOM") {
|
|
||||||
this.getSupportedOsVersions(application.deviceType);
|
|
||||||
}
|
}
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
handleApiError(error, error.response.data.data);
|
||||||
this.setState({
|
this.setState({
|
||||||
application,
|
loading: false,
|
||||||
current: 1
|
isError: true,
|
||||||
|
current: 2,
|
||||||
|
errorText: error.response.data.data,
|
||||||
});
|
});
|
||||||
};
|
});
|
||||||
|
};
|
||||||
|
|
||||||
onSuccessReleaseData = (releaseData) => {
|
onClickBackButton = () => {
|
||||||
const config = this.props.context;
|
const current = this.state.current - 1;
|
||||||
this.setState({
|
this.setState({ current });
|
||||||
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);
|
|
||||||
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
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const {loading, current, isError, supportedOsVersions, errorText, forbiddenErrors} = 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}
|
|
||||||
onSuccessApplicationData={this.onSuccessApplicationData}/>
|
|
||||||
</div>
|
|
||||||
<div style={{display: (current === 1 ? 'unset' : 'none')}}>
|
|
||||||
<NewAppUploadForm
|
|
||||||
forbiddenErrors={forbiddenErrors}
|
|
||||||
formConfig={formConfig}
|
|
||||||
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>
|
|
||||||
|
|
||||||
|
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,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const {
|
||||||
|
loading,
|
||||||
|
current,
|
||||||
|
isError,
|
||||||
|
supportedOsVersions,
|
||||||
|
errorText,
|
||||||
|
forbiddenErrors,
|
||||||
|
} = 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}
|
||||||
|
onSuccessApplicationData={this.onSuccessApplicationData}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div style={{ display: current === 1 ? 'unset' : 'none' }}>
|
||||||
|
<NewAppUploadForm
|
||||||
|
forbiddenErrors={forbiddenErrors}
|
||||||
|
formConfig={formConfig}
|
||||||
|
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));
|
const AddNewAppForm = withRouter(
|
||||||
|
Form.create({ name: 'add-new-app' })(AddNewAppFormComponent),
|
||||||
|
);
|
||||||
export default withConfigContext(AddNewAppForm);
|
export default withConfigContext(AddNewAppForm);
|
||||||
|
|||||||
@ -16,430 +16,481 @@
|
|||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from "react";
|
import React from 'react';
|
||||||
import {Alert, Button, Col, Divider, Form, Icon, Input, notification, Row, Select, Spin, Switch, Upload} from "antd";
|
import { Alert, Button, Col, Form, Input, Row, Select, Spin } from 'antd';
|
||||||
import axios from "axios";
|
import axios from 'axios';
|
||||||
import {withConfigContext} from "../../../context/ConfigContext";
|
import { withConfigContext } from '../../../context/ConfigContext';
|
||||||
import {handleApiError} from "../../../js/Utils";
|
import { handleApiError } from '../../../js/Utils';
|
||||||
import debounce from 'lodash.debounce';
|
import debounce from 'lodash.debounce';
|
||||||
|
|
||||||
const formItemLayout = {
|
const formItemLayout = {
|
||||||
labelCol: {
|
labelCol: {
|
||||||
xs: {span: 24},
|
xs: { span: 24 },
|
||||||
sm: {span: 5},
|
sm: { span: 5 },
|
||||||
},
|
},
|
||||||
wrapperCol: {
|
wrapperCol: {
|
||||||
xs: {span: 24},
|
xs: { span: 24 },
|
||||||
sm: {span: 19},
|
sm: { span: 19 },
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
const {Option} = Select;
|
const { Option } = Select;
|
||||||
const {TextArea} = Input;
|
const { TextArea } = Input;
|
||||||
|
|
||||||
class NewAppDetailsForm extends React.Component {
|
class NewAppDetailsForm extends React.Component {
|
||||||
|
constructor(props) {
|
||||||
constructor(props) {
|
super(props);
|
||||||
super(props);
|
this.state = {
|
||||||
this.state = {
|
categories: [],
|
||||||
categories: [],
|
tags: [],
|
||||||
tags: [],
|
deviceTypes: [],
|
||||||
deviceTypes: [],
|
fetching: false,
|
||||||
fetching: false,
|
roleSearchValue: [],
|
||||||
roleSearchValue: [],
|
unrestrictedRoles: [],
|
||||||
unrestrictedRoles: [],
|
forbiddenErrors: {
|
||||||
forbiddenErrors: {
|
categories: false,
|
||||||
categories: false,
|
tags: false,
|
||||||
tags: false,
|
deviceTypes: false,
|
||||||
deviceTypes: false,
|
roles: false,
|
||||||
roles: false
|
},
|
||||||
}
|
|
||||||
};
|
|
||||||
this.lastFetchId = 0;
|
|
||||||
this.fetchRoles = debounce(this.fetchRoles, 800);
|
|
||||||
}
|
|
||||||
|
|
||||||
handleSubmit = e => {
|
|
||||||
e.preventDefault();
|
|
||||||
const {formConfig} = this.props;
|
|
||||||
const {specificElements} = formConfig;
|
|
||||||
|
|
||||||
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.props.onSuccessApplicationData(application);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
this.lastFetchId = 0;
|
||||||
|
this.fetchRoles = debounce(this.fetchRoles, 800);
|
||||||
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
handleSubmit = e => {
|
||||||
this.getCategories();
|
e.preventDefault();
|
||||||
this.getTags();
|
const { formConfig } = this.props;
|
||||||
this.getDeviceTypes();
|
|
||||||
}
|
|
||||||
|
|
||||||
getCategories = () => {
|
this.props.form.validateFields((err, values) => {
|
||||||
const config = this.props.context;
|
if (!err) {
|
||||||
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);
|
|
||||||
if (error.hasOwnProperty("response") && error.response.status === 403) {
|
|
||||||
const {forbiddenErrors} = this.state;
|
|
||||||
forbiddenErrors.categories = true;
|
|
||||||
this.setState({
|
|
||||||
forbiddenErrors,
|
|
||||||
loading: false
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
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);
|
|
||||||
if (error.hasOwnProperty("response") && error.response.status === 403) {
|
|
||||||
const {forbiddenErrors} = this.state;
|
|
||||||
forbiddenErrors.tags = true;
|
|
||||||
this.setState({
|
|
||||||
forbiddenErrors,
|
|
||||||
loading: false
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
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);
|
|
||||||
if (error.hasOwnProperty("response") && error.response.status === 403) {
|
|
||||||
const {forbiddenErrors} = this.state;
|
|
||||||
forbiddenErrors.roles = true;
|
|
||||||
this.setState({
|
|
||||||
forbiddenErrors,
|
|
||||||
fetching: false
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
this.setState({
|
|
||||||
fetching: false
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
handleRoleSearch = roleSearchValue => {
|
|
||||||
this.setState({
|
this.setState({
|
||||||
roleSearchValue,
|
loading: true,
|
||||||
unrestrictedRoles: [],
|
|
||||||
fetching: false,
|
|
||||||
});
|
});
|
||||||
};
|
const {
|
||||||
|
name,
|
||||||
|
description,
|
||||||
|
categories,
|
||||||
|
tags,
|
||||||
|
unrestrictedRoles,
|
||||||
|
} = values;
|
||||||
|
const unrestrictedRolesData = [];
|
||||||
|
unrestrictedRoles.map(val => {
|
||||||
|
unrestrictedRolesData.push(val.key);
|
||||||
|
});
|
||||||
|
const application = {
|
||||||
|
name,
|
||||||
|
description,
|
||||||
|
categories,
|
||||||
|
tags,
|
||||||
|
unrestrictedRoles: unrestrictedRolesData,
|
||||||
|
};
|
||||||
|
|
||||||
render() {
|
if (formConfig.installationType !== 'WEB_CLIP') {
|
||||||
const {formConfig} = this.props;
|
application.deviceType = values.deviceType;
|
||||||
const {categories, tags, deviceTypes, fetching, roleSearchValue, unrestrictedRoles, forbiddenErrors} = this.state;
|
} else {
|
||||||
const {getFieldDecorator} = this.props.form;
|
application.type = 'WEB_CLIP';
|
||||||
|
application.deviceType = 'ALL';
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
this.props.onSuccessApplicationData(application);
|
||||||
<div>
|
}
|
||||||
<Row>
|
});
|
||||||
<Col md={5}>
|
};
|
||||||
|
|
||||||
</Col>
|
componentDidMount() {
|
||||||
<Col md={14}>
|
this.getCategories();
|
||||||
<Form
|
this.getTags();
|
||||||
labelAlign="right"
|
this.getDeviceTypes();
|
||||||
layout="horizontal"
|
}
|
||||||
onSubmit={this.handleSubmit}>
|
|
||||||
{formConfig.installationType !== "WEB_CLIP" && (
|
|
||||||
<div>
|
|
||||||
{(forbiddenErrors.deviceTypes) && (
|
|
||||||
<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">
|
|
||||||
{
|
|
||||||
deviceTypes.map(deviceType => {
|
|
||||||
return (
|
|
||||||
<Option
|
|
||||||
key={deviceType.name}>
|
|
||||||
{deviceType.name}
|
|
||||||
</Option>
|
|
||||||
)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
</Select>
|
|
||||||
)}
|
|
||||||
</Form.Item>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/*app name*/}
|
getCategories = () => {
|
||||||
<Form.Item {...formItemLayout} label="App Name">
|
const config = this.props.context;
|
||||||
{getFieldDecorator('name', {
|
axios
|
||||||
rules: [{
|
.get(
|
||||||
required: true,
|
window.location.origin +
|
||||||
message: 'Please input a name'
|
config.serverConfig.invoker.uri +
|
||||||
}],
|
config.serverConfig.invoker.publisher +
|
||||||
})(
|
'/applications/categories',
|
||||||
<Input placeholder="ex: Lorem App"/>
|
)
|
||||||
)}
|
.then(res => {
|
||||||
</Form.Item>
|
if (res.status === 200) {
|
||||||
|
let categories = JSON.parse(res.data.data);
|
||||||
{/*description*/}
|
this.setState({
|
||||||
<Form.Item {...formItemLayout} label="Description">
|
categories: categories,
|
||||||
{getFieldDecorator('description', {
|
loading: false,
|
||||||
rules: [{
|
});
|
||||||
required: true,
|
}
|
||||||
message: 'Please enter a description'
|
})
|
||||||
}],
|
.catch(error => {
|
||||||
})(
|
handleApiError(
|
||||||
<TextArea placeholder="Enter the description..." rows={7}/>
|
error,
|
||||||
)}
|
'Error occurred while trying to load categories.',
|
||||||
</Form.Item>
|
true,
|
||||||
|
|
||||||
{/*Unrestricted Roles*/}
|
|
||||||
{(forbiddenErrors.roles) && (
|
|
||||||
<Alert
|
|
||||||
message="You don't have permission to view roles."
|
|
||||||
type="warning"
|
|
||||||
banner
|
|
||||||
closable/>
|
|
||||||
)}
|
|
||||||
<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>
|
|
||||||
{(forbiddenErrors.categories) && (
|
|
||||||
<Alert
|
|
||||||
message="You don't have permission to view categories."
|
|
||||||
type="warning"
|
|
||||||
banner
|
|
||||||
closable/>
|
|
||||||
)}
|
|
||||||
<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>
|
|
||||||
{(forbiddenErrors.tags) && (
|
|
||||||
<Alert
|
|
||||||
message="You don't have permission to view tags."
|
|
||||||
type="warning"
|
|
||||||
banner
|
|
||||||
closable/>
|
|
||||||
)}
|
|
||||||
<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>
|
|
||||||
);
|
);
|
||||||
}
|
if (error.hasOwnProperty('response') && error.response.status === 403) {
|
||||||
|
const { forbiddenErrors } = this.state;
|
||||||
|
forbiddenErrors.categories = true;
|
||||||
|
this.setState({
|
||||||
|
forbiddenErrors,
|
||||||
|
loading: false,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
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,
|
||||||
|
);
|
||||||
|
if (error.hasOwnProperty('response') && error.response.status === 403) {
|
||||||
|
const { forbiddenErrors } = this.state;
|
||||||
|
forbiddenErrors.tags = true;
|
||||||
|
this.setState({
|
||||||
|
forbiddenErrors,
|
||||||
|
loading: false,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
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,
|
||||||
|
);
|
||||||
|
if (error.hasOwnProperty('response') && error.response.status === 403) {
|
||||||
|
const { forbiddenErrors } = this.state;
|
||||||
|
forbiddenErrors.roles = true;
|
||||||
|
this.setState({
|
||||||
|
forbiddenErrors,
|
||||||
|
fetching: false,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
this.setState({
|
||||||
|
fetching: false,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
handleRoleSearch = roleSearchValue => {
|
||||||
|
this.setState({
|
||||||
|
roleSearchValue,
|
||||||
|
unrestrictedRoles: [],
|
||||||
|
fetching: false,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { formConfig } = this.props;
|
||||||
|
const {
|
||||||
|
categories,
|
||||||
|
tags,
|
||||||
|
deviceTypes,
|
||||||
|
fetching,
|
||||||
|
unrestrictedRoles,
|
||||||
|
forbiddenErrors,
|
||||||
|
} = 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>
|
||||||
|
{forbiddenErrors.deviceTypes && (
|
||||||
|
<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"
|
||||||
|
>
|
||||||
|
{deviceTypes.map(deviceType => {
|
||||||
|
return (
|
||||||
|
<Option key={deviceType.name}>
|
||||||
|
{deviceType.name}
|
||||||
|
</Option>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</Select>,
|
||||||
|
)}
|
||||||
|
</Form.Item>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* app name*/}
|
||||||
|
<Form.Item {...formItemLayout} label="App Name">
|
||||||
|
{getFieldDecorator('name', {
|
||||||
|
rules: [
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
message: 'Please input a name',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})(<Input placeholder="ex: Lorem App" />)}
|
||||||
|
</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*/}
|
||||||
|
{forbiddenErrors.roles && (
|
||||||
|
<Alert
|
||||||
|
message="You don't have permission to view roles."
|
||||||
|
type="warning"
|
||||||
|
banner
|
||||||
|
closable
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<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>
|
||||||
|
{forbiddenErrors.categories && (
|
||||||
|
<Alert
|
||||||
|
message="You don't have permission to view categories."
|
||||||
|
type="warning"
|
||||||
|
banner
|
||||||
|
closable
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<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>
|
||||||
|
{forbiddenErrors.tags && (
|
||||||
|
<Alert
|
||||||
|
message="You don't have permission to view tags."
|
||||||
|
type="warning"
|
||||||
|
banner
|
||||||
|
closable
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<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));
|
export default withConfigContext(
|
||||||
|
Form.create({ name: 'app-details-form' })(NewAppDetailsForm),
|
||||||
|
);
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@ -16,146 +16,162 @@
|
|||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from "react";
|
import React from 'react';
|
||||||
import {Form, notification, Spin, Card, Row, Col} from "antd";
|
import { Form, notification, Spin, Card, Row, Col } from 'antd';
|
||||||
import axios from "axios";
|
import axios from 'axios';
|
||||||
import {withRouter} from 'react-router-dom'
|
import { withRouter } from 'react-router-dom';
|
||||||
import {withConfigContext} from "../../context/ConfigContext";
|
import { withConfigContext } from '../../context/ConfigContext';
|
||||||
import {handleApiError} from "../../js/Utils";
|
import { handleApiError } from '../../js/Utils';
|
||||||
import NewAppUploadForm from "../new-app/subForms/NewAppUploadForm";
|
import NewAppUploadForm from '../new-app/subForms/NewAppUploadForm';
|
||||||
|
|
||||||
const formConfig = {
|
const formConfig = {
|
||||||
specificElements: {
|
specificElements: {
|
||||||
binaryFile: {
|
binaryFile: {
|
||||||
required: true
|
required: true,
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
class AddNewReleaseFormComponent extends React.Component {
|
class AddNewReleaseFormComponent extends React.Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
loading: false,
|
||||||
|
supportedOsVersions: [],
|
||||||
|
application: null,
|
||||||
|
release: null,
|
||||||
|
deviceType: null,
|
||||||
|
forbiddenErrors: {
|
||||||
|
supportedOsVersions: false,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
constructor(props) {
|
componentDidMount() {
|
||||||
super(props);
|
this.getSupportedOsVersions(this.props.deviceType);
|
||||||
this.state = {
|
}
|
||||||
|
|
||||||
|
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,
|
loading: false,
|
||||||
supportedOsVersions: [],
|
});
|
||||||
application: null,
|
}
|
||||||
release: null,
|
})
|
||||||
deviceType: null,
|
.catch(error => {
|
||||||
forbiddenErrors: {
|
handleApiError(
|
||||||
supportedOsVersions: false
|
error,
|
||||||
}
|
'Error occurred while trying to load supported OS versions.',
|
||||||
};
|
true,
|
||||||
}
|
|
||||||
|
|
||||||
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('/publisher/apps/releases/' + uuid);
|
|
||||||
|
|
||||||
} 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}
|
|
||||||
supportedOsVersions={supportedOsVersions}
|
|
||||||
onSuccessReleaseData={this.onSuccessReleaseData}
|
|
||||||
onClickBackButton={this.onClickBackButton}
|
|
||||||
/>
|
|
||||||
</Card>
|
|
||||||
</Col>
|
|
||||||
</Row>
|
|
||||||
</Spin>
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
}
|
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('/publisher/apps/releases/' + uuid);
|
||||||
|
} 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}
|
||||||
|
supportedOsVersions={supportedOsVersions}
|
||||||
|
onSuccessReleaseData={this.onSuccessReleaseData}
|
||||||
|
onClickBackButton={this.onClickBackButton}
|
||||||
|
/>
|
||||||
|
</Card>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
</Spin>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const AddReleaseForm = withRouter(Form.create({name: 'add-new-release'})(AddNewReleaseFormComponent));
|
const AddReleaseForm = withRouter(
|
||||||
|
Form.create({ name: 'add-new-release' })(AddNewReleaseFormComponent),
|
||||||
|
);
|
||||||
export default withConfigContext(AddReleaseForm);
|
export default withConfigContext(AddReleaseForm);
|
||||||
|
|||||||
@ -16,19 +16,19 @@
|
|||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from "react";
|
import React from 'react';
|
||||||
|
|
||||||
const ConfigContext = React.createContext();
|
const ConfigContext = React.createContext();
|
||||||
|
|
||||||
export const withConfigContext = Component => {
|
export const withConfigContext = Component => {
|
||||||
return props => (
|
// eslint-disable-next-line react/display-name
|
||||||
<ConfigContext.Consumer>
|
return props => (
|
||||||
{context => {
|
<ConfigContext.Consumer>
|
||||||
return <Component {...props} context={context}/>;
|
{context => {
|
||||||
}}
|
return <Component {...props} context={context} />;
|
||||||
</ConfigContext.Consumer>
|
}}
|
||||||
);
|
</ConfigContext.Consumer>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default ConfigContext;
|
export default ConfigContext;
|
||||||
|
|
||||||
|
|||||||
@ -19,91 +19,87 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import ReactDOM from 'react-dom';
|
import ReactDOM from 'react-dom';
|
||||||
import * as serviceWorker from './serviceWorker';
|
import * as serviceWorker from './serviceWorker';
|
||||||
import App from "./App";
|
import App from './App';
|
||||||
import Login from "./pages/Login";
|
import Login from './pages/Login';
|
||||||
import Dashboard from "./pages/dashboard/Dashboard";
|
import Dashboard from './pages/dashboard/Dashboard';
|
||||||
import Apps from "./pages/dashboard/apps/Apps";
|
import Apps from './pages/dashboard/apps/Apps';
|
||||||
import Release from "./pages/dashboard/apps/release/Release";
|
import Release from './pages/dashboard/apps/release/Release';
|
||||||
import AddNewEnterpriseApp from "./pages/dashboard/add-new-app/AddNewEnterpriseApp";
|
import AddNewEnterpriseApp from './pages/dashboard/add-new-app/AddNewEnterpriseApp';
|
||||||
import Mange from "./pages/dashboard/manage/Manage";
|
import Mange from './pages/dashboard/manage/Manage';
|
||||||
import './index.css';
|
import './index.css';
|
||||||
import AddNewPublicApp from "./pages/dashboard/add-new-app/AddNewPublicApp";
|
import AddNewPublicApp from './pages/dashboard/add-new-app/AddNewPublicApp';
|
||||||
import AddNewWebClip from "./pages/dashboard/add-new-app/AddNewWebClip";
|
import AddNewWebClip from './pages/dashboard/add-new-app/AddNewWebClip';
|
||||||
import AddNewRelease from "./pages/dashboard/add-new-release/AddNewRelease";
|
import AddNewRelease from './pages/dashboard/add-new-release/AddNewRelease';
|
||||||
import AddNewCustomApp from "./pages/dashboard/add-new-app/AddNewCustomApp";
|
import AddNewCustomApp from './pages/dashboard/add-new-app/AddNewCustomApp';
|
||||||
import ManageAndroidEnterprise from "./pages/dashboard/manage/android-enterprise/ManageAndroidEnterprise";
|
import ManageAndroidEnterprise from './pages/dashboard/manage/android-enterprise/ManageAndroidEnterprise';
|
||||||
import Page from "./pages/dashboard/manage/android-enterprise/page/Page";
|
import Page from './pages/dashboard/manage/android-enterprise/page/Page';
|
||||||
|
|
||||||
|
|
||||||
const routes = [
|
const routes = [
|
||||||
{
|
{
|
||||||
path: '/publisher/login',
|
path: '/publisher/login',
|
||||||
|
exact: true,
|
||||||
|
component: Login,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/publisher/',
|
||||||
|
exact: false,
|
||||||
|
component: Dashboard,
|
||||||
|
routes: [
|
||||||
|
{
|
||||||
|
path: '/publisher/apps',
|
||||||
|
component: Apps,
|
||||||
exact: true,
|
exact: true,
|
||||||
component: Login
|
},
|
||||||
},
|
{
|
||||||
{
|
path: '/publisher/apps/releases/:uuid',
|
||||||
path: '/publisher/',
|
exact: true,
|
||||||
exact: false,
|
component: Release,
|
||||||
component: Dashboard,
|
},
|
||||||
routes: [
|
{
|
||||||
{
|
path: '/publisher/apps/:deviceType/:appId/add-release',
|
||||||
path: '/publisher/apps',
|
component: AddNewRelease,
|
||||||
component: Apps,
|
exact: true,
|
||||||
exact: true
|
},
|
||||||
},
|
{
|
||||||
{
|
path: '/publisher/add-new-app/enterprise',
|
||||||
path: '/publisher/apps/releases/:uuid',
|
component: AddNewEnterpriseApp,
|
||||||
exact: true,
|
exact: true,
|
||||||
component: Release
|
},
|
||||||
},
|
{
|
||||||
{
|
path: '/publisher/add-new-app/public',
|
||||||
path: '/publisher/apps/:deviceType/:appId/add-release',
|
component: AddNewPublicApp,
|
||||||
component: AddNewRelease,
|
exact: true,
|
||||||
exact: true
|
},
|
||||||
},
|
{
|
||||||
{
|
path: '/publisher/add-new-app/web-clip',
|
||||||
path: '/publisher/add-new-app/enterprise',
|
component: AddNewWebClip,
|
||||||
component: AddNewEnterpriseApp,
|
exact: true,
|
||||||
exact: true
|
},
|
||||||
},
|
{
|
||||||
{
|
path: '/publisher/add-new-app/custom-app',
|
||||||
path: '/publisher/add-new-app/public',
|
component: AddNewCustomApp,
|
||||||
component: AddNewPublicApp,
|
exact: true,
|
||||||
exact: true
|
},
|
||||||
},
|
{
|
||||||
{
|
path: '/publisher/manage',
|
||||||
path: '/publisher/add-new-app/web-clip',
|
component: Mange,
|
||||||
component: AddNewWebClip,
|
exact: true,
|
||||||
exact: true
|
},
|
||||||
},
|
{
|
||||||
{
|
path: '/publisher/manage/android-enterprise',
|
||||||
path: '/publisher/add-new-app/custom-app',
|
component: ManageAndroidEnterprise,
|
||||||
component: AddNewCustomApp,
|
exact: true,
|
||||||
exact: true
|
},
|
||||||
},
|
{
|
||||||
{
|
path: '/publisher/manage/android-enterprise/pages/:pageName/:pageId',
|
||||||
path: '/publisher/manage',
|
component: Page,
|
||||||
component: Mange,
|
exact: true,
|
||||||
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'));
|
||||||
ReactDOM.render(
|
|
||||||
<App routes={routes}/>,
|
|
||||||
document.getElementById('root'));
|
|
||||||
|
|
||||||
// If you want your app to work offline and load faster, you can change
|
// If you want your app to work offline and load faster, you can change
|
||||||
// unregister() to register() below. Note this comes with some pitfalls.
|
// unregister() to register() below. Note this comes with some pitfalls.
|
||||||
|
|||||||
@ -16,18 +16,29 @@
|
|||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {notification} from "antd";
|
import { notification } from 'antd';
|
||||||
|
|
||||||
export const handleApiError = (error, message, isForbiddenMessageSilent = false) => {
|
export const handleApiError = (
|
||||||
if (error.hasOwnProperty("response") && error.response.status === 401) {
|
error,
|
||||||
const redirectUrl = encodeURI(window.location.href);
|
message,
|
||||||
window.location.href = window.location.origin + `/publisher/login?redirect=${redirectUrl}`;
|
isForbiddenMessageSilent = false,
|
||||||
// silence 403 forbidden message
|
) => {
|
||||||
} else if (!(isForbiddenMessageSilent && error.hasOwnProperty("response") && error.response.status === 403)) {
|
if (error.hasOwnProperty('response') && error.response.status === 401) {
|
||||||
notification["error"]({
|
const redirectUrl = encodeURI(window.location.href);
|
||||||
message: "There was a problem",
|
window.location.href =
|
||||||
duration: 10,
|
window.location.origin + `/publisher/login?redirect=${redirectUrl}`;
|
||||||
description: message,
|
// 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,
|
||||||
|
});
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@ -16,160 +16,183 @@
|
|||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from "react";
|
import React from 'react';
|
||||||
import {Typography, Row, Col, Form, Icon, Input, Button, Checkbox, message, notification} from 'antd';
|
import {
|
||||||
|
Typography,
|
||||||
|
Row,
|
||||||
|
Col,
|
||||||
|
Form,
|
||||||
|
Icon,
|
||||||
|
Input,
|
||||||
|
Button,
|
||||||
|
message,
|
||||||
|
notification,
|
||||||
|
} from 'antd';
|
||||||
import './Login.css';
|
import './Login.css';
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
import "./Login.css";
|
import './Login.css';
|
||||||
import {withConfigContext} from "../context/ConfigContext";
|
import { withConfigContext } from '../context/ConfigContext';
|
||||||
import {handleApiError} from "../js/Utils";
|
|
||||||
|
|
||||||
const {Title} = Typography;
|
const { Title } = Typography;
|
||||||
const {Text} = Typography;
|
const { Text } = Typography;
|
||||||
|
|
||||||
class Login extends React.Component {
|
class Login extends React.Component {
|
||||||
render() {
|
render() {
|
||||||
const config = this.props.context;
|
const config = this.props.context;
|
||||||
return (
|
return (
|
||||||
<div className="login">
|
<div className="login">
|
||||||
<div className="background">
|
<div className="background"></div>
|
||||||
</div>
|
<div className="content">
|
||||||
<div className="content">
|
<Row>
|
||||||
<Row>
|
<Col xs={3} sm={3} md={10}></Col>
|
||||||
<Col xs={3} sm={3} md={10}>
|
<Col xs={18} sm={18} md={4}>
|
||||||
</Col>
|
<Row style={{ marginBottom: 20 }}>
|
||||||
<Col xs={18} sm={18} md={4}>
|
<Col style={{ textAlign: 'center' }}>
|
||||||
<Row style={{marginBottom: 20}}>
|
<img
|
||||||
<Col style={{textAlign: "center"}}>
|
style={{
|
||||||
<img style={
|
marginTop: 36,
|
||||||
{
|
height: 60,
|
||||||
marginTop: 36,
|
}}
|
||||||
height: 60
|
src={config.theme.logo}
|
||||||
}
|
/>
|
||||||
}
|
</Col>
|
||||||
src={config.theme.logo}/>
|
</Row>
|
||||||
</Col>
|
<Title level={2}>Login</Title>
|
||||||
</Row>
|
<WrappedNormalLoginForm />
|
||||||
<Title level={2}>Login</Title>
|
</Col>
|
||||||
<WrappedNormalLoginForm/>
|
</Row>
|
||||||
</Col>
|
<Row>
|
||||||
</Row>
|
<Col span={4} offset={10}></Col>
|
||||||
<Row>
|
</Row>
|
||||||
<Col span={4} offset={10}>
|
</div>
|
||||||
|
</div>
|
||||||
</Col>
|
);
|
||||||
</Row>
|
}
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class NormalLoginForm extends React.Component {
|
class NormalLoginForm extends React.Component {
|
||||||
|
constructor(props) {
|
||||||
constructor(props) {
|
super(props);
|
||||||
super(props);
|
this.state = {
|
||||||
this.state = {
|
inValid: false,
|
||||||
inValid: false,
|
loading: 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() {
|
handleSubmit = e => {
|
||||||
const {getFieldDecorator} = this.props.form;
|
const config = this.props.context;
|
||||||
let errorMsg = "";
|
const thisForm = this;
|
||||||
if (this.state.inValid) {
|
e.preventDefault();
|
||||||
errorMsg = <Text type="danger">Invalid Login Details</Text>;
|
this.props.form.validateFields((err, values) => {
|
||||||
}
|
thisForm.setState({
|
||||||
let loading = "";
|
inValid: false,
|
||||||
if (this.state.loading) {
|
});
|
||||||
loading = <Text type="secondary">Loading..</Text>;
|
if (!err) {
|
||||||
}
|
thisForm.setState({
|
||||||
return (
|
loading: true,
|
||||||
<Form onSubmit={this.handleSubmit} className="login-form">
|
});
|
||||||
<Form.Item>
|
const parameters = {
|
||||||
{getFieldDecorator('username', {
|
username: values.username,
|
||||||
rules: [{required: true, message: 'Please enter your username'}],
|
password: values.password,
|
||||||
})(
|
platform: 'publisher',
|
||||||
<Input style={{height: 32}} prefix={<Icon type="user" style={{color: 'rgba(0,0,0,.25)'}}/>}
|
};
|
||||||
placeholder="Username"/>
|
|
||||||
)}
|
const request = Object.keys(parameters)
|
||||||
</Form.Item>
|
.map(key => key + '=' + parameters[key])
|
||||||
<Form.Item>
|
.join('&');
|
||||||
{getFieldDecorator('password', {
|
|
||||||
rules: [{required: true, message: 'Please enter your password'}],
|
axios
|
||||||
})(
|
.post(window.location.origin + config.serverConfig.loginUri, request)
|
||||||
<Input style={{height: 32}}
|
.then(res => {
|
||||||
prefix={<Icon type="lock" style={{color: 'rgba(0,0,0,.25)'}}/>} type="password"
|
if (res.status === 200) {
|
||||||
placeholder="Password"/>
|
let redirectUrl = window.location.origin + '/publisher';
|
||||||
)}
|
const searchParams = new URLSearchParams(window.location.search);
|
||||||
</Form.Item>
|
if (searchParams.has('redirect')) {
|
||||||
{loading}
|
redirectUrl = searchParams.get('redirect');
|
||||||
{errorMsg}
|
}
|
||||||
<Form.Item>
|
window.location = redirectUrl;
|
||||||
<Button loading={this.state.loading} block type="primary" htmlType="submit" className="login-form-button">
|
}
|
||||||
Log in
|
})
|
||||||
</Button>
|
.catch(function(error) {
|
||||||
</Form.Item>
|
if (
|
||||||
</Form>
|
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={<Icon type="user" 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={<Icon type="lock" 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));
|
const WrappedNormalLoginForm = Form.create({ name: 'normal_login' })(
|
||||||
|
withConfigContext(NormalLoginForm),
|
||||||
|
);
|
||||||
|
|
||||||
export default withConfigContext(Login);
|
export default withConfigContext(Login);
|
||||||
|
|||||||
@ -16,214 +16,247 @@
|
|||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from "react";
|
import React from 'react';
|
||||||
import {Layout, Menu, Icon, Drawer, Button} from 'antd';
|
import { Layout, Menu, Icon, Drawer, Button } from 'antd';
|
||||||
import {Switch, Link} from "react-router-dom";
|
import { Switch, Link } from 'react-router-dom';
|
||||||
import RouteWithSubRoutes from "../../components/RouteWithSubRoutes"
|
import RouteWithSubRoutes from '../../components/RouteWithSubRoutes';
|
||||||
import {Redirect} from 'react-router'
|
import { Redirect } from 'react-router';
|
||||||
import "./Dashboard.css";
|
import './Dashboard.css';
|
||||||
import {withConfigContext} from "../../context/ConfigContext";
|
import { withConfigContext } from '../../context/ConfigContext';
|
||||||
import Logout from "./logout/Logout";
|
import Logout from './logout/Logout';
|
||||||
|
|
||||||
const {Header, Content, Footer} = Layout;
|
const { Header, Content, Footer } = Layout;
|
||||||
const {SubMenu} = Menu;
|
const { SubMenu } = Menu;
|
||||||
|
|
||||||
class Dashboard extends React.Component {
|
class Dashboard extends React.Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
this.state = {
|
this.state = {
|
||||||
routes: props.routes,
|
routes: props.routes,
|
||||||
visible: false,
|
visible: false,
|
||||||
collapsed: 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
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
this.config = this.props.context;
|
||||||
|
this.Logo = this.config.theme.logo;
|
||||||
|
this.footerText = this.config.theme.footerText;
|
||||||
|
}
|
||||||
|
|
||||||
onCloseMobileNavigationBar = () => {
|
showMobileNavigationBar = () => {
|
||||||
this.setState({
|
this.setState({
|
||||||
visible: false,
|
visible: true,
|
||||||
});
|
collapsed: !this.state.collapsed,
|
||||||
};
|
});
|
||||||
|
};
|
||||||
|
|
||||||
render() {
|
onCloseMobileNavigationBar = () => {
|
||||||
return (
|
this.setState({
|
||||||
<div>
|
visible: false,
|
||||||
<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">
|
render() {
|
||||||
<Menu
|
return (
|
||||||
theme="light"
|
<div>
|
||||||
mode="horizontal"
|
<Layout>
|
||||||
defaultSelectedKeys={['1']}
|
<Header
|
||||||
style={{lineHeight: '64px'}}>
|
style={{
|
||||||
<Menu.Item key="1"><Link to="/publisher/apps"><Icon
|
paddingLeft: 0,
|
||||||
type="appstore"/>Apps</Link></Menu.Item>
|
paddingRight: 0,
|
||||||
|
backgroundColor: 'white',
|
||||||
<SubMenu
|
}}
|
||||||
title={
|
>
|
||||||
<span className="submenu-title-wrapper">
|
<div className="logo-image">
|
||||||
<Icon type="plus"/>
|
<Link to="/publisher/apps">
|
||||||
Add New App
|
<img alt="logo" src={this.Logo} />
|
||||||
</span>
|
</Link>
|
||||||
}>
|
|
||||||
<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">
|
|
||||||
<Icon type="control"/>Manage
|
|
||||||
</span>}>
|
|
||||||
<Menu.Item key="manage">
|
|
||||||
<Link to="/publisher/manage">
|
|
||||||
<Icon type="setting"/> General
|
|
||||||
</Link>
|
|
||||||
</Menu.Item>
|
|
||||||
{this.config.androidEnterpriseToken != null && (
|
|
||||||
<Menu.Item key="manage-android-enterprise">
|
|
||||||
<Link to="/publisher/manage/android-enterprise">
|
|
||||||
<Icon type="android" theme="filled"/> Android Enterprise
|
|
||||||
</Link>
|
|
||||||
</Menu.Item>
|
|
||||||
)}
|
|
||||||
</SubMenu>
|
|
||||||
|
|
||||||
<SubMenu className="profile"
|
|
||||||
title={
|
|
||||||
<span className="submenu-title-wrapper">
|
|
||||||
<Icon type="user"/>{this.config.user}
|
|
||||||
</span>}>
|
|
||||||
<Logout/>
|
|
||||||
</SubMenu>
|
|
||||||
</Menu>
|
|
||||||
</div>
|
|
||||||
</Header>
|
|
||||||
</Layout>
|
|
||||||
|
|
||||||
<Layout className="mobile-layout">
|
|
||||||
<div className="mobile-menu-button">
|
|
||||||
<Button type="link" onClick={this.showMobileNavigationBar}>
|
|
||||||
<Icon
|
|
||||||
type={this.state.collapsed ? 'menu-fold' : 'menu-unfold'}
|
|
||||||
className="bar-icon"/>
|
|
||||||
</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">
|
|
||||||
<Icon type="appstore"/>Apps
|
|
||||||
</Link>
|
|
||||||
</Menu.Item>
|
|
||||||
<SubMenu
|
|
||||||
title={
|
|
||||||
<span className="submenu-title-wrapper">
|
|
||||||
<Icon type="plus"/>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">
|
|
||||||
<Icon type="control"/>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">
|
|
||||||
<Icon type="user"/>
|
|
||||||
</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>
|
</div>
|
||||||
);
|
|
||||||
}
|
<div className="web-layout">
|
||||||
|
<Menu
|
||||||
|
theme="light"
|
||||||
|
mode="horizontal"
|
||||||
|
defaultSelectedKeys={['1']}
|
||||||
|
style={{ lineHeight: '64px' }}
|
||||||
|
>
|
||||||
|
<Menu.Item key="1">
|
||||||
|
<Link to="/publisher/apps">
|
||||||
|
<Icon type="appstore" />
|
||||||
|
Apps
|
||||||
|
</Link>
|
||||||
|
</Menu.Item>
|
||||||
|
|
||||||
|
<SubMenu
|
||||||
|
title={
|
||||||
|
<span className="submenu-title-wrapper">
|
||||||
|
<Icon type="plus" />
|
||||||
|
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">
|
||||||
|
<Icon type="control" />
|
||||||
|
Manage
|
||||||
|
</span>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Menu.Item key="manage">
|
||||||
|
<Link to="/publisher/manage">
|
||||||
|
<Icon type="setting" /> General
|
||||||
|
</Link>
|
||||||
|
</Menu.Item>
|
||||||
|
{this.config.androidEnterpriseToken != null && (
|
||||||
|
<Menu.Item key="manage-android-enterprise">
|
||||||
|
<Link to="/publisher/manage/android-enterprise">
|
||||||
|
<Icon type="android" theme="filled" /> Android
|
||||||
|
Enterprise
|
||||||
|
</Link>
|
||||||
|
</Menu.Item>
|
||||||
|
)}
|
||||||
|
</SubMenu>
|
||||||
|
|
||||||
|
<SubMenu
|
||||||
|
className="profile"
|
||||||
|
title={
|
||||||
|
<span className="submenu-title-wrapper">
|
||||||
|
<Icon type="user" />
|
||||||
|
{this.config.user}
|
||||||
|
</span>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Logout />
|
||||||
|
</SubMenu>
|
||||||
|
</Menu>
|
||||||
|
</div>
|
||||||
|
</Header>
|
||||||
|
</Layout>
|
||||||
|
|
||||||
|
<Layout className="mobile-layout">
|
||||||
|
<div className="mobile-menu-button">
|
||||||
|
<Button type="link" onClick={this.showMobileNavigationBar}>
|
||||||
|
<Icon
|
||||||
|
type={this.state.collapsed ? 'menu-fold' : 'menu-unfold'}
|
||||||
|
className="bar-icon"
|
||||||
|
/>
|
||||||
|
</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">
|
||||||
|
<Icon type="appstore" />
|
||||||
|
Apps
|
||||||
|
</Link>
|
||||||
|
</Menu.Item>
|
||||||
|
<SubMenu
|
||||||
|
title={
|
||||||
|
<span className="submenu-title-wrapper">
|
||||||
|
<Icon type="plus" />
|
||||||
|
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">
|
||||||
|
<Icon type="control" />
|
||||||
|
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">
|
||||||
|
<Icon type="user" />
|
||||||
|
</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);
|
export default withConfigContext(Dashboard);
|
||||||
@ -16,69 +16,65 @@
|
|||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from "react";
|
import React from 'react';
|
||||||
import {
|
import { PageHeader, Typography, Breadcrumb, Icon } from 'antd';
|
||||||
PageHeader,
|
import AddNewAppForm from '../../../components/new-app/AddNewAppForm';
|
||||||
Typography,
|
import { Link } from 'react-router-dom';
|
||||||
Breadcrumb,
|
|
||||||
Icon
|
|
||||||
} from "antd";
|
|
||||||
import AddNewAppForm from "../../../components/new-app/AddNewAppForm";
|
|
||||||
import {Link} from "react-router-dom";
|
|
||||||
|
|
||||||
const {Paragraph} = Typography;
|
const { Paragraph } = Typography;
|
||||||
|
|
||||||
const formConfig = {
|
const formConfig = {
|
||||||
installationType: "CUSTOM",
|
installationType: 'CUSTOM',
|
||||||
endpoint: "/custom-app",
|
endpoint: '/custom-app',
|
||||||
jsonPayloadName: "application",
|
jsonPayloadName: 'application',
|
||||||
releaseWrapperName: "customAppReleaseWrappers",
|
releaseWrapperName: 'customAppReleaseWrappers',
|
||||||
specificElements: {
|
specificElements: {
|
||||||
binaryFile: {
|
binaryFile: {
|
||||||
required: true
|
required: true,
|
||||||
},
|
},
|
||||||
packageName : {
|
packageName: {
|
||||||
required: true
|
required: true,
|
||||||
},
|
},
|
||||||
version : {
|
version: {
|
||||||
required: true
|
required: true,
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
class AddNewCustomApp extends React.Component {
|
class AddNewCustomApp extends React.Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
current: 0,
|
||||||
|
categories: [],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
constructor(props) {
|
render() {
|
||||||
super(props);
|
return (
|
||||||
this.state = {
|
<div>
|
||||||
current: 0,
|
<PageHeader style={{ paddingTop: 0, backgroundColor: '#fff' }}>
|
||||||
categories: []
|
<Breadcrumb style={{ paddingBottom: 16 }}>
|
||||||
};
|
<Breadcrumb.Item>
|
||||||
}
|
<Link to="/publisher/apps">
|
||||||
|
<Icon type="home" /> Home
|
||||||
render() {
|
</Link>
|
||||||
return (
|
</Breadcrumb.Item>
|
||||||
<div>
|
<Breadcrumb.Item>Add New Custom App</Breadcrumb.Item>
|
||||||
<PageHeader style={{paddingTop:0, backgroundColor: "#fff"}}>
|
</Breadcrumb>
|
||||||
<Breadcrumb style={{paddingBottom:16}}>
|
<div className="wrap">
|
||||||
<Breadcrumb.Item>
|
<h3>Add New Custom App</h3>
|
||||||
<Link to="/publisher/apps"><Icon type="home"/> Home</Link>
|
<Paragraph>
|
||||||
</Breadcrumb.Item>
|
Submit and share your own application to the corporate app store.
|
||||||
<Breadcrumb.Item>Add New Custom App</Breadcrumb.Item>
|
</Paragraph>
|
||||||
</Breadcrumb>
|
</div>
|
||||||
<div className="wrap">
|
</PageHeader>
|
||||||
<h3>Add New Custom App</h3>
|
<div style={{ background: '#f0f2f5', padding: 24, minHeight: 720 }}>
|
||||||
<Paragraph>Submit and share your own application to the corporate app store.</Paragraph>
|
<AddNewAppForm formConfig={formConfig} />
|
||||||
</div>
|
</div>
|
||||||
</PageHeader>
|
</div>
|
||||||
<div style={{background: '#f0f2f5', padding: 24, minHeight: 720}}>
|
);
|
||||||
<AddNewAppForm formConfig={formConfig}/>
|
}
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default AddNewCustomApp;
|
export default AddNewCustomApp;
|
||||||
|
|||||||
@ -16,63 +16,59 @@
|
|||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from "react";
|
import React from 'react';
|
||||||
import {
|
import { PageHeader, Typography, Breadcrumb, Icon } from 'antd';
|
||||||
PageHeader,
|
import AddNewAppForm from '../../../components/new-app/AddNewAppForm';
|
||||||
Typography,
|
import { Link } from 'react-router-dom';
|
||||||
Breadcrumb,
|
|
||||||
Icon
|
|
||||||
} from "antd";
|
|
||||||
import AddNewAppForm from "../../../components/new-app/AddNewAppForm";
|
|
||||||
import {Link} from "react-router-dom";
|
|
||||||
|
|
||||||
const {Paragraph} = Typography;
|
const { Paragraph } = Typography;
|
||||||
|
|
||||||
const formConfig = {
|
const formConfig = {
|
||||||
installationType: "ENTERPRISE",
|
installationType: 'ENTERPRISE',
|
||||||
endpoint: "/ent-app",
|
endpoint: '/ent-app',
|
||||||
jsonPayloadName: "application",
|
jsonPayloadName: 'application',
|
||||||
releaseWrapperName: "entAppReleaseWrappers",
|
releaseWrapperName: 'entAppReleaseWrappers',
|
||||||
specificElements: {
|
specificElements: {
|
||||||
binaryFile: {
|
binaryFile: {
|
||||||
required: true
|
required: true,
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
class AddNewEnterpriseApp extends React.Component {
|
class AddNewEnterpriseApp extends React.Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
current: 0,
|
||||||
|
categories: [],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
constructor(props) {
|
render() {
|
||||||
super(props);
|
return (
|
||||||
this.state = {
|
<div>
|
||||||
current: 0,
|
<PageHeader style={{ paddingTop: 0, backgroundColor: '#fff' }}>
|
||||||
categories: []
|
<Breadcrumb style={{ paddingBottom: 16 }}>
|
||||||
};
|
<Breadcrumb.Item>
|
||||||
}
|
<Link to="/publisher/apps">
|
||||||
|
<Icon type="home" /> Home
|
||||||
render() {
|
</Link>
|
||||||
return (
|
</Breadcrumb.Item>
|
||||||
<div>
|
<Breadcrumb.Item>Add New Enterprise App</Breadcrumb.Item>
|
||||||
<PageHeader style={{paddingTop:0, backgroundColor: "#fff"}}>
|
</Breadcrumb>
|
||||||
<Breadcrumb style={{paddingBottom:16}}>
|
<div className="wrap">
|
||||||
<Breadcrumb.Item>
|
<h3>Add New Enterprise App</h3>
|
||||||
<Link to="/publisher/apps"><Icon type="home"/> Home</Link>
|
<Paragraph>
|
||||||
</Breadcrumb.Item>
|
Submit and share your own application to the corporate app store.
|
||||||
<Breadcrumb.Item>Add New Enterprise App</Breadcrumb.Item>
|
</Paragraph>
|
||||||
</Breadcrumb>
|
</div>
|
||||||
<div className="wrap">
|
</PageHeader>
|
||||||
<h3>Add New Enterprise App</h3>
|
<div style={{ background: '#f0f2f5', padding: 24, minHeight: 720 }}>
|
||||||
<Paragraph>Submit and share your own application to the corporate app store.</Paragraph>
|
<AddNewAppForm formConfig={formConfig} />
|
||||||
</div>
|
</div>
|
||||||
</PageHeader>
|
</div>
|
||||||
<div style={{background: '#f0f2f5', padding: 24, minHeight: 720}}>
|
);
|
||||||
<AddNewAppForm formConfig={formConfig}/>
|
}
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default AddNewEnterpriseApp;
|
export default AddNewEnterpriseApp;
|
||||||
|
|||||||
@ -16,72 +16,67 @@
|
|||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from "react";
|
import React from 'react';
|
||||||
import {
|
import { Icon, PageHeader, Typography, Breadcrumb } from 'antd';
|
||||||
Icon,
|
import AddNewAppForm from '../../../components/new-app/AddNewAppForm';
|
||||||
PageHeader,
|
import { Link } from 'react-router-dom';
|
||||||
Typography,
|
|
||||||
Breadcrumb
|
|
||||||
} from "antd";
|
|
||||||
import AddNewAppForm from "../../../components/new-app/AddNewAppForm";
|
|
||||||
import {Link} from "react-router-dom";
|
|
||||||
|
|
||||||
const {Paragraph, Title} = Typography;
|
const { Paragraph } = Typography;
|
||||||
|
|
||||||
const formConfig = {
|
const formConfig = {
|
||||||
installationType: "PUBLIC",
|
installationType: 'PUBLIC',
|
||||||
endpoint: "/public-app",
|
endpoint: '/public-app',
|
||||||
jsonPayloadName:"public-app",
|
jsonPayloadName: 'public-app',
|
||||||
releaseWrapperName: "publicAppReleaseWrappers",
|
releaseWrapperName: 'publicAppReleaseWrappers',
|
||||||
specificElements: {
|
specificElements: {
|
||||||
packageName : {
|
packageName: {
|
||||||
required: true
|
required: true,
|
||||||
},
|
},
|
||||||
version : {
|
version: {
|
||||||
required: true
|
required: true,
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
class AddNewEnterpriseApp extends React.Component {
|
class AddNewEnterpriseApp extends React.Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
current: 0,
|
||||||
|
categories: [],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
constructor(props) {
|
componentDidMount() {
|
||||||
super(props);
|
// this.getCategories();
|
||||||
this.state = {
|
}
|
||||||
current: 0,
|
|
||||||
categories: []
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidMount() {
|
render() {
|
||||||
// this.getCategories();
|
return (
|
||||||
}
|
<div>
|
||||||
|
<PageHeader style={{ paddingTop: 0, backgroundColor: '#fff' }}>
|
||||||
|
<Breadcrumb style={{ paddingBottom: 16 }}>
|
||||||
render() {
|
<Breadcrumb.Item>
|
||||||
return (
|
<Link to="/publisher/apps">
|
||||||
<div>
|
<Icon type="home" /> Home
|
||||||
<PageHeader style={{paddingTop:0, backgroundColor: "#fff"}}>
|
</Link>
|
||||||
<Breadcrumb style={{paddingBottom:16}}>
|
</Breadcrumb.Item>
|
||||||
<Breadcrumb.Item>
|
<Breadcrumb.Item>Add New Public App</Breadcrumb.Item>
|
||||||
<Link to="/publisher/apps"><Icon type="home"/> Home</Link>
|
</Breadcrumb>
|
||||||
</Breadcrumb.Item>
|
<div className="wrap">
|
||||||
<Breadcrumb.Item>Add New Public App</Breadcrumb.Item>
|
<h3>Add New Public App</h3>
|
||||||
</Breadcrumb>
|
<Paragraph>
|
||||||
<div className="wrap">
|
Share a public application in google play or apple store to your
|
||||||
<h3>Add New Public App</h3>
|
corporate app store.
|
||||||
<Paragraph>Share a public application in google play or apple store to your corporate app store.
|
</Paragraph>
|
||||||
</Paragraph>
|
</div>
|
||||||
</div>
|
</PageHeader>
|
||||||
</PageHeader>
|
<div style={{ background: '#f0f2f5', padding: 24, minHeight: 720 }}>
|
||||||
<div style={{background: '#f0f2f5', padding: 24, minHeight: 720}}>
|
<AddNewAppForm formConfig={formConfig} />
|
||||||
<AddNewAppForm formConfig={formConfig}/>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
);
|
||||||
</div>
|
}
|
||||||
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default AddNewEnterpriseApp;
|
export default AddNewEnterpriseApp;
|
||||||
|
|||||||
@ -16,67 +16,60 @@
|
|||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from "react";
|
import React from 'react';
|
||||||
import {
|
import { Icon, PageHeader, Typography, Breadcrumb } from 'antd';
|
||||||
Icon,
|
import AddNewAppForm from '../../../components/new-app/AddNewAppForm';
|
||||||
PageHeader,
|
import { Link } from 'react-router-dom';
|
||||||
Typography,
|
|
||||||
Breadcrumb
|
|
||||||
} from "antd";
|
|
||||||
import AddNewAppForm from "../../../components/new-app/AddNewAppForm";
|
|
||||||
import {Link} from "react-router-dom";
|
|
||||||
|
|
||||||
const {Paragraph, Title}= Typography;
|
const { Paragraph } = Typography;
|
||||||
|
|
||||||
const formConfig = {
|
const formConfig = {
|
||||||
installationType: "WEB_CLIP",
|
installationType: 'WEB_CLIP',
|
||||||
endpoint: "/web-app",
|
endpoint: '/web-app',
|
||||||
jsonPayloadName:"webapp",
|
jsonPayloadName: 'webapp',
|
||||||
releaseWrapperName: "webAppReleaseWrappers",
|
releaseWrapperName: 'webAppReleaseWrappers',
|
||||||
specificElements: {
|
specificElements: {
|
||||||
url : {
|
url: {
|
||||||
required: true
|
required: true,
|
||||||
},
|
},
|
||||||
version : {
|
version: {
|
||||||
required: true
|
required: true,
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
class AddNewEnterpriseApp extends React.Component {
|
class AddNewEnterpriseApp extends React.Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
current: 0,
|
||||||
|
categories: [],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
constructor(props) {
|
render() {
|
||||||
super(props);
|
return (
|
||||||
this.state = {
|
<div>
|
||||||
current: 0,
|
<PageHeader style={{ paddingTop: 0, backgroundColor: '#fff' }}>
|
||||||
categories: []
|
<Breadcrumb style={{ paddingBottom: 16 }}>
|
||||||
};
|
<Breadcrumb.Item>
|
||||||
}
|
<Link to="/publisher/apps">
|
||||||
|
<Icon type="home" /> Home
|
||||||
|
</Link>
|
||||||
render() {
|
</Breadcrumb.Item>
|
||||||
return (
|
<Breadcrumb.Item>Add New Web Clip</Breadcrumb.Item>
|
||||||
<div>
|
</Breadcrumb>
|
||||||
<PageHeader style={{paddingTop:0, backgroundColor: "#fff"}}>
|
<div className="wrap">
|
||||||
<Breadcrumb style={{paddingBottom:16}}>
|
<h3>Add New Web Clip</h3>
|
||||||
<Breadcrumb.Item>
|
<Paragraph>Share a Web Clip to your corporate app store.</Paragraph>
|
||||||
<Link to="/publisher/apps"><Icon type="home"/> Home</Link>
|
</div>
|
||||||
</Breadcrumb.Item>
|
</PageHeader>
|
||||||
<Breadcrumb.Item>Add New Web Clip</Breadcrumb.Item>
|
<div style={{ background: '#f0f2f5', padding: 24, minHeight: 720 }}>
|
||||||
</Breadcrumb>
|
<AddNewAppForm formConfig={formConfig} />
|
||||||
<div className="wrap">
|
</div>
|
||||||
<h3>Add New Web Clip</h3>
|
</div>
|
||||||
<Paragraph>Share a Web Clip to your corporate app store.</Paragraph>
|
);
|
||||||
</div>
|
}
|
||||||
</PageHeader>
|
|
||||||
<div style={{background: '#f0f2f5', padding: 24, minHeight: 720}}>
|
|
||||||
<AddNewAppForm formConfig={formConfig}/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default AddNewEnterpriseApp;
|
export default AddNewEnterpriseApp;
|
||||||
|
|||||||
@ -16,52 +16,46 @@
|
|||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from "react";
|
import React from 'react';
|
||||||
import {
|
import { Icon, PageHeader, Typography, Breadcrumb } from 'antd';
|
||||||
Icon,
|
import AddNewReleaseForm from '../../../components/new-release/AddReleaseForm';
|
||||||
PageHeader,
|
import { Link } from 'react-router-dom';
|
||||||
Typography,
|
|
||||||
Breadcrumb
|
|
||||||
} from "antd";
|
|
||||||
import AddNewReleaseForm from "../../../components/new-release/AddReleaseForm";
|
|
||||||
import {Link} from "react-router-dom";
|
|
||||||
|
|
||||||
const Paragraph = Typography;
|
const Paragraph = Typography;
|
||||||
|
|
||||||
class AddNewRelease extends React.Component {
|
class AddNewRelease extends React.Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
current: 0,
|
||||||
|
categories: [],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
constructor(props) {
|
render() {
|
||||||
super(props);
|
const { appId, deviceType } = this.props.match.params;
|
||||||
this.state = {
|
return (
|
||||||
current: 0,
|
<div>
|
||||||
categories: []
|
<PageHeader style={{ paddingTop: 0, backgroundColor: '#fff' }}>
|
||||||
};
|
<Breadcrumb style={{ paddingBottom: 16 }}>
|
||||||
}
|
<Breadcrumb.Item>
|
||||||
|
<Link to="/publisher/apps">
|
||||||
render() {
|
<Icon type="home" /> Home
|
||||||
const {appId, deviceType} = this.props.match.params;
|
</Link>
|
||||||
return (
|
</Breadcrumb.Item>
|
||||||
<div>
|
<Breadcrumb.Item>Add New Release</Breadcrumb.Item>
|
||||||
<PageHeader style={{paddingTop:0, backgroundColor: "#fff"}}>
|
</Breadcrumb>
|
||||||
<Breadcrumb style={{paddingBottom: 16}}>
|
<div className="wrap">
|
||||||
<Breadcrumb.Item>
|
<h3>Add New Release</h3>
|
||||||
<Link to="/publisher/apps"><Icon type="home"/> Home</Link>
|
<Paragraph>Add new release for the application</Paragraph>
|
||||||
</Breadcrumb.Item>
|
</div>
|
||||||
<Breadcrumb.Item>Add New Release</Breadcrumb.Item>
|
</PageHeader>
|
||||||
</Breadcrumb>
|
<div style={{ background: '#f0f2f5', padding: 24, minHeight: 720 }}>
|
||||||
<div className="wrap">
|
<AddNewReleaseForm deviceType={deviceType} appId={appId} />
|
||||||
<h3>Add New Release</h3>
|
</div>
|
||||||
<Paragraph>Add new release for the application</Paragraph>
|
</div>
|
||||||
</div>
|
);
|
||||||
</PageHeader>
|
}
|
||||||
<div style={{background: '#f0f2f5', padding: 24, minHeight: 720}}>
|
|
||||||
<AddNewReleaseForm deviceType={deviceType} appId={appId} />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default AddNewRelease;
|
export default AddNewRelease;
|
||||||
|
|||||||
@ -16,28 +16,25 @@
|
|||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from "react";
|
import React from 'react';
|
||||||
import ListApps from "../../../components/apps/list-apps/ListApps";
|
import ListApps from '../../../components/apps/list-apps/ListApps';
|
||||||
|
|
||||||
class Apps extends React.Component {
|
class Apps extends React.Component {
|
||||||
routes;
|
routes;
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
this.routes = props.routes;
|
this.routes = props.routes;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
render() {
|
||||||
|
return (
|
||||||
render() {
|
<div>
|
||||||
return (
|
<div style={{ background: '#f0f2f5', padding: 24, minHeight: 780 }}>
|
||||||
<div>
|
<ListApps />
|
||||||
<div style={{background: '#f0f2f5', padding: 24, minHeight: 780}}>
|
</div>
|
||||||
<ListApps/>
|
</div>
|
||||||
</div>
|
);
|
||||||
|
}
|
||||||
</div>
|
|
||||||
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Apps;
|
export default Apps;
|
||||||
|
|||||||
@ -16,193 +16,232 @@
|
|||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from "react";
|
import React from 'react';
|
||||||
import '../../../../App.css';
|
import '../../../../App.css';
|
||||||
import {Typography, Row, Col, message, Card, notification, Skeleton} from "antd";
|
import { Typography, Row, Col, Card, Skeleton } from 'antd';
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
import ReleaseView from "../../../../components/apps/release/ReleaseView";
|
import ReleaseView from '../../../../components/apps/release/ReleaseView';
|
||||||
import LifeCycle from "../../../../components/apps/release/lifeCycle/LifeCycle";
|
import LifeCycle from '../../../../components/apps/release/lifeCycle/LifeCycle';
|
||||||
import {withConfigContext} from "../../../../context/ConfigContext";
|
import { withConfigContext } from '../../../../context/ConfigContext';
|
||||||
import {handleApiError} from "../../../../js/Utils";
|
import { handleApiError } from '../../../../js/Utils';
|
||||||
import NewAppUploadForm from "../../../../components/new-app/subForms/NewAppUploadForm";
|
|
||||||
|
|
||||||
const {Title} = Typography;
|
const { Title } = Typography;
|
||||||
|
|
||||||
class Release extends React.Component {
|
class Release extends React.Component {
|
||||||
routes;
|
routes;
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
this.routes = props.routes;
|
this.routes = props.routes;
|
||||||
this.state = {
|
this.state = {
|
||||||
loading: true,
|
loading: true,
|
||||||
app: null,
|
app: null,
|
||||||
uuid: null,
|
uuid: null,
|
||||||
release: null,
|
release: null,
|
||||||
currentLifecycleStatus: null,
|
currentLifecycleStatus: null,
|
||||||
lifecycle: null,
|
lifecycle: null,
|
||||||
supportedOsVersions: [],
|
supportedOsVersions: [],
|
||||||
forbiddenErrors: {
|
forbiddenErrors: {
|
||||||
supportedOsVersions: false,
|
supportedOsVersions: false,
|
||||||
lifeCycle: false
|
lifeCycle: false,
|
||||||
}
|
},
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidMount() {
|
|
||||||
const {uuid} = this.props.match.params;
|
|
||||||
this.fetchData(uuid);
|
|
||||||
this.getLifecycle();
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
changeCurrentLifecycleStatus = (status) => {
|
|
||||||
this.setState({
|
|
||||||
currentLifecycleStatus: status
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
}
|
||||||
|
|
||||||
updateRelease = (release) => {
|
componentDidMount() {
|
||||||
this.setState({
|
const { uuid } = this.props.match.params;
|
||||||
release
|
this.fetchData(uuid);
|
||||||
});
|
this.getLifecycle();
|
||||||
};
|
}
|
||||||
|
|
||||||
fetchData = (uuid) => {
|
changeCurrentLifecycleStatus = status => {
|
||||||
const config = this.props.context;
|
this.setState({
|
||||||
|
currentLifecycleStatus: status,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
//send request to the invoker
|
updateRelease = release => {
|
||||||
axios.get(
|
this.setState({
|
||||||
window.location.origin + config.serverConfig.invoker.uri + config.serverConfig.invoker.publisher + "/applications/release/" + uuid,
|
release,
|
||||||
).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)) {
|
|
||||||
this.getSupportedOsVersions(app.deviceType);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}).catch((error) => {
|
fetchData = uuid => {
|
||||||
handleApiError(error, "Error occurred while trying to load the release.");
|
const config = this.props.context;
|
||||||
this.setState({loading: false});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
getLifecycle = () => {
|
// send request to the invoker
|
||||||
const config = this.props.context;
|
axios
|
||||||
axios.get(
|
.get(
|
||||||
window.location.origin + config.serverConfig.invoker.uri + config.serverConfig.invoker.publisher + "/applications/lifecycle-config"
|
window.location.origin +
|
||||||
).then(res => {
|
config.serverConfig.invoker.uri +
|
||||||
if (res.status === 200) {
|
config.serverConfig.invoker.publisher +
|
||||||
const lifecycle = res.data.data;
|
'/applications/release/' +
|
||||||
this.setState({
|
uuid,
|
||||||
lifecycle: lifecycle
|
)
|
||||||
})
|
.then(res => {
|
||||||
}
|
if (res.status === 200) {
|
||||||
|
const app = res.data.data;
|
||||||
}).catch((error) => {
|
const release = app !== null ? app.applicationReleases[0] : null;
|
||||||
handleApiError(error, "Error occurred while trying to load lifecycle configuration.", true);
|
const currentLifecycleStatus =
|
||||||
if (error.hasOwnProperty("response") && error.response.status === 403) {
|
release !== null ? release.currentStatus : null;
|
||||||
const {forbiddenErrors} = this.state;
|
this.setState({
|
||||||
forbiddenErrors.lifeCycle = true;
|
app: app,
|
||||||
this.setState({
|
release: release,
|
||||||
forbiddenErrors
|
currentLifecycleStatus: currentLifecycleStatus,
|
||||||
})
|
loading: false,
|
||||||
}
|
uuid: uuid,
|
||||||
});
|
});
|
||||||
};
|
if (config.deviceTypes.mobileTypes.includes(app.deviceType)) {
|
||||||
|
this.getSupportedOsVersions(app.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
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}).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
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
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
|
.catch(error => {
|
||||||
return (
|
handleApiError(
|
||||||
<div>
|
error,
|
||||||
<div className="main-container">
|
'Error occurred while trying to load the release.',
|
||||||
<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>
|
|
||||||
<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}
|
|
||||||
/>)
|
|
||||||
}
|
|
||||||
</Skeleton>
|
|
||||||
</Card>
|
|
||||||
</Col>
|
|
||||||
</Row>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
);
|
);
|
||||||
|
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,
|
||||||
|
);
|
||||||
|
if (error.hasOwnProperty('response') && error.response.status === 403) {
|
||||||
|
const { forbiddenErrors } = this.state;
|
||||||
|
forbiddenErrors.lifeCycle = true;
|
||||||
|
this.setState({
|
||||||
|
forbiddenErrors,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
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,
|
||||||
|
);
|
||||||
|
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,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
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>
|
||||||
|
<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}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Skeleton>
|
||||||
|
</Card>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default withConfigContext(Release);
|
export default withConfigContext(Release);
|
||||||
|
|||||||
@ -16,64 +16,66 @@
|
|||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from "react";
|
import React from 'react';
|
||||||
import {notification, Menu, Icon} from 'antd';
|
import { notification, Menu, Icon } from 'antd';
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
import {withConfigContext} from "../../../context/ConfigContext";
|
import { withConfigContext } from '../../../context/ConfigContext';
|
||||||
|
|
||||||
/*
|
/*
|
||||||
This class for call the logout api by sending request
|
This class for call the logout api by sending request
|
||||||
*/
|
*/
|
||||||
class Logout extends React.Component {
|
class Logout extends React.Component {
|
||||||
|
constructor(props) {
|
||||||
constructor(props) {
|
super(props);
|
||||||
super(props);
|
this.state = {
|
||||||
this.state = {
|
inValid: false,
|
||||||
inValid: false,
|
loading: false,
|
||||||
loading: false
|
};
|
||||||
};
|
}
|
||||||
}
|
/*
|
||||||
/*
|
|
||||||
This function call the logout api when the request is success
|
This function call the logout api when the request is success
|
||||||
*/
|
*/
|
||||||
handleSubmit = () => {
|
handleSubmit = () => {
|
||||||
|
const thisForm = this;
|
||||||
|
const config = this.props.context;
|
||||||
|
|
||||||
const thisForm = this;
|
thisForm.setState({
|
||||||
const config = this.props.context;
|
inValid: false,
|
||||||
|
});
|
||||||
|
|
||||||
thisForm.setState({
|
axios
|
||||||
inValid: false
|
.post(window.location.origin + config.serverConfig.logoutUri)
|
||||||
});
|
.then(res => {
|
||||||
|
// if the api call status is correct then user will logout and then it goes to login page
|
||||||
|
if (res.status === 200) {
|
||||||
|
window.location = window.location.origin + '/publisher/login';
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(function(error) {
|
||||||
|
if (error.hasOwnProperty('response') && error.response.status === 400) {
|
||||||
|
thisForm.setState({
|
||||||
|
inValid: true,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
notification.error({
|
||||||
|
message: 'There was a problem',
|
||||||
|
duration: 0,
|
||||||
|
description: 'Error occurred while trying to logout.',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
axios.post(window.location.origin + config.serverConfig.logoutUri
|
render() {
|
||||||
).then(res => {
|
return (
|
||||||
//if the api call status is correct then user will logout and then it goes to login page
|
<Menu>
|
||||||
if (res.status === 200) {
|
<Menu.Item key="1" onClick={this.handleSubmit}>
|
||||||
window.location = window.location.origin + "/publisher/login";
|
<Icon type="logout" />
|
||||||
}
|
Logout
|
||||||
}).catch(function (error) {
|
</Menu.Item>
|
||||||
if (error.hasOwnProperty("response") && error.response.status === 400) {
|
</Menu>
|
||||||
thisForm.setState({
|
);
|
||||||
inValid: true
|
}
|
||||||
});
|
|
||||||
} else {
|
|
||||||
notification["error"]({
|
|
||||||
message: "There was a problem",
|
|
||||||
duration: 0,
|
|
||||||
description:
|
|
||||||
"Error occurred while trying to logout.",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
render() {
|
|
||||||
return (
|
|
||||||
<Menu>
|
|
||||||
<Menu.Item key="1" onClick={this.handleSubmit}><Icon type="logout"/>Logout</Menu.Item>
|
|
||||||
</Menu>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default withConfigContext(Logout);
|
export default withConfigContext(Logout);
|
||||||
|
|||||||
@ -16,56 +16,55 @@
|
|||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from "react";
|
import React from 'react';
|
||||||
import {PageHeader, Typography, Breadcrumb, Row, Col, Icon} from "antd";
|
import { PageHeader, Typography, Breadcrumb, Row, Col, Icon } from 'antd';
|
||||||
import ManageCategories from "../../../components/manage/categories/ManageCategories";
|
import ManageCategories from '../../../components/manage/categories/ManageCategories';
|
||||||
import ManageTags from "../../../components/manage/categories/ManageTags";
|
import ManageTags from '../../../components/manage/categories/ManageTags';
|
||||||
import {Link} from "react-router-dom";
|
import { Link } from 'react-router-dom';
|
||||||
|
|
||||||
const {Paragraph} = Typography;
|
const { Paragraph } = Typography;
|
||||||
|
|
||||||
class Manage extends React.Component {
|
class Manage extends React.Component {
|
||||||
routes;
|
routes;
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
this.routes = props.routes;
|
this.routes = props.routes;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
render() {
|
||||||
|
return (
|
||||||
render() {
|
<div>
|
||||||
return (
|
<PageHeader style={{ paddingTop: 0, backgroundColor: '#fff' }}>
|
||||||
<div>
|
<Breadcrumb style={{ paddingBottom: 16 }}>
|
||||||
<PageHeader style={{paddingTop:0, backgroundColor: "#fff"}}>
|
<Breadcrumb.Item>
|
||||||
<Breadcrumb style={{paddingBottom: 16}}>
|
<Link to="/publisher/apps">
|
||||||
<Breadcrumb.Item>
|
<Icon type="home" /> Home
|
||||||
<Link to="/publisher/apps"><Icon type="home"/> Home</Link>
|
</Link>
|
||||||
</Breadcrumb.Item>
|
</Breadcrumb.Item>
|
||||||
<Breadcrumb.Item>
|
<Breadcrumb.Item>Manage</Breadcrumb.Item>
|
||||||
Manage
|
<Breadcrumb.Item>General</Breadcrumb.Item>
|
||||||
</Breadcrumb.Item>
|
</Breadcrumb>
|
||||||
<Breadcrumb.Item>General</Breadcrumb.Item>
|
<div className="wrap">
|
||||||
</Breadcrumb>
|
<h3>Manage General Settings</h3>
|
||||||
<div className="wrap">
|
<Paragraph>
|
||||||
<h3>Manage General Settings</h3>
|
Maintain and manage categories and tags here..
|
||||||
<Paragraph>Maintain and manage categories and tags here..</Paragraph>
|
</Paragraph>
|
||||||
</div>
|
</div>
|
||||||
</PageHeader>
|
</PageHeader>
|
||||||
<div style={{background: '#f0f2f5', padding: 24, minHeight: 780}}>
|
<div style={{ background: '#f0f2f5', padding: 24, minHeight: 780 }}>
|
||||||
<Row gutter={16}>
|
<Row gutter={16}>
|
||||||
<Col sm={24} md={12}>
|
<Col sm={24} md={12}>
|
||||||
<ManageCategories/>
|
<ManageCategories />
|
||||||
</Col>
|
</Col>
|
||||||
<Col sm={24} md={12}>
|
<Col sm={24} md={12}>
|
||||||
<ManageTags/>
|
<ManageTags />
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
);
|
||||||
|
}
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Manage;
|
export default Manage;
|
||||||
|
|||||||
@ -16,53 +16,50 @@
|
|||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from "react";
|
import React from 'react';
|
||||||
import {PageHeader, Typography, Breadcrumb, Divider, Button, Icon} from "antd";
|
import { PageHeader, Breadcrumb, Divider, Icon } from 'antd';
|
||||||
import {Link} from "react-router-dom";
|
import { Link } from 'react-router-dom';
|
||||||
import SyncAndroidApps from "../../../../components/manage/android-enterprise/SyncAndroidApps";
|
import SyncAndroidApps from '../../../../components/manage/android-enterprise/SyncAndroidApps';
|
||||||
import {withConfigContext} from "../../../../context/ConfigContext";
|
import { withConfigContext } from '../../../../context/ConfigContext';
|
||||||
import GooglePlayIframe from "../../../../components/manage/android-enterprise/GooglePlayIframe";
|
import GooglePlayIframe from '../../../../components/manage/android-enterprise/GooglePlayIframe';
|
||||||
import Pages from "../../../../components/manage/android-enterprise/Pages/Pages";
|
import Pages from '../../../../components/manage/android-enterprise/Pages/Pages';
|
||||||
|
|
||||||
const {Paragraph} = Typography;
|
|
||||||
|
|
||||||
class ManageAndroidEnterprise extends React.Component {
|
class ManageAndroidEnterprise extends React.Component {
|
||||||
routes;
|
routes;
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
this.routes = props.routes;
|
this.routes = props.routes;
|
||||||
this.config = this.props.context;
|
this.config = this.props.context;
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<PageHeader style={{paddingTop:0, backgroundColor: "#fff"}}>
|
<PageHeader style={{ paddingTop: 0, backgroundColor: '#fff' }}>
|
||||||
<Breadcrumb style={{paddingBottom: 16}}>
|
<Breadcrumb style={{ paddingBottom: 16 }}>
|
||||||
<Breadcrumb.Item>
|
<Breadcrumb.Item>
|
||||||
<Link to="/publisher/apps"><Icon type="home"/> Home</Link>
|
<Link to="/publisher/apps">
|
||||||
</Breadcrumb.Item>
|
<Icon type="home" /> Home
|
||||||
<Breadcrumb.Item>
|
</Link>
|
||||||
Manage
|
</Breadcrumb.Item>
|
||||||
</Breadcrumb.Item>
|
<Breadcrumb.Item>Manage</Breadcrumb.Item>
|
||||||
<Breadcrumb.Item>Android Enterprise</Breadcrumb.Item>
|
<Breadcrumb.Item>Android Enterprise</Breadcrumb.Item>
|
||||||
</Breadcrumb>
|
</Breadcrumb>
|
||||||
<div className="wrap">
|
<div className="wrap">
|
||||||
<h3>Manage Android Enterprise</h3>
|
<h3>Manage Android Enterprise</h3>
|
||||||
{/*<Paragraph>Lorem ipsum</Paragraph>*/}
|
{/* <Paragraph>Lorem ipsum</Paragraph>*/}
|
||||||
</div>
|
</div>
|
||||||
</PageHeader>
|
</PageHeader>
|
||||||
<div style={{background: '#f0f2f5', padding: 24, minHeight: 720}}>
|
<div style={{ background: '#f0f2f5', padding: 24, minHeight: 720 }}>
|
||||||
<SyncAndroidApps/>
|
<SyncAndroidApps />
|
||||||
<GooglePlayIframe/>
|
<GooglePlayIframe />
|
||||||
<Divider/>
|
<Divider />
|
||||||
<Pages/>
|
<Pages />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
);
|
||||||
);
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default withConfigContext(ManageAndroidEnterprise);
|
export default withConfigContext(ManageAndroidEnterprise);
|
||||||
|
|||||||
@ -16,371 +16,409 @@
|
|||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from "react";
|
import React from 'react';
|
||||||
import {
|
import {
|
||||||
PageHeader,
|
PageHeader,
|
||||||
Typography,
|
Typography,
|
||||||
Breadcrumb,
|
Breadcrumb,
|
||||||
Button,
|
Button,
|
||||||
Icon,
|
Icon,
|
||||||
Col,
|
Col,
|
||||||
Row,
|
Row,
|
||||||
notification,
|
notification,
|
||||||
message,
|
message,
|
||||||
Spin,
|
Spin,
|
||||||
Select,
|
Tag,
|
||||||
Tag,
|
Divider,
|
||||||
Divider
|
} from 'antd';
|
||||||
} from "antd";
|
import { Link, withRouter } from 'react-router-dom';
|
||||||
import {Link, withRouter} from "react-router-dom";
|
import { withConfigContext } from '../../../../../context/ConfigContext';
|
||||||
import {withConfigContext} from "../../../../../context/ConfigContext";
|
import axios from 'axios';
|
||||||
import axios from "axios";
|
import Cluster from '../../../../../components/manage/android-enterprise/Pages/Cluster/Cluster';
|
||||||
import Cluster from "../../../../../components/manage/android-enterprise/Pages/Cluster/Cluster";
|
import EditLinks from '../../../../../components/manage/android-enterprise/Pages/EditLinks/EditLinks';
|
||||||
import EditLinks from "../../../../../components/manage/android-enterprise/Pages/EditLinks/EditLinks";
|
import { handleApiError } from '../../../../../js/Utils';
|
||||||
import {handleApiError} from "../../../../../js/Utils";
|
|
||||||
|
|
||||||
const {Option} = Select;
|
const { Title } = Typography;
|
||||||
const {Title, Text} = Typography;
|
|
||||||
|
|
||||||
class Page extends React.Component {
|
class Page extends React.Component {
|
||||||
routes;
|
routes;
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
const {pageName, pageId} = this.props.match.params;
|
const { pageName, pageId } = this.props.match.params;
|
||||||
this.pageId = pageId;
|
this.pageId = pageId;
|
||||||
this.routes = props.routes;
|
this.routes = props.routes;
|
||||||
this.config = this.props.context;
|
this.config = this.props.context;
|
||||||
this.pages = [];
|
this.pages = [];
|
||||||
this.pageNames = {};
|
this.pageNames = {};
|
||||||
this.state = {
|
this.state = {
|
||||||
pageName,
|
pageName,
|
||||||
clusters: [],
|
clusters: [],
|
||||||
|
loading: false,
|
||||||
|
applications: [],
|
||||||
|
isAddNewClusterVisible: false,
|
||||||
|
links: [],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
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,
|
loading: false,
|
||||||
applications: [],
|
links,
|
||||||
isAddNewClusterVisible: false,
|
});
|
||||||
links: []
|
}
|
||||||
};
|
})
|
||||||
}
|
.catch(error => {
|
||||||
|
if (error.hasOwnProperty('response') && error.response.status === 401) {
|
||||||
componentDidMount() {
|
message.error('You are not logged in');
|
||||||
this.fetchClusters();
|
window.location.href = window.location.origin + '/publisher/login';
|
||||||
this.fetchApplications();
|
} else {
|
||||||
this.fetchPages();
|
notification.error({
|
||||||
}
|
message: 'There was a problem',
|
||||||
|
duration: 0,
|
||||||
removeLoadedCluster = (clusterId) => {
|
description: 'Error occurred while trying to load pages.',
|
||||||
const clusters = [...this.state.clusters];
|
});
|
||||||
let index = -1;
|
}
|
||||||
for (let i = 0; i < clusters.length; i++) {
|
|
||||||
if (clusters[i].clusterId === clusterId) {
|
this.setState({ loading: false });
|
||||||
index = i;
|
});
|
||||||
break;
|
};
|
||||||
}
|
|
||||||
|
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.',
|
||||||
|
});
|
||||||
}
|
}
|
||||||
clusters.splice(index, 1);
|
|
||||||
this.setState({
|
this.setState({
|
||||||
clusters
|
loading: false,
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// fetch applications
|
||||||
|
fetchApplications = () => {
|
||||||
|
const config = this.props.context;
|
||||||
|
this.setState({ loading: true });
|
||||||
|
|
||||||
|
const filters = {
|
||||||
|
appType: 'PUBLIC',
|
||||||
|
deviceType: 'android',
|
||||||
};
|
};
|
||||||
|
|
||||||
updatePageName = pageName => {
|
// send request to the invoker
|
||||||
const config = this.props.context;
|
axios
|
||||||
if (pageName !== this.state.pageName && pageName !== "") {
|
.post(
|
||||||
const data = {
|
window.location.origin +
|
||||||
locale: "en",
|
config.serverConfig.invoker.uri +
|
||||||
pageName: pageName,
|
config.serverConfig.invoker.publisher +
|
||||||
pageId: this.pageId
|
'/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,
|
||||||
};
|
};
|
||||||
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}`);
|
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">
|
||||||
|
<Icon type="home" /> 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>
|
||||||
|
<Spin spinning={loading}>
|
||||||
|
<div style={{ background: '#f0f2f5', padding: 24, minHeight: 720 }}>
|
||||||
|
<Row>
|
||||||
|
<Col md={8} sm={18} xs={24}>
|
||||||
|
<Title editable={{ onChange: this.updatePageName }} 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;
|
||||||
|
})}
|
||||||
|
<EditLinks
|
||||||
|
updateLinks={this.updateLinks}
|
||||||
|
pageId={this.pageId}
|
||||||
|
selectedLinks={links}
|
||||||
|
pages={this.pages}
|
||||||
|
/>
|
||||||
|
</Col>
|
||||||
|
{/* <Col>*/}
|
||||||
|
|
||||||
|
{/* </Col>*/}
|
||||||
|
</Row>
|
||||||
|
|
||||||
|
<Divider dashed={true} />
|
||||||
|
<Title level={4}>Clusters</Title>
|
||||||
|
|
||||||
|
<div
|
||||||
|
hidden={isAddNewClusterVisible}
|
||||||
|
style={{ textAlign: 'center' }}
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
type="dashed"
|
||||||
|
shape="round"
|
||||||
|
icon="plus"
|
||||||
|
size="large"
|
||||||
|
onClick={() => {
|
||||||
|
this.toggleAddNewClusterVisibility(true);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Add new cluster
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
<div hidden={!isAddNewClusterVisible}>
|
||||||
|
<Cluster
|
||||||
|
cluster={{
|
||||||
|
clusterId: 0,
|
||||||
|
name: 'New Cluster',
|
||||||
|
products: [],
|
||||||
|
}}
|
||||||
|
orderInPage={clusters.length}
|
||||||
|
isTemporary={true}
|
||||||
|
pageId={this.pageId}
|
||||||
|
applications={applications}
|
||||||
|
addSavedClusterToThePage={this.addSavedClusterToThePage}
|
||||||
|
toggleAddNewClusterVisibility={
|
||||||
|
this.toggleAddNewClusterVisibility
|
||||||
}
|
}
|
||||||
}).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"><Icon type="home"/> 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>
|
|
||||||
<Spin spinning={loading}>
|
|
||||||
<div style={{background: '#f0f2f5', padding: 24, minHeight: 720}}>
|
|
||||||
<Row>
|
|
||||||
<Col md={8} sm={18} xs={24}>
|
|
||||||
<Title editable={{onChange: this.updatePageName}} 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>
|
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
<EditLinks
|
|
||||||
updateLinks={this.updateLinks}
|
|
||||||
pageId={this.pageId}
|
|
||||||
selectedLinks={links}
|
|
||||||
pages={this.pages}/>
|
|
||||||
</Col>
|
|
||||||
{/*<Col>*/}
|
|
||||||
|
|
||||||
{/*</Col>*/}
|
|
||||||
</Row>
|
|
||||||
|
|
||||||
<Divider dashed={true}/>
|
|
||||||
<Title level={4}>Clusters</Title>
|
|
||||||
|
|
||||||
<div hidden={isAddNewClusterVisible} style={{textAlign: "center"}}>
|
|
||||||
<Button
|
|
||||||
type="dashed"
|
|
||||||
shape="round"
|
|
||||||
icon="plus"
|
|
||||||
size="large"
|
|
||||||
onClick={() => {
|
|
||||||
this.toggleAddNewClusterVisibility(true);
|
|
||||||
}}
|
|
||||||
>Add new cluster</Button>
|
|
||||||
</div>
|
|
||||||
<div hidden={!isAddNewClusterVisible}>
|
|
||||||
<Cluster
|
|
||||||
cluster={{
|
|
||||||
clusterId: 0,
|
|
||||||
name: "New Cluster",
|
|
||||||
products: []
|
|
||||||
}}
|
|
||||||
orderInPage={clusters.length}
|
|
||||||
isTemporary={true}
|
|
||||||
pageId={this.pageId}
|
|
||||||
applications={applications}
|
|
||||||
addSavedClusterToThePage={this.addSavedClusterToThePage}
|
|
||||||
toggleAddNewClusterVisibility={this.toggleAddNewClusterVisibility}/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{
|
|
||||||
clusters.map((cluster, index) => {
|
|
||||||
return (
|
|
||||||
<Cluster
|
|
||||||
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>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
);
|
{clusters.map((cluster, index) => {
|
||||||
}
|
return (
|
||||||
|
<Cluster
|
||||||
|
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>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default withConfigContext(withRouter(Page));
|
export default withConfigContext(withRouter(Page));
|
||||||
|
|||||||
@ -34,8 +34,8 @@ const isLocalhost = Boolean(
|
|||||||
window.location.hostname === '[::1]' ||
|
window.location.hostname === '[::1]' ||
|
||||||
// 127.0.0.1/8 is considered localhost for IPv4.
|
// 127.0.0.1/8 is considered localhost for IPv4.
|
||||||
window.location.hostname.match(
|
window.location.hostname.match(
|
||||||
/^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
|
/^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/,
|
||||||
)
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
export function register(config) {
|
export function register(config) {
|
||||||
@ -61,7 +61,7 @@ export function register(config) {
|
|||||||
navigator.serviceWorker.ready.then(() => {
|
navigator.serviceWorker.ready.then(() => {
|
||||||
console.log(
|
console.log(
|
||||||
'This web app is being served cache-first by a service ' +
|
'This web app is being served cache-first by a service ' +
|
||||||
'worker. To learn more, visit https://bit.ly/CRA-PWA'
|
'worker. To learn more, visit https://bit.ly/CRA-PWA',
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
@ -89,7 +89,7 @@ function registerValidSW(swUrl, config) {
|
|||||||
// content until all client tabs are closed.
|
// content until all client tabs are closed.
|
||||||
console.log(
|
console.log(
|
||||||
'New content is available and will be used when all ' +
|
'New content is available and will be used when all ' +
|
||||||
'tabs for this page are closed. See https://bit.ly/CRA-PWA.'
|
'tabs for this page are closed. See https://bit.ly/CRA-PWA.',
|
||||||
);
|
);
|
||||||
|
|
||||||
// Execute callback
|
// Execute callback
|
||||||
@ -139,7 +139,7 @@ function checkValidServiceWorker(swUrl, config) {
|
|||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
console.log(
|
console.log(
|
||||||
'No internet connection found. App is running in offline mode.'
|
'No internet connection found. App is running in offline mode.',
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@ -17,119 +17,119 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
var path = require('path');
|
var path = require('path');
|
||||||
const HtmlWebPackPlugin = require("html-webpack-plugin");
|
const HtmlWebPackPlugin = require('html-webpack-plugin');
|
||||||
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
|
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
|
||||||
const configurations = require("./public/conf/config.json");
|
const configurations = require('./public/conf/config.json');
|
||||||
|
|
||||||
const config = {
|
const config = {
|
||||||
devtool: "source-map",
|
devtool: 'source-map',
|
||||||
output: {
|
output: {
|
||||||
publicPath: '/publisher/'
|
publicPath: '/publisher/',
|
||||||
|
},
|
||||||
|
watch: false,
|
||||||
|
resolve: {
|
||||||
|
alias: {
|
||||||
|
AppData: path.resolve(__dirname, 'source/src/app/common/'),
|
||||||
|
AppComponents: path.resolve(__dirname, 'source/src/app/components/'),
|
||||||
},
|
},
|
||||||
watch: false,
|
extensions: ['.jsx', '.js', '.ttf', '.woff', '.woff2', '.svg'],
|
||||||
resolve: {
|
},
|
||||||
alias: {
|
module: {
|
||||||
AppData: path.resolve(__dirname, 'source/src/app/common/'),
|
rules: [
|
||||||
AppComponents: path.resolve(__dirname, 'source/src/app/components/')
|
{
|
||||||
},
|
test: /\.(js|jsx)$/,
|
||||||
extensions: ['.jsx', '.js', '.ttf', '.woff', '.woff2', '.svg']
|
exclude: /node_modules/,
|
||||||
},
|
use: [
|
||||||
module: {
|
{
|
||||||
rules: [
|
loader: 'babel-loader',
|
||||||
{
|
},
|
||||||
test: /\.(js|jsx)$/,
|
],
|
||||||
exclude: /node_modules/,
|
},
|
||||||
use: [
|
{
|
||||||
{
|
test: /\.html$/,
|
||||||
loader: 'babel-loader'
|
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: /\.html$/,
|
],
|
||||||
use: [
|
},
|
||||||
{
|
{
|
||||||
loader: "html-loader",
|
test: /\.(woff|woff2|eot|ttf|svg)$/,
|
||||||
options: {minimize: true}
|
loader: 'url-loader?limit=100000',
|
||||||
}
|
},
|
||||||
]
|
{
|
||||||
|
test: /\.(png|jpe?g)/i,
|
||||||
|
use: [
|
||||||
|
{
|
||||||
|
loader: 'url-loader',
|
||||||
|
options: {
|
||||||
|
name: './img/[name].[ext]',
|
||||||
|
limit: 10000,
|
||||||
},
|
},
|
||||||
{
|
},
|
||||||
test: /\.css$/,
|
{
|
||||||
use: [MiniCssExtractPlugin.loader, "css-loader"]
|
loader: 'img-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'))
|
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") {
|
if (process.env.NODE_ENV === 'development') {
|
||||||
config.watch = true;
|
config.watch = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = config;
|
module.exports = config;
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user