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:
Dharmakeerthi Lasantha 2020-01-16 07:15:52 +00:00
commit 751f2b4bb8
50 changed files with 8713 additions and 7519 deletions

View File

@ -79,6 +79,16 @@
<arguments>install</arguments>
</configuration>
</execution>
<execution>
<id>lint</id>
<goals>
<goal>npm</goal>
</goals>
<configuration>
<arguments>run-script lint</arguments>
</configuration>
<phase>generate-resources</phase>
</execution>
<execution>
<id>prod</id>
<goals>

View File

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

View File

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

View File

@ -18,12 +18,11 @@
module.exports = function(api) {
api.cache(true);
const presets = [ "@babel/preset-env",
"@babel/preset-react" ];
const plugins = ["@babel/plugin-proposal-class-properties"];
const presets = ['@babel/preset-env', '@babel/preset-react'];
const plugins = ['@babel/plugin-proposal-class-properties'];
return {
presets,
plugins
plugins,
};
};

View File

@ -56,6 +56,12 @@
"body-parser": "^1.19.0",
"chai": "^4.1.2",
"css-loader": "^0.28.11",
"eslint": "^5.16.0",
"eslint-config-prettier": "4.3.0",
"eslint-plugin-babel": "5.3.0",
"eslint-plugin-jsx": "0.0.2",
"eslint-plugin-prettier": "3.1.0",
"eslint-plugin-react": "7.14.2",
"express": "^4.17.1",
"express-pino-logger": "^4.0.0",
"file-loader": "^2.0.0",
@ -74,6 +80,7 @@
"npm-run-all": "^4.1.5",
"pino-colada": "^1.4.5",
"postcss-loader": "^3.0.0",
"prettier": "1.18.1",
"react": "^16.8.6",
"react-dom": "^16.8.6",
"react-intl": "^2.9.0",
@ -96,6 +103,7 @@
"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 ",
"server": "node-env-run server --exec nodemon | pino-colada",
"dev2": "run-p server start"
"dev2": "run-p server start",
"lint": "eslint \"src/**/*.js\""
}
}

View File

