mirror of
https://repository.entgra.net/community/device-mgt-core.git
synced 2025-10-06 02:01:45 +00:00
Fix ESLint errors in APPM Store react app
This commit is contained in:
parent
fae659c32c
commit
d4d7089800
@ -16,14 +16,13 @@
|
|||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
module.exports = function (api) {
|
module.exports = function(api) {
|
||||||
api.cache(true);
|
api.cache(true);
|
||||||
const presets = [ "@babel/preset-env",
|
const presets = ['@babel/preset-env', '@babel/preset-react'];
|
||||||
"@babel/preset-react" ];
|
const plugins = ['@babel/plugin-proposal-class-properties'];
|
||||||
const plugins = ["@babel/plugin-proposal-class-properties"];
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
presets,
|
presets,
|
||||||
plugins
|
plugins,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@ -16,140 +16,147 @@
|
|||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from "react";
|
import React from 'react';
|
||||||
import "antd/dist/antd.less";
|
import 'antd/dist/antd.less';
|
||||||
import RouteWithSubRoutes from "./components/RouteWithSubRoutes";
|
import RouteWithSubRoutes from './components/RouteWithSubRoutes';
|
||||||
import {
|
import { BrowserRouter as Router, Redirect, Switch } from 'react-router-dom';
|
||||||
BrowserRouter as Router,
|
import axios from 'axios';
|
||||||
Redirect, Switch,
|
import { Layout, Spin, Result } from 'antd';
|
||||||
} from 'react-router-dom';
|
import ConfigContext from './context/ConfigContext';
|
||||||
import axios from "axios";
|
|
||||||
import {Layout, Spin, Result} from "antd";
|
|
||||||
import ConfigContext from "./context/ConfigContext";
|
|
||||||
|
|
||||||
const {Content} = Layout;
|
const { Content } = Layout;
|
||||||
const loadingView = (
|
const loadingView = (
|
||||||
<Layout>
|
<Layout>
|
||||||
<Content style={{
|
<Content
|
||||||
padding: '0 0',
|
style={{
|
||||||
paddingTop: 300,
|
padding: '0 0',
|
||||||
backgroundColor: '#fff',
|
paddingTop: 300,
|
||||||
textAlign: 'center'
|
backgroundColor: '#fff',
|
||||||
}}>
|
textAlign: 'center',
|
||||||
<Spin tip="Loading..."/>
|
}}
|
||||||
</Content>
|
>
|
||||||
</Layout>
|
<Spin tip="Loading..." />
|
||||||
|
</Content>
|
||||||
|
</Layout>
|
||||||
);
|
);
|
||||||
|
|
||||||
const errorView = (
|
const errorView = (
|
||||||
<Result
|
<Result
|
||||||
style={{
|
style={{
|
||||||
paddingTop: 200
|
paddingTop: 200,
|
||||||
}}
|
}}
|
||||||
status="500"
|
status="500"
|
||||||
title="Error occurred while loading the configuration"
|
title="Error occurred while loading the configuration"
|
||||||
subTitle="Please refresh your browser window"
|
subTitle="Please refresh your browser window"
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
class App extends React.Component {
|
class App extends React.Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
loading: true,
|
||||||
|
error: false,
|
||||||
|
config: {},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
constructor(props) {
|
componentDidMount() {
|
||||||
super(props);
|
this.updateFavicon();
|
||||||
this.state = {
|
axios
|
||||||
loading: true,
|
.get(window.location.origin + '/store/public/conf/config.json')
|
||||||
error: false,
|
.then(res => {
|
||||||
config: {}
|
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,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
|
.catch(error => {
|
||||||
componentDidMount() {
|
if (error.hasOwnProperty('response') && error.response.status === 401) {
|
||||||
this.updateFavicon();
|
const redirectUrl = encodeURI(window.location.href);
|
||||||
axios.get(
|
const pageURL = window.location.pathname;
|
||||||
window.location.origin + "/store/public/conf/config.json",
|
const lastURLSegment = pageURL.substr(pageURL.lastIndexOf('/') + 1);
|
||||||
).then(res => {
|
if (lastURLSegment !== 'login') {
|
||||||
const config = res.data;
|
window.location.href =
|
||||||
this.checkUserLoggedIn(config);
|
window.location.origin + `/store/login?redirect=${redirectUrl}`;
|
||||||
}).catch((error) => {
|
} else {
|
||||||
this.setState({
|
this.setState({
|
||||||
loading: false,
|
loading: false,
|
||||||
error: true
|
config: config,
|
||||||
})
|
});
|
||||||
});
|
}
|
||||||
}
|
} else {
|
||||||
|
this.setState({
|
||||||
|
loading: false,
|
||||||
|
error: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
updateFavicon = () =>{
|
render() {
|
||||||
const link = document.querySelector("link[rel*='icon']") || document.createElement('link');
|
const { loading, error } = this.state;
|
||||||
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) => {
|
const applicationView = (
|
||||||
axios.post(
|
<Router>
|
||||||
window.location.origin + "/store-ui-request-handler/user",
|
<ConfigContext.Provider value={this.state.config}>
|
||||||
"platform=publisher"
|
<div>
|
||||||
).then(res => {
|
<Switch>
|
||||||
config.user = res.data.data;
|
<Redirect exact from="/store" to="/store/android" />
|
||||||
const pageURL = window.location.pathname;
|
{this.props.routes.map(route => (
|
||||||
const lastURLSegment = pageURL.substr(pageURL.lastIndexOf('/') + 1);
|
<RouteWithSubRoutes key={route.path} {...route} />
|
||||||
if (lastURLSegment === "login") {
|
))}
|
||||||
window.location.href = window.location.origin + `/store/`;
|
</Switch>
|
||||||
} else {
|
</div>
|
||||||
this.setState({
|
</ConfigContext.Provider>
|
||||||
loading: false,
|
</Router>
|
||||||
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
|
|
||||||
})
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
render() {
|
return (
|
||||||
const {loading, error} = this.state;
|
<div>
|
||||||
|
{loading && loadingView}
|
||||||
const applicationView = (
|
{!loading && !error && applicationView}
|
||||||
<Router>
|
{error && errorView}
|
||||||
<ConfigContext.Provider value={this.state.config}>
|
</div>
|
||||||
<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>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default App;
|
export default App;
|
||||||
|
|||||||
@ -17,21 +17,28 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import {Route} from 'react-router-dom';
|
import { Route } from 'react-router-dom';
|
||||||
class RouteWithSubRoutes extends React.Component{
|
class RouteWithSubRoutes extends React.Component {
|
||||||
props;
|
props;
|
||||||
constructor(props){
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
this.props = props;
|
this.props = props;
|
||||||
}
|
}
|
||||||
render() {
|
render() {
|
||||||
return(
|
return (
|
||||||
<Route path={this.props.path} exact={this.props.exact} render={(props) => (
|
<Route
|
||||||
<this.props.component {...props} {...this.props} routes={this.props.routes}/>
|
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;
|
overflow: hidden;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -16,63 +16,66 @@
|
|||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {Card, Typography, Col, Row} from 'antd';
|
import { Card, Typography, Col, Row } from 'antd';
|
||||||
import React from "react";
|
import React from 'react';
|
||||||
import {Link} from "react-router-dom";
|
import { Link } from 'react-router-dom';
|
||||||
import "./AppCard.css";
|
import './AppCard.css';
|
||||||
import StarRatings from 'react-star-ratings';
|
import StarRatings from 'react-star-ratings';
|
||||||
|
|
||||||
const {Meta} = Card;
|
const { Meta } = Card;
|
||||||
const {Text} = Typography;
|
const { Text } = Typography;
|
||||||
|
|
||||||
class AppCard extends React.Component {
|
class AppCard extends React.Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
}
|
||||||
|
|
||||||
constructor(props) {
|
render() {
|
||||||
super(props);
|
const app = this.props.app;
|
||||||
}
|
const release = this.props.app.applicationReleases[0];
|
||||||
|
|
||||||
render() {
|
const description = (
|
||||||
const app = this.props.app;
|
<div className="appCard">
|
||||||
const release = this.props.app.applicationReleases[0];
|
<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 = (
|
return (
|
||||||
<div className="appCard">
|
<Card style={{ marginTop: 16 }}>
|
||||||
<Link to={"/store/"+app.deviceType+"/apps/" + release.uuid}>
|
<Meta description={description} />
|
||||||
<Row className="release">
|
</Card>
|
||||||
<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>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default AppCard;
|
export default AppCard;
|
||||||
|
|||||||
@ -16,163 +16,172 @@
|
|||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from "react";
|
import React from 'react';
|
||||||
import AppCard from "./AppCard";
|
import AppCard from './AppCard';
|
||||||
import {Col, Row, Result} from "antd";
|
import { Col, Row, Result } from 'antd';
|
||||||
import axios from "axios";
|
import axios from 'axios';
|
||||||
import {withConfigContext} from "../../context/ConfigContext";
|
import { withConfigContext } from '../../context/ConfigContext';
|
||||||
import {handleApiError} from "../../js/Utils";
|
import { handleApiError } from '../../js/Utils';
|
||||||
import InfiniteScroll from "react-infinite-scroller";
|
import InfiniteScroll from 'react-infinite-scroller';
|
||||||
|
|
||||||
const limit = 30;
|
const limit = 30;
|
||||||
|
|
||||||
class AppList extends React.Component {
|
class AppList extends React.Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
this.state = {
|
this.state = {
|
||||||
apps: [],
|
apps: [],
|
||||||
loading: true,
|
loading: true,
|
||||||
hasMore: true,
|
hasMore: true,
|
||||||
loadMore: true,
|
loadMore: true,
|
||||||
forbiddenErrors: {
|
forbiddenErrors: {
|
||||||
apps: false
|
apps: false,
|
||||||
},
|
},
|
||||||
totalAppCount: 0
|
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
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
}
|
||||||
|
|
||||||
handleInfiniteOnLoad = (count) => {
|
componentDidMount() {
|
||||||
const offset = count * limit;
|
const { deviceType } = this.props;
|
||||||
let apps = this.state.apps;
|
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({
|
this.setState({
|
||||||
loading: true,
|
apps: res,
|
||||||
|
loading: false,
|
||||||
|
hasMore: true,
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
this.fetchData(offset, limit, res => {
|
fetchData = (offset, limit, callbackFunction) => {
|
||||||
if (res.length > 0) {
|
const { deviceType } = this.props;
|
||||||
apps = apps.concat(res);
|
const config = this.props.context;
|
||||||
this.setState({
|
const payload = {
|
||||||
apps,
|
offset,
|
||||||
loading: false,
|
limit,
|
||||||
});
|
|
||||||
} else {
|
|
||||||
this.setState({
|
|
||||||
hasMore: false,
|
|
||||||
loading: false
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
if (deviceType === 'web-clip') {
|
||||||
|
payload.appType = 'WEB_CLIP';
|
||||||
|
} else {
|
||||||
|
payload.deviceType = deviceType;
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
this.setState({
|
||||||
const {apps, loading, forbiddenErrors, hasMore} = this.state;
|
loading: true,
|
||||||
|
});
|
||||||
return (
|
// send request to the invoker
|
||||||
<div>
|
axios
|
||||||
<InfiniteScroll
|
.post(
|
||||||
key={this.props.deviceType}
|
window.location.origin +
|
||||||
initialLoad={false}
|
config.serverConfig.invoker.uri +
|
||||||
pageStart={0}
|
config.serverConfig.invoker.store +
|
||||||
loadMore={this.handleInfiniteOnLoad}
|
'/applications/',
|
||||||
hasMore={!loading && hasMore}
|
payload,
|
||||||
useWindow={true}>
|
)
|
||||||
<Row gutter={16}>
|
.then(res => {
|
||||||
{(forbiddenErrors.apps) && (
|
if (res.status === 200) {
|
||||||
<Result
|
// todo remove this property check after backend improvement
|
||||||
status="403"
|
let apps = res.data.data.hasOwnProperty('applications')
|
||||||
title="403"
|
? res.data.data.applications
|
||||||
subTitle="You don't have permission to view apps."
|
: [];
|
||||||
/>
|
callbackFunction(apps);
|
||||||
)}
|
}
|
||||||
{!(forbiddenErrors.apps) && apps.length === 0 && (
|
})
|
||||||
<Result
|
.catch(error => {
|
||||||
status="404"
|
handleApiError(
|
||||||
title="No apps, yet."
|
error,
|
||||||
subTitle="No apps available, yet! When the administration uploads, apps will show up here."
|
'Error occurred while trying to load apps.',
|
||||||
/>
|
true,
|
||||||
)}
|
|
||||||
{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>
|
|
||||||
);
|
);
|
||||||
}
|
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{
|
.d-rating .numeric-data .people-count{
|
||||||
padding-top: 6px;
|
padding-top: 6px;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -16,87 +16,110 @@
|
|||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from "react";
|
import React from 'react';
|
||||||
import {Row, Typography, Icon, notification} from "antd";
|
import { Row, Typography, Icon } from 'antd';
|
||||||
import StarRatings from "react-star-ratings";
|
import StarRatings from 'react-star-ratings';
|
||||||
import "./DetailedRating.css";
|
import './DetailedRating.css';
|
||||||
import axios from "axios";
|
import { withConfigContext } from '../../../context/ConfigContext';
|
||||||
import {withConfigContext} from "../../../context/ConfigContext";
|
|
||||||
import {handleApiError} from "../../../js/Utils";
|
|
||||||
|
|
||||||
const { Text } = Typography;
|
const { Text } = Typography;
|
||||||
|
|
||||||
|
class DetailedRating extends React.Component {
|
||||||
|
render() {
|
||||||
|
const { detailedRating } = this.props;
|
||||||
|
|
||||||
class DetailedRating extends React.Component{
|
if (detailedRating == null) {
|
||||||
|
return null;
|
||||||
|
|
||||||
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>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from "react";
|
import React from 'react';
|
||||||
import {Divider, Row, Col, Typography, Button, Dropdown, notification, Menu, Icon, Spin, Tabs, Tag} from "antd";
|
import {
|
||||||
import "../../../App.css";
|
Divider,
|
||||||
import ImgViewer from "../../apps/release/images/ImgViewer";
|
Row,
|
||||||
import StarRatings from "react-star-ratings";
|
Col,
|
||||||
import axios from "axios";
|
Typography,
|
||||||
import pSBC from "shade-blend-color";
|
Button,
|
||||||
import AppInstallModal from "./install/AppInstallModal";
|
Dropdown,
|
||||||
import AppUninstallModal from "./install/AppUninstallModal";
|
notification,
|
||||||
import {withConfigContext} from "../../../context/ConfigContext";
|
Menu,
|
||||||
import {handleApiError} from "../../../js/Utils";
|
Icon,
|
||||||
import ReviewContainer from "./review/ReviewContainer";
|
Tabs,
|
||||||
import SubscriptionDetails from "./SubscriptionDetails";
|
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 { Title, Text, Paragraph } = Typography;
|
||||||
const {TabPane} = Tabs;
|
const { TabPane } = Tabs;
|
||||||
|
|
||||||
class ReleaseView extends React.Component {
|
class ReleaseView extends React.Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
this.state = {
|
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,
|
loading: false,
|
||||||
appInstallModalVisible: 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',
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
|
.catch(error => {
|
||||||
appOperation = (type, payload, operation, timestamp = null) => {
|
handleApiError(
|
||||||
const config = this.props.context;
|
error,
|
||||||
const release = this.props.app.applicationReleases[0];
|
'Error occurred while ' + operation + 'ing the app.',
|
||||||
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>
|
|
||||||
);
|
);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
closeAppOperationModal = () => {
|
||||||
<div>
|
this.setState({
|
||||||
<AppInstallModal
|
appInstallModalVisible: false,
|
||||||
uuid={release.uuid}
|
appUninstallModalVisible: false,
|
||||||
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>
|
|
||||||
|
|
||||||
<Row>
|
handleSubscribeClick = e => {
|
||||||
{
|
if (e.key === 'install') {
|
||||||
metaData.map((data, index) => {
|
this.setState({
|
||||||
return (
|
appInstallModalVisible: true, // display app install modal
|
||||||
<Col key={index} lg={8} md={6} xs={24} style={{marginTop: 15}}>
|
});
|
||||||
<Text>{data.key}</Text><br/>
|
} else if (e.key === 'uninstall') {
|
||||||
<Text type="secondary">{data.value}</Text>
|
this.setState({
|
||||||
</Col>
|
appUninstallModalVisible: true, // display app uninstall modal
|
||||||
)
|
});
|
||||||
})
|
|
||||||
}
|
|
||||||
{(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>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
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);
|
export default withConfigContext(ReleaseView);
|
||||||
|
|||||||
@ -16,260 +16,257 @@
|
|||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from "react";
|
import React from 'react';
|
||||||
import axios from "axios";
|
import axios from 'axios';
|
||||||
import {
|
import { Tag, Table, Typography, Button, Alert } from 'antd';
|
||||||
Tag,
|
import TimeAgo from 'javascript-time-ago';
|
||||||
message,
|
|
||||||
notification,
|
|
||||||
Table,
|
|
||||||
Typography,
|
|
||||||
Tooltip,
|
|
||||||
Icon,
|
|
||||||
Divider,
|
|
||||||
Button,
|
|
||||||
Modal,
|
|
||||||
Select,
|
|
||||||
Alert
|
|
||||||
} from "antd";
|
|
||||||
import TimeAgo from 'javascript-time-ago'
|
|
||||||
|
|
||||||
// Load locale-specific relative date/time formatting rules.
|
// Load locale-specific relative date/time formatting rules.
|
||||||
import en from 'javascript-time-ago/locale/en'
|
import en from 'javascript-time-ago/locale/en';
|
||||||
import {withConfigContext} from "../../../context/ConfigContext";
|
import { withConfigContext } from '../../../context/ConfigContext';
|
||||||
import {handleApiError} from "../../../js/Utils";
|
import { handleApiError } from '../../../js/Utils';
|
||||||
|
|
||||||
const {Text} = Typography;
|
const { Text } = Typography;
|
||||||
|
|
||||||
let config = null;
|
|
||||||
|
|
||||||
const columns = [
|
const columns = [
|
||||||
{
|
{
|
||||||
title: 'Device',
|
title: 'Device',
|
||||||
dataIndex: 'device',
|
dataIndex: 'device',
|
||||||
width: 100,
|
width: 100,
|
||||||
render: device => device.name
|
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',
|
title: 'Triggered By',
|
||||||
key: 'owner',
|
dataIndex: 'actionTriggeredBy',
|
||||||
render: device => device.enrolmentInfo.owner
|
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',
|
title: 'Device Status',
|
||||||
key: 'actionType',
|
dataIndex: 'device',
|
||||||
render: actionType => actionType.toLowerCase()
|
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 {
|
class SubscriptionDetails extends React.Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
config = this.props.context;
|
TimeAgo.addLocale(en);
|
||||||
TimeAgo.addLocale(en);
|
this.state = {
|
||||||
this.state = {
|
data: [],
|
||||||
data: [],
|
pagination: {},
|
||||||
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,
|
loading: false,
|
||||||
selectedRows: [],
|
data: res.data.data,
|
||||||
deviceGroups: [],
|
});
|
||||||
groupModalVisible: false,
|
}
|
||||||
selectedGroupId: [],
|
})
|
||||||
isForbidden: false
|
.catch(error => {
|
||||||
};
|
handleApiError(
|
||||||
}
|
error,
|
||||||
|
'Something went wrong when trying to load subscription data.',
|
||||||
componentDidMount() {
|
true,
|
||||||
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>
|
|
||||||
);
|
);
|
||||||
}
|
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.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React, {Component} from 'react';
|
import React, { Component } from 'react';
|
||||||
import RcViewer from 'rc-viewer';
|
import RcViewer from 'rc-viewer';
|
||||||
import {Col} from "antd";
|
|
||||||
|
|
||||||
class ImgViewer extends Component {
|
class ImgViewer extends Component {
|
||||||
render() {
|
render() {
|
||||||
const options = {
|
const options = {
|
||||||
title: false,
|
title: false,
|
||||||
toolbar: {
|
toolbar: {
|
||||||
zoomIn: 0,
|
zoomIn: 0,
|
||||||
zoomOut: 0,
|
zoomOut: 0,
|
||||||
oneToOne: 0,
|
oneToOne: 0,
|
||||||
reset: 0,
|
reset: 0,
|
||||||
prev: 1,
|
prev: 1,
|
||||||
play: {
|
play: {
|
||||||
show: 0
|
show: 0,
|
||||||
},
|
},
|
||||||
next: 1,
|
next: 1,
|
||||||
rotateLeft: 0,
|
rotateLeft: 0,
|
||||||
rotateRight: 0,
|
rotateRight: 0,
|
||||||
flipHorizontal: 0,
|
flipHorizontal: 0,
|
||||||
flipVertical: 0
|
flipVertical: 0,
|
||||||
},
|
},
|
||||||
rotatable: false,
|
rotatable: false,
|
||||||
transition: false,
|
transition: false,
|
||||||
movable : false
|
movable: false,
|
||||||
};
|
};
|
||||||
return (
|
return (
|
||||||
<div className="release-images">
|
<div className="release-images">
|
||||||
<RcViewer options={options} ref='viewer'>
|
{/* eslint-disable-next-line react/no-string-refs */}
|
||||||
{this.props.images.map((screenshotUrl, index) => {
|
<RcViewer options={options} ref="viewer">
|
||||||
return (
|
{this.props.images.map((screenshotUrl, index) => {
|
||||||
<div key={index} className="release-screenshot">
|
return (
|
||||||
<img alt="screenshot" key={screenshotUrl} src={screenshotUrl}/>
|
<div key={index} className="release-screenshot">
|
||||||
</div>
|
<img alt="screenshot" key={screenshotUrl} src={screenshotUrl} />
|
||||||
)
|
</div>
|
||||||
})}
|
);
|
||||||
</RcViewer>
|
})}
|
||||||
</div>
|
</RcViewer>
|
||||||
);
|
</div>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default ImgViewer;
|
export default ImgViewer;
|
||||||
|
|||||||
@ -16,49 +16,53 @@
|
|||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from "react";
|
import React from 'react';
|
||||||
import {Modal, Spin, Tabs} from "antd";
|
import { Modal, Spin, Tabs } from 'antd';
|
||||||
import UserInstall from "./UserInstall";
|
import UserInstall from './UserInstall';
|
||||||
import GroupInstall from "./GroupInstall";
|
import GroupInstall from './GroupInstall';
|
||||||
import RoleInstall from "./RoleInstall";
|
import RoleInstall from './RoleInstall';
|
||||||
import DeviceInstall from "./DeviceInstall";
|
import DeviceInstall from './DeviceInstall';
|
||||||
|
|
||||||
const {TabPane} = Tabs;
|
const { TabPane } = Tabs;
|
||||||
|
|
||||||
class AppInstallModal extends React.Component {
|
class AppInstallModal extends React.Component {
|
||||||
state = {
|
state = {
|
||||||
data: []
|
data: [],
|
||||||
};
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const {deviceType} = this.props;
|
const { deviceType } = this.props;
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<Modal
|
<Modal
|
||||||
title="Install App"
|
title="Install App"
|
||||||
visible={this.props.visible}
|
visible={this.props.visible}
|
||||||
onCancel={this.props.onClose}
|
onCancel={this.props.onClose}
|
||||||
footer={null}>
|
footer={null}
|
||||||
<Spin spinning={this.props.loading}>
|
>
|
||||||
<Tabs defaultActiveKey="device">
|
<Spin spinning={this.props.loading}>
|
||||||
<TabPane tab="Device" key="device">
|
<Tabs defaultActiveKey="device">
|
||||||
<DeviceInstall deviceType={deviceType} onInstall={this.props.onInstall}/>
|
<TabPane tab="Device" key="device">
|
||||||
</TabPane>
|
<DeviceInstall
|
||||||
<TabPane tab="User" key="user">
|
deviceType={deviceType}
|
||||||
<UserInstall onInstall={this.props.onInstall}/>
|
onInstall={this.props.onInstall}
|
||||||
</TabPane>
|
/>
|
||||||
<TabPane tab="Role" key="role">
|
</TabPane>
|
||||||
<RoleInstall onInstall={this.props.onInstall}/>
|
<TabPane tab="User" key="user">
|
||||||
</TabPane>
|
<UserInstall onInstall={this.props.onInstall} />
|
||||||
<TabPane tab="Group" key="group">
|
</TabPane>
|
||||||
<GroupInstall onInstall={this.props.onInstall}/>
|
<TabPane tab="Role" key="role">
|
||||||
</TabPane>
|
<RoleInstall onInstall={this.props.onInstall} />
|
||||||
</Tabs>
|
</TabPane>
|
||||||
</Spin>
|
<TabPane tab="Group" key="group">
|
||||||
</Modal>
|
<GroupInstall onInstall={this.props.onInstall} />
|
||||||
</div>
|
</TabPane>
|
||||||
);
|
</Tabs>
|
||||||
}
|
</Spin>
|
||||||
|
</Modal>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default AppInstallModal;
|
export default AppInstallModal;
|
||||||
|
|||||||
@ -15,50 +15,63 @@
|
|||||||
* specific language governing permissions and limitations
|
* specific language governing permissions and limitations
|
||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
import React from "react";
|
import React from 'react';
|
||||||
import {Modal, Spin, Tabs} from "antd";
|
import { Modal, Spin, Tabs } from 'antd';
|
||||||
import DeviceUninstall from "./DeviceUninstall";
|
import DeviceUninstall from './DeviceUninstall';
|
||||||
import UserUninstall from "./UserUninstall";
|
import UserUninstall from './UserUninstall';
|
||||||
import RoleUninstall from "./RoleUninstall";
|
import RoleUninstall from './RoleUninstall';
|
||||||
import GroupUninstall from "./GroupUninstall";
|
import GroupUninstall from './GroupUninstall';
|
||||||
|
|
||||||
const {TabPane} = Tabs;
|
const { TabPane } = Tabs;
|
||||||
|
|
||||||
class AppUninstallModal extends React.Component {
|
class AppUninstallModal extends React.Component {
|
||||||
state = {
|
state = {
|
||||||
data: []
|
data: [],
|
||||||
};
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const {deviceType} = this.props;
|
const { deviceType } = this.props;
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<Modal
|
<Modal
|
||||||
title="Uninstall App"
|
title="Uninstall App"
|
||||||
visible={this.props.visible}
|
visible={this.props.visible}
|
||||||
onCancel={this.props.onClose}
|
onCancel={this.props.onClose}
|
||||||
footer={null}>
|
footer={null}
|
||||||
<Spin spinning={this.props.loading}>
|
>
|
||||||
<Tabs defaultActiveKey="device">
|
<Spin spinning={this.props.loading}>
|
||||||
<TabPane tab="Device" key="device">
|
<Tabs defaultActiveKey="device">
|
||||||
<DeviceUninstall deviceType={deviceType} onUninstall={this.props.onUninstall}
|
<TabPane tab="Device" key="device">
|
||||||
uuid={this.props.uuid}/>
|
<DeviceUninstall
|
||||||
</TabPane>
|
deviceType={deviceType}
|
||||||
<TabPane tab="User" key="user">
|
onUninstall={this.props.onUninstall}
|
||||||
<UserUninstall onUninstall={this.props.onUninstall} uuid={this.props.uuid}/>
|
uuid={this.props.uuid}
|
||||||
</TabPane>
|
/>
|
||||||
<TabPane tab="Role" key="role">
|
</TabPane>
|
||||||
<RoleUninstall onUninstall={this.props.onUninstall} uuid={this.props.uuid}/>
|
<TabPane tab="User" key="user">
|
||||||
</TabPane>
|
<UserUninstall
|
||||||
<TabPane tab="Group" key="group">
|
onUninstall={this.props.onUninstall}
|
||||||
<GroupUninstall onUninstall={this.props.onUninstall} uuid={this.props.uuid}/>
|
uuid={this.props.uuid}
|
||||||
</TabPane>
|
/>
|
||||||
</Tabs>
|
</TabPane>
|
||||||
</Spin>
|
<TabPane tab="Role" key="role">
|
||||||
</Modal>
|
<RoleUninstall
|
||||||
</div>
|
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;
|
export default AppUninstallModal;
|
||||||
|
|||||||
@ -16,232 +16,250 @@
|
|||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from "react";
|
import React from 'react';
|
||||||
import axios from "axios";
|
import axios from 'axios';
|
||||||
import {Button, message, DatePicker, Table, Typography, Alert} from "antd";
|
import { Table, Typography, Alert } from 'antd';
|
||||||
import TimeAgo from 'javascript-time-ago'
|
import TimeAgo from 'javascript-time-ago';
|
||||||
|
|
||||||
// Load locale-specific relative date/time formatting rules.
|
// Load locale-specific relative date/time formatting rules.
|
||||||
import en from 'javascript-time-ago/locale/en'
|
import en from 'javascript-time-ago/locale/en';
|
||||||
import {withConfigContext} from "../../../../context/ConfigContext";
|
import { withConfigContext } from '../../../../context/ConfigContext';
|
||||||
import {handleApiError} from "../../../../js/Utils";
|
import { handleApiError } from '../../../../js/Utils';
|
||||||
import InstallModalFooter from "./installModalFooter/InstallModalFooter";
|
import InstallModalFooter from './installModalFooter/InstallModalFooter';
|
||||||
|
|
||||||
const {Text} = Typography;
|
const { Text } = Typography;
|
||||||
const columns = [
|
const columns = [
|
||||||
{
|
{
|
||||||
title: 'Device',
|
title: 'Device',
|
||||||
dataIndex: 'name',
|
dataIndex: 'name',
|
||||||
fixed: 'left',
|
fixed: 'left',
|
||||||
width: 100,
|
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;
|
||||||
},
|
},
|
||||||
{
|
// todo add filtering options
|
||||||
title: 'Modal',
|
},
|
||||||
dataIndex: 'deviceInfo',
|
{
|
||||||
key: 'modal',
|
title: 'Status',
|
||||||
render: deviceInfo => `${deviceInfo.vendor} ${deviceInfo.deviceModel}`
|
dataIndex: 'enrolmentInfo',
|
||||||
// todo add filtering options
|
key: 'status',
|
||||||
},
|
render: enrolmentInfo => enrolmentInfo.status,
|
||||||
{
|
// todo add filtering options
|
||||||
title: 'Owner',
|
},
|
||||||
dataIndex: 'enrolmentInfo',
|
{
|
||||||
key: 'owner',
|
title: 'Ownership',
|
||||||
render: enrolmentInfo => enrolmentInfo.owner
|
dataIndex: 'enrolmentInfo',
|
||||||
// todo add filtering options
|
key: 'ownership',
|
||||||
},
|
render: enrolmentInfo => enrolmentInfo.ownership,
|
||||||
{
|
// todo add filtering options
|
||||||
title: 'Last Updated',
|
},
|
||||||
dataIndex: 'deviceInfo',
|
{
|
||||||
key: 'updatedTime',
|
title: 'OS Version',
|
||||||
render: (data) => {
|
dataIndex: 'deviceInfo',
|
||||||
return data.updatedTime;
|
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
|
}
|
||||||
},
|
return imei;
|
||||||
{
|
|
||||||
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
|
|
||||||
},
|
},
|
||||||
|
// todo add filtering options
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
class DeviceInstall extends React.Component {
|
class DeviceInstall extends React.Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
TimeAgo.addLocale(en);
|
TimeAgo.addLocale(en);
|
||||||
this.state = {
|
this.state = {
|
||||||
data: [],
|
data: [],
|
||||||
pagination: {},
|
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,
|
loading: false,
|
||||||
selectedRows: [],
|
data: res.data.data,
|
||||||
scheduledTime: null,
|
pagination,
|
||||||
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 '&'
|
.catch(error => {
|
||||||
const encodedExtraParams = Object.keys(extraParams).map(key => key + '=' + extraParams[key]).join('&');
|
handleApiError(
|
||||||
|
error,
|
||||||
//send request to the invoker
|
'Error occurred while trying to load devices.',
|
||||||
axios.get(
|
true,
|
||||||
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>
|
|
||||||
);
|
);
|
||||||
}
|
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.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from "react";
|
import React from 'react';
|
||||||
import axios from "axios";
|
import axios from 'axios';
|
||||||
import {Alert, Button, Select, Table, Typography} from "antd";
|
import { Alert, Table, Typography } from 'antd';
|
||||||
import TimeAgo from 'javascript-time-ago'
|
import TimeAgo from 'javascript-time-ago';
|
||||||
|
|
||||||
// Load locale-specific relative date/time formatting rules.
|
// Load locale-specific relative date/time formatting rules.
|
||||||
import en from 'javascript-time-ago/locale/en'
|
import en from 'javascript-time-ago/locale/en';
|
||||||
import {withConfigContext} from "../../../../context/ConfigContext";
|
import { withConfigContext } from '../../../../context/ConfigContext';
|
||||||
import {handleApiError} from "../../../../js/Utils";
|
import { handleApiError } from '../../../../js/Utils';
|
||||||
import InstallModalFooter from "./installModalFooter/InstallModalFooter";
|
import InstallModalFooter from './installModalFooter/InstallModalFooter';
|
||||||
|
|
||||||
const {Text} = Typography;
|
const { Text } = Typography;
|
||||||
const columns = [
|
const columns = [
|
||||||
{
|
{
|
||||||
title: 'Device',
|
title: 'Device',
|
||||||
dataIndex: 'name',
|
dataIndex: 'name',
|
||||||
fixed: 'left',
|
fixed: 'left',
|
||||||
width: 100,
|
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;
|
||||||
},
|
},
|
||||||
{
|
// todo add filtering options
|
||||||
title: 'Modal',
|
},
|
||||||
dataIndex: 'deviceInfo',
|
{
|
||||||
key: 'modal',
|
title: 'Status',
|
||||||
render: deviceInfo => `${deviceInfo.vendor} ${deviceInfo.deviceModel}`
|
dataIndex: 'enrolmentInfo',
|
||||||
// todo add filtering options
|
key: 'status',
|
||||||
},
|
render: enrolmentInfo => enrolmentInfo.status,
|
||||||
{
|
// todo add filtering options
|
||||||
title: 'Owner',
|
},
|
||||||
dataIndex: 'enrolmentInfo',
|
{
|
||||||
key: 'owner',
|
title: 'Ownership',
|
||||||
render: enrolmentInfo => enrolmentInfo.owner
|
dataIndex: 'enrolmentInfo',
|
||||||
// todo add filtering options
|
key: 'ownership',
|
||||||
},
|
render: enrolmentInfo => enrolmentInfo.ownership,
|
||||||
{
|
// todo add filtering options
|
||||||
title: 'Last Updated',
|
},
|
||||||
dataIndex: 'deviceInfo',
|
{
|
||||||
key: 'updatedTime',
|
title: 'OS Version',
|
||||||
render: (data) => {
|
dataIndex: 'deviceInfo',
|
||||||
return data.updatedTime;
|
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
|
}
|
||||||
},
|
return imei;
|
||||||
{
|
|
||||||
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
|
|
||||||
},
|
},
|
||||||
|
// todo add filtering options
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
class DeviceUninstall extends React.Component {
|
class DeviceUninstall extends React.Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
TimeAgo.addLocale(en);
|
TimeAgo.addLocale(en);
|
||||||
this.state = {
|
this.state = {
|
||||||
data: [],
|
data: [],
|
||||||
pagination: {},
|
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,
|
loading: false,
|
||||||
selectedRows: [],
|
data: res.data.data,
|
||||||
isForbidden: false
|
pagination,
|
||||||
};
|
});
|
||||||
}
|
|
||||||
|
|
||||||
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 '&'
|
.catch(error => {
|
||||||
const encodedExtraParams = Object.keys(extraParams).map(key => key + '=' + extraParams[key]).join('&');
|
handleApiError(
|
||||||
|
error,
|
||||||
const uuid = this.props.uuid;
|
'Error occurred while trying to load devices.',
|
||||||
axios.get(
|
true,
|
||||||
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>
|
|
||||||
);
|
);
|
||||||
}
|
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);
|
export default withConfigContext(DeviceUninstall);
|
||||||
|
|||||||
@ -16,124 +16,139 @@
|
|||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from "react";
|
import React from 'react';
|
||||||
import {Typography, Select, Spin, message, notification, Button, Alert} from "antd";
|
import { Typography, Select, Spin, Alert } from 'antd';
|
||||||
import debounce from 'lodash.debounce';
|
import debounce from 'lodash.debounce';
|
||||||
import axios from "axios";
|
import axios from 'axios';
|
||||||
import {withConfigContext} from "../../../../context/ConfigContext";
|
import { withConfigContext } from '../../../../context/ConfigContext';
|
||||||
import {handleApiError} from "../../../../js/Utils";
|
import { handleApiError } from '../../../../js/Utils';
|
||||||
import InstallModalFooter from "./installModalFooter/InstallModalFooter";
|
import InstallModalFooter from './installModalFooter/InstallModalFooter';
|
||||||
|
|
||||||
const {Text} = Typography;
|
|
||||||
const {Option} = Select;
|
|
||||||
|
|
||||||
|
const { Text } = Typography;
|
||||||
|
const { Option } = Select;
|
||||||
|
|
||||||
class GroupInstall extends React.Component {
|
class GroupInstall extends React.Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.lastFetchId = 0;
|
||||||
|
this.fetchUser = debounce(this.fetchUser, 800);
|
||||||
|
}
|
||||||
|
|
||||||
constructor(props) {
|
state = {
|
||||||
super(props);
|
data: [],
|
||||||
this.lastFetchId = 0;
|
value: [],
|
||||||
this.fetchUser = debounce(this.fetchUser, 800);
|
fetching: false,
|
||||||
}
|
isForbidden: false,
|
||||||
|
};
|
||||||
|
|
||||||
state = {
|
fetchUser = value => {
|
||||||
data: [],
|
this.lastFetchId += 1;
|
||||||
value: [],
|
const fetchId = this.lastFetchId;
|
||||||
fetching: false,
|
const config = this.props.context;
|
||||||
isForbidden: false
|
this.setState({ data: [], fetching: true });
|
||||||
};
|
|
||||||
|
|
||||||
fetchUser = value => {
|
axios
|
||||||
this.lastFetchId += 1;
|
.get(
|
||||||
const fetchId = this.lastFetchId;
|
window.location.origin +
|
||||||
const config = this.props.context;
|
config.serverConfig.invoker.uri +
|
||||||
this.setState({data: [], fetching: true});
|
config.serverConfig.invoker.deviceMgt +
|
||||||
|
'/groups?name=' +
|
||||||
|
value,
|
||||||
|
)
|
||||||
|
.then(res => {
|
||||||
|
if (res.status === 200) {
|
||||||
|
if (fetchId !== this.lastFetchId) {
|
||||||
|
// for fetch callback order
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
axios.get(
|
const data = res.data.data.deviceGroups.map(group => ({
|
||||||
window.location.origin+ config.serverConfig.invoker.uri + config.serverConfig.invoker.deviceMgt+"/groups?name=" + value,
|
text: group.name,
|
||||||
|
value: group.name,
|
||||||
|
}));
|
||||||
|
|
||||||
).then(res => {
|
this.setState({ data, fetching: false });
|
||||||
if (res.status === 200) {
|
}
|
||||||
if (fetchId !== this.lastFetchId) {
|
})
|
||||||
// for fetch callback order
|
.catch(error => {
|
||||||
return;
|
handleApiError(
|
||||||
}
|
error,
|
||||||
|
'Error occurred while trying to load groups.',
|
||||||
const data = res.data.data.deviceGroups.map(group => ({
|
true,
|
||||||
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>
|
|
||||||
);
|
);
|
||||||
}
|
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.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from "react";
|
import React from 'react';
|
||||||
import {Typography, Select, Spin, message, notification, Button, Alert} from "antd";
|
import { Typography, Select, Spin, Alert } from 'antd';
|
||||||
import debounce from 'lodash.debounce';
|
import debounce from 'lodash.debounce';
|
||||||
import axios from "axios";
|
import axios from 'axios';
|
||||||
import {withConfigContext} from "../../../../context/ConfigContext";
|
import { withConfigContext } from '../../../../context/ConfigContext';
|
||||||
import {handleApiError} from "../../../../js/Utils";
|
import { handleApiError } from '../../../../js/Utils';
|
||||||
import InstallModalFooter from "./installModalFooter/InstallModalFooter";
|
import InstallModalFooter from './installModalFooter/InstallModalFooter';
|
||||||
|
|
||||||
const {Text} = Typography;
|
const { Text } = Typography;
|
||||||
const {Option} = Select;
|
const { Option } = Select;
|
||||||
|
|
||||||
class GroupUninstall extends React.Component {
|
class GroupUninstall extends React.Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.lastFetchId = 0;
|
||||||
|
this.fetchUser = debounce(this.fetchUser, 800);
|
||||||
|
}
|
||||||
|
|
||||||
constructor(props) {
|
state = {
|
||||||
super(props);
|
data: [],
|
||||||
this.lastFetchId = 0;
|
value: [],
|
||||||
this.fetchUser = debounce(this.fetchUser, 800);
|
fetching: false,
|
||||||
}
|
isForbidden: false,
|
||||||
|
};
|
||||||
|
|
||||||
state = {
|
fetchUser = value => {
|
||||||
data: [],
|
this.lastFetchId += 1;
|
||||||
value: [],
|
const fetchId = this.lastFetchId;
|
||||||
fetching: false,
|
const config = this.props.context;
|
||||||
isForbidden: false
|
this.setState({ data: [], fetching: true });
|
||||||
};
|
|
||||||
|
|
||||||
fetchUser = value => {
|
const uuid = this.props.uuid;
|
||||||
this.lastFetchId += 1;
|
|
||||||
const fetchId = this.lastFetchId;
|
|
||||||
const config = this.props.context;
|
|
||||||
this.setState({data: [], fetching: true});
|
|
||||||
|
|
||||||
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(
|
const data = res.data.data.deviceGroups.map(group => ({
|
||||||
window.location.origin + config.serverConfig.invoker.uri + config.serverConfig.invoker.store + "/subscription/" + uuid + "/" +
|
text: group,
|
||||||
"/GROUP?",
|
value: group,
|
||||||
).then(res => {
|
}));
|
||||||
if (res.status === 200) {
|
|
||||||
if (fetchId !== this.lastFetchId) {
|
|
||||||
// for fetch callback order
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const data = res.data.data.deviceGroups.map(group => ({
|
this.setState({ data, fetching: false });
|
||||||
text: group,
|
}
|
||||||
value: group,
|
})
|
||||||
}));
|
.catch(error => {
|
||||||
|
handleApiError(
|
||||||
this.setState({data, fetching: false});
|
error,
|
||||||
}
|
'Error occurred while trying to load groups.',
|
||||||
|
true,
|
||||||
}).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>
|
|
||||||
);
|
);
|
||||||
}
|
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);
|
export default withConfigContext(GroupUninstall);
|
||||||
|
|||||||
@ -16,123 +16,139 @@
|
|||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from "react";
|
import React from 'react';
|
||||||
import {Typography, Select, Spin, message, notification, Button, Alert} from "antd";
|
import { Typography, Select, Spin, Alert } from 'antd';
|
||||||
import debounce from 'lodash.debounce';
|
import debounce from 'lodash.debounce';
|
||||||
import axios from "axios";
|
import axios from 'axios';
|
||||||
import {withConfigContext} from "../../../../context/ConfigContext";
|
import { withConfigContext } from '../../../../context/ConfigContext';
|
||||||
import {handleApiError} from "../../../../js/Utils";
|
import { handleApiError } from '../../../../js/Utils';
|
||||||
import InstallModalFooter from "./installModalFooter/InstallModalFooter";
|
import InstallModalFooter from './installModalFooter/InstallModalFooter';
|
||||||
|
|
||||||
const {Text} = Typography;
|
|
||||||
const {Option} = Select;
|
|
||||||
|
|
||||||
|
const { Text } = Typography;
|
||||||
|
const { Option } = Select;
|
||||||
|
|
||||||
class RoleInstall extends React.Component {
|
class RoleInstall extends React.Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.lastFetchId = 0;
|
||||||
|
this.fetchUser = debounce(this.fetchUser, 800);
|
||||||
|
}
|
||||||
|
|
||||||
constructor(props) {
|
state = {
|
||||||
super(props);
|
data: [],
|
||||||
this.lastFetchId = 0;
|
value: [],
|
||||||
this.fetchUser = debounce(this.fetchUser, 800);
|
fetching: false,
|
||||||
}
|
isForbidden: false,
|
||||||
|
};
|
||||||
|
|
||||||
state = {
|
fetchUser = value => {
|
||||||
data: [],
|
const config = this.props.context;
|
||||||
value: [],
|
this.lastFetchId += 1;
|
||||||
fetching: false,
|
const fetchId = this.lastFetchId;
|
||||||
isForbidden: false
|
this.setState({ data: [], fetching: true });
|
||||||
};
|
|
||||||
|
|
||||||
fetchUser = value => {
|
axios
|
||||||
const config = this.props.context;
|
.get(
|
||||||
this.lastFetchId += 1;
|
window.location.origin +
|
||||||
const fetchId = this.lastFetchId;
|
config.serverConfig.invoker.uri +
|
||||||
this.setState({data: [], fetching: true});
|
config.serverConfig.invoker.deviceMgt +
|
||||||
|
'/roles?filter=' +
|
||||||
|
value,
|
||||||
|
)
|
||||||
|
.then(res => {
|
||||||
|
if (res.status === 200) {
|
||||||
|
if (fetchId !== this.lastFetchId) {
|
||||||
|
// for fetch callback order
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
axios.get(
|
const data = res.data.data.roles.map(role => ({
|
||||||
window.location.origin+ config.serverConfig.invoker.uri + config.serverConfig.invoker.deviceMgt+"/roles?filter=" + value,
|
text: role,
|
||||||
|
value: role,
|
||||||
|
}));
|
||||||
|
|
||||||
).then(res => {
|
this.setState({ data, fetching: false });
|
||||||
if (res.status === 200) {
|
}
|
||||||
if (fetchId !== this.lastFetchId) {
|
})
|
||||||
// for fetch callback order
|
.catch(error => {
|
||||||
return;
|
handleApiError(
|
||||||
}
|
error,
|
||||||
|
'Error occurred while trying to load roles.',
|
||||||
const data = res.data.data.roles.map(role => ({
|
true,
|
||||||
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>
|
|
||||||
);
|
);
|
||||||
}
|
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.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from "react";
|
import React from 'react';
|
||||||
import {Typography, Select, Spin, message, notification, Button, Alert} from "antd";
|
import { Typography, Select, Spin, Alert } from 'antd';
|
||||||
import debounce from 'lodash.debounce';
|
import debounce from 'lodash.debounce';
|
||||||
import axios from "axios";
|
import axios from 'axios';
|
||||||
import {withConfigContext} from "../../../../context/ConfigContext";
|
import { withConfigContext } from '../../../../context/ConfigContext';
|
||||||
import {handleApiError} from "../../../../js/Utils";
|
import { handleApiError } from '../../../../js/Utils';
|
||||||
import InstallModalFooter from "./installModalFooter/InstallModalFooter";
|
import InstallModalFooter from './installModalFooter/InstallModalFooter';
|
||||||
|
|
||||||
const {Text} = Typography;
|
const { Text } = Typography;
|
||||||
const {Option} = Select;
|
const { Option } = Select;
|
||||||
|
|
||||||
class RoleUninstall extends React.Component {
|
class RoleUninstall extends React.Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.lastFetchId = 0;
|
||||||
|
this.fetchUser = debounce(this.fetchUser, 800);
|
||||||
|
}
|
||||||
|
|
||||||
constructor(props) {
|
state = {
|
||||||
super(props);
|
data: [],
|
||||||
this.lastFetchId = 0;
|
value: [],
|
||||||
this.fetchUser = debounce(this.fetchUser, 800);
|
fetching: false,
|
||||||
}
|
isForbidden: false,
|
||||||
|
};
|
||||||
|
|
||||||
state = {
|
fetchUser = value => {
|
||||||
data: [],
|
const config = this.props.context;
|
||||||
value: [],
|
this.lastFetchId += 1;
|
||||||
fetching: false,
|
const fetchId = this.lastFetchId;
|
||||||
isForbidden: false
|
this.setState({ data: [], fetching: true });
|
||||||
};
|
|
||||||
|
|
||||||
fetchUser = value => {
|
const uuid = this.props.uuid;
|
||||||
const config = this.props.context;
|
|
||||||
this.lastFetchId += 1;
|
|
||||||
const fetchId = this.lastFetchId;
|
|
||||||
this.setState({data: [], fetching: true});
|
|
||||||
|
|
||||||
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(
|
const data = res.data.data.roles.map(role => ({
|
||||||
window.location.origin + config.serverConfig.invoker.uri + config.serverConfig.invoker.store + "/subscription/" + uuid + "/" +
|
text: role,
|
||||||
"/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 => ({
|
this.setState({ data, fetching: false });
|
||||||
text: role,
|
}
|
||||||
value: role,
|
})
|
||||||
}));
|
.catch(error => {
|
||||||
|
handleApiError(
|
||||||
this.setState({data, fetching: false});
|
error,
|
||||||
}
|
'Error occurred while trying to load roles.',
|
||||||
|
true,
|
||||||
}).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>
|
|
||||||
);
|
);
|
||||||
}
|
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);
|
export default withConfigContext(RoleUninstall);
|
||||||
|
|||||||
@ -16,124 +16,139 @@
|
|||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from "react";
|
import React from 'react';
|
||||||
import {Typography, Select, Spin, message, notification, Button, Alert} from "antd";
|
import { Typography, Select, Spin, Alert } from 'antd';
|
||||||
import debounce from 'lodash.debounce';
|
import debounce from 'lodash.debounce';
|
||||||
import axios from "axios";
|
import axios from 'axios';
|
||||||
import {withConfigContext} from "../../../../context/ConfigContext";
|
import { withConfigContext } from '../../../../context/ConfigContext';
|
||||||
import {handleApiError} from "../../../../js/Utils";
|
import { handleApiError } from '../../../../js/Utils';
|
||||||
import InstallModalFooter from "./installModalFooter/InstallModalFooter";
|
import InstallModalFooter from './installModalFooter/InstallModalFooter';
|
||||||
|
|
||||||
const {Text} = Typography;
|
|
||||||
const {Option} = Select;
|
|
||||||
|
|
||||||
|
const { Text } = Typography;
|
||||||
|
const { Option } = Select;
|
||||||
|
|
||||||
class UserInstall extends React.Component {
|
class UserInstall extends React.Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.lastFetchId = 0;
|
||||||
|
this.fetchUser = debounce(this.fetchUser, 800);
|
||||||
|
}
|
||||||
|
|
||||||
constructor(props) {
|
state = {
|
||||||
super(props);
|
data: [],
|
||||||
this.lastFetchId = 0;
|
value: [],
|
||||||
this.fetchUser = debounce(this.fetchUser, 800);
|
fetching: false,
|
||||||
}
|
};
|
||||||
|
|
||||||
state = {
|
fetchUser = value => {
|
||||||
data: [],
|
const config = this.props.context;
|
||||||
value: [],
|
this.lastFetchId += 1;
|
||||||
fetching: false,
|
const fetchId = this.lastFetchId;
|
||||||
};
|
this.setState({ data: [], fetching: true });
|
||||||
|
|
||||||
fetchUser = value => {
|
// send request to the invoker
|
||||||
const config = this.props.context;
|
axios
|
||||||
this.lastFetchId += 1;
|
.get(
|
||||||
const fetchId = this.lastFetchId;
|
window.location.origin +
|
||||||
this.setState({data: [], fetching: true});
|
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
|
this.setState({ data, fetching: false });
|
||||||
axios.get(
|
}
|
||||||
window.location.origin+ config.serverConfig.invoker.uri + config.serverConfig.invoker.deviceMgt+"/users/search?username=" + value,
|
})
|
||||||
|
.catch(error => {
|
||||||
).then(res => {
|
handleApiError(
|
||||||
if (res.status === 200) {
|
error,
|
||||||
if (fetchId !== this.lastFetchId) {
|
'Error occurred while trying to load users.',
|
||||||
// for fetch callback order
|
true,
|
||||||
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>
|
|
||||||
);
|
);
|
||||||
}
|
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.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from "react";
|
import React from 'react';
|
||||||
import {Typography, Select, Spin, message, notification, Button, Alert} from "antd";
|
import { Typography, Select, Spin, Alert } from 'antd';
|
||||||
import debounce from 'lodash.debounce';
|
import debounce from 'lodash.debounce';
|
||||||
import axios from "axios";
|
import axios from 'axios';
|
||||||
import {withConfigContext} from "../../../../context/ConfigContext";
|
import { withConfigContext } from '../../../../context/ConfigContext';
|
||||||
import {handleApiError} from "../../../../js/Utils";
|
import { handleApiError } from '../../../../js/Utils';
|
||||||
import InstallModalFooter from "./installModalFooter/InstallModalFooter";
|
import InstallModalFooter from './installModalFooter/InstallModalFooter';
|
||||||
|
|
||||||
const {Text} = Typography;
|
const { Text } = Typography;
|
||||||
const {Option} = Select;
|
const { Option } = Select;
|
||||||
|
|
||||||
class UserUninstall extends React.Component {
|
class UserUninstall extends React.Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.lastFetchId = 0;
|
||||||
|
this.fetchUser = debounce(this.fetchUser, 800);
|
||||||
|
}
|
||||||
|
|
||||||
constructor(props) {
|
state = {
|
||||||
super(props);
|
data: [],
|
||||||
this.lastFetchId = 0;
|
value: [],
|
||||||
this.fetchUser = debounce(this.fetchUser, 800);
|
fetching: false,
|
||||||
}
|
isForbidden: false,
|
||||||
|
};
|
||||||
|
|
||||||
state = {
|
fetchUser = value => {
|
||||||
data: [],
|
const config = this.props.context;
|
||||||
value: [],
|
this.lastFetchId += 1;
|
||||||
fetching: false,
|
const fetchId = this.lastFetchId;
|
||||||
isForbidden: false
|
this.setState({ data: [], fetching: true });
|
||||||
};
|
|
||||||
|
|
||||||
fetchUser = (value) => {
|
const uuid = this.props.uuid;
|
||||||
const config = this.props.context;
|
|
||||||
this.lastFetchId += 1;
|
|
||||||
const fetchId = this.lastFetchId;
|
|
||||||
this.setState({data: [], fetching: true});
|
|
||||||
|
|
||||||
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(
|
this.setState({ data, fetching: false });
|
||||||
window.location.origin + config.serverConfig.invoker.uri + config.serverConfig.invoker.store + "/subscription/" + uuid + "/" +
|
}
|
||||||
"/USER?",
|
})
|
||||||
).then(res => {
|
.catch(error => {
|
||||||
if (res.status === 200) {
|
handleApiError(
|
||||||
if (fetchId !== this.lastFetchId) {
|
error,
|
||||||
// for fetch callback order
|
'Error occurred while trying to load users.',
|
||||||
return;
|
true,
|
||||||
}
|
|
||||||
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>
|
|
||||||
);
|
);
|
||||||
}
|
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);
|
export default withConfigContext(UserUninstall);
|
||||||
|
|||||||
@ -16,72 +16,84 @@
|
|||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from "react";
|
import React from 'react';
|
||||||
import {Button, DatePicker, Checkbox} from "antd";
|
import { Button, DatePicker, Checkbox } from 'antd';
|
||||||
|
|
||||||
class InstallModalFooter extends React.Component {
|
class InstallModalFooter extends React.Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
this.state = {
|
this.state = {
|
||||||
scheduledTime: null,
|
scheduledTime: null,
|
||||||
isScheduledInstallVisible: false
|
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) => {
|
render() {
|
||||||
this.setState({
|
const { scheduledTime, isScheduledInstallVisible } = this.state;
|
||||||
scheduledTime: dateString
|
const { disabled, type } = this.props;
|
||||||
});
|
return (
|
||||||
};
|
<div>
|
||||||
|
<div
|
||||||
toggleScheduledInstall = () => {
|
style={{
|
||||||
this.setState({
|
textAlign: 'right',
|
||||||
isScheduledInstallVisible: !this.state.isScheduledInstallVisible
|
}}
|
||||||
})
|
>
|
||||||
};
|
<div style={{ margin: 8 }}>
|
||||||
|
<Checkbox
|
||||||
triggerInstallOperation = () => {
|
checked={this.state.isScheduledInstallVisible}
|
||||||
const {scheduledTime, isScheduledInstallVisible} = this.state;
|
onChange={this.toggleScheduledInstall}
|
||||||
if (isScheduledInstallVisible && scheduledTime != null) {
|
>
|
||||||
this.props.operation(scheduledTime);
|
Schedule {type}
|
||||||
} else {
|
</Checkbox>
|
||||||
this.props.operation();
|
</div>
|
||||||
}
|
<span
|
||||||
};
|
style={{
|
||||||
|
display: isScheduledInstallVisible ? 'inline' : 'none',
|
||||||
render() {
|
}}
|
||||||
const {scheduledTime, isScheduledInstallVisible} = this.state;
|
>
|
||||||
const {disabled, type} = this.props;
|
<DatePicker
|
||||||
return (
|
showTime
|
||||||
<div>
|
placeholder="Select Time"
|
||||||
<div style={{
|
format="YYYY-MM-DDTHH:mm"
|
||||||
textAlign: "right"
|
onChange={this.onDateTimeChange}
|
||||||
}}>
|
/>
|
||||||
<div style={{margin: 8}}>
|
</span>
|
||||||
<Checkbox checked={this.state.isScheduledInstallVisible} onChange={this.toggleScheduledInstall}>
|
<Button
|
||||||
Schedule {type}
|
style={{ margin: 5 }}
|
||||||
</Checkbox>
|
disabled={
|
||||||
</div>
|
disabled || (isScheduledInstallVisible && scheduledTime == null)
|
||||||
<span style={{
|
}
|
||||||
display: (isScheduledInstallVisible) ? 'inline' : 'none'
|
htmlType="button"
|
||||||
}}>
|
type="primary"
|
||||||
<DatePicker showTime
|
onClick={this.triggerInstallOperation}
|
||||||
placeholder="Select Time"
|
>
|
||||||
format="YYYY-MM-DDTHH:mm"
|
{type}
|
||||||
onChange={this.onDateTimeChange}/>
|
</Button>
|
||||||
</span>
|
</div>
|
||||||
<Button style={{margin: 5}}
|
</div>
|
||||||
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.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from "react";
|
import React from 'react';
|
||||||
import {Drawer, Button, Icon, Row, Col, Typography, Divider, Input, Spin, notification} from 'antd';
|
import {
|
||||||
import StarRatings from "react-star-ratings";
|
Drawer,
|
||||||
import axios from "axios";
|
Button,
|
||||||
import {withConfigContext} from "../../../../context/ConfigContext";
|
Icon,
|
||||||
import {handleApiError} from "../../../../js/Utils";
|
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 { Title } = Typography;
|
||||||
const {TextArea} = Input;
|
const { TextArea } = Input;
|
||||||
|
|
||||||
class AddReview extends React.Component {
|
class AddReview extends React.Component {
|
||||||
state = {
|
state = {
|
||||||
visible: false,
|
visible: false,
|
||||||
content: '',
|
content: '',
|
||||||
rating: 0,
|
rating: 0,
|
||||||
loading: false
|
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 = () => {
|
axios
|
||||||
this.setState({
|
.post(
|
||||||
visible: true,
|
window.location.origin +
|
||||||
content: '',
|
config.serverConfig.invoker.uri +
|
||||||
rating: 0,
|
config.serverConfig.invoker.store +
|
||||||
loading: false
|
'/reviews/' +
|
||||||
});
|
uuid,
|
||||||
};
|
payload,
|
||||||
|
)
|
||||||
onClose = () => {
|
.then(res => {
|
||||||
this.setState({
|
if (res.status === 201) {
|
||||||
|
this.setState({
|
||||||
|
loading: false,
|
||||||
visible: false,
|
visible: false,
|
||||||
|
});
|
||||||
});
|
notification.success({
|
||||||
};
|
message: 'Done!',
|
||||||
changeRating = (newRating, name) => {
|
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({
|
this.setState({
|
||||||
rating: newRating
|
loading: false,
|
||||||
|
visible: false,
|
||||||
});
|
});
|
||||||
};
|
});
|
||||||
|
};
|
||||||
|
|
||||||
onChange = (e) => {
|
render() {
|
||||||
this.setState({content: e.target.value})
|
return (
|
||||||
};
|
<div>
|
||||||
|
<Button type="primary" onClick={this.showDrawer}>
|
||||||
|
<Icon type="star" /> Add a review
|
||||||
|
</Button>
|
||||||
|
|
||||||
onSubmit = () => {
|
<Drawer
|
||||||
const config = this.props.context;
|
// title="Basic Drawer"
|
||||||
const {content, rating} = this.state;
|
placement="bottom"
|
||||||
const {uuid} = this.props;
|
closable={false}
|
||||||
this.setState({
|
onClose={this.onClose}
|
||||||
loading: true
|
visible={this.state.visible}
|
||||||
});
|
height={400}
|
||||||
|
>
|
||||||
const payload = {
|
<Spin spinning={this.state.loading} tip="Posting your review...">
|
||||||
content: content,
|
<Row>
|
||||||
rating: rating
|
<Col lg={8} />
|
||||||
};
|
<Col lg={8}>
|
||||||
|
<Title level={4}>Add review</Title>
|
||||||
axios.post(
|
<Divider />
|
||||||
window.location.origin+ config.serverConfig.invoker.uri + config.serverConfig.invoker.store + "/reviews/" + uuid,
|
<TextArea
|
||||||
payload,
|
placeholder="Tell others what you think about this app. Would you recommend it, and why?"
|
||||||
).then(res => {
|
onChange={this.onChange}
|
||||||
if (res.status === 201) {
|
rows={4}
|
||||||
this.setState({
|
value={this.state.content || ''}
|
||||||
loading: false,
|
style={{ marginBottom: 20 }}
|
||||||
visible: false
|
/>
|
||||||
});
|
<StarRatings
|
||||||
notification["success"]({
|
rating={this.state.rating}
|
||||||
message: 'Done!',
|
changeRating={this.changeRating}
|
||||||
description:
|
starRatedColor="#777"
|
||||||
'Your review has been posted successfully.',
|
starHoverColor="#444"
|
||||||
});
|
starDimension="20px"
|
||||||
this.props.onUpdateReview();
|
starSpacing="2px"
|
||||||
} else {
|
numberOfStars={5}
|
||||||
this.setState({
|
name="rating"
|
||||||
loading: false,
|
/>
|
||||||
visible: false
|
<br />
|
||||||
});
|
<br />
|
||||||
notification["error"]({
|
<Button onClick={this.onClose} style={{ marginRight: 8 }}>
|
||||||
message: "There was a problem",
|
Cancel
|
||||||
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
|
|
||||||
</Button>
|
</Button>
|
||||||
|
<Button
|
||||||
<Drawer
|
disabled={this.state.rating === 0}
|
||||||
// title="Basic Drawer"
|
onClick={this.onSubmit}
|
||||||
placement="bottom"
|
type="primary"
|
||||||
closable={false}
|
|
||||||
onClose={this.onClose}
|
|
||||||
visible={this.state.visible}
|
|
||||||
height={400}
|
|
||||||
>
|
>
|
||||||
<Spin spinning={this.state.loading} tip="Posting your review...">
|
Submit
|
||||||
<Row>
|
</Button>
|
||||||
<Col lg={8}/>
|
</Col>
|
||||||
<Col lg={8}>
|
</Row>
|
||||||
<Title level={4}>Add review</Title>
|
</Spin>
|
||||||
<Divider/>
|
</Drawer>
|
||||||
<TextArea
|
</div>
|
||||||
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>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default withConfigContext(AddReview);
|
export default withConfigContext(AddReview);
|
||||||
|
|||||||
@ -16,81 +16,84 @@
|
|||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from "react";
|
import React from 'react';
|
||||||
import {List, message, Typography, Empty, Button, Row, Col, notification, Alert} from "antd";
|
import { List, Typography, Empty, Alert } from 'antd';
|
||||||
import SingleReview from "./singleReview/SingleReview";
|
import SingleReview from './singleReview/SingleReview';
|
||||||
import axios from "axios";
|
import AddReview from './AddReview';
|
||||||
import AddReview from "./AddReview";
|
import { withConfigContext } from '../../../../context/ConfigContext';
|
||||||
import {withConfigContext} from "../../../../context/ConfigContext";
|
|
||||||
import {handleApiError} from "../../../../js/Utils";
|
|
||||||
|
|
||||||
const {Text, Paragraph} = Typography;
|
const { Text } = Typography;
|
||||||
|
|
||||||
class CurrentUsersReview extends React.Component {
|
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>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{currentUserReviews.length === 0 && (
|
||||||
render() {
|
<div>
|
||||||
const {uuid, currentUserReviews} = this.props;
|
<Empty
|
||||||
return (
|
image={Empty.PRESENTED_IMAGE_DEFAULT}
|
||||||
<div>
|
imagestyle={{
|
||||||
<Text>MY REVIEW</Text>
|
height: 60,
|
||||||
{(this.props.forbidden) && (
|
}}
|
||||||
<Alert
|
description={
|
||||||
message="You don't have permission to add reviews."
|
<span>
|
||||||
type="warning"
|
Share your experience with your community by adding a
|
||||||
banner
|
review.
|
||||||
closable/>
|
</span>
|
||||||
)}
|
}
|
||||||
{(!this.props.forbidden) && (
|
>
|
||||||
<div style={{
|
{/* <Button type="primary">Add review</Button>*/}
|
||||||
overflow: "auto",
|
<AddReview
|
||||||
paddingTop: 8,
|
uuid={uuid}
|
||||||
paddingLeft: 24
|
onUpdateReview={this.props.onUpdateReview}
|
||||||
}}>
|
/>
|
||||||
{currentUserReviews.length > 0 && (
|
</Empty>
|
||||||
<div>
|
</div>
|
||||||
<List
|
)}
|
||||||
dataSource={currentUserReviews}
|
</div>
|
||||||
renderItem={item => (
|
)}
|
||||||
<List.Item key={item.id}>
|
</div>
|
||||||
<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>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default withConfigContext(CurrentUsersReview);
|
export default withConfigContext(CurrentUsersReview);
|
||||||
|
|||||||
@ -16,122 +16,141 @@
|
|||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from "react";
|
import React from 'react';
|
||||||
import CurrentUsersReview from "./CurrentUsersReview";
|
import CurrentUsersReview from './CurrentUsersReview';
|
||||||
import {Col, Divider, Row, Typography} from "antd";
|
import { Col, Divider, Row, Typography } from 'antd';
|
||||||
import DetailedRating from "../DetailedRating";
|
import DetailedRating from '../DetailedRating';
|
||||||
import Reviews from "./Reviews";
|
import Reviews from './Reviews';
|
||||||
import axios from "axios";
|
import axios from 'axios';
|
||||||
import {handleApiError} from "../../../../js/Utils";
|
import { handleApiError } from '../../../../js/Utils';
|
||||||
import {withConfigContext} from "../../../../context/ConfigContext";
|
import { withConfigContext } from '../../../../context/ConfigContext';
|
||||||
|
|
||||||
const {Text} = Typography;
|
const { Text } = Typography;
|
||||||
|
|
||||||
class ReviewContainer extends React.Component {
|
class ReviewContainer extends React.Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
this.state = {
|
this.state = {
|
||||||
currentUserReviews: [],
|
currentUserReviews: [],
|
||||||
detailedRating: null,
|
detailedRating: null,
|
||||||
forbiddenErrors: {
|
forbiddenErrors: {
|
||||||
currentReview: false,
|
currentReview: false,
|
||||||
reviews: false,
|
reviews: false,
|
||||||
rating: 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() {
|
deleteCurrentUserReviewCallback = () => {
|
||||||
this.fetchCurrentUserReviews();
|
this.setState({
|
||||||
this.fetchDetailedRating("app", this.props.uuid);
|
currentUserReviews: [],
|
||||||
}
|
});
|
||||||
|
this.fetchDetailedRating('app', this.props.uuid);
|
||||||
|
};
|
||||||
|
|
||||||
fetchCurrentUserReviews = () => {
|
fetchDetailedRating = (type, uuid) => {
|
||||||
const {uuid} = this.props;
|
const config = this.props.context;
|
||||||
const config = this.props.context;
|
|
||||||
|
|
||||||
axios.get(
|
axios
|
||||||
window.location.origin + config.serverConfig.invoker.uri + config.serverConfig.invoker.store + "/reviews/app/user/" + uuid,
|
.get(
|
||||||
).then(res => {
|
window.location.origin +
|
||||||
if (res.status === 200) {
|
config.serverConfig.invoker.uri +
|
||||||
const currentUserReviews = res.data.data.data;
|
config.serverConfig.invoker.store +
|
||||||
this.setState({currentUserReviews});
|
'/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) => {
|
onUpdateReview = () => {
|
||||||
handleApiError(error, "Error occurred while trying to get your review.", true);
|
this.fetchCurrentUserReviews();
|
||||||
if (error.hasOwnProperty("response") && error.response.status === 403) {
|
this.fetchDetailedRating('app', this.props.uuid);
|
||||||
const {forbiddenErrors} = this.state;
|
};
|
||||||
forbiddenErrors.currentReview = true;
|
|
||||||
this.setState({
|
|
||||||
forbiddenErrors,
|
|
||||||
loading: false
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
this.setState({
|
|
||||||
loading: false
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
deleteCurrentUserReviewCallback = () => {
|
render() {
|
||||||
this.setState({
|
const { uuid } = this.props;
|
||||||
currentUserReviews: []
|
const { currentUserReviews, detailedRating, forbiddenErrors } = this.state;
|
||||||
});
|
return (
|
||||||
this.fetchDetailedRating("app", this.props.uuid);
|
<div>
|
||||||
};
|
<CurrentUsersReview
|
||||||
|
forbidden={forbiddenErrors.currentReview}
|
||||||
fetchDetailedRating = (type, uuid) => {
|
uuid={uuid}
|
||||||
const config = this.props.context;
|
currentUserReviews={currentUserReviews}
|
||||||
|
onUpdateReview={this.onUpdateReview}
|
||||||
axios.get(
|
deleteCallback={this.deleteCurrentUserReviewCallback}
|
||||||
window.location.origin + config.serverConfig.invoker.uri + config.serverConfig.invoker.store + "/reviews/" + uuid + "/" + type + "-rating",
|
/>
|
||||||
).then(res => {
|
<Divider dashed={true} />
|
||||||
if (res.status === 200) {
|
<Text>REVIEWS</Text>
|
||||||
let detailedRating = res.data.data;
|
<Row>
|
||||||
this.setState({
|
<Col lg={18} md={24}>
|
||||||
detailedRating
|
<DetailedRating type="app" detailedRating={detailedRating} />
|
||||||
})
|
</Col>
|
||||||
}
|
</Row>
|
||||||
|
<Reviews type="app" uuid={uuid} deleteCallback={this.onUpdateReview} />
|
||||||
}).catch(function (error) {
|
</div>
|
||||||
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>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default withConfigContext(ReviewContainer);
|
export default withConfigContext(ReviewContainer);
|
||||||
|
|||||||
@ -31,4 +31,4 @@
|
|||||||
position: absolute;
|
position: absolute;
|
||||||
bottom: -40px;
|
bottom: -40px;
|
||||||
left: 50%;
|
left: 50%;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -16,156 +16,183 @@
|
|||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from "react";
|
import React from 'react';
|
||||||
import {List, message, Avatar, Spin, Button, notification, Alert} from 'antd';
|
import { List, Spin, Button, Alert } from 'antd';
|
||||||
import "./Reviews.css";
|
import './Reviews.css';
|
||||||
|
|
||||||
import InfiniteScroll from 'react-infinite-scroller';
|
import InfiniteScroll from 'react-infinite-scroller';
|
||||||
import SingleReview from "./singleReview/SingleReview";
|
import SingleReview from './singleReview/SingleReview';
|
||||||
import axios from "axios";
|
import axios from 'axios';
|
||||||
import {withConfigContext} from "../../../../context/ConfigContext";
|
import { withConfigContext } from '../../../../context/ConfigContext';
|
||||||
import {handleApiError} from "../../../../js/Utils";
|
import { handleApiError } from '../../../../js/Utils';
|
||||||
|
|
||||||
const limit = 5;
|
const limit = 5;
|
||||||
|
|
||||||
class Reviews extends React.Component {
|
class Reviews extends React.Component {
|
||||||
state = {
|
state = {
|
||||||
data: [],
|
data: [],
|
||||||
loading: false,
|
loading: false,
|
||||||
hasMore: false,
|
hasMore: false,
|
||||||
loadMore: false,
|
loadMore: false,
|
||||||
forbiddenErrors: {
|
forbiddenErrors: {
|
||||||
reviews: false
|
reviews: false,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
this.fetchData(0, limit, res => {
|
||||||
|
this.setState({
|
||||||
|
data: res,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fetchData = (offset, limit, callback) => {
|
||||||
|
const { 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(
|
||||||
componentDidMount() {
|
error,
|
||||||
this.fetchData(0, limit, res => {
|
'Error occurred while trying to load reviews.',
|
||||||
this.setState({
|
true,
|
||||||
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>
|
|
||||||
);
|
);
|
||||||
|
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-weight: normal;
|
||||||
font-size: 0.8em;
|
font-size: 0.8em;
|
||||||
padding-left: 5px;
|
padding-left: 5px;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -16,129 +16,155 @@
|
|||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from "react";
|
import React from 'react';
|
||||||
import {Avatar, notification} from "antd";
|
import { Avatar, notification } from 'antd';
|
||||||
import {List, Typography, Popconfirm} from "antd";
|
import { List, Typography, Popconfirm } from 'antd';
|
||||||
import StarRatings from "react-star-ratings";
|
import StarRatings from 'react-star-ratings';
|
||||||
import Twemoji from "react-twemoji";
|
import Twemoji from 'react-twemoji';
|
||||||
import "./SingleReview.css";
|
import './SingleReview.css';
|
||||||
import EditReview from "./editReview/EditReview";
|
import EditReview from './editReview/EditReview';
|
||||||
import axios from "axios";
|
import axios from 'axios';
|
||||||
import {withConfigContext} from "../../../../../context/ConfigContext";
|
import { withConfigContext } from '../../../../../context/ConfigContext';
|
||||||
import {handleApiError} from "../../../../../js/Utils";
|
import { handleApiError } from '../../../../../js/Utils';
|
||||||
|
|
||||||
const {Text, Paragraph} = Typography;
|
const { Text, Paragraph } = Typography;
|
||||||
const colorList = ['#f0932b', '#badc58', '#6ab04c', '#eb4d4b', '#0abde3', '#9b59b6', '#3498db', '#22a6b3', '#e84393', '#f9ca24'];
|
const colorList = [
|
||||||
|
'#f0932b',
|
||||||
|
'#badc58',
|
||||||
|
'#6ab04c',
|
||||||
|
'#eb4d4b',
|
||||||
|
'#0abde3',
|
||||||
|
'#9b59b6',
|
||||||
|
'#3498db',
|
||||||
|
'#22a6b3',
|
||||||
|
'#e84393',
|
||||||
|
'#f9ca24',
|
||||||
|
];
|
||||||
|
|
||||||
class SingleReview extends React.Component {
|
class SingleReview extends React.Component {
|
||||||
|
static defaultProps = {
|
||||||
|
isPersonalReview: false,
|
||||||
|
};
|
||||||
|
|
||||||
static defaultProps = {
|
constructor(props) {
|
||||||
isPersonalReview: false
|
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) {
|
updateCallback = review => {
|
||||||
super(props);
|
this.setState({
|
||||||
const {username} = this.props.review;
|
review,
|
||||||
const color = colorList[username.length % 10];
|
});
|
||||||
this.state = {
|
this.props.onUpdateReview();
|
||||||
content: '',
|
};
|
||||||
rating: 0,
|
|
||||||
color: color,
|
deleteReview = () => {
|
||||||
review: props.review
|
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) => {
|
url += '/reviews/' + uuid + '/' + id;
|
||||||
this.setState({
|
|
||||||
review
|
|
||||||
});
|
|
||||||
this.props.onUpdateReview();
|
|
||||||
};
|
|
||||||
|
|
||||||
deleteReview = () => {
|
axios
|
||||||
const {uuid} = this.props;
|
.delete(url)
|
||||||
const {id} = this.state.review;
|
.then(res => {
|
||||||
const config = this.props.context;
|
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;
|
this.props.deleteCallback(id);
|
||||||
|
|
||||||
// call as an admin api if the review is not a personal review
|
|
||||||
if (!this.props.isPersonalReview) {
|
|
||||||
url += "/admin";
|
|
||||||
}
|
}
|
||||||
|
})
|
||||||
|
.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 => {
|
const title = (
|
||||||
if (res.status === 200) {
|
<div>
|
||||||
notification["success"]({
|
{review.username}
|
||||||
message: 'Done!',
|
{isEditable && (
|
||||||
description:
|
<EditReview
|
||||||
'The review has been deleted successfully.',
|
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);
|
return (
|
||||||
}
|
<div>
|
||||||
}).catch((error) => {
|
<List.Item.Meta
|
||||||
handleApiError(error,"We were unable to delete the review..");
|
avatar={
|
||||||
});
|
<Avatar
|
||||||
|
style={{ backgroundColor: color, verticalAlign: 'middle' }}
|
||||||
};
|
size="large"
|
||||||
|
>
|
||||||
render() {
|
{avatarLetter}
|
||||||
const {isEditable, isDeletable, uuid} = this.props;
|
</Avatar>
|
||||||
const {color, review} = this.state;
|
}
|
||||||
const {content, rating, username} = review;
|
title={title}
|
||||||
const avatarLetter = username.charAt(0).toUpperCase();
|
description={body}
|
||||||
const body = (
|
/>
|
||||||
<div style={{marginTop: -5}}>
|
</div>
|
||||||
<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>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default withConfigContext(SingleReview);
|
export default withConfigContext(SingleReview);
|
||||||
|
|||||||
@ -24,4 +24,4 @@
|
|||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
font-size: 0.8em;
|
font-size: 0.8em;
|
||||||
padding-left: 5px;
|
padding-left: 5px;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -16,166 +16,183 @@
|
|||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from "react";
|
import React from 'react';
|
||||||
import {Drawer, Button, Icon, Row, Col, Typography, Divider, Input, Spin, notification} from 'antd';
|
import {
|
||||||
import StarRatings from "react-star-ratings";
|
Drawer,
|
||||||
import axios from "axios";
|
Button,
|
||||||
import "./EditReview.css";
|
Row,
|
||||||
import {withConfigContext} from "../../../../../../context/ConfigContext";
|
Col,
|
||||||
import {handleApiError} from "../../../../../../js/Utils";
|
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 { Title } = Typography;
|
||||||
const {TextArea} = Input;
|
const { TextArea } = Input;
|
||||||
|
|
||||||
class EditReview extends React.Component {
|
class EditReview extends React.Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
visible: false,
|
||||||
|
content: '',
|
||||||
|
rating: 0,
|
||||||
|
loading: false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
constructor(props) {
|
componentDidMount() {
|
||||||
super(props);
|
const { content, rating } = this.props.review;
|
||||||
this.state = {
|
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,
|
visible: false,
|
||||||
content: '',
|
});
|
||||||
rating: 0,
|
notification.success({
|
||||||
loading: false
|
message: 'Done!',
|
||||||
};
|
description: 'Your review has been update successfully.',
|
||||||
}
|
});
|
||||||
|
|
||||||
componentDidMount() {
|
this.props.updateCallback(res.data.data);
|
||||||
const {content,rating,id} = this.props.review;
|
} else {
|
||||||
this.setState({
|
this.setState({
|
||||||
content,
|
loading: false,
|
||||||
rating
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
showDrawer = () => {
|
|
||||||
this.setState({
|
|
||||||
visible: true,
|
|
||||||
loading: false
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
onClose = () => {
|
|
||||||
this.setState({
|
|
||||||
visible: false,
|
visible: false,
|
||||||
|
});
|
||||||
});
|
notification.error({
|
||||||
};
|
message: 'There was a problem',
|
||||||
|
duration: 0,
|
||||||
changeRating = (newRating, name) => {
|
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({
|
this.setState({
|
||||||
rating: newRating
|
loading: false,
|
||||||
|
visible: false,
|
||||||
});
|
});
|
||||||
};
|
});
|
||||||
|
};
|
||||||
|
|
||||||
onChange = (e) => {
|
render() {
|
||||||
this.setState({content: e.target.value})
|
return (
|
||||||
};
|
<span>
|
||||||
|
<span className="edit-button" onClick={this.showDrawer}>
|
||||||
onSubmit = () => {
|
edit
|
||||||
const config = this.props.context;
|
</span>
|
||||||
const {content, rating} = this.state;
|
<Drawer
|
||||||
const {id} = this.props.review;
|
// title="Basic Drawer"
|
||||||
const {uuid} = this.props;
|
placement="bottom"
|
||||||
this.setState({
|
closable={false}
|
||||||
loading: true
|
onClose={this.onClose}
|
||||||
});
|
visible={this.state.visible}
|
||||||
|
height={400}
|
||||||
const payload = {
|
>
|
||||||
content: content,
|
<Spin spinning={this.state.loading} tip="Posting your review...">
|
||||||
rating: rating
|
<Row>
|
||||||
};
|
<Col lg={8} />
|
||||||
|
<Col lg={8}>
|
||||||
axios.put(
|
<Title level={4}>Edit review</Title>
|
||||||
window.location.origin+ config.serverConfig.invoker.uri + config.serverConfig.invoker.store + "/reviews/" + uuid+"/"+id,
|
<Divider />
|
||||||
payload,
|
<TextArea
|
||||||
).then(res => {
|
placeholder="Tell others what you think about this app. Would you recommend it, and why?"
|
||||||
if (res.status === 200) {
|
onChange={this.onChange}
|
||||||
this.setState({
|
rows={6}
|
||||||
loading: false,
|
value={this.state.content || ''}
|
||||||
visible: false
|
style={{ marginBottom: 20 }}
|
||||||
});
|
/>
|
||||||
notification["success"]({
|
<StarRatings
|
||||||
message: 'Done!',
|
rating={this.state.rating}
|
||||||
description:
|
changeRating={this.changeRating}
|
||||||
'Your review has been update successfully.',
|
starRatedColor="#777"
|
||||||
});
|
starHoverColor="#444"
|
||||||
|
starDimension="20px"
|
||||||
this.props.updateCallback(res.data.data);
|
starSpacing="2px"
|
||||||
} else {
|
numberOfStars={5}
|
||||||
this.setState({
|
name="rating"
|
||||||
loading: false,
|
/>
|
||||||
visible: false
|
<br />
|
||||||
});
|
<br />
|
||||||
notification["error"]({
|
<Button onClick={this.onClose} style={{ marginRight: 8 }}>
|
||||||
message: "There was a problem",
|
Cancel
|
||||||
duration: 0,
|
</Button>
|
||||||
description:
|
<Button
|
||||||
"We are unable to update your review right now.",
|
disabled={this.state.rating === 0}
|
||||||
});
|
onClick={this.onSubmit}
|
||||||
}
|
type="primary"
|
||||||
|
|
||||||
}).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}
|
|
||||||
>
|
>
|
||||||
<Spin spinning={this.state.loading} tip="Posting your review...">
|
Submit
|
||||||
<Row>
|
</Button>
|
||||||
<Col lg={8}/>
|
</Col>
|
||||||
<Col lg={8}>
|
</Row>
|
||||||
<Title level={4}>Edit review</Title>
|
</Spin>
|
||||||
<Divider/>
|
</Drawer>
|
||||||
<TextArea
|
</span>
|
||||||
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>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default withConfigContext(EditReview);
|
export default withConfigContext(EditReview);
|
||||||
|
|||||||
@ -16,19 +16,19 @@
|
|||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from "react";
|
import React from 'react';
|
||||||
|
|
||||||
const ConfigContext = React.createContext();
|
const ConfigContext = React.createContext();
|
||||||
|
|
||||||
export const withConfigContext = Component => {
|
export const withConfigContext = Component => {
|
||||||
return props => (
|
// eslint-disable-next-line react/display-name
|
||||||
<ConfigContext.Consumer>
|
return props => (
|
||||||
{context => {
|
<ConfigContext.Consumer>
|
||||||
return <Component {...props} context={context}/>;
|
{context => {
|
||||||
}}
|
return <Component {...props} context={context} />;
|
||||||
</ConfigContext.Consumer>
|
}}
|
||||||
);
|
</ConfigContext.Consumer>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default ConfigContext;
|
export default ConfigContext;
|
||||||
|
|
||||||
|
|||||||
@ -42,4 +42,4 @@
|
|||||||
|
|
||||||
.ant-input-affix-wrapper .ant-input{
|
.ant-input-affix-wrapper .ant-input{
|
||||||
min-height: 0;
|
min-height: 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -19,42 +19,39 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import ReactDOM from 'react-dom';
|
import ReactDOM from 'react-dom';
|
||||||
import * as serviceWorker from './serviceWorker';
|
import * as serviceWorker from './serviceWorker';
|
||||||
import App from "./App";
|
import App from './App';
|
||||||
import Login from "./pages/Login";
|
import Login from './pages/Login';
|
||||||
import Dashboard from "./pages/dashboard/Dashboard";
|
import Dashboard from './pages/dashboard/Dashboard';
|
||||||
import Apps from "./pages/dashboard/apps/Apps";
|
import Apps from './pages/dashboard/apps/Apps';
|
||||||
import Release from "./pages/dashboard/apps/release/Release";
|
import Release from './pages/dashboard/apps/release/Release';
|
||||||
import './index.css';
|
import './index.css';
|
||||||
|
|
||||||
const routes = [
|
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,
|
exact: true,
|
||||||
component: Login
|
},
|
||||||
},
|
{
|
||||||
{
|
path: '/store/:deviceType/apps/:uuid',
|
||||||
path: '/store',
|
exact: true,
|
||||||
exact: false,
|
component: Release,
|
||||||
component: Dashboard,
|
},
|
||||||
routes: [
|
],
|
||||||
{
|
},
|
||||||
path: '/store/:deviceType',
|
|
||||||
component: Apps,
|
|
||||||
exact: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
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
|
// If you want your app e and load faster, you can change
|
||||||
// unregister() to register() below. Note this comes with some pitfalls.
|
// unregister() to register() below. Note this comes with some pitfalls.
|
||||||
|
|||||||
@ -16,18 +16,29 @@
|
|||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {notification} from "antd";
|
import { notification } from 'antd';
|
||||||
|
|
||||||
export const handleApiError = (error, message, isForbiddenMessageSilent = false) => {
|
export const handleApiError = (
|
||||||
if (error.hasOwnProperty("response") && error.response.status === 401) {
|
error,
|
||||||
const redirectUrl = encodeURI(window.location.href);
|
message,
|
||||||
window.location.href = window.location.origin + `/store/login?redirect=${redirectUrl}`;
|
isForbiddenMessageSilent = false,
|
||||||
// silence 403 forbidden message
|
) => {
|
||||||
} else if (!(isForbiddenMessageSilent && error.hasOwnProperty("response") && error.response.status === 403)) {
|
if (error.hasOwnProperty('response') && error.response.status === 401) {
|
||||||
notification["error"]({
|
const redirectUrl = encodeURI(window.location.href);
|
||||||
message: "There was a problem",
|
window.location.href =
|
||||||
duration: 10,
|
window.location.origin + `/store/login?redirect=${redirectUrl}`;
|
||||||
description: message,
|
// silence 403 forbidden message
|
||||||
});
|
} else if (
|
||||||
}
|
!(
|
||||||
|
isForbiddenMessageSilent &&
|
||||||
|
error.hasOwnProperty('response') &&
|
||||||
|
error.response.status === 403
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
notification.error({
|
||||||
|
message: 'There was a problem',
|
||||||
|
duration: 10,
|
||||||
|
description: message,
|
||||||
|
});
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@ -59,4 +59,4 @@
|
|||||||
.login .content {
|
.login .content {
|
||||||
position: relative;
|
position: relative;
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -16,172 +16,193 @@
|
|||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from "react";
|
import React from 'react';
|
||||||
import {Typography, Row, Col, Form, Icon, Input, Button, Checkbox, notification} from 'antd';
|
import {
|
||||||
|
Typography,
|
||||||
|
Row,
|
||||||
|
Col,
|
||||||
|
Form,
|
||||||
|
Icon,
|
||||||
|
Input,
|
||||||
|
Button,
|
||||||
|
Checkbox,
|
||||||
|
notification,
|
||||||
|
} from 'antd';
|
||||||
import './Login.css';
|
import './Login.css';
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
import {withConfigContext} from "../context/ConfigContext";
|
import { withConfigContext } from '../context/ConfigContext';
|
||||||
import {handleApiError} from "../js/Utils";
|
|
||||||
|
|
||||||
const {Title} = Typography;
|
const { Title } = Typography;
|
||||||
const {Text} = Typography;
|
const { Text } = Typography;
|
||||||
|
|
||||||
class Login extends React.Component {
|
class Login extends React.Component {
|
||||||
render() {
|
render() {
|
||||||
const config = this.props.context;
|
const config = this.props.context;
|
||||||
return (
|
return (
|
||||||
<div className="login">
|
<div className="login">
|
||||||
<div className="background">
|
<div className="background"></div>
|
||||||
</div>
|
<div className="content">
|
||||||
<div className="content">
|
<Row>
|
||||||
<Row>
|
<Col xs={3} sm={3} md={10}></Col>
|
||||||
<Col xs={3} sm={3} md={10}>
|
<Col xs={18} sm={18} md={4}>
|
||||||
|
<Row style={{ marginBottom: 20 }}>
|
||||||
</Col>
|
<Col style={{ textAlign: 'center' }}>
|
||||||
<Col xs={18} sm={18} md={4}>
|
<img
|
||||||
<Row style={{marginBottom: 20}}>
|
style={{
|
||||||
<Col style={{textAlign: "center"}}>
|
marginTop: 36,
|
||||||
<img style={
|
height: 60,
|
||||||
{
|
}}
|
||||||
marginTop: 36,
|
src={config.theme.logo}
|
||||||
height: 60
|
/>
|
||||||
}
|
</Col>
|
||||||
}
|
</Row>
|
||||||
src={config.theme.logo}/>
|
<Title level={2}>Login</Title>
|
||||||
</Col>
|
<WrappedNormalLoginForm />
|
||||||
</Row>
|
</Col>
|
||||||
<Title level={2}>Login</Title>
|
</Row>
|
||||||
<WrappedNormalLoginForm/>
|
<Row>
|
||||||
|
<Col span={4} offset={10}></Col>
|
||||||
</Col>
|
</Row>
|
||||||
</Row>
|
</div>
|
||||||
<Row>
|
</div>
|
||||||
<Col span={4} offset={10}>
|
);
|
||||||
|
}
|
||||||
</Col>
|
|
||||||
</Row>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class NormalLoginForm extends React.Component {
|
class NormalLoginForm extends React.Component {
|
||||||
|
constructor(props) {
|
||||||
constructor(props) {
|
super(props);
|
||||||
super(props);
|
this.state = {
|
||||||
this.state = {
|
inValid: false,
|
||||||
inValid: false,
|
loading: false,
|
||||||
loading: false
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
handleSubmit = (e) => {
|
|
||||||
const 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
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
handleSubmit = e => {
|
||||||
const {getFieldDecorator} = this.props.form;
|
const thisForm = this;
|
||||||
let errorMsg = "";
|
const config = this.props.context;
|
||||||
if (this.state.inValid) {
|
|
||||||
errorMsg = <Text type="danger">Invalid Login Details</Text>;
|
e.preventDefault();
|
||||||
}
|
this.props.form.validateFields((err, values) => {
|
||||||
let loading = "";
|
thisForm.setState({
|
||||||
if (this.state.loading) {
|
inValid: false,
|
||||||
loading = <Text type="secondary">Loading..</Text>;
|
});
|
||||||
}
|
if (!err) {
|
||||||
return (
|
thisForm.setState({
|
||||||
<Form onSubmit={this.handleSubmit} className="login-form">
|
loading: true,
|
||||||
<Form.Item>
|
});
|
||||||
{getFieldDecorator('username', {
|
const parameters = {
|
||||||
rules: [{required: true, message: 'Please input your username!'}],
|
username: values.username,
|
||||||
})(
|
password: values.password,
|
||||||
<Input name="username" style={{height: 32}}
|
platform: 'store',
|
||||||
prefix={<Icon type="user" style={{color: 'rgba(0,0,0,.25)'}}/>}
|
};
|
||||||
placeholder="Username"/>
|
|
||||||
)}
|
const request = Object.keys(parameters)
|
||||||
</Form.Item>
|
.map(key => key + '=' + parameters[key])
|
||||||
<Form.Item>
|
.join('&');
|
||||||
{getFieldDecorator('password', {
|
|
||||||
rules: [{required: true, message: 'Please input your Password!'}],
|
axios
|
||||||
})(
|
.post(window.location.origin + config.serverConfig.loginUri, request)
|
||||||
<Input name="password" style={{height: 32}}
|
.then(res => {
|
||||||
prefix={<Icon type="lock" style={{color: 'rgba(0,0,0,.25)'}}/>} type="password"
|
if (res.status === 200) {
|
||||||
placeholder="Password"/>
|
let redirectUrl = window.location.origin + '/store';
|
||||||
)}
|
const searchParams = new URLSearchParams(window.location.search);
|
||||||
</Form.Item>
|
if (searchParams.has('redirect')) {
|
||||||
{loading}
|
redirectUrl = searchParams.get('redirect');
|
||||||
{errorMsg}
|
}
|
||||||
<Form.Item>
|
window.location = redirectUrl;
|
||||||
{getFieldDecorator('remember', {
|
}
|
||||||
valuePropName: 'checked',
|
})
|
||||||
initialValue: true,
|
.catch(function(error) {
|
||||||
})(
|
if (
|
||||||
<Checkbox>Remember me</Checkbox>
|
error.hasOwnProperty('response') &&
|
||||||
)}
|
error.response.status === 401
|
||||||
<br/>
|
) {
|
||||||
<a className="login-form-forgot" href="">Forgot password</a>
|
thisForm.setState({
|
||||||
<Button loading={this.state.loading} block type="primary" htmlType="submit"
|
loading: false,
|
||||||
className="login-form-button">
|
inValid: true,
|
||||||
Log in
|
});
|
||||||
</Button>
|
} else {
|
||||||
</Form.Item>
|
notification.error({
|
||||||
</Form>
|
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);
|
export default withConfigContext(Login);
|
||||||
|
|||||||
@ -87,4 +87,4 @@
|
|||||||
margin-top: 10%;
|
margin-top: 10%;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -16,225 +16,261 @@
|
|||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from "react";
|
import React from 'react';
|
||||||
import {Layout, Menu, Icon, Drawer, Button, Alert} from 'antd';
|
import { Layout, Menu, Icon, Drawer, Button, Alert } from 'antd';
|
||||||
|
|
||||||
const {Header, Content, Footer} = Layout;
|
const { Header, Content, Footer } = Layout;
|
||||||
import {Link} from "react-router-dom";
|
import { Link } from 'react-router-dom';
|
||||||
import RouteWithSubRoutes from "../../components/RouteWithSubRoutes";
|
import RouteWithSubRoutes from '../../components/RouteWithSubRoutes';
|
||||||
import {Switch} from 'react-router';
|
import { Switch } from 'react-router';
|
||||||
import axios from "axios";
|
import axios from 'axios';
|
||||||
import "./Dashboard.css";
|
import './Dashboard.css';
|
||||||
import {withConfigContext} from "../../context/ConfigContext";
|
import { withConfigContext } from '../../context/ConfigContext';
|
||||||
import Logout from "./logout/Logout";
|
import Logout from './logout/Logout';
|
||||||
import {handleApiError} from "../../js/Utils";
|
import { handleApiError } from '../../js/Utils';
|
||||||
|
|
||||||
const {SubMenu} = Menu;
|
const { SubMenu } = Menu;
|
||||||
|
|
||||||
class Dashboard extends React.Component {
|
class Dashboard extends React.Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
this.state = {
|
this.state = {
|
||||||
routes: props.routes,
|
routes: props.routes,
|
||||||
selectedKeys: [],
|
selectedKeys: [],
|
||||||
deviceTypes: [],
|
deviceTypes: [],
|
||||||
visible: false,
|
visible: false,
|
||||||
collapsed: false,
|
collapsed: false,
|
||||||
forbiddenErrors: {
|
forbiddenErrors: {
|
||||||
deviceTypes: false
|
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
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
this.logo = this.props.context.theme.logo;
|
||||||
|
this.footerText = this.props.context.theme.footerText;
|
||||||
|
this.config = this.props.context;
|
||||||
|
}
|
||||||
|
|
||||||
changeSelectedMenuItem = (key) => {
|
componentDidMount() {
|
||||||
this.setState({
|
this.getDeviceTypes();
|
||||||
selectedKeys: [key]
|
}
|
||||||
})
|
|
||||||
};
|
|
||||||
|
|
||||||
showMobileNavigationBar = () => {
|
getDeviceTypes = () => {
|
||||||
this.setState({
|
const config = this.props.context;
|
||||||
visible: true,
|
axios
|
||||||
collapsed: !this.state.collapsed,
|
.get(
|
||||||
});
|
window.location.origin +
|
||||||
};
|
config.serverConfig.invoker.uri +
|
||||||
|
config.serverConfig.invoker.deviceMgt +
|
||||||
onCloseMobileNavigationBar = () => {
|
'/device-types',
|
||||||
this.setState({
|
)
|
||||||
visible: false,
|
.then(res => {
|
||||||
});
|
if (res.status === 200) {
|
||||||
};
|
const deviceTypes = JSON.parse(res.data.data);
|
||||||
|
this.setState({
|
||||||
render() {
|
deviceTypes,
|
||||||
const config = this.props.context;
|
loading: false,
|
||||||
const {selectedKeys, deviceTypes, forbiddenErrors} = this.state;
|
});
|
||||||
|
}
|
||||||
const DeviceTypesData = deviceTypes.map((deviceType) => {
|
})
|
||||||
const platform = deviceType.name;
|
.catch(error => {
|
||||||
const defaultPlatformIcons = config.defaultPlatformIcons;
|
handleApiError(
|
||||||
let icon = defaultPlatformIcons.default.icon;
|
error,
|
||||||
let theme = defaultPlatformIcons.default.theme;
|
'Error occurred while trying to load device types.',
|
||||||
if (defaultPlatformIcons.hasOwnProperty(platform)) {
|
true,
|
||||||
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>
|
|
||||||
);
|
);
|
||||||
}
|
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);
|
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.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from "react";
|
import React from 'react';
|
||||||
import AppList from "../../../components/apps/AppList";
|
import AppList from '../../../components/apps/AppList';
|
||||||
|
|
||||||
class Apps extends React.Component {
|
class Apps extends React.Component {
|
||||||
routes;
|
routes;
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
this.routes = props.routes;
|
this.routes = props.routes;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
render() {
|
||||||
|
const { deviceType } = this.props.match.params;
|
||||||
|
return (
|
||||||
render() {
|
<div>
|
||||||
const {deviceType} = this.props.match.params;
|
<div style={{ background: '#f0f2f5', padding: 24, minHeight: 760 }}>
|
||||||
return (
|
{deviceType !== null && (
|
||||||
<div>
|
<AppList
|
||||||
<div style={{background: '#f0f2f5', padding: 24, minHeight: 760}}>
|
changeSelectedMenuItem={this.props.changeSelectedMenuItem}
|
||||||
{deviceType!==null && <AppList changeSelectedMenuItem={this.props.changeSelectedMenuItem} deviceType={deviceType}/>}
|
deviceType={deviceType}
|
||||||
</div>
|
/>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Apps;
|
export default Apps;
|
||||||
|
|||||||
@ -16,118 +16,131 @@
|
|||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from "react";
|
import React from 'react';
|
||||||
import '../../../../App.css';
|
import '../../../../App.css';
|
||||||
import {Skeleton, Typography, Row, Col, Card, message, notification, Breadcrumb, Icon} from "antd";
|
import { Skeleton, Typography, Row, Col, Card, Breadcrumb, Icon } from 'antd';
|
||||||
import ReleaseView from "../../../../components/apps/release/ReleaseView";
|
import ReleaseView from '../../../../components/apps/release/ReleaseView';
|
||||||
import axios from "axios";
|
import axios from 'axios';
|
||||||
import {withConfigContext} from "../../../../context/ConfigContext";
|
import { withConfigContext } from '../../../../context/ConfigContext';
|
||||||
import {Link} from "react-router-dom";
|
import { Link } from 'react-router-dom';
|
||||||
import {handleApiError} from "../../../../js/Utils";
|
import { handleApiError } from '../../../../js/Utils';
|
||||||
|
|
||||||
const {Title} = Typography;
|
const { Title } = Typography;
|
||||||
|
|
||||||
class Release extends React.Component {
|
class Release extends React.Component {
|
||||||
routes;
|
routes;
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
this.routes = props.routes;
|
this.routes = props.routes;
|
||||||
this.state = {
|
this.state = {
|
||||||
loading: true,
|
loading: true,
|
||||||
app: null,
|
app: null,
|
||||||
uuid: null,
|
uuid: null,
|
||||||
forbiddenErrors: {
|
forbiddenErrors: {
|
||||||
app: false
|
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
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
componentDidMount() {
|
||||||
const {app, loading} = this.state;
|
const { uuid, deviceType } = this.props.match.params;
|
||||||
const {deviceType} = this.props.match.params;
|
this.fetchData(uuid);
|
||||||
|
this.props.changeSelectedMenuItem(deviceType);
|
||||||
|
}
|
||||||
|
|
||||||
let content = <Title level={3}>No Releases Found</Title>;
|
componentDidUpdate(prevProps, prevState, snapshot) {
|
||||||
let appName = "loading...";
|
if (prevState.uuid !== this.state.uuid) {
|
||||||
|
const { uuid, deviceType } = this.props.match.params;
|
||||||
if (app != null && app.applicationReleases.length !== 0) {
|
this.fetchData(uuid);
|
||||||
content = <ReleaseView app={app} deviceType={deviceType}/>;
|
this.props.changeSelectedMenuItem(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>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
export default withConfigContext(Release);
|
||||||
|
|||||||
@ -16,54 +16,60 @@
|
|||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from "react";
|
import React from 'react';
|
||||||
import {notification, Menu, Icon} from 'antd';
|
import { Menu, Icon } from 'antd';
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
import {withConfigContext} from "../../../context/ConfigContext";
|
import { withConfigContext } from '../../../context/ConfigContext';
|
||||||
import {handleApiError} from "../../../js/Utils";
|
import { handleApiError } from '../../../js/Utils';
|
||||||
|
|
||||||
/*
|
/*
|
||||||
This class for call the logout api by sending request
|
This class for call the logout api by sending request
|
||||||
*/
|
*/
|
||||||
class Logout extends React.Component {
|
class Logout extends React.Component {
|
||||||
|
constructor(props) {
|
||||||
constructor(props) {
|
super(props);
|
||||||
super(props);
|
this.state = {
|
||||||
this.state = {
|
inValid: false,
|
||||||
inValid: false,
|
loading: false,
|
||||||
loading: false
|
};
|
||||||
};
|
}
|
||||||
}
|
/*
|
||||||
/*
|
|
||||||
This function call the logout api when the request is success
|
This function call the logout api when the request is success
|
||||||
*/
|
*/
|
||||||
handleSubmit = () => {
|
handleSubmit = () => {
|
||||||
|
const thisForm = this;
|
||||||
|
const config = this.props.context;
|
||||||
|
|
||||||
const thisForm = this;
|
thisForm.setState({
|
||||||
const config = this.props.context;
|
inValid: false,
|
||||||
|
});
|
||||||
|
|
||||||
thisForm.setState({
|
axios
|
||||||
inValid: false
|
.post(window.location.origin + config.serverConfig.logoutUri)
|
||||||
});
|
.then(res => {
|
||||||
|
// if the api call status is correct then user will logout and then it goes to login page
|
||||||
axios.post(window.location.origin + config.serverConfig.logoutUri
|
if (res.status === 200) {
|
||||||
).then(res => {
|
window.location = window.location.origin + '/store/login';
|
||||||
//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(
|
||||||
}).catch(function (error) {
|
error,
|
||||||
handleApiError(error,"Error occurred while trying to get your review.");
|
'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>
|
|
||||||
);
|
);
|
||||||
}
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<Menu>
|
||||||
|
<Menu.Item key="1" onClick={this.handleSubmit}>
|
||||||
|
<Icon type="logout" />
|
||||||
|
Logout
|
||||||
|
</Menu.Item>
|
||||||
|
</Menu>
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default withConfigContext(Logout);
|
export default withConfigContext(Logout);
|
||||||
|
|||||||
@ -34,8 +34,8 @@ const isLocalhost = Boolean(
|
|||||||
window.location.hostname === '[::1]' ||
|
window.location.hostname === '[::1]' ||
|
||||||
// 127.0.0.1/8 is considered localhost for IPv4.
|
// 127.0.0.1/8 is considered localhost for IPv4.
|
||||||
window.location.hostname.match(
|
window.location.hostname.match(
|
||||||
/^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
|
/^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/,
|
||||||
)
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
export function register(config) {
|
export function register(config) {
|
||||||
@ -61,7 +61,7 @@ export function register(config) {
|
|||||||
navigator.serviceWorker.ready.then(() => {
|
navigator.serviceWorker.ready.then(() => {
|
||||||
console.log(
|
console.log(
|
||||||
'This web app is being served cache-first by a service ' +
|
'This web app is being served cache-first by a service ' +
|
||||||
'worker. To learn more, visit https://bit.ly/CRA-PWA'
|
'worker. To learn more, visit https://bit.ly/CRA-PWA',
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
@ -89,7 +89,7 @@ function registerValidSW(swUrl, config) {
|
|||||||
// content until all client tabs are closed.
|
// content until all client tabs are closed.
|
||||||
console.log(
|
console.log(
|
||||||
'New content is available and will be used when all ' +
|
'New content is available and will be used when all ' +
|
||||||
'tabs for this page are closed. See https://bit.ly/CRA-PWA.'
|
'tabs for this page are closed. See https://bit.ly/CRA-PWA.',
|
||||||
);
|
);
|
||||||
|
|
||||||
// Execute callback
|
// Execute callback
|
||||||
@ -139,7 +139,7 @@ function checkValidServiceWorker(swUrl, config) {
|
|||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
console.log(
|
console.log(
|
||||||
'No internet connection found. App is running in offline mode.'
|
'No internet connection found. App is running in offline mode.',
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@ -16,119 +16,119 @@
|
|||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
var path = require('path');
|
var path = require('path');
|
||||||
const HtmlWebPackPlugin = require("html-webpack-plugin");
|
const HtmlWebPackPlugin = require('html-webpack-plugin');
|
||||||
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
|
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
|
||||||
const configurations = require("./public/conf/config.json");
|
const configurations = require('./public/conf/config.json');
|
||||||
|
|
||||||
const config = {
|
const config = {
|
||||||
devtool: "source-map",
|
devtool: 'source-map',
|
||||||
output: {
|
output: {
|
||||||
publicPath: '/store/'
|
publicPath: '/store/',
|
||||||
|
},
|
||||||
|
watch: false,
|
||||||
|
resolve: {
|
||||||
|
alias: {
|
||||||
|
AppData: path.resolve(__dirname, 'source/src/app/common/'),
|
||||||
|
AppComponents: path.resolve(__dirname, 'source/src/app/components/'),
|
||||||
},
|
},
|
||||||
watch: false,
|
extensions: ['.jsx', '.js', '.ttf', '.woff', '.woff2', '.svg'],
|
||||||
resolve: {
|
},
|
||||||
alias: {
|
module: {
|
||||||
AppData: path.resolve(__dirname, 'source/src/app/common/'),
|
rules: [
|
||||||
AppComponents: path.resolve(__dirname, 'source/src/app/components/')
|
{
|
||||||
},
|
test: /\.(js|jsx)$/,
|
||||||
extensions: ['.jsx', '.js', '.ttf', '.woff', '.woff2', '.svg']
|
exclude: /node_modules/,
|
||||||
},
|
use: [
|
||||||
module: {
|
{
|
||||||
rules: [
|
loader: 'babel-loader',
|
||||||
{
|
},
|
||||||
test: /\.(js|jsx)$/,
|
],
|
||||||
exclude: /node_modules/,
|
},
|
||||||
use: [
|
{
|
||||||
{
|
test: /\.html$/,
|
||||||
loader: 'babel-loader'
|
use: [
|
||||||
}
|
{
|
||||||
]
|
loader: 'html-loader',
|
||||||
|
options: { minimize: true },
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
test: /\.css$/,
|
||||||
|
use: [MiniCssExtractPlugin.loader, 'css-loader'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
test: /\.scss$/,
|
||||||
|
use: [
|
||||||
|
MiniCssExtractPlugin.loader,
|
||||||
|
'css-loader',
|
||||||
|
'postcss-loader',
|
||||||
|
'sass-loader',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
test: /\.scss$/,
|
||||||
|
use: ['style-loader', 'scss-loader'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
test: /\.less$/,
|
||||||
|
use: [
|
||||||
|
{
|
||||||
|
loader: 'style-loader',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
loader: 'css-loader',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
loader: 'less-loader',
|
||||||
|
options: {
|
||||||
|
modifyVars: {
|
||||||
|
'primary-color': configurations.theme.primaryColor,
|
||||||
|
'link-color': configurations.theme.primaryColor,
|
||||||
|
},
|
||||||
|
javascriptEnabled: true,
|
||||||
},
|
},
|
||||||
{
|
},
|
||||||
test: /\.html$/,
|
],
|
||||||
use: [
|
},
|
||||||
{
|
{
|
||||||
loader: "html-loader",
|
test: /\.(woff|woff2|eot|ttf|svg)$/,
|
||||||
options: { minimize: true }
|
loader: 'url-loader?limit=100000',
|
||||||
}
|
},
|
||||||
]
|
{
|
||||||
|
test: /\.(png|jpe?g)/i,
|
||||||
|
use: [
|
||||||
|
{
|
||||||
|
loader: 'url-loader',
|
||||||
|
options: {
|
||||||
|
name: './img/[name].[ext]',
|
||||||
|
limit: 10000,
|
||||||
},
|
},
|
||||||
{
|
},
|
||||||
test: /\.css$/,
|
{
|
||||||
use: [MiniCssExtractPlugin.loader, "css-loader"]
|
loader: 'img-loader',
|
||||||
},
|
},
|
||||||
{
|
],
|
||||||
test: /\.scss$/,
|
},
|
||||||
use: [
|
|
||||||
MiniCssExtractPlugin.loader,
|
|
||||||
"css-loader",
|
|
||||||
"postcss-loader",
|
|
||||||
"sass-loader"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
test: /\.scss$/,
|
|
||||||
use: [ 'style-loader', 'scss-loader' ]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
test: /\.less$/,
|
|
||||||
use: [
|
|
||||||
{
|
|
||||||
loader: "style-loader"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
loader: "css-loader",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
loader: "less-loader",
|
|
||||||
options: {
|
|
||||||
modifyVars: {
|
|
||||||
'primary-color': configurations.theme.primaryColor,
|
|
||||||
'link-color': configurations.theme.primaryColor,
|
|
||||||
},
|
|
||||||
javascriptEnabled: true,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
test: /\.(woff|woff2|eot|ttf|svg)$/,
|
|
||||||
loader: 'url-loader?limit=100000',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
test: /\.(png|jpe?g)/i,
|
|
||||||
use: [
|
|
||||||
{
|
|
||||||
loader: "url-loader",
|
|
||||||
options: {
|
|
||||||
name: "./img/[name].[ext]",
|
|
||||||
limit: 10000
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
loader: "img-loader"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
plugins: [
|
|
||||||
new HtmlWebPackPlugin({
|
|
||||||
template: "./src/index.html",
|
|
||||||
filename: "./index.html"
|
|
||||||
}),
|
|
||||||
new MiniCssExtractPlugin({
|
|
||||||
filename: "[name].css",
|
|
||||||
chunkFilename: "[id].css"
|
|
||||||
})
|
|
||||||
],
|
],
|
||||||
externals: {
|
},
|
||||||
'Config': JSON.stringify(require('./public/conf/config.json'))
|
plugins: [
|
||||||
}
|
new HtmlWebPackPlugin({
|
||||||
|
template: './src/index.html',
|
||||||
|
filename: './index.html',
|
||||||
|
}),
|
||||||
|
new MiniCssExtractPlugin({
|
||||||
|
filename: '[name].css',
|
||||||
|
chunkFilename: '[id].css',
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
externals: {
|
||||||
|
Config: JSON.stringify(require('./public/conf/config.json')),
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
if (process.env.NODE_ENV === "development") {
|
if (process.env.NODE_ENV === 'development') {
|
||||||
config.watch = true;
|
config.watch = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = config;
|
module.exports = config;
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user