mirror of
https://repository.entgra.net/community/device-mgt-core.git
synced 2025-10-06 02:01:45 +00:00
Merge branch 'eslint' into 'master'
Add ESLint to APPM Store react app Closes product-iots#293 See merge request entgra/carbon-device-mgt!407
This commit is contained in:
commit
351e599895
@ -82,6 +82,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>
|
||||
|
||||
@ -0,0 +1,325 @@
|
||||
{
|
||||
"parser": "babel-eslint",
|
||||
"plugins": [
|
||||
"react",
|
||||
"babel",
|
||||
"jsx",
|
||||
"prettier"
|
||||
],
|
||||
"extends": [
|
||||
"eslint:recommended",
|
||||
"plugin:react/recommended"
|
||||
],
|
||||
"parserOptions": {
|
||||
"ecmaVersion": 2016,
|
||||
"sourceType": "module",
|
||||
"ecmaFeatures": {
|
||||
"jsx": true
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
"react": {
|
||||
"createClass": "createReactClass",
|
||||
"pragma": "React",
|
||||
"version": "16.8.6"
|
||||
}
|
||||
},
|
||||
"env": {
|
||||
"node": true,
|
||||
"commonjs": true,
|
||||
"browser": true,
|
||||
"jasmine": true,
|
||||
"es6": true
|
||||
},
|
||||
"globals": {
|
||||
"document": true,
|
||||
"console": true,
|
||||
// Only for development purposes
|
||||
"setTimeout": true,
|
||||
"window" : true
|
||||
},
|
||||
"rules": {
|
||||
"prettier/prettier": "error",
|
||||
// Enforce the spacing around the * in generator functions.
|
||||
"generator-star-spacing": [2, "after"],
|
||||
// Disallow using variables outside the blocks they are defined (especially
|
||||
// since only let and const are used, see "no-var").
|
||||
"block-scoped-var": 2,
|
||||
// Require camel case names
|
||||
"camelcase": 2,
|
||||
// Allow trailing commas for easy list extension. Having them does not
|
||||
// impair readability, but also not required either.
|
||||
"comma-dangle": 0,
|
||||
// Warn about cyclomatic complexity in functions.
|
||||
"complexity": 1,
|
||||
// Don't warn for inconsistent naming when capturing this (not so important
|
||||
// with auto-binding fat arrow functions).
|
||||
"consistent-this": 0,
|
||||
// Enforce curly brace conventions for all control statements.
|
||||
"curly": 2,
|
||||
// Don't require a default case in switch statements. Avoid being forced to
|
||||
// add a bogus default when you know all possible cases are handled.
|
||||
"default-case": 0,
|
||||
// Encourage the use of dot notation whenever possible.
|
||||
"dot-notation": 2,
|
||||
// Allow mixed 'LF' and 'CRLF' as linebreaks.
|
||||
"linebreak-style": 0,
|
||||
// Don't enforce the maximum depth that blocks can be nested.
|
||||
"max-depth": 0,
|
||||
// Maximum length of a line.
|
||||
"max-len": [2, 100, 2, { "ignoreStrings": true, "ignoreUrls": true}],
|
||||
// Maximum depth callbacks can be nested.
|
||||
"max-nested-callbacks": [2, 3],
|
||||
// Don't limit the number of parameters that can be used in a function.
|
||||
"max-params": 0,
|
||||
// Don't limit the maximum number of statement allowed in a function.
|
||||
"max-statements": 0,
|
||||
// Require a capital letter for constructors, only check if all new
|
||||
// operators are followed by a capital letter. Don't warn when capitalized
|
||||
// functions are used without the new operator.
|
||||
"new-cap": [2, {"capIsNew": false}],
|
||||
// Disallow use of the Array constructor.
|
||||
"no-array-constructor": 2,
|
||||
// Allow use of bitwise operators.
|
||||
"no-bitwise": 0,
|
||||
// Disallow use of arguments.caller or arguments.callee.
|
||||
"no-caller": 2,
|
||||
// Disallow the catch clause parameter name being the same as a variable in
|
||||
// the outer scope, to avoid confusion.
|
||||
"no-catch-shadow": 2,
|
||||
// Disallow assignment in conditional expressions.
|
||||
"no-cond-assign": 2,
|
||||
// Allow using the console API.
|
||||
"no-console": 0,
|
||||
// Allow using constant expressions in conditions like while (true)
|
||||
"no-constant-condition": 0,
|
||||
// Allow use of the continue statement.
|
||||
"no-continue": 0,
|
||||
// Disallow control characters in regular expressions.
|
||||
"no-control-regex": 2,
|
||||
// Disallow deletion of variables (deleting properties is fine).
|
||||
"no-delete-var": 2,
|
||||
// Disallow duplicate arguments in functions.
|
||||
"no-dupe-args": 2,
|
||||
// Disallow duplicate keys when creating object literals.
|
||||
"no-dupe-keys": 2,
|
||||
// Disallow multiple empty lines
|
||||
"no-multiple-empty-lines": "error",
|
||||
// Disallow a duplicate case label.
|
||||
"no-duplicate-case": 2,
|
||||
// Disallow else after a return in an if. The else around the second return
|
||||
// here is useless:
|
||||
// if (something) { return false; } else { return true; }
|
||||
"no-else-return": 2,
|
||||
// Disallow empty statements. This will report an error for:
|
||||
// try { something(); } catch (e) {}
|
||||
// but will not report it for:
|
||||
// try { something(); } catch (e) { /* Silencing the error because ...*/ }
|
||||
// which is a valid use case.
|
||||
"no-empty": 2,
|
||||
// Disallow the use of empty character classes in regular expressions.
|
||||
"no-empty-character-class": 2,
|
||||
// Disallow use of labels for anything other then loops and switches.
|
||||
"no-labels": 2,
|
||||
// Disallow use of eval(). We have other APIs to evaluate code in content.
|
||||
"no-eval": 2,
|
||||
// Disallow assigning to the exception in a catch block.
|
||||
"no-ex-assign": 2,
|
||||
// Disallow adding to native types
|
||||
"no-extend-native": 2,
|
||||
// Disallow unnecessary function binding.
|
||||
"no-extra-bind": 2,
|
||||
// Disallow double-negation boolean casts in a boolean context.
|
||||
"no-extra-boolean-cast": 2,
|
||||
// Allow unnecessary parentheses, as they may make the code more readable.
|
||||
"no-extra-parens": 0,
|
||||
// Disallow fallthrough of case statements, except if there is a comment.
|
||||
"no-fallthrough": 2,
|
||||
// Allow the use of leading or trailing decimal points in numeric literals.
|
||||
"no-floating-decimal": 0,
|
||||
// Disallow if as the only statement in an else block.
|
||||
"no-lonely-if": 2,
|
||||
// Disallow use of multiline strings (use template strings instead).
|
||||
"no-multi-str": 2,
|
||||
// Disallow reassignments of native objects.
|
||||
"no-native-reassign": 2,
|
||||
// Disallow nested ternary expressions, they make the code hard to read.
|
||||
"no-nested-ternary": 2,
|
||||
// Allow use of new operator with the require function.
|
||||
"no-new-require": 0,
|
||||
// Disallow use of octal literals.
|
||||
"no-octal": 2,
|
||||
// Allow reassignment of function parameters.
|
||||
"no-param-reassign": 0,
|
||||
// Allow string concatenation with __dirname and __filename (not a node env).
|
||||
"no-path-concat": 0,
|
||||
// Allow use of unary operators, ++ and --.
|
||||
"no-plusplus": 0,
|
||||
// Allow using process.env (not a node environment).
|
||||
"no-process-env": 0,
|
||||
// Allow using process.exit (not a node environment).
|
||||
"no-process-exit": 0,
|
||||
// Disallow usage of __proto__ property.
|
||||
"no-proto": 2,
|
||||
// Disallow declaring the same variable more than once (we use let anyway).
|
||||
"no-redeclare": 2,
|
||||
// Disallow multiple spaces in a regular expression literal.
|
||||
"no-regex-spaces": 2,
|
||||
// Allow reserved words being used as object literal keys.
|
||||
"no-reserved-keys": 0,
|
||||
// Don't restrict usage of specified node modules (not a node environment).
|
||||
"no-restricted-modules": 0,
|
||||
// Disallow use of assignment in return statement. It is preferable for a
|
||||
// single line of code to have only one easily predictable effect.
|
||||
"no-return-assign": 2,
|
||||
// Allow use of javascript: urls.
|
||||
"no-script-url": 0,
|
||||
// Disallow comparisons where both sides are exactly the same.
|
||||
"no-self-compare": 2,
|
||||
// Disallow use of comma operator.
|
||||
"no-sequences": 2,
|
||||
// Warn about declaration of variables already declared in the outer scope.
|
||||
// This isn't an error because it sometimes is useful to use the same name
|
||||
// in a small helper function rather than having to come up with another
|
||||
// random name.
|
||||
// Still, making this a warning can help people avoid being confused.
|
||||
"no-shadow": 0,
|
||||
// Require empty line at end of file
|
||||
"eol-last": "error",
|
||||
// Disallow shadowing of names such as arguments.
|
||||
"no-shadow-restricted-names": 2,
|
||||
"no-space-before-semi": 0,
|
||||
// Disallow sparse arrays, eg. let arr = [,,2].
|
||||
// Array destructuring is fine though:
|
||||
// for (let [, breakpointPromise] of aPromises)
|
||||
"no-sparse-arrays": 2,
|
||||
// Allow use of synchronous methods (not a node environment).
|
||||
"no-sync": 0,
|
||||
// Allow the use of ternary operators.
|
||||
"no-ternary": 0,
|
||||
// Don't allow spaces after end of line
|
||||
"no-trailing-spaces": "error",
|
||||
// Disallow throwing literals (eg. throw "error" instead of
|
||||
// throw new Error("error")).
|
||||
"no-throw-literal": 2,
|
||||
// Disallow use of undeclared variables unless mentioned in a /*global */
|
||||
// block. Note that globals from head.js are automatically imported in tests
|
||||
// by the import-headjs-globals rule form the mozilla eslint plugin.
|
||||
"no-undef": 2,
|
||||
// Allow use of undefined variable.
|
||||
"no-undefined": 0,
|
||||
// Disallow the use of Boolean literals in conditional expressions.
|
||||
"no-unneeded-ternary": 2,
|
||||
// Disallow unreachable statements after a return, throw, continue, or break
|
||||
// statement.
|
||||
"no-unreachable": 2,
|
||||
// Allow using variables before they are defined.
|
||||
"no-unused-vars": [2, {"vars": "all", "args": "none"}],
|
||||
// Disallow global and local variables that arent used, but allow unused function arguments.
|
||||
"no-use-before-define": 0,
|
||||
// We use var-only-at-top-level instead of no-var as we allow top level
|
||||
// vars.
|
||||
"no-var": 0,
|
||||
// Allow using TODO/FIXME comments.
|
||||
"no-warning-comments": 0,
|
||||
// Disallow use of the with statement.
|
||||
"no-with": 2,
|
||||
// Dont require method and property shorthand syntax for object literals.
|
||||
// We use this in the code a lot, but not consistently, and this seems more
|
||||
// like something to check at code review time.
|
||||
"object-shorthand": 0,
|
||||
// Allow more than one variable declaration per function.
|
||||
"one-var": 0,
|
||||
// Single quotes should be used.
|
||||
"quotes": [2, "single", "avoid-escape"],
|
||||
// Require use of the second argument for parseInt().
|
||||
"radix": 2,
|
||||
// Dont require to sort variables within the same declaration block.
|
||||
// Anyway, one-var is disabled.
|
||||
"sort-vars": 0,
|
||||
"space-after-function-name": 0,
|
||||
"space-before-function-parentheses": 0,
|
||||
// Disallow space before function opening parenthesis.
|
||||
//"space-before-function-paren": [2, "never"],
|
||||
// Disable the rule that checks if spaces inside {} and [] are there or not.
|
||||
// Our code is split on conventions, and itd be nice to have 2 rules
|
||||
// instead, one for [] and one for {}. So, disabling until we write them.
|
||||
"space-in-brackets": 0,
|
||||
// Deprecated, will be removed in 1.0.
|
||||
"space-unary-word-ops": 0,
|
||||
// Require a space immediately following the // in a line comment.
|
||||
"spaced-comment": [2, "always"],
|
||||
// Require "use strict" to be defined globally in the script.
|
||||
"strict": [2, "global"],
|
||||
// Disallow comparisons with the value NaN.
|
||||
"use-isnan": 2,
|
||||
// Warn about invalid JSDoc comments.
|
||||
// Disabled for now because of https://github.com/eslint/eslint/issues/2270
|
||||
// The rule fails on some jsdoc comments like in:
|
||||
// devtools/client/webconsole/console-output.js
|
||||
"valid-jsdoc": 0,
|
||||
// Ensure that the results of typeof are compared against a valid string.
|
||||
"valid-typeof": 2,
|
||||
// Allow vars to be declared anywhere in the scope.
|
||||
"vars-on-top": 0,
|
||||
// Dont require immediate function invocation to be wrapped in parentheses.
|
||||
"wrap-iife": 0,
|
||||
// Don't require regex literals to be wrapped in parentheses (which
|
||||
// supposedly prevent them from being mistaken for division operators).
|
||||
"wrap-regex": 0,
|
||||
// Require for-in loops to have an if statement.
|
||||
"guard-for-in": 0,
|
||||
// allow/disallow an empty newline after var statement
|
||||
"newline-after-var": 0,
|
||||
// disallow the use of alert, confirm, and prompt
|
||||
"no-alert": 0,
|
||||
// disallow the use of deprecated react changes and lifecycle methods
|
||||
"react/no-deprecated": 0,
|
||||
// disallow comparisons to null without a type-checking operator
|
||||
"no-eq-null": 0,
|
||||
// disallow overwriting functions written as function declarations
|
||||
"no-func-assign": 0,
|
||||
// disallow use of eval()-like methods
|
||||
"no-implied-eval": 0,
|
||||
// disallow function or variable declarations in nested blocks
|
||||
"no-inner-declarations": 0,
|
||||
// disallow invalid regular expression strings in the RegExp constructor
|
||||
"no-invalid-regexp": 0,
|
||||
// disallow irregular whitespace outside of strings and comments
|
||||
"no-irregular-whitespace": 0,
|
||||
// disallow unnecessary nested blocks
|
||||
"no-lone-blocks": 0,
|
||||
// disallow creation of functions within loops
|
||||
"no-loop-func": 0,
|
||||
// disallow use of new operator when not part of the assignment or
|
||||
// comparison
|
||||
"no-new": 0,
|
||||
// disallow use of new operator for Function object
|
||||
"no-new-func": 0,
|
||||
// disallow use of the Object constructor
|
||||
"no-new-object": 0,
|
||||
// disallows creating new instances of String,Number, and Boolean
|
||||
"no-new-wrappers": 0,
|
||||
// disallow the use of object properties of the global object (Math and
|
||||
// JSON) as functions
|
||||
"no-obj-calls": 0,
|
||||
// disallow use of octal escape sequences in string literals, such as
|
||||
// var foo = "Copyright \251";
|
||||
"no-octal-escape": 0,
|
||||
// disallow use of undefined when initializing variables
|
||||
"no-undef-init": 0,
|
||||
// disallow usage of expressions in statement position
|
||||
"no-unused-expressions": 0,
|
||||
// disallow use of void operator
|
||||
"no-void": 0,
|
||||
// disallow wrapping of non-IIFE statements in parens
|
||||
"no-wrap-func": 0,
|
||||
// require assignment operator shorthand where possible or prohibit it
|
||||
// entirely
|
||||
"operator-assignment": 0,
|
||||
// enforce operators to be placed before or after line breaks
|
||||
"operator-linebreak": 0,
|
||||
// disable chacking prop types
|
||||
"react/prop-types": 0
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,5 @@
|
||||
{
|
||||
"singleQuote": true,
|
||||
"trailingComma": "all",
|
||||
"parser": "flow"
|
||||
}
|
||||
@ -16,14 +16,13 @@
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
module.exports = function (api) {
|
||||
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,
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
@ -13,8 +13,10 @@
|
||||
"acorn": "^6.2.0",
|
||||
"antd": "^3.23.6",
|
||||
"axios": "^0.18.1",
|
||||
"babel-eslint": "^9.0.0",
|
||||
"d3": "^5.9.7",
|
||||
"dagre": "^0.8.4",
|
||||
"imagemin": "^5.3.1",
|
||||
"javascript-time-ago": "^2.0.1",
|
||||
"keymirror": "^0.1.1",
|
||||
"lodash.debounce": "^4.0.8",
|
||||
@ -43,6 +45,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",
|
||||
@ -61,6 +69,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",
|
||||
@ -81,6 +90,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\""
|
||||
}
|
||||
}
|
||||
|
||||
@ -16,140 +16,147 @@
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
import "antd/dist/antd.less";
|
||||
import RouteWithSubRoutes from "./components/RouteWithSubRoutes";
|
||||
import {
|
||||
BrowserRouter as Router,
|
||||
Redirect, Switch,
|
||||
} from 'react-router-dom';
|
||||
import axios from "axios";
|
||||
import {Layout, Spin, Result} from "antd";
|
||||
import ConfigContext from "./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 { Content } = Layout;
|
||||
const loadingView = (
|
||||
<Layout>
|
||||
<Content style={{
|
||||
padding: '0 0',
|
||||
paddingTop: 300,
|
||||
backgroundColor: '#fff',
|
||||
textAlign: 'center'
|
||||
}}>
|
||||
<Spin tip="Loading..."/>
|
||||
</Content>
|
||||
</Layout>
|
||||
<Layout>
|
||||
<Content
|
||||
style={{
|
||||
padding: '0 0',
|
||||
paddingTop: 300,
|
||||
backgroundColor: '#fff',
|
||||
textAlign: 'center',
|
||||
}}
|
||||
>
|
||||
<Spin tip="Loading..." />
|
||||
</Content>
|
||||
</Layout>
|
||||
);
|
||||
|
||||
const errorView = (
|
||||
<Result
|
||||
style={{
|
||||
paddingTop: 200
|
||||
}}
|
||||
status="500"
|
||||
title="Error occurred while loading the configuration"
|
||||
subTitle="Please refresh your browser window"
|
||||
/>
|
||||
<Result
|
||||
style={{
|
||||
paddingTop: 200,
|
||||
}}
|
||||
status="500"
|
||||
title="Error occurred while loading the configuration"
|
||||
subTitle="Please refresh your browser window"
|
||||
/>
|
||||
);
|
||||
|
||||
class App extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
loading: true,
|
||||
error: false,
|
||||
config: {},
|
||||
};
|
||||
}
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
loading: true,
|
||||
error: false,
|
||||
config: {}
|
||||
componentDidMount() {
|
||||
this.updateFavicon();
|
||||
axios
|
||||
.get(window.location.origin + '/store/public/conf/config.json')
|
||||
.then(res => {
|
||||
const config = res.data;
|
||||
this.checkUserLoggedIn(config);
|
||||
})
|
||||
.catch(error => {
|
||||
this.setState({
|
||||
loading: false,
|
||||
error: true,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
updateFavicon = () => {
|
||||
const link =
|
||||
document.querySelector("link[rel*='icon']") ||
|
||||
document.createElement('link');
|
||||
link.type = 'image/x-icon';
|
||||
link.rel = 'shortcut icon';
|
||||
link.href =
|
||||
window.location.origin +
|
||||
'/devicemgt/public/uuf.unit.favicon/img/favicon.png';
|
||||
document.getElementsByTagName('head')[0].appendChild(link);
|
||||
};
|
||||
|
||||
checkUserLoggedIn = config => {
|
||||
axios
|
||||
.post(
|
||||
window.location.origin + '/store-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 + '/store/';
|
||||
} else {
|
||||
this.setState({
|
||||
loading: false,
|
||||
config: config,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.updateFavicon();
|
||||
axios.get(
|
||||
window.location.origin + "/store/public/conf/config.json",
|
||||
).then(res => {
|
||||
const config = res.data;
|
||||
this.checkUserLoggedIn(config);
|
||||
}).catch((error) => {
|
||||
})
|
||||
.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 + `/store/login?redirect=${redirectUrl}`;
|
||||
} else {
|
||||
this.setState({
|
||||
loading: false,
|
||||
error: true
|
||||
})
|
||||
});
|
||||
}
|
||||
loading: false,
|
||||
config: config,
|
||||
});
|
||||
}
|
||||
} else {
|
||||
this.setState({
|
||||
loading: false,
|
||||
error: true,
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
updateFavicon = () =>{
|
||||
const link = document.querySelector("link[rel*='icon']") || document.createElement('link');
|
||||
link.type = 'image/x-icon';
|
||||
link.rel = 'shortcut icon';
|
||||
link.href = window.location.origin+'/devicemgt/public/uuf.unit.favicon/img/favicon.png';
|
||||
document.getElementsByTagName('head')[0].appendChild(link);
|
||||
};
|
||||
render() {
|
||||
const { loading, error } = this.state;
|
||||
|
||||
checkUserLoggedIn = (config) => {
|
||||
axios.post(
|
||||
window.location.origin + "/store-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 + `/store/`;
|
||||
} else {
|
||||
this.setState({
|
||||
loading: false,
|
||||
config: config
|
||||
});
|
||||
}
|
||||
}).catch((error) => {
|
||||
if (error.hasOwnProperty("response") && error.response.status === 401) {
|
||||
const redirectUrl = encodeURI(window.location.href);
|
||||
const pageURL = window.location.pathname;
|
||||
const lastURLSegment = pageURL.substr(pageURL.lastIndexOf('/') + 1);
|
||||
if (lastURLSegment !== "login") {
|
||||
window.location.href = window.location.origin + `/store/login?redirect=${redirectUrl}`;
|
||||
} else {
|
||||
this.setState({
|
||||
loading: false,
|
||||
config: config
|
||||
})
|
||||
}
|
||||
} else {
|
||||
this.setState({
|
||||
loading: false,
|
||||
error: true
|
||||
})
|
||||
}
|
||||
});
|
||||
};
|
||||
const applicationView = (
|
||||
<Router>
|
||||
<ConfigContext.Provider value={this.state.config}>
|
||||
<div>
|
||||
<Switch>
|
||||
<Redirect exact from="/store" to="/store/android" />
|
||||
{this.props.routes.map(route => (
|
||||
<RouteWithSubRoutes key={route.path} {...route} />
|
||||
))}
|
||||
</Switch>
|
||||
</div>
|
||||
</ConfigContext.Provider>
|
||||
</Router>
|
||||
);
|
||||
|
||||
render() {
|
||||
const {loading, error} = this.state;
|
||||
|
||||
const applicationView = (
|
||||
<Router>
|
||||
<ConfigContext.Provider value={this.state.config}>
|
||||
<div>
|
||||
<Switch>
|
||||
<Redirect exact from="/store" to="/store/android"/>
|
||||
{this.props.routes.map((route) => (
|
||||
<RouteWithSubRoutes key={route.path} {...route} />
|
||||
))}
|
||||
</Switch>
|
||||
</div>
|
||||
</ConfigContext.Provider>
|
||||
</Router>
|
||||
);
|
||||
|
||||
return (
|
||||
<div>
|
||||
{loading && loadingView}
|
||||
{!loading && !error && applicationView}
|
||||
{error && errorView}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<div>
|
||||
{loading && loadingView}
|
||||
{!loading && !error && applicationView}
|
||||
{error && errorView}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default App;
|
||||
|
||||
@ -17,21 +17,28 @@
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import {Route} from 'react-router-dom';
|
||||
class RouteWithSubRoutes extends React.Component{
|
||||
props;
|
||||
constructor(props){
|
||||
super(props);
|
||||
this.props = props;
|
||||
}
|
||||
render() {
|
||||
return(
|
||||
<Route path={this.props.path} exact={this.props.exact} render={(props) => (
|
||||
<this.props.component {...props} {...this.props} routes={this.props.routes}/>
|
||||
)}/>
|
||||
);
|
||||
}
|
||||
|
||||
import { Route } from 'react-router-dom';
|
||||
class RouteWithSubRoutes extends React.Component {
|
||||
props;
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.props = props;
|
||||
}
|
||||
render() {
|
||||
return (
|
||||
<Route
|
||||
path={this.props.path}
|
||||
exact={this.props.exact}
|
||||
render={props => (
|
||||
<this.props.component
|
||||
{...props}
|
||||
{...this.props}
|
||||
routes={this.props.routes}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default RouteWithSubRoutes;
|
||||
export default RouteWithSubRoutes;
|
||||
|
||||
@ -53,4 +53,4 @@
|
||||
overflow: hidden;
|
||||
width: 100%;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
}
|
||||
|
||||
@ -16,63 +16,66 @@
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import {Card, Typography, Col, Row} from 'antd';
|
||||
import React from "react";
|
||||
import {Link} from "react-router-dom";
|
||||
import "./AppCard.css";
|
||||
import { Card, Typography, Col, Row } from 'antd';
|
||||
import React from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import './AppCard.css';
|
||||
import StarRatings from 'react-star-ratings';
|
||||
|
||||
const {Meta} = Card;
|
||||
const {Text} = Typography;
|
||||
const { Meta } = Card;
|
||||
const { Text } = Typography;
|
||||
|
||||
class AppCard extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
}
|
||||
render() {
|
||||
const app = this.props.app;
|
||||
const release = this.props.app.applicationReleases[0];
|
||||
|
||||
render() {
|
||||
const app = this.props.app;
|
||||
const release = this.props.app.applicationReleases[0];
|
||||
const description = (
|
||||
<div className="appCard">
|
||||
<Link to={'/store/' + app.deviceType + '/apps/' + release.uuid}>
|
||||
<Row className="release">
|
||||
<Col span={24} className="release-icon">
|
||||
<div className="box">
|
||||
<div className="content">
|
||||
<img className="app-icon" src={release.iconPath} alt="icon" />
|
||||
</div>
|
||||
</div>
|
||||
{/* <img src={release.iconPath} alt="icon"/>*/}
|
||||
{/* <Avatar shape="square" size={128} src={release.iconPath} />*/}
|
||||
</Col>
|
||||
<Col span={24} style={{ paddingTop: 10 }}>
|
||||
<Text className="app-name" strong level={4}>
|
||||
{app.name}
|
||||
</Text>
|
||||
<br />
|
||||
<Text type="secondary" level={4}>
|
||||
{app.type.toLowerCase()}
|
||||
</Text>
|
||||
<br />
|
||||
<StarRatings
|
||||
rating={app.rating}
|
||||
starRatedColor="#777"
|
||||
starDimension="12px"
|
||||
starSpacing="0"
|
||||
numberOfStars={5}
|
||||
name="rating"
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
</Link>
|
||||
</div>
|
||||
);
|
||||
|
||||
const description = (
|
||||
<div className="appCard">
|
||||
<Link to={"/store/"+app.deviceType+"/apps/" + release.uuid}>
|
||||
<Row className="release">
|
||||
<Col span={24} className="release-icon">
|
||||
<div className='box'>
|
||||
<div className='content'>
|
||||
<img className='app-icon' src={release.iconPath} alt="icon"/>
|
||||
</div>
|
||||
</div>
|
||||
{/*<img src={release.iconPath} alt="icon"/>*/}
|
||||
{/*<Avatar shape="square" size={128} src={release.iconPath} />*/}
|
||||
</Col>
|
||||
<Col span={24} style={{paddingTop:10}}>
|
||||
<Text className="app-name" strong level={4}>{app.name}</Text><br/>
|
||||
<Text type="secondary" level={4}>{app.type.toLowerCase()}</Text><br/>
|
||||
<StarRatings
|
||||
rating={app.rating}
|
||||
starRatedColor="#777"
|
||||
starDimension = "12px"
|
||||
starSpacing = "0"
|
||||
numberOfStars={5}
|
||||
name='rating'
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
</Link>
|
||||
</div>
|
||||
);
|
||||
|
||||
return (
|
||||
<Card style={{marginTop: 16}}>
|
||||
<Meta
|
||||
description={description}
|
||||
/>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<Card style={{ marginTop: 16 }}>
|
||||
<Meta description={description} />
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default AppCard;
|
||||
export default AppCard;
|
||||
|
||||
@ -16,163 +16,172 @@
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
import AppCard from "./AppCard";
|
||||
import {Col, Row, Result} from "antd";
|
||||
import axios from "axios";
|
||||
import {withConfigContext} from "../../context/ConfigContext";
|
||||
import {handleApiError} from "../../js/Utils";
|
||||
import InfiniteScroll from "react-infinite-scroller";
|
||||
import React from 'react';
|
||||
import AppCard from './AppCard';
|
||||
import { Col, Row, Result } from 'antd';
|
||||
import axios from 'axios';
|
||||
import { withConfigContext } from '../../context/ConfigContext';
|
||||
import { handleApiError } from '../../js/Utils';
|
||||
import InfiniteScroll from 'react-infinite-scroller';
|
||||
|
||||
const limit = 30;
|
||||
|
||||
class AppList extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
apps: [],
|
||||
loading: true,
|
||||
hasMore: true,
|
||||
loadMore: true,
|
||||
forbiddenErrors: {
|
||||
apps: false
|
||||
},
|
||||
totalAppCount: 0
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
const {deviceType} = this.props;
|
||||
this.props.changeSelectedMenuItem(deviceType);
|
||||
this.fetchData(0, 30, res => {
|
||||
this.setState({
|
||||
apps: res,
|
||||
loading: false
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
componentDidUpdate(prevProps, prevState) {
|
||||
if (prevProps.deviceType !== this.props.deviceType) {
|
||||
const {deviceType} = this.props;
|
||||
this.props.changeSelectedMenuItem(deviceType);
|
||||
this.fetchData(0, 30, res => {
|
||||
this.setState({
|
||||
apps: res,
|
||||
loading: false,
|
||||
hasMore: true
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
fetchData = (offset, limit, callbackFunction) => {
|
||||
const {deviceType} = this.props;
|
||||
const config = this.props.context;
|
||||
const payload = {
|
||||
offset,
|
||||
limit
|
||||
};
|
||||
if (deviceType === "web-clip") {
|
||||
payload.appType = "WEB_CLIP";
|
||||
} else {
|
||||
payload.deviceType = deviceType;
|
||||
}
|
||||
|
||||
this.setState({
|
||||
loading: true
|
||||
});
|
||||
//send request to the invoker
|
||||
axios.post(
|
||||
window.location.origin + config.serverConfig.invoker.uri + config.serverConfig.invoker.store + "/applications/",
|
||||
payload,
|
||||
).then(res => {
|
||||
if (res.status === 200) {
|
||||
//todo remove this property check after backend improvement
|
||||
let apps = (res.data.data.hasOwnProperty("applications")) ? res.data.data.applications : [];
|
||||
callbackFunction(apps);
|
||||
}
|
||||
|
||||
}).catch((error) => {
|
||||
handleApiError(error, "Error occurred while trying to load apps.", true);
|
||||
if (error.hasOwnProperty("response") && error.response.status === 403) {
|
||||
const {forbiddenErrors} = this.state;
|
||||
forbiddenErrors.apps = true;
|
||||
this.setState({
|
||||
forbiddenErrors,
|
||||
loading: false
|
||||
})
|
||||
} else {
|
||||
this.setState({
|
||||
loading: false
|
||||
});
|
||||
}
|
||||
});
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
apps: [],
|
||||
loading: true,
|
||||
hasMore: true,
|
||||
loadMore: true,
|
||||
forbiddenErrors: {
|
||||
apps: false,
|
||||
},
|
||||
totalAppCount: 0,
|
||||
};
|
||||
}
|
||||
|
||||
handleInfiniteOnLoad = (count) => {
|
||||
const offset = count * limit;
|
||||
let apps = this.state.apps;
|
||||
componentDidMount() {
|
||||
const { deviceType } = this.props;
|
||||
this.props.changeSelectedMenuItem(deviceType);
|
||||
this.fetchData(0, 30, res => {
|
||||
this.setState({
|
||||
apps: res,
|
||||
loading: false,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps, prevState) {
|
||||
if (prevProps.deviceType !== this.props.deviceType) {
|
||||
const { deviceType } = this.props;
|
||||
this.props.changeSelectedMenuItem(deviceType);
|
||||
this.fetchData(0, 30, res => {
|
||||
this.setState({
|
||||
loading: true,
|
||||
apps: res,
|
||||
loading: false,
|
||||
hasMore: true,
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
this.fetchData(offset, limit, res => {
|
||||
if (res.length > 0) {
|
||||
apps = apps.concat(res);
|
||||
this.setState({
|
||||
apps,
|
||||
loading: false,
|
||||
});
|
||||
} else {
|
||||
this.setState({
|
||||
hasMore: false,
|
||||
loading: false
|
||||
});
|
||||
}
|
||||
});
|
||||
fetchData = (offset, limit, callbackFunction) => {
|
||||
const { deviceType } = this.props;
|
||||
const config = this.props.context;
|
||||
const payload = {
|
||||
offset,
|
||||
limit,
|
||||
};
|
||||
if (deviceType === 'web-clip') {
|
||||
payload.appType = 'WEB_CLIP';
|
||||
} else {
|
||||
payload.deviceType = deviceType;
|
||||
}
|
||||
|
||||
render() {
|
||||
const {apps, loading, forbiddenErrors, hasMore} = this.state;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<InfiniteScroll
|
||||
key={this.props.deviceType}
|
||||
initialLoad={false}
|
||||
pageStart={0}
|
||||
loadMore={this.handleInfiniteOnLoad}
|
||||
hasMore={!loading && hasMore}
|
||||
useWindow={true}>
|
||||
<Row gutter={16}>
|
||||
{(forbiddenErrors.apps) && (
|
||||
<Result
|
||||
status="403"
|
||||
title="403"
|
||||
subTitle="You don't have permission to view apps."
|
||||
/>
|
||||
)}
|
||||
{!(forbiddenErrors.apps) && apps.length === 0 && (
|
||||
<Result
|
||||
status="404"
|
||||
title="No apps, yet."
|
||||
subTitle="No apps available, yet! When the administration uploads, apps will show up here."
|
||||
/>
|
||||
)}
|
||||
{apps.map(app => (
|
||||
<Col key={app.id} xs={12} sm={6} md={6} lg={4} xl={3}>
|
||||
<AppCard key={app.id}
|
||||
app={app}
|
||||
/>
|
||||
</Col>
|
||||
))}
|
||||
</Row>
|
||||
</InfiniteScroll>
|
||||
</div>
|
||||
this.setState({
|
||||
loading: true,
|
||||
});
|
||||
// send request to the invoker
|
||||
axios
|
||||
.post(
|
||||
window.location.origin +
|
||||
config.serverConfig.invoker.uri +
|
||||
config.serverConfig.invoker.store +
|
||||
'/applications/',
|
||||
payload,
|
||||
)
|
||||
.then(res => {
|
||||
if (res.status === 200) {
|
||||
// todo remove this property check after backend improvement
|
||||
let apps = res.data.data.hasOwnProperty('applications')
|
||||
? res.data.data.applications
|
||||
: [];
|
||||
callbackFunction(apps);
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
handleApiError(
|
||||
error,
|
||||
'Error occurred while trying to load apps.',
|
||||
true,
|
||||
);
|
||||
}
|
||||
if (error.hasOwnProperty('response') && error.response.status === 403) {
|
||||
const { forbiddenErrors } = this.state;
|
||||
forbiddenErrors.apps = true;
|
||||
this.setState({
|
||||
forbiddenErrors,
|
||||
loading: false,
|
||||
});
|
||||
} else {
|
||||
this.setState({
|
||||
loading: false,
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
handleInfiniteOnLoad = count => {
|
||||
const offset = count * limit;
|
||||
let apps = this.state.apps;
|
||||
this.setState({
|
||||
loading: true,
|
||||
});
|
||||
|
||||
this.fetchData(offset, limit, res => {
|
||||
if (res.length > 0) {
|
||||
apps = apps.concat(res);
|
||||
this.setState({
|
||||
apps,
|
||||
loading: false,
|
||||
});
|
||||
} else {
|
||||
this.setState({
|
||||
hasMore: false,
|
||||
loading: false,
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
render() {
|
||||
const { apps, loading, forbiddenErrors, hasMore } = this.state;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<InfiniteScroll
|
||||
key={this.props.deviceType}
|
||||
initialLoad={false}
|
||||
pageStart={0}
|
||||
loadMore={this.handleInfiniteOnLoad}
|
||||
hasMore={!loading && hasMore}
|
||||
useWindow={true}
|
||||
>
|
||||
<Row gutter={16}>
|
||||
{forbiddenErrors.apps && (
|
||||
<Result
|
||||
status="403"
|
||||
title="403"
|
||||
subTitle="You don't have permission to view apps."
|
||||
/>
|
||||
)}
|
||||
{!forbiddenErrors.apps && apps.length === 0 && (
|
||||
<Result
|
||||
status="404"
|
||||
title="No apps, yet."
|
||||
subTitle="No apps available, yet! When the administration uploads, apps will show up here."
|
||||
/>
|
||||
)}
|
||||
{apps.map(app => (
|
||||
<Col key={app.id} xs={12} sm={6} md={6} lg={4} xl={3}>
|
||||
<AppCard key={app.id} app={app} />
|
||||
</Col>
|
||||
))}
|
||||
</Row>
|
||||
</InfiniteScroll>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default withConfigContext(AppList);
|
||||
export default withConfigContext(AppList);
|
||||
|
||||
@ -87,4 +87,4 @@
|
||||
|
||||
.d-rating .numeric-data .people-count{
|
||||
padding-top: 6px;
|
||||
}
|
||||
}
|
||||
|
||||
@ -16,87 +16,110 @@
|
||||
* 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 { withConfigContext } from '../../../context/ConfigContext';
|
||||
|
||||
const { Text } = Typography;
|
||||
|
||||
class DetailedRating extends React.Component {
|
||||
render() {
|
||||
const { detailedRating } = this.props;
|
||||
|
||||
class DetailedRating extends React.Component{
|
||||
|
||||
|
||||
render() {
|
||||
const {detailedRating} = this.props;
|
||||
|
||||
if(detailedRating ==null){
|
||||
return null;
|
||||
}
|
||||
|
||||
const totalCount = detailedRating.noOfUsers;
|
||||
const ratingVariety = detailedRating.ratingVariety;
|
||||
|
||||
const ratingArray = [];
|
||||
|
||||
for (let [key, value] of Object.entries(ratingVariety)) {
|
||||
ratingArray.push(value);
|
||||
}
|
||||
|
||||
const maximumRating = Math.max(...ratingArray);
|
||||
|
||||
const ratingBarPercentages = [0,0,0,0,0];
|
||||
|
||||
if(maximumRating>0){
|
||||
for(let i = 0; i<5; i++){
|
||||
ratingBarPercentages[i] = (ratingVariety[(i+1).toString()])/maximumRating*100;
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<Row className="d-rating">
|
||||
<div className="numeric-data">
|
||||
<div className="rate">{detailedRating.ratingValue.toFixed(1)}</div>
|
||||
<StarRatings
|
||||
rating={detailedRating.ratingValue}
|
||||
starRatedColor="#777"
|
||||
starDimension = "16px"
|
||||
starSpacing = "2px"
|
||||
numberOfStars={5}
|
||||
name='rating'
|
||||
/>
|
||||
<br/>
|
||||
<Text type="secondary" className="people-count"><Icon type="team" /> {totalCount} total</Text>
|
||||
</div>
|
||||
<div className="bar-containers">
|
||||
<div className="bar-container">
|
||||
<span className="number">5</span>
|
||||
<span className="bar rate-5" style={{width: ratingBarPercentages[4]+"%"}}> </span>
|
||||
</div>
|
||||
<div className="bar-container">
|
||||
<span className="number">4</span>
|
||||
<span className="bar rate-4" style={{width: ratingBarPercentages[3]+"%"}}> </span>
|
||||
</div>
|
||||
<div className="bar-container">
|
||||
<span className="number">3</span>
|
||||
<span className="bar rate-3" style={{width: ratingBarPercentages[2]+"%"}}> </span>
|
||||
</div>
|
||||
<div className="bar-container">
|
||||
<span className="number">2</span>
|
||||
<span className="bar rate-2" style={{width: ratingBarPercentages[1]+"%"}}> </span>
|
||||
</div>
|
||||
<div className="bar-container">
|
||||
<span className="number">1</span>
|
||||
<span className="bar rate-1" style={{width: ratingBarPercentages[0]+"%"}}> </span>
|
||||
</div>
|
||||
</div>
|
||||
</Row>
|
||||
);
|
||||
if (detailedRating == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const totalCount = detailedRating.noOfUsers;
|
||||
const ratingVariety = detailedRating.ratingVariety;
|
||||
|
||||
const ratingArray = [];
|
||||
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
for (let [key, value] of Object.entries(ratingVariety)) {
|
||||
ratingArray.push(value);
|
||||
}
|
||||
|
||||
const maximumRating = Math.max(...ratingArray);
|
||||
|
||||
const ratingBarPercentages = [0, 0, 0, 0, 0];
|
||||
|
||||
if (maximumRating > 0) {
|
||||
for (let i = 0; i < 5; i++) {
|
||||
ratingBarPercentages[i] =
|
||||
(ratingVariety[(i + 1).toString()] / maximumRating) * 100;
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<Row className="d-rating">
|
||||
<div className="numeric-data">
|
||||
<div className="rate">{detailedRating.ratingValue.toFixed(1)}</div>
|
||||
<StarRatings
|
||||
rating={detailedRating.ratingValue}
|
||||
starRatedColor="#777"
|
||||
starDimension="16px"
|
||||
starSpacing="2px"
|
||||
numberOfStars={5}
|
||||
name="rating"
|
||||
/>
|
||||
<br />
|
||||
<Text type="secondary" className="people-count">
|
||||
<Icon type="team" /> {totalCount} total
|
||||
</Text>
|
||||
</div>
|
||||
<div className="bar-containers">
|
||||
<div className="bar-container">
|
||||
<span className="number">5</span>
|
||||
<span
|
||||
className="bar rate-5"
|
||||
style={{ width: ratingBarPercentages[4] + '%' }}
|
||||
>
|
||||
{' '}
|
||||
</span>
|
||||
</div>
|
||||
<div className="bar-container">
|
||||
<span className="number">4</span>
|
||||
<span
|
||||
className="bar rate-4"
|
||||
style={{ width: ratingBarPercentages[3] + '%' }}
|
||||
>
|
||||
{' '}
|
||||
</span>
|
||||
</div>
|
||||
<div className="bar-container">
|
||||
<span className="number">3</span>
|
||||
<span
|
||||
className="bar rate-3"
|
||||
style={{ width: ratingBarPercentages[2] + '%' }}
|
||||
>
|
||||
{' '}
|
||||
</span>
|
||||
</div>
|
||||
<div className="bar-container">
|
||||
<span className="number">2</span>
|
||||
<span
|
||||
className="bar rate-2"
|
||||
style={{ width: ratingBarPercentages[1] + '%' }}
|
||||
>
|
||||
{' '}
|
||||
</span>
|
||||
</div>
|
||||
<div className="bar-container">
|
||||
<span className="number">1</span>
|
||||
<span
|
||||
className="bar rate-1"
|
||||
style={{ width: ratingBarPercentages[0] + '%' }}
|
||||
>
|
||||
{' '}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</Row>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export default withConfigContext(DetailedRating);
|
||||
export default withConfigContext(DetailedRating);
|
||||
|
||||
@ -16,244 +16,279 @@
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
import {Divider, Row, Col, Typography, Button, Dropdown, notification, Menu, Icon, Spin, Tabs, Tag} from "antd";
|
||||
import "../../../App.css";
|
||||
import ImgViewer from "../../apps/release/images/ImgViewer";
|
||||
import StarRatings from "react-star-ratings";
|
||||
import axios from "axios";
|
||||
import pSBC from "shade-blend-color";
|
||||
import AppInstallModal from "./install/AppInstallModal";
|
||||
import AppUninstallModal from "./install/AppUninstallModal";
|
||||
import {withConfigContext} from "../../../context/ConfigContext";
|
||||
import {handleApiError} from "../../../js/Utils";
|
||||
import ReviewContainer from "./review/ReviewContainer";
|
||||
import SubscriptionDetails from "./SubscriptionDetails";
|
||||
import React from 'react';
|
||||
import {
|
||||
Divider,
|
||||
Row,
|
||||
Col,
|
||||
Typography,
|
||||
Button,
|
||||
Dropdown,
|
||||
notification,
|
||||
Menu,
|
||||
Icon,
|
||||
Tabs,
|
||||
Tag,
|
||||
} from 'antd';
|
||||
import '../../../App.css';
|
||||
import ImgViewer from '../../apps/release/images/ImgViewer';
|
||||
import StarRatings from 'react-star-ratings';
|
||||
import axios from 'axios';
|
||||
import pSBC from 'shade-blend-color';
|
||||
import AppInstallModal from './install/AppInstallModal';
|
||||
import AppUninstallModal from './install/AppUninstallModal';
|
||||
import { withConfigContext } from '../../../context/ConfigContext';
|
||||
import { handleApiError } from '../../../js/Utils';
|
||||
import ReviewContainer from './review/ReviewContainer';
|
||||
import SubscriptionDetails from './SubscriptionDetails';
|
||||
|
||||
const {Title, Text, Paragraph} = Typography;
|
||||
const {TabPane} = Tabs;
|
||||
const { Title, Text, Paragraph } = Typography;
|
||||
const { TabPane } = Tabs;
|
||||
|
||||
class ReleaseView extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
loading: false,
|
||||
appInstallModalVisible: false,
|
||||
appUninstallModalVisible: false,
|
||||
};
|
||||
}
|
||||
|
||||
appOperation = (type, payload, operation, timestamp = null) => {
|
||||
const config = this.props.context;
|
||||
const release = this.props.app.applicationReleases[0];
|
||||
const { uuid } = release;
|
||||
const { isAndroidEnterpriseApp } = this.props.app;
|
||||
|
||||
this.setState({
|
||||
loading: true,
|
||||
});
|
||||
const parameters = {};
|
||||
|
||||
let url =
|
||||
window.location.origin +
|
||||
config.serverConfig.invoker.uri +
|
||||
config.serverConfig.invoker.store +
|
||||
'/subscription/' +
|
||||
uuid +
|
||||
'/' +
|
||||
type +
|
||||
'/';
|
||||
if (isAndroidEnterpriseApp) {
|
||||
url += 'ent-app-install/'; // add ent-app-install path param for android enterprise app
|
||||
parameters.requiresUpdatingExternal = true;
|
||||
}
|
||||
url += operation; // add operation to url
|
||||
if (timestamp != null) {
|
||||
parameters.timestamp = timestamp; // add timestamp for scheduled operations
|
||||
}
|
||||
|
||||
const queryParams = Object.keys(parameters)
|
||||
.map(key => key + '=' + parameters[key])
|
||||
.join('&');
|
||||
url += '?' + queryParams;
|
||||
|
||||
axios
|
||||
.post(url, payload, {
|
||||
headers: { 'X-Platform': config.serverConfig.platform },
|
||||
})
|
||||
.then(res => {
|
||||
if (res.status === 200 || res.status === 201) {
|
||||
this.setState({
|
||||
loading: false,
|
||||
appInstallModalVisible: false,
|
||||
appUninstallModalVisible: false
|
||||
appUninstallModalVisible: false,
|
||||
});
|
||||
notification.success({
|
||||
message: 'Done!',
|
||||
description: 'Operation triggered.',
|
||||
});
|
||||
} else {
|
||||
this.setState({
|
||||
loading: false,
|
||||
});
|
||||
notification.error({
|
||||
message: 'There was a problem',
|
||||
duration: 0,
|
||||
description: 'Error occurred while ' + operation + 'ing app',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
appOperation = (type, payload, operation, timestamp = null) => {
|
||||
const config = this.props.context;
|
||||
const release = this.props.app.applicationReleases[0];
|
||||
const {uuid} = release;
|
||||
const {isAndroidEnterpriseApp} = this.props.app;
|
||||
|
||||
this.setState({
|
||||
loading: true,
|
||||
});
|
||||
const parameters = {};
|
||||
|
||||
let url = window.location.origin + config.serverConfig.invoker.uri +
|
||||
config.serverConfig.invoker.store + "/subscription/" + uuid + "/" + type + "/";
|
||||
if (isAndroidEnterpriseApp) {
|
||||
url += "ent-app-install/"; // add ent-app-install path param for android enterprise app
|
||||
parameters.requiresUpdatingExternal = true;
|
||||
}
|
||||
url += operation; // add operation to url
|
||||
if (timestamp != null) {
|
||||
parameters.timestamp = timestamp; // add timestamp for scheduled operations
|
||||
}
|
||||
|
||||
const queryParams = Object.keys(parameters).map(key => key + '=' + parameters[key]).join('&');
|
||||
url += '?' + queryParams;
|
||||
|
||||
axios.post(
|
||||
url,
|
||||
payload,
|
||||
{
|
||||
headers: {'X-Platform': config.serverConfig.platform}
|
||||
}
|
||||
).then(res => {
|
||||
if (res.status === 200 || res.status === 201) {
|
||||
this.setState({
|
||||
loading: false,
|
||||
appInstallModalVisible: false,
|
||||
appUninstallModalVisible: false,
|
||||
});
|
||||
notification["success"]({
|
||||
message: 'Done!',
|
||||
description:
|
||||
'Operation triggered.',
|
||||
});
|
||||
} else {
|
||||
this.setState({
|
||||
loading: false
|
||||
});
|
||||
notification["error"]({
|
||||
message: "There was a problem",
|
||||
duration: 0,
|
||||
description:
|
||||
"Error occurred while " + operation + "ing app",
|
||||
});
|
||||
}
|
||||
}).catch((error) => {
|
||||
handleApiError(error, "Error occurred while " + operation + "ing the app.");
|
||||
});
|
||||
};
|
||||
|
||||
closeAppOperationModal = () => {
|
||||
this.setState({
|
||||
appInstallModalVisible: false,
|
||||
appUninstallModalVisible: false
|
||||
});
|
||||
};
|
||||
|
||||
handleSubscribeClick = (e) => {
|
||||
if (e.key === "install") {
|
||||
this.setState({
|
||||
appInstallModalVisible: true // display app install modal
|
||||
})
|
||||
} else if (e.key === "uninstall") {
|
||||
this.setState({
|
||||
appUninstallModalVisible: true // display app uninstall modal
|
||||
})
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
const {app, deviceType} = this.props;
|
||||
const config = this.props.context;
|
||||
const release = app.applicationReleases[0];
|
||||
|
||||
let metaData = [];
|
||||
try {
|
||||
metaData = JSON.parse(release.metaData);
|
||||
} catch (e) {
|
||||
|
||||
}
|
||||
if (app.hasOwnProperty("packageName")) {
|
||||
metaData.push({
|
||||
key: "Package Name",
|
||||
value: app.packageName
|
||||
});
|
||||
}
|
||||
const menu = (
|
||||
<Menu onClick={this.handleSubscribeClick}>
|
||||
<Menu.Item key="install">Install</Menu.Item>
|
||||
<Menu.Item key="uninstall">Uninstall</Menu.Item>
|
||||
</Menu>
|
||||
})
|
||||
.catch(error => {
|
||||
handleApiError(
|
||||
error,
|
||||
'Error occurred while ' + operation + 'ing the app.',
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<AppInstallModal
|
||||
uuid={release.uuid}
|
||||
loading={this.state.loading}
|
||||
visible={this.state.appInstallModalVisible}
|
||||
deviceType={deviceType}
|
||||
onClose={this.closeAppOperationModal}
|
||||
onInstall={this.appOperation}/>
|
||||
<AppUninstallModal
|
||||
uuid={release.uuid}
|
||||
loading={this.state.loading}
|
||||
visible={this.state.appUninstallModalVisible}
|
||||
deviceType={deviceType}
|
||||
onClose={this.closeAppOperationModal}
|
||||
onUninstall={this.appOperation}/>
|
||||
<div className="release">
|
||||
<Row>
|
||||
<Col xl={4} sm={6} xs={8} className="release-icon">
|
||||
<img src={release.iconPath} alt="icon"/>
|
||||
</Col>
|
||||
<Col xl={10} sm={11} className="release-title">
|
||||
<Title level={2}>{app.name}</Title>
|
||||
<Text>Version : {release.version}</Text><br/><br/>
|
||||
<StarRatings
|
||||
rating={app.rating}
|
||||
starRatedColor="#777"
|
||||
starDimension="20px"
|
||||
starSpacing="2px"
|
||||
numberOfStars={5}
|
||||
name='rating'
|
||||
/>
|
||||
</Col>
|
||||
<Col xl={8} md={10} sm={24} xs={24} style={{float: "right"}}>
|
||||
<div style={{
|
||||
textAlign: "right"
|
||||
}}>
|
||||
<Dropdown overlay={menu}>
|
||||
<Button type="primary">
|
||||
Subscribe <Icon type="down"/>
|
||||
</Button>
|
||||
</Dropdown>
|
||||
</div>
|
||||
</Col>
|
||||
</Row>
|
||||
<Divider dashed={true}/>
|
||||
<Tabs>
|
||||
<TabPane tab="App" key="1">
|
||||
<Row>
|
||||
<ImgViewer images={release.screenshots}/>
|
||||
</Row>
|
||||
<Divider/>
|
||||
<Paragraph type="secondary" ellipsis={{rows: 3, expandable: true}}>
|
||||
{release.description}
|
||||
</Paragraph>
|
||||
<Divider/>
|
||||
<Text>CATEGORIES</Text>
|
||||
<div style={{marginTop: 8}}>
|
||||
{
|
||||
app.categories.map(category => {
|
||||
return (
|
||||
<Tag color={pSBC(0.30, config.theme.primaryColor)} key={category}
|
||||
style={{marginBottom: 5}}>
|
||||
{category}
|
||||
</Tag>
|
||||
);
|
||||
})
|
||||
}
|
||||
</div>
|
||||
<Divider/>
|
||||
<Text>TAGS</Text>
|
||||
<div style={{marginTop: 8}}>
|
||||
{
|
||||
app.tags.map(tag => {
|
||||
return (
|
||||
<Tag color="#34495e" key={tag} style={{marginBottom: 5}}>
|
||||
{tag}
|
||||
</Tag>
|
||||
);
|
||||
})
|
||||
}
|
||||
</div>
|
||||
<Divider/>
|
||||
<Text>META DATA</Text>
|
||||
closeAppOperationModal = () => {
|
||||
this.setState({
|
||||
appInstallModalVisible: false,
|
||||
appUninstallModalVisible: false,
|
||||
});
|
||||
};
|
||||
|
||||
<Row>
|
||||
{
|
||||
metaData.map((data, index) => {
|
||||
return (
|
||||
<Col key={index} lg={8} md={6} xs={24} style={{marginTop: 15}}>
|
||||
<Text>{data.key}</Text><br/>
|
||||
<Text type="secondary">{data.value}</Text>
|
||||
</Col>
|
||||
)
|
||||
})
|
||||
}
|
||||
{(metaData.length === 0) && (<Text type="secondary">No meta data available.</Text>)}
|
||||
</Row>
|
||||
<Divider/>
|
||||
<ReviewContainer uuid={release.uuid}/>
|
||||
</TabPane>
|
||||
<TabPane tab="Subscription Details" key="2">
|
||||
<SubscriptionDetails uuid={release.uuid}/>
|
||||
</TabPane>
|
||||
</Tabs>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
handleSubscribeClick = e => {
|
||||
if (e.key === 'install') {
|
||||
this.setState({
|
||||
appInstallModalVisible: true, // display app install modal
|
||||
});
|
||||
} else if (e.key === 'uninstall') {
|
||||
this.setState({
|
||||
appUninstallModalVisible: true, // display app uninstall modal
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
const { app, deviceType } = this.props;
|
||||
const config = this.props.context;
|
||||
const release = app.applicationReleases[0];
|
||||
|
||||
let metaData = [];
|
||||
try {
|
||||
metaData = JSON.parse(release.metaData);
|
||||
// eslint-disable-next-line no-empty
|
||||
} catch (e) {}
|
||||
if (app.hasOwnProperty('packageName')) {
|
||||
metaData.push({
|
||||
key: 'Package Name',
|
||||
value: app.packageName,
|
||||
});
|
||||
}
|
||||
const menu = (
|
||||
<Menu onClick={this.handleSubscribeClick}>
|
||||
<Menu.Item key="install">Install</Menu.Item>
|
||||
<Menu.Item key="uninstall">Uninstall</Menu.Item>
|
||||
</Menu>
|
||||
);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<AppInstallModal
|
||||
uuid={release.uuid}
|
||||
loading={this.state.loading}
|
||||
visible={this.state.appInstallModalVisible}
|
||||
deviceType={deviceType}
|
||||
onClose={this.closeAppOperationModal}
|
||||
onInstall={this.appOperation}
|
||||
/>
|
||||
<AppUninstallModal
|
||||
uuid={release.uuid}
|
||||
loading={this.state.loading}
|
||||
visible={this.state.appUninstallModalVisible}
|
||||
deviceType={deviceType}
|
||||
onClose={this.closeAppOperationModal}
|
||||
onUninstall={this.appOperation}
|
||||
/>
|
||||
<div className="release">
|
||||
<Row>
|
||||
<Col xl={4} sm={6} xs={8} className="release-icon">
|
||||
<img src={release.iconPath} alt="icon" />
|
||||
</Col>
|
||||
<Col xl={10} sm={11} className="release-title">
|
||||
<Title level={2}>{app.name}</Title>
|
||||
<Text>Version : {release.version}</Text>
|
||||
<br />
|
||||
<br />
|
||||
<StarRatings
|
||||
rating={app.rating}
|
||||
starRatedColor="#777"
|
||||
starDimension="20px"
|
||||
starSpacing="2px"
|
||||
numberOfStars={5}
|
||||
name="rating"
|
||||
/>
|
||||
</Col>
|
||||
<Col xl={8} md={10} sm={24} xs={24} style={{ float: 'right' }}>
|
||||
<div
|
||||
style={{
|
||||
textAlign: 'right',
|
||||
}}
|
||||
>
|
||||
<Dropdown overlay={menu}>
|
||||
<Button type="primary">
|
||||
Subscribe <Icon type="down" />
|
||||
</Button>
|
||||
</Dropdown>
|
||||
</div>
|
||||
</Col>
|
||||
</Row>
|
||||
<Divider dashed={true} />
|
||||
<Tabs>
|
||||
<TabPane tab="App" key="1">
|
||||
<Row>
|
||||
<ImgViewer images={release.screenshots} />
|
||||
</Row>
|
||||
<Divider />
|
||||
<Paragraph
|
||||
type="secondary"
|
||||
ellipsis={{ rows: 3, expandable: true }}
|
||||
>
|
||||
{release.description}
|
||||
</Paragraph>
|
||||
<Divider />
|
||||
<Text>CATEGORIES</Text>
|
||||
<div style={{ marginTop: 8 }}>
|
||||
{app.categories.map(category => {
|
||||
return (
|
||||
<Tag
|
||||
color={pSBC(0.3, config.theme.primaryColor)}
|
||||
key={category}
|
||||
style={{ marginBottom: 5 }}
|
||||
>
|
||||
{category}
|
||||
</Tag>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
<Divider />
|
||||
<Text>TAGS</Text>
|
||||
<div style={{ marginTop: 8 }}>
|
||||
{app.tags.map(tag => {
|
||||
return (
|
||||
<Tag color="#34495e" key={tag} style={{ marginBottom: 5 }}>
|
||||
{tag}
|
||||
</Tag>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
<Divider />
|
||||
<Text>META DATA</Text>
|
||||
|
||||
<Row>
|
||||
{metaData.map((data, index) => {
|
||||
return (
|
||||
<Col
|
||||
key={index}
|
||||
lg={8}
|
||||
md={6}
|
||||
xs={24}
|
||||
style={{ marginTop: 15 }}
|
||||
>
|
||||
<Text>{data.key}</Text>
|
||||
<br />
|
||||
<Text type="secondary">{data.value}</Text>
|
||||
</Col>
|
||||
);
|
||||
})}
|
||||
{metaData.length === 0 && (
|
||||
<Text type="secondary">No meta data available.</Text>
|
||||
)}
|
||||
</Row>
|
||||
<Divider />
|
||||
<ReviewContainer uuid={release.uuid} />
|
||||
</TabPane>
|
||||
<TabPane tab="Subscription Details" key="2">
|
||||
<SubscriptionDetails uuid={release.uuid} />
|
||||
</TabPane>
|
||||
</Tabs>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default withConfigContext(ReleaseView);
|
||||
|
||||
@ -16,260 +16,257 @@
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
import axios from "axios";
|
||||
import {
|
||||
Tag,
|
||||
message,
|
||||
notification,
|
||||
Table,
|
||||
Typography,
|
||||
Tooltip,
|
||||
Icon,
|
||||
Divider,
|
||||
Button,
|
||||
Modal,
|
||||
Select,
|
||||
Alert
|
||||
} from "antd";
|
||||
import TimeAgo from 'javascript-time-ago'
|
||||
import React from 'react';
|
||||
import axios from 'axios';
|
||||
import { Tag, Table, Typography, Button, Alert } from 'antd';
|
||||
import TimeAgo from 'javascript-time-ago';
|
||||
|
||||
// Load locale-specific relative date/time formatting rules.
|
||||
import en from 'javascript-time-ago/locale/en'
|
||||
import {withConfigContext} from "../../../context/ConfigContext";
|
||||
import {handleApiError} from "../../../js/Utils";
|
||||
import en from 'javascript-time-ago/locale/en';
|
||||
import { withConfigContext } from '../../../context/ConfigContext';
|
||||
import { handleApiError } from '../../../js/Utils';
|
||||
|
||||
const {Text} = Typography;
|
||||
|
||||
let config = null;
|
||||
const { Text } = Typography;
|
||||
|
||||
const columns = [
|
||||
{
|
||||
title: 'Device',
|
||||
dataIndex: 'device',
|
||||
width: 100,
|
||||
render: device => device.name
|
||||
{
|
||||
title: 'Device',
|
||||
dataIndex: 'device',
|
||||
width: 100,
|
||||
render: device => device.name,
|
||||
},
|
||||
{
|
||||
title: 'Owner',
|
||||
dataIndex: 'device',
|
||||
key: 'owner',
|
||||
render: device => device.enrolmentInfo.owner,
|
||||
},
|
||||
{
|
||||
title: 'Action Type',
|
||||
dataIndex: 'actionType',
|
||||
key: 'actionType',
|
||||
render: actionType => actionType.toLowerCase(),
|
||||
},
|
||||
{
|
||||
title: 'Action',
|
||||
dataIndex: 'action',
|
||||
key: 'action',
|
||||
// eslint-disable-next-line react/display-name
|
||||
render: action => {
|
||||
action = action.toLowerCase();
|
||||
let color = 'fff';
|
||||
if (action === 'subscribed') {
|
||||
color = '#6ab04c';
|
||||
} else if (action === 'unsubscribed') {
|
||||
color = '#f0932b';
|
||||
}
|
||||
return <span style={{ color: color }}>{action}</span>;
|
||||
},
|
||||
{
|
||||
title: 'Owner',
|
||||
dataIndex: 'device',
|
||||
key: 'owner',
|
||||
render: device => device.enrolmentInfo.owner
|
||||
},
|
||||
{
|
||||
title: 'Triggered By',
|
||||
dataIndex: 'actionTriggeredBy',
|
||||
key: 'actionTriggeredBy',
|
||||
},
|
||||
{
|
||||
title: 'Action Triggered At',
|
||||
dataIndex: 'actionTriggeredTimestamp',
|
||||
key: 'actionTriggeredTimestamp',
|
||||
},
|
||||
{
|
||||
title: 'Action Status',
|
||||
dataIndex: 'status',
|
||||
key: 'actionStatus',
|
||||
// eslint-disable-next-line react/display-name
|
||||
render: status => {
|
||||
let color = '#f9ca24';
|
||||
switch (status) {
|
||||
case 'COMPLETED':
|
||||
color = '#badc58';
|
||||
break;
|
||||
case 'REPEATED':
|
||||
color = '#6ab04c';
|
||||
break;
|
||||
case 'ERROR':
|
||||
case 'INVALID':
|
||||
case 'UNAUTHORIZED':
|
||||
color = '#ff7979';
|
||||
break;
|
||||
case 'IN_PROGRESS':
|
||||
color = '#f9ca24';
|
||||
break;
|
||||
case 'PENDING':
|
||||
color = '#636e72';
|
||||
break;
|
||||
}
|
||||
return <Tag color={color}>{status.toLowerCase()}</Tag>;
|
||||
},
|
||||
{
|
||||
title: 'Action Type',
|
||||
dataIndex: 'actionType',
|
||||
key: 'actionType',
|
||||
render: actionType => actionType.toLowerCase()
|
||||
},
|
||||
{
|
||||
title: 'Device Status',
|
||||
dataIndex: 'device',
|
||||
key: 'deviceStatus',
|
||||
// eslint-disable-next-line react/display-name
|
||||
render: device => {
|
||||
const status = device.enrolmentInfo.status.toLowerCase();
|
||||
let color = '#f9ca24';
|
||||
switch (status) {
|
||||
case 'active':
|
||||
color = '#badc58';
|
||||
break;
|
||||
case 'created':
|
||||
color = '#6ab04c';
|
||||
break;
|
||||
case 'removed':
|
||||
color = '#ff7979';
|
||||
break;
|
||||
case 'inactive':
|
||||
color = '#f9ca24';
|
||||
break;
|
||||
case 'blocked':
|
||||
color = '#636e72';
|
||||
break;
|
||||
}
|
||||
return <Tag color={color}>{status}</Tag>;
|
||||
},
|
||||
{
|
||||
title: 'Action',
|
||||
dataIndex: 'action',
|
||||
key: 'action',
|
||||
render: action => {
|
||||
action = action.toLowerCase();
|
||||
let color = "fff";
|
||||
if(action==="subscribed"){
|
||||
color = "#6ab04c"
|
||||
}else if(action === "unsubscribed"){
|
||||
color = "#f0932b"
|
||||
}
|
||||
return <span style={{color:color}}>{action}</span>
|
||||
}
|
||||
},
|
||||
{
|
||||
title: 'Triggered By',
|
||||
dataIndex: 'actionTriggeredBy',
|
||||
key: 'actionTriggeredBy'
|
||||
},
|
||||
{
|
||||
title: 'Action Triggered At',
|
||||
dataIndex: 'actionTriggeredTimestamp',
|
||||
key: 'actionTriggeredTimestamp'
|
||||
},
|
||||
{
|
||||
title: 'Action Status',
|
||||
dataIndex: 'status',
|
||||
key: 'actionStatus',
|
||||
render: (status) => {
|
||||
let color = "#f9ca24";
|
||||
switch (status) {
|
||||
case "COMPLETED":
|
||||
color = "#badc58";
|
||||
break;
|
||||
case "REPEATED":
|
||||
color = "#6ab04c";
|
||||
break;
|
||||
case "ERROR":
|
||||
case "INVALID":
|
||||
case "UNAUTHORIZED":
|
||||
color = "#ff7979";
|
||||
break;
|
||||
case "IN_PROGRESS":
|
||||
color = "#f9ca24";
|
||||
break;
|
||||
case "PENDING":
|
||||
color = "#636e72";
|
||||
break;
|
||||
}
|
||||
return <Tag color={color}>{status.toLowerCase()}</Tag>;
|
||||
}
|
||||
},
|
||||
{
|
||||
title: 'Device Status',
|
||||
dataIndex: 'device',
|
||||
key: 'deviceStatus',
|
||||
render: (device) => {
|
||||
const status = device.enrolmentInfo.status.toLowerCase();
|
||||
let color = "#f9ca24";
|
||||
switch (status) {
|
||||
case "active":
|
||||
color = "#badc58";
|
||||
break;
|
||||
case "created":
|
||||
color = "#6ab04c";
|
||||
break;
|
||||
case "removed":
|
||||
color = "#ff7979";
|
||||
break;
|
||||
case "inactive":
|
||||
color = "#f9ca24";
|
||||
break;
|
||||
case "blocked":
|
||||
color = "#636e72";
|
||||
break;
|
||||
}
|
||||
return <Tag color={color}>{status}</Tag>;
|
||||
}
|
||||
}
|
||||
},
|
||||
];
|
||||
|
||||
const getTimeAgo = (time) => {
|
||||
const timeAgo = new TimeAgo('en-US');
|
||||
return timeAgo.format(time);
|
||||
};
|
||||
|
||||
|
||||
class SubscriptionDetails extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
config = this.props.context;
|
||||
TimeAgo.addLocale(en);
|
||||
this.state = {
|
||||
data: [],
|
||||
pagination: {},
|
||||
constructor(props) {
|
||||
super(props);
|
||||
TimeAgo.addLocale(en);
|
||||
this.state = {
|
||||
data: [],
|
||||
pagination: {},
|
||||
loading: false,
|
||||
selectedRows: [],
|
||||
deviceGroups: [],
|
||||
groupModalVisible: false,
|
||||
selectedGroupId: [],
|
||||
isForbidden: false,
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.fetch();
|
||||
}
|
||||
|
||||
// fetch data from api
|
||||
fetch = (params = {}) => {
|
||||
const config = this.props.context;
|
||||
this.setState({ loading: true });
|
||||
// get current page
|
||||
const currentPage = params.hasOwnProperty('page') ? params.page : 1;
|
||||
|
||||
const extraParams = {
|
||||
offset: 10 * (currentPage - 1), // calculate the offset
|
||||
limit: 10,
|
||||
requireDeviceInfo: true,
|
||||
};
|
||||
|
||||
const encodedExtraParams = Object.keys(extraParams)
|
||||
.map(key => key + '=' + extraParams[key])
|
||||
.join('&');
|
||||
|
||||
// send request to the invoker
|
||||
axios
|
||||
.get(
|
||||
window.location.origin +
|
||||
config.serverConfig.invoker.uri +
|
||||
config.serverConfig.invoker.store +
|
||||
`/admin/subscription/${this.props.uuid}?` +
|
||||
encodedExtraParams,
|
||||
)
|
||||
.then(res => {
|
||||
if (res.status === 200) {
|
||||
this.setState({
|
||||
loading: false,
|
||||
selectedRows: [],
|
||||
deviceGroups: [],
|
||||
groupModalVisible: false,
|
||||
selectedGroupId: [],
|
||||
isForbidden: false
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.fetch();
|
||||
}
|
||||
|
||||
//fetch data from api
|
||||
fetch = (params = {}) => {
|
||||
const config = this.props.context;
|
||||
this.setState({loading: true});
|
||||
// get current page
|
||||
const currentPage = (params.hasOwnProperty("page")) ? params.page : 1;
|
||||
|
||||
const extraParams = {
|
||||
offset: 10 * (currentPage - 1), //calculate the offset
|
||||
limit: 10,
|
||||
requireDeviceInfo: true,
|
||||
};
|
||||
|
||||
const encodedExtraParams = Object.keys(extraParams)
|
||||
.map(key => key + '=' + extraParams[key]).join('&');
|
||||
|
||||
//send request to the invoker
|
||||
axios.get(
|
||||
window.location.origin + config.serverConfig.invoker.uri +
|
||||
config.serverConfig.invoker.store +
|
||||
`/admin/subscription/${this.props.uuid}?` + encodedExtraParams,
|
||||
).then(res => {
|
||||
if (res.status === 200) {
|
||||
this.setState({
|
||||
loading: false,
|
||||
data: res.data.data
|
||||
});
|
||||
}
|
||||
|
||||
}).catch((error) => {
|
||||
handleApiError(error, "Something went wrong when trying to load subscription data.", true);
|
||||
if (error.hasOwnProperty("response") && error.response.status === 403) {
|
||||
this.setState({
|
||||
isForbidden: true,
|
||||
loading: false
|
||||
})
|
||||
} else {
|
||||
this.setState({
|
||||
loading: false
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
handleTableChange = (pagination, filters, sorter) => {
|
||||
const pager = {...this.state.pagination};
|
||||
pager.current = pagination.current;
|
||||
this.setState({
|
||||
pagination: pager,
|
||||
});
|
||||
this.fetch({
|
||||
results: pagination.pageSize,
|
||||
page: pagination.current,
|
||||
sortField: sorter.field,
|
||||
sortOrder: sorter.order,
|
||||
...filters,
|
||||
});
|
||||
};
|
||||
|
||||
render() {
|
||||
const {data, pagination, loading, selectedRows} = this.state;
|
||||
return (
|
||||
<div>
|
||||
{(this.state.isForbidden) && (
|
||||
<Alert
|
||||
message="You don't have permission to view subscription details."
|
||||
type="warning"
|
||||
banner
|
||||
closable/>
|
||||
)}
|
||||
<div style={{paddingBottom: 24}}>
|
||||
<Text>
|
||||
The following are the subscription details of the application in each respective device.
|
||||
</Text>
|
||||
</div>
|
||||
<div style={{textAlign: "right", paddingBottom: 6}}>
|
||||
<Button icon="sync" onClick={this.fetch}>
|
||||
Refresh
|
||||
</Button>
|
||||
</div>
|
||||
<Table
|
||||
columns={columns}
|
||||
rowKey={record => (record.device.deviceIdentifier + record.device.enrolmentInfo.owner + record.device.enrolmentInfo.ownership)}
|
||||
dataSource={data.data}
|
||||
pagination={{
|
||||
...pagination,
|
||||
size: "small",
|
||||
// position: "top",
|
||||
total: data.recordsTotal,
|
||||
showTotal: (total, range) => `showing ${range[0]}-${range[1]} of ${total} devices`
|
||||
// showQuickJumper: true
|
||||
}}
|
||||
onChange={this.handleTableChange}
|
||||
loading={loading}
|
||||
scroll={{x: 1000}}
|
||||
/>
|
||||
</div>
|
||||
data: res.data.data,
|
||||
});
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
handleApiError(
|
||||
error,
|
||||
'Something went wrong when trying to load subscription data.',
|
||||
true,
|
||||
);
|
||||
}
|
||||
if (error.hasOwnProperty('response') && error.response.status === 403) {
|
||||
this.setState({
|
||||
isForbidden: true,
|
||||
loading: false,
|
||||
});
|
||||
} else {
|
||||
this.setState({
|
||||
loading: false,
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
handleTableChange = (pagination, filters, sorter) => {
|
||||
const pager = { ...this.state.pagination };
|
||||
pager.current = pagination.current;
|
||||
this.setState({
|
||||
pagination: pager,
|
||||
});
|
||||
this.fetch({
|
||||
results: pagination.pageSize,
|
||||
page: pagination.current,
|
||||
sortField: sorter.field,
|
||||
sortOrder: sorter.order,
|
||||
...filters,
|
||||
});
|
||||
};
|
||||
|
||||
render() {
|
||||
const { data, pagination, loading } = this.state;
|
||||
return (
|
||||
<div>
|
||||
{this.state.isForbidden && (
|
||||
<Alert
|
||||
message="You don't have permission to view subscription details."
|
||||
type="warning"
|
||||
banner
|
||||
closable
|
||||
/>
|
||||
)}
|
||||
<div style={{ paddingBottom: 24 }}>
|
||||
<Text>
|
||||
The following are the subscription details of the application in
|
||||
each respective device.
|
||||
</Text>
|
||||
</div>
|
||||
<div style={{ textAlign: 'right', paddingBottom: 6 }}>
|
||||
<Button icon="sync" onClick={this.fetch}>
|
||||
Refresh
|
||||
</Button>
|
||||
</div>
|
||||
<Table
|
||||
columns={columns}
|
||||
rowKey={record =>
|
||||
record.device.deviceIdentifier +
|
||||
record.device.enrolmentInfo.owner +
|
||||
record.device.enrolmentInfo.ownership
|
||||
}
|
||||
dataSource={data.data}
|
||||
pagination={{
|
||||
...pagination,
|
||||
size: 'small',
|
||||
// position: "top",
|
||||
total: data.recordsTotal,
|
||||
showTotal: (total, range) =>
|
||||
`showing ${range[0]}-${range[1]} of ${total} devices`,
|
||||
// showQuickJumper: true
|
||||
}}
|
||||
onChange={this.handleTableChange}
|
||||
loading={loading}
|
||||
scroll={{ x: 1000 }}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default withConfigContext(SubscriptionDetails);
|
||||
export default withConfigContext(SubscriptionDetails);
|
||||
|
||||
@ -16,48 +16,47 @@
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import React, {Component} from 'react';
|
||||
import React, { Component } from 'react';
|
||||
import RcViewer from 'rc-viewer';
|
||||
import {Col} from "antd";
|
||||
|
||||
class ImgViewer extends Component {
|
||||
render() {
|
||||
const options = {
|
||||
title: false,
|
||||
toolbar: {
|
||||
zoomIn: 0,
|
||||
zoomOut: 0,
|
||||
oneToOne: 0,
|
||||
reset: 0,
|
||||
prev: 1,
|
||||
play: {
|
||||
show: 0
|
||||
},
|
||||
next: 1,
|
||||
rotateLeft: 0,
|
||||
rotateRight: 0,
|
||||
flipHorizontal: 0,
|
||||
flipVertical: 0
|
||||
},
|
||||
rotatable: false,
|
||||
transition: false,
|
||||
movable : false
|
||||
};
|
||||
return (
|
||||
<div className="release-images">
|
||||
<RcViewer options={options} ref='viewer'>
|
||||
{this.props.images.map((screenshotUrl, index) => {
|
||||
return (
|
||||
<div key={index} className="release-screenshot">
|
||||
<img alt="screenshot" key={screenshotUrl} src={screenshotUrl}/>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</RcViewer>
|
||||
</div>
|
||||
);
|
||||
|
||||
}
|
||||
render() {
|
||||
const options = {
|
||||
title: false,
|
||||
toolbar: {
|
||||
zoomIn: 0,
|
||||
zoomOut: 0,
|
||||
oneToOne: 0,
|
||||
reset: 0,
|
||||
prev: 1,
|
||||
play: {
|
||||
show: 0,
|
||||
},
|
||||
next: 1,
|
||||
rotateLeft: 0,
|
||||
rotateRight: 0,
|
||||
flipHorizontal: 0,
|
||||
flipVertical: 0,
|
||||
},
|
||||
rotatable: false,
|
||||
transition: false,
|
||||
movable: false,
|
||||
};
|
||||
return (
|
||||
<div className="release-images">
|
||||
{/* eslint-disable-next-line react/no-string-refs */}
|
||||
<RcViewer options={options} ref="viewer">
|
||||
{this.props.images.map((screenshotUrl, index) => {
|
||||
return (
|
||||
<div key={index} className="release-screenshot">
|
||||
<img alt="screenshot" key={screenshotUrl} src={screenshotUrl} />
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</RcViewer>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default ImgViewer;
|
||||
export default ImgViewer;
|
||||
|
||||
@ -16,49 +16,53 @@
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
import {Modal, Spin, Tabs} from "antd";
|
||||
import UserInstall from "./UserInstall";
|
||||
import GroupInstall from "./GroupInstall";
|
||||
import RoleInstall from "./RoleInstall";
|
||||
import DeviceInstall from "./DeviceInstall";
|
||||
import React from 'react';
|
||||
import { Modal, Spin, Tabs } from 'antd';
|
||||
import UserInstall from './UserInstall';
|
||||
import GroupInstall from './GroupInstall';
|
||||
import RoleInstall from './RoleInstall';
|
||||
import DeviceInstall from './DeviceInstall';
|
||||
|
||||
const {TabPane} = Tabs;
|
||||
const { TabPane } = Tabs;
|
||||
|
||||
class AppInstallModal extends React.Component {
|
||||
state = {
|
||||
data: []
|
||||
};
|
||||
state = {
|
||||
data: [],
|
||||
};
|
||||
|
||||
render() {
|
||||
const {deviceType} = this.props;
|
||||
return (
|
||||
<div>
|
||||
<Modal
|
||||
title="Install App"
|
||||
visible={this.props.visible}
|
||||
onCancel={this.props.onClose}
|
||||
footer={null}>
|
||||
<Spin spinning={this.props.loading}>
|
||||
<Tabs defaultActiveKey="device">
|
||||
<TabPane tab="Device" key="device">
|
||||
<DeviceInstall deviceType={deviceType} onInstall={this.props.onInstall}/>
|
||||
</TabPane>
|
||||
<TabPane tab="User" key="user">
|
||||
<UserInstall onInstall={this.props.onInstall}/>
|
||||
</TabPane>
|
||||
<TabPane tab="Role" key="role">
|
||||
<RoleInstall onInstall={this.props.onInstall}/>
|
||||
</TabPane>
|
||||
<TabPane tab="Group" key="group">
|
||||
<GroupInstall onInstall={this.props.onInstall}/>
|
||||
</TabPane>
|
||||
</Tabs>
|
||||
</Spin>
|
||||
</Modal>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
render() {
|
||||
const { deviceType } = this.props;
|
||||
return (
|
||||
<div>
|
||||
<Modal
|
||||
title="Install App"
|
||||
visible={this.props.visible}
|
||||
onCancel={this.props.onClose}
|
||||
footer={null}
|
||||
>
|
||||
<Spin spinning={this.props.loading}>
|
||||
<Tabs defaultActiveKey="device">
|
||||
<TabPane tab="Device" key="device">
|
||||
<DeviceInstall
|
||||
deviceType={deviceType}
|
||||
onInstall={this.props.onInstall}
|
||||
/>
|
||||
</TabPane>
|
||||
<TabPane tab="User" key="user">
|
||||
<UserInstall onInstall={this.props.onInstall} />
|
||||
</TabPane>
|
||||
<TabPane tab="Role" key="role">
|
||||
<RoleInstall onInstall={this.props.onInstall} />
|
||||
</TabPane>
|
||||
<TabPane tab="Group" key="group">
|
||||
<GroupInstall onInstall={this.props.onInstall} />
|
||||
</TabPane>
|
||||
</Tabs>
|
||||
</Spin>
|
||||
</Modal>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default AppInstallModal;
|
||||
export default AppInstallModal;
|
||||
|
||||
@ -15,50 +15,63 @@
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import React from "react";
|
||||
import {Modal, Spin, Tabs} from "antd";
|
||||
import DeviceUninstall from "./DeviceUninstall";
|
||||
import UserUninstall from "./UserUninstall";
|
||||
import RoleUninstall from "./RoleUninstall";
|
||||
import GroupUninstall from "./GroupUninstall";
|
||||
import React from 'react';
|
||||
import { Modal, Spin, Tabs } from 'antd';
|
||||
import DeviceUninstall from './DeviceUninstall';
|
||||
import UserUninstall from './UserUninstall';
|
||||
import RoleUninstall from './RoleUninstall';
|
||||
import GroupUninstall from './GroupUninstall';
|
||||
|
||||
const {TabPane} = Tabs;
|
||||
const { TabPane } = Tabs;
|
||||
|
||||
class AppUninstallModal extends React.Component {
|
||||
state = {
|
||||
data: []
|
||||
};
|
||||
state = {
|
||||
data: [],
|
||||
};
|
||||
|
||||
render() {
|
||||
const {deviceType} = this.props;
|
||||
return (
|
||||
<div>
|
||||
<Modal
|
||||
title="Uninstall App"
|
||||
visible={this.props.visible}
|
||||
onCancel={this.props.onClose}
|
||||
footer={null}>
|
||||
<Spin spinning={this.props.loading}>
|
||||
<Tabs defaultActiveKey="device">
|
||||
<TabPane tab="Device" key="device">
|
||||
<DeviceUninstall deviceType={deviceType} onUninstall={this.props.onUninstall}
|
||||
uuid={this.props.uuid}/>
|
||||
</TabPane>
|
||||
<TabPane tab="User" key="user">
|
||||
<UserUninstall onUninstall={this.props.onUninstall} uuid={this.props.uuid}/>
|
||||
</TabPane>
|
||||
<TabPane tab="Role" key="role">
|
||||
<RoleUninstall onUninstall={this.props.onUninstall} uuid={this.props.uuid}/>
|
||||
</TabPane>
|
||||
<TabPane tab="Group" key="group">
|
||||
<GroupUninstall onUninstall={this.props.onUninstall} uuid={this.props.uuid}/>
|
||||
</TabPane>
|
||||
</Tabs>
|
||||
</Spin>
|
||||
</Modal>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
render() {
|
||||
const { deviceType } = this.props;
|
||||
return (
|
||||
<div>
|
||||
<Modal
|
||||
title="Uninstall App"
|
||||
visible={this.props.visible}
|
||||
onCancel={this.props.onClose}
|
||||
footer={null}
|
||||
>
|
||||
<Spin spinning={this.props.loading}>
|
||||
<Tabs defaultActiveKey="device">
|
||||
<TabPane tab="Device" key="device">
|
||||
<DeviceUninstall
|
||||
deviceType={deviceType}
|
||||
onUninstall={this.props.onUninstall}
|
||||
uuid={this.props.uuid}
|
||||
/>
|
||||
</TabPane>
|
||||
<TabPane tab="User" key="user">
|
||||
<UserUninstall
|
||||
onUninstall={this.props.onUninstall}
|
||||
uuid={this.props.uuid}
|
||||
/>
|
||||
</TabPane>
|
||||
<TabPane tab="Role" key="role">
|
||||
<RoleUninstall
|
||||
onUninstall={this.props.onUninstall}
|
||||
uuid={this.props.uuid}
|
||||
/>
|
||||
</TabPane>
|
||||
<TabPane tab="Group" key="group">
|
||||
<GroupUninstall
|
||||
onUninstall={this.props.onUninstall}
|
||||
uuid={this.props.uuid}
|
||||
/>
|
||||
</TabPane>
|
||||
</Tabs>
|
||||
</Spin>
|
||||
</Modal>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default AppUninstallModal;
|
||||
|
||||
@ -16,232 +16,250 @@
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
import axios from "axios";
|
||||
import {Button, message, DatePicker, Table, Typography, Alert} from "antd";
|
||||
import TimeAgo from 'javascript-time-ago'
|
||||
import React from 'react';
|
||||
import axios from 'axios';
|
||||
import { Table, Typography, Alert } from 'antd';
|
||||
import TimeAgo from 'javascript-time-ago';
|
||||
|
||||
// Load locale-specific relative date/time formatting rules.
|
||||
import en from 'javascript-time-ago/locale/en'
|
||||
import {withConfigContext} from "../../../../context/ConfigContext";
|
||||
import {handleApiError} from "../../../../js/Utils";
|
||||
import InstallModalFooter from "./installModalFooter/InstallModalFooter";
|
||||
import en from 'javascript-time-ago/locale/en';
|
||||
import { withConfigContext } from '../../../../context/ConfigContext';
|
||||
import { handleApiError } from '../../../../js/Utils';
|
||||
import InstallModalFooter from './installModalFooter/InstallModalFooter';
|
||||
|
||||
const {Text} = Typography;
|
||||
const { Text } = Typography;
|
||||
const columns = [
|
||||
{
|
||||
title: 'Device',
|
||||
dataIndex: 'name',
|
||||
fixed: 'left',
|
||||
width: 100,
|
||||
{
|
||||
title: 'Device',
|
||||
dataIndex: 'name',
|
||||
fixed: 'left',
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
title: 'Modal',
|
||||
dataIndex: 'deviceInfo',
|
||||
key: 'modal',
|
||||
render: deviceInfo => `${deviceInfo.vendor} ${deviceInfo.deviceModel}`,
|
||||
// todo add filtering options
|
||||
},
|
||||
{
|
||||
title: 'Owner',
|
||||
dataIndex: 'enrolmentInfo',
|
||||
key: 'owner',
|
||||
render: enrolmentInfo => enrolmentInfo.owner,
|
||||
// todo add filtering options
|
||||
},
|
||||
{
|
||||
title: 'Last Updated',
|
||||
dataIndex: 'deviceInfo',
|
||||
key: 'updatedTime',
|
||||
render: data => {
|
||||
return data.updatedTime;
|
||||
},
|
||||
{
|
||||
title: 'Modal',
|
||||
dataIndex: 'deviceInfo',
|
||||
key: 'modal',
|
||||
render: deviceInfo => `${deviceInfo.vendor} ${deviceInfo.deviceModel}`
|
||||
// todo add filtering options
|
||||
},
|
||||
{
|
||||
title: 'Owner',
|
||||
dataIndex: 'enrolmentInfo',
|
||||
key: 'owner',
|
||||
render: enrolmentInfo => enrolmentInfo.owner
|
||||
// todo add filtering options
|
||||
},
|
||||
{
|
||||
title: 'Last Updated',
|
||||
dataIndex: 'deviceInfo',
|
||||
key: 'updatedTime',
|
||||
render: (data) => {
|
||||
return data.updatedTime;
|
||||
// todo add filtering options
|
||||
},
|
||||
{
|
||||
title: 'Status',
|
||||
dataIndex: 'enrolmentInfo',
|
||||
key: 'status',
|
||||
render: enrolmentInfo => enrolmentInfo.status,
|
||||
// todo add filtering options
|
||||
},
|
||||
{
|
||||
title: 'Ownership',
|
||||
dataIndex: 'enrolmentInfo',
|
||||
key: 'ownership',
|
||||
render: enrolmentInfo => enrolmentInfo.ownership,
|
||||
// todo add filtering options
|
||||
},
|
||||
{
|
||||
title: 'OS Version',
|
||||
dataIndex: 'deviceInfo',
|
||||
key: 'osVersion',
|
||||
render: deviceInfo => deviceInfo.osVersion,
|
||||
// todo add filtering options
|
||||
},
|
||||
{
|
||||
title: 'IMEI',
|
||||
dataIndex: 'properties',
|
||||
key: 'imei',
|
||||
render: properties => {
|
||||
let imei = 'not-found';
|
||||
for (let i = 0; i < properties.length; i++) {
|
||||
if (properties[i].name === 'IMEI') {
|
||||
imei = properties[i].value;
|
||||
}
|
||||
// todo add filtering options
|
||||
},
|
||||
{
|
||||
title: 'Status',
|
||||
dataIndex: 'enrolmentInfo',
|
||||
key: 'status',
|
||||
render: enrolmentInfo => enrolmentInfo.status
|
||||
// todo add filtering options
|
||||
},
|
||||
{
|
||||
title: 'Ownership',
|
||||
dataIndex: 'enrolmentInfo',
|
||||
key: 'ownership',
|
||||
render: enrolmentInfo => enrolmentInfo.ownership
|
||||
// todo add filtering options
|
||||
},
|
||||
{
|
||||
title: 'OS Version',
|
||||
dataIndex: 'deviceInfo',
|
||||
key: 'osVersion',
|
||||
render: deviceInfo => deviceInfo.osVersion
|
||||
// todo add filtering options
|
||||
},
|
||||
{
|
||||
title: 'IMEI',
|
||||
dataIndex: 'properties',
|
||||
key: 'imei',
|
||||
render: properties => {
|
||||
let imei = "not-found";
|
||||
for (let i = 0; i < properties.length; i++) {
|
||||
if (properties[i].name === "IMEI") {
|
||||
imei = properties[i].value;
|
||||
}
|
||||
}
|
||||
return imei;
|
||||
}
|
||||
// todo add filtering options
|
||||
}
|
||||
return imei;
|
||||
},
|
||||
// todo add filtering options
|
||||
},
|
||||
];
|
||||
|
||||
class DeviceInstall extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
TimeAgo.addLocale(en);
|
||||
this.state = {
|
||||
data: [],
|
||||
pagination: {},
|
||||
constructor(props) {
|
||||
super(props);
|
||||
TimeAgo.addLocale(en);
|
||||
this.state = {
|
||||
data: [],
|
||||
pagination: {},
|
||||
loading: false,
|
||||
selectedRows: [],
|
||||
scheduledTime: null,
|
||||
isScheduledInstallVisible: false,
|
||||
isForbidden: false,
|
||||
};
|
||||
}
|
||||
|
||||
rowSelection = {
|
||||
onChange: (selectedRowKeys, selectedRows) => {
|
||||
this.setState({
|
||||
selectedRows: selectedRows,
|
||||
});
|
||||
},
|
||||
getCheckboxProps: record => ({
|
||||
disabled: record.name === 'Disabled User', // Column configuration not to be checked
|
||||
name: record.name,
|
||||
}),
|
||||
};
|
||||
|
||||
componentDidMount() {
|
||||
this.fetch();
|
||||
}
|
||||
|
||||
// fetch data from api
|
||||
fetch = (params = {}) => {
|
||||
const config = this.props.context;
|
||||
this.setState({ loading: true });
|
||||
const { deviceType } = this.props;
|
||||
// get current page
|
||||
const currentPage = params.hasOwnProperty('page') ? params.page : 1;
|
||||
|
||||
const extraParams = {
|
||||
offset: 10 * (currentPage - 1), // calculate the offset
|
||||
limit: 10,
|
||||
status: 'ACTIVE',
|
||||
requireDeviceInfo: true,
|
||||
};
|
||||
|
||||
if (deviceType !== 'ANY') {
|
||||
extraParams.type = deviceType;
|
||||
}
|
||||
|
||||
// note: encode with '%26' not '&'
|
||||
const encodedExtraParams = Object.keys(extraParams)
|
||||
.map(key => key + '=' + extraParams[key])
|
||||
.join('&');
|
||||
|
||||
// send request to the invoker
|
||||
axios
|
||||
.get(
|
||||
window.location.origin +
|
||||
config.serverConfig.invoker.uri +
|
||||
config.serverConfig.invoker.deviceMgt +
|
||||
'/devices?' +
|
||||
encodedExtraParams,
|
||||
)
|
||||
.then(res => {
|
||||
if (res.status === 200) {
|
||||
const pagination = { ...this.state.pagination };
|
||||
this.setState({
|
||||
loading: false,
|
||||
selectedRows: [],
|
||||
scheduledTime: null,
|
||||
isScheduledInstallVisible: false,
|
||||
isForbidden: false
|
||||
};
|
||||
}
|
||||
|
||||
rowSelection = {
|
||||
onChange: (selectedRowKeys, selectedRows) => {
|
||||
this.setState({
|
||||
selectedRows: selectedRows
|
||||
})
|
||||
},
|
||||
getCheckboxProps: record => ({
|
||||
disabled: record.name === 'Disabled User', // Column configuration not to be checked
|
||||
name: record.name,
|
||||
}),
|
||||
};
|
||||
|
||||
componentDidMount() {
|
||||
this.fetch();
|
||||
}
|
||||
|
||||
//fetch data from api
|
||||
fetch = (params = {}) => {
|
||||
const config = this.props.context;
|
||||
this.setState({loading: true});
|
||||
const {deviceType} = this.props;
|
||||
// get current page
|
||||
const currentPage = (params.hasOwnProperty("page")) ? params.page : 1;
|
||||
|
||||
const extraParams = {
|
||||
offset: 10 * (currentPage - 1), //calculate the offset
|
||||
limit: 10,
|
||||
status: "ACTIVE",
|
||||
requireDeviceInfo: true,
|
||||
};
|
||||
|
||||
if (deviceType !== 'ANY') {
|
||||
extraParams.type = deviceType;
|
||||
data: res.data.data,
|
||||
pagination,
|
||||
});
|
||||
}
|
||||
|
||||
// note: encode with '%26' not '&'
|
||||
const encodedExtraParams = Object.keys(extraParams).map(key => key + '=' + extraParams[key]).join('&');
|
||||
|
||||
//send request to the invoker
|
||||
axios.get(
|
||||
window.location.origin + config.serverConfig.invoker.uri + config.serverConfig.invoker.deviceMgt +
|
||||
"/devices?" + encodedExtraParams,
|
||||
).then(res => {
|
||||
if (res.status === 200) {
|
||||
const pagination = {...this.state.pagination};
|
||||
this.setState({
|
||||
loading: false,
|
||||
data: res.data.data,
|
||||
pagination,
|
||||
});
|
||||
}
|
||||
|
||||
}).catch((error) => {
|
||||
handleApiError(error, "Error occurred while trying to load devices.", true);
|
||||
if (error.hasOwnProperty("response") && error.response.status === 403) {
|
||||
this.setState({
|
||||
isForbidden: true,
|
||||
loading: false
|
||||
})
|
||||
} else {
|
||||
this.setState({
|
||||
loading: false
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
handleTableChange = (pagination, filters, sorter) => {
|
||||
const pager = {...this.state.pagination};
|
||||
pager.current = pagination.current;
|
||||
this.setState({
|
||||
pagination: pager,
|
||||
});
|
||||
this.fetch({
|
||||
results: pagination.pageSize,
|
||||
page: pagination.current,
|
||||
sortField: sorter.field,
|
||||
sortOrder: sorter.order,
|
||||
...filters,
|
||||
});
|
||||
};
|
||||
|
||||
install = (timestamp=null) => {
|
||||
const {selectedRows} = this.state;
|
||||
const payload = [];
|
||||
selectedRows.map(device => {
|
||||
payload.push({
|
||||
id: device.deviceIdentifier,
|
||||
type: device.type
|
||||
});
|
||||
});
|
||||
this.props.onInstall("devices", payload, "install",timestamp);
|
||||
};
|
||||
|
||||
render() {
|
||||
const {data, pagination, loading, selectedRows, scheduledTime,isScheduledInstallVisible} = this.state;
|
||||
return (
|
||||
<div>
|
||||
<Text>
|
||||
Start installing the application for one or more users by entering the corresponding user name.
|
||||
Select install to automatically start downloading the application for the respective user/users.
|
||||
</Text>
|
||||
{(this.state.isForbidden) && (
|
||||
<Alert
|
||||
message="You don't have permission to view devices."
|
||||
type="warning"
|
||||
banner
|
||||
closable/>
|
||||
)}
|
||||
<Table
|
||||
style={{paddingTop: 20}}
|
||||
columns={columns}
|
||||
rowKey={record => record.deviceIdentifier}
|
||||
dataSource={data.devices}
|
||||
pagination={{
|
||||
...pagination,
|
||||
size: "small",
|
||||
// position: "top",
|
||||
total: data.count,
|
||||
showTotal: (total, range) => `showing ${range[0]}-${range[1]} of ${total} devices`
|
||||
// showQuickJumper: true
|
||||
}}
|
||||
loading={loading}
|
||||
onChange={this.handleTableChange}
|
||||
rowSelection={this.rowSelection}
|
||||
scroll={{x: 1000}}
|
||||
/>
|
||||
<InstallModalFooter type="Install" operation={this.install} disabled={selectedRows.length === 0}/>
|
||||
</div>
|
||||
})
|
||||
.catch(error => {
|
||||
handleApiError(
|
||||
error,
|
||||
'Error occurred while trying to load devices.',
|
||||
true,
|
||||
);
|
||||
}
|
||||
if (error.hasOwnProperty('response') && error.response.status === 403) {
|
||||
this.setState({
|
||||
isForbidden: true,
|
||||
loading: false,
|
||||
});
|
||||
} else {
|
||||
this.setState({
|
||||
loading: false,
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
handleTableChange = (pagination, filters, sorter) => {
|
||||
const pager = { ...this.state.pagination };
|
||||
pager.current = pagination.current;
|
||||
this.setState({
|
||||
pagination: pager,
|
||||
});
|
||||
this.fetch({
|
||||
results: pagination.pageSize,
|
||||
page: pagination.current,
|
||||
sortField: sorter.field,
|
||||
sortOrder: sorter.order,
|
||||
...filters,
|
||||
});
|
||||
};
|
||||
|
||||
install = (timestamp = null) => {
|
||||
const { selectedRows } = this.state;
|
||||
const payload = [];
|
||||
selectedRows.map(device => {
|
||||
payload.push({
|
||||
id: device.deviceIdentifier,
|
||||
type: device.type,
|
||||
});
|
||||
});
|
||||
this.props.onInstall('devices', payload, 'install', timestamp);
|
||||
};
|
||||
|
||||
render() {
|
||||
const { data, pagination, loading, selectedRows } = this.state;
|
||||
return (
|
||||
<div>
|
||||
<Text>
|
||||
Start installing the application for one or more users by entering the
|
||||
corresponding user name. Select install to automatically start
|
||||
downloading the application for the respective user/users.
|
||||
</Text>
|
||||
{this.state.isForbidden && (
|
||||
<Alert
|
||||
message="You don't have permission to view devices."
|
||||
type="warning"
|
||||
banner
|
||||
closable
|
||||
/>
|
||||
)}
|
||||
<Table
|
||||
style={{ paddingTop: 20 }}
|
||||
columns={columns}
|
||||
rowKey={record => record.deviceIdentifier}
|
||||
dataSource={data.devices}
|
||||
pagination={{
|
||||
...pagination,
|
||||
size: 'small',
|
||||
// position: "top",
|
||||
total: data.count,
|
||||
showTotal: (total, range) =>
|
||||
`showing ${range[0]}-${range[1]} of ${total} devices`,
|
||||
// showQuickJumper: true
|
||||
}}
|
||||
loading={loading}
|
||||
onChange={this.handleTableChange}
|
||||
rowSelection={this.rowSelection}
|
||||
scroll={{ x: 1000 }}
|
||||
/>
|
||||
<InstallModalFooter
|
||||
type="Install"
|
||||
operation={this.install}
|
||||
disabled={selectedRows.length === 0}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default withConfigContext(DeviceInstall);
|
||||
export default withConfigContext(DeviceInstall);
|
||||
|
||||
@ -16,226 +16,248 @@
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
import axios from "axios";
|
||||
import {Alert, Button, Select, Table, Typography} from "antd";
|
||||
import TimeAgo from 'javascript-time-ago'
|
||||
import React from 'react';
|
||||
import axios from 'axios';
|
||||
import { Alert, Table, Typography } from 'antd';
|
||||
import TimeAgo from 'javascript-time-ago';
|
||||
|
||||
// Load locale-specific relative date/time formatting rules.
|
||||
import en from 'javascript-time-ago/locale/en'
|
||||
import {withConfigContext} from "../../../../context/ConfigContext";
|
||||
import {handleApiError} from "../../../../js/Utils";
|
||||
import InstallModalFooter from "./installModalFooter/InstallModalFooter";
|
||||
import en from 'javascript-time-ago/locale/en';
|
||||
import { withConfigContext } from '../../../../context/ConfigContext';
|
||||
import { handleApiError } from '../../../../js/Utils';
|
||||
import InstallModalFooter from './installModalFooter/InstallModalFooter';
|
||||
|
||||
const {Text} = Typography;
|
||||
const { Text } = Typography;
|
||||
const columns = [
|
||||
{
|
||||
title: 'Device',
|
||||
dataIndex: 'name',
|
||||
fixed: 'left',
|
||||
width: 100,
|
||||
{
|
||||
title: 'Device',
|
||||
dataIndex: 'name',
|
||||
fixed: 'left',
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
title: 'Modal',
|
||||
dataIndex: 'deviceInfo',
|
||||
key: 'modal',
|
||||
render: deviceInfo => `${deviceInfo.vendor} ${deviceInfo.deviceModel}`,
|
||||
// todo add filtering options
|
||||
},
|
||||
{
|
||||
title: 'Owner',
|
||||
dataIndex: 'enrolmentInfo',
|
||||
key: 'owner',
|
||||
render: enrolmentInfo => enrolmentInfo.owner,
|
||||
// todo add filtering options
|
||||
},
|
||||
{
|
||||
title: 'Last Updated',
|
||||
dataIndex: 'deviceInfo',
|
||||
key: 'updatedTime',
|
||||
render: data => {
|
||||
return data.updatedTime;
|
||||
},
|
||||
{
|
||||
title: 'Modal',
|
||||
dataIndex: 'deviceInfo',
|
||||
key: 'modal',
|
||||
render: deviceInfo => `${deviceInfo.vendor} ${deviceInfo.deviceModel}`
|
||||
// todo add filtering options
|
||||
},
|
||||
{
|
||||
title: 'Owner',
|
||||
dataIndex: 'enrolmentInfo',
|
||||
key: 'owner',
|
||||
render: enrolmentInfo => enrolmentInfo.owner
|
||||
// todo add filtering options
|
||||
},
|
||||
{
|
||||
title: 'Last Updated',
|
||||
dataIndex: 'deviceInfo',
|
||||
key: 'updatedTime',
|
||||
render: (data) => {
|
||||
return data.updatedTime;
|
||||
// todo add filtering options
|
||||
},
|
||||
{
|
||||
title: 'Status',
|
||||
dataIndex: 'enrolmentInfo',
|
||||
key: 'status',
|
||||
render: enrolmentInfo => enrolmentInfo.status,
|
||||
// todo add filtering options
|
||||
},
|
||||
{
|
||||
title: 'Ownership',
|
||||
dataIndex: 'enrolmentInfo',
|
||||
key: 'ownership',
|
||||
render: enrolmentInfo => enrolmentInfo.ownership,
|
||||
// todo add filtering options
|
||||
},
|
||||
{
|
||||
title: 'OS Version',
|
||||
dataIndex: 'deviceInfo',
|
||||
key: 'osVersion',
|
||||
render: deviceInfo => deviceInfo.osVersion,
|
||||
// todo add filtering options
|
||||
},
|
||||
{
|
||||
title: 'IMEI',
|
||||
dataIndex: 'properties',
|
||||
key: 'imei',
|
||||
render: properties => {
|
||||
let imei = 'not-found';
|
||||
for (let i = 0; i < properties.length; i++) {
|
||||
if (properties[i].name === 'IMEI') {
|
||||
imei = properties[i].value;
|
||||
}
|
||||
// todo add filtering options
|
||||
},
|
||||
{
|
||||
title: 'Status',
|
||||
dataIndex: 'enrolmentInfo',
|
||||
key: 'status',
|
||||
render: enrolmentInfo => enrolmentInfo.status
|
||||
// todo add filtering options
|
||||
},
|
||||
{
|
||||
title: 'Ownership',
|
||||
dataIndex: 'enrolmentInfo',
|
||||
key: 'ownership',
|
||||
render: enrolmentInfo => enrolmentInfo.ownership
|
||||
// todo add filtering options
|
||||
},
|
||||
{
|
||||
title: 'OS Version',
|
||||
dataIndex: 'deviceInfo',
|
||||
key: 'osVersion',
|
||||
render: deviceInfo => deviceInfo.osVersion
|
||||
// todo add filtering options
|
||||
},
|
||||
{
|
||||
title: 'IMEI',
|
||||
dataIndex: 'properties',
|
||||
key: 'imei',
|
||||
render: properties => {
|
||||
let imei = "not-found";
|
||||
for (let i = 0; i < properties.length; i++) {
|
||||
if (properties[i].name === "IMEI") {
|
||||
imei = properties[i].value;
|
||||
}
|
||||
}
|
||||
return imei;
|
||||
}
|
||||
// todo add filtering options
|
||||
}
|
||||
return imei;
|
||||
},
|
||||
// todo add filtering options
|
||||
},
|
||||
];
|
||||
|
||||
class DeviceUninstall extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
TimeAgo.addLocale(en);
|
||||
this.state = {
|
||||
data: [],
|
||||
pagination: {},
|
||||
constructor(props) {
|
||||
super(props);
|
||||
TimeAgo.addLocale(en);
|
||||
this.state = {
|
||||
data: [],
|
||||
pagination: {},
|
||||
loading: false,
|
||||
selectedRows: [],
|
||||
isForbidden: false,
|
||||
};
|
||||
}
|
||||
|
||||
rowSelection = {
|
||||
onChange: (selectedRowKeys, selectedRows) => {
|
||||
this.setState({
|
||||
selectedRows: selectedRows,
|
||||
});
|
||||
},
|
||||
getCheckboxProps: record => ({
|
||||
disabled: record.name === 'Disabled User', // Column configuration not to be checked
|
||||
name: record.name,
|
||||
}),
|
||||
};
|
||||
|
||||
componentDidMount() {
|
||||
this.fetch();
|
||||
}
|
||||
|
||||
// fetch data from api
|
||||
fetch = (params = {}) => {
|
||||
const config = this.props.context;
|
||||
this.setState({ loading: true });
|
||||
const { deviceType } = this.props;
|
||||
// get current page
|
||||
const currentPage = params.hasOwnProperty('page') ? params.page : 1;
|
||||
|
||||
const extraParams = {
|
||||
offset: 10 * (currentPage - 1), // calculate the offset
|
||||
limit: 10,
|
||||
status: 'ACTIVE',
|
||||
};
|
||||
|
||||
if (deviceType !== 'ANY') {
|
||||
extraParams.type = deviceType;
|
||||
}
|
||||
|
||||
// note: encode with '%26' not '&'
|
||||
const encodedExtraParams = Object.keys(extraParams)
|
||||
.map(key => key + '=' + extraParams[key])
|
||||
.join('&');
|
||||
|
||||
const uuid = this.props.uuid;
|
||||
axios
|
||||
.get(
|
||||
window.location.origin +
|
||||
config.serverConfig.invoker.uri +
|
||||
config.serverConfig.invoker.store +
|
||||
'/subscription/' +
|
||||
uuid +
|
||||
'/' +
|
||||
'/devices?' +
|
||||
encodedExtraParams,
|
||||
)
|
||||
.then(res => {
|
||||
if (res.status === 200) {
|
||||
const pagination = { ...this.state.pagination };
|
||||
this.setState({
|
||||
loading: false,
|
||||
selectedRows: [],
|
||||
isForbidden: false
|
||||
};
|
||||
}
|
||||
|
||||
rowSelection = {
|
||||
onChange: (selectedRowKeys, selectedRows) => {
|
||||
this.setState({
|
||||
selectedRows: selectedRows
|
||||
})
|
||||
},
|
||||
getCheckboxProps: record => ({
|
||||
disabled: record.name === 'Disabled User', // Column configuration not to be checked
|
||||
name: record.name,
|
||||
}),
|
||||
};
|
||||
|
||||
componentDidMount() {
|
||||
this.fetch();
|
||||
}
|
||||
|
||||
//fetch data from api
|
||||
fetch = (params = {}) => {
|
||||
const config = this.props.context;
|
||||
this.setState({loading: true});
|
||||
const {deviceType} = this.props;
|
||||
// get current page
|
||||
const currentPage = (params.hasOwnProperty("page")) ? params.page : 1;
|
||||
|
||||
const extraParams = {
|
||||
offset: 10 * (currentPage - 1), //calculate the offset
|
||||
limit: 10,
|
||||
status: "ACTIVE",
|
||||
};
|
||||
|
||||
if (deviceType !== 'ANY') {
|
||||
extraParams.type = deviceType;
|
||||
data: res.data.data,
|
||||
pagination,
|
||||
});
|
||||
}
|
||||
|
||||
// note: encode with '%26' not '&'
|
||||
const encodedExtraParams = Object.keys(extraParams).map(key => key + '=' + extraParams[key]).join('&');
|
||||
|
||||
const uuid = this.props.uuid;
|
||||
axios.get(
|
||||
window.location.origin + config.serverConfig.invoker.uri + config.serverConfig.invoker.store + "/subscription/" + uuid + "/" +
|
||||
"/devices?" + encodedExtraParams,
|
||||
).then(res => {
|
||||
if (res.status === 200) {
|
||||
const pagination = {...this.state.pagination};
|
||||
this.setState({
|
||||
loading: false,
|
||||
data: res.data.data,
|
||||
pagination,
|
||||
});
|
||||
}
|
||||
}).catch((error) => {
|
||||
handleApiError(error, "Error occurred while trying to load devices.", true);
|
||||
if (error.hasOwnProperty("response") && error.response.status === 403) {
|
||||
this.setState({
|
||||
isForbidden: true,
|
||||
loading: false
|
||||
})
|
||||
} else {
|
||||
this.setState({
|
||||
loading: false
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
handleTableChange = (pagination, filters, sorter) => {
|
||||
const pager = {...this.state.pagination};
|
||||
pager.current = pagination.current;
|
||||
this.setState({
|
||||
pagination: pager,
|
||||
});
|
||||
this.fetch({
|
||||
results: pagination.pageSize,
|
||||
page: pagination.current,
|
||||
sortField: sorter.field,
|
||||
sortOrder: sorter.order,
|
||||
...filters,
|
||||
});
|
||||
};
|
||||
|
||||
uninstall = (timestamp = null) => {
|
||||
const {selectedRows} = this.state;
|
||||
const payload = [];
|
||||
selectedRows.map(device => {
|
||||
payload.push({
|
||||
id: device.deviceIdentifier,
|
||||
type: device.type
|
||||
});
|
||||
});
|
||||
this.props.onUninstall("devices", payload, "uninstall", timestamp);
|
||||
};
|
||||
|
||||
render() {
|
||||
const {data, pagination, loading, selectedRows} = this.state;
|
||||
return (
|
||||
<div>
|
||||
<Text>
|
||||
Start uninstalling the application for devices by selecting the corresponding devices.
|
||||
Select uninstall to automatically start uninstalling the application for the respective devices.
|
||||
</Text>
|
||||
{(this.state.isForbidden) && (
|
||||
<Alert
|
||||
message="You don't have permission to view installed devices."
|
||||
type="warning"
|
||||
banner
|
||||
closable/>
|
||||
)}
|
||||
<Table
|
||||
style={{paddingTop: 20}}
|
||||
columns={columns}
|
||||
rowKey={record => record.deviceIdentifier}
|
||||
dataSource={data.devices}
|
||||
pagination={{
|
||||
...pagination,
|
||||
size: "small",
|
||||
total: data.count,
|
||||
showTotal: (total, range) => `showing ${range[0]}-${range[1]} of ${total} devices`
|
||||
}}
|
||||
loading={loading}
|
||||
onChange={this.handleTableChange}
|
||||
rowSelection={this.rowSelection}
|
||||
scroll={{x: 1000}}
|
||||
/>
|
||||
<InstallModalFooter type="Uninstall" operation={this.uninstall} disabled={selectedRows.length === 0}/>
|
||||
</div>
|
||||
})
|
||||
.catch(error => {
|
||||
handleApiError(
|
||||
error,
|
||||
'Error occurred while trying to load devices.',
|
||||
true,
|
||||
);
|
||||
}
|
||||
if (error.hasOwnProperty('response') && error.response.status === 403) {
|
||||
this.setState({
|
||||
isForbidden: true,
|
||||
loading: false,
|
||||
});
|
||||
} else {
|
||||
this.setState({
|
||||
loading: false,
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
handleTableChange = (pagination, filters, sorter) => {
|
||||
const pager = { ...this.state.pagination };
|
||||
pager.current = pagination.current;
|
||||
this.setState({
|
||||
pagination: pager,
|
||||
});
|
||||
this.fetch({
|
||||
results: pagination.pageSize,
|
||||
page: pagination.current,
|
||||
sortField: sorter.field,
|
||||
sortOrder: sorter.order,
|
||||
...filters,
|
||||
});
|
||||
};
|
||||
|
||||
uninstall = (timestamp = null) => {
|
||||
const { selectedRows } = this.state;
|
||||
const payload = [];
|
||||
selectedRows.map(device => {
|
||||
payload.push({
|
||||
id: device.deviceIdentifier,
|
||||
type: device.type,
|
||||
});
|
||||
});
|
||||
this.props.onUninstall('devices', payload, 'uninstall', timestamp);
|
||||
};
|
||||
|
||||
render() {
|
||||
const { data, pagination, loading, selectedRows } = this.state;
|
||||
return (
|
||||
<div>
|
||||
<Text>
|
||||
Start uninstalling the application for devices by selecting the
|
||||
corresponding devices. Select uninstall to automatically start
|
||||
uninstalling the application for the respective devices.
|
||||
</Text>
|
||||
{this.state.isForbidden && (
|
||||
<Alert
|
||||
message="You don't have permission to view installed devices."
|
||||
type="warning"
|
||||
banner
|
||||
closable
|
||||
/>
|
||||
)}
|
||||
<Table
|
||||
style={{ paddingTop: 20 }}
|
||||
columns={columns}
|
||||
rowKey={record => record.deviceIdentifier}
|
||||
dataSource={data.devices}
|
||||
pagination={{
|
||||
...pagination,
|
||||
size: 'small',
|
||||
total: data.count,
|
||||
showTotal: (total, range) =>
|
||||
`showing ${range[0]}-${range[1]} of ${total} devices`,
|
||||
}}
|
||||
loading={loading}
|
||||
onChange={this.handleTableChange}
|
||||
rowSelection={this.rowSelection}
|
||||
scroll={{ x: 1000 }}
|
||||
/>
|
||||
<InstallModalFooter
|
||||
type="Uninstall"
|
||||
operation={this.uninstall}
|
||||
disabled={selectedRows.length === 0}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default withConfigContext(DeviceUninstall);
|
||||
|
||||
@ -16,124 +16,139 @@
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
import {Typography, Select, Spin, message, notification, Button, Alert} from "antd";
|
||||
import React from 'react';
|
||||
import { Typography, Select, Spin, Alert } from 'antd';
|
||||
import debounce from 'lodash.debounce';
|
||||
import axios from "axios";
|
||||
import {withConfigContext} from "../../../../context/ConfigContext";
|
||||
import {handleApiError} from "../../../../js/Utils";
|
||||
import InstallModalFooter from "./installModalFooter/InstallModalFooter";
|
||||
|
||||
const {Text} = Typography;
|
||||
const {Option} = Select;
|
||||
import axios from 'axios';
|
||||
import { withConfigContext } from '../../../../context/ConfigContext';
|
||||
import { handleApiError } from '../../../../js/Utils';
|
||||
import InstallModalFooter from './installModalFooter/InstallModalFooter';
|
||||
|
||||
const { Text } = Typography;
|
||||
const { Option } = Select;
|
||||
|
||||
class GroupInstall extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.lastFetchId = 0;
|
||||
this.fetchUser = debounce(this.fetchUser, 800);
|
||||
}
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.lastFetchId = 0;
|
||||
this.fetchUser = debounce(this.fetchUser, 800);
|
||||
}
|
||||
state = {
|
||||
data: [],
|
||||
value: [],
|
||||
fetching: false,
|
||||
isForbidden: false,
|
||||
};
|
||||
|
||||
state = {
|
||||
data: [],
|
||||
value: [],
|
||||
fetching: false,
|
||||
isForbidden: false
|
||||
};
|
||||
fetchUser = value => {
|
||||
this.lastFetchId += 1;
|
||||
const fetchId = this.lastFetchId;
|
||||
const config = this.props.context;
|
||||
this.setState({ data: [], fetching: true });
|
||||
|
||||
fetchUser = value => {
|
||||
this.lastFetchId += 1;
|
||||
const fetchId = this.lastFetchId;
|
||||
const config = this.props.context;
|
||||
this.setState({data: [], fetching: true});
|
||||
axios
|
||||
.get(
|
||||
window.location.origin +
|
||||
config.serverConfig.invoker.uri +
|
||||
config.serverConfig.invoker.deviceMgt +
|
||||
'/groups?name=' +
|
||||
value,
|
||||
)
|
||||
.then(res => {
|
||||
if (res.status === 200) {
|
||||
if (fetchId !== this.lastFetchId) {
|
||||
// for fetch callback order
|
||||
return;
|
||||
}
|
||||
|
||||
axios.get(
|
||||
window.location.origin+ config.serverConfig.invoker.uri + config.serverConfig.invoker.deviceMgt+"/groups?name=" + value,
|
||||
const data = res.data.data.deviceGroups.map(group => ({
|
||||
text: group.name,
|
||||
value: group.name,
|
||||
}));
|
||||
|
||||
).then(res => {
|
||||
if (res.status === 200) {
|
||||
if (fetchId !== this.lastFetchId) {
|
||||
// for fetch callback order
|
||||
return;
|
||||
}
|
||||
|
||||
const data = res.data.data.deviceGroups.map(group => ({
|
||||
text: group.name,
|
||||
value: group.name,
|
||||
}));
|
||||
|
||||
this.setState({data, fetching: false});
|
||||
}
|
||||
|
||||
}).catch((error) => {
|
||||
handleApiError(error,"Error occurred while trying to load groups.", true);
|
||||
if (error.hasOwnProperty("response") && error.response.status === 403) {
|
||||
this.setState({
|
||||
isForbidden: true,
|
||||
loading: false
|
||||
})
|
||||
} else {
|
||||
this.setState({
|
||||
loading: false
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
handleChange = value => {
|
||||
this.setState({
|
||||
value,
|
||||
data: [],
|
||||
fetching: false,
|
||||
});
|
||||
};
|
||||
|
||||
install = () =>{
|
||||
const {value} = this.state;
|
||||
const data = [];
|
||||
value.map(val=>{
|
||||
data.push(val.key);
|
||||
});
|
||||
this.props.onInstall("group", data, "install");
|
||||
};
|
||||
|
||||
render() {
|
||||
|
||||
const {fetching, data, value} = this.state;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Text>Start installing the application for one or more groups by entering the corresponding group name. Select install to automatically start downloading the application for the respective device group/ groups.</Text>
|
||||
{(this.state.isForbidden) && (
|
||||
<Alert
|
||||
message="You don't have permission to view groups."
|
||||
type="warning"
|
||||
banner
|
||||
closable/>
|
||||
)}
|
||||
<br/>
|
||||
<br/>
|
||||
<Select
|
||||
mode="multiple"
|
||||
labelInValue
|
||||
value={value}
|
||||
placeholder="Search groups"
|
||||
notFoundContent={fetching ? <Spin size="small"/> : null}
|
||||
filterOption={false}
|
||||
onSearch={this.fetchUser}
|
||||
onChange={this.handleChange}
|
||||
style={{width: '100%'}}
|
||||
>
|
||||
{data.map(d => (
|
||||
<Option key={d.value}>{d.text}</Option>
|
||||
))}
|
||||
</Select>
|
||||
<InstallModalFooter type="Install" operation={this.install} disabled={value.length===0}/>
|
||||
</div>
|
||||
this.setState({ data, fetching: false });
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
handleApiError(
|
||||
error,
|
||||
'Error occurred while trying to load groups.',
|
||||
true,
|
||||
);
|
||||
}
|
||||
if (error.hasOwnProperty('response') && error.response.status === 403) {
|
||||
this.setState({
|
||||
isForbidden: true,
|
||||
loading: false,
|
||||
});
|
||||
} else {
|
||||
this.setState({
|
||||
loading: false,
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
handleChange = value => {
|
||||
this.setState({
|
||||
value,
|
||||
data: [],
|
||||
fetching: false,
|
||||
});
|
||||
};
|
||||
|
||||
install = () => {
|
||||
const { value } = this.state;
|
||||
const data = [];
|
||||
value.map(val => {
|
||||
data.push(val.key);
|
||||
});
|
||||
this.props.onInstall('group', data, 'install');
|
||||
};
|
||||
|
||||
render() {
|
||||
const { fetching, data, value } = this.state;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Text>
|
||||
Start installing the application for one or more groups by entering
|
||||
the corresponding group name. Select install to automatically start
|
||||
downloading the application for the respective device group/ groups.
|
||||
</Text>
|
||||
{this.state.isForbidden && (
|
||||
<Alert
|
||||
message="You don't have permission to view groups."
|
||||
type="warning"
|
||||
banner
|
||||
closable
|
||||
/>
|
||||
)}
|
||||
<br />
|
||||
<br />
|
||||
<Select
|
||||
mode="multiple"
|
||||
labelInValue
|
||||
value={value}
|
||||
placeholder="Search groups"
|
||||
notFoundContent={fetching ? <Spin size="small" /> : null}
|
||||
filterOption={false}
|
||||
onSearch={this.fetchUser}
|
||||
onChange={this.handleChange}
|
||||
style={{ width: '100%' }}
|
||||
>
|
||||
{data.map(d => (
|
||||
<Option key={d.value}>{d.text}</Option>
|
||||
))}
|
||||
</Select>
|
||||
<InstallModalFooter
|
||||
type="Install"
|
||||
operation={this.install}
|
||||
disabled={value.length === 0}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default withConfigContext(GroupInstall);
|
||||
export default withConfigContext(GroupInstall);
|
||||
|
||||
@ -16,126 +16,143 @@
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
import {Typography, Select, Spin, message, notification, Button, Alert} from "antd";
|
||||
import React from 'react';
|
||||
import { Typography, Select, Spin, Alert } from 'antd';
|
||||
import debounce from 'lodash.debounce';
|
||||
import axios from "axios";
|
||||
import {withConfigContext} from "../../../../context/ConfigContext";
|
||||
import {handleApiError} from "../../../../js/Utils";
|
||||
import InstallModalFooter from "./installModalFooter/InstallModalFooter";
|
||||
import axios from 'axios';
|
||||
import { withConfigContext } from '../../../../context/ConfigContext';
|
||||
import { handleApiError } from '../../../../js/Utils';
|
||||
import InstallModalFooter from './installModalFooter/InstallModalFooter';
|
||||
|
||||
const {Text} = Typography;
|
||||
const {Option} = Select;
|
||||
const { Text } = Typography;
|
||||
const { Option } = Select;
|
||||
|
||||
class GroupUninstall extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.lastFetchId = 0;
|
||||
this.fetchUser = debounce(this.fetchUser, 800);
|
||||
}
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.lastFetchId = 0;
|
||||
this.fetchUser = debounce(this.fetchUser, 800);
|
||||
}
|
||||
state = {
|
||||
data: [],
|
||||
value: [],
|
||||
fetching: false,
|
||||
isForbidden: false,
|
||||
};
|
||||
|
||||
state = {
|
||||
data: [],
|
||||
value: [],
|
||||
fetching: false,
|
||||
isForbidden: false
|
||||
};
|
||||
fetchUser = value => {
|
||||
this.lastFetchId += 1;
|
||||
const fetchId = this.lastFetchId;
|
||||
const config = this.props.context;
|
||||
this.setState({ data: [], fetching: true });
|
||||
|
||||
fetchUser = value => {
|
||||
this.lastFetchId += 1;
|
||||
const fetchId = this.lastFetchId;
|
||||
const config = this.props.context;
|
||||
this.setState({data: [], fetching: true});
|
||||
const uuid = this.props.uuid;
|
||||
|
||||
const uuid = this.props.uuid;
|
||||
axios
|
||||
.get(
|
||||
window.location.origin +
|
||||
config.serverConfig.invoker.uri +
|
||||
config.serverConfig.invoker.store +
|
||||
'/subscription/' +
|
||||
uuid +
|
||||
'/' +
|
||||
'/GROUP?',
|
||||
)
|
||||
.then(res => {
|
||||
if (res.status === 200) {
|
||||
if (fetchId !== this.lastFetchId) {
|
||||
// for fetch callback order
|
||||
return;
|
||||
}
|
||||
|
||||
axios.get(
|
||||
window.location.origin + config.serverConfig.invoker.uri + config.serverConfig.invoker.store + "/subscription/" + uuid + "/" +
|
||||
"/GROUP?",
|
||||
).then(res => {
|
||||
if (res.status === 200) {
|
||||
if (fetchId !== this.lastFetchId) {
|
||||
// for fetch callback order
|
||||
return;
|
||||
}
|
||||
const data = res.data.data.deviceGroups.map(group => ({
|
||||
text: group,
|
||||
value: group,
|
||||
}));
|
||||
|
||||
const data = res.data.data.deviceGroups.map(group => ({
|
||||
text: group,
|
||||
value: group,
|
||||
}));
|
||||
|
||||
this.setState({data, fetching: false});
|
||||
}
|
||||
|
||||
}).catch((error) => {
|
||||
handleApiError(error, "Error occurred while trying to load groups.", true);
|
||||
if (error.hasOwnProperty("response") && error.response.status === 403) {
|
||||
this.setState({
|
||||
isForbidden: true,
|
||||
loading: false
|
||||
})
|
||||
} else {
|
||||
this.setState({
|
||||
loading: false
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
handleChange = value => {
|
||||
this.setState({
|
||||
value,
|
||||
data: [],
|
||||
fetching: false,
|
||||
});
|
||||
};
|
||||
|
||||
uninstall = (timestamp = null) => {
|
||||
const {value} = this.state;
|
||||
const data = [];
|
||||
value.map(val => {
|
||||
data.push(val.key);
|
||||
});
|
||||
this.props.onUninstall("group", data, "uninstall", timestamp);
|
||||
};
|
||||
|
||||
render() {
|
||||
|
||||
const {fetching, data, value} = this.state;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Text>Start uninstalling the application for one or more groups by entering the corresponding group
|
||||
name. Select uninstall to automatically start uninstalling the application for the respective device
|
||||
group/ groups.</Text>
|
||||
{(this.state.isForbidden) && (
|
||||
<Alert
|
||||
message="You don't have permission to view installed groups."
|
||||
type="warning"
|
||||
banner
|
||||
closable/>
|
||||
)}
|
||||
<br/>
|
||||
<br/>
|
||||
<Select
|
||||
mode="multiple"
|
||||
labelInValue
|
||||
value={value}
|
||||
placeholder="Search groups"
|
||||
notFoundContent={fetching ? <Spin size="small"/> : null}
|
||||
filterOption={false}
|
||||
onSearch={this.fetchUser}
|
||||
onChange={this.handleChange}
|
||||
style={{width: '100%'}}>
|
||||
{data.map(d => (
|
||||
<Option key={d.value}>{d.text}</Option>
|
||||
))}
|
||||
</Select>
|
||||
<InstallModalFooter type="Uninstall" operation={this.uninstall} disabled={value.length === 0}/>
|
||||
</div>
|
||||
this.setState({ data, fetching: false });
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
handleApiError(
|
||||
error,
|
||||
'Error occurred while trying to load groups.',
|
||||
true,
|
||||
);
|
||||
}
|
||||
if (error.hasOwnProperty('response') && error.response.status === 403) {
|
||||
this.setState({
|
||||
isForbidden: true,
|
||||
loading: false,
|
||||
});
|
||||
} else {
|
||||
this.setState({
|
||||
loading: false,
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
handleChange = value => {
|
||||
this.setState({
|
||||
value,
|
||||
data: [],
|
||||
fetching: false,
|
||||
});
|
||||
};
|
||||
|
||||
uninstall = (timestamp = null) => {
|
||||
const { value } = this.state;
|
||||
const data = [];
|
||||
value.map(val => {
|
||||
data.push(val.key);
|
||||
});
|
||||
this.props.onUninstall('group', data, 'uninstall', timestamp);
|
||||
};
|
||||
|
||||
render() {
|
||||
const { fetching, data, value } = this.state;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Text>
|
||||
Start uninstalling the application for one or more groups by entering
|
||||
the corresponding group name. Select uninstall to automatically start
|
||||
uninstalling the application for the respective device group/ groups.
|
||||
</Text>
|
||||
{this.state.isForbidden && (
|
||||
<Alert
|
||||
message="You don't have permission to view installed groups."
|
||||
type="warning"
|
||||
banner
|
||||
closable
|
||||
/>
|
||||
)}
|
||||
<br />
|
||||
<br />
|
||||
<Select
|
||||
mode="multiple"
|
||||
labelInValue
|
||||
value={value}
|
||||
placeholder="Search groups"
|
||||
notFoundContent={fetching ? <Spin size="small" /> : null}
|
||||
filterOption={false}
|
||||
onSearch={this.fetchUser}
|
||||
onChange={this.handleChange}
|
||||
style={{ width: '100%' }}
|
||||
>
|
||||
{data.map(d => (
|
||||
<Option key={d.value}>{d.text}</Option>
|
||||
))}
|
||||
</Select>
|
||||
<InstallModalFooter
|
||||
type="Uninstall"
|
||||
operation={this.uninstall}
|
||||
disabled={value.length === 0}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default withConfigContext(GroupUninstall);
|
||||
|
||||
@ -16,123 +16,139 @@
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
import {Typography, Select, Spin, message, notification, Button, Alert} from "antd";
|
||||
import React from 'react';
|
||||
import { Typography, Select, Spin, Alert } from 'antd';
|
||||
import debounce from 'lodash.debounce';
|
||||
import axios from "axios";
|
||||
import {withConfigContext} from "../../../../context/ConfigContext";
|
||||
import {handleApiError} from "../../../../js/Utils";
|
||||
import InstallModalFooter from "./installModalFooter/InstallModalFooter";
|
||||
|
||||
const {Text} = Typography;
|
||||
const {Option} = Select;
|
||||
import axios from 'axios';
|
||||
import { withConfigContext } from '../../../../context/ConfigContext';
|
||||
import { handleApiError } from '../../../../js/Utils';
|
||||
import InstallModalFooter from './installModalFooter/InstallModalFooter';
|
||||
|
||||
const { Text } = Typography;
|
||||
const { Option } = Select;
|
||||
|
||||
class RoleInstall extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.lastFetchId = 0;
|
||||
this.fetchUser = debounce(this.fetchUser, 800);
|
||||
}
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.lastFetchId = 0;
|
||||
this.fetchUser = debounce(this.fetchUser, 800);
|
||||
}
|
||||
state = {
|
||||
data: [],
|
||||
value: [],
|
||||
fetching: false,
|
||||
isForbidden: false,
|
||||
};
|
||||
|
||||
state = {
|
||||
data: [],
|
||||
value: [],
|
||||
fetching: false,
|
||||
isForbidden: false
|
||||
};
|
||||
fetchUser = value => {
|
||||
const config = this.props.context;
|
||||
this.lastFetchId += 1;
|
||||
const fetchId = this.lastFetchId;
|
||||
this.setState({ data: [], fetching: true });
|
||||
|
||||
fetchUser = value => {
|
||||
const config = this.props.context;
|
||||
this.lastFetchId += 1;
|
||||
const fetchId = this.lastFetchId;
|
||||
this.setState({data: [], fetching: true});
|
||||
axios
|
||||
.get(
|
||||
window.location.origin +
|
||||
config.serverConfig.invoker.uri +
|
||||
config.serverConfig.invoker.deviceMgt +
|
||||
'/roles?filter=' +
|
||||
value,
|
||||
)
|
||||
.then(res => {
|
||||
if (res.status === 200) {
|
||||
if (fetchId !== this.lastFetchId) {
|
||||
// for fetch callback order
|
||||
return;
|
||||
}
|
||||
|
||||
axios.get(
|
||||
window.location.origin+ config.serverConfig.invoker.uri + config.serverConfig.invoker.deviceMgt+"/roles?filter=" + value,
|
||||
const data = res.data.data.roles.map(role => ({
|
||||
text: role,
|
||||
value: role,
|
||||
}));
|
||||
|
||||
).then(res => {
|
||||
if (res.status === 200) {
|
||||
if (fetchId !== this.lastFetchId) {
|
||||
// for fetch callback order
|
||||
return;
|
||||
}
|
||||
|
||||
const data = res.data.data.roles.map(role => ({
|
||||
text: role,
|
||||
value: role,
|
||||
}));
|
||||
|
||||
this.setState({data, fetching: false});
|
||||
}
|
||||
|
||||
}).catch((error) => {
|
||||
handleApiError(error,"Error occurred while trying to load roles.", true);
|
||||
if (error.hasOwnProperty("response") && error.response.status === 403) {
|
||||
this.setState({
|
||||
isForbidden: true,
|
||||
loading: false
|
||||
})
|
||||
} else {
|
||||
this.setState({
|
||||
loading: false
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
handleChange = value => {
|
||||
this.setState({
|
||||
value,
|
||||
data: [],
|
||||
fetching: false,
|
||||
});
|
||||
};
|
||||
|
||||
install = (timestamp=null) =>{
|
||||
const {value} = this.state;
|
||||
const data = [];
|
||||
value.map(val=>{
|
||||
data.push(val.key);
|
||||
});
|
||||
this.props.onInstall("role", data, "install", timestamp);
|
||||
};
|
||||
|
||||
render() {
|
||||
|
||||
const {fetching, data, value} = this.state;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Text>Start installing the application for one or more roles by entering the corresponding role name. Select install to automatically start downloading the application for the respective user role/roles.</Text>
|
||||
{(this.state.isForbidden) && (
|
||||
<Alert
|
||||
message="You don't have permission to view roles."
|
||||
type="warning"
|
||||
banner
|
||||
closable/>
|
||||
)}
|
||||
<br/>
|
||||
<br/>
|
||||
<Select
|
||||
mode="multiple"
|
||||
labelInValue
|
||||
value={value}
|
||||
placeholder="Search roles"
|
||||
notFoundContent={fetching ? <Spin size="small"/> : null}
|
||||
filterOption={false}
|
||||
onSearch={this.fetchUser}
|
||||
onChange={this.handleChange}
|
||||
style={{width: '100%'}}>
|
||||
{data.map(d => (
|
||||
<Option key={d.value}>{d.text}</Option>
|
||||
))}
|
||||
</Select>
|
||||
<InstallModalFooter type="Install" operation={this.install} disabled={value.length===0}/>
|
||||
</div>
|
||||
this.setState({ data, fetching: false });
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
handleApiError(
|
||||
error,
|
||||
'Error occurred while trying to load roles.',
|
||||
true,
|
||||
);
|
||||
}
|
||||
if (error.hasOwnProperty('response') && error.response.status === 403) {
|
||||
this.setState({
|
||||
isForbidden: true,
|
||||
loading: false,
|
||||
});
|
||||
} else {
|
||||
this.setState({
|
||||
loading: false,
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
handleChange = value => {
|
||||
this.setState({
|
||||
value,
|
||||
data: [],
|
||||
fetching: false,
|
||||
});
|
||||
};
|
||||
|
||||
install = (timestamp = null) => {
|
||||
const { value } = this.state;
|
||||
const data = [];
|
||||
value.map(val => {
|
||||
data.push(val.key);
|
||||
});
|
||||
this.props.onInstall('role', data, 'install', timestamp);
|
||||
};
|
||||
|
||||
render() {
|
||||
const { fetching, data, value } = this.state;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Text>
|
||||
Start installing the application for one or more roles by entering the
|
||||
corresponding role name. Select install to automatically start
|
||||
downloading the application for the respective user role/roles.
|
||||
</Text>
|
||||
{this.state.isForbidden && (
|
||||
<Alert
|
||||
message="You don't have permission to view roles."
|
||||
type="warning"
|
||||
banner
|
||||
closable
|
||||
/>
|
||||
)}
|
||||
<br />
|
||||
<br />
|
||||
<Select
|
||||
mode="multiple"
|
||||
labelInValue
|
||||
value={value}
|
||||
placeholder="Search roles"
|
||||
notFoundContent={fetching ? <Spin size="small" /> : null}
|
||||
filterOption={false}
|
||||
onSearch={this.fetchUser}
|
||||
onChange={this.handleChange}
|
||||
style={{ width: '100%' }}
|
||||
>
|
||||
{data.map(d => (
|
||||
<Option key={d.value}>{d.text}</Option>
|
||||
))}
|
||||
</Select>
|
||||
<InstallModalFooter
|
||||
type="Install"
|
||||
operation={this.install}
|
||||
disabled={value.length === 0}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default withConfigContext(RoleInstall);
|
||||
export default withConfigContext(RoleInstall);
|
||||
|
||||
@ -16,126 +16,143 @@
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
import {Typography, Select, Spin, message, notification, Button, Alert} from "antd";
|
||||
import React from 'react';
|
||||
import { Typography, Select, Spin, Alert } from 'antd';
|
||||
import debounce from 'lodash.debounce';
|
||||
import axios from "axios";
|
||||
import {withConfigContext} from "../../../../context/ConfigContext";
|
||||
import {handleApiError} from "../../../../js/Utils";
|
||||
import InstallModalFooter from "./installModalFooter/InstallModalFooter";
|
||||
import axios from 'axios';
|
||||
import { withConfigContext } from '../../../../context/ConfigContext';
|
||||
import { handleApiError } from '../../../../js/Utils';
|
||||
import InstallModalFooter from './installModalFooter/InstallModalFooter';
|
||||
|
||||
const {Text} = Typography;
|
||||
const {Option} = Select;
|
||||
const { Text } = Typography;
|
||||
const { Option } = Select;
|
||||
|
||||
class RoleUninstall extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.lastFetchId = 0;
|
||||
this.fetchUser = debounce(this.fetchUser, 800);
|
||||
}
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.lastFetchId = 0;
|
||||
this.fetchUser = debounce(this.fetchUser, 800);
|
||||
}
|
||||
state = {
|
||||
data: [],
|
||||
value: [],
|
||||
fetching: false,
|
||||
isForbidden: false,
|
||||
};
|
||||
|
||||
state = {
|
||||
data: [],
|
||||
value: [],
|
||||
fetching: false,
|
||||
isForbidden: false
|
||||
};
|
||||
fetchUser = value => {
|
||||
const config = this.props.context;
|
||||
this.lastFetchId += 1;
|
||||
const fetchId = this.lastFetchId;
|
||||
this.setState({ data: [], fetching: true });
|
||||
|
||||
fetchUser = value => {
|
||||
const config = this.props.context;
|
||||
this.lastFetchId += 1;
|
||||
const fetchId = this.lastFetchId;
|
||||
this.setState({data: [], fetching: true});
|
||||
const uuid = this.props.uuid;
|
||||
|
||||
const uuid = this.props.uuid;
|
||||
axios
|
||||
.get(
|
||||
window.location.origin +
|
||||
config.serverConfig.invoker.uri +
|
||||
config.serverConfig.invoker.store +
|
||||
'/subscription/' +
|
||||
uuid +
|
||||
'/' +
|
||||
'/ROLE?',
|
||||
)
|
||||
.then(res => {
|
||||
if (res.status === 200) {
|
||||
if (fetchId !== this.lastFetchId) {
|
||||
// for fetch callback order
|
||||
return;
|
||||
}
|
||||
|
||||
axios.get(
|
||||
window.location.origin + config.serverConfig.invoker.uri + config.serverConfig.invoker.store + "/subscription/" + uuid + "/" +
|
||||
"/ROLE?",
|
||||
).then(res => {
|
||||
if (res.status === 200) {
|
||||
if (fetchId !== this.lastFetchId) {
|
||||
// for fetch callback order
|
||||
return;
|
||||
}
|
||||
const data = res.data.data.roles.map(role => ({
|
||||
text: role,
|
||||
value: role,
|
||||
}));
|
||||
|
||||
const data = res.data.data.roles.map(role => ({
|
||||
text: role,
|
||||
value: role,
|
||||
}));
|
||||
|
||||
this.setState({data, fetching: false});
|
||||
}
|
||||
|
||||
}).catch((error) => {
|
||||
handleApiError(error, "Error occurred while trying to load roles.", true);
|
||||
if (error.hasOwnProperty("response") && error.response.status === 403) {
|
||||
this.setState({
|
||||
isForbidden: true,
|
||||
loading: false
|
||||
})
|
||||
} else {
|
||||
this.setState({
|
||||
loading: false
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
handleChange = value => {
|
||||
this.setState({
|
||||
value,
|
||||
data: [],
|
||||
fetching: false,
|
||||
});
|
||||
};
|
||||
|
||||
uninstall = (timestamp = null) => {
|
||||
const {value} = this.state;
|
||||
const data = [];
|
||||
value.map(val => {
|
||||
data.push(val.key);
|
||||
});
|
||||
this.props.onUninstall("role", data, "uninstall", timestamp);
|
||||
};
|
||||
|
||||
render() {
|
||||
const {fetching, data, value} = this.state;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Text>Start uninstalling the application for one or more roles by entering the corresponding role name.
|
||||
Select uninstall to automatically start uninstalling the application for the respective user
|
||||
role/roles.</Text>
|
||||
{(this.state.isForbidden) && (
|
||||
<Alert
|
||||
message="You don't have permission to view uninstalled roles."
|
||||
type="warning"
|
||||
banner
|
||||
closable/>
|
||||
)}
|
||||
<br/>
|
||||
<br/>
|
||||
<Select
|
||||
mode="multiple"
|
||||
labelInValue
|
||||
value={value}
|
||||
placeholder="Search roles"
|
||||
notFoundContent={fetching ? <Spin size="small"/> : null}
|
||||
filterOption={false}
|
||||
onSearch={this.fetchUser}
|
||||
onChange={this.handleChange}
|
||||
style={{width: '100%'}}
|
||||
>
|
||||
{data.map(d => (
|
||||
<Option key={d.value}>{d.text}</Option>
|
||||
))}
|
||||
</Select>
|
||||
<InstallModalFooter type="Uninstall" operation={this.uninstall} disabled={value.length === 0}/>
|
||||
</div>
|
||||
this.setState({ data, fetching: false });
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
handleApiError(
|
||||
error,
|
||||
'Error occurred while trying to load roles.',
|
||||
true,
|
||||
);
|
||||
}
|
||||
if (error.hasOwnProperty('response') && error.response.status === 403) {
|
||||
this.setState({
|
||||
isForbidden: true,
|
||||
loading: false,
|
||||
});
|
||||
} else {
|
||||
this.setState({
|
||||
loading: false,
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
handleChange = value => {
|
||||
this.setState({
|
||||
value,
|
||||
data: [],
|
||||
fetching: false,
|
||||
});
|
||||
};
|
||||
|
||||
uninstall = (timestamp = null) => {
|
||||
const { value } = this.state;
|
||||
const data = [];
|
||||
value.map(val => {
|
||||
data.push(val.key);
|
||||
});
|
||||
this.props.onUninstall('role', data, 'uninstall', timestamp);
|
||||
};
|
||||
|
||||
render() {
|
||||
const { fetching, data, value } = this.state;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Text>
|
||||
Start uninstalling the application for one or more roles by entering
|
||||
the corresponding role name. Select uninstall to automatically start
|
||||
uninstalling the application for the respective user role/roles.
|
||||
</Text>
|
||||
{this.state.isForbidden && (
|
||||
<Alert
|
||||
message="You don't have permission to view uninstalled roles."
|
||||
type="warning"
|
||||
banner
|
||||
closable
|
||||
/>
|
||||
)}
|
||||
<br />
|
||||
<br />
|
||||
<Select
|
||||
mode="multiple"
|
||||
labelInValue
|
||||
value={value}
|
||||
placeholder="Search roles"
|
||||
notFoundContent={fetching ? <Spin size="small" /> : null}
|
||||
filterOption={false}
|
||||
onSearch={this.fetchUser}
|
||||
onChange={this.handleChange}
|
||||
style={{ width: '100%' }}
|
||||
>
|
||||
{data.map(d => (
|
||||
<Option key={d.value}>{d.text}</Option>
|
||||
))}
|
||||
</Select>
|
||||
<InstallModalFooter
|
||||
type="Uninstall"
|
||||
operation={this.uninstall}
|
||||
disabled={value.length === 0}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default withConfigContext(RoleUninstall);
|
||||
|
||||
@ -16,124 +16,139 @@
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
import {Typography, Select, Spin, message, notification, Button, Alert} from "antd";
|
||||
import React from 'react';
|
||||
import { Typography, Select, Spin, Alert } from 'antd';
|
||||
import debounce from 'lodash.debounce';
|
||||
import axios from "axios";
|
||||
import {withConfigContext} from "../../../../context/ConfigContext";
|
||||
import {handleApiError} from "../../../../js/Utils";
|
||||
import InstallModalFooter from "./installModalFooter/InstallModalFooter";
|
||||
|
||||
const {Text} = Typography;
|
||||
const {Option} = Select;
|
||||
import axios from 'axios';
|
||||
import { withConfigContext } from '../../../../context/ConfigContext';
|
||||
import { handleApiError } from '../../../../js/Utils';
|
||||
import InstallModalFooter from './installModalFooter/InstallModalFooter';
|
||||
|
||||
const { Text } = Typography;
|
||||
const { Option } = Select;
|
||||
|
||||
class UserInstall extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.lastFetchId = 0;
|
||||
this.fetchUser = debounce(this.fetchUser, 800);
|
||||
}
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.lastFetchId = 0;
|
||||
this.fetchUser = debounce(this.fetchUser, 800);
|
||||
}
|
||||
state = {
|
||||
data: [],
|
||||
value: [],
|
||||
fetching: false,
|
||||
};
|
||||
|
||||
state = {
|
||||
data: [],
|
||||
value: [],
|
||||
fetching: false,
|
||||
};
|
||||
fetchUser = value => {
|
||||
const config = this.props.context;
|
||||
this.lastFetchId += 1;
|
||||
const fetchId = this.lastFetchId;
|
||||
this.setState({ data: [], fetching: true });
|
||||
|
||||
fetchUser = value => {
|
||||
const config = this.props.context;
|
||||
this.lastFetchId += 1;
|
||||
const fetchId = this.lastFetchId;
|
||||
this.setState({data: [], fetching: true});
|
||||
// send request to the invoker
|
||||
axios
|
||||
.get(
|
||||
window.location.origin +
|
||||
config.serverConfig.invoker.uri +
|
||||
config.serverConfig.invoker.deviceMgt +
|
||||
'/users/search?username=' +
|
||||
value,
|
||||
)
|
||||
.then(res => {
|
||||
if (res.status === 200) {
|
||||
if (fetchId !== this.lastFetchId) {
|
||||
// for fetch callback order
|
||||
return;
|
||||
}
|
||||
|
||||
const data = res.data.data.users.map(user => ({
|
||||
text: user.username,
|
||||
value: user.username,
|
||||
}));
|
||||
|
||||
//send request to the invoker
|
||||
axios.get(
|
||||
window.location.origin+ config.serverConfig.invoker.uri + config.serverConfig.invoker.deviceMgt+"/users/search?username=" + value,
|
||||
|
||||
).then(res => {
|
||||
if (res.status === 200) {
|
||||
if (fetchId !== this.lastFetchId) {
|
||||
// for fetch callback order
|
||||
return;
|
||||
}
|
||||
|
||||
const data = res.data.data.users.map(user => ({
|
||||
text: user.username,
|
||||
value: user.username,
|
||||
}));
|
||||
|
||||
this.setState({data, fetching: false});
|
||||
}
|
||||
|
||||
}).catch((error) => {
|
||||
handleApiError(error,"Error occurred while trying to load users.", true);
|
||||
if (error.hasOwnProperty("response") && error.response.status === 403) {
|
||||
this.setState({
|
||||
isForbidden: true,
|
||||
loading: false
|
||||
})
|
||||
} else {
|
||||
this.setState({
|
||||
loading: false
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
handleChange = value => {
|
||||
this.setState({
|
||||
value,
|
||||
data: [],
|
||||
fetching: false,
|
||||
isForbidden: false
|
||||
});
|
||||
};
|
||||
|
||||
install = (timestamp=null) => {
|
||||
const {value} = this.state;
|
||||
const data = [];
|
||||
value.map(val => {
|
||||
data.push(val.key);
|
||||
});
|
||||
this.props.onInstall("user", data, "install",timestamp);
|
||||
};
|
||||
|
||||
render() {
|
||||
const {fetching, data, value} = this.state;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Text>Start installing the application for one or more users by entering the corresponding user name. Select install to automatically start downloading the application for the respective user/users. </Text>
|
||||
{(this.state.isForbidden) && (
|
||||
<Alert
|
||||
message="You don't have permission to view users."
|
||||
type="warning"
|
||||
banner
|
||||
closable/>
|
||||
)}
|
||||
<p>Select users</p>
|
||||
<Select
|
||||
mode="multiple"
|
||||
labelInValue
|
||||
value={value}
|
||||
placeholder="Enter the username"
|
||||
notFoundContent={fetching ? <Spin size="small"/> : null}
|
||||
filterOption={false}
|
||||
onSearch={this.fetchUser}
|
||||
onChange={this.handleChange}
|
||||
style={{width: '100%'}}
|
||||
>
|
||||
{data.map(d => (
|
||||
<Option key={d.value}>{d.text}</Option>
|
||||
))}
|
||||
</Select>
|
||||
<InstallModalFooter type="Install" operation={this.install} disabled={value.length===0}/>
|
||||
</div>
|
||||
this.setState({ data, fetching: false });
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
handleApiError(
|
||||
error,
|
||||
'Error occurred while trying to load users.',
|
||||
true,
|
||||
);
|
||||
}
|
||||
if (error.hasOwnProperty('response') && error.response.status === 403) {
|
||||
this.setState({
|
||||
isForbidden: true,
|
||||
loading: false,
|
||||
});
|
||||
} else {
|
||||
this.setState({
|
||||
loading: false,
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
handleChange = value => {
|
||||
this.setState({
|
||||
value,
|
||||
data: [],
|
||||
fetching: false,
|
||||
isForbidden: false,
|
||||
});
|
||||
};
|
||||
|
||||
install = (timestamp = null) => {
|
||||
const { value } = this.state;
|
||||
const data = [];
|
||||
value.map(val => {
|
||||
data.push(val.key);
|
||||
});
|
||||
this.props.onInstall('user', data, 'install', timestamp);
|
||||
};
|
||||
|
||||
render() {
|
||||
const { fetching, data, value } = this.state;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Text>
|
||||
Start installing the application for one or more users by entering the
|
||||
corresponding user name. Select install to automatically start
|
||||
downloading the application for the respective user/users.{' '}
|
||||
</Text>
|
||||
{this.state.isForbidden && (
|
||||
<Alert
|
||||
message="You don't have permission to view users."
|
||||
type="warning"
|
||||
banner
|
||||
closable
|
||||
/>
|
||||
)}
|
||||
<p>Select users</p>
|
||||
<Select
|
||||
mode="multiple"
|
||||
labelInValue
|
||||
value={value}
|
||||
placeholder="Enter the username"
|
||||
notFoundContent={fetching ? <Spin size="small" /> : null}
|
||||
filterOption={false}
|
||||
onSearch={this.fetchUser}
|
||||
onChange={this.handleChange}
|
||||
style={{ width: '100%' }}
|
||||
>
|
||||
{data.map(d => (
|
||||
<Option key={d.value}>{d.text}</Option>
|
||||
))}
|
||||
</Select>
|
||||
<InstallModalFooter
|
||||
type="Install"
|
||||
operation={this.install}
|
||||
disabled={value.length === 0}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default withConfigContext(UserInstall);
|
||||
export default withConfigContext(UserInstall);
|
||||
|
||||
@ -16,123 +16,141 @@
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
import {Typography, Select, Spin, message, notification, Button, Alert} from "antd";
|
||||
import React from 'react';
|
||||
import { Typography, Select, Spin, Alert } from 'antd';
|
||||
import debounce from 'lodash.debounce';
|
||||
import axios from "axios";
|
||||
import {withConfigContext} from "../../../../context/ConfigContext";
|
||||
import {handleApiError} from "../../../../js/Utils";
|
||||
import InstallModalFooter from "./installModalFooter/InstallModalFooter";
|
||||
import axios from 'axios';
|
||||
import { withConfigContext } from '../../../../context/ConfigContext';
|
||||
import { handleApiError } from '../../../../js/Utils';
|
||||
import InstallModalFooter from './installModalFooter/InstallModalFooter';
|
||||
|
||||
const {Text} = Typography;
|
||||
const {Option} = Select;
|
||||
const { Text } = Typography;
|
||||
const { Option } = Select;
|
||||
|
||||
class UserUninstall extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.lastFetchId = 0;
|
||||
this.fetchUser = debounce(this.fetchUser, 800);
|
||||
}
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.lastFetchId = 0;
|
||||
this.fetchUser = debounce(this.fetchUser, 800);
|
||||
}
|
||||
state = {
|
||||
data: [],
|
||||
value: [],
|
||||
fetching: false,
|
||||
isForbidden: false,
|
||||
};
|
||||
|
||||
state = {
|
||||
data: [],
|
||||
value: [],
|
||||
fetching: false,
|
||||
isForbidden: false
|
||||
};
|
||||
fetchUser = value => {
|
||||
const config = this.props.context;
|
||||
this.lastFetchId += 1;
|
||||
const fetchId = this.lastFetchId;
|
||||
this.setState({ data: [], fetching: true });
|
||||
|
||||
fetchUser = (value) => {
|
||||
const config = this.props.context;
|
||||
this.lastFetchId += 1;
|
||||
const fetchId = this.lastFetchId;
|
||||
this.setState({data: [], fetching: true});
|
||||
const uuid = this.props.uuid;
|
||||
|
||||
const uuid = this.props.uuid;
|
||||
axios
|
||||
.get(
|
||||
window.location.origin +
|
||||
config.serverConfig.invoker.uri +
|
||||
config.serverConfig.invoker.store +
|
||||
'/subscription/' +
|
||||
uuid +
|
||||
'/' +
|
||||
'/USER?',
|
||||
)
|
||||
.then(res => {
|
||||
if (res.status === 200) {
|
||||
if (fetchId !== this.lastFetchId) {
|
||||
// for fetch callback order
|
||||
return;
|
||||
}
|
||||
const data = res.data.data.users.map(user => ({
|
||||
text: user,
|
||||
value: user,
|
||||
}));
|
||||
|
||||
axios.get(
|
||||
window.location.origin + config.serverConfig.invoker.uri + config.serverConfig.invoker.store + "/subscription/" + uuid + "/" +
|
||||
"/USER?",
|
||||
).then(res => {
|
||||
if (res.status === 200) {
|
||||
if (fetchId !== this.lastFetchId) {
|
||||
// for fetch callback order
|
||||
return;
|
||||
}
|
||||
const data = res.data.data.users.map(user => ({
|
||||
text: user,
|
||||
value: user,
|
||||
}));
|
||||
|
||||
this.setState({data, fetching: false});
|
||||
}
|
||||
|
||||
}).catch((error) => {
|
||||
handleApiError(error, "Error occurred while trying to load users.", true);
|
||||
if (error.hasOwnProperty("response") && error.response.status === 403) {
|
||||
this.setState({
|
||||
isForbidden: true,
|
||||
loading: false
|
||||
})
|
||||
} else {
|
||||
this.setState({
|
||||
loading: false
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
handleChange = value => {
|
||||
this.setState({
|
||||
value,
|
||||
data: [],
|
||||
fetching: false,
|
||||
});
|
||||
};
|
||||
|
||||
uninstall = (timestamp=null) => {
|
||||
const {value} = this.state;
|
||||
const data = [];
|
||||
value.map(val => {
|
||||
data.push(val.key);
|
||||
});
|
||||
this.props.onUninstall("user", data, "uninstall",timestamp);
|
||||
};
|
||||
|
||||
render() {
|
||||
const {fetching, data, value} = this.state;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Text>Start uninstalling the application for one or more users by entering the corresponding user name.
|
||||
Select uninstall to automatically start uninstalling the application for the respective
|
||||
user/users. </Text>
|
||||
{(this.state.isForbidden) && (
|
||||
<Alert
|
||||
message="You don't have permission to view uninstalled users."
|
||||
type="warning"
|
||||
banner
|
||||
closable/>
|
||||
)}
|
||||
<p>Select users</p>
|
||||
<Select
|
||||
mode="multiple"
|
||||
labelInValue
|
||||
value={value}
|
||||
placeholder="Enter the username"
|
||||
notFoundContent={fetching ? <Spin size="small"/> : null}
|
||||
filterOption={false}
|
||||
onSearch={this.fetchUser}
|
||||
onChange={this.handleChange}
|
||||
style={{width: '100%'}}>
|
||||
{data.map(d => (
|
||||
<Option key={d.value}>{d.text}</Option>
|
||||
))}
|
||||
</Select>
|
||||
<InstallModalFooter type="Uninstall" operation={this.uninstall} disabled={value.length === 0}/>
|
||||
</div>
|
||||
this.setState({ data, fetching: false });
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
handleApiError(
|
||||
error,
|
||||
'Error occurred while trying to load users.',
|
||||
true,
|
||||
);
|
||||
}
|
||||
if (error.hasOwnProperty('response') && error.response.status === 403) {
|
||||
this.setState({
|
||||
isForbidden: true,
|
||||
loading: false,
|
||||
});
|
||||
} else {
|
||||
this.setState({
|
||||
loading: false,
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
handleChange = value => {
|
||||
this.setState({
|
||||
value,
|
||||
data: [],
|
||||
fetching: false,
|
||||
});
|
||||
};
|
||||
|
||||
uninstall = (timestamp = null) => {
|
||||
const { value } = this.state;
|
||||
const data = [];
|
||||
value.map(val => {
|
||||
data.push(val.key);
|
||||
});
|
||||
this.props.onUninstall('user', data, 'uninstall', timestamp);
|
||||
};
|
||||
|
||||
render() {
|
||||
const { fetching, data, value } = this.state;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Text>
|
||||
Start uninstalling the application for one or more users by entering
|
||||
the corresponding user name. Select uninstall to automatically start
|
||||
uninstalling the application for the respective user/users.{' '}
|
||||
</Text>
|
||||
{this.state.isForbidden && (
|
||||
<Alert
|
||||
message="You don't have permission to view uninstalled users."
|
||||
type="warning"
|
||||
banner
|
||||
closable
|
||||
/>
|
||||
)}
|
||||
<p>Select users</p>
|
||||
<Select
|
||||
mode="multiple"
|
||||
labelInValue
|
||||
value={value}
|
||||
placeholder="Enter the username"
|
||||
notFoundContent={fetching ? <Spin size="small" /> : null}
|
||||
filterOption={false}
|
||||
onSearch={this.fetchUser}
|
||||
onChange={this.handleChange}
|
||||
style={{ width: '100%' }}
|
||||
>
|
||||
{data.map(d => (
|
||||
<Option key={d.value}>{d.text}</Option>
|
||||
))}
|
||||
</Select>
|
||||
<InstallModalFooter
|
||||
type="Uninstall"
|
||||
operation={this.uninstall}
|
||||
disabled={value.length === 0}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default withConfigContext(UserUninstall);
|
||||
|
||||
@ -16,72 +16,84 @@
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
import {Button, DatePicker, Checkbox} from "antd";
|
||||
import React from 'react';
|
||||
import { Button, DatePicker, Checkbox } from 'antd';
|
||||
|
||||
class InstallModalFooter extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
scheduledTime: null,
|
||||
isScheduledInstallVisible: false
|
||||
}
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
scheduledTime: null,
|
||||
isScheduledInstallVisible: false,
|
||||
};
|
||||
}
|
||||
|
||||
onDateTimeChange = (value, dateString) => {
|
||||
this.setState({
|
||||
scheduledTime: dateString,
|
||||
});
|
||||
};
|
||||
|
||||
toggleScheduledInstall = () => {
|
||||
this.setState({
|
||||
isScheduledInstallVisible: !this.state.isScheduledInstallVisible,
|
||||
});
|
||||
};
|
||||
|
||||
triggerInstallOperation = () => {
|
||||
const { scheduledTime, isScheduledInstallVisible } = this.state;
|
||||
if (isScheduledInstallVisible && scheduledTime != null) {
|
||||
this.props.operation(scheduledTime);
|
||||
} else {
|
||||
this.props.operation();
|
||||
}
|
||||
};
|
||||
|
||||
onDateTimeChange = (value, dateString) => {
|
||||
this.setState({
|
||||
scheduledTime: dateString
|
||||
});
|
||||
};
|
||||
|
||||
toggleScheduledInstall = () => {
|
||||
this.setState({
|
||||
isScheduledInstallVisible: !this.state.isScheduledInstallVisible
|
||||
})
|
||||
};
|
||||
|
||||
triggerInstallOperation = () => {
|
||||
const {scheduledTime, isScheduledInstallVisible} = this.state;
|
||||
if (isScheduledInstallVisible && scheduledTime != null) {
|
||||
this.props.operation(scheduledTime);
|
||||
} else {
|
||||
this.props.operation();
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
const {scheduledTime, isScheduledInstallVisible} = this.state;
|
||||
const {disabled, type} = this.props;
|
||||
return (
|
||||
<div>
|
||||
<div style={{
|
||||
textAlign: "right"
|
||||
}}>
|
||||
<div style={{margin: 8}}>
|
||||
<Checkbox checked={this.state.isScheduledInstallVisible} onChange={this.toggleScheduledInstall}>
|
||||
Schedule {type}
|
||||
</Checkbox>
|
||||
</div>
|
||||
<span style={{
|
||||
display: (isScheduledInstallVisible) ? 'inline' : 'none'
|
||||
}}>
|
||||
<DatePicker showTime
|
||||
placeholder="Select Time"
|
||||
format="YYYY-MM-DDTHH:mm"
|
||||
onChange={this.onDateTimeChange}/>
|
||||
</span>
|
||||
<Button style={{margin: 5}}
|
||||
disabled={disabled || (isScheduledInstallVisible && scheduledTime == null)}
|
||||
htmlType="button"
|
||||
type="primary"
|
||||
onClick={this.triggerInstallOperation}>
|
||||
{type}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
);
|
||||
}
|
||||
render() {
|
||||
const { scheduledTime, isScheduledInstallVisible } = this.state;
|
||||
const { disabled, type } = this.props;
|
||||
return (
|
||||
<div>
|
||||
<div
|
||||
style={{
|
||||
textAlign: 'right',
|
||||
}}
|
||||
>
|
||||
<div style={{ margin: 8 }}>
|
||||
<Checkbox
|
||||
checked={this.state.isScheduledInstallVisible}
|
||||
onChange={this.toggleScheduledInstall}
|
||||
>
|
||||
Schedule {type}
|
||||
</Checkbox>
|
||||
</div>
|
||||
<span
|
||||
style={{
|
||||
display: isScheduledInstallVisible ? 'inline' : 'none',
|
||||
}}
|
||||
>
|
||||
<DatePicker
|
||||
showTime
|
||||
placeholder="Select Time"
|
||||
format="YYYY-MM-DDTHH:mm"
|
||||
onChange={this.onDateTimeChange}
|
||||
/>
|
||||
</span>
|
||||
<Button
|
||||
style={{ margin: 5 }}
|
||||
disabled={
|
||||
disabled || (isScheduledInstallVisible && scheduledTime == null)
|
||||
}
|
||||
htmlType="button"
|
||||
type="primary"
|
||||
onClick={this.triggerInstallOperation}
|
||||
>
|
||||
{type}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default InstallModalFooter;
|
||||
export default InstallModalFooter;
|
||||
|
||||
@ -16,155 +16,170 @@
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
import {Drawer, Button, Icon, Row, Col, Typography, Divider, Input, Spin, notification} from 'antd';
|
||||
import StarRatings from "react-star-ratings";
|
||||
import axios from "axios";
|
||||
import {withConfigContext} from "../../../../context/ConfigContext";
|
||||
import {handleApiError} from "../../../../js/Utils";
|
||||
import React from 'react';
|
||||
import {
|
||||
Drawer,
|
||||
Button,
|
||||
Icon,
|
||||
Row,
|
||||
Col,
|
||||
Typography,
|
||||
Divider,
|
||||
Input,
|
||||
Spin,
|
||||
notification,
|
||||
} from 'antd';
|
||||
import StarRatings from 'react-star-ratings';
|
||||
import axios from 'axios';
|
||||
import { withConfigContext } from '../../../../context/ConfigContext';
|
||||
import { handleApiError } from '../../../../js/Utils';
|
||||
|
||||
const {Title} = Typography;
|
||||
const {TextArea} = Input;
|
||||
const { Title } = Typography;
|
||||
const { TextArea } = Input;
|
||||
|
||||
class AddReview extends React.Component {
|
||||
state = {
|
||||
visible: false,
|
||||
content: '',
|
||||
rating: 0,
|
||||
loading: false
|
||||
state = {
|
||||
visible: false,
|
||||
content: '',
|
||||
rating: 0,
|
||||
loading: false,
|
||||
};
|
||||
|
||||
showDrawer = () => {
|
||||
this.setState({
|
||||
visible: true,
|
||||
content: '',
|
||||
rating: 0,
|
||||
loading: false,
|
||||
});
|
||||
};
|
||||
|
||||
onClose = () => {
|
||||
this.setState({
|
||||
visible: false,
|
||||
});
|
||||
};
|
||||
changeRating = (newRating, name) => {
|
||||
this.setState({
|
||||
rating: newRating,
|
||||
});
|
||||
};
|
||||
|
||||
onChange = e => {
|
||||
this.setState({ content: e.target.value });
|
||||
};
|
||||
|
||||
onSubmit = () => {
|
||||
const config = this.props.context;
|
||||
const { content, rating } = this.state;
|
||||
const { uuid } = this.props;
|
||||
this.setState({
|
||||
loading: true,
|
||||
});
|
||||
|
||||
const payload = {
|
||||
content: content,
|
||||
rating: rating,
|
||||
};
|
||||
|
||||
showDrawer = () => {
|
||||
this.setState({
|
||||
visible: true,
|
||||
content: '',
|
||||
rating: 0,
|
||||
loading: false
|
||||
});
|
||||
};
|
||||
|
||||
onClose = () => {
|
||||
this.setState({
|
||||
axios
|
||||
.post(
|
||||
window.location.origin +
|
||||
config.serverConfig.invoker.uri +
|
||||
config.serverConfig.invoker.store +
|
||||
'/reviews/' +
|
||||
uuid,
|
||||
payload,
|
||||
)
|
||||
.then(res => {
|
||||
if (res.status === 201) {
|
||||
this.setState({
|
||||
loading: false,
|
||||
visible: false,
|
||||
|
||||
});
|
||||
};
|
||||
changeRating = (newRating, name) => {
|
||||
});
|
||||
notification.success({
|
||||
message: 'Done!',
|
||||
description: 'Your review has been posted successfully.',
|
||||
});
|
||||
this.props.onUpdateReview();
|
||||
} else {
|
||||
this.setState({
|
||||
loading: false,
|
||||
visible: false,
|
||||
});
|
||||
notification.error({
|
||||
message: 'There was a problem',
|
||||
duration: 0,
|
||||
description: 'We are unable to add your review right now.',
|
||||
});
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
handleApiError(error, 'We are unable to add your review right now.');
|
||||
this.setState({
|
||||
rating: newRating
|
||||
loading: false,
|
||||
visible: false,
|
||||
});
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
onChange = (e) => {
|
||||
this.setState({content: e.target.value})
|
||||
};
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
<Button type="primary" onClick={this.showDrawer}>
|
||||
<Icon type="star" /> Add a review
|
||||
</Button>
|
||||
|
||||
onSubmit = () => {
|
||||
const config = this.props.context;
|
||||
const {content, rating} = this.state;
|
||||
const {uuid} = this.props;
|
||||
this.setState({
|
||||
loading: true
|
||||
});
|
||||
|
||||
const payload = {
|
||||
content: content,
|
||||
rating: rating
|
||||
};
|
||||
|
||||
axios.post(
|
||||
window.location.origin+ config.serverConfig.invoker.uri + config.serverConfig.invoker.store + "/reviews/" + uuid,
|
||||
payload,
|
||||
).then(res => {
|
||||
if (res.status === 201) {
|
||||
this.setState({
|
||||
loading: false,
|
||||
visible: false
|
||||
});
|
||||
notification["success"]({
|
||||
message: 'Done!',
|
||||
description:
|
||||
'Your review has been posted successfully.',
|
||||
});
|
||||
this.props.onUpdateReview();
|
||||
} else {
|
||||
this.setState({
|
||||
loading: false,
|
||||
visible: false
|
||||
});
|
||||
notification["error"]({
|
||||
message: "There was a problem",
|
||||
duration: 0,
|
||||
description:
|
||||
"We are unable to add your review right now.",
|
||||
});
|
||||
}
|
||||
|
||||
}).catch((error) => {
|
||||
handleApiError(error,"We are unable to add your review right now.");
|
||||
this.setState({
|
||||
loading: false,
|
||||
visible: false
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
};
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
<Button type="primary" onClick={this.showDrawer}>
|
||||
<Icon type="star"/> Add a review
|
||||
<Drawer
|
||||
// title="Basic Drawer"
|
||||
placement="bottom"
|
||||
closable={false}
|
||||
onClose={this.onClose}
|
||||
visible={this.state.visible}
|
||||
height={400}
|
||||
>
|
||||
<Spin spinning={this.state.loading} tip="Posting your review...">
|
||||
<Row>
|
||||
<Col lg={8} />
|
||||
<Col lg={8}>
|
||||
<Title level={4}>Add review</Title>
|
||||
<Divider />
|
||||
<TextArea
|
||||
placeholder="Tell others what you think about this app. Would you recommend it, and why?"
|
||||
onChange={this.onChange}
|
||||
rows={4}
|
||||
value={this.state.content || ''}
|
||||
style={{ marginBottom: 20 }}
|
||||
/>
|
||||
<StarRatings
|
||||
rating={this.state.rating}
|
||||
changeRating={this.changeRating}
|
||||
starRatedColor="#777"
|
||||
starHoverColor="#444"
|
||||
starDimension="20px"
|
||||
starSpacing="2px"
|
||||
numberOfStars={5}
|
||||
name="rating"
|
||||
/>
|
||||
<br />
|
||||
<br />
|
||||
<Button onClick={this.onClose} style={{ marginRight: 8 }}>
|
||||
Cancel
|
||||
</Button>
|
||||
|
||||
<Drawer
|
||||
// title="Basic Drawer"
|
||||
placement="bottom"
|
||||
closable={false}
|
||||
onClose={this.onClose}
|
||||
visible={this.state.visible}
|
||||
height={400}
|
||||
<Button
|
||||
disabled={this.state.rating === 0}
|
||||
onClick={this.onSubmit}
|
||||
type="primary"
|
||||
>
|
||||
<Spin spinning={this.state.loading} tip="Posting your review...">
|
||||
<Row>
|
||||
<Col lg={8}/>
|
||||
<Col lg={8}>
|
||||
<Title level={4}>Add review</Title>
|
||||
<Divider/>
|
||||
<TextArea
|
||||
placeholder="Tell others what you think about this app. Would you recommend it, and why?"
|
||||
onChange={this.onChange}
|
||||
rows={4}
|
||||
value={this.state.content || ''}
|
||||
style={{marginBottom: 20}}
|
||||
/>
|
||||
<StarRatings
|
||||
rating={this.state.rating}
|
||||
changeRating={this.changeRating}
|
||||
starRatedColor="#777"
|
||||
starHoverColor="#444"
|
||||
starDimension="20px"
|
||||
starSpacing="2px"
|
||||
numberOfStars={5}
|
||||
name='rating'
|
||||
/>
|
||||
<br/><br/>
|
||||
<Button onClick={this.onClose} style={{marginRight: 8}}>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button disabled={this.state.rating === 0} onClick={this.onSubmit} type="primary">
|
||||
Submit
|
||||
</Button>
|
||||
</Col>
|
||||
</Row>
|
||||
</Spin>
|
||||
</Drawer>
|
||||
|
||||
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Submit
|
||||
</Button>
|
||||
</Col>
|
||||
</Row>
|
||||
</Spin>
|
||||
</Drawer>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default withConfigContext(AddReview);
|
||||
export default withConfigContext(AddReview);
|
||||
|
||||
@ -16,81 +16,84 @@
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
import {List, message, Typography, Empty, Button, Row, Col, notification, Alert} from "antd";
|
||||
import SingleReview from "./singleReview/SingleReview";
|
||||
import axios from "axios";
|
||||
import AddReview from "./AddReview";
|
||||
import {withConfigContext} from "../../../../context/ConfigContext";
|
||||
import {handleApiError} from "../../../../js/Utils";
|
||||
import React from 'react';
|
||||
import { List, Typography, Empty, Alert } from 'antd';
|
||||
import SingleReview from './singleReview/SingleReview';
|
||||
import AddReview from './AddReview';
|
||||
import { withConfigContext } from '../../../../context/ConfigContext';
|
||||
|
||||
const {Text, Paragraph} = Typography;
|
||||
const { Text } = Typography;
|
||||
|
||||
class CurrentUsersReview extends React.Component {
|
||||
render() {
|
||||
const { uuid, currentUserReviews } = this.props;
|
||||
return (
|
||||
<div>
|
||||
<Text>MY REVIEW</Text>
|
||||
{this.props.forbidden && (
|
||||
<Alert
|
||||
message="You don't have permission to add reviews."
|
||||
type="warning"
|
||||
banner
|
||||
closable
|
||||
/>
|
||||
)}
|
||||
{!this.props.forbidden && (
|
||||
<div
|
||||
style={{
|
||||
overflow: 'auto',
|
||||
paddingTop: 8,
|
||||
paddingLeft: 24,
|
||||
}}
|
||||
>
|
||||
{currentUserReviews.length > 0 && (
|
||||
<div>
|
||||
<List
|
||||
dataSource={currentUserReviews}
|
||||
renderItem={item => (
|
||||
<List.Item key={item.id}>
|
||||
<SingleReview
|
||||
uuid={uuid}
|
||||
review={item}
|
||||
isDeletable={true}
|
||||
isEditable={true}
|
||||
deleteCallback={this.props.deleteCallback}
|
||||
onUpdateReview={this.props.onUpdateReview}
|
||||
isPersonalReview={true}
|
||||
/>
|
||||
</List.Item>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
||||
render() {
|
||||
const {uuid, currentUserReviews} = this.props;
|
||||
return (
|
||||
<div>
|
||||
<Text>MY REVIEW</Text>
|
||||
{(this.props.forbidden) && (
|
||||
<Alert
|
||||
message="You don't have permission to add reviews."
|
||||
type="warning"
|
||||
banner
|
||||
closable/>
|
||||
)}
|
||||
{(!this.props.forbidden) && (
|
||||
<div style={{
|
||||
overflow: "auto",
|
||||
paddingTop: 8,
|
||||
paddingLeft: 24
|
||||
}}>
|
||||
{currentUserReviews.length > 0 && (
|
||||
<div>
|
||||
<List
|
||||
dataSource={currentUserReviews}
|
||||
renderItem={item => (
|
||||
<List.Item key={item.id}>
|
||||
<SingleReview
|
||||
uuid={uuid}
|
||||
review={item}
|
||||
isDeletable={true}
|
||||
isEditable={true}
|
||||
deleteCallback={this.props.deleteCallback}
|
||||
onUpdateReview={this.props.onUpdateReview}
|
||||
isPersonalReview={true}/>
|
||||
</List.Item>
|
||||
)}
|
||||
>
|
||||
</List>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{currentUserReviews.length === 0 && (
|
||||
<div>
|
||||
<Empty
|
||||
image={Empty.PRESENTED_IMAGE_DEFAULT}
|
||||
imagestyle={{
|
||||
height: 60,
|
||||
}}
|
||||
description={
|
||||
<span>Share your experience with your community by adding a review.</span>
|
||||
}>
|
||||
{/*<Button type="primary">Add review</Button>*/}
|
||||
<AddReview
|
||||
uuid={uuid}
|
||||
onUpdateReview={this.props.onUpdateReview}/>
|
||||
</Empty>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
{currentUserReviews.length === 0 && (
|
||||
<div>
|
||||
<Empty
|
||||
image={Empty.PRESENTED_IMAGE_DEFAULT}
|
||||
imagestyle={{
|
||||
height: 60,
|
||||
}}
|
||||
description={
|
||||
<span>
|
||||
Share your experience with your community by adding a
|
||||
review.
|
||||
</span>
|
||||
}
|
||||
>
|
||||
{/* <Button type="primary">Add review</Button>*/}
|
||||
<AddReview
|
||||
uuid={uuid}
|
||||
onUpdateReview={this.props.onUpdateReview}
|
||||
/>
|
||||
</Empty>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default withConfigContext(CurrentUsersReview);
|
||||
export default withConfigContext(CurrentUsersReview);
|
||||
|
||||
@ -16,122 +16,141 @@
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
import CurrentUsersReview from "./CurrentUsersReview";
|
||||
import {Col, Divider, Row, Typography} from "antd";
|
||||
import DetailedRating from "../DetailedRating";
|
||||
import Reviews from "./Reviews";
|
||||
import axios from "axios";
|
||||
import {handleApiError} from "../../../../js/Utils";
|
||||
import {withConfigContext} from "../../../../context/ConfigContext";
|
||||
import React from 'react';
|
||||
import CurrentUsersReview from './CurrentUsersReview';
|
||||
import { Col, Divider, Row, Typography } from 'antd';
|
||||
import DetailedRating from '../DetailedRating';
|
||||
import Reviews from './Reviews';
|
||||
import axios from 'axios';
|
||||
import { handleApiError } from '../../../../js/Utils';
|
||||
import { withConfigContext } from '../../../../context/ConfigContext';
|
||||
|
||||
const {Text} = Typography;
|
||||
const { Text } = Typography;
|
||||
|
||||
class ReviewContainer extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
currentUserReviews: [],
|
||||
detailedRating: null,
|
||||
forbiddenErrors: {
|
||||
currentReview: false,
|
||||
reviews: false,
|
||||
rating: false
|
||||
}
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
currentUserReviews: [],
|
||||
detailedRating: null,
|
||||
forbiddenErrors: {
|
||||
currentReview: false,
|
||||
reviews: false,
|
||||
rating: false,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.fetchCurrentUserReviews();
|
||||
this.fetchDetailedRating('app', this.props.uuid);
|
||||
}
|
||||
|
||||
fetchCurrentUserReviews = () => {
|
||||
const { uuid } = this.props;
|
||||
const config = this.props.context;
|
||||
|
||||
axios
|
||||
.get(
|
||||
window.location.origin +
|
||||
config.serverConfig.invoker.uri +
|
||||
config.serverConfig.invoker.store +
|
||||
'/reviews/app/user/' +
|
||||
uuid,
|
||||
)
|
||||
.then(res => {
|
||||
if (res.status === 200) {
|
||||
const currentUserReviews = res.data.data.data;
|
||||
this.setState({ currentUserReviews });
|
||||
}
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
handleApiError(
|
||||
error,
|
||||
'Error occurred while trying to get your review.',
|
||||
true,
|
||||
);
|
||||
if (error.hasOwnProperty('response') && error.response.status === 403) {
|
||||
const { forbiddenErrors } = this.state;
|
||||
forbiddenErrors.currentReview = true;
|
||||
this.setState({
|
||||
forbiddenErrors,
|
||||
loading: false,
|
||||
});
|
||||
} else {
|
||||
this.setState({
|
||||
loading: false,
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
componentDidMount() {
|
||||
this.fetchCurrentUserReviews();
|
||||
this.fetchDetailedRating("app", this.props.uuid);
|
||||
}
|
||||
deleteCurrentUserReviewCallback = () => {
|
||||
this.setState({
|
||||
currentUserReviews: [],
|
||||
});
|
||||
this.fetchDetailedRating('app', this.props.uuid);
|
||||
};
|
||||
|
||||
fetchCurrentUserReviews = () => {
|
||||
const {uuid} = this.props;
|
||||
const config = this.props.context;
|
||||
fetchDetailedRating = (type, uuid) => {
|
||||
const config = this.props.context;
|
||||
|
||||
axios.get(
|
||||
window.location.origin + config.serverConfig.invoker.uri + config.serverConfig.invoker.store + "/reviews/app/user/" + uuid,
|
||||
).then(res => {
|
||||
if (res.status === 200) {
|
||||
const currentUserReviews = res.data.data.data;
|
||||
this.setState({currentUserReviews});
|
||||
}
|
||||
axios
|
||||
.get(
|
||||
window.location.origin +
|
||||
config.serverConfig.invoker.uri +
|
||||
config.serverConfig.invoker.store +
|
||||
'/reviews/' +
|
||||
uuid +
|
||||
'/' +
|
||||
type +
|
||||
'-rating',
|
||||
)
|
||||
.then(res => {
|
||||
if (res.status === 200) {
|
||||
let detailedRating = res.data.data;
|
||||
this.setState({
|
||||
detailedRating,
|
||||
});
|
||||
}
|
||||
})
|
||||
.catch(function(error) {
|
||||
handleApiError(
|
||||
error,
|
||||
'Error occurred while trying to load ratings.',
|
||||
true,
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
}).catch((error) => {
|
||||
handleApiError(error, "Error occurred while trying to get your review.", true);
|
||||
if (error.hasOwnProperty("response") && error.response.status === 403) {
|
||||
const {forbiddenErrors} = this.state;
|
||||
forbiddenErrors.currentReview = true;
|
||||
this.setState({
|
||||
forbiddenErrors,
|
||||
loading: false
|
||||
})
|
||||
} else {
|
||||
this.setState({
|
||||
loading: false
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
onUpdateReview = () => {
|
||||
this.fetchCurrentUserReviews();
|
||||
this.fetchDetailedRating('app', this.props.uuid);
|
||||
};
|
||||
|
||||
deleteCurrentUserReviewCallback = () => {
|
||||
this.setState({
|
||||
currentUserReviews: []
|
||||
});
|
||||
this.fetchDetailedRating("app", this.props.uuid);
|
||||
};
|
||||
|
||||
fetchDetailedRating = (type, uuid) => {
|
||||
const config = this.props.context;
|
||||
|
||||
axios.get(
|
||||
window.location.origin + config.serverConfig.invoker.uri + config.serverConfig.invoker.store + "/reviews/" + uuid + "/" + type + "-rating",
|
||||
).then(res => {
|
||||
if (res.status === 200) {
|
||||
let detailedRating = res.data.data;
|
||||
this.setState({
|
||||
detailedRating
|
||||
})
|
||||
}
|
||||
|
||||
}).catch(function (error) {
|
||||
handleApiError(error, "Error occurred while trying to load ratings.", true);
|
||||
});
|
||||
};
|
||||
|
||||
onUpdateReview = () => {
|
||||
this.fetchCurrentUserReviews();
|
||||
this.fetchDetailedRating("app", this.props.uuid);
|
||||
};
|
||||
|
||||
render() {
|
||||
const {uuid} = this.props;
|
||||
const {currentUserReviews,detailedRating, forbiddenErrors} = this.state;
|
||||
return (
|
||||
<div>
|
||||
<CurrentUsersReview
|
||||
forbidden={forbiddenErrors.currentReview}
|
||||
uuid={uuid}
|
||||
currentUserReviews={currentUserReviews}
|
||||
onUpdateReview={this.onUpdateReview}
|
||||
deleteCallback={this.deleteCurrentUserReviewCallback}/>
|
||||
<Divider dashed={true}/>
|
||||
<Text>REVIEWS</Text>
|
||||
<Row>
|
||||
<Col lg={18} md={24}>
|
||||
<DetailedRating
|
||||
type="app"
|
||||
detailedRating={detailedRating}/>
|
||||
</Col>
|
||||
</Row>
|
||||
<Reviews
|
||||
type="app"
|
||||
uuid={uuid}
|
||||
deleteCallback={this.onUpdateReview}/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
render() {
|
||||
const { uuid } = this.props;
|
||||
const { currentUserReviews, detailedRating, forbiddenErrors } = this.state;
|
||||
return (
|
||||
<div>
|
||||
<CurrentUsersReview
|
||||
forbidden={forbiddenErrors.currentReview}
|
||||
uuid={uuid}
|
||||
currentUserReviews={currentUserReviews}
|
||||
onUpdateReview={this.onUpdateReview}
|
||||
deleteCallback={this.deleteCurrentUserReviewCallback}
|
||||
/>
|
||||
<Divider dashed={true} />
|
||||
<Text>REVIEWS</Text>
|
||||
<Row>
|
||||
<Col lg={18} md={24}>
|
||||
<DetailedRating type="app" detailedRating={detailedRating} />
|
||||
</Col>
|
||||
</Row>
|
||||
<Reviews type="app" uuid={uuid} deleteCallback={this.onUpdateReview} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default withConfigContext(ReviewContainer);
|
||||
export default withConfigContext(ReviewContainer);
|
||||
|
||||
@ -31,4 +31,4 @@
|
||||
position: absolute;
|
||||
bottom: -40px;
|
||||
left: 50%;
|
||||
}
|
||||
}
|
||||
|
||||
@ -16,156 +16,183 @@
|
||||
* 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/SingleReview";
|
||||
import axios from "axios";
|
||||
import {withConfigContext} from "../../../../context/ConfigContext";
|
||||
import {handleApiError} from "../../../../js/Utils";
|
||||
import SingleReview from './singleReview/SingleReview';
|
||||
import axios from 'axios';
|
||||
import { withConfigContext } from '../../../../context/ConfigContext';
|
||||
import { handleApiError } from '../../../../js/Utils';
|
||||
|
||||
const limit = 5;
|
||||
|
||||
class Reviews extends React.Component {
|
||||
state = {
|
||||
data: [],
|
||||
loading: false,
|
||||
hasMore: false,
|
||||
loadMore: false,
|
||||
forbiddenErrors: {
|
||||
reviews: false
|
||||
state = {
|
||||
data: [],
|
||||
loading: false,
|
||||
hasMore: false,
|
||||
loadMore: false,
|
||||
forbiddenErrors: {
|
||||
reviews: false,
|
||||
},
|
||||
};
|
||||
|
||||
componentDidMount() {
|
||||
this.fetchData(0, limit, res => {
|
||||
this.setState({
|
||||
data: res,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
fetchData = (offset, limit, callback) => {
|
||||
const { uuid, type } = this.props;
|
||||
const config = this.props.context;
|
||||
|
||||
axios
|
||||
.get(
|
||||
window.location.origin +
|
||||
config.serverConfig.invoker.uri +
|
||||
config.serverConfig.invoker.store +
|
||||
'/reviews/' +
|
||||
type +
|
||||
'/' +
|
||||
uuid,
|
||||
{
|
||||
headers: { 'X-Platform': config.serverConfig.platform },
|
||||
},
|
||||
)
|
||||
.then(res => {
|
||||
if (res.status === 200) {
|
||||
let reviews = res.data.data.data;
|
||||
callback(reviews);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
componentDidMount() {
|
||||
this.fetchData(0, limit, res => {
|
||||
this.setState({
|
||||
data: res,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
fetchData = (offset, limit, callback) => {
|
||||
|
||||
const {uuid, type} = this.props;
|
||||
const config = this.props.context;
|
||||
|
||||
axios.get(
|
||||
window.location.origin + config.serverConfig.invoker.uri + config.serverConfig.invoker.store + "/reviews/" + type + "/" + uuid,
|
||||
{
|
||||
headers: {'X-Platform': config.serverConfig.platform}
|
||||
}).then(res => {
|
||||
if (res.status === 200) {
|
||||
let reviews = res.data.data.data;
|
||||
callback(reviews);
|
||||
}
|
||||
|
||||
}).catch((error) => {
|
||||
handleApiError(error, "Error occurred while trying to load reviews.", true);
|
||||
if (error.hasOwnProperty("response") && error.response.status === 403) {
|
||||
const {forbiddenErrors} = this.state;
|
||||
forbiddenErrors.reviews = true;
|
||||
this.setState({
|
||||
forbiddenErrors,
|
||||
loading: false
|
||||
})
|
||||
} else {
|
||||
this.setState({
|
||||
loading: false
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
handleInfiniteOnLoad = (count) => {
|
||||
const offset = count * limit;
|
||||
let data = this.state.data;
|
||||
this.setState({
|
||||
loading: true,
|
||||
});
|
||||
if (data.length > 149) {
|
||||
this.setState({
|
||||
hasMore: false,
|
||||
loading: false,
|
||||
});
|
||||
return;
|
||||
}
|
||||
this.fetchData(offset, limit, res => {
|
||||
if (res.length > 0) {
|
||||
data = data.concat(res);
|
||||
this.setState({
|
||||
data,
|
||||
loading: false,
|
||||
});
|
||||
} else {
|
||||
this.setState({
|
||||
hasMore: false,
|
||||
loading: false
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
enableLoading = () => {
|
||||
this.setState({
|
||||
hasMore: true,
|
||||
loadMore: true
|
||||
});
|
||||
};
|
||||
|
||||
deleteCallback = () => {
|
||||
this.fetchData(0, limit, res => {
|
||||
this.setState({
|
||||
data: res,
|
||||
});
|
||||
});
|
||||
this.props.deleteCallback();
|
||||
};
|
||||
|
||||
render() {
|
||||
const {loading, hasMore, data, loadMore} = this.state;
|
||||
const {uuid} = this.props;
|
||||
return (
|
||||
<div>
|
||||
{(this.state.forbiddenErrors.reviews) && (
|
||||
<Alert
|
||||
message="You don't have permission to view reviews."
|
||||
type="warning"
|
||||
banner
|
||||
closable/>
|
||||
)}
|
||||
<div className="infinite-container">
|
||||
<InfiniteScroll
|
||||
initialLoad={false}
|
||||
pageStart={0}
|
||||
loadMore={this.handleInfiniteOnLoad}
|
||||
hasMore={!loading && hasMore}
|
||||
useWindow={true}>
|
||||
<List
|
||||
dataSource={data}
|
||||
renderItem={item => (
|
||||
<List.Item key={item.id}>
|
||||
<SingleReview uuid={uuid} review={item} isDeletable={true} isEditable={false}
|
||||
deleteCallback={this.deleteCallback}/>
|
||||
</List.Item>
|
||||
)}>
|
||||
{loading && hasMore && (
|
||||
<div className="loading-container">
|
||||
<Spin/>
|
||||
</div>
|
||||
)}
|
||||
</List>
|
||||
</InfiniteScroll>
|
||||
{!loadMore && (data.length >= limit) && (<div style={{textAlign: "center"}}>
|
||||
<Button type="dashed" htmlType="button" onClick={this.enableLoading}>Read All Reviews</Button>
|
||||
</div>)}
|
||||
</div>
|
||||
</div>
|
||||
})
|
||||
.catch(error => {
|
||||
handleApiError(
|
||||
error,
|
||||
'Error occurred while trying to load reviews.',
|
||||
true,
|
||||
);
|
||||
if (error.hasOwnProperty('response') && error.response.status === 403) {
|
||||
const { forbiddenErrors } = this.state;
|
||||
forbiddenErrors.reviews = true;
|
||||
this.setState({
|
||||
forbiddenErrors,
|
||||
loading: false,
|
||||
});
|
||||
} else {
|
||||
this.setState({
|
||||
loading: false,
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
handleInfiniteOnLoad = count => {
|
||||
const offset = count * limit;
|
||||
let data = this.state.data;
|
||||
this.setState({
|
||||
loading: true,
|
||||
});
|
||||
if (data.length > 149) {
|
||||
this.setState({
|
||||
hasMore: false,
|
||||
loading: false,
|
||||
});
|
||||
return;
|
||||
}
|
||||
this.fetchData(offset, limit, res => {
|
||||
if (res.length > 0) {
|
||||
data = data.concat(res);
|
||||
this.setState({
|
||||
data,
|
||||
loading: false,
|
||||
});
|
||||
} else {
|
||||
this.setState({
|
||||
hasMore: false,
|
||||
loading: false,
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
enableLoading = () => {
|
||||
this.setState({
|
||||
hasMore: true,
|
||||
loadMore: true,
|
||||
});
|
||||
};
|
||||
|
||||
deleteCallback = () => {
|
||||
this.fetchData(0, limit, res => {
|
||||
this.setState({
|
||||
data: res,
|
||||
});
|
||||
});
|
||||
this.props.deleteCallback();
|
||||
};
|
||||
|
||||
render() {
|
||||
const { loading, hasMore, data, loadMore } = this.state;
|
||||
const { uuid } = this.props;
|
||||
return (
|
||||
<div>
|
||||
{this.state.forbiddenErrors.reviews && (
|
||||
<Alert
|
||||
message="You don't have permission to view reviews."
|
||||
type="warning"
|
||||
banner
|
||||
closable
|
||||
/>
|
||||
)}
|
||||
<div className="infinite-container">
|
||||
<InfiniteScroll
|
||||
initialLoad={false}
|
||||
pageStart={0}
|
||||
loadMore={this.handleInfiniteOnLoad}
|
||||
hasMore={!loading && hasMore}
|
||||
useWindow={true}
|
||||
>
|
||||
<List
|
||||
dataSource={data}
|
||||
renderItem={item => (
|
||||
<List.Item key={item.id}>
|
||||
<SingleReview
|
||||
uuid={uuid}
|
||||
review={item}
|
||||
isDeletable={true}
|
||||
isEditable={false}
|
||||
deleteCallback={this.deleteCallback}
|
||||
/>
|
||||
</List.Item>
|
||||
)}
|
||||
>
|
||||
{loading && hasMore && (
|
||||
<div className="loading-container">
|
||||
<Spin />
|
||||
</div>
|
||||
)}
|
||||
</List>
|
||||
</InfiniteScroll>
|
||||
{!loadMore && data.length >= limit && (
|
||||
<div style={{ textAlign: 'center' }}>
|
||||
<Button
|
||||
type="dashed"
|
||||
htmlType="button"
|
||||
onClick={this.enableLoading}
|
||||
>
|
||||
Read All Reviews
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default withConfigContext(Reviews);
|
||||
export default withConfigContext(Reviews);
|
||||
|
||||
@ -41,4 +41,4 @@ img.twemoji {
|
||||
font-weight: normal;
|
||||
font-size: 0.8em;
|
||||
padding-left: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
@ -16,129 +16,155 @@
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
import {Avatar, notification} from "antd";
|
||||
import {List, Typography, Popconfirm} from "antd";
|
||||
import StarRatings from "react-star-ratings";
|
||||
import Twemoji from "react-twemoji";
|
||||
import "./SingleReview.css";
|
||||
import EditReview from "./editReview/EditReview";
|
||||
import axios from "axios";
|
||||
import {withConfigContext} from "../../../../../context/ConfigContext";
|
||||
import {handleApiError} from "../../../../../js/Utils";
|
||||
import React from 'react';
|
||||
import { Avatar, notification } from 'antd';
|
||||
import { List, Typography, Popconfirm } from 'antd';
|
||||
import StarRatings from 'react-star-ratings';
|
||||
import Twemoji from 'react-twemoji';
|
||||
import './SingleReview.css';
|
||||
import EditReview from './editReview/EditReview';
|
||||
import axios from 'axios';
|
||||
import { withConfigContext } from '../../../../../context/ConfigContext';
|
||||
import { handleApiError } from '../../../../../js/Utils';
|
||||
|
||||
const {Text, Paragraph} = Typography;
|
||||
const colorList = ['#f0932b', '#badc58', '#6ab04c', '#eb4d4b', '#0abde3', '#9b59b6', '#3498db', '#22a6b3', '#e84393', '#f9ca24'];
|
||||
const { Text, Paragraph } = Typography;
|
||||
const colorList = [
|
||||
'#f0932b',
|
||||
'#badc58',
|
||||
'#6ab04c',
|
||||
'#eb4d4b',
|
||||
'#0abde3',
|
||||
'#9b59b6',
|
||||
'#3498db',
|
||||
'#22a6b3',
|
||||
'#e84393',
|
||||
'#f9ca24',
|
||||
];
|
||||
|
||||
class SingleReview extends React.Component {
|
||||
static defaultProps = {
|
||||
isPersonalReview: false,
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
isPersonalReview: false
|
||||
constructor(props) {
|
||||
super(props);
|
||||
const { username } = this.props.review;
|
||||
const color = colorList[username.length % 10];
|
||||
this.state = {
|
||||
content: '',
|
||||
rating: 0,
|
||||
color: color,
|
||||
review: props.review,
|
||||
};
|
||||
}
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
const {username} = this.props.review;
|
||||
const color = colorList[username.length % 10];
|
||||
this.state = {
|
||||
content: '',
|
||||
rating: 0,
|
||||
color: color,
|
||||
review: props.review
|
||||
}
|
||||
updateCallback = review => {
|
||||
this.setState({
|
||||
review,
|
||||
});
|
||||
this.props.onUpdateReview();
|
||||
};
|
||||
|
||||
deleteReview = () => {
|
||||
const { uuid } = this.props;
|
||||
const { id } = this.state.review;
|
||||
const config = this.props.context;
|
||||
|
||||
let url =
|
||||
window.location.origin +
|
||||
config.serverConfig.invoker.uri +
|
||||
config.serverConfig.invoker.store;
|
||||
|
||||
// call as an admin api if the review is not a personal review
|
||||
if (!this.props.isPersonalReview) {
|
||||
url += '/admin';
|
||||
}
|
||||
|
||||
updateCallback = (review) => {
|
||||
this.setState({
|
||||
review
|
||||
});
|
||||
this.props.onUpdateReview();
|
||||
};
|
||||
url += '/reviews/' + uuid + '/' + id;
|
||||
|
||||
deleteReview = () => {
|
||||
const {uuid} = this.props;
|
||||
const {id} = this.state.review;
|
||||
const config = this.props.context;
|
||||
axios
|
||||
.delete(url)
|
||||
.then(res => {
|
||||
if (res.status === 200) {
|
||||
notification.success({
|
||||
message: 'Done!',
|
||||
description: 'The review has been deleted successfully.',
|
||||
});
|
||||
|
||||
let url =window.location.origin+ config.serverConfig.invoker.uri + config.serverConfig.invoker.store;
|
||||
|
||||
// call as an admin api if the review is not a personal review
|
||||
if (!this.props.isPersonalReview) {
|
||||
url += "/admin";
|
||||
this.props.deleteCallback(id);
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
handleApiError(error, 'We were unable to delete the review..');
|
||||
});
|
||||
};
|
||||
|
||||
url += "/reviews/" + uuid + "/" + id;
|
||||
render() {
|
||||
const { isEditable, isDeletable, uuid } = this.props;
|
||||
const { color, review } = this.state;
|
||||
const { content, rating, username } = review;
|
||||
const avatarLetter = username.charAt(0).toUpperCase();
|
||||
const body = (
|
||||
<div style={{ marginTop: -5 }}>
|
||||
<StarRatings
|
||||
rating={rating}
|
||||
starRatedColor="#777"
|
||||
starDimension="12px"
|
||||
starSpacing="2px"
|
||||
numberOfStars={5}
|
||||
name="rating"
|
||||
/>
|
||||
<Text style={{ fontSize: 12, color: '#aaa' }} type="secondary">
|
||||
{' '}
|
||||
{review.createdAt}
|
||||
</Text>
|
||||
<br />
|
||||
<Paragraph style={{ color: '#777' }}>
|
||||
<Twemoji options={{ className: 'twemoji' }}>{content}</Twemoji>
|
||||
</Paragraph>
|
||||
</div>
|
||||
);
|
||||
|
||||
axios.delete(url).then(res => {
|
||||
if (res.status === 200) {
|
||||
notification["success"]({
|
||||
message: 'Done!',
|
||||
description:
|
||||
'The review has been deleted successfully.',
|
||||
});
|
||||
const title = (
|
||||
<div>
|
||||
{review.username}
|
||||
{isEditable && (
|
||||
<EditReview
|
||||
uuid={uuid}
|
||||
review={review}
|
||||
updateCallback={this.updateCallback}
|
||||
/>
|
||||
)}
|
||||
{isDeletable && (
|
||||
<Popconfirm
|
||||
title="Are you sure delete this review?"
|
||||
onConfirm={this.deleteReview}
|
||||
okText="Yes"
|
||||
cancelText="No"
|
||||
>
|
||||
<span className="delete-button">delete</span>
|
||||
</Popconfirm>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
||||
this.props.deleteCallback(id);
|
||||
}
|
||||
}).catch((error) => {
|
||||
handleApiError(error,"We were unable to delete the review..");
|
||||
});
|
||||
|
||||
};
|
||||
|
||||
render() {
|
||||
const {isEditable, isDeletable, uuid} = this.props;
|
||||
const {color, review} = this.state;
|
||||
const {content, rating, username} = review;
|
||||
const avatarLetter = username.charAt(0).toUpperCase();
|
||||
const body = (
|
||||
<div style={{marginTop: -5}}>
|
||||
<StarRatings
|
||||
rating={rating}
|
||||
starRatedColor="#777"
|
||||
starDimension="12px"
|
||||
starSpacing="2px"
|
||||
numberOfStars={5}
|
||||
name='rating'
|
||||
/>
|
||||
<Text style={{fontSize: 12, color: "#aaa"}} type="secondary"> {review.createdAt}</Text><br/>
|
||||
<Paragraph style={{color: "#777"}}>
|
||||
<Twemoji options={{className: 'twemoji'}}>
|
||||
{content}
|
||||
</Twemoji>
|
||||
</Paragraph>
|
||||
</div>
|
||||
);
|
||||
|
||||
const title = (
|
||||
<div>
|
||||
{review.username}
|
||||
{isEditable && (<EditReview uuid={uuid} review={review} updateCallback={this.updateCallback}/>)}
|
||||
{isDeletable && (
|
||||
<Popconfirm
|
||||
title="Are you sure delete this review?"
|
||||
onConfirm={this.deleteReview}
|
||||
okText="Yes"
|
||||
cancelText="No"
|
||||
>
|
||||
<span className="delete-button">delete</span>
|
||||
</Popconfirm>)}
|
||||
</div>
|
||||
);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<List.Item.Meta
|
||||
avatar={
|
||||
<Avatar style={{backgroundColor: color, verticalAlign: 'middle'}} size="large">
|
||||
{avatarLetter}
|
||||
</Avatar>
|
||||
}
|
||||
title={title}
|
||||
description={body}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<div>
|
||||
<List.Item.Meta
|
||||
avatar={
|
||||
<Avatar
|
||||
style={{ backgroundColor: color, verticalAlign: 'middle' }}
|
||||
size="large"
|
||||
>
|
||||
{avatarLetter}
|
||||
</Avatar>
|
||||
}
|
||||
title={title}
|
||||
description={body}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default withConfigContext(SingleReview);
|
||||
export default withConfigContext(SingleReview);
|
||||
|
||||
@ -24,4 +24,4 @@
|
||||
font-weight: normal;
|
||||
font-size: 0.8em;
|
||||
padding-left: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
@ -16,166 +16,183 @@
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
import {Drawer, Button, Icon, Row, Col, Typography, Divider, Input, Spin, notification} from 'antd';
|
||||
import StarRatings from "react-star-ratings";
|
||||
import axios from "axios";
|
||||
import "./EditReview.css";
|
||||
import {withConfigContext} from "../../../../../../context/ConfigContext";
|
||||
import {handleApiError} from "../../../../../../js/Utils";
|
||||
import React from 'react';
|
||||
import {
|
||||
Drawer,
|
||||
Button,
|
||||
Row,
|
||||
Col,
|
||||
Typography,
|
||||
Divider,
|
||||
Input,
|
||||
Spin,
|
||||
notification,
|
||||
} from 'antd';
|
||||
import StarRatings from 'react-star-ratings';
|
||||
import axios from 'axios';
|
||||
import './EditReview.css';
|
||||
import { withConfigContext } from '../../../../../../context/ConfigContext';
|
||||
import { handleApiError } from '../../../../../../js/Utils';
|
||||
|
||||
const {Title} = Typography;
|
||||
const {TextArea} = Input;
|
||||
const { Title } = Typography;
|
||||
const { TextArea } = Input;
|
||||
|
||||
class EditReview extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
visible: false,
|
||||
content: '',
|
||||
rating: 0,
|
||||
loading: false,
|
||||
};
|
||||
}
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
componentDidMount() {
|
||||
const { content, rating } = this.props.review;
|
||||
this.setState({
|
||||
content,
|
||||
rating,
|
||||
});
|
||||
}
|
||||
|
||||
showDrawer = () => {
|
||||
this.setState({
|
||||
visible: true,
|
||||
loading: false,
|
||||
});
|
||||
};
|
||||
|
||||
onClose = () => {
|
||||
this.setState({
|
||||
visible: false,
|
||||
});
|
||||
};
|
||||
|
||||
changeRating = (newRating, name) => {
|
||||
this.setState({
|
||||
rating: newRating,
|
||||
});
|
||||
};
|
||||
|
||||
onChange = e => {
|
||||
this.setState({ content: e.target.value });
|
||||
};
|
||||
|
||||
onSubmit = () => {
|
||||
const config = this.props.context;
|
||||
const { content, rating } = this.state;
|
||||
const { id } = this.props.review;
|
||||
const { uuid } = this.props;
|
||||
this.setState({
|
||||
loading: true,
|
||||
});
|
||||
|
||||
const payload = {
|
||||
content: content,
|
||||
rating: rating,
|
||||
};
|
||||
|
||||
axios
|
||||
.put(
|
||||
window.location.origin +
|
||||
config.serverConfig.invoker.uri +
|
||||
config.serverConfig.invoker.store +
|
||||
'/reviews/' +
|
||||
uuid +
|
||||
'/' +
|
||||
id,
|
||||
payload,
|
||||
)
|
||||
.then(res => {
|
||||
if (res.status === 200) {
|
||||
this.setState({
|
||||
loading: false,
|
||||
visible: false,
|
||||
content: '',
|
||||
rating: 0,
|
||||
loading: false
|
||||
};
|
||||
}
|
||||
});
|
||||
notification.success({
|
||||
message: 'Done!',
|
||||
description: 'Your review has been update successfully.',
|
||||
});
|
||||
|
||||
componentDidMount() {
|
||||
const {content,rating,id} = this.props.review;
|
||||
this.setState({
|
||||
content,
|
||||
rating
|
||||
});
|
||||
}
|
||||
|
||||
showDrawer = () => {
|
||||
this.setState({
|
||||
visible: true,
|
||||
loading: false
|
||||
});
|
||||
};
|
||||
|
||||
onClose = () => {
|
||||
this.setState({
|
||||
this.props.updateCallback(res.data.data);
|
||||
} else {
|
||||
this.setState({
|
||||
loading: false,
|
||||
visible: false,
|
||||
|
||||
});
|
||||
};
|
||||
|
||||
changeRating = (newRating, name) => {
|
||||
});
|
||||
notification.error({
|
||||
message: 'There was a problem',
|
||||
duration: 0,
|
||||
description: 'We are unable to update your review right now.',
|
||||
});
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
handleApiError(error, 'We are unable to add your review right now.');
|
||||
this.setState({
|
||||
rating: newRating
|
||||
loading: false,
|
||||
visible: false,
|
||||
});
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
onChange = (e) => {
|
||||
this.setState({content: e.target.value})
|
||||
};
|
||||
|
||||
onSubmit = () => {
|
||||
const config = this.props.context;
|
||||
const {content, rating} = this.state;
|
||||
const {id} = this.props.review;
|
||||
const {uuid} = this.props;
|
||||
this.setState({
|
||||
loading: true
|
||||
});
|
||||
|
||||
const payload = {
|
||||
content: content,
|
||||
rating: rating
|
||||
};
|
||||
|
||||
axios.put(
|
||||
window.location.origin+ config.serverConfig.invoker.uri + config.serverConfig.invoker.store + "/reviews/" + uuid+"/"+id,
|
||||
payload,
|
||||
).then(res => {
|
||||
if (res.status === 200) {
|
||||
this.setState({
|
||||
loading: false,
|
||||
visible: false
|
||||
});
|
||||
notification["success"]({
|
||||
message: 'Done!',
|
||||
description:
|
||||
'Your review has been update successfully.',
|
||||
});
|
||||
|
||||
this.props.updateCallback(res.data.data);
|
||||
} else {
|
||||
this.setState({
|
||||
loading: false,
|
||||
visible: false
|
||||
});
|
||||
notification["error"]({
|
||||
message: "There was a problem",
|
||||
duration: 0,
|
||||
description:
|
||||
"We are unable to update your review right now.",
|
||||
});
|
||||
}
|
||||
|
||||
}).catch((error) => {
|
||||
handleApiError(error,"We are unable to add your review right now.");
|
||||
this.setState({
|
||||
loading: false,
|
||||
visible: false
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
};
|
||||
|
||||
render() {
|
||||
return (
|
||||
<span>
|
||||
<span className="edit-button" onClick={this.showDrawer}>edit</span>
|
||||
<Drawer
|
||||
// title="Basic Drawer"
|
||||
placement="bottom"
|
||||
closable={false}
|
||||
onClose={this.onClose}
|
||||
visible={this.state.visible}
|
||||
height={400}
|
||||
render() {
|
||||
return (
|
||||
<span>
|
||||
<span className="edit-button" onClick={this.showDrawer}>
|
||||
edit
|
||||
</span>
|
||||
<Drawer
|
||||
// title="Basic Drawer"
|
||||
placement="bottom"
|
||||
closable={false}
|
||||
onClose={this.onClose}
|
||||
visible={this.state.visible}
|
||||
height={400}
|
||||
>
|
||||
<Spin spinning={this.state.loading} tip="Posting your review...">
|
||||
<Row>
|
||||
<Col lg={8} />
|
||||
<Col lg={8}>
|
||||
<Title level={4}>Edit review</Title>
|
||||
<Divider />
|
||||
<TextArea
|
||||
placeholder="Tell others what you think about this app. Would you recommend it, and why?"
|
||||
onChange={this.onChange}
|
||||
rows={6}
|
||||
value={this.state.content || ''}
|
||||
style={{ marginBottom: 20 }}
|
||||
/>
|
||||
<StarRatings
|
||||
rating={this.state.rating}
|
||||
changeRating={this.changeRating}
|
||||
starRatedColor="#777"
|
||||
starHoverColor="#444"
|
||||
starDimension="20px"
|
||||
starSpacing="2px"
|
||||
numberOfStars={5}
|
||||
name="rating"
|
||||
/>
|
||||
<br />
|
||||
<br />
|
||||
<Button onClick={this.onClose} style={{ marginRight: 8 }}>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
disabled={this.state.rating === 0}
|
||||
onClick={this.onSubmit}
|
||||
type="primary"
|
||||
>
|
||||
<Spin spinning={this.state.loading} tip="Posting your review...">
|
||||
<Row>
|
||||
<Col lg={8}/>
|
||||
<Col lg={8}>
|
||||
<Title level={4}>Edit review</Title>
|
||||
<Divider/>
|
||||
<TextArea
|
||||
placeholder="Tell others what you think about this app. Would you recommend it, and why?"
|
||||
onChange={this.onChange}
|
||||
rows={6}
|
||||
value={this.state.content || ''}
|
||||
style={{marginBottom: 20}}
|
||||
/>
|
||||
<StarRatings
|
||||
rating={this.state.rating}
|
||||
changeRating={this.changeRating}
|
||||
starRatedColor="#777"
|
||||
starHoverColor="#444"
|
||||
starDimension="20px"
|
||||
starSpacing="2px"
|
||||
numberOfStars={5}
|
||||
name='rating'
|
||||
/>
|
||||
<br/><br/>
|
||||
<Button onClick={this.onClose} style={{marginRight: 8}}>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button disabled={this.state.rating === 0} onClick={this.onSubmit} type="primary">
|
||||
Submit
|
||||
</Button>
|
||||
</Col>
|
||||
</Row>
|
||||
</Spin>
|
||||
</Drawer>
|
||||
|
||||
|
||||
</span>
|
||||
);
|
||||
}
|
||||
Submit
|
||||
</Button>
|
||||
</Col>
|
||||
</Row>
|
||||
</Spin>
|
||||
</Drawer>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default withConfigContext(EditReview);
|
||||
export default withConfigContext(EditReview);
|
||||
|
||||
@ -16,19 +16,19 @@
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
import React from 'react';
|
||||
|
||||
const ConfigContext = React.createContext();
|
||||
|
||||
export const withConfigContext = Component => {
|
||||
return props => (
|
||||
<ConfigContext.Consumer>
|
||||
{context => {
|
||||
return <Component {...props} context={context}/>;
|
||||
}}
|
||||
</ConfigContext.Consumer>
|
||||
);
|
||||
// eslint-disable-next-line react/display-name
|
||||
return props => (
|
||||
<ConfigContext.Consumer>
|
||||
{context => {
|
||||
return <Component {...props} context={context} />;
|
||||
}}
|
||||
</ConfigContext.Consumer>
|
||||
);
|
||||
};
|
||||
|
||||
export default ConfigContext;
|
||||
|
||||
|
||||
@ -42,4 +42,4 @@
|
||||
|
||||
.ant-input-affix-wrapper .ant-input{
|
||||
min-height: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@ -19,42 +19,39 @@
|
||||
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 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 './index.css';
|
||||
|
||||
const routes = [
|
||||
{
|
||||
path: '/store/login',
|
||||
{
|
||||
path: '/store/login',
|
||||
exact: true,
|
||||
component: Login,
|
||||
},
|
||||
{
|
||||
path: '/store',
|
||||
exact: false,
|
||||
component: Dashboard,
|
||||
routes: [
|
||||
{
|
||||
path: '/store/:deviceType',
|
||||
component: Apps,
|
||||
exact: true,
|
||||
component: Login
|
||||
},
|
||||
{
|
||||
path: '/store',
|
||||
exact: false,
|
||||
component: Dashboard,
|
||||
routes: [
|
||||
{
|
||||
path: '/store/:deviceType',
|
||||
component: Apps,
|
||||
exact: true
|
||||
},
|
||||
{
|
||||
path: '/store/:deviceType/apps/:uuid',
|
||||
exact: true,
|
||||
component: Release
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
path: '/store/:deviceType/apps/:uuid',
|
||||
exact: true,
|
||||
component: Release,
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
|
||||
ReactDOM.render(
|
||||
<App routes={routes}/>,
|
||||
document.getElementById('root'));
|
||||
ReactDOM.render(<App routes={routes} />, document.getElementById('root'));
|
||||
|
||||
// If you want your app e and load faster, you can change
|
||||
// unregister() to register() below. Note this comes with some pitfalls.
|
||||
|
||||
@ -16,18 +16,29 @@
|
||||
* 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) {
|
||||
const redirectUrl = encodeURI(window.location.href);
|
||||
window.location.href = window.location.origin + `/store/login?redirect=${redirectUrl}`;
|
||||
// silence 403 forbidden message
|
||||
} else if (!(isForbiddenMessageSilent && error.hasOwnProperty("response") && error.response.status === 403)) {
|
||||
notification["error"]({
|
||||
message: "There was a problem",
|
||||
duration: 10,
|
||||
description: message,
|
||||
});
|
||||
}
|
||||
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 + `/store/login?redirect=${redirectUrl}`;
|
||||
// silence 403 forbidden message
|
||||
} else if (
|
||||
!(
|
||||
isForbiddenMessageSilent &&
|
||||
error.hasOwnProperty('response') &&
|
||||
error.response.status === 403
|
||||
)
|
||||
) {
|
||||
notification.error({
|
||||
message: 'There was a problem',
|
||||
duration: 10,
|
||||
description: message,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
@ -59,4 +59,4 @@
|
||||
.login .content {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@ -16,172 +16,193 @@
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
import {Typography, Row, Col, Form, Icon, Input, Button, Checkbox, notification} from 'antd';
|
||||
import React from 'react';
|
||||
import {
|
||||
Typography,
|
||||
Row,
|
||||
Col,
|
||||
Form,
|
||||
Icon,
|
||||
Input,
|
||||
Button,
|
||||
Checkbox,
|
||||
notification,
|
||||
} from 'antd';
|
||||
import './Login.css';
|
||||
import axios from 'axios';
|
||||
import {withConfigContext} from "../context/ConfigContext";
|
||||
import {handleApiError} from "../js/Utils";
|
||||
import { withConfigContext } from '../context/ConfigContext';
|
||||
|
||||
const {Title} = Typography;
|
||||
const {Text} = Typography;
|
||||
const { Title } = Typography;
|
||||
const { Text } = Typography;
|
||||
|
||||
class Login extends React.Component {
|
||||
render() {
|
||||
const config = this.props.context;
|
||||
return (
|
||||
<div className="login">
|
||||
<div className="background">
|
||||
</div>
|
||||
<div className="content">
|
||||
<Row>
|
||||
<Col xs={3} sm={3} md={10}>
|
||||
|
||||
</Col>
|
||||
<Col xs={18} sm={18} md={4}>
|
||||
<Row style={{marginBottom: 20}}>
|
||||
<Col style={{textAlign: "center"}}>
|
||||
<img style={
|
||||
{
|
||||
marginTop: 36,
|
||||
height: 60
|
||||
}
|
||||
}
|
||||
src={config.theme.logo}/>
|
||||
</Col>
|
||||
</Row>
|
||||
<Title level={2}>Login</Title>
|
||||
<WrappedNormalLoginForm/>
|
||||
|
||||
</Col>
|
||||
</Row>
|
||||
<Row>
|
||||
<Col span={4} offset={10}>
|
||||
|
||||
</Col>
|
||||
</Row>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
);
|
||||
}
|
||||
render() {
|
||||
const config = this.props.context;
|
||||
return (
|
||||
<div className="login">
|
||||
<div className="background"></div>
|
||||
<div className="content">
|
||||
<Row>
|
||||
<Col xs={3} sm={3} md={10}></Col>
|
||||
<Col xs={18} sm={18} md={4}>
|
||||
<Row style={{ marginBottom: 20 }}>
|
||||
<Col style={{ textAlign: 'center' }}>
|
||||
<img
|
||||
style={{
|
||||
marginTop: 36,
|
||||
height: 60,
|
||||
}}
|
||||
src={config.theme.logo}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
<Title level={2}>Login</Title>
|
||||
<WrappedNormalLoginForm />
|
||||
</Col>
|
||||
</Row>
|
||||
<Row>
|
||||
<Col span={4} offset={10}></Col>
|
||||
</Row>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class NormalLoginForm extends React.Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
inValid: false,
|
||||
loading: false
|
||||
};
|
||||
}
|
||||
|
||||
handleSubmit = (e) => {
|
||||
const thisForm = this;
|
||||
const config = this.props.context;
|
||||
|
||||
e.preventDefault();
|
||||
this.props.form.validateFields((err, values) => {
|
||||
thisForm.setState({
|
||||
inValid: false
|
||||
});
|
||||
if (!err) {
|
||||
thisForm.setState({
|
||||
loading: true
|
||||
});
|
||||
const parameters = {
|
||||
username: values.username,
|
||||
password: values.password,
|
||||
platform: "store"
|
||||
};
|
||||
|
||||
const request = Object.keys(parameters).map(key => key + '=' + parameters[key]).join('&');
|
||||
|
||||
axios.post(window.location.origin + config.serverConfig.loginUri, request
|
||||
).then(res => {
|
||||
if (res.status === 200) {
|
||||
let redirectUrl = window.location.origin + "/store";
|
||||
const searchParams = new URLSearchParams(window.location.search);
|
||||
if (searchParams.has("redirect")) {
|
||||
redirectUrl = searchParams.get("redirect");
|
||||
}
|
||||
window.location = redirectUrl;
|
||||
}
|
||||
}).catch(function (error) {
|
||||
if (error.hasOwnProperty("response") && error.response.status === 401) {
|
||||
thisForm.setState({
|
||||
loading: false,
|
||||
inValid: true
|
||||
});
|
||||
} else {
|
||||
notification["error"]({
|
||||
message: "There was a problem",
|
||||
duration: 10,
|
||||
description: message,
|
||||
});
|
||||
thisForm.setState({
|
||||
loading: false,
|
||||
inValid: false
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
});
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
inValid: false,
|
||||
loading: false,
|
||||
};
|
||||
}
|
||||
|
||||
render() {
|
||||
const {getFieldDecorator} = this.props.form;
|
||||
let errorMsg = "";
|
||||
if (this.state.inValid) {
|
||||
errorMsg = <Text type="danger">Invalid Login Details</Text>;
|
||||
}
|
||||
let loading = "";
|
||||
if (this.state.loading) {
|
||||
loading = <Text type="secondary">Loading..</Text>;
|
||||
}
|
||||
return (
|
||||
<Form onSubmit={this.handleSubmit} className="login-form">
|
||||
<Form.Item>
|
||||
{getFieldDecorator('username', {
|
||||
rules: [{required: true, message: 'Please input your username!'}],
|
||||
})(
|
||||
<Input name="username" 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 input your Password!'}],
|
||||
})(
|
||||
<Input name="password" style={{height: 32}}
|
||||
prefix={<Icon type="lock" style={{color: 'rgba(0,0,0,.25)'}}/>} type="password"
|
||||
placeholder="Password"/>
|
||||
)}
|
||||
</Form.Item>
|
||||
{loading}
|
||||
{errorMsg}
|
||||
<Form.Item>
|
||||
{getFieldDecorator('remember', {
|
||||
valuePropName: 'checked',
|
||||
initialValue: true,
|
||||
})(
|
||||
<Checkbox>Remember me</Checkbox>
|
||||
)}
|
||||
<br/>
|
||||
<a className="login-form-forgot" href="">Forgot password</a>
|
||||
<Button loading={this.state.loading} block type="primary" htmlType="submit"
|
||||
className="login-form-button">
|
||||
Log in
|
||||
</Button>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
);
|
||||
handleSubmit = e => {
|
||||
const thisForm = this;
|
||||
const config = this.props.context;
|
||||
|
||||
e.preventDefault();
|
||||
this.props.form.validateFields((err, values) => {
|
||||
thisForm.setState({
|
||||
inValid: false,
|
||||
});
|
||||
if (!err) {
|
||||
thisForm.setState({
|
||||
loading: true,
|
||||
});
|
||||
const parameters = {
|
||||
username: values.username,
|
||||
password: values.password,
|
||||
platform: 'store',
|
||||
};
|
||||
|
||||
const request = Object.keys(parameters)
|
||||
.map(key => key + '=' + parameters[key])
|
||||
.join('&');
|
||||
|
||||
axios
|
||||
.post(window.location.origin + config.serverConfig.loginUri, request)
|
||||
.then(res => {
|
||||
if (res.status === 200) {
|
||||
let redirectUrl = window.location.origin + '/store';
|
||||
const searchParams = new URLSearchParams(window.location.search);
|
||||
if (searchParams.has('redirect')) {
|
||||
redirectUrl = searchParams.get('redirect');
|
||||
}
|
||||
window.location = redirectUrl;
|
||||
}
|
||||
})
|
||||
.catch(function(error) {
|
||||
if (
|
||||
error.hasOwnProperty('response') &&
|
||||
error.response.status === 401
|
||||
) {
|
||||
thisForm.setState({
|
||||
loading: false,
|
||||
inValid: true,
|
||||
});
|
||||
} else {
|
||||
notification.error({
|
||||
message: 'There was a problem',
|
||||
duration: 10,
|
||||
description: '',
|
||||
});
|
||||
thisForm.setState({
|
||||
loading: false,
|
||||
inValid: false,
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
render() {
|
||||
const { getFieldDecorator } = this.props.form;
|
||||
let errorMsg = '';
|
||||
if (this.state.inValid) {
|
||||
errorMsg = <Text type="danger">Invalid Login Details</Text>;
|
||||
}
|
||||
let loading = '';
|
||||
if (this.state.loading) {
|
||||
loading = <Text type="secondary">Loading..</Text>;
|
||||
}
|
||||
return (
|
||||
<Form onSubmit={this.handleSubmit} className="login-form">
|
||||
<Form.Item>
|
||||
{getFieldDecorator('username', {
|
||||
rules: [{ required: true, message: 'Please input your username!' }],
|
||||
})(
|
||||
<Input
|
||||
name="username"
|
||||
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 input your Password!' }],
|
||||
})(
|
||||
<Input
|
||||
name="password"
|
||||
style={{ height: 32 }}
|
||||
prefix={<Icon type="lock" style={{ color: 'rgba(0,0,0,.25)' }} />}
|
||||
type="password"
|
||||
placeholder="Password"
|
||||
/>,
|
||||
)}
|
||||
</Form.Item>
|
||||
{loading}
|
||||
{errorMsg}
|
||||
<Form.Item>
|
||||
{getFieldDecorator('remember', {
|
||||
valuePropName: 'checked',
|
||||
initialValue: true,
|
||||
})(<Checkbox>Remember me</Checkbox>)}
|
||||
<br />
|
||||
<a className="login-form-forgot" href="">
|
||||
Forgot password
|
||||
</a>
|
||||
<Button
|
||||
loading={this.state.loading}
|
||||
block
|
||||
type="primary"
|
||||
htmlType="submit"
|
||||
className="login-form-button"
|
||||
>
|
||||
Log in
|
||||
</Button>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const WrappedNormalLoginForm = withConfigContext(Form.create({name: 'normal_login'})(NormalLoginForm));
|
||||
const WrappedNormalLoginForm = withConfigContext(
|
||||
Form.create({ name: 'normal_login' })(NormalLoginForm),
|
||||
);
|
||||
|
||||
export default withConfigContext(Login);
|
||||
|
||||
@ -87,4 +87,4 @@
|
||||
margin-top: 10%;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@ -16,225 +16,261 @@
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
import {Layout, Menu, Icon, Drawer, Button, Alert} from 'antd';
|
||||
import React from 'react';
|
||||
import { Layout, Menu, Icon, Drawer, Button, Alert } from 'antd';
|
||||
|
||||
const {Header, Content, Footer} = Layout;
|
||||
import {Link} from "react-router-dom";
|
||||
import RouteWithSubRoutes from "../../components/RouteWithSubRoutes";
|
||||
import {Switch} from 'react-router';
|
||||
import axios from "axios";
|
||||
import "./Dashboard.css";
|
||||
import {withConfigContext} from "../../context/ConfigContext";
|
||||
import Logout from "./logout/Logout";
|
||||
import {handleApiError} from "../../js/Utils";
|
||||
const { Header, Content, Footer } = Layout;
|
||||
import { Link } from 'react-router-dom';
|
||||
import RouteWithSubRoutes from '../../components/RouteWithSubRoutes';
|
||||
import { Switch } from 'react-router';
|
||||
import axios from 'axios';
|
||||
import './Dashboard.css';
|
||||
import { withConfigContext } from '../../context/ConfigContext';
|
||||
import Logout from './logout/Logout';
|
||||
import { handleApiError } from '../../js/Utils';
|
||||
|
||||
const {SubMenu} = Menu;
|
||||
const { SubMenu } = Menu;
|
||||
|
||||
class Dashboard extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
routes: props.routes,
|
||||
selectedKeys: [],
|
||||
deviceTypes: [],
|
||||
visible: false,
|
||||
collapsed: false,
|
||||
forbiddenErrors: {
|
||||
deviceTypes: false
|
||||
}
|
||||
};
|
||||
this.logo = this.props.context.theme.logo;
|
||||
this.footerText = this.props.context.theme.footerText;
|
||||
this.config = this.props.context;
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.getDeviceTypes();
|
||||
}
|
||||
|
||||
getDeviceTypes = () => {
|
||||
const config = this.props.context;
|
||||
axios.get(
|
||||
window.location.origin + config.serverConfig.invoker.uri + config.serverConfig.invoker.deviceMgt + "/device-types"
|
||||
).then(res => {
|
||||
if (res.status === 200) {
|
||||
const deviceTypes = JSON.parse(res.data.data);
|
||||
this.setState({
|
||||
deviceTypes,
|
||||
loading: false,
|
||||
});
|
||||
}
|
||||
|
||||
}).catch((error) => {
|
||||
handleApiError(error, "Error occurred while trying to load device types.", true);
|
||||
if (error.hasOwnProperty("response") && error.response.status === 403) {
|
||||
const {forbiddenErrors} = this.state;
|
||||
forbiddenErrors.deviceTypes = true;
|
||||
this.setState({
|
||||
forbiddenErrors,
|
||||
loading: false
|
||||
})
|
||||
} else {
|
||||
this.setState({
|
||||
loading: false
|
||||
});
|
||||
}
|
||||
});
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
routes: props.routes,
|
||||
selectedKeys: [],
|
||||
deviceTypes: [],
|
||||
visible: false,
|
||||
collapsed: false,
|
||||
forbiddenErrors: {
|
||||
deviceTypes: false,
|
||||
},
|
||||
};
|
||||
this.logo = this.props.context.theme.logo;
|
||||
this.footerText = this.props.context.theme.footerText;
|
||||
this.config = this.props.context;
|
||||
}
|
||||
|
||||
changeSelectedMenuItem = (key) => {
|
||||
this.setState({
|
||||
selectedKeys: [key]
|
||||
})
|
||||
};
|
||||
componentDidMount() {
|
||||
this.getDeviceTypes();
|
||||
}
|
||||
|
||||
showMobileNavigationBar = () => {
|
||||
this.setState({
|
||||
visible: true,
|
||||
collapsed: !this.state.collapsed,
|
||||
});
|
||||
};
|
||||
|
||||
onCloseMobileNavigationBar = () => {
|
||||
this.setState({
|
||||
visible: false,
|
||||
});
|
||||
};
|
||||
|
||||
render() {
|
||||
const config = this.props.context;
|
||||
const {selectedKeys, deviceTypes, forbiddenErrors} = this.state;
|
||||
|
||||
const DeviceTypesData = deviceTypes.map((deviceType) => {
|
||||
const platform = deviceType.name;
|
||||
const defaultPlatformIcons = config.defaultPlatformIcons;
|
||||
let icon = defaultPlatformIcons.default.icon;
|
||||
let theme = defaultPlatformIcons.default.theme;
|
||||
if (defaultPlatformIcons.hasOwnProperty(platform)) {
|
||||
icon = defaultPlatformIcons[platform].icon;
|
||||
theme = defaultPlatformIcons[platform].theme;
|
||||
}
|
||||
return (
|
||||
<Menu.Item key={platform}>
|
||||
<Link to={"/store/" + platform}>
|
||||
<Icon type={icon} theme={theme}/>
|
||||
{platform}
|
||||
</Link>
|
||||
</Menu.Item>
|
||||
);
|
||||
});
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Layout>
|
||||
<Header style={{paddingLeft: 0, paddingRight: 0, backgroundColor: "white"}}>
|
||||
<div className="logo-image">
|
||||
<Link to="/store"><img alt="logo" src={this.logo}/></Link>
|
||||
</div>
|
||||
|
||||
<div className="web-layout">
|
||||
<Menu
|
||||
theme="light"
|
||||
mode="horizontal"
|
||||
defaultSelectedKeys={selectedKeys}
|
||||
style={{lineHeight: '64px'}}>
|
||||
|
||||
{DeviceTypesData}
|
||||
|
||||
<Menu.Item key="web-clip">
|
||||
<Link to="/store/web-clip">
|
||||
<Icon type="upload"/>
|
||||
Web Clips
|
||||
</Link>
|
||||
</Menu.Item>
|
||||
|
||||
<SubMenu className="profile"
|
||||
title={
|
||||
<span className="submenu-title-wrapper">
|
||||
<Icon type="user"/>
|
||||
{this.config.user}
|
||||
</span>}>
|
||||
<Logout/>
|
||||
</SubMenu>
|
||||
</Menu>
|
||||
</div>
|
||||
</Header>
|
||||
</Layout>
|
||||
|
||||
<Layout className="mobile-layout">
|
||||
|
||||
<div className="mobile-menu-button">
|
||||
<Button type="link" onClick={this.showMobileNavigationBar}>
|
||||
<Icon type={this.state.collapsed ? 'menu-fold' : 'menu-unfold'} className="bar-icon"/>
|
||||
</Button>
|
||||
</div>
|
||||
</Layout>
|
||||
<Drawer
|
||||
title={<Link to="/store" onClick={this.onCloseMobileNavigationBar}>
|
||||
<img alt="logo" src={this.logo} style={{marginLeft: 30}} width={"60%"}/>
|
||||
</Link>}
|
||||
placement="left"
|
||||
closable={false}
|
||||
onClose={this.onCloseMobileNavigationBar}
|
||||
visible={this.state.visible}
|
||||
getContainer={false}
|
||||
style={{position: 'absolute'}}>
|
||||
<Menu
|
||||
theme="light"
|
||||
mode="inline"
|
||||
defaultSelectedKeys={selectedKeys}
|
||||
style={{lineHeight: '64px', width: 231}}
|
||||
onClick={this.onCloseMobileNavigationBar}>
|
||||
|
||||
{DeviceTypesData}
|
||||
|
||||
<Menu.Item key="web-clip">
|
||||
<Link to="/store/web-clip">
|
||||
<Icon type="upload"/>Web Clips
|
||||
</Link>
|
||||
</Menu.Item>
|
||||
</Menu>
|
||||
</Drawer>
|
||||
<Layout className="mobile-layout">
|
||||
<Menu
|
||||
mode="horizontal"
|
||||
defaultSelectedKeys={selectedKeys}
|
||||
style={{lineHeight: '63px', position: 'fixed', marginLeft: '80%'}}>
|
||||
<SubMenu
|
||||
title={
|
||||
<span className="submenu-title-wrapper">
|
||||
<Icon type="user"/>
|
||||
</span>}>
|
||||
<Logout/>
|
||||
</SubMenu>
|
||||
</Menu>
|
||||
</Layout>
|
||||
|
||||
<Layout className="dashboard-body">
|
||||
{(forbiddenErrors.deviceTypes) && (
|
||||
<Alert
|
||||
message="You don't have permission to view device types."
|
||||
type="warning"
|
||||
banner
|
||||
closable/>
|
||||
)}
|
||||
<Content style={{padding: '0 0'}}>
|
||||
<Switch>
|
||||
{this.state.routes.map((route) => (
|
||||
<RouteWithSubRoutes changeSelectedMenuItem={this.changeSelectedMenuItem}
|
||||
key={route.path} {...route} />
|
||||
))}
|
||||
</Switch>
|
||||
</Content>
|
||||
|
||||
<Footer style={{textAlign: 'center'}}>
|
||||
{this.footerText}
|
||||
</Footer>
|
||||
</Layout>
|
||||
</div>
|
||||
getDeviceTypes = () => {
|
||||
const config = this.props.context;
|
||||
axios
|
||||
.get(
|
||||
window.location.origin +
|
||||
config.serverConfig.invoker.uri +
|
||||
config.serverConfig.invoker.deviceMgt +
|
||||
'/device-types',
|
||||
)
|
||||
.then(res => {
|
||||
if (res.status === 200) {
|
||||
const deviceTypes = JSON.parse(res.data.data);
|
||||
this.setState({
|
||||
deviceTypes,
|
||||
loading: false,
|
||||
});
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
handleApiError(
|
||||
error,
|
||||
'Error occurred while trying to load device types.',
|
||||
true,
|
||||
);
|
||||
}
|
||||
if (error.hasOwnProperty('response') && error.response.status === 403) {
|
||||
const { forbiddenErrors } = this.state;
|
||||
forbiddenErrors.deviceTypes = true;
|
||||
this.setState({
|
||||
forbiddenErrors,
|
||||
loading: false,
|
||||
});
|
||||
} else {
|
||||
this.setState({
|
||||
loading: false,
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
changeSelectedMenuItem = key => {
|
||||
this.setState({
|
||||
selectedKeys: [key],
|
||||
});
|
||||
};
|
||||
|
||||
showMobileNavigationBar = () => {
|
||||
this.setState({
|
||||
visible: true,
|
||||
collapsed: !this.state.collapsed,
|
||||
});
|
||||
};
|
||||
|
||||
onCloseMobileNavigationBar = () => {
|
||||
this.setState({
|
||||
visible: false,
|
||||
});
|
||||
};
|
||||
|
||||
render() {
|
||||
const config = this.props.context;
|
||||
const { selectedKeys, deviceTypes, forbiddenErrors } = this.state;
|
||||
|
||||
const DeviceTypesData = deviceTypes.map(deviceType => {
|
||||
const platform = deviceType.name;
|
||||
const defaultPlatformIcons = config.defaultPlatformIcons;
|
||||
let icon = defaultPlatformIcons.default.icon;
|
||||
let theme = defaultPlatformIcons.default.theme;
|
||||
if (defaultPlatformIcons.hasOwnProperty(platform)) {
|
||||
icon = defaultPlatformIcons[platform].icon;
|
||||
theme = defaultPlatformIcons[platform].theme;
|
||||
}
|
||||
return (
|
||||
<Menu.Item key={platform}>
|
||||
<Link to={'/store/' + platform}>
|
||||
<Icon type={icon} theme={theme} />
|
||||
{platform}
|
||||
</Link>
|
||||
</Menu.Item>
|
||||
);
|
||||
});
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Layout>
|
||||
<Header
|
||||
style={{
|
||||
paddingLeft: 0,
|
||||
paddingRight: 0,
|
||||
backgroundColor: 'white',
|
||||
}}
|
||||
>
|
||||
<div className="logo-image">
|
||||
<Link to="/store">
|
||||
<img alt="logo" src={this.logo} />
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
<div className="web-layout">
|
||||
<Menu
|
||||
theme="light"
|
||||
mode="horizontal"
|
||||
defaultSelectedKeys={selectedKeys}
|
||||
style={{ lineHeight: '64px' }}
|
||||
>
|
||||
{DeviceTypesData}
|
||||
|
||||
<Menu.Item key="web-clip">
|
||||
<Link to="/store/web-clip">
|
||||
<Icon type="upload" />
|
||||
Web Clips
|
||||
</Link>
|
||||
</Menu.Item>
|
||||
|
||||
<SubMenu
|
||||
className="profile"
|
||||
title={
|
||||
<span className="submenu-title-wrapper">
|
||||
<Icon type="user" />
|
||||
{this.config.user}
|
||||
</span>
|
||||
}
|
||||
>
|
||||
<Logout />
|
||||
</SubMenu>
|
||||
</Menu>
|
||||
</div>
|
||||
</Header>
|
||||
</Layout>
|
||||
|
||||
<Layout className="mobile-layout">
|
||||
<div className="mobile-menu-button">
|
||||
<Button type="link" onClick={this.showMobileNavigationBar}>
|
||||
<Icon
|
||||
type={this.state.collapsed ? 'menu-fold' : 'menu-unfold'}
|
||||
className="bar-icon"
|
||||
/>
|
||||
</Button>
|
||||
</div>
|
||||
</Layout>
|
||||
<Drawer
|
||||
title={
|
||||
<Link to="/store" onClick={this.onCloseMobileNavigationBar}>
|
||||
<img
|
||||
alt="logo"
|
||||
src={this.logo}
|
||||
style={{ marginLeft: 30 }}
|
||||
width={'60%'}
|
||||
/>
|
||||
</Link>
|
||||
}
|
||||
placement="left"
|
||||
closable={false}
|
||||
onClose={this.onCloseMobileNavigationBar}
|
||||
visible={this.state.visible}
|
||||
getContainer={false}
|
||||
style={{ position: 'absolute' }}
|
||||
>
|
||||
<Menu
|
||||
theme="light"
|
||||
mode="inline"
|
||||
defaultSelectedKeys={selectedKeys}
|
||||
style={{ lineHeight: '64px', width: 231 }}
|
||||
onClick={this.onCloseMobileNavigationBar}
|
||||
>
|
||||
{DeviceTypesData}
|
||||
|
||||
<Menu.Item key="web-clip">
|
||||
<Link to="/store/web-clip">
|
||||
<Icon type="upload" />
|
||||
Web Clips
|
||||
</Link>
|
||||
</Menu.Item>
|
||||
</Menu>
|
||||
</Drawer>
|
||||
<Layout className="mobile-layout">
|
||||
<Menu
|
||||
mode="horizontal"
|
||||
defaultSelectedKeys={selectedKeys}
|
||||
style={{ lineHeight: '63px', position: 'fixed', marginLeft: '80%' }}
|
||||
>
|
||||
<SubMenu
|
||||
title={
|
||||
<span className="submenu-title-wrapper">
|
||||
<Icon type="user" />
|
||||
</span>
|
||||
}
|
||||
>
|
||||
<Logout />
|
||||
</SubMenu>
|
||||
</Menu>
|
||||
</Layout>
|
||||
|
||||
<Layout className="dashboard-body">
|
||||
{forbiddenErrors.deviceTypes && (
|
||||
<Alert
|
||||
message="You don't have permission to view device types."
|
||||
type="warning"
|
||||
banner
|
||||
closable
|
||||
/>
|
||||
)}
|
||||
<Content style={{ padding: '0 0' }}>
|
||||
<Switch>
|
||||
{this.state.routes.map(route => (
|
||||
<RouteWithSubRoutes
|
||||
changeSelectedMenuItem={this.changeSelectedMenuItem}
|
||||
key={route.path}
|
||||
{...route}
|
||||
/>
|
||||
))}
|
||||
</Switch>
|
||||
</Content>
|
||||
|
||||
<Footer style={{ textAlign: 'center' }}>{this.footerText}</Footer>
|
||||
</Layout>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default withConfigContext(Dashboard);
|
||||
|
||||
@ -1,353 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2019, Entgra (pvt) Ltd. (http://entgra.io) All Rights Reserved.
|
||||
*
|
||||
* Entgra (pvt) Ltd. licenses this file to you under the Apache License,
|
||||
* Version 2.0 (the "License"); you may not use this file except
|
||||
* in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
import {
|
||||
PageHeader,
|
||||
Typography,
|
||||
Card,
|
||||
Steps,
|
||||
Button,
|
||||
message,
|
||||
Row,
|
||||
Col,
|
||||
Tag,
|
||||
Tooltip,
|
||||
Input,
|
||||
Icon,
|
||||
Select,
|
||||
Switch,
|
||||
Form,
|
||||
Upload,
|
||||
Divider
|
||||
} from "antd";
|
||||
import Step1 from "./Step1";
|
||||
import Step2 from "./Step2";
|
||||
import Step3 from "./Step3";
|
||||
import styles from "./Style.less";
|
||||
import IconImage from "./IconImg";
|
||||
import UploadScreenshots from "./UploadScreenshots";
|
||||
|
||||
const Paragraph = Typography;
|
||||
const Dragger = Upload.Dragger;
|
||||
const routes = [
|
||||
{
|
||||
path: 'index',
|
||||
breadcrumbName: 'store',
|
||||
},
|
||||
{
|
||||
path: 'first',
|
||||
breadcrumbName: 'dashboard',
|
||||
},
|
||||
{
|
||||
path: 'second',
|
||||
breadcrumbName: 'add new app',
|
||||
},
|
||||
];
|
||||
|
||||
const props = {
|
||||
name: 'file',
|
||||
multiple: false,
|
||||
action: '//jsonplaceholder.typicode.com/posts/',
|
||||
onChange(info) {
|
||||
const status = info.file.status;
|
||||
if (status !== 'uploading') {
|
||||
// console.log(info.file, info.fileList);
|
||||
}
|
||||
if (status === 'done') {
|
||||
message.success(`${info.file.name} file uploaded successfully.`);
|
||||
} else if (status === 'error') {
|
||||
message.error(`${info.file.name} file upload failed.`);
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
const Step = Steps.Step;
|
||||
|
||||
const steps = [{
|
||||
title: 'First',
|
||||
content: Step1
|
||||
}, {
|
||||
title: 'Second',
|
||||
content: Step2,
|
||||
}, {
|
||||
title: 'Last',
|
||||
content: Step3,
|
||||
}];
|
||||
|
||||
|
||||
const {Option} = Select;
|
||||
const {TextArea} = Input;
|
||||
const InputGroup = Input.Group;
|
||||
|
||||
const formItemLayout = {
|
||||
labelCol: {
|
||||
span: 4,
|
||||
},
|
||||
wrapperCol: {
|
||||
span: 20,
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
|
||||
class EditableTagGroup extends React.Component {
|
||||
state = {
|
||||
tags: [],
|
||||
inputVisible: false,
|
||||
inputValue: '',
|
||||
};
|
||||
|
||||
handleClose = (removedTag) => {
|
||||
const tags = this.state.tags.filter(tag => tag !== removedTag);
|
||||
// console.log(tags);
|
||||
this.setState({tags});
|
||||
}
|
||||
|
||||
showInput = () => {
|
||||
this.setState({inputVisible: true}, () => this.input.focus());
|
||||
}
|
||||
|
||||
handleInputChange = (e) => {
|
||||
this.setState({inputValue: e.target.value});
|
||||
}
|
||||
|
||||
handleInputConfirm = () => {
|
||||
const {inputValue} = this.state;
|
||||
let {tags} = this.state;
|
||||
if (inputValue && tags.indexOf(inputValue) === -1) {
|
||||
tags = [...tags, inputValue];
|
||||
}
|
||||
// console.log(tags);
|
||||
this.setState({
|
||||
tags,
|
||||
inputVisible: false,
|
||||
inputValue: '',
|
||||
});
|
||||
}
|
||||
|
||||
saveInputRef = input => this.input = input
|
||||
|
||||
render() {
|
||||
const {tags, inputVisible, inputValue} = this.state;
|
||||
return (
|
||||
<div>
|
||||
{tags.map((tag, index) => {
|
||||
const isLongTag = tag.length > 20;
|
||||
const tagElem = (
|
||||
<Tag key={tag} closable={index !== 0} onClose={() => this.handleClose(tag)}>
|
||||
{isLongTag ? `${tag.slice(0, 20)}...` : tag}
|
||||
</Tag>
|
||||
);
|
||||
return isLongTag ? <Tooltip title={tag} key={tag}>{tagElem}</Tooltip> : tagElem;
|
||||
})}
|
||||
{inputVisible && (
|
||||
<Input
|
||||
ref={this.saveInputRef}
|
||||
type="text"
|
||||
size="small"
|
||||
style={{width: 78}}
|
||||
value={inputValue}
|
||||
onChange={this.handleInputChange}
|
||||
onBlur={this.handleInputConfirm}
|
||||
onPressEnter={this.handleInputConfirm}
|
||||
/>
|
||||
)}
|
||||
{!inputVisible && (
|
||||
<Tag
|
||||
onClick={this.showInput}
|
||||
style={{background: '#fff', borderStyle: 'dashed'}}
|
||||
>
|
||||
<Icon type="plus"/> New Tag
|
||||
</Tag>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class AddNewApp extends React.Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
current: 0,
|
||||
};
|
||||
}
|
||||
|
||||
tags = [];
|
||||
|
||||
addTag(key, value){
|
||||
this.tags.push(<Option key={key}>{value}</Option>);
|
||||
}
|
||||
|
||||
next() {
|
||||
const current = this.state.current + 1;
|
||||
this.setState({current});
|
||||
}
|
||||
|
||||
prev() {
|
||||
const current = this.state.current - 1;
|
||||
this.setState({current});
|
||||
}
|
||||
|
||||
|
||||
render() {
|
||||
const {current} = this.state;
|
||||
const Content = steps[current].content;
|
||||
this.addTag('1','Lorem');
|
||||
this.addTag('2','Ipsum');
|
||||
return (
|
||||
<div>
|
||||
<PageHeader
|
||||
title="Add New App"
|
||||
breadcrumb={{routes}}
|
||||
>
|
||||
<div className="wrap">
|
||||
<div className="content">
|
||||
<Paragraph>
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempo.
|
||||
</Paragraph>
|
||||
</div>
|
||||
</div>
|
||||
</PageHeader>
|
||||
<div style={{background: '#f0f2f5', padding: 24, minHeight: 720}}>
|
||||
<Row>
|
||||
<Col span={20} offset={2}>
|
||||
<Card>
|
||||
<Row>
|
||||
<Col span={12}>
|
||||
<div>
|
||||
<Form labelAlign="left" layout="horizontal" className={styles.stepForm}
|
||||
hideRequiredMark>
|
||||
<Form.Item {...formItemLayout} label="Platform">
|
||||
<Select placeholder="ex: android">
|
||||
<Option value="Android">Android</Option>
|
||||
<Option value="iOS">iOS</Option>
|
||||
</Select>
|
||||
</Form.Item>
|
||||
<Form.Item {...formItemLayout} label="Type">
|
||||
<Select value="Enterprise">
|
||||
<Option value="Enterprise" selected>Enterprise</Option>
|
||||
</Select>
|
||||
</Form.Item>
|
||||
<Form.Item {...formItemLayout} label="App Name">
|
||||
<Input placeholder="ex: Lorem App"/>
|
||||
</Form.Item>
|
||||
<Form.Item {...formItemLayout} label="Description">
|
||||
<TextArea placeholder="Enter the description..." rows={7}/>
|
||||
</Form.Item>
|
||||
<Form.Item {...formItemLayout} label="Category">
|
||||
<Select placeholder="Select a category">
|
||||
<Option value="travel">Travel</Option>
|
||||
<Option value="entertainment">Entertainment</Option>
|
||||
</Select>
|
||||
</Form.Item>
|
||||
<Form.Item {...formItemLayout} label="Price">
|
||||
<Input prefix="$" placeholder="00.00"/>
|
||||
</Form.Item>
|
||||
<Form.Item {...formItemLayout} label="Is Sahred?">
|
||||
<Switch checkedChildren={<Icon type="check" />} unCheckedChildren={<Icon type="close" />} defaultChecked />
|
||||
</Form.Item>
|
||||
<Divider/>
|
||||
<Form.Item {...formItemLayout} label="Tags">
|
||||
|
||||
<InputGroup>
|
||||
<Row gutter={8}>
|
||||
<Col span={22}>
|
||||
<Select
|
||||
mode="multiple"
|
||||
style={{ width: '100%' }}
|
||||
placeholder="Tags Mode"
|
||||
>
|
||||
{this.tags}
|
||||
</Select>
|
||||
</Col>
|
||||
<Col span={2}>
|
||||
<Button type="dashed" shape="circle" icon="plus"/>
|
||||
</Col>
|
||||
</Row>
|
||||
</InputGroup>
|
||||
</Form.Item>
|
||||
<Form.Item {...formItemLayout} label="Meta Daa">
|
||||
<InputGroup>
|
||||
<Row gutter={8}>
|
||||
<Col span={10}>
|
||||
<Input placeholder="Key"/>
|
||||
</Col>
|
||||
<Col span={12}>
|
||||
<Input placeholder="value"/>
|
||||
</Col>
|
||||
<Col span={2}>
|
||||
<Button type="dashed" shape="circle" icon="plus"/>
|
||||
</Col>
|
||||
</Row>
|
||||
</InputGroup>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</div>
|
||||
</Col>
|
||||
<Col span={12} style={{paddingTop: 40, paddingLeft: 20}}>
|
||||
<p>Application</p>
|
||||
<div style={{height: 170}}>
|
||||
<Dragger {...props}>
|
||||
<p className="ant-upload-drag-icon">
|
||||
<Icon type="inbox"/>
|
||||
</p>
|
||||
<p className="ant-upload-text">Click or drag file to this area to
|
||||
upload</p>
|
||||
<p className="ant-upload-hint">Support for a single or bulk upload.
|
||||
Strictly prohibit from uploading company data or other band
|
||||
files</p>
|
||||
</Dragger>
|
||||
</div>
|
||||
<Row style={{marginTop: 40}}>
|
||||
<Col span={12}>
|
||||
<p>Icon</p>
|
||||
<IconImage/>
|
||||
</Col>
|
||||
<Col span={12}>
|
||||
<p>Banner</p>
|
||||
<IconImage/>
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
|
||||
<Row style={{marginTop: 40}}>
|
||||
<Col span={24}>
|
||||
<p>Screenshots</p>
|
||||
<UploadScreenshots/>
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
</Col>
|
||||
|
||||
</Row>
|
||||
</Card>
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default AddNewApp;
|
||||
@ -1,84 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2019, Entgra (pvt) Ltd. (http://entgra.io) All Rights Reserved.
|
||||
*
|
||||
* Entgra (pvt) Ltd. licenses this file to you under the Apache License,
|
||||
* Version 2.0 (the "License"); you may not use this file except
|
||||
* in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
import { Upload, Icon, message } from 'antd';
|
||||
|
||||
function getBase64(img, callback) {
|
||||
const reader = new FileReader();
|
||||
reader.addEventListener('load', () => callback(reader.result));
|
||||
reader.readAsDataURL(img);
|
||||
}
|
||||
|
||||
function beforeUpload(file) {
|
||||
const isJPG = file.type === 'image/jpeg';
|
||||
if (!isJPG) {
|
||||
message.error('You can only upload JPG file!');
|
||||
}
|
||||
const isLt2M = file.size / 1024 / 1024 < 2;
|
||||
if (!isLt2M) {
|
||||
message.error('Image must smaller than 2MB!');
|
||||
}
|
||||
return isJPG && isLt2M;
|
||||
}
|
||||
|
||||
|
||||
class IconImage extends React.Component {
|
||||
state = {
|
||||
loading: false,
|
||||
};
|
||||
|
||||
handleChange = (info) => {
|
||||
if (info.file.status === 'uploading') {
|
||||
this.setState({ loading: true });
|
||||
return;
|
||||
}
|
||||
if (info.file.status === 'done') {
|
||||
// Get this url from response in real world.
|
||||
getBase64(info.file.originFileObj, imageUrl => this.setState({
|
||||
imageUrl,
|
||||
loading: false,
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const uploadButton = (
|
||||
<div>
|
||||
<Icon type={this.state.loading ? 'loading' : 'plus'} />
|
||||
<div className="ant-upload-text">Upload</div>
|
||||
</div>
|
||||
);
|
||||
const imageUrl = this.state.imageUrl;
|
||||
return (
|
||||
<Upload
|
||||
name="avatar"
|
||||
listType="picture-card"
|
||||
className="avatar-uploader"
|
||||
showUploadList={false}
|
||||
action="//jsonplaceholder.typicode.com/posts/"
|
||||
beforeUpload={beforeUpload}
|
||||
onChange={this.handleChange}
|
||||
>
|
||||
{imageUrl ? <img src={imageUrl} alt="avatar" /> : uploadButton}
|
||||
</Upload>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default IconImage;
|
||||
@ -1,170 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2019, Entgra (pvt) Ltd. (http://entgra.io) All Rights Reserved.
|
||||
*
|
||||
* Entgra (pvt) Ltd. licenses this file to you under the Apache License,
|
||||
* Version 2.0 (the "License"); you may not use this file except
|
||||
* in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
import {Form, Input, Button, Select, Divider, Tag, Tooltip, Icon, Checkbox, Row, Col} from "antd";
|
||||
import styles from './Style.less';
|
||||
|
||||
const { Option } = Select;
|
||||
const { TextArea } = Input;
|
||||
const InputGroup = Input.Group;
|
||||
|
||||
const formItemLayout = {
|
||||
labelCol: {
|
||||
span: 8,
|
||||
},
|
||||
wrapperCol: {
|
||||
span: 16,
|
||||
},
|
||||
};
|
||||
|
||||
class EditableTagGroup extends React.Component {
|
||||
state = {
|
||||
tags: [],
|
||||
inputVisible: false,
|
||||
inputValue: '',
|
||||
};
|
||||
|
||||
handleClose = (removedTag) => {
|
||||
const tags = this.state.tags.filter(tag => tag !== removedTag);
|
||||
// console.log(tags);
|
||||
this.setState({ tags });
|
||||
}
|
||||
|
||||
showInput = () => {
|
||||
this.setState({ inputVisible: true }, () => this.input.focus());
|
||||
}
|
||||
|
||||
handleInputChange = (e) => {
|
||||
this.setState({ inputValue: e.target.value });
|
||||
}
|
||||
|
||||
handleInputConfirm = () => {
|
||||
const { inputValue } = this.state;
|
||||
let { tags } = this.state;
|
||||
if (inputValue && tags.indexOf(inputValue) === -1) {
|
||||
tags = [...tags, inputValue];
|
||||
}
|
||||
// console.log(tags);
|
||||
this.setState({
|
||||
tags,
|
||||
inputVisible: false,
|
||||
inputValue: '',
|
||||
});
|
||||
}
|
||||
|
||||
saveInputRef = input => this.input = input
|
||||
|
||||
render() {
|
||||
const { tags, inputVisible, inputValue } = this.state;
|
||||
return (
|
||||
<div>
|
||||
{tags.map((tag, index) => {
|
||||
const isLongTag = tag.length > 20;
|
||||
const tagElem = (
|
||||
<Tag key={tag} closable={index !== 0} onClose={() => this.handleClose(tag)}>
|
||||
{isLongTag ? `${tag.slice(0, 20)}...` : tag}
|
||||
</Tag>
|
||||
);
|
||||
return isLongTag ? <Tooltip title={tag} key={tag}>{tagElem}</Tooltip> : tagElem;
|
||||
})}
|
||||
{inputVisible && (
|
||||
<Input
|
||||
ref={this.saveInputRef}
|
||||
type="text"
|
||||
size="small"
|
||||
style={{ width: 78 }}
|
||||
value={inputValue}
|
||||
onChange={this.handleInputChange}
|
||||
onBlur={this.handleInputConfirm}
|
||||
onPressEnter={this.handleInputConfirm}
|
||||
/>
|
||||
)}
|
||||
{!inputVisible && (
|
||||
<Tag
|
||||
onClick={this.showInput}
|
||||
style={{ background: '#fff', borderStyle: 'dashed' }}
|
||||
>
|
||||
<Icon type="plus" /> New Tag
|
||||
</Tag>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class Step1 extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
<Form layout="horizontal" className={styles.stepForm} hideRequiredMark>
|
||||
|
||||
<Form.Item {...formItemLayout} label="Platform">
|
||||
<Select placeholder="ex: android">
|
||||
<Option value="Android">Android</Option>
|
||||
<Option value="iOS">iOS</Option>
|
||||
</Select>
|
||||
</Form.Item>
|
||||
<Form.Item {...formItemLayout} label="Type">
|
||||
<Select value="Enterprise">
|
||||
<Option value="Enterprise" selected>Enterprise</Option>
|
||||
</Select>
|
||||
</Form.Item>
|
||||
<Form.Item {...formItemLayout} label="Name">
|
||||
<Input placeholder="App Name" />
|
||||
</Form.Item>
|
||||
<Form.Item {...formItemLayout} label="Description">
|
||||
<TextArea placeholder="Enter the description" rows={4} />
|
||||
</Form.Item>
|
||||
<Form.Item {...formItemLayout} label="Category">
|
||||
<Select placeholder="Select a category">
|
||||
<Option value="travel">Travel</Option>
|
||||
<Option value="entertainment">Entertainment</Option>
|
||||
</Select>
|
||||
</Form.Item>
|
||||
<Form.Item {...formItemLayout} label="Tags">
|
||||
<EditableTagGroup/>
|
||||
</Form.Item>
|
||||
<Form.Item {...formItemLayout} label="Price">
|
||||
<Input prefix="$" placeholder="00.00" />
|
||||
</Form.Item>
|
||||
<Form.Item {...formItemLayout} label="Share with all tenents?">
|
||||
<Checkbox > </Checkbox>
|
||||
</Form.Item>
|
||||
<Form.Item {...formItemLayout} label="Meta Daa">
|
||||
<InputGroup>
|
||||
<Row gutter={8}>
|
||||
<Col span={5}>
|
||||
<Input placeholder="Key" />
|
||||
</Col>
|
||||
<Col span={10}>
|
||||
<Input placeholder="value" />
|
||||
</Col>
|
||||
<Col span={4}>
|
||||
<Button type="dashed" shape="circle" icon="plus" />
|
||||
</Col>
|
||||
</Row>
|
||||
</InputGroup>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default Step1;
|
||||
@ -1,29 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2019, Entgra (pvt) Ltd. (http://entgra.io) All Rights Reserved.
|
||||
*
|
||||
* Entgra (pvt) Ltd. licenses this file to you under the Apache License,
|
||||
* Version 2.0 (the "License"); you may not use this file except
|
||||
* in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import React from "react"
|
||||
|
||||
class Step2 extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<p>tttoooeeee</p>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default Step2;
|
||||
@ -1,29 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2019, Entgra (pvt) Ltd. (http://entgra.io) All Rights Reserved.
|
||||
*
|
||||
* Entgra (pvt) Ltd. licenses this file to you under the Apache License,
|
||||
* Version 2.0 (the "License"); you may not use this file except
|
||||
* in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import React from "react"
|
||||
|
||||
class Step3 extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<p>tttoooeeee</p>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default Step3;
|
||||
@ -1,22 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2019, Entgra (pvt) Ltd. (http://entgra.io) All Rights Reserved.
|
||||
*
|
||||
* Entgra (pvt) Ltd. licenses this file to you under the Apache License,
|
||||
* Version 2.0 (the "License"); you may not use this file except
|
||||
* in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
.stepForm {
|
||||
max-width: 500px;
|
||||
margin: 40px auto 0;
|
||||
}
|
||||
@ -1,67 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2019, Entgra (pvt) Ltd. (http://entgra.io) All Rights Reserved.
|
||||
*
|
||||
* Entgra (pvt) Ltd. licenses this file to you under the Apache License,
|
||||
* Version 2.0 (the "License"); you may not use this file except
|
||||
* in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
import { Upload, Icon, Modal} from 'antd';
|
||||
|
||||
|
||||
class UploadScreenshots extends React.Component {
|
||||
state = {
|
||||
previewVisible: false,
|
||||
previewImage: '',
|
||||
fileList: [],
|
||||
};
|
||||
|
||||
handleCancel = () => this.setState({ previewVisible: false });
|
||||
|
||||
handlePreview = (file) => {
|
||||
this.setState({
|
||||
previewImage: file.url || file.thumbUrl,
|
||||
previewVisible: true,
|
||||
});
|
||||
};
|
||||
|
||||
handleChange = ({ fileList }) => this.setState({ fileList });
|
||||
|
||||
render() {
|
||||
const { previewVisible, previewImage, fileList } = this.state;
|
||||
const uploadButton = (
|
||||
<div>
|
||||
<Icon type="plus" />
|
||||
<div className="ant-upload-text">Upload</div>
|
||||
</div>
|
||||
);
|
||||
return (
|
||||
<div className="clearfix">
|
||||
<Upload
|
||||
action="//jsonplaceholder.typicode.com/posts/"
|
||||
listType="picture-card"
|
||||
fileList={fileList}
|
||||
onPreview={this.handlePreview}
|
||||
onChange={this.handleChange}
|
||||
>
|
||||
{fileList.length >= 3 ? null : uploadButton}
|
||||
</Upload>
|
||||
<Modal visible={previewVisible} footer={null} onCancel={this.handleCancel}>
|
||||
<img alt="example" style={{ width: '100%' }} src={previewImage} />
|
||||
</Modal>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
export default UploadScreenshots;
|
||||
@ -1,67 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2019, Entgra (pvt) Ltd. (http://entgra.io) All Rights Reserved.
|
||||
*
|
||||
* Entgra (pvt) Ltd. licenses this file to you under the Apache License,
|
||||
* Version 2.0 (the "License"); you may not use this file except
|
||||
* in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
import { Upload, Icon, Modal} from 'antd';
|
||||
|
||||
|
||||
class AddTagModal extends React.Component {
|
||||
state = {
|
||||
previewVisible: false,
|
||||
previewImage: '',
|
||||
fileList: [],
|
||||
};
|
||||
|
||||
handleCancel = () => this.setState({ previewVisible: false });
|
||||
|
||||
handlePreview = (file) => {
|
||||
this.setState({
|
||||
previewImage: file.url || file.thumbUrl,
|
||||
previewVisible: true,
|
||||
});
|
||||
};
|
||||
|
||||
handleChange = ({ fileList }) => this.setState({ fileList });
|
||||
|
||||
render() {
|
||||
const { previewVisible, previewImage, fileList } = this.state;
|
||||
const uploadButton = (
|
||||
<div>
|
||||
<Icon type="plus" />
|
||||
<div className="ant-upload-text">Upload</div>
|
||||
</div>
|
||||
);
|
||||
return (
|
||||
<div className="clearfix">
|
||||
<Upload
|
||||
action="//jsonplaceholder.typicode.com/posts/"
|
||||
listType="picture-card"
|
||||
fileList={fileList}
|
||||
onPreview={this.handlePreview}
|
||||
onChange={this.handleChange}
|
||||
>
|
||||
{fileList.length >= 3 ? null : uploadButton}
|
||||
</Upload>
|
||||
<Modal visible={previewVisible} footer={null} onCancel={this.handleCancel}>
|
||||
<img alt="example" style={{ width: '100%' }} src={previewImage} />
|
||||
</Modal>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
export default AddTagModal;
|
||||
@ -16,30 +16,31 @@
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
import AppList from "../../../components/apps/AppList";
|
||||
import React from 'react';
|
||||
import AppList from '../../../components/apps/AppList';
|
||||
|
||||
class Apps extends React.Component {
|
||||
routes;
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.routes = props.routes;
|
||||
routes;
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.routes = props.routes;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
render() {
|
||||
const {deviceType} = this.props.match.params;
|
||||
return (
|
||||
<div>
|
||||
<div style={{background: '#f0f2f5', padding: 24, minHeight: 760}}>
|
||||
{deviceType!==null && <AppList changeSelectedMenuItem={this.props.changeSelectedMenuItem} deviceType={deviceType}/>}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
);
|
||||
}
|
||||
render() {
|
||||
const { deviceType } = this.props.match.params;
|
||||
return (
|
||||
<div>
|
||||
<div style={{ background: '#f0f2f5', padding: 24, minHeight: 760 }}>
|
||||
{deviceType !== null && (
|
||||
<AppList
|
||||
changeSelectedMenuItem={this.props.changeSelectedMenuItem}
|
||||
deviceType={deviceType}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default Apps;
|
||||
|
||||
@ -16,118 +16,131 @@
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
import React from 'react';
|
||||
import '../../../../App.css';
|
||||
import {Skeleton, Typography, Row, Col, Card, message, notification, Breadcrumb, Icon} from "antd";
|
||||
import ReleaseView from "../../../../components/apps/release/ReleaseView";
|
||||
import axios from "axios";
|
||||
import {withConfigContext} from "../../../../context/ConfigContext";
|
||||
import {Link} from "react-router-dom";
|
||||
import {handleApiError} from "../../../../js/Utils";
|
||||
import { Skeleton, Typography, Row, Col, Card, Breadcrumb, Icon } from 'antd';
|
||||
import ReleaseView from '../../../../components/apps/release/ReleaseView';
|
||||
import axios from 'axios';
|
||||
import { withConfigContext } from '../../../../context/ConfigContext';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { handleApiError } from '../../../../js/Utils';
|
||||
|
||||
const {Title} = Typography;
|
||||
const { Title } = Typography;
|
||||
|
||||
class Release extends React.Component {
|
||||
routes;
|
||||
routes;
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.routes = props.routes;
|
||||
this.state = {
|
||||
loading: true,
|
||||
app: null,
|
||||
uuid: null,
|
||||
forbiddenErrors: {
|
||||
app: false
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
const {uuid, deviceType} = this.props.match.params;
|
||||
this.fetchData(uuid);
|
||||
this.props.changeSelectedMenuItem(deviceType);
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps, prevState, snapshot) {
|
||||
if (prevState.uuid !== this.state.uuid) {
|
||||
const {uuid, deviceType} = this.props.match.params;
|
||||
this.fetchData(uuid);
|
||||
this.props.changeSelectedMenuItem(deviceType);
|
||||
}
|
||||
}
|
||||
|
||||
fetchData = (uuid) => {
|
||||
const config = this.props.context;
|
||||
|
||||
//send request to the invoker
|
||||
axios.get(
|
||||
window.location.origin + config.serverConfig.invoker.uri + config.serverConfig.invoker.store + "/applications/" + uuid,
|
||||
).then(res => {
|
||||
if (res.status === 200) {
|
||||
let app = res.data.data;
|
||||
|
||||
this.setState({
|
||||
app: app,
|
||||
loading: false,
|
||||
uuid: uuid
|
||||
})
|
||||
}
|
||||
|
||||
}).catch((error) => {
|
||||
handleApiError(error,"Error occurred while trying to load releases.", false);
|
||||
if (error.hasOwnProperty("response") && error.response.status === 403) {
|
||||
const {forbiddenErrors} = this.state;
|
||||
forbiddenErrors.app = true;
|
||||
this.setState({
|
||||
forbiddenErrors,
|
||||
loading: false
|
||||
})
|
||||
} else {
|
||||
this.setState({
|
||||
loading: false
|
||||
});
|
||||
}
|
||||
});
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.routes = props.routes;
|
||||
this.state = {
|
||||
loading: true,
|
||||
app: null,
|
||||
uuid: null,
|
||||
forbiddenErrors: {
|
||||
app: false,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
render() {
|
||||
const {app, loading} = this.state;
|
||||
const {deviceType} = this.props.match.params;
|
||||
componentDidMount() {
|
||||
const { uuid, deviceType } = this.props.match.params;
|
||||
this.fetchData(uuid);
|
||||
this.props.changeSelectedMenuItem(deviceType);
|
||||
}
|
||||
|
||||
let content = <Title level={3}>No Releases Found</Title>;
|
||||
let appName = "loading...";
|
||||
|
||||
if (app != null && app.applicationReleases.length !== 0) {
|
||||
content = <ReleaseView app={app} deviceType={deviceType}/>;
|
||||
appName = app.name;
|
||||
}
|
||||
|
||||
return (
|
||||
<div style={{background: '#f0f2f5', minHeight: 780}}>
|
||||
<Row style={{padding: 10}}>
|
||||
<Col lg={4}>
|
||||
|
||||
</Col>
|
||||
<Col lg={16} md={24} style={{padding: 3}}>
|
||||
<Breadcrumb style={{paddingBottom: 16}}>
|
||||
<Breadcrumb.Item>
|
||||
<Link to={"/store/"+deviceType}><Icon type="home"/> {deviceType + " apps"} </Link>
|
||||
</Breadcrumb.Item>
|
||||
<Breadcrumb.Item>{appName}</Breadcrumb.Item>
|
||||
</Breadcrumb>
|
||||
<Card>
|
||||
<Skeleton loading={loading} avatar={{size: 'large'}} active paragraph={{rows: 8}}>
|
||||
{content}
|
||||
</Skeleton>
|
||||
</Card>
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
</div>
|
||||
);
|
||||
componentDidUpdate(prevProps, prevState, snapshot) {
|
||||
if (prevState.uuid !== this.state.uuid) {
|
||||
const { uuid, deviceType } = this.props.match.params;
|
||||
this.fetchData(uuid);
|
||||
this.props.changeSelectedMenuItem(deviceType);
|
||||
}
|
||||
}
|
||||
|
||||
fetchData = uuid => {
|
||||
const config = this.props.context;
|
||||
|
||||
// send request to the invoker
|
||||
axios
|
||||
.get(
|
||||
window.location.origin +
|
||||
config.serverConfig.invoker.uri +
|
||||
config.serverConfig.invoker.store +
|
||||
'/applications/' +
|
||||
uuid,
|
||||
)
|
||||
.then(res => {
|
||||
if (res.status === 200) {
|
||||
let app = res.data.data;
|
||||
|
||||
this.setState({
|
||||
app: app,
|
||||
loading: false,
|
||||
uuid: uuid,
|
||||
});
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
handleApiError(
|
||||
error,
|
||||
'Error occurred while trying to load releases.',
|
||||
false,
|
||||
);
|
||||
if (error.hasOwnProperty('response') && error.response.status === 403) {
|
||||
const { forbiddenErrors } = this.state;
|
||||
forbiddenErrors.app = true;
|
||||
this.setState({
|
||||
forbiddenErrors,
|
||||
loading: false,
|
||||
});
|
||||
} else {
|
||||
this.setState({
|
||||
loading: false,
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
render() {
|
||||
const { app, loading } = this.state;
|
||||
const { deviceType } = this.props.match.params;
|
||||
|
||||
let content = <Title level={3}>No Releases Found</Title>;
|
||||
let appName = 'loading...';
|
||||
|
||||
if (app != null && app.applicationReleases.length !== 0) {
|
||||
content = <ReleaseView app={app} deviceType={deviceType} />;
|
||||
appName = app.name;
|
||||
}
|
||||
|
||||
return (
|
||||
<div style={{ background: '#f0f2f5', minHeight: 780 }}>
|
||||
<Row style={{ padding: 10 }}>
|
||||
<Col lg={4}></Col>
|
||||
<Col lg={16} md={24} style={{ padding: 3 }}>
|
||||
<Breadcrumb style={{ paddingBottom: 16 }}>
|
||||
<Breadcrumb.Item>
|
||||
<Link to={'/store/' + deviceType}>
|
||||
<Icon type="home" /> {deviceType + ' apps'}{' '}
|
||||
</Link>
|
||||
</Breadcrumb.Item>
|
||||
<Breadcrumb.Item>{appName}</Breadcrumb.Item>
|
||||
</Breadcrumb>
|
||||
<Card>
|
||||
<Skeleton
|
||||
loading={loading}
|
||||
avatar={{ size: 'large' }}
|
||||
active
|
||||
paragraph={{ rows: 8 }}
|
||||
>
|
||||
{content}
|
||||
</Skeleton>
|
||||
</Card>
|
||||
</Col>
|
||||
</Row>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export default withConfigContext(Release);
|
||||
|
||||
@ -16,54 +16,60 @@
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
import {notification, Menu, Icon} from 'antd';
|
||||
import React from 'react';
|
||||
import { Menu, Icon } from 'antd';
|
||||
import axios from 'axios';
|
||||
import {withConfigContext} from "../../../context/ConfigContext";
|
||||
import {handleApiError} from "../../../js/Utils";
|
||||
import { withConfigContext } from '../../../context/ConfigContext';
|
||||
import { handleApiError } from '../../../js/Utils';
|
||||
|
||||
/*
|
||||
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
|
||||
};
|
||||
}
|
||||
/*
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
inValid: false,
|
||||
loading: false,
|
||||
};
|
||||
}
|
||||
/*
|
||||
This function call the logout api when the request is success
|
||||
*/
|
||||
handleSubmit = () => {
|
||||
handleSubmit = () => {
|
||||
const thisForm = this;
|
||||
const config = this.props.context;
|
||||
|
||||
const thisForm = this;
|
||||
const config = this.props.context;
|
||||
thisForm.setState({
|
||||
inValid: false,
|
||||
});
|
||||
|
||||
thisForm.setState({
|
||||
inValid: false
|
||||
});
|
||||
|
||||
axios.post(window.location.origin + config.serverConfig.logoutUri
|
||||
).then(res => {
|
||||
//if the api call status is correct then user will logout and then it goes to login page
|
||||
if (res.status === 200) {
|
||||
window.location = window.location.origin + "/store/login";
|
||||
}
|
||||
}).catch(function (error) {
|
||||
handleApiError(error,"Error occurred while trying to get your review.");
|
||||
});
|
||||
};
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Menu>
|
||||
<Menu.Item key="1" onClick={this.handleSubmit}><Icon type="logout"/>Logout</Menu.Item>
|
||||
</Menu>
|
||||
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 + '/store/login';
|
||||
}
|
||||
})
|
||||
.catch(function(error) {
|
||||
handleApiError(
|
||||
error,
|
||||
'Error occurred while trying to get your review.',
|
||||
);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Menu>
|
||||
<Menu.Item key="1" onClick={this.handleSubmit}>
|
||||
<Icon type="logout" />
|
||||
Logout
|
||||
</Menu.Item>
|
||||
</Menu>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default withConfigContext(Logout);
|
||||
|
||||
@ -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.',
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
@ -16,119 +16,119 @@
|
||||
* under the License.
|
||||
*/
|
||||
var path = require('path');
|
||||
const HtmlWebPackPlugin = require("html-webpack-plugin");
|
||||
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
|
||||
const configurations = require("./public/conf/config.json");
|
||||
const HtmlWebPackPlugin = require('html-webpack-plugin');
|
||||
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
|
||||
const configurations = require('./public/conf/config.json');
|
||||
|
||||
const config = {
|
||||
devtool: "source-map",
|
||||
output: {
|
||||
publicPath: '/store/'
|
||||
devtool: 'source-map',
|
||||
output: {
|
||||
publicPath: '/store/',
|
||||
},
|
||||
watch: false,
|
||||
resolve: {
|
||||
alias: {
|
||||
AppData: path.resolve(__dirname, 'source/src/app/common/'),
|
||||
AppComponents: path.resolve(__dirname, 'source/src/app/components/'),
|
||||
},
|
||||
watch: false,
|
||||
resolve: {
|
||||
alias: {
|
||||
AppData: path.resolve(__dirname, 'source/src/app/common/'),
|
||||
AppComponents: path.resolve(__dirname, 'source/src/app/components/')
|
||||
},
|
||||
extensions: ['.jsx', '.js', '.ttf', '.woff', '.woff2', '.svg']
|
||||
},
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.(js|jsx)$/,
|
||||
exclude: /node_modules/,
|
||||
use: [
|
||||
{
|
||||
loader: 'babel-loader'
|
||||
}
|
||||
]
|
||||
extensions: ['.jsx', '.js', '.ttf', '.woff', '.woff2', '.svg'],
|
||||
},
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.(js|jsx)$/,
|
||||
exclude: /node_modules/,
|
||||
use: [
|
||||
{
|
||||
loader: 'babel-loader',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
test: /\.html$/,
|
||||
use: [
|
||||
{
|
||||
loader: 'html-loader',
|
||||
options: { minimize: true },
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
test: /\.css$/,
|
||||
use: [MiniCssExtractPlugin.loader, 'css-loader'],
|
||||
},
|
||||
{
|
||||
test: /\.scss$/,
|
||||
use: [
|
||||
MiniCssExtractPlugin.loader,
|
||||
'css-loader',
|
||||
'postcss-loader',
|
||||
'sass-loader',
|
||||
],
|
||||
},
|
||||
{
|
||||
test: /\.scss$/,
|
||||
use: ['style-loader', 'scss-loader'],
|
||||
},
|
||||
{
|
||||
test: /\.less$/,
|
||||
use: [
|
||||
{
|
||||
loader: 'style-loader',
|
||||
},
|
||||
{
|
||||
loader: 'css-loader',
|
||||
},
|
||||
{
|
||||
loader: 'less-loader',
|
||||
options: {
|
||||
modifyVars: {
|
||||
'primary-color': configurations.theme.primaryColor,
|
||||
'link-color': configurations.theme.primaryColor,
|
||||
},
|
||||
javascriptEnabled: true,
|
||||
},
|
||||
{
|
||||
test: /\.html$/,
|
||||
use: [
|
||||
{
|
||||
loader: "html-loader",
|
||||
options: { minimize: true }
|
||||
}
|
||||
]
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
test: /\.(woff|woff2|eot|ttf|svg)$/,
|
||||
loader: 'url-loader?limit=100000',
|
||||
},
|
||||
{
|
||||
test: /\.(png|jpe?g)/i,
|
||||
use: [
|
||||
{
|
||||
loader: 'url-loader',
|
||||
options: {
|
||||
name: './img/[name].[ext]',
|
||||
limit: 10000,
|
||||
},
|
||||
{
|
||||
test: /\.css$/,
|
||||
use: [MiniCssExtractPlugin.loader, "css-loader"]
|
||||
},
|
||||
{
|
||||
test: /\.scss$/,
|
||||
use: [
|
||||
MiniCssExtractPlugin.loader,
|
||||
"css-loader",
|
||||
"postcss-loader",
|
||||
"sass-loader"
|
||||
]
|
||||
},
|
||||
{
|
||||
test: /\.scss$/,
|
||||
use: [ 'style-loader', 'scss-loader' ]
|
||||
},
|
||||
{
|
||||
test: /\.less$/,
|
||||
use: [
|
||||
{
|
||||
loader: "style-loader"
|
||||
},
|
||||
{
|
||||
loader: "css-loader",
|
||||
},
|
||||
{
|
||||
loader: "less-loader",
|
||||
options: {
|
||||
modifyVars: {
|
||||
'primary-color': configurations.theme.primaryColor,
|
||||
'link-color': configurations.theme.primaryColor,
|
||||
},
|
||||
javascriptEnabled: true,
|
||||
},
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
test: /\.(woff|woff2|eot|ttf|svg)$/,
|
||||
loader: 'url-loader?limit=100000',
|
||||
},
|
||||
{
|
||||
test: /\.(png|jpe?g)/i,
|
||||
use: [
|
||||
{
|
||||
loader: "url-loader",
|
||||
options: {
|
||||
name: "./img/[name].[ext]",
|
||||
limit: 10000
|
||||
}
|
||||
},
|
||||
{
|
||||
loader: "img-loader"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
plugins: [
|
||||
new HtmlWebPackPlugin({
|
||||
template: "./src/index.html",
|
||||
filename: "./index.html"
|
||||
}),
|
||||
new MiniCssExtractPlugin({
|
||||
filename: "[name].css",
|
||||
chunkFilename: "[id].css"
|
||||
})
|
||||
},
|
||||
{
|
||||
loader: 'img-loader',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
externals: {
|
||||
'Config': JSON.stringify(require('./public/conf/config.json'))
|
||||
}
|
||||
},
|
||||
plugins: [
|
||||
new HtmlWebPackPlugin({
|
||||
template: './src/index.html',
|
||||
filename: './index.html',
|
||||
}),
|
||||
new MiniCssExtractPlugin({
|
||||
filename: '[name].css',
|
||||
chunkFilename: '[id].css',
|
||||
}),
|
||||
],
|
||||
externals: {
|
||||
Config: JSON.stringify(require('./public/conf/config.json')),
|
||||
},
|
||||
};
|
||||
|
||||
if (process.env.NODE_ENV === "development") {
|
||||
config.watch = true;
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
config.watch = true;
|
||||
}
|
||||
|
||||
module.exports = config;
|
||||
|
||||
Loading…
Reference in New Issue
Block a user