@ -16,26 +16,25 @@
* under the License.
*/
import React from "react";
import "antd/dist/antd.less";
import RouteWithSubRoutes from "./components/RouteWithSubRoutes";
import {
BrowserRouter as Router,
Redirect, Switch,
} from 'react-router-dom';
import axios from "axios";
import {Layout, Spin, Result, notification} from "antd";
import ConfigContext from "./context/ConfigContext";
import React from 'react';
import 'antd/dist/antd.less';
import RouteWithSubRoutes from './components/RouteWithSubRoutes';
import { BrowserRouter as Router, Redirect, Switch } from 'react-router-dom';
import axios from 'axios';
import { Layout, Spin, Result } from 'antd';
import ConfigContext from './context/ConfigContext';
const { Content } = Layout;
const loadingView = (
<Layout>
<Content style={{
<Content
style={{
padding: '0 0',
paddingTop: 300,
backgroundColor: '#fff',
textAlign: 'center'
}}>
textAlign: 'center',
}}
>
<Spin tip="Loading..." />
</Content>
</Layout>
@ -44,7 +43,7 @@ const loadingView = (
const errorView = (
<Result
style={{
paddingTop: 200
paddingTop: 200,
}}
status="500"
title="Error occurred while loading the configuration"
@ -53,89 +52,103 @@ const errorView = (
);
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
loading: true,
error: false,
config: {}
}
config: {},
};
}
componentDidMount() {
this.updateFavicon();
axios.get(
window.location.origin + "/publisher/public/conf/config.json",
).then(res => {
axios
.get(window.location.origin + '/publisher/public/conf/config.json')
.then(res => {
const config = res.data;
this.checkUserLoggedIn(config);
}).catch((error) => {
})
.catch(error => {
this.setState({
loading: false,
error: true
})
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 => {
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
config: config,
});
}).catch((error) => {
})
.catch(error => {
config.androidEnterpriseToken = null;
this.setState({
loading: false,
config: config
})
config: config,
});
});
};
checkUserLoggedIn = (config) => {
axios.post(
window.location.origin + "/publisher-ui-request-handler/user",
"platform=publisher"
).then(res => {
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/`;
if (lastURLSegment === 'login') {
window.location.href = window.location.origin + '/publisher/';
} else {
this.getAndroidEnterpriseToken(config);
}
}).catch((error) => {
if (error.hasOwnProperty("response") && error.response.status === 401) {
})
.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}`;
if (lastURLSegment !== 'login') {
window.location.href =
window.location.origin +
`/publisher/login?redirect=${redirectUrl}`;
} else {
this.getAndroidEnterpriseToken(config);
}
} else {
this.setState({
loading: false,
error: true
})
error: true,
});
}
});
};
updateFavicon = () => {
const link = document.querySelector("link[rel*='icon']") || document.createElement('link');
const link =
document.querySelector("link[rel*='icon']") ||
document.createElement('link');
link.type = 'image/x-icon';
link.rel = 'shortcut icon';
link.href = window.location.origin+'/devicemgt/public/uuf.unit.favicon/img/favicon.png';
link.href =
window.location.origin +
'/devicemgt/public/uuf.unit.favicon/img/favicon.png';
document.getElementsByTagName('head')[0].appendChild(link);
};
@ -148,7 +161,7 @@ class App extends React.Component {
<div>
<Switch>
<Redirect exact from="/publisher" to="/publisher/apps" />
{this.props.routes.map((route) => (
{this.props.routes.map(route => (
<RouteWithSubRoutes key={route.path} {...route} />
))}
</Switch>

View File

@ -26,12 +26,15 @@ class RouteWithSubRoutes extends React.Component{
}
render() {
return (
<Route path={this.props.path} exact={this.props.exact} render={(props) => (
<Route
path={this.props.path}
exact={this.props.exact}
render={props => (
<this.props.component {...props} routes={this.props.routes} />
)}/>
)}
/>
);
}
}
export default RouteWithSubRoutes;

View File

@ -16,24 +16,22 @@
* under the License.
*/
import React from "react";
import {Row, Typography, Icon, notification} from "antd";
import StarRatings from "react-star-ratings";
import "./DetailedRating.css";
import axios from "axios";
import {withConfigContext} from "../../../context/ConfigContext";
import {handleApiError} from "../../../js/Utils";
import React from 'react';
import { Row, Typography, Icon } from 'antd';
import StarRatings from 'react-star-ratings';
import './DetailedRating.css';
import axios from 'axios';
import { withConfigContext } from '../../../context/ConfigContext';
import { handleApiError } from '../../../js/Utils';
const { Text } = Typography;
class DetailedRating extends React.Component {
constructor(props) {
super(props);
this.state = {
detailedRating: null
}
detailedRating: null,
};
}
componentDidMount() {
@ -50,25 +48,37 @@ class DetailedRating extends React.Component{
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 => {
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
})
detailedRating,
});
}
}).catch(function (error) {
handleApiError(error, "Error occurred while trying to load rating for the release.", true);
})
.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;
}
@ -78,6 +88,7 @@ class DetailedRating extends React.Component{
const ratingArray = [];
// eslint-disable-next-line no-unused-vars
for (let [key, value] of Object.entries(ratingVariety)) {
ratingArray.push(value);
}
@ -88,11 +99,11 @@ class DetailedRating extends React.Component{
if (maximumRating > 0) {
for (let i = 0; i < 5; i++) {
ratingBarPercentages[i] = (ratingVariety[(i+1).toString()])/maximumRating*100;
ratingBarPercentages[i] =
(ratingVariety[(i + 1).toString()] / maximumRating) * 100;
}
}
return (
<Row className="d-rating">
<div className="numeric-data">
@ -103,31 +114,58 @@ class DetailedRating extends React.Component{
starDimension="16px"
starSpacing="2px"
numberOfStars={5}
name='rating'
name="rating"
/>
<br />
<Text type="secondary" className="people-count"><Icon type="team" /> {totalCount} total</Text>
<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>
<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>
<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>
<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>
<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>
<span
className="bar rate-1"
style={{ width: ratingBarPercentages[0] + '%' }}
>
{' '}
</span>
</div>
</div>
</Row>
@ -135,5 +173,4 @@ class DetailedRating extends React.Component{
}
}
export default withConfigContext(DetailedRating);

View File

@ -30,19 +30,20 @@ import {
Spin,
message,
Icon,
Card, Badge, Tooltip, Popover
Card,
Badge,
Tooltip,
} from 'antd';
import DetailedRating from "../../detailed-rating/DetailedRating";
import {Link} from "react-router-dom";
import axios from "axios";
import ReactQuill from "react-quill";
import DetailedRating from '../../detailed-rating/DetailedRating';
import { Link } from 'react-router-dom';
import axios from 'axios';
import ReactQuill from 'react-quill';
import ReactHtmlParser from 'react-html-parser';
import "./AppDetailsDrawer.css";
import pSBC from "shade-blend-color";
import {withConfigContext} from "../../../../context/ConfigContext";
import ManagedConfigurationsIframe
from "../../../manage/android-enterprise/ManagedConfigurationsIframe/ManagedConfigurationsIframe";
import {handleApiError} from "../../../../js/Utils";
import './AppDetailsDrawer.css';
import pSBC from 'shade-blend-color';
import { withConfigContext } from '../../../../context/ConfigContext';
import ManagedConfigurationsIframe from '../../../manage/android-enterprise/ManagedConfigurationsIframe/ManagedConfigurationsIframe';
import { handleApiError } from '../../../../js/Utils';
const { Meta } = Card;
const { Text, Title } = Typography;
@ -58,15 +59,20 @@ const IconText = ({type, text}) => (
const modules = {
toolbar: [
['bold', 'italic', 'underline', 'strike', 'blockquote'],
[{'list': 'ordered'}, {'list': 'bullet'}],
['link']
[{ list: 'ordered' }, { list: 'bullet' }],
['link'],
],
};
const formats = [
'bold', 'italic', 'underline', 'strike', 'blockquote',
'list', 'bullet',
'link'
'bold',
'italic',
'underline',
'strike',
'blockquote',
'list',
'bullet',
'link',
];
class AppDetailsDrawer extends React.Component {
@ -76,7 +82,7 @@ class AppDetailsDrawer extends React.Component {
this.state = {
loading: false,
name: "",
name: '',
description: null,
globalCategories: [],
globalTags: [],
@ -89,7 +95,7 @@ class AppDetailsDrawer extends React.Component {
isCategoriesEditEnabled: false,
isTagsEditEnabled: false,
drawer: null,
drawerWidth
drawerWidth,
};
}
@ -114,100 +120,115 @@ class AppDetailsDrawer extends React.Component {
getCategories = () => {
const config = this.props.context;
axios.get(
window.location.origin + config.serverConfig.invoker.uri + config.serverConfig.invoker.publisher + "/applications/categories"
).then(res => {
axios
.get(
window.location.origin +
config.serverConfig.invoker.uri +
config.serverConfig.invoker.publisher +
'/applications/categories',
)
.then(res => {
if (res.status === 200) {
const categories = JSON.parse(res.data.data);
this.getTags();
const globalCategories = categories.map(category => {
return (
<Option
key={category.categoryName}>
<Option key={category.categoryName}>
{category.categoryName}
</Option>
)
);
});
this.setState({
globalCategories,
loading: false
loading: false,
});
}
}).catch((error) => {
handleApiError(error, "Error occurred while trying to load categories.", true);
})
.catch(error => {
handleApiError(
error,
'Error occurred while trying to load categories.',
true,
);
this.setState({
loading: false
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 => {
axios
.get(
window.location.origin +
config.serverConfig.invoker.uri +
config.serverConfig.invoker.publisher +
'/applications/tags',
)
.then(res => {
if (res.status === 200) {
const tags = JSON.parse(res.data.data);
const globalTags = tags.map(tag => {
return (
<Option
key={tag.tagName}>
{tag.tagName}
</Option>
)
return <Option key={tag.tagName}>{tag.tagName}</Option>;
});
this.setState({
globalTags,
loading: false
loading: false,
});
}
}).catch((error) => {
handleApiError(error, "Error occurred while trying to load tags.");
})
.catch(error => {
handleApiError(error, 'Error occurred while trying to load tags.');
this.setState({
loading: false
loading: false,
});
});
};
// change the app name
handleNameSave = name => {
const config = this.props.context;
const { id } = this.props.app;
if (name !== this.state.name && name !== "") {
if (name !== this.state.name && name !== '') {
const data = { name: name };
axios.put(
window.location.origin + config.serverConfig.invoker.uri + config.serverConfig.invoker.publisher + "/applications/" + id,
data
).then(res => {
axios
.put(
window.location.origin +
config.serverConfig.invoker.uri +
config.serverConfig.invoker.publisher +
'/applications/' +
id,
data,
)
.then(res => {
if (res.status === 200) {
const app = res.data.data;
this.props.onUpdateApp("name", app.name);
notification["success"]({
this.props.onUpdateApp('name', app.name);
notification.success({
message: 'Saved!',
description: 'App name updated successfully!'
description: 'App name updated successfully!',
});
this.setState({
loading: false,
name: app.name,
});
}
}).catch((error) => {
if (error.hasOwnProperty("response") && error.response.status === 401) {
})
.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",
notification.error({
message: 'There was a problem',
duration: 0,
description:
"Error occurred while trying to save the app name.",
description: 'Error occurred while trying to save the app name.',
});
}
@ -217,14 +238,14 @@ class AppDetailsDrawer extends React.Component {
};
// handle description change
handleDescriptionChange = (temporaryDescription) => {
this.setState({temporaryDescription})
handleDescriptionChange = temporaryDescription => {
this.setState({ temporaryDescription });
};
enableDescriptionEdit = () => {
this.setState({
isDescriptionEditEnabled: true,
temporaryDescription: this.state.description
temporaryDescription: this.state.description,
});
};
@ -237,7 +258,7 @@ class AppDetailsDrawer extends React.Component {
enableCategoriesEdit = () => {
this.setState({
isCategoriesEditEnabled: true,
temporaryCategories: this.state.categories
temporaryCategories: this.state.categories,
});
};
@ -248,8 +269,8 @@ class AppDetailsDrawer extends React.Component {
};
// handle description change
handleCategoryChange = (temporaryCategories) => {
this.setState({temporaryCategories})
handleCategoryChange = temporaryCategories => {
this.setState({ temporaryCategories });
};
// change app categories
@ -264,34 +285,43 @@ class AppDetailsDrawer extends React.Component {
if (difference.length !== 0 && temporaryCategories.length !== 0) {
const data = { categories: temporaryCategories };
axios.put(
window.location.origin + config.serverConfig.invoker.uri + config.serverConfig.invoker.publisher + "/applications/" + id,
data
).then(res => {
axios
.put(
window.location.origin +
config.serverConfig.invoker.uri +
config.serverConfig.invoker.publisher +
'/applications/' +
id,
data,
)
.then(res => {
if (res.status === 200) {
const app = res.data.data;
this.props.onUpdateApp("categories", temporaryCategories);
notification["success"]({
this.props.onUpdateApp('categories', temporaryCategories);
notification.success({
message: 'Saved!',
description: 'App categories updated successfully!'
description: 'App categories updated successfully!',
});
this.setState({
loading: false,
categories: app.categories,
isCategoriesEditEnabled: false
isCategoriesEditEnabled: false,
});
}
}).catch((error) => {
if (error.hasOwnProperty("response") && error.response.status === 401) {
})
.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",
notification.error({
message: 'There was a problem',
duration: 0,
description:
"Error occurred while trying to updating categories.",
'Error occurred while trying to updating categories.',
});
}
@ -303,7 +333,7 @@ class AppDetailsDrawer extends React.Component {
enableTagsEdit = () => {
this.setState({
isTagsEditEnabled: true,
temporaryTags: this.state.tags
temporaryTags: this.state.tags,
});
};
@ -314,8 +344,8 @@ class AppDetailsDrawer extends React.Component {
};
// handle description change
handleTagsChange = (temporaryTags) => {
this.setState({temporaryTags})
handleTagsChange = temporaryTags => {
this.setState({ temporaryTags });
};
// change app tags
@ -324,39 +354,47 @@ class AppDetailsDrawer extends React.Component {
const { id } = this.props.app;
const { temporaryTags, tags } = this.state;
const difference = temporaryTags
.filter(x => !tags.includes(x))
.concat(tags.filter(x => !temporaryTags.includes(x)));
if (difference.length !== 0 && temporaryTags.length !== 0) {
const data = { tags: temporaryTags };
axios.put(
window.location.origin + config.serverConfig.invoker.uri + config.serverConfig.invoker.publisher + "/applications/" + id,
data
).then(res => {
axios
.put(
window.location.origin +
config.serverConfig.invoker.uri +
config.serverConfig.invoker.publisher +
'/applications/' +
id,
data,
)
.then(res => {
if (res.status === 200) {
const app = res.data.data;
notification["success"]({
notification.success({
message: 'Saved!',
description: 'App tags updated successfully!'
description: 'App tags updated successfully!',
});
this.setState({
loading: false,
tags: app.tags,
isTagsEditEnabled: false
isTagsEditEnabled: false,
});
}
}).catch((error) => {
if (error.hasOwnProperty("response") && error.response.status === 401) {
})
.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",
notification.error({
message: 'There was a problem',
duration: 0,
description:
"Error occurred while trying to update tags",
description: 'Error occurred while trying to update tags',
});
}
@ -371,26 +409,39 @@ class AppDetailsDrawer extends React.Component {
const { id } = this.props.app;
const { description, temporaryDescription } = this.state;
if (temporaryDescription !== description && temporaryDescription !== "<p><br></p>") {
if (
temporaryDescription !== description &&
temporaryDescription !== '<p><br></p>'
) {
const data = { description: temporaryDescription };
axios.put(
window.location.origin + config.serverConfig.invoker.uri + config.serverConfig.invoker.publisher + "/applications/" + id,
data
).then(res => {
axios
.put(
window.location.origin +
config.serverConfig.invoker.uri +
config.serverConfig.invoker.publisher +
'/applications/' +
id,
data,
)
.then(res => {
if (res.status === 200) {
const app = res.data.data;
notification["success"]({
notification.success({
message: 'Saved!',
description: 'App description updated successfully!'
description: 'App description updated successfully!',
});
this.setState({
loading: false,
description: app.description,
isDescriptionEditEnabled: false
isDescriptionEditEnabled: false,
});
}
}).catch((error) => {
if (error.hasOwnProperty("response") && error.response.status === 401) {
})
.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 {
@ -404,14 +455,23 @@ class AppDetailsDrawer extends React.Component {
}
};
render() {
const config = this.props.context;
const { app, visible, onClose } = this.props;
const {
name, loading, description, isDescriptionEditEnabled, isCategoriesEditEnabled,
isTagsEditEnabled, temporaryDescription, temporaryCategories, temporaryTags,
globalCategories, globalTags, categories, tags
name,
loading,
description,
isDescriptionEditEnabled,
isCategoriesEditEnabled,
isTagsEditEnabled,
temporaryDescription,
temporaryCategories,
temporaryTags,
globalCategories,
globalTags,
categories,
tags,
} = this.state;
if (app == null) {
return null;
@ -422,13 +482,15 @@ class AppDetailsDrawer extends React.Component {
if (app.applicationReleases.length === 0) {
const avatarLetter = name.charAt(0).toUpperCase();
avatar = (
<Avatar shape="square"
<Avatar
shape="square"
size={100}
style={{
marginBottom: 10,
borderRadius: "28%",
backgroundColor: pSBC(0.50, config.theme.primaryColor)
}}>
borderRadius: '28%',
backgroundColor: pSBC(0.5, config.theme.primaryColor),
}}
>
{avatarLetter}
</Avatar>
);
@ -438,12 +500,12 @@ class AppDetailsDrawer extends React.Component {
style={{
marginBottom: 10,
width: 100,
borderRadius: "28%",
border: "1px solid #ddd"
borderRadius: '28%',
border: '1px solid #ddd',
}}
src={app.applicationReleases[0].iconPath}
/>
)
);
}
return (
@ -453,36 +515,39 @@ class AppDetailsDrawer extends React.Component {
width={this.state.drawerWidth}
closable={false}
onClose={onClose}
visible={visible}>
visible={visible}
>
<Spin spinning={loading} delay={500}>
<div style={{textAlign: "center"}}>
<div style={{ textAlign: 'center' }}>
{avatar}
<Title editable={{onChange: this.handleNameSave}} level={2}>{name}</Title>
<Title editable={{ onChange: this.handleNameSave }} level={2}>
{name}
</Title>
</div>
<Divider />
{/* display manage config button only if the app is public android app*/}
{(app.isAndroidEnterpriseApp) &&
(config.androidEnterpriseToken !== null) &&
(
{app.isAndroidEnterpriseApp &&
config.androidEnterpriseToken !== null && (
<div>
<div>
<Text strong={true}>Set up managed configurations</Text>
</div>
<div style={{ paddingTop: 16 }}>
<Text>
If you are developing apps for the enterprise market, you may need to satisfy
particular requirements set by a organization's policies. Managed
configurations,
previously known as application restrictions, allow the organization's IT admin
to
remotely specify settings for apps. This capability is particularly useful for
If you are developing apps for the enterprise market, you
may need to satisfy particular requirements set by a
organization&apos;s policies. Managed configurations,
previously known as application restrictions, allow the
organization&apos;s IT admin to remotely specify settings
for apps. This capability is particularly useful for
organization-approved apps deployed to a work profile.
</Text>
</div>
<br />
<ManagedConfigurationsIframe
style={{ paddingTop: 16 }}
packageName={app.packageName}/>
packageName={app.packageName}
/>
<Divider dashed={true} />
</div>
)}
@ -493,55 +558,74 @@ class AppDetailsDrawer extends React.Component {
grid={{ gutter: 16, column: 2 }}
pagination={{
pageSize: 4, // number of releases per page
size: "small",
size: 'small',
}}
dataSource={app.applicationReleases}
renderItem={release => (
<div className="app-release-cards">
<List.Item>
<Tooltip title="Click to view full details"
placement="topRight">
<Link to={"apps/releases/" + release.uuid}>
<Tooltip
title="Click to view full details"
placement="topRight"
>
<Link to={'apps/releases/' + release.uuid}>
<Card className="release-card">
<Meta
avatar={
<div>
{(release.currentStatus === "PUBLISHED") ? (
{release.currentStatus === 'PUBLISHED' ? (
<Badge
title="Published"
count={
<Tooltip
title="Published">
<Tooltip title="Published">
<Icon
style={{
backgroundColor: '#52c41a',
borderRadius: "50%",
color: "white"
borderRadius: '50%',
color: 'white',
}}
type="check-circle"/>
type="check-circle"
/>
</Tooltip>
}>
<Avatar size="large" shape="square"
src={release.iconPath}/>
}
>
<Avatar
size="large"
shape="square"
src={release.iconPath}
/>
</Badge>
) : (
<Avatar size="large" shape="square"
src={release.iconPath}/>
<Avatar
size="large"
shape="square"
src={release.iconPath}
/>
)}
</div>
}
title={release.version}
description={
<div style={{
fontSize: "0.7em"
}} className="description-view">
<IconText type="check"
text={release.currentStatus}/>
<div
style={{
fontSize: '0.7em',
}}
className="description-view"
>
<IconText
type="check"
text={release.currentStatus}
/>
<Divider type="vertical" />
<IconText type="upload" text={release.releaseType}/>
<IconText
type="upload"
text={release.releaseType}
/>
<Divider type="vertical" />
<IconText type="star-o"
text={release.rating.toFixed(1)}/>
<IconText
type="star-o"
text={release.rating.toFixed(1)}
/>
</div>
}
/>
@ -555,23 +639,21 @@ class AppDetailsDrawer extends React.Component {
</div>
{/* display add new release only if app type is enterprise*/}
{(app.type === "ENTERPRISE") && (
{app.type === 'ENTERPRISE' && (
<div>
<Divider dashed={true} />
<div style={{ paddingBottom: 16 }}>
<Text>
Add new release for the application
</Text>
<Text>Add new release for the application</Text>
</div>
<Link to={`/publisher/apps/${app.deviceType}/${app.id}/add-release`}>
<Button
htmlType="button"
type="primary"
size="small">
<Link
to={`/publisher/apps/${app.deviceType}/${app.id}/add-release`}
>
<Button htmlType="button" type="primary" size="small">
Add
</Button>
</Link>
</div>)}
</div>
)}
<Divider dashed={true} />
<Text strong={true}>Description </Text>
@ -579,9 +661,10 @@ class AppDetailsDrawer extends React.Component {
<Text
style={{
color: config.theme.primaryColor,
cursor: "pointer"
cursor: 'pointer',
}}
onClick={this.enableDescriptionEdit}>
onClick={this.enableDescriptionEdit}
>
<Icon type="edit" />
</Text>
)}
@ -601,21 +684,35 @@ class AppDetailsDrawer extends React.Component {
placeholder="Add description"
style={{
marginBottom: 10,
marginTop: 10
marginTop: 10,
}}
/>
<Button style={{marginRight: 10}} size="small" htmlType="button"
onClick={this.disableDescriptionEdit}>Cancel</Button>
<Button size="small" type="primary" htmlType="button"
onClick={this.handleDescriptionSave}>Save</Button>
<Button
style={{ marginRight: 10 }}
size="small"
htmlType="button"
onClick={this.disableDescriptionEdit}
>
Cancel
</Button>
<Button
size="small"
type="primary"
htmlType="button"
onClick={this.handleDescriptionSave}
>
Save
</Button>
</div>
)}
<Divider dashed={true} />
<Text strong={true}>Categories </Text>
{!isCategoriesEditEnabled && (<Text
style={{color: config.theme.primaryColor, cursor: "pointer"}}
onClick={this.enableCategoriesEdit}>
{!isCategoriesEditEnabled && (
<Text
style={{ color: config.theme.primaryColor, cursor: 'pointer' }}
onClick={this.enableCategoriesEdit}
>
<Icon type="edit" />
</Text>
)}
@ -633,35 +730,48 @@ class AppDetailsDrawer extends React.Component {
{globalCategories}
</Select>
<div style={{ marginTop: 10 }}>
<Button style={{marginRight: 10}} size="small" htmlType="button"
onClick={this.disableCategoriesEdit}>Cancel</Button>
<Button
style={{ marginRight: 10 }}
size="small"
htmlType="button"
onClick={this.disableCategoriesEdit}
>
Cancel
</Button>
<Button
size="small"
type="primary"
htmlType="button"
onClick={this.handleCategorySave}>Save</Button>
onClick={this.handleCategorySave}
>
Save
</Button>
</div>
</div>
)}
{!isCategoriesEditEnabled && (
<span>{
categories.map(category => {
<span>
{categories.map(category => {
return (
<Tag color={pSBC(0.30, config.theme.primaryColor)} key={category}
style={{marginBottom: 5}}>
<Tag
color={pSBC(0.3, config.theme.primaryColor)}
key={category}
style={{ marginBottom: 5 }}
>
{category}
</Tag>
);
})
}</span>
})}
</span>
)}
<Divider dashed={true} />
<Text strong={true}>Tags </Text>
{!isTagsEditEnabled && (<Text
style={{color: config.theme.primaryColor, cursor: "pointer"}}
onClick={this.enableTagsEdit}>
{!isTagsEditEnabled && (
<Text
style={{ color: config.theme.primaryColor, cursor: 'pointer' }}
onClick={this.enableTagsEdit}
>
<Icon type="edit" />
</Text>
)}
@ -679,33 +789,46 @@ class AppDetailsDrawer extends React.Component {
{globalTags}
</Select>
<div style={{ marginTop: 10 }}>
<Button style={{marginRight: 10}} size="small" htmlType="button"
onClick={this.disableTagsEdit}>Cancel</Button>
<Button
style={{ marginRight: 10 }}
size="small"
htmlType="button"
onClick={this.disableTagsEdit}
>
Cancel
</Button>
<Button
size="small"
type="primary"
htmlType="button"
onClick={this.handleTagsSave}>Save</Button>
onClick={this.handleTagsSave}
>
Save
</Button>
</div>
</div>
)}
{!isTagsEditEnabled && (
<span>{
tags.map(tag => {
<span>
{tags.map(tag => {
return (
<Tag color="#34495e" key={tag} style={{ marginBottom: 5 }}>
{tag}
</Tag>
);
})
}</span>
})}
</span>
)}
<Divider dashed={true} />
<div className="app-rate">
{app.applicationReleases.length > 0 && (
<DetailedRating type="app" uuid={app.applicationReleases[0].uuid}/>)}
<DetailedRating
type="app"
uuid={app.applicationReleases[0].uuid}
/>
)}
</div>
</Spin>
</Drawer>

View File

@ -16,30 +16,25 @@
* under the License.
*/
import React from "react";
import React from 'react';
import {
Card,
Col,
Row,
Typography,
Input,
Divider,
Icon,
Select,
Button,
Form,
message,
Radio,
notification, Alert
} from "antd";
import axios from "axios";
import {withConfigContext} from "../../../context/ConfigContext";
import {handleApiError} from "../../../js/Utils";
Alert,
} from 'antd';
import axios from 'axios';
import { withConfigContext } from '../../../context/ConfigContext';
import { handleApiError } from '../../../js/Utils';
const { Option } = Select;
const { Title } = Typography;
class FiltersForm extends React.Component {
constructor(props) {
super(props);
@ -50,8 +45,8 @@ class FiltersForm extends React.Component {
forbiddenErrors: {
categories: false,
tags: false,
deviceTypes: false
}
deviceTypes: false,
},
};
}
@ -64,12 +59,12 @@ class FiltersForm extends React.Component {
}
}
if (values.hasOwnProperty("deviceType") && values.deviceType === "ALL") {
delete values["deviceType"];
if (values.hasOwnProperty('deviceType') && values.deviceType === 'ALL') {
delete values.deviceType;
}
if (values.hasOwnProperty("appType") && values.appType === "ALL") {
delete values["appType"];
if (values.hasOwnProperty('appType') && values.appType === 'ALL') {
delete values.appType;
}
this.props.setFilters(values);
@ -84,29 +79,38 @@ class FiltersForm extends React.Component {
getCategories = () => {
const config = this.props.context;
axios.get(
window.location.origin + config.serverConfig.invoker.uri + config.serverConfig.invoker.publisher + "/applications/categories"
).then(res => {
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
loading: false,
});
}
}).catch((error) => {
handleApiError(error, "Error occurred while trying to load categories.", true);
if (error.hasOwnProperty("response") && error.response.status === 403) {
})
.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
})
loading: false,
});
} else {
this.setState({
loading: false
loading: false,
});
}
});
@ -114,9 +118,14 @@ class FiltersForm extends React.Component {
getTags = () => {
const config = this.props.context;
axios.get(
window.location.origin + config.serverConfig.invoker.uri + config.serverConfig.invoker.publisher + "/applications/tags"
).then(res => {
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({
@ -124,30 +133,38 @@ class FiltersForm extends React.Component {
loading: false,
});
}
}).catch((error) => {
handleApiError(error, "Error occurred while trying to load tags.", true);
if (error.hasOwnProperty("response") && error.response.status === 403) {
})
.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
})
loading: false,
});
} else {
this.setState({
loading: false
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 => {
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({
@ -155,19 +172,23 @@ class FiltersForm extends React.Component {
loading: false,
});
}
}).catch((error) => {
handleApiError(error, "Error occurred while trying to load device types.", true);
if (error.hasOwnProperty("response") && error.response.status === 403) {
})
.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
})
loading: false,
});
} else {
this.setState({
loading: false
loading: false,
});
}
});
@ -178,141 +199,133 @@ class FiltersForm extends React.Component {
const { getFieldDecorator } = this.props.form;
return (
<Card>
<Form labelAlign="left" layout="horizontal"
<Form
labelAlign="left"
layout="horizontal"
hideRequiredMark
onSubmit={this.handleSubmit}>
onSubmit={this.handleSubmit}
>
<Row>
<Col span={12}>
<Title level={4}>Filter</Title>
</Col>
<Col span={12}>
<Form.Item style={{
float: "right",
<Form.Item
style={{
float: 'right',
marginBottom: 0,
marginTop: -5
}}>
<Button
size="small"
type="primary"
htmlType="submit">
marginTop: -5,
}}
>
<Button size="small" type="primary" htmlType="submit">
Submit
</Button>
</Form.Item>
</Col>
</Row>
{(forbiddenErrors.categories) && (
{forbiddenErrors.categories && (
<Alert
message="You don't have permission to view categories."
type="warning"
banner
closable/>
closable
/>
)}
<Form.Item label="Categories">
{getFieldDecorator('categories', {
rules: [{
rules: [
{
required: false,
message: 'Please select categories'
}],
message: 'Please select categories',
},
],
})(
<Select
mode="multiple"
style={{ width: '100%' }}
placeholder="Select a Category"
onChange={this.handleCategoryChange}>
{
categories.map(category => {
onChange={this.handleCategoryChange}
>
{categories.map(category => {
return (
<Option
key={category.categoryName}>
<Option key={category.categoryName}>
{category.categoryName}
</Option>
)
})
}
</Select>
);
})}
</Select>,
)}
</Form.Item>
{(forbiddenErrors.deviceTypes) && (
{forbiddenErrors.deviceTypes && (
<Alert
message="You don't have permission to view device types."
type="warning"
banner
closable/>
closable
/>
)}
<Form.Item label="Device Type">
{getFieldDecorator('deviceType', {
rules: [{
rules: [
{
required: false,
message: 'Please select device types'
}],
message: 'Please select device types',
},
],
})(
<Select
style={{ width: '100%' }}
placeholder="Select device types">
{
deviceTypes.map(deviceType => {
placeholder="Select device types"
>
{deviceTypes.map(deviceType => {
return (
<Option
key={deviceType.name}>
{deviceType.name}
</Option>
)
})
}
<Option
key="ALL">All
</Option>
</Select>
<Option key={deviceType.name}>{deviceType.name}</Option>
);
})}
<Option key="ALL">All</Option>
</Select>,
)}
</Form.Item>
{(forbiddenErrors.tags) && (
{forbiddenErrors.tags && (
<Alert
message="You don't have permission to view tags."
type="warning"
banner
closable/>
closable
/>
)}
<Form.Item label="Tags">
{getFieldDecorator('tags', {
rules: [{
rules: [
{
required: false,
message: 'Please select tags'
}],
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>
{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"
>
<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>
</Select>,
)}
</Form.Item>
<Divider />
@ -322,8 +335,8 @@ class FiltersForm extends React.Component {
}
}
const Filters = withConfigContext(Form.create({name: 'filter-apps'})(FiltersForm));
const Filters = withConfigContext(
Form.create({ name: 'filter-apps' })(FiltersForm),
);
export default withConfigContext(Filters);

View File

@ -16,12 +16,10 @@
* under the License.
*/
import React from "react";
import {Card, Col, Row, Typography, Input, Divider, notification} from "antd";
import AppsTable from "./appsTable/AppsTable";
import Filters from "./Filters";
import AppDetailsDrawer from "./AppDetailsDrawer/AppDetailsDrawer";
import axios from "axios";
import React from 'react';
import { Card, Col, Row, Typography, Input, Divider } from 'antd';
import AppsTable from './appsTable/AppsTable';
import Filters from './Filters';
const { Title } = Typography;
const Search = Input.Search;
@ -30,48 +28,48 @@ class ListApps extends React.Component {
constructor(props) {
super(props);
this.state = {
filters: {}
filters: {},
};
this.appName = '';
}
setFilters = (filters) => {
if (this.appName === '' && filters.hasOwnProperty("appName")) {
delete filters["appName"];
setFilters = filters => {
if (this.appName === '' && filters.hasOwnProperty('appName')) {
delete filters.appName;
} else {
filters.appName = this.appName;
}
this.setState({
filters
filters,
});
};
setSearchText = (appName) => {
setSearchText = appName => {
const filters = { ...this.state.filters };
this.appName = appName;
if (appName === '' && filters.hasOwnProperty("appName")) {
delete filters["appName"];
if (appName === '' && filters.hasOwnProperty('appName')) {
delete filters.appName;
} else {
filters.appName = appName;
}
this.setState({
filters
filters,
});
};
onChangeSearchText = (e) => {
onChangeSearchText = e => {
const filters = { ...this.state.filters };
const appName = e.target.value;
if (appName === '' && filters.hasOwnProperty("appName")) {
delete filters["appName"];
if (appName === '' && filters.hasOwnProperty('appName')) {
delete filters.appName;
this.setState({
filters
filters,
});
}
};
render() {
const {isDrawerVisible, filters} = this.state;
const { filters } = this.state;
return (
<Card>
<Row gutter={28}>
@ -83,7 +81,7 @@ class ListApps extends React.Component {
<Col span={6}>
<Title level={4}>Apps</Title>
</Col>
<Col span={18} style={{textAlign: "right"}}>
<Col span={18} style={{ textAlign: 'right' }}>
<Search
placeholder="Search by app name"
onSearch={this.setSearchText}

View File

@ -16,14 +16,14 @@
* under the License.
*/
import React from "react";
import {Avatar, Table, Tag, Icon, message, notification, Col, Badge, Alert, Tooltip} from "antd";
import axios from "axios";
import React from 'react';
import { Avatar, Table, Tag, Icon, Badge, Alert, Tooltip } from 'antd';
import axios from 'axios';
import pSBC from 'shade-blend-color';
import "./AppsTable.css";
import {withConfigContext} from "../../../../context/ConfigContext";
import AppDetailsDrawer from "../AppDetailsDrawer/AppDetailsDrawer";
import {handleApiError} from "../../../../js/Utils";
import './AppsTable.css';
import { withConfigContext } from '../../../../context/ConfigContext';
import AppDetailsDrawer from '../AppDetailsDrawer/AppDetailsDrawer';
import { handleApiError } from '../../../../js/Utils';
let config = null;
@ -31,18 +31,22 @@ const columns = [
{
title: '',
dataIndex: 'name',
// eslint-disable-next-line react/display-name
render: (name, row) => {
let avatar = null;
if (row.applicationReleases.length === 0) {
const avatarLetter = name.charAt(0).toUpperCase();
avatar = (
<Avatar shape="square" size="large"
<Avatar
shape="square"
size="large"
style={{
marginRight: 20,
borderRadius: "28%",
border: "1px solid #ddd",
backgroundColor: pSBC(0.50, config.theme.primaryColor)
}}>
borderRadius: '28%',
border: '1px solid #ddd',
backgroundColor: pSBC(0.5, config.theme.primaryColor),
}}
>
{avatarLetter}
</Avatar>
);
@ -50,40 +54,49 @@ const columns = [
const { applicationReleases } = row;
let hasPublishedRelease = false;
for (let i = 0; i < applicationReleases.length; i++) {
if (applicationReleases[i].currentStatus === "PUBLISHED") {
if (applicationReleases[i].currentStatus === 'PUBLISHED') {
hasPublishedRelease = true;
break;
}
}
avatar = (hasPublishedRelease) ? (
avatar = hasPublishedRelease ? (
<Badge
title="Published"
style={{backgroundColor: '#52c41a', borderRadius: "50%", color: "white"}}
style={{
backgroundColor: '#52c41a',
borderRadius: '50%',
color: 'white',
}}
count={
<Tooltip
title="Published">
<Tooltip title="Published">
<Icon
style={{
backgroundColor: '#52c41a',
borderRadius: "50%",
color: "white"
borderRadius: '50%',
color: 'white',
}}
type="check-circle"/>
type="check-circle"
/>
</Tooltip>
}>
<Avatar shape="square" size="large"
}
>
<Avatar
shape="square"
size="large"
style={{
borderRadius: "28%",
border: "1px solid #ddd"
borderRadius: '28%',
border: '1px solid #ddd',
}}
src={row.applicationReleases[0].iconPath}
/>
</Badge>
) : (
<Avatar shape="square" size="large"
<Avatar
shape="square"
size="large"
style={{
borderRadius: "28%",
border: "1px solid #ddd"
borderRadius: '28%',
border: '1px solid #ddd',
}}
src={row.applicationReleases[0].iconPath}
/>
@ -94,30 +107,34 @@ const columns = [
<div>
{avatar}
<span style={{ marginLeft: 20 }}>{name}</span>
</div>);
}
</div>
);
},
},
{
title: 'Categories',
dataIndex: 'categories',
// eslint-disable-next-line react/display-name
render: categories => (
<span>
{categories.map(category => {
return (
<Tag
style={{ marginBottom: 8 }}
color={pSBC(0.30, config.theme.primaryColor)}
key={category}>
color={pSBC(0.3, config.theme.primaryColor)}
key={category}
>
{category}
</Tag>
);
})}
</span>
)
),
},
{
title: 'Platform',
dataIndex: 'deviceType',
// eslint-disable-next-line react/display-name
render: platform => {
const defaultPlatformIcons = config.defaultPlatformIcons;
let icon = defaultPlatformIcons.default.icon;
@ -129,19 +146,19 @@ const columns = [
theme = defaultPlatformIcons[platform].theme;
}
return (
<span style={{fontSize: 20, color: color, textAlign: "center"}}>
<span style={{ fontSize: 20, color: color, textAlign: 'center' }}>
<Icon type={icon} theme={theme} />
</span>
);
}
},
},
{
title: 'Type',
dataIndex: 'type'
dataIndex: 'type',
},
{
title: 'Subscription',
dataIndex: 'subMethod'
dataIndex: 'subMethod',
},
];
@ -156,7 +173,7 @@ class AppsTable extends React.Component {
selectedApp: null,
selectedAppIndex: -1,
loading: false,
isForbiddenErrorVisible: false
isForbiddenErrorVisible: false,
};
config = this.props.context;
}
@ -164,17 +181,16 @@ class AppsTable extends React.Component {
componentDidMount() {
const { filters } = this.props;
this.setState({
filters
filters,
});
this.fetch(filters);
}
componentDidUpdate(prevProps, prevState, snapshot) {
const { filters } = this.props;
if (prevProps.filters !== this.props.filters) {
this.setState({
filters
filters,
});
this.fetch(filters);
}
@ -185,15 +201,15 @@ class AppsTable extends React.Component {
this.setState({
isDrawerVisible: true,
selectedApp: app,
selectedAppIndex: appIndex
selectedAppIndex: appIndex,
});
};
// handler to close the app drawer
closeDrawer = () => {
this.setState({
isDrawerVisible: false
})
isDrawerVisible: false,
});
};
handleTableChange = (pagination, filters, sorter) => {
@ -216,25 +232,30 @@ class AppsTable extends React.Component {
this.setState({ loading: true });
const config = this.props.context;
if (!params.hasOwnProperty("page")) {
if (!params.hasOwnProperty('page')) {
params.page = 1;
}
const data = {
offset: 10 * (params.page - 1),
limit: 10,
...filters
...filters,
};
axios.post(
window.location.origin + config.serverConfig.invoker.uri + config.serverConfig.invoker.publisher + "/applications",
axios
.post(
window.location.origin +
config.serverConfig.invoker.uri +
config.serverConfig.invoker.publisher +
'/applications',
data,
).then(res => {
)
.then(res => {
if (res.status === 200) {
const data = res.data.data;
let apps = [];
if (res.data.data.hasOwnProperty("applications")) {
if (res.data.data.hasOwnProperty('applications')) {
apps = data.applications;
}
const pagination = { ...this.state.pagination };
@ -247,12 +268,17 @@ class AppsTable extends React.Component {
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
})
.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 });
});
@ -262,7 +288,7 @@ class AppsTable extends React.Component {
const apps = [...this.state.apps];
apps[this.state.selectedAppIndex][key] = value;
this.setState({
apps
apps,
});
};
@ -270,12 +296,13 @@ class AppsTable extends React.Component {
const { isDrawerVisible, loading } = this.state;
return (
<div>
{(this.state.isForbiddenErrorVisible) && (
{this.state.isForbiddenErrorVisible && (
<Alert
message="You don't have permission to view apps."
type="warning"
banner
closable/>
closable
/>
)}
<div className="apps-table">
<Table
@ -292,12 +319,14 @@ class AppsTable extends React.Component {
this.showDrawer(record, rowIndex);
},
};
}}/>
}}
/>
<AppDetailsDrawer
visible={isDrawerVisible}
onClose={this.closeDrawer}
app={this.state.selectedApp}
onUpdateApp={this.onUpdateApp}/>
onUpdateApp={this.onUpdateApp}
/>
</div>
</div>
);

View File

@ -16,28 +16,25 @@
* under the License.
*/
import React from "react";
import {Divider, Row, Col, Typography, Button, Drawer, Icon, Tooltip, Empty} from "antd";
import StarRatings from "react-star-ratings";
import Reviews from "./review/Reviews";
import "../../../App.css";
import DetailedRating from "../detailed-rating/DetailedRating";
import EditRelease from "./edit-release/EditRelease";
import {withConfigContext} from "../../../context/ConfigContext";
import NewAppUploadForm from "../../new-app/subForms/NewAppUploadForm";
import React from 'react';
import { Divider, Row, Col, Typography, Button, Icon, Tooltip } from 'antd';
import StarRatings from 'react-star-ratings';
import Reviews from './review/Reviews';
import '../../../App.css';
import DetailedRating from '../detailed-rating/DetailedRating';
import EditRelease from './edit-release/EditRelease';
import { withConfigContext } from '../../../context/ConfigContext';
const { Title, Text, Paragraph } = Typography;
class ReleaseView extends React.Component {
constructor(props) {
super(props);
this.state = {
}
this.state = {};
}
componentDidMount() {
console.log("mounted: Release view");
console.log('mounted: Release view');
}
render() {
@ -49,7 +46,9 @@ class ReleaseView extends React.Component {
return null;
}
const {isAppUpdatable, isAppInstallable} = lifecycle[currentLifecycleStatus];
const { isAppUpdatable, isAppInstallable } = lifecycle[
currentLifecycleStatus
];
const platform = app.deviceType;
const defaultPlatformIcons = config.defaultPlatformIcons;
@ -66,7 +65,7 @@ class ReleaseView extends React.Component {
try {
metaData = JSON.parse(release.metaData);
} catch (e) {
console.log(e);
}
return (
@ -84,18 +83,16 @@ class ReleaseView extends React.Component {
starDimension="20px"
starSpacing="2px"
numberOfStars={5}
name='rating'
name="rating"
/>
<br />
<Text>Platform : </Text>
<span style={{fontSize: 20, color: color, textAlign: "center"}}>
<Icon
type={icon}
theme={theme}
/>
<span style={{ fontSize: 20, color: color, textAlign: 'center' }}>
<Icon type={icon} theme={theme} />
</span>
<Divider type="vertical" />
<Text>Version : {release.version}</Text><br/>
<Text>Version : {release.version}</Text>
<br />
<EditRelease
forbiddenErrors={this.props.forbiddenErrors}
@ -106,21 +103,32 @@ class ReleaseView extends React.Component {
updateRelease={this.props.updateRelease}
supportedOsVersions={[...this.props.supportedOsVersions]}
/>
</Col>
<Col xl={8} md={10} sm={24} xs={24} style={{float: "right"}}>
<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"}>
title={
isAppInstallable
? 'Open this app in store'
: "This release isn't in an installable state"
}
>
<Button
style={{float: "right"}}
style={{ float: 'right' }}
htmlType="button"
type="primary"
icon="shop"
disabled={!isAppInstallable}
onClick={() => {
window.open(window.location.origin + "/store/" + app.deviceType + "/apps/" + release.uuid)
}}>
window.open(
window.location.origin +
'/store/' +
app.deviceType +
'/apps/' +
release.uuid,
);
}}
>
Open in store
</Button>
</Tooltip>
@ -134,7 +142,7 @@ class ReleaseView extends React.Component {
<div key={index} className="release-screenshot">
<img key={screenshotUrl} src={screenshotUrl} />
</div>
)
);
})}
</Row>
<Divider />
@ -144,17 +152,24 @@ class ReleaseView extends React.Component {
<Divider />
<Text>META DATA</Text>
<Row>
{
metaData.map((data, index)=>{
{metaData.map((data, index) => {
return (
<Col key={index} lg={8} md={6} xs={24} style={{marginTop:15}}>
<Text>{data.key}</Text><br/>
<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>)}
);
})}
{metaData.length === 0 && (
<Text type="secondary">No meta data available.</Text>
)}
</Row>
<Divider />
<Text>REVIEWS</Text>

View File

@ -16,7 +16,7 @@
* under the License.
*/
import React from "react";
import React from 'react';
import {
Modal,
Button,
@ -26,16 +26,16 @@ import {
Tooltip,
Upload,
Input,
Switch,
Form,
Divider,
Row,
Col,
Select, Alert
Select,
Alert,
} from 'antd';
import axios from "axios";
import "@babel/polyfill";
import {withConfigContext} from "../../../../context/ConfigContext";
import axios from 'axios';
import '@babel/polyfill';
import { withConfigContext } from '../../../../context/ConfigContext';
const { TextArea } = Input;
const InputGroup = Input.Group;
@ -74,8 +74,8 @@ class EditReleaseModal extends React.Component {
binaryFiles: [],
metaData: [],
formConfig: {
specificElements: {}
}
specificElements: {},
},
};
this.lowerOsVersion = null;
this.upperOsVersion = null;
@ -88,65 +88,64 @@ class EditReleaseModal extends React.Component {
generateConfig = () => {
const { type } = this.props;
const formConfig = {
type
type,
};
switch (type) {
case "ENTERPRISE":
formConfig.endpoint = "/ent-app-release";
case 'ENTERPRISE':
formConfig.endpoint = '/ent-app-release';
formConfig.specificElements = {
binaryFile: {
required: true
}
required: true,
},
};
break;
case "PUBLIC":
formConfig.endpoint = "/public-app-release";
case 'PUBLIC':
formConfig.endpoint = '/public-app-release';
formConfig.specificElements = {
packageName: {
required: true
required: true,
},
version: {
required: true
}
required: true,
},
};
break;
case "WEB_CLIP":
formConfig.endpoint = "/web-app-release";
case 'WEB_CLIP':
formConfig.endpoint = '/web-app-release';
formConfig.specificElements = {
version: {
required: true
required: true,
},
url: {
required: true
}
required: true,
},
};
break;
case "CUSTOM":
formConfig.endpoint = "/custom-app-release";
case 'CUSTOM':
formConfig.endpoint = '/custom-app-release';
formConfig.specificElements = {
binaryFile: {
required: true
required: true,
},
packageName: {
required: true
required: true,
},
version: {
required: true
}
required: true,
},
};
break;
}
this.setState({
formConfig
formConfig,
});
};
showModal = () => {
const config = this.props.context;
const {app, release} = this.props;
const { release } = this.props;
const { formConfig } = this.state;
const { specificElements } = formConfig;
let metaData = [];
@ -154,58 +153,58 @@ class EditReleaseModal extends React.Component {
try {
metaData = JSON.parse(release.metaData);
} catch (e) {
console.log(e);
}
this.props.form.setFields({
releaseType: {
value: release.releaseType
value: release.releaseType,
},
releaseDescription: {
value: release.description
}
value: release.description,
},
});
if ((config.deviceTypes.mobileTypes.includes(this.props.deviceType))) {
const osVersions = release.supportedOsVersions.split("-");
if (config.deviceTypes.mobileTypes.includes(this.props.deviceType)) {
const osVersions = release.supportedOsVersions.split('-');
this.lowerOsVersion = osVersions[0];
this.upperOsVersion = osVersions[1];
this.props.form.setFields({
lowerOsVersion: {
value: osVersions[0]
value: osVersions[0],
},
upperOsVersion: {
value: osVersions[1]
}
value: osVersions[1],
},
});
}
if (specificElements.hasOwnProperty("version")) {
if (specificElements.hasOwnProperty('version')) {
this.props.form.setFields({
version: {
value: release.version
}
value: release.version,
},
});
}
if (specificElements.hasOwnProperty("url")) {
if (specificElements.hasOwnProperty('url')) {
this.props.form.setFields({
url: {
value: release.url
}
value: release.url,
},
});
}
if (specificElements.hasOwnProperty("packageName")) {
if (specificElements.hasOwnProperty('packageName')) {
this.props.form.setFields({
packageName: {
value: release.packageName
}
value: release.packageName,
},
});
}
this.setState({
visible: true,
metaData
metaData,
});
};
@ -229,10 +228,11 @@ class EditReleaseModal extends React.Component {
};
handleIconChange = ({ fileList }) => this.setState({ icons: fileList });
handleBinaryFileChange = ({fileList}) => this.setState({binaryFiles: fileList});
handleScreenshotChange = ({fileList}) => this.setState({screenshots: fileList});
handleBinaryFileChange = ({ fileList }) =>
this.setState({ binaryFiles: fileList });
handleScreenshotChange = ({ fileList }) =>
this.setState({ screenshots: fileList });
handleSubmit = e => {
e.preventDefault();
@ -245,7 +245,7 @@ class EditReleaseModal extends React.Component {
this.props.form.validateFields((err, values) => {
if (!err) {
this.setState({
loading: true
loading: true,
});
const { releaseDescription, releaseType } = values;
@ -262,19 +262,22 @@ class EditReleaseModal extends React.Component {
releaseType: releaseType,
};
if ((config.deviceTypes.mobileTypes.includes(this.props.deviceType))) {
if (config.deviceTypes.mobileTypes.includes(this.props.deviceType)) {
release.supportedOsVersions = `${this.lowerOsVersion}-${this.upperOsVersion}`;
}
if (specificElements.hasOwnProperty("binaryFile") && binaryFiles.length === 1) {
if (
specificElements.hasOwnProperty('binaryFile') &&
binaryFiles.length === 1
) {
data.append('binaryFile', binaryFiles[0].originFileObj);
}
if (specificElements.hasOwnProperty("version")) {
if (specificElements.hasOwnProperty('version')) {
release.version = values.version;
}
if (specificElements.hasOwnProperty("url")) {
if (specificElements.hasOwnProperty('url')) {
release.url = values.url;
}
@ -296,19 +299,24 @@ class EditReleaseModal extends React.Component {
const json = JSON.stringify(release);
const blob = new Blob([json], {
type: 'application/json'
type: 'application/json',
});
data.append("applicationRelease", blob);
data.append('applicationRelease', blob);
const url = window.location.origin + config.serverConfig.invoker.uri + config.serverConfig.invoker.publisher + "/applications" + formConfig.endpoint + "/" + uuid;
const url =
window.location.origin +
config.serverConfig.invoker.uri +
config.serverConfig.invoker.publisher +
'/applications' +
formConfig.endpoint +
'/' +
uuid;
axios.put(
url,
data
).then(res => {
axios
.put(url, data)
.then(res => {
if (res.status === 200) {
const updatedRelease = res.data.data;
this.setState({
@ -316,27 +324,29 @@ class EditReleaseModal extends React.Component {
visible: false,
});
notification["success"]({
message: "Done!",
description:
"Saved!",
notification.success({
message: 'Done!',
description: 'Saved!',
});
// console.log(updatedRelease);
this.props.updateRelease(updatedRelease);
}
}).catch((error) => {
if (error.hasOwnProperty("response") && error.response.status === 401) {
window.location.href = window.location.origin + '/publisher/login';
})
.catch(error => {
if (
error.hasOwnProperty('response') &&
error.response.status === 401
) {
window.location.href =
window.location.origin + '/publisher/login';
} else {
notification["error"]({
message: "Something went wrong!",
description:
"Sorry, we were unable to complete your request.",
notification.error({
message: 'Something went wrong!',
description: 'Sorry, we were unable to complete your request.',
});
}
this.setState({
loading: false
loading: false,
});
});
}
@ -345,8 +355,8 @@ class EditReleaseModal extends React.Component {
addNewMetaData = () => {
this.setState({
metaData: this.state.metaData.concat({'key': '', 'value': ''})
})
metaData: this.state.metaData.concat({ key: '', value: '' }),
});
};
handlePreviewCancel = () => this.setState({ previewVisible: false });
@ -362,11 +372,11 @@ class EditReleaseModal extends React.Component {
});
};
handleLowerOsVersionChange = (lowerOsVersion) => {
handleLowerOsVersionChange = lowerOsVersion => {
this.lowerOsVersion = lowerOsVersion;
};
handleUpperOsVersionChange = (upperOsVersion) => {
handleUpperOsVersionChange = upperOsVersion => {
this.upperOsVersion = upperOsVersion;
};
@ -380,9 +390,6 @@ class EditReleaseModal extends React.Component {
metaData,
previewImage,
previewVisible,
binaryFileHelperText,
iconHelperText,
screenshotHelperText
} = this.state;
const { getFieldDecorator } = this.props.form;
const { isAppUpdatable, supportedOsVersions, deviceType } = this.props;
@ -396,10 +403,19 @@ class EditReleaseModal extends React.Component {
return (
<div>
<Tooltip title={isAppUpdatable ? "Edit this release" : "This release isn't in an editable state"}>
<Tooltip
title={
isAppUpdatable
? 'Edit this release'
: "This release isn't in an editable state"
}
>
<Button
disabled={!isAppUpdatable}
size="small" type="primary" onClick={this.showModal}>
size="small"
type="primary"
onClick={this.showModal}
>
<Icon type="edit" /> Edit
</Button>
</Tooltip>
@ -408,19 +424,23 @@ class EditReleaseModal extends React.Component {
visible={this.state.visible}
footer={null}
width={580}
onCancel={this.handleCancel}>
onCancel={this.handleCancel}
>
<div>
<Spin tip="Uploading..." spinning={loading}>
<Form labelAlign="left" layout="horizontal"
<Form
labelAlign="left"
layout="horizontal"
hideRequiredMark
onSubmit={this.handleSubmit}>
{formConfig.specificElements.hasOwnProperty("binaryFile") && (
onSubmit={this.handleSubmit}
>
{formConfig.specificElements.hasOwnProperty('binaryFile') && (
<Form.Item {...formItemLayout} label="Application">
{getFieldDecorator('binaryFile', {
valuePropName: 'binaryFile',
getValueFromEvent: this.normFile,
required: true,
message: 'Please select application'
message: 'Please select application',
})(
<Upload
name="binaryFile"
@ -437,29 +457,29 @@ class EditReleaseModal extends React.Component {
</Form.Item>
)}
{formConfig.specificElements.hasOwnProperty("url") && (
{formConfig.specificElements.hasOwnProperty('url') && (
<Form.Item {...formItemLayout} label="URL">
{getFieldDecorator('url', {
rules: [{
rules: [
{
required: true,
message: 'Please input the url'
}],
})(
<Input placeholder="url"/>
)}
message: 'Please input the url',
},
],
})(<Input placeholder="url" />)}
</Form.Item>
)}
{formConfig.specificElements.hasOwnProperty("version") && (
{formConfig.specificElements.hasOwnProperty('version') && (
<Form.Item {...formItemLayout} label="Version">
{getFieldDecorator('version', {
rules: [{
rules: [
{
required: true,
message: 'Please input the version'
}],
})(
<Input placeholder="Version"/>
)}
message: 'Please input the version',
},
],
})(<Input placeholder="Version" />)}
</Form.Item>
)}
@ -468,14 +488,15 @@ class EditReleaseModal extends React.Component {
valuePropName: 'icon',
getValueFromEvent: this.normFile,
required: true,
message: 'Please select a icon'
message: 'Please select a icon',
})(
<Upload
name="logo"
listType="picture-card"
onChange={this.handleIconChange}
beforeUpload={() => false}
onPreview={this.handlePreview}>
onPreview={this.handlePreview}
>
{icons.length === 1 ? null : uploadButton}
</Upload>,
)}
@ -486,14 +507,15 @@ class EditReleaseModal extends React.Component {
valuePropName: 'icon',
getValueFromEvent: this.normFile,
required: true,
message: 'Please select a icon'
message: 'Please select a icon',
})(
<Upload
name="screenshots"
listType="picture-card"
onChange={this.handleScreenshotChange}
beforeUpload={() => false}
onPreview={this.handlePreview}>
onPreview={this.handlePreview}
>
{screenshots.length >= 3 ? null : uploadButton}
</Upload>,
)}
@ -501,36 +523,44 @@ class EditReleaseModal extends React.Component {
<Form.Item {...formItemLayout} label="Release Type">
{getFieldDecorator('releaseType', {
rules: [{
rules: [
{
required: true,
message: 'Please input the Release Type'
}],
})(
<Input placeholder="Release Type"/>
)}
message: 'Please input the Release Type',
},
],
})(<Input placeholder="Release Type" />)}
</Form.Item>
<Form.Item {...formItemLayout} label="Description">
{getFieldDecorator('releaseDescription', {
rules: [{
rules: [
{
required: true,
message: 'Please enter a description for release'
}],
message: 'Please enter a description for release',
},
],
})(
<TextArea placeholder="Enter a description for release"
rows={5}/>
<TextArea
placeholder="Enter a description for release"
rows={5}
/>,
)}
</Form.Item>
{(config.deviceTypes.mobileTypes.includes(deviceType)) && (
{config.deviceTypes.mobileTypes.includes(deviceType) && (
<div>
{(this.props.forbiddenErrors.supportedOsVersions) && (
{this.props.forbiddenErrors.supportedOsVersions && (
<Alert
message="You don't have permission to view supported OS versions."
type="warning"
banner
closable/>
closable
/>
)}
<Form.Item {...formItemLayout} label="Supported OS Versions">
<Form.Item
{...formItemLayout}
label="Supported OS Versions"
>
{getFieldDecorator('supportedOS')(
<div>
<InputGroup>
@ -538,22 +568,27 @@ class EditReleaseModal extends React.Component {
<Col span={11}>
<Form.Item>
{getFieldDecorator('lowerOsVersion', {
rules: [{
rules: [
{
required: true,
message: 'Please select Value'
}],
message: 'Please select Value',
},
],
})(
<Select
placeholder="Lower version"
style={{width: "100%"}}
onChange={this.handleLowerOsVersionChange}>
style={{ width: '100%' }}
onChange={this.handleLowerOsVersionChange}
>
{supportedOsVersions.map(version => (
<Option key={version.versionName}
value={version.versionName}>
<Option
key={version.versionName}
value={version.versionName}
>
{version.versionName}
</Option>
))}
</Select>
</Select>,
)}
</Form.Item>
</Col>
@ -563,43 +598,49 @@ class EditReleaseModal extends React.Component {
<Col span={11}>
<Form.Item>
{getFieldDecorator('upperOsVersion', {
rules: [{
rules: [
{
required: true,
message: 'Please select Value'
}],
message: 'Please select Value',
},
],
})(
<Select style={{width: "100%"}}
<Select
style={{ width: '100%' }}
placeholder="Upper version"
onChange={this.handleUpperOsVersionChange}>
onChange={this.handleUpperOsVersionChange}
>
{supportedOsVersions.map(version => (
<Option key={version.versionName}
value={version.versionName}>
<Option
key={version.versionName}
value={version.versionName}
>
{version.versionName}
</Option>
))}
</Select>
</Select>,
)}
</Form.Item>
</Col>
</Row>
</InputGroup>
</div>
</div>,
)}
</Form.Item>
</div>
)}
<Form.Item {...formItemLayout} label="Meta Data">
{getFieldDecorator('meta', {
rules: [{
rules: [
{
required: true,
message: 'Please fill empty fields'
}],
initialValue: false
message: 'Please fill empty fields',
},
],
initialValue: false,
})(
<div>
{
metaData.map((data, index) => {
{metaData.map((data, index) => {
return (
<InputGroup key={index}>
<Row gutter={8}>
@ -607,55 +648,61 @@ class EditReleaseModal extends React.Component {
<Input
placeholder="key"
value={data.key}
onChange={(e) => {
metaData[index]['key'] = e.currentTarget.value;
onChange={e => {
metaData[index].key = e.currentTarget.value;
this.setState({
metaData
})
}}/>
metaData,
});
}}
/>
</Col>
<Col span={10}>
<Input
placeholder="value"
value={data.value}
onChange={(e) => {
metaData[index].value = e.currentTarget.value;
onChange={e => {
metaData[index].value =
e.currentTarget.value;
this.setState({
metaData
metaData,
});
}}/>
}}
/>
</Col>
<Col span={3}>
<Button type="dashed"
<Button
type="dashed"
shape="circle"
icon="minus"
onClick={() => {
metaData.splice(index, 1);
this.setState({
metaData
metaData,
});
}}/>
}}
/>
</Col>
</Row>
</InputGroup>
)
}
)
}
<Button type="dashed" icon="plus" onClick={this.addNewMetaData}>
);
})}
<Button
type="dashed"
icon="plus"
onClick={this.addNewMetaData}
>
Add
</Button>
</div>
</div>,
)}
</Form.Item>
<Divider />
<Form.Item style={{float: "right", marginLeft: 8}}>
<Form.Item style={{ float: 'right', marginLeft: 8 }}>
<Button type="primary" htmlType="submit">
Update
</Button>
</Form.Item>
<Form.Item style={{float: "right"}}>
<Form.Item style={{ float: 'right' }}>
<Button htmlType="button" onClick={this.handleCancel}>
Back
</Button>
@ -664,8 +711,16 @@ class EditReleaseModal extends React.Component {
</Form>
</Spin>
</div>
<Modal visible={previewVisible} footer={null} onCancel={this.handlePreviewCancel}>
<img alt="Preview Image" style={{width: '100%'}} src={previewImage}/>
<Modal
visible={previewVisible}
footer={null}
onCancel={this.handlePreviewCancel}
>
<img
alt="Preview Image"
style={{ width: '100%' }}
src={previewImage}
/>
</Modal>
</Modal>
</div>
@ -673,6 +728,8 @@ class EditReleaseModal extends React.Component {
}
}
const EditRelease = withConfigContext(Form.create({name: 'add-new-release'})(EditReleaseModal));
const EditRelease = withConfigContext(
Form.create({ name: 'add-new-release' })(EditReleaseModal),
);
export default EditRelease;

View File

@ -16,38 +16,51 @@
* under the License.
*/
import React from "react";
import {Typography, Tag, Divider, Select, Button, Modal, message, notification, Collapse} from "antd";
import axios from "axios";
import React from 'react';
import {
Typography,
Tag,
Divider,
Select,
Button,
Modal,
notification,
} from 'antd';
import axios from 'axios';
import ReactQuill from 'react-quill';
import 'react-quill/dist/quill.snow.css';
import './LifeCycle.css';
import LifeCycleDetailsModal from "./lifeCycleDetailsModal/lifeCycleDetailsModal";
import {withConfigContext} from "../../../../context/ConfigContext";
import {handleApiError} from "../../../../js/Utils";
import LifeCycleDetailsModal from './lifeCycleDetailsModal/lifeCycleDetailsModal';
import { withConfigContext } from '../../../../context/ConfigContext';
import { handleApiError } from '../../../../js/Utils';
const { Text, Title, Paragraph } = Typography;
const { Option } = Select;
const modules = {
toolbar: [
[{'header': [1, 2, false]}],
[{ header: [1, 2, false] }],
['bold', 'italic', 'underline', 'strike', 'blockquote', 'code-block'],
[{'list': 'ordered'}, {'list': 'bullet'}],
['link', 'image']
[{ list: 'ordered' }, { list: 'bullet' }],
['link', 'image'],
],
};
const formats = [
'header',
'bold', 'italic', 'underline', 'strike', 'blockquote', 'code-block',
'list', 'bullet',
'link', 'image'
'bold',
'italic',
'underline',
'strike',
'blockquote',
'code-block',
'list',
'bullet',
'link',
'image',
];
class LifeCycle extends React.Component {
constructor(props) {
super(props);
this.state = {
@ -55,35 +68,38 @@ class LifeCycle extends React.Component {
selectedStatus: null,
reasonText: '',
isReasonModalVisible: false,
isConfirmButtonLoading: false
}
isConfirmButtonLoading: false,
};
}
componentDidUpdate(prevProps, prevState, snapshot) {
if (prevProps.currentStatus !== this.props.currentStatus || prevProps.uuid !== this.props.uuid) {
if (
prevProps.currentStatus !== this.props.currentStatus ||
prevProps.uuid !== this.props.uuid
) {
this.setState({
currentStatus: this.props.currentStatus
currentStatus: this.props.currentStatus,
});
}
}
handleChange = (value) => {
this.setState({reasonText: value})
handleChange = value => {
this.setState({ reasonText: value });
};
handleSelectChange = (value) => {
this.setState({selectedStatus: value})
handleSelectChange = value => {
this.setState({ selectedStatus: value });
};
showReasonModal = () => {
this.setState({
isReasonModalVisible: true
isReasonModalVisible: true,
});
};
closeReasonModal = () => {
this.setState({
isReasonModalVisible: false
isReasonModalVisible: false,
});
};
@ -93,51 +109,61 @@ class LifeCycle extends React.Component {
const { uuid } = this.props;
const data = {
action: selectedStatus,
reason: reasonText
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 => {
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,
reasonText: ''
reasonText: '',
});
this.props.changeCurrentLifecycleStatus(selectedStatus);
notification["success"]({
message: "Done!",
description:
"Lifecycle state updated successfully!",
notification.success({
message: 'Done!',
description: 'Lifecycle state updated successfully!',
});
}
}).catch((error) => {
handleApiError(error, "Error occurred while trying to add lifecycle");
})
.catch(error => {
handleApiError(error, 'Error occurred while trying to add lifecycle');
this.setState({
isConfirmButtonLoading: false
isConfirmButtonLoading: false,
});
});
};
render() {
const {currentStatus, selectedStatus, isConfirmButtonLoading} = this.state;
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")) {
if (
lifecycle !== null &&
lifecycle.hasOwnProperty(currentStatus) &&
lifecycle[currentStatus].hasOwnProperty('proceedingStates')
) {
proceedingStates = lifecycle[currentStatus].proceedingStates;
}
@ -146,14 +172,19 @@ class LifeCycle extends React.Component {
<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
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}/>)}
{lifecycle !== null && <LifeCycleDetailsModal lifecycle={lifecycle} />}
<Divider dashed={true} />
<Text strong={true}>Current State: </Text> <Tag color="blue">{currentStatus}</Tag><br/><br/>
<Text strong={true}>Current State: </Text>{' '}
<Tag color="blue">{currentStatus}</Tag>
<br />
<br />
<Text>Change State to: </Text>
<Select
placeholder="Select state"
@ -165,14 +196,11 @@ class LifeCycle extends React.Component {
>
{proceedingStates.map(lifecycleState => {
return (
<Option
key={lifecycleState}
value={lifecycleState}>
<Option key={lifecycleState} value={lifecycleState}>
{lifecycleState}
</Option>
)
})
}
);
})}
</Select>
<Button
style={{ marginLeft: 10 }}
@ -181,7 +209,8 @@ class LifeCycle extends React.Component {
htmlType="button"
onClick={this.showReasonModal}
loading={isConfirmButtonLoading}
disabled={selectedStatus == null}>
disabled={selectedStatus == null}
>
Change
</Button>
<Divider />
@ -190,13 +219,16 @@ class LifeCycle extends React.Component {
visible={this.state.isReasonModalVisible}
onOk={this.addLifeCycle}
onCancel={this.closeReasonModal}
okText="Confirm">
okText="Confirm"
>
<Text>
You are going to change the lifecycle state from,<br/>
<Tag color="blue">{currentStatus}</Tag>to <Tag
color="blue">{selectedStatus}</Tag>
You are going to change the lifecycle state from,
<br />
<Tag color="blue">{currentStatus}</Tag>to{' '}
<Tag color="blue">{selectedStatus}</Tag>
</Text>
<br/><br/>
<br />
<br />
<ReactQuill
theme="snow"
value={this.state.reasonText}
@ -206,11 +238,9 @@ class LifeCycle extends React.Component {
placeholder="Leave a comment (optional)"
/>
</Modal>
</div>
);
}
}
export default withConfigContext(LifeCycle);

View File

@ -16,15 +16,14 @@
* under the License.
*/
import React from "react";
import React from 'react';
import { Modal, Button, Tag, List, Typography } from 'antd';
import pSBC from "shade-blend-color";
import {withConfigContext} from "../../../../../context/ConfigContext";
import pSBC from 'shade-blend-color';
import { withConfigContext } from '../../../../../context/ConfigContext';
const { Text } = Typography;
class LifeCycleDetailsModal extends React.Component {
constructor(props) {
super(props);
this.state = { visible: false };
@ -48,11 +47,7 @@ class LifeCycleDetailsModal extends React.Component {
const { lifecycle } = this.props;
return (
<div>
<Button
size="small"
icon="question-circle"
onClick={this.showModal}
>
<Button size="small" icon="question-circle" onClick={this.showModal}>
Learn more
</Button>
<Modal
@ -61,48 +56,47 @@ class LifeCycleDetailsModal extends React.Component {
footer={null}
onCancel={this.handleCancel}
>
<List
itemLayout="horizontal"
dataSource={Object.keys(lifecycle)}
renderItem={lifecycleState => {
let text = "";
let footerText = "";
let text = '';
let footerText = '';
let nextProceedingStates = [];
if (lifeCycleConfig.hasOwnProperty(lifecycleState)) {
text = lifeCycleConfig[lifecycleState].text;
}
if (lifecycle[lifecycleState].hasOwnProperty("proceedingStates")) {
nextProceedingStates = lifecycle[lifecycleState].proceedingStates;
footerText = "You can only proceed to one of the following states:"
if (
lifecycle[lifecycleState].hasOwnProperty('proceedingStates')
) {
nextProceedingStates =
lifecycle[lifecycleState].proceedingStates;
footerText =
'You can only proceed to one of the following states:';
}
return (
<List.Item>
<List.Item.Meta
title={lifecycleState}
/>
<List.Item.Meta title={lifecycleState} />
{text}
<br />
<Text type="secondary">{footerText}</Text>
<div>
{
nextProceedingStates.map(lifecycleState => {
{nextProceedingStates.map(lifecycleState => {
return (
<Tag
key={lifecycleState}
style={{ margin: 5 }}
color={pSBC(0.30, config.theme.primaryColor)}
color={pSBC(0.3, config.theme.primaryColor)}
>
{lifecycleState}
</Tag>
)
})
}
);
})}
</div>
</List.Item>
)
);
}}
/>
</Modal>

View File

@ -16,15 +16,15 @@
* under the License.
*/
import React from "react";
import {List, message, Avatar, Spin, Button, notification, Alert} from 'antd';
import "./Reviews.css";
import React from 'react';
import { List, Spin, Button, Alert } from 'antd';
import './Reviews.css';
import InfiniteScroll from 'react-infinite-scroller';
import SingleReview from "./SingleReview";
import axios from "axios";
import {withConfigContext} from "../../../../context/ConfigContext";
import {handleApiError} from "../../../../js/Utils";
import SingleReview from './SingleReview';
import axios from 'axios';
import { withConfigContext } from '../../../../context/ConfigContext';
import { handleApiError } from '../../../../js/Utils';
const limit = 5;
@ -35,11 +35,10 @@ class Reviews extends React.Component {
hasMore: false,
loadMore: false,
forbiddenErrors: {
reviews: false
}
reviews: false,
},
};
componentDidMount() {
this.fetchData(0, limit, res => {
this.setState({
@ -53,37 +52,46 @@ class Reviews extends React.Component {
const { uuid, type } = this.props;
this.setState({
loading: true
loading: true,
});
axios.get(
axios
.get(
window.location.origin +
config.serverConfig.invoker.uri +
config.serverConfig.invoker.publisher +
"/admin/reviews/" + type + "/" + uuid
).then(res => {
'/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) {
})
.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
})
loading: false,
});
} else {
this.setState({
loading: false
loading: false,
});
}
});
};
handleInfiniteOnLoad = (count) => {
handleInfiniteOnLoad = count => {
const offset = count * limit;
let data = this.state.data;
this.setState({
@ -106,7 +114,7 @@ class Reviews extends React.Component {
} else {
this.setState({
hasMore: false,
loading: false
loading: false,
});
}
});
@ -115,19 +123,20 @@ class Reviews extends React.Component {
enableLoading = () => {
this.setState({
hasMore: true,
loadMore: true
loadMore: true,
});
};
render() {
return (
<div>
{(this.state.forbiddenErrors.reviews) && (
{this.state.forbiddenErrors.reviews && (
<Alert
message="You don't have permission to view reviews."
type="warning"
banner
closable/>
closable
/>
)}
<div className="demo-infinite-container">
<InfiniteScroll
@ -135,14 +144,16 @@ class Reviews extends React.Component {
pageStart={0}
loadMore={this.handleInfiniteOnLoad}
hasMore={!this.state.loading && this.state.hasMore}
useWindow={true}>
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 />
@ -150,9 +161,17 @@ class Reviews extends React.Component {
)}
</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>)}
{!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>
);

View File

@ -16,19 +16,27 @@
* under the License.
*/
import React from "react";
import {Avatar} from "antd";
import {List,Typography} from "antd";
import StarRatings from "react-star-ratings";
import React from 'react';
import { Avatar } from 'antd';
import { List, Typography } from 'antd';
import StarRatings from 'react-star-ratings';
const { Text, Paragraph } = Typography;
const colorList = ['#f0932b','#badc58','#6ab04c','#eb4d4b','#0abde3', '#9b59b6','#3498db','#22a6b3'];
const colorList = [
'#f0932b',
'#badc58',
'#6ab04c',
'#eb4d4b',
'#0abde3',
'#9b59b6',
'#3498db',
'#22a6b3',
];
class SingleReview extends React.Component {
render() {
const review = this.props.review;
const randomColor = colorList[Math.floor(Math.random() * (colorList.length))];
const randomColor = colorList[Math.floor(Math.random() * colorList.length)];
const avatarLetter = review.username.charAt(0).toUpperCase();
const content = (
<div style={{ marginTop: -5 }}>
@ -38,10 +46,19 @@ class SingleReview extends React.Component {
starDimension="12px"
starSpacing="2px"
numberOfStars={5}
name='rating'
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>
<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>
);
@ -49,7 +66,10 @@ class SingleReview extends React.Component {
<div>
<List.Item.Meta
avatar={
<Avatar style={{ backgroundColor: randomColor, verticalAlign: 'middle' }} size="large">
<Avatar
style={{ backgroundColor: randomColor, verticalAlign: 'middle' }}
size="large"
>
{avatarLetter}
</Avatar>
}

View File

@ -16,35 +16,33 @@
* under the License.
*/
import React from "react";
import {Button, Divider, Form, Input, message, Modal, notification, Spin} from "antd";
import axios from "axios";
import {withConfigContext} from "../../../../context/ConfigContext";
import {withRouter} from "react-router";
import {handleApiError} from "../../../../js/Utils";
import React from 'react';
import { Button, Divider, Input, Modal, notification, Spin } from 'antd';
import axios from 'axios';
import { withConfigContext } from '../../../../context/ConfigContext';
import { withRouter } from 'react-router';
import { handleApiError } from '../../../../js/Utils';
class AddNewPage extends React.Component {
state = {
visible: false,
pageName: ''
pageName: '',
};
showModal = () => {
this.setState({
visible: true,
loading: false
loading: false,
});
};
handleCancel = e => {
this.setState({
visible: false,
});
};
handlePageName = (e) => {
handlePageName = e => {
this.setState({
pageName: e.target.value,
});
@ -54,29 +52,37 @@ class AddNewPage extends React.Component {
const config = this.props.context;
this.setState({ loading: true });
axios.post(
window.location.origin + config.serverConfig.invoker.uri +
"/device-mgt/android/v1.0/enterprise/store-layout/page",
axios
.post(
window.location.origin +
config.serverConfig.invoker.uri +
'/device-mgt/android/v1.0/enterprise/store-layout/page',
{
"locale": "en",
"pageName": this.state.pageName
}
).then(res => {
locale: 'en',
pageName: this.state.pageName,
},
)
.then(res => {
if (res.status === 200) {
const { pageId, pageName } = res.data.data;
notification["success"]({
notification.success({
message: 'Saved!',
description: 'Page created successfully!'
description: 'Page created successfully!',
});
this.setState({ loading: false });
this.props.history.push(`/publisher/manage/android-enterprise/pages/${pageName}/${pageId}`);
this.props.history.push(
`/publisher/manage/android-enterprise/pages/${pageName}/${pageId}`,
);
}
}).catch((error) => {
handleApiError(error, "Error occurred while trying to update the cluster.");
})
.catch(error => {
handleApiError(
error,
'Error occurred while trying to update the cluster.',
);
this.setState({ loading: false });
});
};
@ -84,9 +90,7 @@ class AddNewPage extends React.Component {
render() {
return (
<div style={{ marginTop: 24, marginBottom: 24 }}>
<Button
type="dashed"
onClick={this.showModal}>
<Button type="dashed" onClick={this.showModal}>
Add new page
</Button>
<Modal
@ -102,15 +106,14 @@ class AddNewPage extends React.Component {
<Input onChange={this.handlePageName} />
<Divider />
<div>
<Button
onClick={this.handleCancel}>
Cancel
</Button>
<Button onClick={this.handleCancel}>Cancel</Button>
<Divider type="vertical" />
<Button
onClick={this.createNewPage}
htmlType="button" type="primary"
disabled={this.state.pageName.length === 0}>
htmlType="button"
type="primary"
disabled={this.state.pageName.length === 0}
>
Create Page
</Button>
</div>

View File

@ -16,9 +16,9 @@
* under the License.
*/
import React from "react";
import {Modal, Button} from "antd";
import {withConfigContext} from "../../../context/ConfigContext";
import React from 'react';
import { Modal, Button } from 'antd';
import { withConfigContext } from '../../../context/ConfigContext';
class GooglePlayIframe extends React.Component {
constructor(props) {
@ -26,7 +26,7 @@ class GooglePlayIframe extends React.Component {
this.config = this.props.context;
this.state = {
visible: false
visible: false,
};
}
@ -50,7 +50,7 @@ class GooglePlayIframe extends React.Component {
render() {
return (
<div style={{display: "inline-block", padding: 4}}>
<div style={{ display: 'inline-block', padding: 4 }}>
<Button type="primary" onClick={this.showModal}>
Approve Applications
</Button>
@ -60,15 +60,19 @@ class GooglePlayIframe extends React.Component {
onOk={this.handleOk}
onCancel={this.handleCancel}
width={740}
footer={null}>
footer={null}
>
<iframe
style={{
height: 720,
border: 0,
width: "100%"
width: '100%',
}}
src={"https://play.google.com/work/embedded/search?token=" + this.config.androidEnterpriseToken +
"&mode=APPROVE&showsearchbox=TRUE"}
src={
'https://play.google.com/work/embedded/search?token=' +
this.config.androidEnterpriseToken +
'&mode=APPROVE&showsearchbox=TRUE'
}
/>
</Modal>
</div>

View File

@ -16,22 +16,19 @@
* under the License.
*/
import React from "react";
import {Button, message, Modal, notification, Spin} from "antd";
import axios from "axios";
import {withConfigContext} from "../../../../context/ConfigContext";
import {handleApiError} from "../../../../js/Utils";
// import gapi from 'gapi-client';
import React from 'react';
import { Button, Modal, notification, Spin } from 'antd';
import axios from 'axios';
import { withConfigContext } from '../../../../context/ConfigContext';
import { handleApiError } from '../../../../js/Utils';
class ManagedConfigurationsIframe extends React.Component {
constructor(props) {
super(props);
this.config = this.props.context;
this.state = {
visible: false,
loading: false
loading: false,
};
}
@ -59,55 +56,76 @@ class ManagedConfigurationsIframe extends React.Component {
this.setState({ loading: true });
// send request to the invoker
axios.get(
window.location.origin + this.config.serverConfig.invoker.uri +
"/device-mgt/android/v1.0/enterprise/managed-configs/package/" + packageName,
).then(res => {
axios
.get(
window.location.origin +
this.config.serverConfig.invoker.uri +
'/device-mgt/android/v1.0/enterprise/managed-configs/package/' +
packageName,
)
.then(res => {
if (res.status === 200) {
let mcmId = null;
if (res.data.hasOwnProperty("data")) {
if (res.data.hasOwnProperty('data')) {
mcmId = res.data.data.mcmId;
}
this.loadIframe(mcmId);
this.setState({ loading: false });
}
}).catch((error) => {
handleApiError(error, "Error occurred while trying to load configurations.");
})
.catch(error => {
handleApiError(
error,
'Error occurred while trying to load configurations.',
);
this.setState({ loading: false, visible: false });
});
};
loadIframe = (mcmId) => {
loadIframe = mcmId => {
const { packageName } = this.props;
let method = "post";
let method = 'post';
// eslint-disable-next-line no-undef
gapi.load('gapi.iframes', () => {
const parameters = {
token: this.config.androidEnterpriseToken,
packageName: packageName
packageName: packageName,
};
if (mcmId != null) {
parameters.mcmId = mcmId;
parameters.canDelete = true;
method = "put";
method = 'put';
}
const queryString = Object.keys(parameters).map(key => key + '=' + parameters[key]).join('&');
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'}
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) => {
iframe.register(
'onconfigupdated',
event => {
this.updateConfig(method, event);
}, gapi.iframes.CROSS_ORIGIN_IFRAMES_FILTER);
},
// eslint-disable-next-line no-undef
gapi.iframes.CROSS_ORIGIN_IFRAMES_FILTER,
);
iframe.register('onconfigdeleted', (event) => {
iframe.register(
'onconfigdeleted',
event => {
this.deleteConfig(event);
}, gapi.iframes.CROSS_ORIGIN_IFRAMES_FILTER);
},
// eslint-disable-next-line no-undef
gapi.iframes.CROSS_ORIGIN_IFRAMES_FILTER,
);
});
};
@ -118,54 +136,67 @@ class ManagedConfigurationsIframe extends React.Component {
const data = {
mcmId: event.mcmId,
profileName: event.name,
packageName
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 => {
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"]({
notification.success({
message: 'Saved!',
description: 'Configuration Profile updated Successfully',
});
this.setState({
loading: false,
visible: false
visible: false,
});
}
}).catch((error) => {
handleApiError(error, "Error occurred while trying to update configurations.");
})
.catch(error => {
handleApiError(
error,
'Error occurred while trying to update configurations.',
);
this.setState({ loading: false });
});
};
deleteConfig = (event) => {
const {packageName} = this.props;
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 => {
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"]({
notification.success({
message: 'Saved!',
description: 'Configuration Profile removed Successfully',
});
this.setState({
loading: false,
visible: false
visible: false,
});
}
}).catch((error) => {
handleApiError(error, "Error occurred while trying to remove configurations.");
})
.catch(error => {
handleApiError(
error,
'Error occurred while trying to remove configurations.',
);
this.setState({ loading: false });
});
};
@ -177,17 +208,18 @@ class ManagedConfigurationsIframe extends React.Component {
size="small"
type="primary"
icon="setting"
onClick={this.showModal}>
onClick={this.showModal}
>
Manage
</Button>
<Modal
visible={this.state.visible}
onOk={this.handleOk}
onCancel={this.handleCancel}
footer={null}>
footer={null}
>
<Spin spinning={this.state.loading}>
<div id="manage-config-iframe-container">
</div>
<div id="manage-config-iframe-container"></div>
</Spin>
</Modal>
</div>

View File

@ -16,28 +16,29 @@
* under the License.
*/
import React from "react";
import React from 'react';
import { Modal, Icon, Table, Avatar } from 'antd';
import "../Cluster.css";
import {withConfigContext} from "../../../../../../context/ConfigContext";
import '../Cluster.css';
import { withConfigContext } from '../../../../../../context/ConfigContext';
const columns = [
{
title: '',
dataIndex: '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',
key: 'name'
key: 'name',
},
{
title: 'Page',
dataIndex: 'packageId',
key: 'packageId'
}
key: 'packageId',
},
];
class AddAppsToClusterModal extends React.Component {
@ -47,7 +48,7 @@ class AddAppsToClusterModal extends React.Component {
visible: false,
loading: false,
selectedProducts: [],
homePageId: null
homePageId: null,
};
}
@ -71,8 +72,8 @@ class AddAppsToClusterModal extends React.Component {
rowSelection = {
onChange: (selectedRowKeys, selectedRows) => {
this.setState({
selectedProducts: selectedRows
})
selectedProducts: selectedRows,
});
},
};
@ -82,21 +83,19 @@ class AddAppsToClusterModal extends React.Component {
<div>
<div className="btn-add-new-wrapper">
<div className="btn-add-new">
<button className="btn"
onClick={this.showModal}>
<Icon style={{position: "relative"}} type="plus"/>
<button className="btn" onClick={this.showModal}>
<Icon style={{ position: 'relative' }} type="plus" />
</button>
</div>
<div className="title">
Add app
</div>
<div className="title">Add app</div>
</div>
<Modal
title="Select Apps"
width={640}
visible={this.state.visible}
onOk={this.handleOk}
onCancel={this.handleCancel}>
onCancel={this.handleCancel}
>
<Table
columns={columns}
rowKey={record => record.packageId}
@ -104,10 +103,11 @@ class AddAppsToClusterModal extends React.Component {
scroll={{ x: 300 }}
pagination={{
...pagination,
size: "small",
size: 'small',
// position: "top",
showTotal: (total, range) => `showing ${range[0]}-${range[1]} of ${total} pages`,
showQuickJumper: true
showTotal: (total, range) =>
`showing ${range[0]}-${range[1]} of ${total} pages`,
showQuickJumper: true,
}}
loading={loading}
onChange={this.handleTableChange}

View File

@ -16,19 +16,30 @@
* under the License.
*/
import React from "react";
import {Button, Col, Divider, Icon, message, notification, Popconfirm, Row, Spin, Tooltip, Typography} from "antd";
import React from 'react';
import {
Button,
Col,
Divider,
Icon,
message,
notification,
Popconfirm,
Row,
Spin,
Tooltip,
Typography,
} from 'antd';
import "./Cluster.css";
import axios from "axios";
import {withConfigContext} from "../../../../../context/ConfigContext";
import AddAppsToClusterModal from "./AddAppsToClusterModal/AddAppsToClusterModal";
import {handleApiError} from "../../../../../js/Utils";
import './Cluster.css';
import axios from 'axios';
import { withConfigContext } from '../../../../../context/ConfigContext';
import AddAppsToClusterModal from './AddAppsToClusterModal/AddAppsToClusterModal';
import { handleApiError } from '../../../../../js/Utils';
const { Title } = Typography;
class Cluster extends React.Component {
constructor(props) {
super(props);
const { cluster, pageId } = this.props;
@ -40,22 +51,22 @@ class Cluster extends React.Component {
name,
products,
isSaveable: false,
loading: false
loading: false,
};
}
handleNameChange = (name) => {
handleNameChange = name => {
this.setState({
name
name,
});
if (name !== this.originalCluster.name) {
this.setState({
isSaveable: true
isSaveable: true,
});
}
};
isProductsChanged = (currentProducts) => {
isProductsChanged = currentProducts => {
let isChanged = false;
const originalProducts = this.originalCluster.products;
if (currentProducts.length === originalProducts.length) {
@ -75,26 +86,28 @@ class Cluster extends React.Component {
const products = [...this.state.products];
if (swapIndex !== -1 && index < products.length) {
// swap elements
[products[index], products[swapIndex]] = [products[swapIndex], products[index]];
[products[index], products[swapIndex]] = [
products[swapIndex],
products[index],
];
this.setState({
products,
});
this.setState({
isSaveable: this.isProductsChanged(products)
})
isSaveable: this.isProductsChanged(products),
});
}
};
removeProduct = (index) => {
removeProduct = index => {
const products = [...this.state.products];
products.splice(index, 1);
this.setState({
products,
isSaveable: true
isSaveable: true,
});
};
getCurrentCluster = () => {
@ -104,7 +117,7 @@ class Cluster extends React.Component {
clusterId: this.clusterId,
name: name,
products: products,
orderInPage: this.props.orderInPage
orderInPage: this.props.orderInPage,
};
};
@ -116,7 +129,7 @@ class Cluster extends React.Component {
loading: false,
name,
products,
isSaveable: false
isSaveable: false,
});
};
@ -126,18 +139,20 @@ class Cluster extends React.Component {
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 => {
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"]({
notification.success({
message: 'Saved!',
description: 'Cluster updated successfully!'
description: 'Cluster updated successfully!',
});
const cluster = res.data.data;
const {name, products} = cluster;
this.originalCluster = Object.assign({}, cluster);
@ -146,25 +161,32 @@ class Cluster extends React.Component {
this.props.toggleAddNewClusterVisibility(false);
}
}
}).catch((error) => {
handleApiError(error, "Error occurred while trying to update the cluster.");
})
.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 => {
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"]({
notification.success({
message: 'Done!',
description: 'Cluster deleted successfully!'
description: 'Cluster deleted successfully!',
});
this.setState({
@ -172,13 +194,15 @@ class Cluster extends React.Component {
});
this.props.removeLoadedCluster(this.clusterId);
}
}).catch((error) => {
handleApiError(error, "Error occurred while trying to update the cluster.");
})
.catch(error => {
handleApiError(
error,
'Error occurred while trying to update the cluster.',
);
this.setState({ loading: false });
});
};
getUnselectedProducts = () => {
@ -189,7 +213,7 @@ class Cluster extends React.Component {
const unSelectedProducts = [...applications];
// remove selected products from unselected products
selectedProducts.forEach((selectedProduct) => {
selectedProducts.forEach(selectedProduct => {
for (let i = 0; i < unSelectedProducts.length; i++) {
if (selectedProduct.packageId === unSelectedProducts[i].packageId) {
// remove item from array
@ -201,10 +225,10 @@ class Cluster extends React.Component {
return unSelectedProducts;
};
addSelectedProducts = (products) => {
addSelectedProducts = products => {
this.setState({
products: [...this.state.products, ...products],
isSaveable: products.length > 0
isSaveable: products.length > 0,
});
};
@ -219,15 +243,18 @@ class Cluster extends React.Component {
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 => {
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"]({
notification.success({
message: 'Saved!',
description: 'Cluster updated successfully!'
description: 'Cluster updated successfully!',
});
const cluster = res.data.data;
@ -235,16 +262,16 @@ class Cluster extends React.Component {
this.resetChanges();
this.props.addSavedClusterToThePage(cluster);
}
}).catch((error) => {
if (error.hasOwnProperty("response") && error.response.status === 401) {
})
.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",
notification.error({
message: 'There was a problem',
duration: 0,
description:
"Error occurred while trying to update the cluster.",
description: 'Error occurred while trying to update the cluster.',
});
}
@ -258,10 +285,10 @@ class Cluster extends React.Component {
const { isTemporary, index } = this.props;
const Product = ({ product, index }) => {
const { packageId } = product;
let imageSrc = "";
let imageSrc = '';
const iconUrl = product.iconUrl;
// check if the icon url is an url or google image id
if (iconUrl.startsWith("http")) {
if (iconUrl.startsWith('http')) {
imageSrc = iconUrl;
} else {
imageSrc = `https://lh3.googleusercontent.com/${iconUrl}=s240-rw`;
@ -269,19 +296,20 @@ class Cluster extends React.Component {
return (
<div className="product">
<div className="arrow">
<button disabled={index === 0} className="btn"
<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>
<div className="title">{packageId}</div>
</Tooltip>
</div>
<div className="arrow">
@ -289,13 +317,19 @@ class Cluster extends React.Component {
disabled={index === products.length - 1}
onClick={() => {
this.swapProduct(index, index + 1);
}} className="btn btn-right"><Icon type="caret-right" theme="filled"/></button>
}}
className="btn btn-right"
>
<Icon type="caret-right" theme="filled" />
</button>
</div>
<div className="delete-btn">
<button className="btn"
<button
className="btn"
onClick={() => {
this.removeProduct(index)
}}>
this.removeProduct(index);
}}
>
<Icon type="close-circle" theme="filled" />
</button>
</div>
@ -308,19 +342,23 @@ class Cluster extends React.Component {
<Spin spinning={loading}>
<Row>
<Col span={16}>
<Title editable={{onChange: this.handleNameChange}} level={4}>{name}</Title>
<Title editable={{ onChange: this.handleNameChange }} level={4}>
{name}
</Title>
</Col>
<Col span={8}>
{!isTemporary && (
<div style={{float: "right"}}>
<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"/>
this.props.swapClusters(index, index - 1);
}}
htmlType="button"
/>
</Tooltip>
<Tooltip title="Move Down">
<Button
@ -328,20 +366,24 @@ class Cluster extends React.Component {
icon="caret-down"
size="large"
onClick={() => {
this.props.swapClusters(index, index + 1)
}} htmlType="button"/>
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}>
onConfirm={this.deleteCluster}
>
<Button
type="danger"
icon="delete"
shape="circle"
htmlType="button"/>
htmlType="button"
/>
</Popconfirm>
</Tooltip>
</div>
@ -351,33 +393,37 @@ class Cluster extends React.Component {
<div className="products-row">
<AddAppsToClusterModal
addSelectedProducts={this.addSelectedProducts}
unselectedProducts={unselectedProducts}/>
{
products.map((product, index) => {
unselectedProducts={unselectedProducts}
/>
{products.map((product, index) => {
return (
<Product
key={product.packageId}
product={product}
index={index}/>
index={index}
/>
);
})
}
})}
</div>
<Row>
<Col>
{isTemporary && (
<div>
<Button
onClick={this.cancelAddingNewCluster}>
Cancel
</Button>
<Button onClick={this.cancelAddingNewCluster}>Cancel</Button>
<Divider type="vertical" />
<Tooltip
title={(products.length === 0) ? "You must add applications to the cluster before saving" : ""}>
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">
htmlType="button"
type="primary"
>
Save
</Button>
</Tooltip>
@ -387,14 +433,17 @@ class Cluster extends React.Component {
<div>
<Button
onClick={this.resetChanges}
disabled={!this.state.isSaveable}>
disabled={!this.state.isSaveable}
>
Cancel
</Button>
<Divider type="vertical" />
<Button
onClick={this.updateCluster}
htmlType="button" type="primary"
disabled={!this.state.isSaveable}>
htmlType="button"
type="primary"
disabled={!this.state.isSaveable}
>
Save
</Button>
</div>

View File

@ -16,32 +16,30 @@
* under the License.
*/
import React from "react";
import {Button, message, Modal, notification, Select, Spin} from "antd";
import axios from "axios";
import {withConfigContext} from "../../../../../context/ConfigContext";
import {handleApiError} from "../../../../../js/Utils";
import React from 'react';
import { Button, Modal, notification, Select, Spin } from 'antd';
import axios from 'axios';
import { withConfigContext } from '../../../../../context/ConfigContext';
import { handleApiError } from '../../../../../js/Utils';
const { Option } = Select;
class EditLinks extends React.Component {
constructor(props) {
super(props);
this.selectedLinks = [];
this.state = {
visible: false
visible: false,
};
}
showModal = () => {
this.setState({
visible: true,
loading: false
loading: false,
});
};
handleCancel = e => {
this.setState({
visible: false,
@ -52,61 +50,70 @@ class EditLinks extends React.Component {
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",
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 => {
links: this.selectedLinks,
},
)
.then(res => {
if (res.status === 200) {
notification["success"]({
notification.success({
message: 'Saved!',
description: 'Links updated successfully!'
description: 'Links updated successfully!',
});
this.props.updateLinks(this.selectedLinks);
this.setState({
loading: false,
visible: false
visible: false,
});
}
}).catch((error) => {
handleApiError(error, "Error occurred while trying to update the cluster.");
})
.catch(error => {
handleApiError(
error,
'Error occurred while trying to update the cluster.',
);
this.setState({ loading: false });
});
};
handleChange= (selectedLinks) =>{
handleChange = selectedLinks => {
this.selectedLinks = selectedLinks;
};
render() {
return (
<div>
<Button onClick={this.showModal} type="link">[add / remove links]</Button>
<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">
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) => (
onChange={this.handleChange}
>
{this.props.pages.map(page => (
<Option disabled={page.id === this.props.pageId} key={page.id}>
{page.name[0]["text"]}
</Option>))
}
{page.name[0].text}
</Option>
))}
</Select>
</Spin>
</Modal>

View File

@ -16,41 +16,45 @@
* under the License.
*/
import React from "react";
import axios from "axios";
import {Tag, message, notification, Table, Typography, Divider, Icon, Popconfirm, Button} from "antd";
import React from 'react';
import axios from 'axios';
import {
Tag,
notification,
Table,
Typography,
Divider,
Icon,
Popconfirm,
Button,
} from 'antd';
import {withConfigContext} from "../../../../context/ConfigContext";
import "./Pages.css";
import {Link} from "react-router-dom";
import AddNewPage from "../AddNewPage/AddNewPage";
import {handleApiError} from "../../../../js/Utils";
import { withConfigContext } from '../../../../context/ConfigContext';
import './Pages.css';
import { Link } from 'react-router-dom';
import AddNewPage from '../AddNewPage/AddNewPage';
import { handleApiError } from '../../../../js/Utils';
const { Text, Title } = Typography;
let config = null;
class Pages extends React.Component {
constructor(props) {
super(props);
config = this.props.context;
// TimeAgo.addLocale(en);
this.state = {
data: [],
pagination: {},
loading: false,
selectedRows: [],
homePageId: null
homePageId: null,
};
}
rowSelection = {
onChange: (selectedRowKeys, selectedRows) => {
this.setState({
selectedRows: selectedRows
})
}
selectedRows: selectedRows,
});
},
};
componentDidMount() {
@ -62,19 +66,15 @@ class Pages extends React.Component {
fetch = (params = {}) => {
const config = this.props.context;
this.setState({ loading: true });
// get current page
const currentPage = (params.hasOwnProperty("page")) ? params.page : 1;
const extraParams = {
offset: 10 * (currentPage - 1), //calculate the offset
limit: 10,
};
// 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 => {
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({
@ -83,9 +83,9 @@ class Pages extends React.Component {
pagination,
});
}
}).catch((error) => {
handleApiError(error, "Error occurred while trying to load pages.");
})
.catch(error => {
handleApiError(error, 'Error occurred while trying to load pages.');
this.setState({ loading: false });
});
};
@ -93,67 +93,80 @@ class Pages extends React.Component {
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 => {
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
homePageId: res.data.data.homepageId,
});
}
}).catch((error) => {
handleApiError(error, "Error occurred while trying to get home page.");
})
.catch(error => {
handleApiError(error, 'Error occurred while trying to get home page.');
this.setState({ loading: false });
});
};
updateHomePage = (pageId) => {
updateHomePage = pageId => {
const config = this.props.context;
this.setState({
loading: true
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 => {
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!",
notification.success({
message: 'Done!',
description: 'Home page was updated successfully!',
});
this.setState({
homePageId: res.data.data.homepageId,
loading: false
loading: false,
});
}
}).catch((error) => {
handleApiError(error, "Error occurred while trying to update the home page.");
})
.catch(error => {
handleApiError(
error,
'Error occurred while trying to update the home page.',
);
this.setState({ loading: false });
});
};
deletePage = (pageId) => {
deletePage = pageId => {
const { data } = this.state;
const config = this.props.context;
this.setState({
loading: true
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 => {
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!",
notification.success({
message: 'Done!',
description: 'Home page was updated successfully!',
});
for (let i = 0; i < data.length; i++) {
@ -164,12 +177,15 @@ class Pages extends React.Component {
this.setState({
loading: false,
data: data
data: data,
});
}
}).catch((error) => {
handleApiError(error, "Error occurred while trying to delete the page.");
})
.catch(error => {
handleApiError(
error,
'Error occurred while trying to delete the page.',
);
this.setState({ loading: false });
});
};
@ -190,12 +206,20 @@ class Pages extends React.Component {
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>)
}
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',
@ -203,13 +227,15 @@ class Pages extends React.Component {
render: (name, page) => (
<div>
<span className="action">
<Button disabled={page.id === this.state.homePageId}
<Button
disabled={page.id === this.state.homePageId}
className="btn-warning"
icon="home"
type="link"
onClick={() => {
this.updateHomePage(page.id);
}}>
}}
>
set as homepage
</Button>
</span>
@ -220,9 +246,12 @@ class Pages extends React.Component {
cancelText="No"
onConfirm={() => {
this.deletePage(page.id);
}}>
}}
>
<span className="action">
<Text type="danger"><Icon type="delete"/> delete</Text>
<Text type="danger">
<Icon type="delete" /> delete
</Text>
</span>
</Popconfirm>
</div>
@ -231,22 +260,23 @@ class Pages extends React.Component {
];
render() {
const {data, pagination, loading, selectedRows} = this.state;
const { data, pagination, loading } = this.state;
return (
<div className="layout-pages">
<Title level={4}>Pages</Title>
<AddNewPage />
<div style={{backgroundColor: "#ffffff", borderRadius: 5}}>
<div style={{ backgroundColor: '#ffffff', borderRadius: 5 }}>
<Table
columns={this.columns}
rowKey={record => record.id}
dataSource={data}
pagination={{
...pagination,
size: "small",
size: 'small',
// position: "top",
showTotal: (total, range) => `showing ${range[0]}-${range[1]} of ${total} pages`,
showQuickJumper: true
showTotal: (total, range) =>
`showing ${range[0]}-${range[1]} of ${total} pages`,
showQuickJumper: true,
}}
loading={loading}
onChange={this.handleTableChange}

View File

@ -16,52 +16,54 @@
* under the License.
*/
import React from "react";
import {Button, notification} from "antd";
import axios from "axios";
import {withConfigContext} from "../../../context/ConfigContext";
import {handleApiError} from "../../../js/Utils";
import React from 'react';
import { Button, notification } from 'antd';
import axios from 'axios';
import { withConfigContext } from '../../../context/ConfigContext';
import { handleApiError } from '../../../js/Utils';
class SyncAndroidApps extends React.Component {
constructor(props) {
super(props);
this.state = {
loading: false
}
loading: false,
};
}
syncApps = () => {
const config = this.props.context;
this.setState({
loading: true
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!",
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
loading: false,
});
}).catch((error) => {
handleApiError(error, "Error occurred while syncing the apps.");
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}}>
<div style={{ display: 'inline-block', padding: 4 }}>
<Button
onClick={this.syncApps}
loading={loading}
@ -69,10 +71,10 @@ class SyncAndroidApps extends React.Component {
type="primary"
icon="sync"
>
Sync{loading && "ing..."}
Sync{loading && 'ing...'}
</Button>
</div>
)
);
}
}

View File

@ -16,7 +16,7 @@
* under the License.
*/
import React from "react";
import React from 'react';
import {
Card,
Tag,
@ -32,13 +32,14 @@ import {
Modal,
Row,
Col,
Typography, Alert
} from "antd";
import axios from "axios";
Typography,
Alert,
} from 'antd';
import axios from 'axios';
import { TweenOneGroup } from 'rc-tween-one';
import pSBC from "shade-blend-color";
import {withConfigContext} from "../../../context/ConfigContext";
import {handleApiError} from "../../../js/Utils";
import pSBC from 'shade-blend-color';
import { withConfigContext } from '../../../context/ConfigContext';
import { handleApiError } from '../../../js/Utils';
const { Title } = Typography;
@ -55,35 +56,44 @@ class ManageCategories extends React.Component {
currentlyEditingId: null,
editingValue: null,
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 => {
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
loading: false,
});
}
}).catch((error) => {
handleApiError(error, "Error occured while trying to load categories", true);
if (error.hasOwnProperty("response") && error.response.status === 403) {
})
.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
})
loading: false,
});
} else {
this.setState({
loading: false
loading: false,
});
}
});
@ -92,58 +102,69 @@ class ManageCategories extends React.Component {
handleCloseButton = () => {
this.setState({
tempElements: [],
isAddNewVisible: false
isAddNewVisible: false,
});
};
deleteCategory = (id) => {
deleteCategory = id => {
const config = this.props.context;
this.setState({
loading: true
loading: true,
});
axios.delete(
window.location.origin + config.serverConfig.invoker.uri + config.serverConfig.invoker.publisher + "/admin/applications/categories/" + id,
).then(res => {
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!",
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
categories: remainingElements,
});
}
}).catch((error) => {
handleApiError(error, "Error occurred while trying to load categories.");
})
.catch(error => {
handleApiError(
error,
'Error occurred while trying to load categories.',
);
this.setState({
loading: false
loading: false,
});
});
};
renderElement = (category) => {
renderElement = category => {
const config = this.props.context;
const categoryName = category.categoryName;
const tagElem = (
<Tag
color={pSBC(0.30, config.theme.primaryColor)}
style={{marginTop: 8}}>
color={pSBC(0.3, config.theme.primaryColor)}
style={{ marginTop: 8 }}
>
{categoryName}
<Divider type="vertical" />
<Tooltip title="edit">
<Icon onClick={() => {
this.openEditModal(categoryName)
}} type="edit"/>
<Icon
onClick={() => {
this.openEditModal(categoryName);
}}
type="edit"
/>
</Tooltip>
<Divider type="vertical" />
<Tooltip title="delete">
@ -153,15 +174,16 @@ class ManageCategories extends React.Component {
if (category.isCategoryDeletable) {
this.deleteCategory(categoryName);
} else {
notification["error"]({
notification.error({
message: 'Cannot delete "' + categoryName + '"',
description:
"This category is currently used. Please unassign the category from apps.",
'This category is currently used. Please unassign the category from apps.',
});
}
}}
okText="Yes"
cancelText="No">
cancelText="No"
>
<Icon type="delete" />
</Popconfirm>
</Tooltip>
@ -174,8 +196,7 @@ class ManageCategories extends React.Component {
);
};
renderTempElement = (category) => {
const config = this.props.context;
renderTempElement = category => {
const tagElem = (
<Tag
style={{ marginTop: 8 }}
@ -184,12 +205,10 @@ class ManageCategories extends React.Component {
e.preventDefault();
const { tempElements } = this.state;
const remainingElements = tempElements.filter(function(value) {
return value.categoryName !== category.categoryName;
});
this.setState({
tempElements: remainingElements
tempElements: remainingElements,
});
}}
>
@ -215,8 +234,14 @@ class ManageCategories extends React.Component {
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}];
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');
}
@ -233,20 +258,24 @@ class ManageCategories extends React.Component {
const config = this.props.context;
const { tempElements, categories } = this.state;
this.setState({
loading: true
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",
axios
.post(
window.location.origin +
config.serverConfig.invoker.uri +
config.serverConfig.invoker.publisher +
'/admin/applications/categories',
data,
).then(res => {
)
.then(res => {
if (res.status === 200) {
notification["success"]({
message: "Done!",
description:
"New Categories were added successfully",
notification.success({
message: 'Done!',
description: 'New Categories were added successfully',
});
this.setState({
@ -255,18 +284,16 @@ class ManageCategories extends React.Component {
inputVisible: false,
inputValue: '',
loading: false,
isAddNewVisible: false
isAddNewVisible: false,
});
}
}).catch((error) => {
handleApiError(error, "Error occurred while trying to add categories.");
})
.catch(error => {
handleApiError(error, 'Error occurred while trying to add categories.');
this.setState({
loading: false
loading: false,
});
});
};
saveInputRef = input => (this.input = input);
@ -274,16 +301,16 @@ class ManageCategories extends React.Component {
closeEditModal = e => {
this.setState({
isEditModalVisible: false,
currentlyEditingId: null
currentlyEditingId: null,
});
};
openEditModal = (id) => {
openEditModal = id => {
this.setState({
isEditModalVisible: true,
currentlyEditingId: id,
editingValue: id
})
editingValue: id,
});
};
editItem = () => {
@ -296,55 +323,73 @@ class ManageCategories extends React.Component {
isEditModalVisible: false,
});
axios.put(
window.location.origin + config.serverConfig.invoker.uri + config.serverConfig.invoker.publisher + "/admin/applications/categories/rename?from=" + currentlyEditingId + "&to=" + editingValue,
axios
.put(
window.location.origin +
config.serverConfig.invoker.uri +
config.serverConfig.invoker.publisher +
'/admin/applications/categories/rename?from=' +
currentlyEditingId +
'&to=' +
editingValue,
{},
).then(res => {
)
.then(res => {
if (res.status === 200) {
notification["success"]({
message: "Done!",
description:
"Category was edited successfully",
notification.success({
message: 'Done!',
description: 'Category was edited successfully',
});
categories[categories.findIndex(i => i.categoryName === currentlyEditingId)].categoryName = editingValue;
categories[
categories.findIndex(i => i.categoryName === currentlyEditingId)
].categoryName = editingValue;
this.setState({
categories: categories,
loading: false,
editingValue: null
editingValue: null,
});
}
}).catch((error) => {
handleApiError(error, "Error occurred while trying to delete the category.");
})
.catch(error => {
handleApiError(
error,
'Error occurred while trying to delete the category.',
);
this.setState({
loading: false,
editingValue: null
editingValue: null,
});
});
};
handleEditInputChange = (e) => {
handleEditInputChange = e => {
this.setState({
editingValue: e.target.value
editingValue: e.target.value,
});
};
render() {
const {categories, inputVisible, inputValue, tempElements, isAddNewVisible, forbiddenErrors} = this.state;
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) && (
{forbiddenErrors.categories && (
<Alert
message="You don't have permission to view categories."
type="warning"
banner
closable/>
closable
/>
)}
<Card>
<Spin tip="Working on it..." spinning={this.state.loading}>
@ -353,24 +398,30 @@ class ManageCategories extends React.Component {
<Title level={4}>Categories</Title>
</Col>
<Col span={8}>
{!isAddNewVisible &&
<div style={{float: "right"}}>
{!isAddNewVisible && (
<div style={{ float: 'right' }}>
<Button
icon="plus"
// type="primary"
size="small"
onClick={() => {
this.setState({
this.setState(
{
isAddNewVisible: true,
inputVisible: true
}, () => this.input.focus())
}} htmlType="button">Add
inputVisible: true,
},
() => this.input.focus(),
);
}}
htmlType="button"
>
Add
</Button>
</div>
}
)}
</Col>
</Row>
{isAddNewVisible &&
{isAddNewVisible && (
<div>
<Divider />
<div style={{ marginBottom: 16 }}>
@ -402,8 +453,10 @@ class ManageCategories extends React.Component {
/>
)}
{!inputVisible && (
<Tag onClick={this.showInput}
style={{background: '#fff', borderStyle: 'dashed'}}>
<Tag
onClick={this.showInput}
style={{ background: '#fff', borderStyle: 'dashed' }}
>
<Icon type="plus" /> New Category
</Tag>
)}
@ -414,7 +467,8 @@ class ManageCategories extends React.Component {
<span>
<Button
onClick={this.handleSave}
htmlType="button" type="primary"
htmlType="button"
type="primary"
size="small"
>
Save
@ -422,14 +476,12 @@ class ManageCategories extends React.Component {
<Divider type="vertical" />
</span>
)}
<Button
onClick={this.handleCloseButton}
size="small">
<Button onClick={this.handleCloseButton} size="small">
Cancel
</Button>
</div>
</div>
}
)}
<Divider dashed="true" />
<div style={{ marginTop: 8 }}>
<TweenOneGroup
@ -456,8 +508,11 @@ class ManageCategories extends React.Component {
onCancel={this.closeEditModal}
onOk={this.editItem}
>
<Input value={this.state.editingValue} ref={(input) => this.editingInput = input}
onChange={this.handleEditInputChange}/>
<Input
value={this.state.editingValue}
ref={input => (this.editingInput = input)}
onChange={this.handleEditInputChange}
/>
</Modal>
</div>
);

View File

@ -16,7 +16,7 @@
* under the License.
*/
import React from "react";
import React from 'react';
import {
Card,
Tag,
@ -30,13 +30,15 @@ import {
Tooltip,
Popconfirm,
Modal,
Row, Col,
Typography, Alert
} from "antd";
import axios from "axios";
Row,
Col,
Typography,
Alert,
} from 'antd';
import axios from 'axios';
import { TweenOneGroup } from 'rc-tween-one';
import {withConfigContext} from "../../../context/ConfigContext";
import {handleApiError} from "../../../js/Utils";
import { withConfigContext } from '../../../context/ConfigContext';
import { handleApiError } from '../../../js/Utils';
const { Title } = Typography;
@ -53,35 +55,44 @@ class ManageTags extends React.Component {
currentlyEditingId: null,
editingValue: null,
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 => {
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
loading: false,
});
}
}).catch((error) => {
handleApiError(error, "Error occurred while trying to load tags.", true);
if (error.hasOwnProperty("response") && error.response.status === 403) {
})
.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
})
loading: false,
});
} else {
this.setState({
loading: false
loading: false,
});
}
});
@ -90,60 +101,64 @@ class ManageTags extends React.Component {
handleCloseButton = () => {
this.setState({
tempElements: [],
isAddNewVisible: false
isAddNewVisible: false,
});
};
deleteTag = (id) => {
deleteTag = id => {
const config = this.props.context;
this.setState({
loading: true
loading: true,
});
axios.delete(
window.location.origin + config.serverConfig.invoker.uri + config.serverConfig.invoker.publisher + "/admin/applications/tags/" + id
).then(res => {
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!",
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
tags: remainingElements,
});
}
}).catch((error) => {
handleApiError(error, "Error occurred while trying to delete the tag.");
})
.catch(error => {
handleApiError(error, 'Error occurred while trying to delete the tag.');
this.setState({
loading: false
loading: false,
});
});
};
renderElement = (tag) => {
renderElement = tag => {
const tagName = tag.tagName;
const tagElem = (
<Tag
color="#34495e"
style={{marginTop: 8}}
>
<Tag color="#34495e" style={{ marginTop: 8 }}>
{tagName}
<Divider type="vertical" />
<Tooltip title="edit">
<Icon onClick={() => {
this.openEditModal(tagName)
}} type="edit"/>
<Icon
onClick={() => {
this.openEditModal(tagName);
}}
type="edit"
/>
</Tooltip>
<Divider type="vertical" />
<Tooltip title="delete">
@ -153,10 +168,10 @@ class ManageTags extends React.Component {
if (tag.isTagDeletable) {
this.deleteTag(tagName);
} else {
notification["error"]({
notification.error({
message: 'Cannot delete "' + tagName + '"',
description:
"This tag is currently used. Please unassign the tag from apps.",
'This tag is currently used. Please unassign the tag from apps.',
});
}
}}
@ -175,7 +190,7 @@ class ManageTags extends React.Component {
);
};
renderTempElement = (tag) => {
renderTempElement = tag => {
const { tempElements } = this.state;
const tagElem = (
<Tag
@ -184,12 +199,10 @@ class ManageTags extends React.Component {
onClose={e => {
e.preventDefault();
const remainingElements = tempElements.filter(function(value) {
return value.tagName !== tag.tagName;
});
this.setState({
tempElements: remainingElements
tempElements: remainingElements,
});
}}
>
@ -215,8 +228,14 @@ class ManageTags extends React.Component {
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}];
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');
}
@ -233,19 +252,24 @@ class ManageTags extends React.Component {
const config = this.props.context;
const { tempElements, tags } = this.state;
this.setState({
loading: true
loading: true,
});
const data = tempElements.map(tag => tag.tagName);
axios.post(window.location.origin + config.serverConfig.invoker.uri + config.serverConfig.invoker.publisher + "/applications/tags",
axios
.post(
window.location.origin +
config.serverConfig.invoker.uri +
config.serverConfig.invoker.publisher +
'/applications/tags',
data,
).then(res => {
)
.then(res => {
if (res.status === 200) {
notification["success"]({
message: "Done!",
description:
"New tags were added successfully",
notification.success({
message: 'Done!',
description: 'New tags were added successfully',
});
this.setState({
@ -254,18 +278,16 @@ class ManageTags extends React.Component {
inputVisible: false,
inputValue: '',
loading: false,
isAddNewVisible: false
isAddNewVisible: false,
});
}
}).catch((error) => {
handleApiError(error, "Error occurred while trying to delete tag.");
})
.catch(error => {
handleApiError(error, 'Error occurred while trying to delete tag.');
this.setState({
loading: false
loading: false,
});
});
};
saveInputRef = input => (this.input = input);
@ -273,16 +295,16 @@ class ManageTags extends React.Component {
closeEditModal = e => {
this.setState({
isEditModalVisible: false,
currentlyEditingId: null
currentlyEditingId: null,
});
};
openEditModal = (id) => {
openEditModal = id => {
this.setState({
isEditModalVisible: true,
currentlyEditingId: id,
editingValue: id
})
editingValue: id,
});
};
editItem = () => {
@ -295,55 +317,70 @@ class ManageTags extends React.Component {
isEditModalVisible: false,
});
axios.put(
window.location.origin + config.serverConfig.invoker.uri + config.serverConfig.invoker.publisher + "/applications/tags/rename?from=" + currentlyEditingId + "&to=" + editingValue,
axios
.put(
window.location.origin +
config.serverConfig.invoker.uri +
config.serverConfig.invoker.publisher +
'/applications/tags/rename?from=' +
currentlyEditingId +
'&to=' +
editingValue,
{},
).then(res => {
)
.then(res => {
if (res.status === 200) {
notification["success"]({
message: "Done!",
description:
"Tag was edited successfully",
notification.success({
message: 'Done!',
description: 'Tag was edited successfully',
});
tags[tags.findIndex(i => i.tagName === currentlyEditingId)].tagName = editingValue;
tags[
tags.findIndex(i => i.tagName === currentlyEditingId)
].tagName = editingValue;
this.setState({
tags: tags,
loading: false,
editingValue: null
editingValue: null,
});
}
}).catch((error) => {
handleApiError(error, "Error occurred while trying to edit tag.");
})
.catch(error => {
handleApiError(error, 'Error occurred while trying to edit tag.');
this.setState({
loading: false,
editingValue: null
editingValue: null,
});
});
};
handleEditInputChange = (e) => {
handleEditInputChange = e => {
this.setState({
editingValue: e.target.value
editingValue: e.target.value,
});
};
render() {
const {tags, inputVisible, inputValue, tempElements, isAddNewVisible, forbiddenErrors} = this.state;
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) && (
{forbiddenErrors.tags && (
<Alert
message="You don't have permission to view tags."
type="warning"
banner
closable/>
closable
/>
)}
<Card>
<Spin tip="Working on it..." spinning={this.state.loading}>
@ -352,24 +389,30 @@ class ManageTags extends React.Component {
<Title level={4}>Tags</Title>
</Col>
<Col span={8}>
{!isAddNewVisible &&
<div style={{float: "right"}}>
{!isAddNewVisible && (
<div style={{ float: 'right' }}>
<Button
icon="plus"
// type="primary"
size="small"
onClick={() => {
this.setState({
this.setState(
{
isAddNewVisible: true,
inputVisible: true
}, () => this.input.focus())
}} htmlType="button">Add
inputVisible: true,
},
() => this.input.focus(),
);
}}
htmlType="button"
>
Add
</Button>
</div>
}
)}
</Col>
</Row>
{isAddNewVisible &&
{isAddNewVisible && (
<div>
<Divider />
<div style={{ marginBottom: 16 }}>
@ -384,7 +427,8 @@ class ManageTags extends React.Component {
},
}}
leave={{ opacity: 0, width: 0, scale: 0, duration: 200 }}
appear={false}>
appear={false}
>
{temporaryElements}
{inputVisible && (
@ -400,8 +444,10 @@ class ManageTags extends React.Component {
/>
)}
{!inputVisible && (
<Tag onClick={this.showInput}
style={{background: '#fff', borderStyle: 'dashed'}}>
<Tag
onClick={this.showInput}
style={{ background: '#fff', borderStyle: 'dashed' }}
>
<Icon type="plus" /> New Tag
</Tag>
)}
@ -412,22 +458,22 @@ class ManageTags extends React.Component {
<span>
<Button
onClick={this.handleSave}
htmlType="button" type="primary"
htmlType="button"
type="primary"
size="small"
disabled={tempElements.length === 0}>
disabled={tempElements.length === 0}
>
Save
</Button>
<Divider type="vertical" />
</span>
)}
< Button
onClick={this.handleCloseButton}
size="small">
<Button onClick={this.handleCloseButton} size="small">
Cancel
</Button>
</div>
</div>
}
)}
<Divider dashed="true" />
<div style={{ marginTop: 8 }}>
<TweenOneGroup
@ -454,8 +500,11 @@ class ManageTags extends React.Component {
onCancel={this.closeEditModal}
onOk={this.editItem}
>
<Input value={this.state.editingValue} ref={(input) => this.editingInput = input}
onChange={this.handleEditInputChange}/>
<Input
value={this.state.editingValue}
ref={input => (this.editingInput = input)}
onChange={this.handleEditInputChange}
/>
</Modal>
</div>
);

View File

@ -16,30 +16,18 @@
* under the License.
*/
import React from "react";
import {
Card,
Button,
Steps,
Row,
Col,
Form,
Result,
notification,
Spin
} from "antd";
import axios from "axios";
import React from 'react';
import { Card, Button, Steps, Row, Col, Form, Result, 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";
import NewAppDetailsForm from './subForms/NewAppDetailsForm';
import NewAppUploadForm from './subForms/NewAppUploadForm';
import { withConfigContext } from '../../context/ConfigContext';
import { handleApiError } from '../../js/Utils';
const { Step } = Steps;
class AddNewAppFormComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
@ -55,76 +43,81 @@ class AddNewAppFormComponent extends React.Component {
isError: false,
deviceType: null,
supportedOsVersions: [],
errorText: "",
errorText: '',
forbiddenErrors: {
supportedOsVersions: false
}
supportedOsVersions: false,
},
};
}
onSuccessApplicationData = (application) => {
onSuccessApplicationData = application => {
const { formConfig } = this.props;
if (application.hasOwnProperty("deviceType") &&
formConfig.installationType !== "WEB_CLIP" &&
formConfig.installationType !== "CUSTOM") {
if (
application.hasOwnProperty('deviceType') &&
formConfig.installationType !== 'WEB_CLIP' &&
formConfig.installationType !== 'CUSTOM'
) {
this.getSupportedOsVersions(application.deviceType);
}
this.setState({
application,
current: 1
current: 1,
});
};
onSuccessReleaseData = (releaseData) => {
onSuccessReleaseData = releaseData => {
const config = this.props.context;
this.setState({
loading: true,
isError: false
isError: false,
});
const { application } = this.state;
const { data, release } = releaseData;
const { formConfig } = this.props;
const { price } = release;
application.subMethod = (price === 0) ? "FREE" : "PAID";
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'
type: 'application/json',
});
data.append(formConfig.jsonPayloadName, blob);
const url = window.location.origin + config.serverConfig.invoker.uri + config.serverConfig.invoker.publisher + "/applications" + formConfig.endpoint;
const url =
window.location.origin +
config.serverConfig.invoker.uri +
config.serverConfig.invoker.publisher +
'/applications' +
formConfig.endpoint;
axios.post(
url,
data
).then(res => {
axios
.post(url, data)
.then(res => {
if (res.status === 201) {
this.setState({
loading: false,
current: 2
current: 2,
});
} else {
this.setState({
loading: false,
isError: true,
current: 2
current: 2,
});
}
}).catch((error) => {
})
.catch(error => {
handleApiError(error, error.response.data.data);
this.setState({
loading: false,
isError: true,
current: 2,
errorText: error.response.data.data
errorText: error.response.data.data,
});
});
};
onClickBackButton = () => {
@ -132,12 +125,16 @@ class AddNewAppFormComponent extends React.Component {
this.setState({ current });
};
getSupportedOsVersions = (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 => {
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({
@ -145,25 +142,37 @@ class AddNewAppFormComponent extends React.Component {
loading: false,
});
}
}).catch((error) => {
handleApiError(error, "Error occurred while trying to load supported OS versions.", true);
if (error.hasOwnProperty("response") && error.response.status === 403) {
})
.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
})
loading: false,
});
} else {
this.setState({
loading: false
loading: false,
});
}
});
};
render() {
const {loading, current, isError, supportedOsVersions, errorText, forbiddenErrors} = this.state;
const {
loading,
current,
isError,
supportedOsVersions,
errorText,
forbiddenErrors,
} = this.state;
const { formConfig } = this.props;
return (
<div>
@ -176,49 +185,62 @@ class AddNewAppFormComponent extends React.Component {
<Step key="Result" title="Result" />
</Steps>
<Card style={{ marginTop: 24 }}>
<div style={{display: (current === 0 ? 'unset' : 'none')}}>
<div style={{ display: current === 0 ? 'unset' : 'none' }}>
<NewAppDetailsForm
formConfig={formConfig}
onSuccessApplicationData={this.onSuccessApplicationData}/>
onSuccessApplicationData={this.onSuccessApplicationData}
/>
</div>
<div style={{display: (current === 1 ? 'unset' : 'none')}}>
<div style={{ display: current === 1 ? 'unset' : 'none' }}>
<NewAppUploadForm
forbiddenErrors={forbiddenErrors}
formConfig={formConfig}
supportedOsVersions={supportedOsVersions}
onSuccessReleaseData={this.onSuccessReleaseData}
onClickBackButton={this.onClickBackButton}/>
onClickBackButton={this.onClickBackButton}
/>
</div>
<div style={{display: (current === 2 ? 'unset' : 'none')}}>
{!isError && (<Result
<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')}>
<Button
type="primary"
key="console"
onClick={() =>
this.props.history.push('/publisher/apps')
}
>
Go to applications
</Button>
</Button>,
]}
/>)}
/>
)}
{isError && (<Result
{isError && (
<Result
status="500"
title={errorText}
subTitle="Go back to edit the details and submit again."
extra={<Button onClick={this.onClickBackButton}>Back</Button>}
/>)}
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);

View File

@ -16,11 +16,11 @@
* under the License.
*/
import React from "react";
import {Alert, Button, Col, Divider, Form, Icon, Input, notification, Row, Select, Spin, Switch, Upload} from "antd";
import axios from "axios";
import {withConfigContext} from "../../../context/ConfigContext";
import {handleApiError} from "../../../js/Utils";
import React from 'react';
import { Alert, Button, Col, Form, Input, Row, Select, Spin } from 'antd';
import axios from 'axios';
import { withConfigContext } from '../../../context/ConfigContext';
import { handleApiError } from '../../../js/Utils';
import debounce from 'lodash.debounce';
const formItemLayout = {
@ -37,7 +37,6 @@ const {Option} = Select;
const { TextArea } = Input;
class NewAppDetailsForm extends React.Component {
constructor(props) {
super(props);
this.state = {
@ -51,8 +50,8 @@ class NewAppDetailsForm extends React.Component {
categories: false,
tags: false,
deviceTypes: false,
roles: false
}
roles: false,
},
};
this.lastFetchId = 0;
this.fetchRoles = debounce(this.fetchRoles, 800);
@ -61,14 +60,19 @@ class NewAppDetailsForm extends React.Component {
handleSubmit = e => {
e.preventDefault();
const { formConfig } = this.props;
const {specificElements} = formConfig;
this.props.form.validateFields((err, values) => {
if (!err) {
this.setState({
loading: true
loading: true,
});
const {name, description, categories, tags, unrestrictedRoles} = values;
const {
name,
description,
categories,
tags,
unrestrictedRoles,
} = values;
const unrestrictedRolesData = [];
unrestrictedRoles.map(val => {
unrestrictedRolesData.push(val.key);
@ -81,11 +85,11 @@ class NewAppDetailsForm extends React.Component {
unrestrictedRoles: unrestrictedRolesData,
};
if (formConfig.installationType !== "WEB_CLIP") {
if (formConfig.installationType !== 'WEB_CLIP') {
application.deviceType = values.deviceType;
} else {
application.type = "WEB_CLIP";
application.deviceType = "ALL";
application.type = 'WEB_CLIP';
application.deviceType = 'ALL';
}
this.props.onSuccessApplicationData(application);
@ -101,28 +105,38 @@ class NewAppDetailsForm extends React.Component {
getCategories = () => {
const config = this.props.context;
axios.get(
window.location.origin + config.serverConfig.invoker.uri + config.serverConfig.invoker.publisher + "/applications/categories"
).then(res => {
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
loading: false,
});
}
}).catch((error) => {
handleApiError(error, "Error occurred while trying to load categories.", true);
if (error.hasOwnProperty("response") && error.response.status === 403) {
})
.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
})
loading: false,
});
} else {
this.setState({
loading: false
loading: false,
});
}
});
@ -130,9 +144,14 @@ class NewAppDetailsForm extends React.Component {
getTags = () => {
const config = this.props.context;
axios.get(
window.location.origin + config.serverConfig.invoker.uri + config.serverConfig.invoker.publisher + "/applications/tags"
).then(res => {
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({
@ -140,18 +159,23 @@ class NewAppDetailsForm extends React.Component {
loading: false,
});
}
}).catch((error) => {
handleApiError(error, "Error occurred while trying to load tags.", true);
if (error.hasOwnProperty("response") && error.response.status === 403) {
})
.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
})
loading: false,
});
} else {
this.setState({
loading: false
loading: false,
});
}
});
@ -162,16 +186,21 @@ class NewAppDetailsForm extends React.Component {
const { formConfig } = this.props;
const { installationType } = formConfig;
axios.get(
window.location.origin + config.serverConfig.invoker.uri + config.serverConfig.invoker.deviceMgt + "/device-types"
).then(res => {
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") {
if (installationType === 'CUSTOM') {
allDeviceTypes.forEach(deviceType => {
if (!mobileDeviceTypes.includes(deviceType.name)) {
allowedDeviceTypes.push(deviceType);
@ -190,18 +219,23 @@ class NewAppDetailsForm extends React.Component {
loading: false,
});
}
}).catch((error) => {
handleApiError(error, "Error occurred while trying to load device types.", true);
if (error.hasOwnProperty("response") && error.response.status === 403) {
})
.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
})
loading: false,
});
} else {
this.setState({
loading: false
loading: false,
});
}
});
@ -213,9 +247,15 @@ class NewAppDetailsForm extends React.Component {
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 => {
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
@ -229,22 +269,26 @@ class NewAppDetailsForm extends React.Component {
this.setState({
unrestrictedRoles: data,
fetching: false
fetching: false,
});
}
}).catch((error) => {
handleApiError(error, "Error occurred while trying to load roles.", true);
if (error.hasOwnProperty("response") && error.response.status === 403) {
})
.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
})
fetching: false,
});
} else {
this.setState({
fetching: false
fetching: false,
});
}
});
@ -260,53 +304,57 @@ class NewAppDetailsForm extends React.Component {
render() {
const { formConfig } = this.props;
const {categories, tags, deviceTypes, fetching, roleSearchValue, unrestrictedRoles, forbiddenErrors} = this.state;
const {
categories,
tags,
deviceTypes,
fetching,
unrestrictedRoles,
forbiddenErrors,
} = this.state;
const { getFieldDecorator } = this.props.form;
return (
<div>
<Row>
<Col md={5}>
</Col>
<Col md={5}></Col>
<Col md={14}>
<Form
labelAlign="right"
layout="horizontal"
onSubmit={this.handleSubmit}>
{formConfig.installationType !== "WEB_CLIP" && (
onSubmit={this.handleSubmit}
>
{formConfig.installationType !== 'WEB_CLIP' && (
<div>
{(forbiddenErrors.deviceTypes) && (
{forbiddenErrors.deviceTypes && (
<Alert
message="You don't have permission to view device types."
type="warning"
banner
closable/>
closable
/>
)}
<Form.Item {...formItemLayout} label="Device Type">
{getFieldDecorator('deviceType', {
rules: [
{
required: true,
message: 'Please select device type'
}
message: 'Please select device type',
},
],
}
)(
})(
<Select
style={{ width: '100%' }}
placeholder="select device type">
{
deviceTypes.map(deviceType => {
placeholder="select device type"
>
{deviceTypes.map(deviceType => {
return (
<Option
key={deviceType.name}>
<Option key={deviceType.name}>
{deviceType.name}
</Option>
)
})
}
</Select>
);
})}
</Select>,
)}
</Form.Item>
</div>
@ -315,39 +363,42 @@ class NewAppDetailsForm extends React.Component {
{/* app name*/}
<Form.Item {...formItemLayout} label="App Name">
{getFieldDecorator('name', {
rules: [{
rules: [
{
required: true,
message: 'Please input a name'
}],
})(
<Input placeholder="ex: Lorem App"/>
)}
message: 'Please input a name',
},
],
})(<Input placeholder="ex: Lorem App" />)}
</Form.Item>
{/* description*/}
<Form.Item {...formItemLayout} label="Description">
{getFieldDecorator('description', {
rules: [{
rules: [
{
required: true,
message: 'Please enter a description'
}],
message: 'Please enter a description',
},
],
})(
<TextArea placeholder="Enter the description..." rows={7}/>
<TextArea placeholder="Enter the description..." rows={7} />,
)}
</Form.Item>
{/* Unrestricted Roles*/}
{(forbiddenErrors.roles) && (
{forbiddenErrors.roles && (
<Alert
message="You don't have permission to view roles."
type="warning"
banner
closable/>
closable
/>
)}
<Form.Item {...formItemLayout} label="Visible Roles">
{getFieldDecorator('unrestrictedRoles', {
rules: [],
initialValue: []
initialValue: [],
})(
<Select
mode="multiple"
@ -358,77 +409,76 @@ class NewAppDetailsForm extends React.Component {
filterOption={false}
onSearch={this.fetchRoles}
onChange={this.handleRoleSearch}
style={{width: '100%'}}>
style={{ width: '100%' }}
>
{unrestrictedRoles.map(d => (
<Option key={d.value}>{d.text}</Option>
))}
</Select>
</Select>,
)}
</Form.Item>
{(forbiddenErrors.categories) && (
{forbiddenErrors.categories && (
<Alert
message="You don't have permission to view categories."
type="warning"
banner
closable/>
closable
/>
)}
<Form.Item {...formItemLayout} label="Categories">
{getFieldDecorator('categories', {
rules: [{
rules: [
{
required: true,
message: 'Please select categories'
}],
message: 'Please select categories',
},
],
})(
<Select
mode="multiple"
style={{ width: '100%' }}
placeholder="Select a Category"
onChange={this.handleCategoryChange}>
{
categories.map(category => {
onChange={this.handleCategoryChange}
>
{categories.map(category => {
return (
<Option
key={category.categoryName}>
<Option key={category.categoryName}>
{category.categoryName}
</Option>
)
})
}
</Select>
);
})}
</Select>,
)}
</Form.Item>
{(forbiddenErrors.tags) && (
{forbiddenErrors.tags && (
<Alert
message="You don't have permission to view tags."
type="warning"
banner
closable/>
closable
/>
)}
<Form.Item {...formItemLayout} label="Tags">
{getFieldDecorator('tags', {
rules: [{
rules: [
{
required: true,
message: 'Please select tags'
}],
message: 'Please select tags',
},
],
})(
<Select
mode="tags"
style={{ width: '100%' }}
placeholder="Tags">
{
tags.map(tag => {
return (
<Option
key={tag.tagName}>
{tag.tagName}
</Option>
)
})
}
</Select>
placeholder="Tags"
>
{tags.map(tag => {
return <Option key={tag.tagName}>{tag.tagName}</Option>;
})}
</Select>,
)}
</Form.Item>
<Form.Item style={{float: "right"}}>
<Form.Item style={{ float: 'right' }}>
<Button type="primary" htmlType="submit">
Next
</Button>
@ -439,7 +489,8 @@ class NewAppDetailsForm extends React.Component {
</div>
);
}
}
export default withConfigContext(Form.create({name: 'app-details-form'})(NewAppDetailsForm));
export default withConfigContext(
Form.create({ name: 'app-details-form' })(NewAppDetailsForm),
);

View File

@ -16,9 +16,21 @@
* under the License.
*/
import React from "react";
import {Button, Col, Form, Icon, Input, Row, Select, Switch, Upload, Typography, Modal, Alert, Tooltip} from "antd";
import "@babel/polyfill";
import React from 'react';
import {
Button,
Col,
Form,
Icon,
Input,
Row,
Select,
Upload,
Typography,
Modal,
Alert,
} from 'antd';
import '@babel/polyfill';
const { Text } = Typography;
@ -46,7 +58,6 @@ function getBase64(file) {
}
class NewAppUploadForm extends React.Component {
constructor(props) {
super(props);
this.state = {
@ -63,7 +74,7 @@ class NewAppUploadForm extends React.Component {
screenshotHelperText: '',
osVersionsHelperText: '',
osVersionsValidateStatus: 'validating',
metaData: []
metaData: [],
};
this.lowerOsVersion = null;
this.upperOsVersion = null;
@ -84,9 +95,15 @@ class NewAppUploadForm extends React.Component {
this.props.form.validateFields((err, values) => {
if (!err) {
this.setState({
loading: true
loading: true,
});
const {price, isSharedWithAllTenants, binaryFile, icon, screenshots, releaseDescription, releaseType} = values;
const {
binaryFile,
icon,
screenshots,
releaseDescription,
releaseType,
} = values;
// add release data
const release = {
@ -94,30 +111,35 @@ class NewAppUploadForm extends React.Component {
price: 0,
isSharedWithAllTenants: false,
metaData: JSON.stringify(this.state.metaData),
releaseType: releaseType
releaseType: releaseType,
};
if (specificElements.hasOwnProperty("version")) {
if (specificElements.hasOwnProperty('version')) {
release.version = values.version;
}
if (specificElements.hasOwnProperty("url")) {
if (specificElements.hasOwnProperty('url')) {
release.url = values.url;
}
if (specificElements.hasOwnProperty("packageName")) {
if (specificElements.hasOwnProperty('packageName')) {
release.packageName = values.packageName;
}
const data = new FormData();
let isFormValid = true; // flag to check if this form is valid
if (formConfig.installationType !== "WEB_CLIP" && formConfig.installationType !== "CUSTOM") {
if (
formConfig.installationType !== 'WEB_CLIP' &&
formConfig.installationType !== 'CUSTOM'
) {
if (this.lowerOsVersion == null || this.upperOsVersion == null) {
isFormValid = false;
this.setState({
osVersionsHelperText: 'Please select supported OS versions',
osVersionsValidateStatus: 'error',
});
} else if (parseFloat(this.lowerOsVersion) >= parseFloat(this.upperOsVersion)) {
} else if (
parseFloat(this.lowerOsVersion) >= parseFloat(this.upperOsVersion)
) {
isFormValid = false;
this.setState({
osVersionsHelperText: 'Please select valid range',
@ -128,28 +150,31 @@ class NewAppUploadForm extends React.Component {
}
}
if (specificElements.hasOwnProperty("binaryFile") && this.state.binaryFiles.length !== 1) {
if (
specificElements.hasOwnProperty('binaryFile') &&
this.state.binaryFiles.length !== 1
) {
isFormValid = false;
this.setState({
binaryFileHelperText: 'Please select the application'
binaryFileHelperText: 'Please select the application',
});
}
if (this.state.icons.length !== 1) {
isFormValid = false;
this.setState({
iconHelperText: 'Please select an icon'
iconHelperText: 'Please select an icon',
});
}
if (this.state.screenshots.length !== 3) {
isFormValid = false;
this.setState({
screenshotHelperText: 'Please select 3 screenshots'
screenshotHelperText: 'Please select 3 screenshots',
});
}
if (this.state.screenshots.length !== 3) {
isFormValid = false;
this.setState({
screenshotHelperText: 'Please select 3 screenshots'
screenshotHelperText: 'Please select 3 screenshots',
});
}
if (isFormValid) {
@ -157,7 +182,7 @@ class NewAppUploadForm extends React.Component {
data.append('screenshot1', screenshots[0].originFileObj);
data.append('screenshot2', screenshots[1].originFileObj);
data.append('screenshot3', screenshots[2].originFileObj);
if (specificElements.hasOwnProperty("binaryFile")) {
if (specificElements.hasOwnProperty('binaryFile')) {
data.append('binaryFile', binaryFile[0].originFileObj);
}
this.props.onSuccessReleaseData({ data, release });
@ -169,17 +194,17 @@ class NewAppUploadForm extends React.Component {
handleIconChange = ({ fileList }) => {
if (fileList.length === 1) {
this.setState({
iconHelperText: ''
iconHelperText: '',
});
}
this.setState({
icons: fileList
icons: fileList,
});
};
handleBinaryFileChange = ({ fileList }) => {
if (fileList.length === 1) {
this.setState({
binaryFileHelperText: ''
binaryFileHelperText: '',
});
}
this.setState({ binaryFiles: fileList });
@ -188,11 +213,11 @@ class NewAppUploadForm extends React.Component {
handleScreenshotChange = ({ fileList }) => {
if (fileList.length === 3) {
this.setState({
screenshotHelperText: ''
screenshotHelperText: '',
});
}
this.setState({
screenshots: fileList
screenshots: fileList,
});
};
@ -210,27 +235,29 @@ class NewAppUploadForm extends React.Component {
addNewMetaData = () => {
this.setState({
metaData: this.state.metaData.concat({'key': '', 'value': ''})
})
};
handleLowerOsVersionChange = (lowerOsVersion) => {
this.lowerOsVersion = lowerOsVersion;
this.setState({
osVersionsValidateStatus: 'validating',
osVersionsHelperText: ''
metaData: this.state.metaData.concat({ key: '', value: '' }),
});
};
handleUpperOsVersionChange = (upperOsVersion) => {
if (upperOsVersion === "all") {
this.upperOsVersion = this.props.supportedOsVersions[this.props.supportedOsVersions.length - 1]["versionName"];
handleLowerOsVersionChange = lowerOsVersion => {
this.lowerOsVersion = lowerOsVersion;
this.setState({
osVersionsValidateStatus: 'validating',
osVersionsHelperText: '',
});
};
handleUpperOsVersionChange = upperOsVersion => {
if (upperOsVersion === 'all') {
this.upperOsVersion = this.props.supportedOsVersions[
this.props.supportedOsVersions.length - 1
].versionName;
} else {
this.upperOsVersion = upperOsVersion;
}
this.setState({
osVersionsValidateStatus: 'validating',
osVersionsHelperText: ''
osVersionsHelperText: '',
});
};
@ -248,7 +275,7 @@ class NewAppUploadForm extends React.Component {
screenshotHelperText,
metaData,
osVersionsHelperText,
osVersionsValidateStatus
osVersionsValidateStatus,
} = this.state;
const uploadButton = (
<div>
@ -260,29 +287,31 @@ class NewAppUploadForm extends React.Component {
return (
<div>
<Row>
<Col md={5}>
</Col>
<Col md={5}></Col>
<Col md={14}>
<Form
labelAlign="right"
layout="horizontal"
onSubmit={this.handleSubmit}>
{formConfig.specificElements.hasOwnProperty("binaryFile") && (
<Form.Item {...formItemLayout}
onSubmit={this.handleSubmit}
>
{formConfig.specificElements.hasOwnProperty('binaryFile') && (
<Form.Item
{...formItemLayout}
label="Application"
validateStatus="error"
help={binaryFileHelperText}>
help={binaryFileHelperText}
>
{getFieldDecorator('binaryFile', {
valuePropName: 'binaryFile',
getValueFromEvent: this.normFile,
required: true,
message: 'Please select application'
message: 'Please select application',
})(
<Upload
name="binaryFile"
onChange={this.handleBinaryFileChange}
beforeUpload={() => false}>
beforeUpload={() => false}
>
{binaryFiles.length !== 1 && (
<Button>
<Icon type="upload" /> Click to upload
@ -293,68 +322,74 @@ class NewAppUploadForm extends React.Component {
</Form.Item>
)}
<Form.Item {...formItemLayout}
<Form.Item
{...formItemLayout}
label="Icon"
validateStatus="error"
help={iconHelperText}
style={{
marginBottom: 0
}}>
marginBottom: 0,
}}
>
{getFieldDecorator('icon', {
valuePropName: 'icon',
getValueFromEvent: this.normFile,
required: true,
message: 'Please select a icon'
message: 'Please select a icon',
})(
<Upload
name="logo"
listType="picture-card"
onChange={this.handleIconChange}
beforeUpload={() => false}
onPreview={this.handlePreview}>
onPreview={this.handlePreview}
>
{icons.length === 1 ? null : uploadButton}
</Upload>
</Upload>,
)}
</Form.Item>
<Row style={{
marginBottom: 24
}}>
<Col xs={24} sm={8}>
</Col>
<Row
style={{
marginBottom: 24,
}}
>
<Col xs={24} sm={8}></Col>
<Col xs={24} sm={16}>
<Text type="secondary">
Recommended : 240px x 240px
</Text>
<Text type="secondary">Recommended : 240px x 240px</Text>
</Col>
</Row>
<Form.Item {...formItemLayout}
<Form.Item
{...formItemLayout}
label="Screenshots"
validateStatus="error"
help={screenshotHelperText}
style={{
marginBottom: 0
}}>
marginBottom: 0,
}}
>
{getFieldDecorator('screenshots', {
valuePropName: 'icon',
getValueFromEvent: this.normFile,
required: true,
message: 'Please select a icon'
message: 'Please select a icon',
})(
<Upload
name="screenshots"
listType="picture-card"
onChange={this.handleScreenshotChange}
beforeUpload={() => false}
onPreview={this.handlePreview}>
onPreview={this.handlePreview}
>
{screenshots.length >= 3 ? null : uploadButton}
</Upload>
</Upload>,
)}
</Form.Item>
<Row style={{
marginBottom: 24
}}>
<Col xs={24} sm={8}>
</Col>
<Row
style={{
marginBottom: 24,
}}
>
<Col xs={24} sm={8}></Col>
<Col xs={24} sm={16}>
<Text type="secondary">
It is mandatory to upload three screenshots and the
@ -362,86 +397,96 @@ class NewAppUploadForm extends React.Component {
</Text>
</Col>
</Row>
{formConfig.specificElements.hasOwnProperty("packageName") && (
{formConfig.specificElements.hasOwnProperty('packageName') && (
<Form.Item {...formItemLayout} label="Package Name">
{getFieldDecorator('packageName', {
rules: [{
rules: [
{
required: true,
message: 'Please input the package name'
}],
})(
<Input placeholder="Package Name"/>
)}
message: 'Please input the package name',
},
],
})(<Input placeholder="Package Name" />)}
</Form.Item>
)}
{formConfig.specificElements.hasOwnProperty("url") && (
{formConfig.specificElements.hasOwnProperty('url') && (
<Form.Item {...formItemLayout} label="URL">
{getFieldDecorator('url', {
rules: [{
rules: [
{
required: true,
message: 'Please input the url'
}],
})(
<Input placeholder="url"/>
)}
message: 'Please input the url',
},
],
})(<Input placeholder="url" />)}
</Form.Item>
)}
{formConfig.specificElements.hasOwnProperty("version") && (
{formConfig.specificElements.hasOwnProperty('version') && (
<Form.Item {...formItemLayout} label="Version">
{getFieldDecorator('version', {
rules: [{
rules: [
{
required: true,
message: 'Please input the version'
}],
})(
<Input placeholder="Version"/>
)}
message: 'Please input the version',
},
],
})(<Input placeholder="Version" />)}
</Form.Item>
)}
<Form.Item {...formItemLayout} label="Release Type">
{getFieldDecorator('releaseType', {
rules: [{
rules: [
{
required: true,
message: 'Please input the Release Type'
}],
})(
<Input placeholder="Release Type"/>
)}
message: 'Please input the Release Type',
},
],
})(<Input placeholder="Release Type" />)}
</Form.Item>
<Form.Item {...formItemLayout} label="Description">
{getFieldDecorator('releaseDescription', {
rules: [{
rules: [
{
required: true,
message: 'Please enter a description for release'
}],
message: 'Please enter a description for release',
},
],
})(
<TextArea placeholder="Enter a description for release" rows={5}/>
<TextArea
placeholder="Enter a description for release"
rows={5}
/>,
)}
</Form.Item>
{(formConfig.installationType !== "WEB_CLIP" && formConfig.installationType !== "CUSTOM") && (
{formConfig.installationType !== 'WEB_CLIP' &&
formConfig.installationType !== 'CUSTOM' && (
<div>
{(this.props.forbiddenErrors.supportedOsVersions) && (
{this.props.forbiddenErrors.supportedOsVersions && (
<Alert
message="You don't have permission to view supported OS versions."
type="warning"
banner
closable/>
closable
/>
)}
<Form.Item
{...formItemLayout}
label="Supported OS Versions"
validateStatus={osVersionsValidateStatus}
help={osVersionsHelperText}>
help={osVersionsHelperText}
>
{getFieldDecorator('supportedOS', {
rules: [{
required: true
}],
initialValue: false
rules: [
{
required: true,
},
],
initialValue: false,
})(
<div>
<InputGroup>
@ -449,11 +494,14 @@ class NewAppUploadForm extends React.Component {
<Col span={11}>
<Select
placeholder="Lower version"
style={{width: "100%"}}
onChange={this.handleLowerOsVersionChange}>
style={{ width: '100%' }}
onChange={this.handleLowerOsVersionChange}
>
{supportedOsVersions.map(version => (
<Option key={version.versionName}
value={version.versionName}>
<Option
key={version.versionName}
value={version.versionName}
>
{version.versionName}
</Option>
))}
@ -463,19 +511,22 @@ class NewAppUploadForm extends React.Component {
<p> - </p>
</Col>
<Col span={11}>
<Select style={{width: "100%"}}
<Select
style={{ width: '100%' }}
placeholder="Upper version"
defaultActiveFirstOption={true}
onChange={this.handleUpperOsVersionChange}>
{(supportedOsVersions.length > 0) && (
<Option key="all"
value="all">
onChange={this.handleUpperOsVersionChange}
>
{supportedOsVersions.length > 0 && (
<Option key="all" value="all">
All
</Option>
)}
{supportedOsVersions.map(version => (
<Option key={version.versionName}
value={version.versionName}>
<Option
key={version.versionName}
value={version.versionName}
>
{version.versionName}
</Option>
))}
@ -483,7 +534,7 @@ class NewAppUploadForm extends React.Component {
</Col>
</Row>
</InputGroup>
</div>
</div>,
)}
</Form.Item>
</div>
@ -491,8 +542,7 @@ class NewAppUploadForm extends React.Component {
<Form.Item {...formItemLayout} label="Meta Data">
{getFieldDecorator('meta', {})(
<div>
{
metaData.map((data, index) => {
{metaData.map((data, index) => {
return (
<InputGroup key={index}>
<Row gutter={8}>
@ -500,66 +550,83 @@ class NewAppUploadForm extends React.Component {
<Input
placeholder="key"
value={data.key}
onChange={(e) => {
metaData[index]['key'] = e.currentTarget.value;
onChange={e => {
metaData[index].key = e.currentTarget.value;
this.setState({
metaData
})
}}/>
metaData,
});
}}
/>
</Col>
<Col span={8}>
<Input
placeholder="value"
value={data.value}
onChange={(e) => {
onChange={e => {
metaData[index].value = e.currentTarget.value;
this.setState({
metaData
metaData,
});
}}/>
}}
/>
</Col>
<Col span={3}>
<Button type="dashed"
<Button
type="dashed"
shape="circle"
icon="minus"
onClick={() => {
metaData.splice(index, 1);
this.setState({
metaData
metaData,
});
}}/>
}}
/>
</Col>
</Row>
</InputGroup>
)
}
)
}
<Button type="dashed" icon="plus" onClick={this.addNewMetaData}>
);
})}
<Button
type="dashed"
icon="plus"
onClick={this.addNewMetaData}
>
Add
</Button>
</div>
</div>,
)}
</Form.Item>
<Form.Item style={{float: "right", marginLeft: 8}}>
<Form.Item style={{ float: 'right', marginLeft: 8 }}>
<Button type="primary" htmlType="submit">
Submit
</Button>
</Form.Item>
<Form.Item style={{float: "right"}}>
<Button htmlType="button" onClick={this.props.onClickBackButton}>
<Form.Item style={{ float: 'right' }}>
<Button
htmlType="button"
onClick={this.props.onClickBackButton}
>
Back
</Button>
</Form.Item>
</Form>
</Col>
</Row>
<Modal visible={previewVisible} footer={null} onCancel={this.handlePreviewCancel}>
<img alt="Preview Image" style={{width: '100%'}} src={previewImage}/>
<Modal
visible={previewVisible}
footer={null}
onCancel={this.handlePreviewCancel}
>
<img
alt="Preview Image"
style={{ width: '100%' }}
src={previewImage}
/>
</Modal>
</div>
);
}
}
export default (Form.create({name: 'app-upload-form'})(NewAppUploadForm));
export default Form.create({ name: 'app-upload-form' })(NewAppUploadForm);

View File

@ -16,24 +16,23 @@
* under the License.
*/
import React from "react";
import {Form, notification, Spin, Card, Row, Col} from "antd";
import axios from "axios";
import {withRouter} from 'react-router-dom'
import {withConfigContext} from "../../context/ConfigContext";
import {handleApiError} from "../../js/Utils";
import NewAppUploadForm from "../new-app/subForms/NewAppUploadForm";
import React from 'react';
import { Form, notification, Spin, Card, Row, Col } from 'antd';
import axios from 'axios';
import { withRouter } from 'react-router-dom';
import { withConfigContext } from '../../context/ConfigContext';
import { handleApiError } from '../../js/Utils';
import NewAppUploadForm from '../new-app/subForms/NewAppUploadForm';
const formConfig = {
specificElements: {
binaryFile: {
required: true
}
}
required: true,
},
},
};
class AddNewReleaseFormComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
@ -43,8 +42,8 @@ class AddNewReleaseFormComponent extends React.Component {
release: null,
deviceType: null,
forbiddenErrors: {
supportedOsVersions: false
}
supportedOsVersions: false,
},
};
}
@ -52,12 +51,16 @@ class AddNewReleaseFormComponent extends React.Component {
this.getSupportedOsVersions(this.props.deviceType);
}
getSupportedOsVersions = (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 => {
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({
@ -65,68 +68,79 @@ class AddNewReleaseFormComponent extends React.Component {
loading: false,
});
}
}).catch((error) => {
handleApiError(error, "Error occurred while trying to load supported OS versions.", true);
if (error.hasOwnProperty("response") && error.response.status === 403) {
})
.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
})
loading: false,
});
} else {
this.setState({
loading: false
loading: false,
});
}
});
};
onSuccessReleaseData = (releaseData) => {
onSuccessReleaseData = releaseData => {
const config = this.props.context;
const { appId, deviceType } = this.props;
this.setState({
loading: true
loading: true,
});
const { data, release } = releaseData;
const json = JSON.stringify(release);
const blob = new Blob([json], {
type: 'application/json'
type: 'application/json',
});
data.append("applicationRelease", blob);
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 => {
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",
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
loading: false,
});
}
}).catch((error) => {
handleApiError(error, "Sorry, we were unable to complete your request.");
})
.catch(error => {
handleApiError(
error,
'Sorry, we were unable to complete your request.',
);
this.setState({
loading: false
loading: false,
});
});
};
onClickBackButton = () => {
@ -157,5 +171,7 @@ class AddNewReleaseFormComponent extends React.Component {
}
}
const AddReleaseForm = withRouter(Form.create({name: 'add-new-release'})(AddNewReleaseFormComponent));
const AddReleaseForm = withRouter(
Form.create({ name: 'add-new-release' })(AddNewReleaseFormComponent),
);
export default withConfigContext(AddReleaseForm);

View File

@ -16,11 +16,12 @@
* under the License.
*/
import React from "react";
import React from 'react';
const ConfigContext = React.createContext();
export const withConfigContext = Component => {
// eslint-disable-next-line react/display-name
return props => (
<ConfigContext.Consumer>
{context => {
@ -31,4 +32,3 @@ export const withConfigContext = Component => {
};
export default ConfigContext;

View File

@ -19,27 +19,26 @@
import React from 'react';
import ReactDOM from 'react-dom';
import * as serviceWorker from './serviceWorker';
import App from "./App";
import Login from "./pages/Login";
import Dashboard from "./pages/dashboard/Dashboard";
import Apps from "./pages/dashboard/apps/Apps";
import Release from "./pages/dashboard/apps/release/Release";
import AddNewEnterpriseApp from "./pages/dashboard/add-new-app/AddNewEnterpriseApp";
import Mange from "./pages/dashboard/manage/Manage";
import App from './App';
import Login from './pages/Login';
import Dashboard from './pages/dashboard/Dashboard';
import Apps from './pages/dashboard/apps/Apps';
import Release from './pages/dashboard/apps/release/Release';
import AddNewEnterpriseApp from './pages/dashboard/add-new-app/AddNewEnterpriseApp';
import Mange from './pages/dashboard/manage/Manage';
import './index.css';
import AddNewPublicApp from "./pages/dashboard/add-new-app/AddNewPublicApp";
import AddNewWebClip from "./pages/dashboard/add-new-app/AddNewWebClip";
import AddNewRelease from "./pages/dashboard/add-new-release/AddNewRelease";
import AddNewCustomApp from "./pages/dashboard/add-new-app/AddNewCustomApp";
import ManageAndroidEnterprise from "./pages/dashboard/manage/android-enterprise/ManageAndroidEnterprise";
import Page from "./pages/dashboard/manage/android-enterprise/page/Page";
import AddNewPublicApp from './pages/dashboard/add-new-app/AddNewPublicApp';
import AddNewWebClip from './pages/dashboard/add-new-app/AddNewWebClip';
import AddNewRelease from './pages/dashboard/add-new-release/AddNewRelease';
import AddNewCustomApp from './pages/dashboard/add-new-app/AddNewCustomApp';
import ManageAndroidEnterprise from './pages/dashboard/manage/android-enterprise/ManageAndroidEnterprise';
import Page from './pages/dashboard/manage/android-enterprise/page/Page';
const routes = [
{
path: '/publisher/login',
exact: true,
component: Login
component: Login,
},
{
path: '/publisher/',
@ -49,61 +48,58 @@ const routes = [
{
path: '/publisher/apps',
component: Apps,
exact: true
exact: true,
},
{
path: '/publisher/apps/releases/:uuid',
exact: true,
component: Release
component: Release,
},
{
path: '/publisher/apps/:deviceType/:appId/add-release',
component: AddNewRelease,
exact: true
exact: true,
},
{
path: '/publisher/add-new-app/enterprise',
component: AddNewEnterpriseApp,
exact: true
exact: true,
},
{
path: '/publisher/add-new-app/public',
component: AddNewPublicApp,
exact: true
exact: true,
},
{
path: '/publisher/add-new-app/web-clip',
component: AddNewWebClip,
exact: true
exact: true,
},
{
path: '/publisher/add-new-app/custom-app',
component: AddNewCustomApp,
exact: true
exact: true,
},
{
path: '/publisher/manage',
component: Mange,
exact: true
exact: true,
},
{
path: '/publisher/manage/android-enterprise',
component: ManageAndroidEnterprise,
exact: true
exact: true,
},
{
path: '/publisher/manage/android-enterprise/pages/:pageName/:pageId',
component: Page,
exact: true
}
]
}
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
// unregister() to register() below. Note this comes with some pitfalls.

View File

@ -16,16 +16,27 @@
* under the License.
*/
import {notification} from "antd";
import { notification } from 'antd';
export const handleApiError = (error, message, isForbiddenMessageSilent = false) => {
if (error.hasOwnProperty("response") && error.response.status === 401) {
export const handleApiError = (
error,
message,
isForbiddenMessageSilent = false,
) => {
if (error.hasOwnProperty('response') && error.response.status === 401) {
const redirectUrl = encodeURI(window.location.href);
window.location.href = window.location.origin + `/publisher/login?redirect=${redirectUrl}`;
window.location.href =
window.location.origin + `/publisher/login?redirect=${redirectUrl}`;
// silence 403 forbidden message
} else if (!(isForbiddenMessageSilent && error.hasOwnProperty("response") && error.response.status === 403)) {
notification["error"]({
message: "There was a problem",
} else if (
!(
isForbiddenMessageSilent &&
error.hasOwnProperty('response') &&
error.response.status === 403
)
) {
notification.error({
message: 'There was a problem',
duration: 10,
description: message,
});

View File

@ -16,13 +16,22 @@
* under the License.
*/
import React from "react";
import {Typography, Row, Col, Form, Icon, Input, Button, Checkbox, message, notification} from 'antd';
import React from 'react';
import {
Typography,
Row,
Col,
Form,
Icon,
Input,
Button,
message,
notification,
} from 'antd';
import './Login.css';
import axios from 'axios';
import "./Login.css";
import {withConfigContext} from "../context/ConfigContext";
import {handleApiError} from "../js/Utils";
import './Login.css';
import { withConfigContext } from '../context/ConfigContext';
const { Title } = Typography;
const { Text } = Typography;
@ -32,22 +41,20 @@ class Login extends React.Component {
const config = this.props.context;
return (
<div className="login">
<div className="background">
</div>
<div className="background"></div>
<div className="content">
<Row>
<Col xs={3} sm={3} md={10}>
</Col>
<Col xs={3} sm={3} md={10}></Col>
<Col xs={18} sm={18} md={4}>
<Row style={{ marginBottom: 20 }}>
<Col style={{textAlign: "center"}}>
<img style={
{
<Col style={{ textAlign: 'center' }}>
<img
style={{
marginTop: 36,
height: 60
}
}
src={config.theme.logo}/>
height: 60,
}}
src={config.theme.logo}
/>
</Col>
</Row>
<Title level={2}>Login</Title>
@ -55,87 +62,89 @@ class Login extends React.Component {
</Col>
</Row>
<Row>
<Col span={4} offset={10}>
</Col>
<Col span={4} offset={10}></Col>
</Row>
</div>
</div>
);
}
}
class NormalLoginForm extends React.Component {
constructor(props) {
super(props);
this.state = {
inValid: false,
loading: false
loading: false,
};
}
handleSubmit = (e) => {
handleSubmit = e => {
const config = this.props.context;
const thisForm = this;
e.preventDefault();
this.props.form.validateFields((err, values) => {
thisForm.setState({
inValid: false
inValid: false,
});
if (!err) {
thisForm.setState({
loading: true
loading: true,
});
const parameters = {
username: values.username,
password: values.password,
platform: "publisher"
platform: 'publisher',
};
const request = Object.keys(parameters).map(key => key + '=' + parameters[key]).join('&');
const request = Object.keys(parameters)
.map(key => key + '=' + parameters[key])
.join('&');
axios.post(window.location.origin+ config.serverConfig.loginUri, request
).then(res=>{
axios
.post(window.location.origin + config.serverConfig.loginUri, request)
.then(res => {
if (res.status === 200) {
let redirectUrl = window.location.origin+"/publisher";
let redirectUrl = window.location.origin + '/publisher';
const searchParams = new URLSearchParams(window.location.search);
if(searchParams.has("redirect")){
redirectUrl = searchParams.get("redirect");
if (searchParams.has('redirect')) {
redirectUrl = searchParams.get('redirect');
}
window.location = redirectUrl;
}
}).catch(function (error) {
if (error.hasOwnProperty("response") && error.response.status === 401) {
})
.catch(function(error) {
if (
error.hasOwnProperty('response') &&
error.response.status === 401
) {
thisForm.setState({
loading: false,
inValid: true
inValid: true,
});
} else {
notification["error"]({
message: "There was a problem",
notification.error({
message: 'There was a problem',
duration: 10,
description: message,
});
thisForm.setState({
loading: false,
inValid: false
inValid: false,
});
}
});
}
});
};
render() {
const { getFieldDecorator } = this.props.form;
let errorMsg = "";
let errorMsg = '';
if (this.state.inValid) {
errorMsg = <Text type="danger">Invalid Login Details</Text>;
}
let loading = "";
let loading = '';
if (this.state.loading) {
loading = <Text type="secondary">Loading..</Text>;
}
@ -145,23 +154,35 @@ class NormalLoginForm extends React.Component {
{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"/>
<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"/>
<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">
<Button
loading={this.state.loading}
block
type="primary"
htmlType="submit"
className="login-form-button"
>
Log in
</Button>
</Form.Item>
@ -170,6 +191,8 @@ class NormalLoginForm extends React.Component {
}
}
const WrappedNormalLoginForm = Form.create({name: 'normal_login'})(withConfigContext(NormalLoginForm));
const WrappedNormalLoginForm = Form.create({ name: 'normal_login' })(
withConfigContext(NormalLoginForm),
);
export default withConfigContext(Login);

View File

@ -16,14 +16,14 @@
* under the License.
*/
import React from "react";
import React from 'react';
import { Layout, Menu, Icon, Drawer, Button } from 'antd';
import {Switch, Link} from "react-router-dom";
import RouteWithSubRoutes from "../../components/RouteWithSubRoutes"
import {Redirect} from 'react-router'
import "./Dashboard.css";
import {withConfigContext} from "../../context/ConfigContext";
import Logout from "./logout/Logout";
import { Switch, Link } from 'react-router-dom';
import RouteWithSubRoutes from '../../components/RouteWithSubRoutes';
import { Redirect } from 'react-router';
import './Dashboard.css';
import { withConfigContext } from '../../context/ConfigContext';
import Logout from './logout/Logout';
const { Header, Content, Footer } = Layout;
const { SubMenu } = Menu;
@ -34,7 +34,7 @@ class Dashboard extends React.Component {
this.state = {
routes: props.routes,
visible: false,
collapsed: false
collapsed: false,
};
this.config = this.props.context;
this.Logo = this.config.theme.logo;
@ -44,7 +44,7 @@ class Dashboard extends React.Component {
showMobileNavigationBar = () => {
this.setState({
visible: true,
collapsed: !this.state.collapsed
collapsed: !this.state.collapsed,
});
};
@ -58,9 +58,17 @@ class Dashboard extends React.Component {
return (
<div>
<Layout>
<Header style={{paddingLeft: 0, paddingRight: 0, backgroundColor: "white"}}>
<Header
style={{
paddingLeft: 0,
paddingRight: 0,
backgroundColor: 'white',
}}
>
<div className="logo-image">
<Link to="/publisher/apps"><img alt="logo" src={this.Logo}/></Link>
<Link to="/publisher/apps">
<img alt="logo" src={this.Logo} />
</Link>
</div>
<div className="web-layout">
@ -68,9 +76,14 @@ class Dashboard extends React.Component {
theme="light"
mode="horizontal"
defaultSelectedKeys={['1']}
style={{lineHeight: '64px'}}>
<Menu.Item key="1"><Link to="/publisher/apps"><Icon
type="appstore"/>Apps</Link></Menu.Item>
style={{ lineHeight: '64px' }}
>
<Menu.Item key="1">
<Link to="/publisher/apps">
<Icon type="appstore" />
Apps
</Link>
</Menu.Item>
<SubMenu
title={
@ -78,11 +91,10 @@ class Dashboard extends React.Component {
<Icon type="plus" />
Add New App
</span>
}>
}
>
<Menu.Item key="add-new-public-app">
<Link to="/publisher/add-new-app/public">
Public App
</Link>
<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">
@ -90,9 +102,7 @@ class Dashboard extends React.Component {
</Link>
</Menu.Item>
<Menu.Item key="add-new-web-clip">
<Link to="/publisher/add-new-app/web-clip">
Web Clip
</Link>
<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">
@ -104,8 +114,11 @@ class Dashboard extends React.Component {
<SubMenu
title={
<span className="submenu-title-wrapper">
<Icon type="control"/>Manage
</span>}>
<Icon type="control" />
Manage
</span>
}
>
<Menu.Item key="manage">
<Link to="/publisher/manage">
<Icon type="setting" /> General
@ -114,17 +127,22 @@ class Dashboard extends React.Component {
{this.config.androidEnterpriseToken != null && (
<Menu.Item key="manage-android-enterprise">
<Link to="/publisher/manage/android-enterprise">
<Icon type="android" theme="filled"/> Android Enterprise
<Icon type="android" theme="filled" /> Android
Enterprise
</Link>
</Menu.Item>
)}
</SubMenu>
<SubMenu className="profile"
<SubMenu
className="profile"
title={
<span className="submenu-title-wrapper">
<Icon type="user"/>{this.config.user}
</span>}>
<Icon type="user" />
{this.config.user}
</span>
}
>
<Logout />
</SubMenu>
</Menu>
@ -137,17 +155,23 @@ class Dashboard extends React.Component {
<Button type="link" onClick={this.showMobileNavigationBar}>
<Icon
type={this.state.collapsed ? 'menu-fold' : 'menu-unfold'}
className="bar-icon"/>
className="bar-icon"
/>
</Button>
</div>
</Layout>
<Drawer
title={
<Link to="/publisher/apps" onClick={this.onCloseMobileNavigationBar}>
<img alt="logo"
<Link
to="/publisher/apps"
onClick={this.onCloseMobileNavigationBar}
>
<img
alt="logo"
src={this.Logo}
style={{ marginLeft: 30 }}
width={"60%"}/>
width={'60%'}
/>
</Link>
}
placement="left"
@ -155,29 +179,36 @@ class Dashboard extends React.Component {
onClose={this.onCloseMobileNavigationBar}
visible={this.state.visible}
getContainer={false}
style={{position: 'absolute'}}>
style={{ position: 'absolute' }}
>
<Menu
theme="light"
mode="inline"
defaultSelectedKeys={['1']}
style={{ lineHeight: '64px', width: 231 }}
onClick={this.onCloseMobileNavigationBar}>
onClick={this.onCloseMobileNavigationBar}
>
<Menu.Item key="1">
<Link to="/publisher/apps">
<Icon type="appstore"/>Apps
<Icon type="appstore" />
Apps
</Link>
</Menu.Item>
<SubMenu
title={
<span className="submenu-title-wrapper">
<Icon type="plus"/>Add New App
<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>
<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>
@ -188,7 +219,8 @@ class Dashboard extends React.Component {
</SubMenu>
<Menu.Item key="2">
<Link to="/publisher/manage">
<Icon type="control"/>Manage
<Icon type="control" />
Manage
</Link>
</Menu.Item>
</Menu>
@ -197,12 +229,15 @@ class Dashboard extends React.Component {
<Menu
mode="horizontal"
defaultSelectedKeys={['1']}
style={{lineHeight: '63px', position: 'fixed', marginLeft: '80%'}}>
style={{ lineHeight: '63px', position: 'fixed', marginLeft: '80%' }}
>
<SubMenu
title={
<span className="submenu-title-wrapper">
<Icon type="user" />
</span>}>
</span>
}
>
<Logout />
</SubMenu>
</Menu>
@ -212,14 +247,12 @@ class Dashboard extends React.Component {
<Content style={{ marginTop: 2 }}>
<Switch>
<Redirect exact from="/publisher" to="/publisher/apps" />
{this.state.routes.map((route) => (
{this.state.routes.map(route => (
<RouteWithSubRoutes key={route.path} {...route} />
))}
</Switch>
</Content>
<Footer style={{textAlign: 'center'}}>
{this.footerText}
</Footer>
<Footer style={{ textAlign: 'center' }}>{this.footerText}</Footer>
</Layout>
</div>
);

View File

@ -16,67 +16,63 @@
* under the License.
*/
import React from "react";
import {
PageHeader,
Typography,
Breadcrumb,
Icon
} from "antd";
import AddNewAppForm from "../../../components/new-app/AddNewAppForm";
import {Link} from "react-router-dom";
import React from 'react';
import { PageHeader, Typography, Breadcrumb, Icon } from 'antd';
import AddNewAppForm from '../../../components/new-app/AddNewAppForm';
import { Link } from 'react-router-dom';
const { Paragraph } = Typography;
const formConfig = {
installationType: "CUSTOM",
endpoint: "/custom-app",
jsonPayloadName: "application",
releaseWrapperName: "customAppReleaseWrappers",
installationType: 'CUSTOM',
endpoint: '/custom-app',
jsonPayloadName: 'application',
releaseWrapperName: 'customAppReleaseWrappers',
specificElements: {
binaryFile: {
required: true
required: true,
},
packageName: {
required: true
required: true,
},
version: {
required: true
}
}
required: true,
},
},
};
class AddNewCustomApp extends React.Component {
constructor(props) {
super(props);
this.state = {
current: 0,
categories: []
categories: [],
};
}
render() {
return (
<div>
<PageHeader style={{paddingTop:0, backgroundColor: "#fff"}}>
<PageHeader style={{ paddingTop: 0, backgroundColor: '#fff' }}>
<Breadcrumb style={{ paddingBottom: 16 }}>
<Breadcrumb.Item>
<Link to="/publisher/apps"><Icon type="home"/> Home</Link>
<Link to="/publisher/apps">
<Icon type="home" /> Home
</Link>
</Breadcrumb.Item>
<Breadcrumb.Item>Add New Custom App</Breadcrumb.Item>
</Breadcrumb>
<div className="wrap">
<h3>Add New Custom App</h3>
<Paragraph>Submit and share your own application to the corporate app store.</Paragraph>
<Paragraph>
Submit and share your own application to the corporate app store.
</Paragraph>
</div>
</PageHeader>
<div style={{ background: '#f0f2f5', padding: 24, minHeight: 720 }}>
<AddNewAppForm formConfig={formConfig} />
</div>
</div>
);
}
}

View File

@ -16,61 +16,57 @@
* under the License.
*/
import React from "react";
import {
PageHeader,
Typography,
Breadcrumb,
Icon
} from "antd";
import AddNewAppForm from "../../../components/new-app/AddNewAppForm";
import {Link} from "react-router-dom";
import React from 'react';
import { PageHeader, Typography, Breadcrumb, Icon } from 'antd';
import AddNewAppForm from '../../../components/new-app/AddNewAppForm';
import { Link } from 'react-router-dom';
const { Paragraph } = Typography;
const formConfig = {
installationType: "ENTERPRISE",
endpoint: "/ent-app",
jsonPayloadName: "application",
releaseWrapperName: "entAppReleaseWrappers",
installationType: 'ENTERPRISE',
endpoint: '/ent-app',
jsonPayloadName: 'application',
releaseWrapperName: 'entAppReleaseWrappers',
specificElements: {
binaryFile: {
required: true
}
}
required: true,
},
},
};
class AddNewEnterpriseApp extends React.Component {
constructor(props) {
super(props);
this.state = {
current: 0,
categories: []
categories: [],
};
}
render() {
return (
<div>
<PageHeader style={{paddingTop:0, backgroundColor: "#fff"}}>
<PageHeader style={{ paddingTop: 0, backgroundColor: '#fff' }}>
<Breadcrumb style={{ paddingBottom: 16 }}>
<Breadcrumb.Item>
<Link to="/publisher/apps"><Icon type="home"/> Home</Link>
<Link to="/publisher/apps">
<Icon type="home" /> Home
</Link>
</Breadcrumb.Item>
<Breadcrumb.Item>Add New Enterprise App</Breadcrumb.Item>
</Breadcrumb>
<div className="wrap">
<h3>Add New Enterprise App</h3>
<Paragraph>Submit and share your own application to the corporate app store.</Paragraph>
<Paragraph>
Submit and share your own application to the corporate app store.
</Paragraph>
</div>
</PageHeader>
<div style={{ background: '#f0f2f5', padding: 24, minHeight: 720 }}>
<AddNewAppForm formConfig={formConfig} />
</div>
</div>
);
}
}

View File

@ -16,40 +16,34 @@
* under the License.
*/
import React from "react";
import {
Icon,
PageHeader,
Typography,
Breadcrumb
} from "antd";
import AddNewAppForm from "../../../components/new-app/AddNewAppForm";
import {Link} from "react-router-dom";
import React from 'react';
import { Icon, PageHeader, 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 = {
installationType: "PUBLIC",
endpoint: "/public-app",
jsonPayloadName:"public-app",
releaseWrapperName: "publicAppReleaseWrappers",
installationType: 'PUBLIC',
endpoint: '/public-app',
jsonPayloadName: 'public-app',
releaseWrapperName: 'publicAppReleaseWrappers',
specificElements: {
packageName: {
required: true
required: true,
},
version: {
required: true
}
}
required: true,
},
},
};
class AddNewEnterpriseApp extends React.Component {
constructor(props) {
super(props);
this.state = {
current: 0,
categories: []
categories: [],
};
}
@ -57,29 +51,30 @@ class AddNewEnterpriseApp extends React.Component {
// this.getCategories();
}
render() {
return (
<div>
<PageHeader style={{paddingTop:0, backgroundColor: "#fff"}}>
<PageHeader style={{ paddingTop: 0, backgroundColor: '#fff' }}>
<Breadcrumb style={{ paddingBottom: 16 }}>
<Breadcrumb.Item>
<Link to="/publisher/apps"><Icon type="home"/> Home</Link>
<Link to="/publisher/apps">
<Icon type="home" /> Home
</Link>
</Breadcrumb.Item>
<Breadcrumb.Item>Add New Public App</Breadcrumb.Item>
</Breadcrumb>
<div className="wrap">
<h3>Add New Public App</h3>
<Paragraph>Share a public application in google play or apple store to your corporate app store.
<Paragraph>
Share a public application in google play or apple store to your
corporate app store.
</Paragraph>
</div>
</PageHeader>
<div style={{ background: '#f0f2f5', padding: 24, minHeight: 720 }}>
<AddNewAppForm formConfig={formConfig} />
</div>
</div>
);
}
}

View File

@ -16,51 +16,46 @@
* under the License.
*/
import React from "react";
import {
Icon,
PageHeader,
Typography,
Breadcrumb
} from "antd";
import AddNewAppForm from "../../../components/new-app/AddNewAppForm";
import {Link} from "react-router-dom";
import React from 'react';
import { Icon, PageHeader, 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 = {
installationType: "WEB_CLIP",
endpoint: "/web-app",
jsonPayloadName:"webapp",
releaseWrapperName: "webAppReleaseWrappers",
installationType: 'WEB_CLIP',
endpoint: '/web-app',
jsonPayloadName: 'webapp',
releaseWrapperName: 'webAppReleaseWrappers',
specificElements: {
url: {
required: true
required: true,
},
version: {
required: true
}
}
required: true,
},
},
};
class AddNewEnterpriseApp extends React.Component {
constructor(props) {
super(props);
this.state = {
current: 0,
categories: []
categories: [],
};
}
render() {
return (
<div>
<PageHeader style={{paddingTop:0, backgroundColor: "#fff"}}>
<PageHeader style={{ paddingTop: 0, backgroundColor: '#fff' }}>
<Breadcrumb style={{ paddingBottom: 16 }}>
<Breadcrumb.Item>
<Link to="/publisher/apps"><Icon type="home"/> Home</Link>
<Link to="/publisher/apps">
<Icon type="home" /> Home
</Link>
</Breadcrumb.Item>
<Breadcrumb.Item>Add New Web Clip</Breadcrumb.Item>
</Breadcrumb>
@ -72,9 +67,7 @@ class AddNewEnterpriseApp extends React.Component {
<div style={{ background: '#f0f2f5', padding: 24, minHeight: 720 }}>
<AddNewAppForm formConfig={formConfig} />
</div>
</div>
);
}
}

View File

@ -16,25 +16,19 @@
* under the License.
*/
import React from "react";
import {
Icon,
PageHeader,
Typography,
Breadcrumb
} from "antd";
import AddNewReleaseForm from "../../../components/new-release/AddReleaseForm";
import {Link} from "react-router-dom";
import React from 'react';
import { Icon, PageHeader, Typography, Breadcrumb } from 'antd';
import AddNewReleaseForm from '../../../components/new-release/AddReleaseForm';
import { Link } from 'react-router-dom';
const Paragraph = Typography;
class AddNewRelease extends React.Component {
constructor(props) {
super(props);
this.state = {
current: 0,
categories: []
categories: [],
};
}
@ -42,10 +36,12 @@ class AddNewRelease extends React.Component {
const { appId, deviceType } = this.props.match.params;
return (
<div>
<PageHeader style={{paddingTop:0, backgroundColor: "#fff"}}>
<PageHeader style={{ paddingTop: 0, backgroundColor: '#fff' }}>
<Breadcrumb style={{ paddingBottom: 16 }}>
<Breadcrumb.Item>
<Link to="/publisher/apps"><Icon type="home"/> Home</Link>
<Link to="/publisher/apps">
<Icon type="home" /> Home
</Link>
</Breadcrumb.Item>
<Breadcrumb.Item>Add New Release</Breadcrumb.Item>
</Breadcrumb>
@ -57,9 +53,7 @@ class AddNewRelease extends React.Component {
<div style={{ background: '#f0f2f5', padding: 24, minHeight: 720 }}>
<AddNewReleaseForm deviceType={deviceType} appId={appId} />
</div>
</div>
);
}
}

View File

@ -16,15 +16,14 @@
* under the License.
*/
import React from "react";
import ListApps from "../../../components/apps/list-apps/ListApps";
import React from 'react';
import ListApps from '../../../components/apps/list-apps/ListApps';
class Apps extends React.Component {
routes;
constructor(props) {
super(props);
this.routes = props.routes;
}
render() {
@ -33,9 +32,7 @@ class Apps extends React.Component {
<div style={{ background: '#f0f2f5', padding: 24, minHeight: 780 }}>
<ListApps />
</div>
</div>
);
}
}

View File

@ -16,15 +16,14 @@
* under the License.
*/
import React from "react";
import React from 'react';
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 ReleaseView from "../../../../components/apps/release/ReleaseView";
import LifeCycle from "../../../../components/apps/release/lifeCycle/LifeCycle";
import {withConfigContext} from "../../../../context/ConfigContext";
import {handleApiError} from "../../../../js/Utils";
import NewAppUploadForm from "../../../../components/new-app/subForms/NewAppUploadForm";
import ReleaseView from '../../../../components/apps/release/ReleaseView';
import LifeCycle from '../../../../components/apps/release/lifeCycle/LifeCycle';
import { withConfigContext } from '../../../../context/ConfigContext';
import { handleApiError } from '../../../../js/Utils';
const { Title } = Typography;
@ -44,8 +43,8 @@ class Release extends React.Component {
supportedOsVersions: [],
forbiddenErrors: {
supportedOsVersions: false,
lifeCycle: false
}
lifeCycle: false,
},
};
}
@ -53,105 +52,139 @@ class Release extends React.Component {
const { uuid } = this.props.match.params;
this.fetchData(uuid);
this.getLifecycle();
}
changeCurrentLifecycleStatus = (status) => {
changeCurrentLifecycleStatus = status => {
this.setState({
currentLifecycleStatus: status
currentLifecycleStatus: status,
});
};
updateRelease = (release) => {
updateRelease = release => {
this.setState({
release
release,
});
};
fetchData = (uuid) => {
fetchData = uuid => {
const config = this.props.context;
// send request to the invoker
axios.get(
window.location.origin + config.serverConfig.invoker.uri + config.serverConfig.invoker.publisher + "/applications/release/" + uuid,
).then(res => {
axios
.get(
window.location.origin +
config.serverConfig.invoker.uri +
config.serverConfig.invoker.publisher +
'/applications/release/' +
uuid,
)
.then(res => {
if (res.status === 200) {
const app = res.data.data;
const release = (app !== null) ? app.applicationReleases[0] : null;
const currentLifecycleStatus = (release !== null) ? release.currentStatus : null;
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
uuid: uuid,
});
if (config.deviceTypes.mobileTypes.includes(app.deviceType)) {
this.getSupportedOsVersions(app.deviceType);
}
}
}).catch((error) => {
handleApiError(error, "Error occurred while trying to load the release.");
})
.catch(error => {
handleApiError(
error,
'Error occurred while trying to load the release.',
);
this.setState({ loading: false });
});
};
getLifecycle = () => {
const config = this.props.context;
axios.get(
window.location.origin + config.serverConfig.invoker.uri + config.serverConfig.invoker.publisher + "/applications/lifecycle-config"
).then(res => {
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
})
lifecycle: lifecycle,
});
}
}).catch((error) => {
handleApiError(error, "Error occurred while trying to load lifecycle configuration.", true);
if (error.hasOwnProperty("response") && error.response.status === 403) {
})
.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
})
forbiddenErrors,
});
}
});
};
getSupportedOsVersions = (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 => {
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
supportedOsVersions,
});
}
}).catch((error) => {
handleApiError(error, "Error occurred while trying to load supported OS versions.", true);
if (error.hasOwnProperty("response") && error.response.status === 403) {
})
.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
})
loading: false,
});
} else {
this.setState({
loading: false
loading: false,
});
}
});
};
render() {
const {app, release, currentLifecycleStatus, lifecycle, loading, forbiddenErrors} = this.state;
const {
app,
release,
currentLifecycleStatus,
lifecycle,
loading,
forbiddenErrors,
} = this.state;
if (release == null && loading === false) {
return (
@ -168,8 +201,13 @@ class Release extends React.Component {
<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) && (
<Skeleton
loading={loading}
avatar={{ size: 'large' }}
active
paragraph={{ rows: 18 }}
>
{release !== null && (
<ReleaseView
forbiddenErrors={forbiddenErrors}
app={app}
@ -178,29 +216,30 @@ class Release extends React.Component {
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) && (
{release !== null && (
<LifeCycle
uuid={release.uuid}
currentStatus={release.currentStatus.toUpperCase()}
changeCurrentLifecycleStatus={this.changeCurrentLifecycleStatus}
lifecycle={lifecycle}
/>)
changeCurrentLifecycleStatus={
this.changeCurrentLifecycleStatus
}
lifecycle={lifecycle}
/>
)}
</Skeleton>
</Card>
</Col>
</Row>
</div>
</div>
);
}
}

View File

@ -16,52 +16,51 @@
* under the License.
*/
import React from "react";
import React from 'react';
import { notification, Menu, Icon } from 'antd';
import axios from 'axios';
import {withConfigContext} from "../../../context/ConfigContext";
import { withConfigContext } from '../../../context/ConfigContext';
/*
This class for call the logout api by sending request
*/
class Logout extends React.Component {
constructor(props) {
super(props);
this.state = {
inValid: false,
loading: false
loading: false,
};
}
/*
This function call the logout api when the request is success
*/
handleSubmit = () => {
const thisForm = this;
const config = this.props.context;
thisForm.setState({
inValid: false
inValid: false,
});
axios.post(window.location.origin + config.serverConfig.logoutUri
).then(res => {
axios
.post(window.location.origin + config.serverConfig.logoutUri)
.then(res => {
// if the api call status is correct then user will logout and then it goes to login page
if (res.status === 200) {
window.location = window.location.origin + "/publisher/login";
window.location = window.location.origin + '/publisher/login';
}
}).catch(function (error) {
if (error.hasOwnProperty("response") && error.response.status === 400) {
})
.catch(function(error) {
if (error.hasOwnProperty('response') && error.response.status === 400) {
thisForm.setState({
inValid: true
inValid: true,
});
} else {
notification["error"]({
message: "There was a problem",
notification.error({
message: 'There was a problem',
duration: 0,
description:
"Error occurred while trying to logout.",
description: 'Error occurred while trying to logout.',
});
}
});
@ -70,7 +69,10 @@ class Logout extends React.Component {
render() {
return (
<Menu>
<Menu.Item key="1" onClick={this.handleSubmit}><Icon type="logout"/>Logout</Menu.Item>
<Menu.Item key="1" onClick={this.handleSubmit}>
<Icon type="logout" />
Logout
</Menu.Item>
</Menu>
);
}

View File

@ -16,11 +16,11 @@
* under the License.
*/
import React from "react";
import {PageHeader, Typography, Breadcrumb, Row, Col, Icon} from "antd";
import ManageCategories from "../../../components/manage/categories/ManageCategories";
import ManageTags from "../../../components/manage/categories/ManageTags";
import {Link} from "react-router-dom";
import React from 'react';
import { PageHeader, Typography, Breadcrumb, Row, Col, Icon } from 'antd';
import ManageCategories from '../../../components/manage/categories/ManageCategories';
import ManageTags from '../../../components/manage/categories/ManageTags';
import { Link } from 'react-router-dom';
const { Paragraph } = Typography;
@ -30,25 +30,26 @@ class Manage extends React.Component {
constructor(props) {
super(props);
this.routes = props.routes;
}
render() {
return (
<div>
<PageHeader style={{paddingTop:0, backgroundColor: "#fff"}}>
<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
<Link to="/publisher/apps">
<Icon type="home" /> Home
</Link>
</Breadcrumb.Item>
<Breadcrumb.Item>Manage</Breadcrumb.Item>
<Breadcrumb.Item>General</Breadcrumb.Item>
</Breadcrumb>
<div className="wrap">
<h3>Manage General Settings</h3>
<Paragraph>Maintain and manage categories and tags here..</Paragraph>
<Paragraph>
Maintain and manage categories and tags here..
</Paragraph>
</div>
</PageHeader>
<div style={{ background: '#f0f2f5', padding: 24, minHeight: 780 }}>
@ -61,9 +62,7 @@ class Manage extends React.Component {
</Col>
</Row>
</div>
</div>
);
}
}

View File

@ -16,15 +16,13 @@
* under the License.
*/
import React from "react";
import {PageHeader, Typography, Breadcrumb, Divider, Button, Icon} from "antd";
import {Link} from "react-router-dom";
import SyncAndroidApps from "../../../../components/manage/android-enterprise/SyncAndroidApps";
import {withConfigContext} from "../../../../context/ConfigContext";
import GooglePlayIframe from "../../../../components/manage/android-enterprise/GooglePlayIframe";
import Pages from "../../../../components/manage/android-enterprise/Pages/Pages";
const {Paragraph} = Typography;
import React from 'react';
import { PageHeader, Breadcrumb, Divider, Icon } from 'antd';
import { Link } from 'react-router-dom';
import SyncAndroidApps from '../../../../components/manage/android-enterprise/SyncAndroidApps';
import { withConfigContext } from '../../../../context/ConfigContext';
import GooglePlayIframe from '../../../../components/manage/android-enterprise/GooglePlayIframe';
import Pages from '../../../../components/manage/android-enterprise/Pages/Pages';
class ManageAndroidEnterprise extends React.Component {
routes;
@ -38,14 +36,14 @@ class ManageAndroidEnterprise extends React.Component {
render() {
return (
<div>
<PageHeader style={{paddingTop:0, backgroundColor: "#fff"}}>
<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
<Link to="/publisher/apps">
<Icon type="home" /> Home
</Link>
</Breadcrumb.Item>
<Breadcrumb.Item>Manage</Breadcrumb.Item>
<Breadcrumb.Item>Android Enterprise</Breadcrumb.Item>
</Breadcrumb>
<div className="wrap">
@ -60,7 +58,6 @@ class ManageAndroidEnterprise extends React.Component {
<Pages />
</div>
</div>
);
}
}

View File

@ -16,7 +16,7 @@
* under the License.
*/
import React from "react";
import React from 'react';
import {
PageHeader,
Typography,
@ -28,19 +28,17 @@ import {
notification,
message,
Spin,
Select,
Tag,
Divider
} from "antd";
import {Link, withRouter} from "react-router-dom";
import {withConfigContext} from "../../../../../context/ConfigContext";
import axios from "axios";
import Cluster from "../../../../../components/manage/android-enterprise/Pages/Cluster/Cluster";
import EditLinks from "../../../../../components/manage/android-enterprise/Pages/EditLinks/EditLinks";
import {handleApiError} from "../../../../../js/Utils";
Divider,
} from 'antd';
import { Link, withRouter } from 'react-router-dom';
import { withConfigContext } from '../../../../../context/ConfigContext';
import axios from 'axios';
import Cluster from '../../../../../components/manage/android-enterprise/Pages/Cluster/Cluster';
import EditLinks from '../../../../../components/manage/android-enterprise/Pages/EditLinks/EditLinks';
import { handleApiError } from '../../../../../js/Utils';
const {Option} = Select;
const {Title, Text} = Typography;
const { Title } = Typography;
class Page extends React.Component {
routes;
@ -59,7 +57,7 @@ class Page extends React.Component {
loading: false,
applications: [],
isAddNewClusterVisible: false,
links: []
links: [],
};
}
@ -69,7 +67,7 @@ class Page extends React.Component {
this.fetchPages();
}
removeLoadedCluster = (clusterId) => {
removeLoadedCluster = clusterId => {
const clusters = [...this.state.clusters];
let index = -1;
for (let i = 0; i < clusters.length; i++) {
@ -80,38 +78,46 @@ class Page extends React.Component {
}
clusters.splice(index, 1);
this.setState({
clusters
clusters,
});
};
updatePageName = pageName => {
const config = this.props.context;
if (pageName !== this.state.pageName && pageName !== "") {
if (pageName !== this.state.pageName && pageName !== '') {
const data = {
locale: "en",
locale: 'en',
pageName: pageName,
pageId: this.pageId
pageId: this.pageId,
};
axios.put(
window.location.origin + config.serverConfig.invoker.uri +
"/device-mgt/android/v1.0/enterprise/store-layout/page",
data
).then(res => {
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"]({
notification.success({
message: 'Saved!',
description: 'Page name updated successfully!'
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.props.history.push(
`/publisher/manage/android-enterprise/pages/${pageName}/${this.pageId}`,
);
}
}).catch((error) => {
handleApiError(error, "Error occurred while trying to save the page name.");
})
.catch(error => {
handleApiError(
error,
'Error occurred while trying to save the page name.',
);
this.setState({ loading: false });
});
}
@ -122,7 +128,10 @@ class Page extends React.Component {
if (swapIndex !== -1 && index < clusters.length) {
// swap elements
[clusters[index], clusters[swapIndex]] = [clusters[swapIndex], clusters[index]];
[clusters[index], clusters[swapIndex]] = [
clusters[swapIndex],
clusters[index],
];
this.setState({
clusters,
@ -135,38 +144,40 @@ class Page extends React.Component {
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 => {
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.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
links,
});
}
}).catch((error) => {
if (error.hasOwnProperty("response") && error.response.status === 401) {
})
.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",
notification.error({
message: 'There was a problem',
duration: 0,
description:
"Error occurred while trying to load pages.",
description: 'Error occurred while trying to load pages.',
});
}
@ -176,35 +187,40 @@ class Page extends React.Component {
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 => {
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);
clusters.sort((a, b) => (a.orderInPage > b.orderInPage ? 1 : -1));
this.setState({
clusters,
loading: false
loading: false,
});
}
}).catch((error) => {
if (error.hasOwnProperty("response") && error.response.status === 401) {
})
.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)) {
} else if (
!(error.hasOwnProperty('response') && error.response.status === 404)
) {
// API sends 404 when no apps
notification["error"]({
message: "There was a problem",
notification.error({
message: 'There was a problem',
duration: 0,
description:
"Error occurred while trying to load clusters.",
description: 'Error occurred while trying to load clusters.',
});
}
this.setState({
loading: false
loading: false,
});
});
};
@ -215,23 +231,28 @@ class Page extends React.Component {
this.setState({ loading: true });
const filters = {
appType: "PUBLIC",
deviceType: "android"
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 => {
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
}
name: application.name,
};
});
this.setState({
@ -239,17 +260,16 @@ class Page extends React.Component {
applications,
});
}
}).catch((error) => {
if (error.hasOwnProperty("response") && error.response.status === 401) {
})
.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",
notification.error({
message: 'There was a problem',
duration: 0,
description:
"Error occurred while trying to load pages.",
description: 'Error occurred while trying to load pages.',
});
}
@ -257,40 +277,49 @@ class Page extends React.Component {
});
};
toggleAddNewClusterVisibility = (isAddNewClusterVisible) => {
toggleAddNewClusterVisibility = isAddNewClusterVisible => {
this.setState({
isAddNewClusterVisible
isAddNewClusterVisible,
});
};
addSavedClusterToThePage = (cluster) => {
addSavedClusterToThePage = cluster => {
this.setState({
clusters: [...this.state.clusters, cluster],
isAddNewClusterVisible: false
isAddNewClusterVisible: false,
});
window.scrollTo(0, document.body.scrollHeight);
};
updateLinks = (links) =>{
updateLinks = links => {
this.setState({
links
links,
});
};
render() {
const {pageName, loading, clusters, applications, isAddNewClusterVisible, links} = this.state;
const {
pageName,
loading,
clusters,
applications,
isAddNewClusterVisible,
links,
} = this.state;
return (
<div>
<PageHeader style={{paddingTop:0, backgroundColor: "#fff"}}>
<PageHeader style={{ paddingTop: 0, backgroundColor: '#fff' }}>
<Breadcrumb style={{ paddingBottom: 16 }}>
<Breadcrumb.Item>
<Link to="/publisher/apps"><Icon type="home"/> Home</Link>
<Link to="/publisher/apps">
<Icon type="home" /> Home
</Link>
</Breadcrumb.Item>
<Breadcrumb.Item>Manage</Breadcrumb.Item>
<Breadcrumb.Item>
Manage
</Breadcrumb.Item>
<Breadcrumb.Item>
<Link to="/publisher/manage/android-enterprise">Android Enterprise</Link>
<Link to="/publisher/manage/android-enterprise">
Android Enterprise
</Link>
</Breadcrumb.Item>
<Breadcrumb.Item>Manage Page</Breadcrumb.Item>
</Breadcrumb>
@ -303,27 +332,30 @@ class Page extends React.Component {
<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>
<Title editable={{ onChange: this.updatePageName }} level={2}>
{pageName}
</Title>
</Col>
</Row>
<Row>
<Col>
<Title level={4}>Links</Title>
{
links.map(link => {
{links.map(link => {
if (this.pageNames.hasOwnProperty(link.toString())) {
return <Tag key={link}
color="#87d068">{this.pageNames[link.toString()]}</Tag>
} else {
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}/>
pages={this.pages}
/>
</Col>
{/* <Col>*/}
@ -333,7 +365,10 @@ class Page extends React.Component {
<Divider dashed={true} />
<Title level={4}>Clusters</Title>
<div hidden={isAddNewClusterVisible} style={{textAlign: "center"}}>
<div
hidden={isAddNewClusterVisible}
style={{ textAlign: 'center' }}
>
<Button
type="dashed"
shape="round"
@ -342,25 +377,29 @@ class Page extends React.Component {
onClick={() => {
this.toggleAddNewClusterVisibility(true);
}}
>Add new cluster</Button>
>
Add new cluster
</Button>
</div>
<div hidden={!isAddNewClusterVisible}>
<Cluster
cluster={{
clusterId: 0,
name: "New Cluster",
products: []
name: 'New Cluster',
products: [],
}}
orderInPage={clusters.length}
isTemporary={true}
pageId={this.pageId}
applications={applications}
addSavedClusterToThePage={this.addSavedClusterToThePage}
toggleAddNewClusterVisibility={this.toggleAddNewClusterVisibility}/>
toggleAddNewClusterVisibility={
this.toggleAddNewClusterVisibility
}
/>
</div>
{
clusters.map((cluster, index) => {
{clusters.map((cluster, index) => {
return (
<Cluster
key={cluster.clusterId}
@ -371,14 +410,13 @@ class Page extends React.Component {
pageId={this.pageId}
applications={applications}
swapClusters={this.swapClusters}
removeLoadedCluster={this.removeLoadedCluster}/>
removeLoadedCluster={this.removeLoadedCluster}
/>
);
})
}
})}
</div>
</Spin>
</div>
);
}
}

View File

@ -34,8 +34,8 @@ const isLocalhost = Boolean(
window.location.hostname === '[::1]' ||
// 127.0.0.1/8 is considered localhost for IPv4.
window.location.hostname.match(
/^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
)
/^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/,
),
);
export function register(config) {
@ -61,7 +61,7 @@ export function register(config) {
navigator.serviceWorker.ready.then(() => {
console.log(
'This web app is being served cache-first by a service ' +
'worker. To learn more, visit https://bit.ly/CRA-PWA'
'worker. To learn more, visit https://bit.ly/CRA-PWA',
);
});
} else {
@ -89,7 +89,7 @@ function registerValidSW(swUrl, config) {
// content until all client tabs are closed.
console.log(
'New content is available and will be used when all ' +
'tabs for this page are closed. See https://bit.ly/CRA-PWA.'
'tabs for this page are closed. See https://bit.ly/CRA-PWA.',
);
// Execute callback
@ -139,7 +139,7 @@ function checkValidServiceWorker(swUrl, config) {
})
.catch(() => {
console.log(
'No internet connection found. App is running in offline mode.'
'No internet connection found. App is running in offline mode.',
);
});
}

View File

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