Fix ESLint errors in APPM Store react app

This commit is contained in:
Jayasanka 2020-01-01 16:47:24 +05:30
parent fae659c32c
commit d4d7089800
52 changed files with 3995 additions and 4378 deletions

View File

@ -16,14 +16,13 @@
* under the License.
*/
module.exports = function (api) {
module.exports = function(api) {
api.cache(true);
const presets = [ "@babel/preset-env",
"@babel/preset-react" ];
const plugins = ["@babel/plugin-proposal-class-properties"];
const presets = ['@babel/preset-env', '@babel/preset-react'];
const plugins = ['@babel/plugin-proposal-class-properties'];
return {
presets,
plugins
plugins,
};
};
};

View File

@ -16,140 +16,147 @@
* under the License.
*/
import React from "react";
import "antd/dist/antd.less";
import RouteWithSubRoutes from "./components/RouteWithSubRoutes";
import {
BrowserRouter as Router,
Redirect, Switch,
} from 'react-router-dom';
import axios from "axios";
import {Layout, Spin, Result} from "antd";
import ConfigContext from "./context/ConfigContext";
import React from 'react';
import 'antd/dist/antd.less';
import RouteWithSubRoutes from './components/RouteWithSubRoutes';
import { BrowserRouter as Router, Redirect, Switch } from 'react-router-dom';
import axios from 'axios';
import { Layout, Spin, Result } from 'antd';
import ConfigContext from './context/ConfigContext';
const {Content} = Layout;
const { Content } = Layout;
const loadingView = (
<Layout>
<Content style={{
padding: '0 0',
paddingTop: 300,
backgroundColor: '#fff',
textAlign: 'center'
}}>
<Spin tip="Loading..."/>
</Content>
</Layout>
<Layout>
<Content
style={{
padding: '0 0',
paddingTop: 300,
backgroundColor: '#fff',
textAlign: 'center',
}}
>
<Spin tip="Loading..." />
</Content>
</Layout>
);
const errorView = (
<Result
style={{
paddingTop: 200
}}
status="500"
title="Error occurred while loading the configuration"
subTitle="Please refresh your browser window"
/>
<Result
style={{
paddingTop: 200,
}}
status="500"
title="Error occurred while loading the configuration"
subTitle="Please refresh your browser window"
/>
);
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
loading: true,
error: false,
config: {},
};
}
constructor(props) {
super(props);
this.state = {
loading: true,
error: false,
config: {}
componentDidMount() {
this.updateFavicon();
axios
.get(window.location.origin + '/store/public/conf/config.json')
.then(res => {
const config = res.data;
this.checkUserLoggedIn(config);
})
.catch(error => {
this.setState({
loading: false,
error: true,
});
});
}
updateFavicon = () => {
const link =
document.querySelector("link[rel*='icon']") ||
document.createElement('link');
link.type = 'image/x-icon';
link.rel = 'shortcut icon';
link.href =
window.location.origin +
'/devicemgt/public/uuf.unit.favicon/img/favicon.png';
document.getElementsByTagName('head')[0].appendChild(link);
};
checkUserLoggedIn = config => {
axios
.post(
window.location.origin + '/store-ui-request-handler/user',
'platform=publisher',
)
.then(res => {
config.user = res.data.data;
const pageURL = window.location.pathname;
const lastURLSegment = pageURL.substr(pageURL.lastIndexOf('/') + 1);
if (lastURLSegment === 'login') {
window.location.href = window.location.origin + '/store/';
} else {
this.setState({
loading: false,
config: config,
});
}
}
componentDidMount() {
this.updateFavicon();
axios.get(
window.location.origin + "/store/public/conf/config.json",
).then(res => {
const config = res.data;
this.checkUserLoggedIn(config);
}).catch((error) => {
})
.catch(error => {
if (error.hasOwnProperty('response') && error.response.status === 401) {
const redirectUrl = encodeURI(window.location.href);
const pageURL = window.location.pathname;
const lastURLSegment = pageURL.substr(pageURL.lastIndexOf('/') + 1);
if (lastURLSegment !== 'login') {
window.location.href =
window.location.origin + `/store/login?redirect=${redirectUrl}`;
} else {
this.setState({
loading: false,
error: true
})
});
}
loading: false,
config: config,
});
}
} else {
this.setState({
loading: false,
error: true,
});
}
});
};
updateFavicon = () =>{
const link = document.querySelector("link[rel*='icon']") || document.createElement('link');
link.type = 'image/x-icon';
link.rel = 'shortcut icon';
link.href = window.location.origin+'/devicemgt/public/uuf.unit.favicon/img/favicon.png';
document.getElementsByTagName('head')[0].appendChild(link);
};
render() {
const { loading, error } = this.state;
checkUserLoggedIn = (config) => {
axios.post(
window.location.origin + "/store-ui-request-handler/user",
"platform=publisher"
).then(res => {
config.user = res.data.data;
const pageURL = window.location.pathname;
const lastURLSegment = pageURL.substr(pageURL.lastIndexOf('/') + 1);
if (lastURLSegment === "login") {
window.location.href = window.location.origin + `/store/`;
} else {
this.setState({
loading: false,
config: config
});
}
}).catch((error) => {
if (error.hasOwnProperty("response") && error.response.status === 401) {
const redirectUrl = encodeURI(window.location.href);
const pageURL = window.location.pathname;
const lastURLSegment = pageURL.substr(pageURL.lastIndexOf('/') + 1);
if (lastURLSegment !== "login") {
window.location.href = window.location.origin + `/store/login?redirect=${redirectUrl}`;
} else {
this.setState({
loading: false,
config: config
})
}
} else {
this.setState({
loading: false,
error: true
})
}
});
};
const applicationView = (
<Router>
<ConfigContext.Provider value={this.state.config}>
<div>
<Switch>
<Redirect exact from="/store" to="/store/android" />
{this.props.routes.map(route => (
<RouteWithSubRoutes key={route.path} {...route} />
))}
</Switch>
</div>
</ConfigContext.Provider>
</Router>
);
render() {
const {loading, error} = this.state;
const applicationView = (
<Router>
<ConfigContext.Provider value={this.state.config}>
<div>
<Switch>
<Redirect exact from="/store" to="/store/android"/>
{this.props.routes.map((route) => (
<RouteWithSubRoutes key={route.path} {...route} />
))}
</Switch>
</div>
</ConfigContext.Provider>
</Router>
);
return (
<div>
{loading && loadingView}
{!loading && !error && applicationView}
{error && errorView}
</div>
);
}
return (
<div>
{loading && loadingView}
{!loading && !error && applicationView}
{error && errorView}
</div>
);
}
}
export default App;

View File

@ -17,21 +17,28 @@
*/
import React from 'react';
import {Route} from 'react-router-dom';
class RouteWithSubRoutes extends React.Component{
props;
constructor(props){
super(props);
this.props = props;
}
render() {
return(
<Route path={this.props.path} exact={this.props.exact} render={(props) => (
<this.props.component {...props} {...this.props} routes={this.props.routes}/>
)}/>
);
}
import { Route } from 'react-router-dom';
class RouteWithSubRoutes extends React.Component {
props;
constructor(props) {
super(props);
this.props = props;
}
render() {
return (
<Route
path={this.props.path}
exact={this.props.exact}
render={props => (
<this.props.component
{...props}
{...this.props}
routes={this.props.routes}
/>
)}
/>
);
}
}
export default RouteWithSubRoutes;
export default RouteWithSubRoutes;

View File

@ -53,4 +53,4 @@
overflow: hidden;
width: 100%;
text-overflow: ellipsis;
}
}

View File

@ -16,63 +16,66 @@
* under the License.
*/
import {Card, Typography, Col, Row} from 'antd';
import React from "react";
import {Link} from "react-router-dom";
import "./AppCard.css";
import { Card, Typography, Col, Row } from 'antd';
import React from 'react';
import { Link } from 'react-router-dom';
import './AppCard.css';
import StarRatings from 'react-star-ratings';
const {Meta} = Card;
const {Text} = Typography;
const { Meta } = Card;
const { Text } = Typography;
class AppCard extends React.Component {
constructor(props) {
super(props);
}
constructor(props) {
super(props);
}
render() {
const app = this.props.app;
const release = this.props.app.applicationReleases[0];
render() {
const app = this.props.app;
const release = this.props.app.applicationReleases[0];
const description = (
<div className="appCard">
<Link to={'/store/' + app.deviceType + '/apps/' + release.uuid}>
<Row className="release">
<Col span={24} className="release-icon">
<div className="box">
<div className="content">
<img className="app-icon" src={release.iconPath} alt="icon" />
</div>
</div>
{/* <img src={release.iconPath} alt="icon"/>*/}
{/* <Avatar shape="square" size={128} src={release.iconPath} />*/}
</Col>
<Col span={24} style={{ paddingTop: 10 }}>
<Text className="app-name" strong level={4}>
{app.name}
</Text>
<br />
<Text type="secondary" level={4}>
{app.type.toLowerCase()}
</Text>
<br />
<StarRatings
rating={app.rating}
starRatedColor="#777"
starDimension="12px"
starSpacing="0"
numberOfStars={5}
name="rating"
/>
</Col>
</Row>
</Link>
</div>
);
const description = (
<div className="appCard">
<Link to={"/store/"+app.deviceType+"/apps/" + release.uuid}>
<Row className="release">
<Col span={24} className="release-icon">
<div className='box'>
<div className='content'>
<img className='app-icon' src={release.iconPath} alt="icon"/>
</div>
</div>
{/*<img src={release.iconPath} alt="icon"/>*/}
{/*<Avatar shape="square" size={128} src={release.iconPath} />*/}
</Col>
<Col span={24} style={{paddingTop:10}}>
<Text className="app-name" strong level={4}>{app.name}</Text><br/>
<Text type="secondary" level={4}>{app.type.toLowerCase()}</Text><br/>
<StarRatings
rating={app.rating}
starRatedColor="#777"
starDimension = "12px"
starSpacing = "0"
numberOfStars={5}
name='rating'
/>
</Col>
</Row>
</Link>
</div>
);
return (
<Card style={{marginTop: 16}}>
<Meta
description={description}
/>
</Card>
);
}
return (
<Card style={{ marginTop: 16 }}>
<Meta description={description} />
</Card>
);
}
}
export default AppCard;
export default AppCard;

View File

@ -16,163 +16,172 @@
* under the License.
*/
import React from "react";
import AppCard from "./AppCard";
import {Col, Row, Result} from "antd";
import axios from "axios";
import {withConfigContext} from "../../context/ConfigContext";
import {handleApiError} from "../../js/Utils";
import InfiniteScroll from "react-infinite-scroller";
import React from 'react';
import AppCard from './AppCard';
import { Col, Row, Result } from 'antd';
import axios from 'axios';
import { withConfigContext } from '../../context/ConfigContext';
import { handleApiError } from '../../js/Utils';
import InfiniteScroll from 'react-infinite-scroller';
const limit = 30;
class AppList extends React.Component {
constructor(props) {
super(props);
this.state = {
apps: [],
loading: true,
hasMore: true,
loadMore: true,
forbiddenErrors: {
apps: false
},
totalAppCount: 0
}
}
componentDidMount() {
const {deviceType} = this.props;
this.props.changeSelectedMenuItem(deviceType);
this.fetchData(0, 30, res => {
this.setState({
apps: res,
loading: false
});
});
}
componentDidUpdate(prevProps, prevState) {
if (prevProps.deviceType !== this.props.deviceType) {
const {deviceType} = this.props;
this.props.changeSelectedMenuItem(deviceType);
this.fetchData(0, 30, res => {
this.setState({
apps: res,
loading: false,
hasMore: true
});
});
}
}
fetchData = (offset, limit, callbackFunction) => {
const {deviceType} = this.props;
const config = this.props.context;
const payload = {
offset,
limit
};
if (deviceType === "web-clip") {
payload.appType = "WEB_CLIP";
} else {
payload.deviceType = deviceType;
}
this.setState({
loading: true
});
//send request to the invoker
axios.post(
window.location.origin + config.serverConfig.invoker.uri + config.serverConfig.invoker.store + "/applications/",
payload,
).then(res => {
if (res.status === 200) {
//todo remove this property check after backend improvement
let apps = (res.data.data.hasOwnProperty("applications")) ? res.data.data.applications : [];
callbackFunction(apps);
}
}).catch((error) => {
handleApiError(error, "Error occurred while trying to load apps.", true);
if (error.hasOwnProperty("response") && error.response.status === 403) {
const {forbiddenErrors} = this.state;
forbiddenErrors.apps = true;
this.setState({
forbiddenErrors,
loading: false
})
} else {
this.setState({
loading: false
});
}
});
constructor(props) {
super(props);
this.state = {
apps: [],
loading: true,
hasMore: true,
loadMore: true,
forbiddenErrors: {
apps: false,
},
totalAppCount: 0,
};
}
handleInfiniteOnLoad = (count) => {
const offset = count * limit;
let apps = this.state.apps;
componentDidMount() {
const { deviceType } = this.props;
this.props.changeSelectedMenuItem(deviceType);
this.fetchData(0, 30, res => {
this.setState({
apps: res,
loading: false,
});
});
}
componentDidUpdate(prevProps, prevState) {
if (prevProps.deviceType !== this.props.deviceType) {
const { deviceType } = this.props;
this.props.changeSelectedMenuItem(deviceType);
this.fetchData(0, 30, res => {
this.setState({
loading: true,
apps: res,
loading: false,
hasMore: true,
});
});
}
}
this.fetchData(offset, limit, res => {
if (res.length > 0) {
apps = apps.concat(res);
this.setState({
apps,
loading: false,
});
} else {
this.setState({
hasMore: false,
loading: false
});
}
});
fetchData = (offset, limit, callbackFunction) => {
const { deviceType } = this.props;
const config = this.props.context;
const payload = {
offset,
limit,
};
if (deviceType === 'web-clip') {
payload.appType = 'WEB_CLIP';
} else {
payload.deviceType = deviceType;
}
render() {
const {apps, loading, forbiddenErrors, hasMore} = this.state;
return (
<div>
<InfiniteScroll
key={this.props.deviceType}
initialLoad={false}
pageStart={0}
loadMore={this.handleInfiniteOnLoad}
hasMore={!loading && hasMore}
useWindow={true}>
<Row gutter={16}>
{(forbiddenErrors.apps) && (
<Result
status="403"
title="403"
subTitle="You don't have permission to view apps."
/>
)}
{!(forbiddenErrors.apps) && apps.length === 0 && (
<Result
status="404"
title="No apps, yet."
subTitle="No apps available, yet! When the administration uploads, apps will show up here."
/>
)}
{apps.map(app => (
<Col key={app.id} xs={12} sm={6} md={6} lg={4} xl={3}>
<AppCard key={app.id}
app={app}
/>
</Col>
))}
</Row>
</InfiniteScroll>
</div>
this.setState({
loading: true,
});
// send request to the invoker
axios
.post(
window.location.origin +
config.serverConfig.invoker.uri +
config.serverConfig.invoker.store +
'/applications/',
payload,
)
.then(res => {
if (res.status === 200) {
// todo remove this property check after backend improvement
let apps = res.data.data.hasOwnProperty('applications')
? res.data.data.applications
: [];
callbackFunction(apps);
}
})
.catch(error => {
handleApiError(
error,
'Error occurred while trying to load apps.',
true,
);
}
if (error.hasOwnProperty('response') && error.response.status === 403) {
const { forbiddenErrors } = this.state;
forbiddenErrors.apps = true;
this.setState({
forbiddenErrors,
loading: false,
});
} else {
this.setState({
loading: false,
});
}
});
};
handleInfiniteOnLoad = count => {
const offset = count * limit;
let apps = this.state.apps;
this.setState({
loading: true,
});
this.fetchData(offset, limit, res => {
if (res.length > 0) {
apps = apps.concat(res);
this.setState({
apps,
loading: false,
});
} else {
this.setState({
hasMore: false,
loading: false,
});
}
});
};
render() {
const { apps, loading, forbiddenErrors, hasMore } = this.state;
return (
<div>
<InfiniteScroll
key={this.props.deviceType}
initialLoad={false}
pageStart={0}
loadMore={this.handleInfiniteOnLoad}
hasMore={!loading && hasMore}
useWindow={true}
>
<Row gutter={16}>
{forbiddenErrors.apps && (
<Result
status="403"
title="403"
subTitle="You don't have permission to view apps."
/>
)}
{!forbiddenErrors.apps && apps.length === 0 && (
<Result
status="404"
title="No apps, yet."
subTitle="No apps available, yet! When the administration uploads, apps will show up here."
/>
)}
{apps.map(app => (
<Col key={app.id} xs={12} sm={6} md={6} lg={4} xl={3}>
<AppCard key={app.id} app={app} />
</Col>
))}
</Row>
</InfiniteScroll>
</div>
);
}
}
export default withConfigContext(AppList);
export default withConfigContext(AppList);

View File

@ -87,4 +87,4 @@
.d-rating .numeric-data .people-count{
padding-top: 6px;
}
}

View File

@ -16,87 +16,110 @@
* under the License.
*/
import React from "react";
import {Row, Typography, Icon, notification} from "antd";
import StarRatings from "react-star-ratings";
import "./DetailedRating.css";
import axios from "axios";
import {withConfigContext} from "../../../context/ConfigContext";
import {handleApiError} from "../../../js/Utils";
import React from 'react';
import { Row, Typography, Icon } from 'antd';
import StarRatings from 'react-star-ratings';
import './DetailedRating.css';
import { withConfigContext } from '../../../context/ConfigContext';
const { Text } = Typography;
class DetailedRating extends React.Component {
render() {
const { detailedRating } = this.props;
class DetailedRating extends React.Component{
render() {
const {detailedRating} = this.props;
if(detailedRating ==null){
return null;
}
const totalCount = detailedRating.noOfUsers;
const ratingVariety = detailedRating.ratingVariety;
const ratingArray = [];
for (let [key, value] of Object.entries(ratingVariety)) {
ratingArray.push(value);
}
const maximumRating = Math.max(...ratingArray);
const ratingBarPercentages = [0,0,0,0,0];
if(maximumRating>0){
for(let i = 0; i<5; i++){
ratingBarPercentages[i] = (ratingVariety[(i+1).toString()])/maximumRating*100;
}
}
return (
<Row className="d-rating">
<div className="numeric-data">
<div className="rate">{detailedRating.ratingValue.toFixed(1)}</div>
<StarRatings
rating={detailedRating.ratingValue}
starRatedColor="#777"
starDimension = "16px"
starSpacing = "2px"
numberOfStars={5}
name='rating'
/>
<br/>
<Text type="secondary" className="people-count"><Icon type="team" /> {totalCount} total</Text>
</div>
<div className="bar-containers">
<div className="bar-container">
<span className="number">5</span>
<span className="bar rate-5" style={{width: ratingBarPercentages[4]+"%"}}> </span>
</div>
<div className="bar-container">
<span className="number">4</span>
<span className="bar rate-4" style={{width: ratingBarPercentages[3]+"%"}}> </span>
</div>
<div className="bar-container">
<span className="number">3</span>
<span className="bar rate-3" style={{width: ratingBarPercentages[2]+"%"}}> </span>
</div>
<div className="bar-container">
<span className="number">2</span>
<span className="bar rate-2" style={{width: ratingBarPercentages[1]+"%"}}> </span>
</div>
<div className="bar-container">
<span className="number">1</span>
<span className="bar rate-1" style={{width: ratingBarPercentages[0]+"%"}}> </span>
</div>
</div>
</Row>
);
if (detailedRating == null) {
return null;
}
const totalCount = detailedRating.noOfUsers;
const ratingVariety = detailedRating.ratingVariety;
const ratingArray = [];
// eslint-disable-next-line no-unused-vars
for (let [key, value] of Object.entries(ratingVariety)) {
ratingArray.push(value);
}
const maximumRating = Math.max(...ratingArray);
const ratingBarPercentages = [0, 0, 0, 0, 0];
if (maximumRating > 0) {
for (let i = 0; i < 5; i++) {
ratingBarPercentages[i] =
(ratingVariety[(i + 1).toString()] / maximumRating) * 100;
}
}
return (
<Row className="d-rating">
<div className="numeric-data">
<div className="rate">{detailedRating.ratingValue.toFixed(1)}</div>
<StarRatings
rating={detailedRating.ratingValue}
starRatedColor="#777"
starDimension="16px"
starSpacing="2px"
numberOfStars={5}
name="rating"
/>
<br />
<Text type="secondary" className="people-count">
<Icon type="team" /> {totalCount} total
</Text>
</div>
<div className="bar-containers">
<div className="bar-container">
<span className="number">5</span>
<span
className="bar rate-5"
style={{ width: ratingBarPercentages[4] + '%' }}
>
{' '}
</span>
</div>
<div className="bar-container">
<span className="number">4</span>
<span
className="bar rate-4"
style={{ width: ratingBarPercentages[3] + '%' }}
>
{' '}
</span>
</div>
<div className="bar-container">
<span className="number">3</span>
<span
className="bar rate-3"
style={{ width: ratingBarPercentages[2] + '%' }}
>
{' '}
</span>
</div>
<div className="bar-container">
<span className="number">2</span>
<span
className="bar rate-2"
style={{ width: ratingBarPercentages[1] + '%' }}
>
{' '}
</span>
</div>
<div className="bar-container">
<span className="number">1</span>
<span
className="bar rate-1"
style={{ width: ratingBarPercentages[0] + '%' }}
>
{' '}
</span>
</div>
</div>
</Row>
);
}
}
export default withConfigContext(DetailedRating);
export default withConfigContext(DetailedRating);

View File

@ -16,244 +16,279 @@
* under the License.
*/
import React from "react";
import {Divider, Row, Col, Typography, Button, Dropdown, notification, Menu, Icon, Spin, Tabs, Tag} from "antd";
import "../../../App.css";
import ImgViewer from "../../apps/release/images/ImgViewer";
import StarRatings from "react-star-ratings";
import axios from "axios";
import pSBC from "shade-blend-color";
import AppInstallModal from "./install/AppInstallModal";
import AppUninstallModal from "./install/AppUninstallModal";
import {withConfigContext} from "../../../context/ConfigContext";
import {handleApiError} from "../../../js/Utils";
import ReviewContainer from "./review/ReviewContainer";
import SubscriptionDetails from "./SubscriptionDetails";
import React from 'react';
import {
Divider,
Row,
Col,
Typography,
Button,
Dropdown,
notification,
Menu,
Icon,
Tabs,
Tag,
} from 'antd';
import '../../../App.css';
import ImgViewer from '../../apps/release/images/ImgViewer';
import StarRatings from 'react-star-ratings';
import axios from 'axios';
import pSBC from 'shade-blend-color';
import AppInstallModal from './install/AppInstallModal';
import AppUninstallModal from './install/AppUninstallModal';
import { withConfigContext } from '../../../context/ConfigContext';
import { handleApiError } from '../../../js/Utils';
import ReviewContainer from './review/ReviewContainer';
import SubscriptionDetails from './SubscriptionDetails';
const {Title, Text, Paragraph} = Typography;
const {TabPane} = Tabs;
const { Title, Text, Paragraph } = Typography;
const { TabPane } = Tabs;
class ReleaseView extends React.Component {
constructor(props) {
super(props);
this.state = {
constructor(props) {
super(props);
this.state = {
loading: false,
appInstallModalVisible: false,
appUninstallModalVisible: false,
};
}
appOperation = (type, payload, operation, timestamp = null) => {
const config = this.props.context;
const release = this.props.app.applicationReleases[0];
const { uuid } = release;
const { isAndroidEnterpriseApp } = this.props.app;
this.setState({
loading: true,
});
const parameters = {};
let url =
window.location.origin +
config.serverConfig.invoker.uri +
config.serverConfig.invoker.store +
'/subscription/' +
uuid +
'/' +
type +
'/';
if (isAndroidEnterpriseApp) {
url += 'ent-app-install/'; // add ent-app-install path param for android enterprise app
parameters.requiresUpdatingExternal = true;
}
url += operation; // add operation to url
if (timestamp != null) {
parameters.timestamp = timestamp; // add timestamp for scheduled operations
}
const queryParams = Object.keys(parameters)
.map(key => key + '=' + parameters[key])
.join('&');
url += '?' + queryParams;
axios
.post(url, payload, {
headers: { 'X-Platform': config.serverConfig.platform },
})
.then(res => {
if (res.status === 200 || res.status === 201) {
this.setState({
loading: false,
appInstallModalVisible: false,
appUninstallModalVisible: false
appUninstallModalVisible: false,
});
notification.success({
message: 'Done!',
description: 'Operation triggered.',
});
} else {
this.setState({
loading: false,
});
notification.error({
message: 'There was a problem',
duration: 0,
description: 'Error occurred while ' + operation + 'ing app',
});
}
}
appOperation = (type, payload, operation, timestamp = null) => {
const config = this.props.context;
const release = this.props.app.applicationReleases[0];
const {uuid} = release;
const {isAndroidEnterpriseApp} = this.props.app;
this.setState({
loading: true,
});
const parameters = {};
let url = window.location.origin + config.serverConfig.invoker.uri +
config.serverConfig.invoker.store + "/subscription/" + uuid + "/" + type + "/";
if (isAndroidEnterpriseApp) {
url += "ent-app-install/"; // add ent-app-install path param for android enterprise app
parameters.requiresUpdatingExternal = true;
}
url += operation; // add operation to url
if (timestamp != null) {
parameters.timestamp = timestamp; // add timestamp for scheduled operations
}
const queryParams = Object.keys(parameters).map(key => key + '=' + parameters[key]).join('&');
url += '?' + queryParams;
axios.post(
url,
payload,
{
headers: {'X-Platform': config.serverConfig.platform}
}
).then(res => {
if (res.status === 200 || res.status === 201) {
this.setState({
loading: false,
appInstallModalVisible: false,
appUninstallModalVisible: false,
});
notification["success"]({
message: 'Done!',
description:
'Operation triggered.',
});
} else {
this.setState({
loading: false
});
notification["error"]({
message: "There was a problem",
duration: 0,
description:
"Error occurred while " + operation + "ing app",
});
}
}).catch((error) => {
handleApiError(error, "Error occurred while " + operation + "ing the app.");
});
};
closeAppOperationModal = () => {
this.setState({
appInstallModalVisible: false,
appUninstallModalVisible: false
});
};
handleSubscribeClick = (e) => {
if (e.key === "install") {
this.setState({
appInstallModalVisible: true // display app install modal
})
} else if (e.key === "uninstall") {
this.setState({
appUninstallModalVisible: true // display app uninstall modal
})
}
};
render() {
const {app, deviceType} = this.props;
const config = this.props.context;
const release = app.applicationReleases[0];
let metaData = [];
try {
metaData = JSON.parse(release.metaData);
} catch (e) {
}
if (app.hasOwnProperty("packageName")) {
metaData.push({
key: "Package Name",
value: app.packageName
});
}
const menu = (
<Menu onClick={this.handleSubscribeClick}>
<Menu.Item key="install">Install</Menu.Item>
<Menu.Item key="uninstall">Uninstall</Menu.Item>
</Menu>
})
.catch(error => {
handleApiError(
error,
'Error occurred while ' + operation + 'ing the app.',
);
});
};
return (
<div>
<AppInstallModal
uuid={release.uuid}
loading={this.state.loading}
visible={this.state.appInstallModalVisible}
deviceType={deviceType}
onClose={this.closeAppOperationModal}
onInstall={this.appOperation}/>
<AppUninstallModal
uuid={release.uuid}
loading={this.state.loading}
visible={this.state.appUninstallModalVisible}
deviceType={deviceType}
onClose={this.closeAppOperationModal}
onUninstall={this.appOperation}/>
<div className="release">
<Row>
<Col xl={4} sm={6} xs={8} className="release-icon">
<img src={release.iconPath} alt="icon"/>
</Col>
<Col xl={10} sm={11} className="release-title">
<Title level={2}>{app.name}</Title>
<Text>Version : {release.version}</Text><br/><br/>
<StarRatings
rating={app.rating}
starRatedColor="#777"
starDimension="20px"
starSpacing="2px"
numberOfStars={5}
name='rating'
/>
</Col>
<Col xl={8} md={10} sm={24} xs={24} style={{float: "right"}}>
<div style={{
textAlign: "right"
}}>
<Dropdown overlay={menu}>
<Button type="primary">
Subscribe <Icon type="down"/>
</Button>
</Dropdown>
</div>
</Col>
</Row>
<Divider dashed={true}/>
<Tabs>
<TabPane tab="App" key="1">
<Row>
<ImgViewer images={release.screenshots}/>
</Row>
<Divider/>
<Paragraph type="secondary" ellipsis={{rows: 3, expandable: true}}>
{release.description}
</Paragraph>
<Divider/>
<Text>CATEGORIES</Text>
<div style={{marginTop: 8}}>
{
app.categories.map(category => {
return (
<Tag color={pSBC(0.30, config.theme.primaryColor)} key={category}
style={{marginBottom: 5}}>
{category}
</Tag>
);
})
}
</div>
<Divider/>
<Text>TAGS</Text>
<div style={{marginTop: 8}}>
{
app.tags.map(tag => {
return (
<Tag color="#34495e" key={tag} style={{marginBottom: 5}}>
{tag}
</Tag>
);
})
}
</div>
<Divider/>
<Text>META DATA</Text>
closeAppOperationModal = () => {
this.setState({
appInstallModalVisible: false,
appUninstallModalVisible: false,
});
};
<Row>
{
metaData.map((data, index) => {
return (
<Col key={index} lg={8} md={6} xs={24} style={{marginTop: 15}}>
<Text>{data.key}</Text><br/>
<Text type="secondary">{data.value}</Text>
</Col>
)
})
}
{(metaData.length === 0) && (<Text type="secondary">No meta data available.</Text>)}
</Row>
<Divider/>
<ReviewContainer uuid={release.uuid}/>
</TabPane>
<TabPane tab="Subscription Details" key="2">
<SubscriptionDetails uuid={release.uuid}/>
</TabPane>
</Tabs>
</div>
</div>
);
handleSubscribeClick = e => {
if (e.key === 'install') {
this.setState({
appInstallModalVisible: true, // display app install modal
});
} else if (e.key === 'uninstall') {
this.setState({
appUninstallModalVisible: true, // display app uninstall modal
});
}
};
render() {
const { app, deviceType } = this.props;
const config = this.props.context;
const release = app.applicationReleases[0];
let metaData = [];
try {
metaData = JSON.parse(release.metaData);
// eslint-disable-next-line no-empty
} catch (e) {}
if (app.hasOwnProperty('packageName')) {
metaData.push({
key: 'Package Name',
value: app.packageName,
});
}
const menu = (
<Menu onClick={this.handleSubscribeClick}>
<Menu.Item key="install">Install</Menu.Item>
<Menu.Item key="uninstall">Uninstall</Menu.Item>
</Menu>
);
return (
<div>
<AppInstallModal
uuid={release.uuid}
loading={this.state.loading}
visible={this.state.appInstallModalVisible}
deviceType={deviceType}
onClose={this.closeAppOperationModal}
onInstall={this.appOperation}
/>
<AppUninstallModal
uuid={release.uuid}
loading={this.state.loading}
visible={this.state.appUninstallModalVisible}
deviceType={deviceType}
onClose={this.closeAppOperationModal}
onUninstall={this.appOperation}
/>
<div className="release">
<Row>
<Col xl={4} sm={6} xs={8} className="release-icon">
<img src={release.iconPath} alt="icon" />
</Col>
<Col xl={10} sm={11} className="release-title">
<Title level={2}>{app.name}</Title>
<Text>Version : {release.version}</Text>
<br />
<br />
<StarRatings
rating={app.rating}
starRatedColor="#777"
starDimension="20px"
starSpacing="2px"
numberOfStars={5}
name="rating"
/>
</Col>
<Col xl={8} md={10} sm={24} xs={24} style={{ float: 'right' }}>
<div
style={{
textAlign: 'right',
}}
>
<Dropdown overlay={menu}>
<Button type="primary">
Subscribe <Icon type="down" />
</Button>
</Dropdown>
</div>
</Col>
</Row>
<Divider dashed={true} />
<Tabs>
<TabPane tab="App" key="1">
<Row>
<ImgViewer images={release.screenshots} />
</Row>
<Divider />
<Paragraph
type="secondary"
ellipsis={{ rows: 3, expandable: true }}
>
{release.description}
</Paragraph>
<Divider />
<Text>CATEGORIES</Text>
<div style={{ marginTop: 8 }}>
{app.categories.map(category => {
return (
<Tag
color={pSBC(0.3, config.theme.primaryColor)}
key={category}
style={{ marginBottom: 5 }}
>
{category}
</Tag>
);
})}
</div>
<Divider />
<Text>TAGS</Text>
<div style={{ marginTop: 8 }}>
{app.tags.map(tag => {
return (
<Tag color="#34495e" key={tag} style={{ marginBottom: 5 }}>
{tag}
</Tag>
);
})}
</div>
<Divider />
<Text>META DATA</Text>
<Row>
{metaData.map((data, index) => {
return (
<Col
key={index}
lg={8}
md={6}
xs={24}
style={{ marginTop: 15 }}
>
<Text>{data.key}</Text>
<br />
<Text type="secondary">{data.value}</Text>
</Col>
);
})}
{metaData.length === 0 && (
<Text type="secondary">No meta data available.</Text>
)}
</Row>
<Divider />
<ReviewContainer uuid={release.uuid} />
</TabPane>
<TabPane tab="Subscription Details" key="2">
<SubscriptionDetails uuid={release.uuid} />
</TabPane>
</Tabs>
</div>
</div>
);
}
}
export default withConfigContext(ReleaseView);

View File

@ -16,260 +16,257 @@
* under the License.
*/
import React from "react";
import axios from "axios";
import {
Tag,
message,
notification,
Table,
Typography,
Tooltip,
Icon,
Divider,
Button,
Modal,
Select,
Alert
} from "antd";
import TimeAgo from 'javascript-time-ago'
import React from 'react';
import axios from 'axios';
import { Tag, Table, Typography, Button, Alert } from 'antd';
import TimeAgo from 'javascript-time-ago';
// Load locale-specific relative date/time formatting rules.
import en from 'javascript-time-ago/locale/en'
import {withConfigContext} from "../../../context/ConfigContext";
import {handleApiError} from "../../../js/Utils";
import en from 'javascript-time-ago/locale/en';
import { withConfigContext } from '../../../context/ConfigContext';
import { handleApiError } from '../../../js/Utils';
const {Text} = Typography;
let config = null;
const { Text } = Typography;
const columns = [
{
title: 'Device',
dataIndex: 'device',
width: 100,
render: device => device.name
{
title: 'Device',
dataIndex: 'device',
width: 100,
render: device => device.name,
},
{
title: 'Owner',
dataIndex: 'device',
key: 'owner',
render: device => device.enrolmentInfo.owner,
},
{
title: 'Action Type',
dataIndex: 'actionType',
key: 'actionType',
render: actionType => actionType.toLowerCase(),
},
{
title: 'Action',
dataIndex: 'action',
key: 'action',
// eslint-disable-next-line react/display-name
render: action => {
action = action.toLowerCase();
let color = 'fff';
if (action === 'subscribed') {
color = '#6ab04c';
} else if (action === 'unsubscribed') {
color = '#f0932b';
}
return <span style={{ color: color }}>{action}</span>;
},
{
title: 'Owner',
dataIndex: 'device',
key: 'owner',
render: device => device.enrolmentInfo.owner
},
{
title: 'Triggered By',
dataIndex: 'actionTriggeredBy',
key: 'actionTriggeredBy',
},
{
title: 'Action Triggered At',
dataIndex: 'actionTriggeredTimestamp',
key: 'actionTriggeredTimestamp',
},
{
title: 'Action Status',
dataIndex: 'status',
key: 'actionStatus',
// eslint-disable-next-line react/display-name
render: status => {
let color = '#f9ca24';
switch (status) {
case 'COMPLETED':
color = '#badc58';
break;
case 'REPEATED':
color = '#6ab04c';
break;
case 'ERROR':
case 'INVALID':
case 'UNAUTHORIZED':
color = '#ff7979';
break;
case 'IN_PROGRESS':
color = '#f9ca24';
break;
case 'PENDING':
color = '#636e72';
break;
}
return <Tag color={color}>{status.toLowerCase()}</Tag>;
},
{
title: 'Action Type',
dataIndex: 'actionType',
key: 'actionType',
render: actionType => actionType.toLowerCase()
},
{
title: 'Device Status',
dataIndex: 'device',
key: 'deviceStatus',
// eslint-disable-next-line react/display-name
render: device => {
const status = device.enrolmentInfo.status.toLowerCase();
let color = '#f9ca24';
switch (status) {
case 'active':
color = '#badc58';
break;
case 'created':
color = '#6ab04c';
break;
case 'removed':
color = '#ff7979';
break;
case 'inactive':
color = '#f9ca24';
break;
case 'blocked':
color = '#636e72';
break;
}
return <Tag color={color}>{status}</Tag>;
},
{
title: 'Action',
dataIndex: 'action',
key: 'action',
render: action => {
action = action.toLowerCase();
let color = "fff";
if(action==="subscribed"){
color = "#6ab04c"
}else if(action === "unsubscribed"){
color = "#f0932b"
}
return <span style={{color:color}}>{action}</span>
}
},
{
title: 'Triggered By',
dataIndex: 'actionTriggeredBy',
key: 'actionTriggeredBy'
},
{
title: 'Action Triggered At',
dataIndex: 'actionTriggeredTimestamp',
key: 'actionTriggeredTimestamp'
},
{
title: 'Action Status',
dataIndex: 'status',
key: 'actionStatus',
render: (status) => {
let color = "#f9ca24";
switch (status) {
case "COMPLETED":
color = "#badc58";
break;
case "REPEATED":
color = "#6ab04c";
break;
case "ERROR":
case "INVALID":
case "UNAUTHORIZED":
color = "#ff7979";
break;
case "IN_PROGRESS":
color = "#f9ca24";
break;
case "PENDING":
color = "#636e72";
break;
}
return <Tag color={color}>{status.toLowerCase()}</Tag>;
}
},
{
title: 'Device Status',
dataIndex: 'device',
key: 'deviceStatus',
render: (device) => {
const status = device.enrolmentInfo.status.toLowerCase();
let color = "#f9ca24";
switch (status) {
case "active":
color = "#badc58";
break;
case "created":
color = "#6ab04c";
break;
case "removed":
color = "#ff7979";
break;
case "inactive":
color = "#f9ca24";
break;
case "blocked":
color = "#636e72";
break;
}
return <Tag color={color}>{status}</Tag>;
}
}
},
];
const getTimeAgo = (time) => {
const timeAgo = new TimeAgo('en-US');
return timeAgo.format(time);
};
class SubscriptionDetails extends React.Component {
constructor(props) {
super(props);
config = this.props.context;
TimeAgo.addLocale(en);
this.state = {
data: [],
pagination: {},
constructor(props) {
super(props);
TimeAgo.addLocale(en);
this.state = {
data: [],
pagination: {},
loading: false,
selectedRows: [],
deviceGroups: [],
groupModalVisible: false,
selectedGroupId: [],
isForbidden: false,
};
}
componentDidMount() {
this.fetch();
}
// fetch data from api
fetch = (params = {}) => {
const config = this.props.context;
this.setState({ loading: true });
// get current page
const currentPage = params.hasOwnProperty('page') ? params.page : 1;
const extraParams = {
offset: 10 * (currentPage - 1), // calculate the offset
limit: 10,
requireDeviceInfo: true,
};
const encodedExtraParams = Object.keys(extraParams)
.map(key => key + '=' + extraParams[key])
.join('&');
// send request to the invoker
axios
.get(
window.location.origin +
config.serverConfig.invoker.uri +
config.serverConfig.invoker.store +
`/admin/subscription/${this.props.uuid}?` +
encodedExtraParams,
)
.then(res => {
if (res.status === 200) {
this.setState({
loading: false,
selectedRows: [],
deviceGroups: [],
groupModalVisible: false,
selectedGroupId: [],
isForbidden: false
};
}
componentDidMount() {
this.fetch();
}
//fetch data from api
fetch = (params = {}) => {
const config = this.props.context;
this.setState({loading: true});
// get current page
const currentPage = (params.hasOwnProperty("page")) ? params.page : 1;
const extraParams = {
offset: 10 * (currentPage - 1), //calculate the offset
limit: 10,
requireDeviceInfo: true,
};
const encodedExtraParams = Object.keys(extraParams)
.map(key => key + '=' + extraParams[key]).join('&');
//send request to the invoker
axios.get(
window.location.origin + config.serverConfig.invoker.uri +
config.serverConfig.invoker.store +
`/admin/subscription/${this.props.uuid}?` + encodedExtraParams,
).then(res => {
if (res.status === 200) {
this.setState({
loading: false,
data: res.data.data
});
}
}).catch((error) => {
handleApiError(error, "Something went wrong when trying to load subscription data.", true);
if (error.hasOwnProperty("response") && error.response.status === 403) {
this.setState({
isForbidden: true,
loading: false
})
} else {
this.setState({
loading: false
});
}
});
};
handleTableChange = (pagination, filters, sorter) => {
const pager = {...this.state.pagination};
pager.current = pagination.current;
this.setState({
pagination: pager,
});
this.fetch({
results: pagination.pageSize,
page: pagination.current,
sortField: sorter.field,
sortOrder: sorter.order,
...filters,
});
};
render() {
const {data, pagination, loading, selectedRows} = this.state;
return (
<div>
{(this.state.isForbidden) && (
<Alert
message="You don't have permission to view subscription details."
type="warning"
banner
closable/>
)}
<div style={{paddingBottom: 24}}>
<Text>
The following are the subscription details of the application in each respective device.
</Text>
</div>
<div style={{textAlign: "right", paddingBottom: 6}}>
<Button icon="sync" onClick={this.fetch}>
Refresh
</Button>
</div>
<Table
columns={columns}
rowKey={record => (record.device.deviceIdentifier + record.device.enrolmentInfo.owner + record.device.enrolmentInfo.ownership)}
dataSource={data.data}
pagination={{
...pagination,
size: "small",
// position: "top",
total: data.recordsTotal,
showTotal: (total, range) => `showing ${range[0]}-${range[1]} of ${total} devices`
// showQuickJumper: true
}}
onChange={this.handleTableChange}
loading={loading}
scroll={{x: 1000}}
/>
</div>
data: res.data.data,
});
}
})
.catch(error => {
handleApiError(
error,
'Something went wrong when trying to load subscription data.',
true,
);
}
if (error.hasOwnProperty('response') && error.response.status === 403) {
this.setState({
isForbidden: true,
loading: false,
});
} else {
this.setState({
loading: false,
});
}
});
};
handleTableChange = (pagination, filters, sorter) => {
const pager = { ...this.state.pagination };
pager.current = pagination.current;
this.setState({
pagination: pager,
});
this.fetch({
results: pagination.pageSize,
page: pagination.current,
sortField: sorter.field,
sortOrder: sorter.order,
...filters,
});
};
render() {
const { data, pagination, loading } = this.state;
return (
<div>
{this.state.isForbidden && (
<Alert
message="You don't have permission to view subscription details."
type="warning"
banner
closable
/>
)}
<div style={{ paddingBottom: 24 }}>
<Text>
The following are the subscription details of the application in
each respective device.
</Text>
</div>
<div style={{ textAlign: 'right', paddingBottom: 6 }}>
<Button icon="sync" onClick={this.fetch}>
Refresh
</Button>
</div>
<Table
columns={columns}
rowKey={record =>
record.device.deviceIdentifier +
record.device.enrolmentInfo.owner +
record.device.enrolmentInfo.ownership
}
dataSource={data.data}
pagination={{
...pagination,
size: 'small',
// position: "top",
total: data.recordsTotal,
showTotal: (total, range) =>
`showing ${range[0]}-${range[1]} of ${total} devices`,
// showQuickJumper: true
}}
onChange={this.handleTableChange}
loading={loading}
scroll={{ x: 1000 }}
/>
</div>
);
}
}
export default withConfigContext(SubscriptionDetails);
export default withConfigContext(SubscriptionDetails);

View File

@ -16,48 +16,47 @@
* under the License.
*/
import React, {Component} from 'react';
import React, { Component } from 'react';
import RcViewer from 'rc-viewer';
import {Col} from "antd";
class ImgViewer extends Component {
render() {
const options = {
title: false,
toolbar: {
zoomIn: 0,
zoomOut: 0,
oneToOne: 0,
reset: 0,
prev: 1,
play: {
show: 0
},
next: 1,
rotateLeft: 0,
rotateRight: 0,
flipHorizontal: 0,
flipVertical: 0
},
rotatable: false,
transition: false,
movable : false
};
return (
<div className="release-images">
<RcViewer options={options} ref='viewer'>
{this.props.images.map((screenshotUrl, index) => {
return (
<div key={index} className="release-screenshot">
<img alt="screenshot" key={screenshotUrl} src={screenshotUrl}/>
</div>
)
})}
</RcViewer>
</div>
);
}
render() {
const options = {
title: false,
toolbar: {
zoomIn: 0,
zoomOut: 0,
oneToOne: 0,
reset: 0,
prev: 1,
play: {
show: 0,
},
next: 1,
rotateLeft: 0,
rotateRight: 0,
flipHorizontal: 0,
flipVertical: 0,
},
rotatable: false,
transition: false,
movable: false,
};
return (
<div className="release-images">
{/* eslint-disable-next-line react/no-string-refs */}
<RcViewer options={options} ref="viewer">
{this.props.images.map((screenshotUrl, index) => {
return (
<div key={index} className="release-screenshot">
<img alt="screenshot" key={screenshotUrl} src={screenshotUrl} />
</div>
);
})}
</RcViewer>
</div>
);
}
}
export default ImgViewer;
export default ImgViewer;

View File

@ -16,49 +16,53 @@
* under the License.
*/
import React from "react";
import {Modal, Spin, Tabs} from "antd";
import UserInstall from "./UserInstall";
import GroupInstall from "./GroupInstall";
import RoleInstall from "./RoleInstall";
import DeviceInstall from "./DeviceInstall";
import React from 'react';
import { Modal, Spin, Tabs } from 'antd';
import UserInstall from './UserInstall';
import GroupInstall from './GroupInstall';
import RoleInstall from './RoleInstall';
import DeviceInstall from './DeviceInstall';
const {TabPane} = Tabs;
const { TabPane } = Tabs;
class AppInstallModal extends React.Component {
state = {
data: []
};
state = {
data: [],
};
render() {
const {deviceType} = this.props;
return (
<div>
<Modal
title="Install App"
visible={this.props.visible}
onCancel={this.props.onClose}
footer={null}>
<Spin spinning={this.props.loading}>
<Tabs defaultActiveKey="device">
<TabPane tab="Device" key="device">
<DeviceInstall deviceType={deviceType} onInstall={this.props.onInstall}/>
</TabPane>
<TabPane tab="User" key="user">
<UserInstall onInstall={this.props.onInstall}/>
</TabPane>
<TabPane tab="Role" key="role">
<RoleInstall onInstall={this.props.onInstall}/>
</TabPane>
<TabPane tab="Group" key="group">
<GroupInstall onInstall={this.props.onInstall}/>
</TabPane>
</Tabs>
</Spin>
</Modal>
</div>
);
}
render() {
const { deviceType } = this.props;
return (
<div>
<Modal
title="Install App"
visible={this.props.visible}
onCancel={this.props.onClose}
footer={null}
>
<Spin spinning={this.props.loading}>
<Tabs defaultActiveKey="device">
<TabPane tab="Device" key="device">
<DeviceInstall
deviceType={deviceType}
onInstall={this.props.onInstall}
/>
</TabPane>
<TabPane tab="User" key="user">
<UserInstall onInstall={this.props.onInstall} />
</TabPane>
<TabPane tab="Role" key="role">
<RoleInstall onInstall={this.props.onInstall} />
</TabPane>
<TabPane tab="Group" key="group">
<GroupInstall onInstall={this.props.onInstall} />
</TabPane>
</Tabs>
</Spin>
</Modal>
</div>
);
}
}
export default AppInstallModal;
export default AppInstallModal;

View File

@ -15,50 +15,63 @@
* specific language governing permissions and limitations
* under the License.
*/
import React from "react";
import {Modal, Spin, Tabs} from "antd";
import DeviceUninstall from "./DeviceUninstall";
import UserUninstall from "./UserUninstall";
import RoleUninstall from "./RoleUninstall";
import GroupUninstall from "./GroupUninstall";
import React from 'react';
import { Modal, Spin, Tabs } from 'antd';
import DeviceUninstall from './DeviceUninstall';
import UserUninstall from './UserUninstall';
import RoleUninstall from './RoleUninstall';
import GroupUninstall from './GroupUninstall';
const {TabPane} = Tabs;
const { TabPane } = Tabs;
class AppUninstallModal extends React.Component {
state = {
data: []
};
state = {
data: [],
};
render() {
const {deviceType} = this.props;
return (
<div>
<Modal
title="Uninstall App"
visible={this.props.visible}
onCancel={this.props.onClose}
footer={null}>
<Spin spinning={this.props.loading}>
<Tabs defaultActiveKey="device">
<TabPane tab="Device" key="device">
<DeviceUninstall deviceType={deviceType} onUninstall={this.props.onUninstall}
uuid={this.props.uuid}/>
</TabPane>
<TabPane tab="User" key="user">
<UserUninstall onUninstall={this.props.onUninstall} uuid={this.props.uuid}/>
</TabPane>
<TabPane tab="Role" key="role">
<RoleUninstall onUninstall={this.props.onUninstall} uuid={this.props.uuid}/>
</TabPane>
<TabPane tab="Group" key="group">
<GroupUninstall onUninstall={this.props.onUninstall} uuid={this.props.uuid}/>
</TabPane>
</Tabs>
</Spin>
</Modal>
</div>
);
}
render() {
const { deviceType } = this.props;
return (
<div>
<Modal
title="Uninstall App"
visible={this.props.visible}
onCancel={this.props.onClose}
footer={null}
>
<Spin spinning={this.props.loading}>
<Tabs defaultActiveKey="device">
<TabPane tab="Device" key="device">
<DeviceUninstall
deviceType={deviceType}
onUninstall={this.props.onUninstall}
uuid={this.props.uuid}
/>
</TabPane>
<TabPane tab="User" key="user">
<UserUninstall
onUninstall={this.props.onUninstall}
uuid={this.props.uuid}
/>
</TabPane>
<TabPane tab="Role" key="role">
<RoleUninstall
onUninstall={this.props.onUninstall}
uuid={this.props.uuid}
/>
</TabPane>
<TabPane tab="Group" key="group">
<GroupUninstall
onUninstall={this.props.onUninstall}
uuid={this.props.uuid}
/>
</TabPane>
</Tabs>
</Spin>
</Modal>
</div>
);
}
}
export default AppUninstallModal;

View File

@ -16,232 +16,250 @@
* under the License.
*/
import React from "react";
import axios from "axios";
import {Button, message, DatePicker, Table, Typography, Alert} from "antd";
import TimeAgo from 'javascript-time-ago'
import React from 'react';
import axios from 'axios';
import { Table, Typography, Alert } from 'antd';
import TimeAgo from 'javascript-time-ago';
// Load locale-specific relative date/time formatting rules.
import en from 'javascript-time-ago/locale/en'
import {withConfigContext} from "../../../../context/ConfigContext";
import {handleApiError} from "../../../../js/Utils";
import InstallModalFooter from "./installModalFooter/InstallModalFooter";
import en from 'javascript-time-ago/locale/en';
import { withConfigContext } from '../../../../context/ConfigContext';
import { handleApiError } from '../../../../js/Utils';
import InstallModalFooter from './installModalFooter/InstallModalFooter';
const {Text} = Typography;
const { Text } = Typography;
const columns = [
{
title: 'Device',
dataIndex: 'name',
fixed: 'left',
width: 100,
{
title: 'Device',
dataIndex: 'name',
fixed: 'left',
width: 100,
},
{
title: 'Modal',
dataIndex: 'deviceInfo',
key: 'modal',
render: deviceInfo => `${deviceInfo.vendor} ${deviceInfo.deviceModel}`,
// todo add filtering options
},
{
title: 'Owner',
dataIndex: 'enrolmentInfo',
key: 'owner',
render: enrolmentInfo => enrolmentInfo.owner,
// todo add filtering options
},
{
title: 'Last Updated',
dataIndex: 'deviceInfo',
key: 'updatedTime',
render: data => {
return data.updatedTime;
},
{
title: 'Modal',
dataIndex: 'deviceInfo',
key: 'modal',
render: deviceInfo => `${deviceInfo.vendor} ${deviceInfo.deviceModel}`
// todo add filtering options
},
{
title: 'Owner',
dataIndex: 'enrolmentInfo',
key: 'owner',
render: enrolmentInfo => enrolmentInfo.owner
// todo add filtering options
},
{
title: 'Last Updated',
dataIndex: 'deviceInfo',
key: 'updatedTime',
render: (data) => {
return data.updatedTime;
// todo add filtering options
},
{
title: 'Status',
dataIndex: 'enrolmentInfo',
key: 'status',
render: enrolmentInfo => enrolmentInfo.status,
// todo add filtering options
},
{
title: 'Ownership',
dataIndex: 'enrolmentInfo',
key: 'ownership',
render: enrolmentInfo => enrolmentInfo.ownership,
// todo add filtering options
},
{
title: 'OS Version',
dataIndex: 'deviceInfo',
key: 'osVersion',
render: deviceInfo => deviceInfo.osVersion,
// todo add filtering options
},
{
title: 'IMEI',
dataIndex: 'properties',
key: 'imei',
render: properties => {
let imei = 'not-found';
for (let i = 0; i < properties.length; i++) {
if (properties[i].name === 'IMEI') {
imei = properties[i].value;
}
// todo add filtering options
},
{
title: 'Status',
dataIndex: 'enrolmentInfo',
key: 'status',
render: enrolmentInfo => enrolmentInfo.status
// todo add filtering options
},
{
title: 'Ownership',
dataIndex: 'enrolmentInfo',
key: 'ownership',
render: enrolmentInfo => enrolmentInfo.ownership
// todo add filtering options
},
{
title: 'OS Version',
dataIndex: 'deviceInfo',
key: 'osVersion',
render: deviceInfo => deviceInfo.osVersion
// todo add filtering options
},
{
title: 'IMEI',
dataIndex: 'properties',
key: 'imei',
render: properties => {
let imei = "not-found";
for (let i = 0; i < properties.length; i++) {
if (properties[i].name === "IMEI") {
imei = properties[i].value;
}
}
return imei;
}
// todo add filtering options
}
return imei;
},
// todo add filtering options
},
];
class DeviceInstall extends React.Component {
constructor(props) {
super(props);
TimeAgo.addLocale(en);
this.state = {
data: [],
pagination: {},
constructor(props) {
super(props);
TimeAgo.addLocale(en);
this.state = {
data: [],
pagination: {},
loading: false,
selectedRows: [],
scheduledTime: null,
isScheduledInstallVisible: false,
isForbidden: false,
};
}
rowSelection = {
onChange: (selectedRowKeys, selectedRows) => {
this.setState({
selectedRows: selectedRows,
});
},
getCheckboxProps: record => ({
disabled: record.name === 'Disabled User', // Column configuration not to be checked
name: record.name,
}),
};
componentDidMount() {
this.fetch();
}
// fetch data from api
fetch = (params = {}) => {
const config = this.props.context;
this.setState({ loading: true });
const { deviceType } = this.props;
// get current page
const currentPage = params.hasOwnProperty('page') ? params.page : 1;
const extraParams = {
offset: 10 * (currentPage - 1), // calculate the offset
limit: 10,
status: 'ACTIVE',
requireDeviceInfo: true,
};
if (deviceType !== 'ANY') {
extraParams.type = deviceType;
}
// note: encode with '%26' not '&'
const encodedExtraParams = Object.keys(extraParams)
.map(key => key + '=' + extraParams[key])
.join('&');
// send request to the invoker
axios
.get(
window.location.origin +
config.serverConfig.invoker.uri +
config.serverConfig.invoker.deviceMgt +
'/devices?' +
encodedExtraParams,
)
.then(res => {
if (res.status === 200) {
const pagination = { ...this.state.pagination };
this.setState({
loading: false,
selectedRows: [],
scheduledTime: null,
isScheduledInstallVisible: false,
isForbidden: false
};
}
rowSelection = {
onChange: (selectedRowKeys, selectedRows) => {
this.setState({
selectedRows: selectedRows
})
},
getCheckboxProps: record => ({
disabled: record.name === 'Disabled User', // Column configuration not to be checked
name: record.name,
}),
};
componentDidMount() {
this.fetch();
}
//fetch data from api
fetch = (params = {}) => {
const config = this.props.context;
this.setState({loading: true});
const {deviceType} = this.props;
// get current page
const currentPage = (params.hasOwnProperty("page")) ? params.page : 1;
const extraParams = {
offset: 10 * (currentPage - 1), //calculate the offset
limit: 10,
status: "ACTIVE",
requireDeviceInfo: true,
};
if (deviceType !== 'ANY') {
extraParams.type = deviceType;
data: res.data.data,
pagination,
});
}
// note: encode with '%26' not '&'
const encodedExtraParams = Object.keys(extraParams).map(key => key + '=' + extraParams[key]).join('&');
//send request to the invoker
axios.get(
window.location.origin + config.serverConfig.invoker.uri + config.serverConfig.invoker.deviceMgt +
"/devices?" + encodedExtraParams,
).then(res => {
if (res.status === 200) {
const pagination = {...this.state.pagination};
this.setState({
loading: false,
data: res.data.data,
pagination,
});
}
}).catch((error) => {
handleApiError(error, "Error occurred while trying to load devices.", true);
if (error.hasOwnProperty("response") && error.response.status === 403) {
this.setState({
isForbidden: true,
loading: false
})
} else {
this.setState({
loading: false
});
}
});
};
handleTableChange = (pagination, filters, sorter) => {
const pager = {...this.state.pagination};
pager.current = pagination.current;
this.setState({
pagination: pager,
});
this.fetch({
results: pagination.pageSize,
page: pagination.current,
sortField: sorter.field,
sortOrder: sorter.order,
...filters,
});
};
install = (timestamp=null) => {
const {selectedRows} = this.state;
const payload = [];
selectedRows.map(device => {
payload.push({
id: device.deviceIdentifier,
type: device.type
});
});
this.props.onInstall("devices", payload, "install",timestamp);
};
render() {
const {data, pagination, loading, selectedRows, scheduledTime,isScheduledInstallVisible} = this.state;
return (
<div>
<Text>
Start installing the application for one or more users by entering the corresponding user name.
Select install to automatically start downloading the application for the respective user/users.
</Text>
{(this.state.isForbidden) && (
<Alert
message="You don't have permission to view devices."
type="warning"
banner
closable/>
)}
<Table
style={{paddingTop: 20}}
columns={columns}
rowKey={record => record.deviceIdentifier}
dataSource={data.devices}
pagination={{
...pagination,
size: "small",
// position: "top",
total: data.count,
showTotal: (total, range) => `showing ${range[0]}-${range[1]} of ${total} devices`
// showQuickJumper: true
}}
loading={loading}
onChange={this.handleTableChange}
rowSelection={this.rowSelection}
scroll={{x: 1000}}
/>
<InstallModalFooter type="Install" operation={this.install} disabled={selectedRows.length === 0}/>
</div>
})
.catch(error => {
handleApiError(
error,
'Error occurred while trying to load devices.',
true,
);
}
if (error.hasOwnProperty('response') && error.response.status === 403) {
this.setState({
isForbidden: true,
loading: false,
});
} else {
this.setState({
loading: false,
});
}
});
};
handleTableChange = (pagination, filters, sorter) => {
const pager = { ...this.state.pagination };
pager.current = pagination.current;
this.setState({
pagination: pager,
});
this.fetch({
results: pagination.pageSize,
page: pagination.current,
sortField: sorter.field,
sortOrder: sorter.order,
...filters,
});
};
install = (timestamp = null) => {
const { selectedRows } = this.state;
const payload = [];
selectedRows.map(device => {
payload.push({
id: device.deviceIdentifier,
type: device.type,
});
});
this.props.onInstall('devices', payload, 'install', timestamp);
};
render() {
const { data, pagination, loading, selectedRows } = this.state;
return (
<div>
<Text>
Start installing the application for one or more users by entering the
corresponding user name. Select install to automatically start
downloading the application for the respective user/users.
</Text>
{this.state.isForbidden && (
<Alert
message="You don't have permission to view devices."
type="warning"
banner
closable
/>
)}
<Table
style={{ paddingTop: 20 }}
columns={columns}
rowKey={record => record.deviceIdentifier}
dataSource={data.devices}
pagination={{
...pagination,
size: 'small',
// position: "top",
total: data.count,
showTotal: (total, range) =>
`showing ${range[0]}-${range[1]} of ${total} devices`,
// showQuickJumper: true
}}
loading={loading}
onChange={this.handleTableChange}
rowSelection={this.rowSelection}
scroll={{ x: 1000 }}
/>
<InstallModalFooter
type="Install"
operation={this.install}
disabled={selectedRows.length === 0}
/>
</div>
);
}
}
export default withConfigContext(DeviceInstall);
export default withConfigContext(DeviceInstall);

View File

@ -16,226 +16,248 @@
* under the License.
*/
import React from "react";
import axios from "axios";
import {Alert, Button, Select, Table, Typography} from "antd";
import TimeAgo from 'javascript-time-ago'
import React from 'react';
import axios from 'axios';
import { Alert, Table, Typography } from 'antd';
import TimeAgo from 'javascript-time-ago';
// Load locale-specific relative date/time formatting rules.
import en from 'javascript-time-ago/locale/en'
import {withConfigContext} from "../../../../context/ConfigContext";
import {handleApiError} from "../../../../js/Utils";
import InstallModalFooter from "./installModalFooter/InstallModalFooter";
import en from 'javascript-time-ago/locale/en';
import { withConfigContext } from '../../../../context/ConfigContext';
import { handleApiError } from '../../../../js/Utils';
import InstallModalFooter from './installModalFooter/InstallModalFooter';
const {Text} = Typography;
const { Text } = Typography;
const columns = [
{
title: 'Device',
dataIndex: 'name',
fixed: 'left',
width: 100,
{
title: 'Device',
dataIndex: 'name',
fixed: 'left',
width: 100,
},
{
title: 'Modal',
dataIndex: 'deviceInfo',
key: 'modal',
render: deviceInfo => `${deviceInfo.vendor} ${deviceInfo.deviceModel}`,
// todo add filtering options
},
{
title: 'Owner',
dataIndex: 'enrolmentInfo',
key: 'owner',
render: enrolmentInfo => enrolmentInfo.owner,
// todo add filtering options
},
{
title: 'Last Updated',
dataIndex: 'deviceInfo',
key: 'updatedTime',
render: data => {
return data.updatedTime;
},
{
title: 'Modal',
dataIndex: 'deviceInfo',
key: 'modal',
render: deviceInfo => `${deviceInfo.vendor} ${deviceInfo.deviceModel}`
// todo add filtering options
},
{
title: 'Owner',
dataIndex: 'enrolmentInfo',
key: 'owner',
render: enrolmentInfo => enrolmentInfo.owner
// todo add filtering options
},
{
title: 'Last Updated',
dataIndex: 'deviceInfo',
key: 'updatedTime',
render: (data) => {
return data.updatedTime;
// todo add filtering options
},
{
title: 'Status',
dataIndex: 'enrolmentInfo',
key: 'status',
render: enrolmentInfo => enrolmentInfo.status,
// todo add filtering options
},
{
title: 'Ownership',
dataIndex: 'enrolmentInfo',
key: 'ownership',
render: enrolmentInfo => enrolmentInfo.ownership,
// todo add filtering options
},
{
title: 'OS Version',
dataIndex: 'deviceInfo',
key: 'osVersion',
render: deviceInfo => deviceInfo.osVersion,
// todo add filtering options
},
{
title: 'IMEI',
dataIndex: 'properties',
key: 'imei',
render: properties => {
let imei = 'not-found';
for (let i = 0; i < properties.length; i++) {
if (properties[i].name === 'IMEI') {
imei = properties[i].value;
}
// todo add filtering options
},
{
title: 'Status',
dataIndex: 'enrolmentInfo',
key: 'status',
render: enrolmentInfo => enrolmentInfo.status
// todo add filtering options
},
{
title: 'Ownership',
dataIndex: 'enrolmentInfo',
key: 'ownership',
render: enrolmentInfo => enrolmentInfo.ownership
// todo add filtering options
},
{
title: 'OS Version',
dataIndex: 'deviceInfo',
key: 'osVersion',
render: deviceInfo => deviceInfo.osVersion
// todo add filtering options
},
{
title: 'IMEI',
dataIndex: 'properties',
key: 'imei',
render: properties => {
let imei = "not-found";
for (let i = 0; i < properties.length; i++) {
if (properties[i].name === "IMEI") {
imei = properties[i].value;
}
}
return imei;
}
// todo add filtering options
}
return imei;
},
// todo add filtering options
},
];
class DeviceUninstall extends React.Component {
constructor(props) {
super(props);
TimeAgo.addLocale(en);
this.state = {
data: [],
pagination: {},
constructor(props) {
super(props);
TimeAgo.addLocale(en);
this.state = {
data: [],
pagination: {},
loading: false,
selectedRows: [],
isForbidden: false,
};
}
rowSelection = {
onChange: (selectedRowKeys, selectedRows) => {
this.setState({
selectedRows: selectedRows,
});
},
getCheckboxProps: record => ({
disabled: record.name === 'Disabled User', // Column configuration not to be checked
name: record.name,
}),
};
componentDidMount() {
this.fetch();
}
// fetch data from api
fetch = (params = {}) => {
const config = this.props.context;
this.setState({ loading: true });
const { deviceType } = this.props;
// get current page
const currentPage = params.hasOwnProperty('page') ? params.page : 1;
const extraParams = {
offset: 10 * (currentPage - 1), // calculate the offset
limit: 10,
status: 'ACTIVE',
};
if (deviceType !== 'ANY') {
extraParams.type = deviceType;
}
// note: encode with '%26' not '&'
const encodedExtraParams = Object.keys(extraParams)
.map(key => key + '=' + extraParams[key])
.join('&');
const uuid = this.props.uuid;
axios
.get(
window.location.origin +
config.serverConfig.invoker.uri +
config.serverConfig.invoker.store +
'/subscription/' +
uuid +
'/' +
'/devices?' +
encodedExtraParams,
)
.then(res => {
if (res.status === 200) {
const pagination = { ...this.state.pagination };
this.setState({
loading: false,
selectedRows: [],
isForbidden: false
};
}
rowSelection = {
onChange: (selectedRowKeys, selectedRows) => {
this.setState({
selectedRows: selectedRows
})
},
getCheckboxProps: record => ({
disabled: record.name === 'Disabled User', // Column configuration not to be checked
name: record.name,
}),
};
componentDidMount() {
this.fetch();
}
//fetch data from api
fetch = (params = {}) => {
const config = this.props.context;
this.setState({loading: true});
const {deviceType} = this.props;
// get current page
const currentPage = (params.hasOwnProperty("page")) ? params.page : 1;
const extraParams = {
offset: 10 * (currentPage - 1), //calculate the offset
limit: 10,
status: "ACTIVE",
};
if (deviceType !== 'ANY') {
extraParams.type = deviceType;
data: res.data.data,
pagination,
});
}
// note: encode with '%26' not '&'
const encodedExtraParams = Object.keys(extraParams).map(key => key + '=' + extraParams[key]).join('&');
const uuid = this.props.uuid;
axios.get(
window.location.origin + config.serverConfig.invoker.uri + config.serverConfig.invoker.store + "/subscription/" + uuid + "/" +
"/devices?" + encodedExtraParams,
).then(res => {
if (res.status === 200) {
const pagination = {...this.state.pagination};
this.setState({
loading: false,
data: res.data.data,
pagination,
});
}
}).catch((error) => {
handleApiError(error, "Error occurred while trying to load devices.", true);
if (error.hasOwnProperty("response") && error.response.status === 403) {
this.setState({
isForbidden: true,
loading: false
})
} else {
this.setState({
loading: false
});
}
});
};
handleTableChange = (pagination, filters, sorter) => {
const pager = {...this.state.pagination};
pager.current = pagination.current;
this.setState({
pagination: pager,
});
this.fetch({
results: pagination.pageSize,
page: pagination.current,
sortField: sorter.field,
sortOrder: sorter.order,
...filters,
});
};
uninstall = (timestamp = null) => {
const {selectedRows} = this.state;
const payload = [];
selectedRows.map(device => {
payload.push({
id: device.deviceIdentifier,
type: device.type
});
});
this.props.onUninstall("devices", payload, "uninstall", timestamp);
};
render() {
const {data, pagination, loading, selectedRows} = this.state;
return (
<div>
<Text>
Start uninstalling the application for devices by selecting the corresponding devices.
Select uninstall to automatically start uninstalling the application for the respective devices.
</Text>
{(this.state.isForbidden) && (
<Alert
message="You don't have permission to view installed devices."
type="warning"
banner
closable/>
)}
<Table
style={{paddingTop: 20}}
columns={columns}
rowKey={record => record.deviceIdentifier}
dataSource={data.devices}
pagination={{
...pagination,
size: "small",
total: data.count,
showTotal: (total, range) => `showing ${range[0]}-${range[1]} of ${total} devices`
}}
loading={loading}
onChange={this.handleTableChange}
rowSelection={this.rowSelection}
scroll={{x: 1000}}
/>
<InstallModalFooter type="Uninstall" operation={this.uninstall} disabled={selectedRows.length === 0}/>
</div>
})
.catch(error => {
handleApiError(
error,
'Error occurred while trying to load devices.',
true,
);
}
if (error.hasOwnProperty('response') && error.response.status === 403) {
this.setState({
isForbidden: true,
loading: false,
});
} else {
this.setState({
loading: false,
});
}
});
};
handleTableChange = (pagination, filters, sorter) => {
const pager = { ...this.state.pagination };
pager.current = pagination.current;
this.setState({
pagination: pager,
});
this.fetch({
results: pagination.pageSize,
page: pagination.current,
sortField: sorter.field,
sortOrder: sorter.order,
...filters,
});
};
uninstall = (timestamp = null) => {
const { selectedRows } = this.state;
const payload = [];
selectedRows.map(device => {
payload.push({
id: device.deviceIdentifier,
type: device.type,
});
});
this.props.onUninstall('devices', payload, 'uninstall', timestamp);
};
render() {
const { data, pagination, loading, selectedRows } = this.state;
return (
<div>
<Text>
Start uninstalling the application for devices by selecting the
corresponding devices. Select uninstall to automatically start
uninstalling the application for the respective devices.
</Text>
{this.state.isForbidden && (
<Alert
message="You don't have permission to view installed devices."
type="warning"
banner
closable
/>
)}
<Table
style={{ paddingTop: 20 }}
columns={columns}
rowKey={record => record.deviceIdentifier}
dataSource={data.devices}
pagination={{
...pagination,
size: 'small',
total: data.count,
showTotal: (total, range) =>
`showing ${range[0]}-${range[1]} of ${total} devices`,
}}
loading={loading}
onChange={this.handleTableChange}
rowSelection={this.rowSelection}
scroll={{ x: 1000 }}
/>
<InstallModalFooter
type="Uninstall"
operation={this.uninstall}
disabled={selectedRows.length === 0}
/>
</div>
);
}
}
export default withConfigContext(DeviceUninstall);

View File

@ -16,124 +16,139 @@
* under the License.
*/
import React from "react";
import {Typography, Select, Spin, message, notification, Button, Alert} from "antd";
import React from 'react';
import { Typography, Select, Spin, Alert } from 'antd';
import debounce from 'lodash.debounce';
import axios from "axios";
import {withConfigContext} from "../../../../context/ConfigContext";
import {handleApiError} from "../../../../js/Utils";
import InstallModalFooter from "./installModalFooter/InstallModalFooter";
const {Text} = Typography;
const {Option} = Select;
import axios from 'axios';
import { withConfigContext } from '../../../../context/ConfigContext';
import { handleApiError } from '../../../../js/Utils';
import InstallModalFooter from './installModalFooter/InstallModalFooter';
const { Text } = Typography;
const { Option } = Select;
class GroupInstall extends React.Component {
constructor(props) {
super(props);
this.lastFetchId = 0;
this.fetchUser = debounce(this.fetchUser, 800);
}
constructor(props) {
super(props);
this.lastFetchId = 0;
this.fetchUser = debounce(this.fetchUser, 800);
}
state = {
data: [],
value: [],
fetching: false,
isForbidden: false,
};
state = {
data: [],
value: [],
fetching: false,
isForbidden: false
};
fetchUser = value => {
this.lastFetchId += 1;
const fetchId = this.lastFetchId;
const config = this.props.context;
this.setState({ data: [], fetching: true });
fetchUser = value => {
this.lastFetchId += 1;
const fetchId = this.lastFetchId;
const config = this.props.context;
this.setState({data: [], fetching: true});
axios
.get(
window.location.origin +
config.serverConfig.invoker.uri +
config.serverConfig.invoker.deviceMgt +
'/groups?name=' +
value,
)
.then(res => {
if (res.status === 200) {
if (fetchId !== this.lastFetchId) {
// for fetch callback order
return;
}
axios.get(
window.location.origin+ config.serverConfig.invoker.uri + config.serverConfig.invoker.deviceMgt+"/groups?name=" + value,
const data = res.data.data.deviceGroups.map(group => ({
text: group.name,
value: group.name,
}));
).then(res => {
if (res.status === 200) {
if (fetchId !== this.lastFetchId) {
// for fetch callback order
return;
}
const data = res.data.data.deviceGroups.map(group => ({
text: group.name,
value: group.name,
}));
this.setState({data, fetching: false});
}
}).catch((error) => {
handleApiError(error,"Error occurred while trying to load groups.", true);
if (error.hasOwnProperty("response") && error.response.status === 403) {
this.setState({
isForbidden: true,
loading: false
})
} else {
this.setState({
loading: false
});
}
});
};
handleChange = value => {
this.setState({
value,
data: [],
fetching: false,
});
};
install = () =>{
const {value} = this.state;
const data = [];
value.map(val=>{
data.push(val.key);
});
this.props.onInstall("group", data, "install");
};
render() {
const {fetching, data, value} = this.state;
return (
<div>
<Text>Start installing the application for one or more groups by entering the corresponding group name. Select install to automatically start downloading the application for the respective device group/ groups.</Text>
{(this.state.isForbidden) && (
<Alert
message="You don't have permission to view groups."
type="warning"
banner
closable/>
)}
<br/>
<br/>
<Select
mode="multiple"
labelInValue
value={value}
placeholder="Search groups"
notFoundContent={fetching ? <Spin size="small"/> : null}
filterOption={false}
onSearch={this.fetchUser}
onChange={this.handleChange}
style={{width: '100%'}}
>
{data.map(d => (
<Option key={d.value}>{d.text}</Option>
))}
</Select>
<InstallModalFooter type="Install" operation={this.install} disabled={value.length===0}/>
</div>
this.setState({ data, fetching: false });
}
})
.catch(error => {
handleApiError(
error,
'Error occurred while trying to load groups.',
true,
);
}
if (error.hasOwnProperty('response') && error.response.status === 403) {
this.setState({
isForbidden: true,
loading: false,
});
} else {
this.setState({
loading: false,
});
}
});
};
handleChange = value => {
this.setState({
value,
data: [],
fetching: false,
});
};
install = () => {
const { value } = this.state;
const data = [];
value.map(val => {
data.push(val.key);
});
this.props.onInstall('group', data, 'install');
};
render() {
const { fetching, data, value } = this.state;
return (
<div>
<Text>
Start installing the application for one or more groups by entering
the corresponding group name. Select install to automatically start
downloading the application for the respective device group/ groups.
</Text>
{this.state.isForbidden && (
<Alert
message="You don't have permission to view groups."
type="warning"
banner
closable
/>
)}
<br />
<br />
<Select
mode="multiple"
labelInValue
value={value}
placeholder="Search groups"
notFoundContent={fetching ? <Spin size="small" /> : null}
filterOption={false}
onSearch={this.fetchUser}
onChange={this.handleChange}
style={{ width: '100%' }}
>
{data.map(d => (
<Option key={d.value}>{d.text}</Option>
))}
</Select>
<InstallModalFooter
type="Install"
operation={this.install}
disabled={value.length === 0}
/>
</div>
);
}
}
export default withConfigContext(GroupInstall);
export default withConfigContext(GroupInstall);

View File

@ -16,126 +16,143 @@
* under the License.
*/
import React from "react";
import {Typography, Select, Spin, message, notification, Button, Alert} from "antd";
import React from 'react';
import { Typography, Select, Spin, Alert } from 'antd';
import debounce from 'lodash.debounce';
import axios from "axios";
import {withConfigContext} from "../../../../context/ConfigContext";
import {handleApiError} from "../../../../js/Utils";
import InstallModalFooter from "./installModalFooter/InstallModalFooter";
import axios from 'axios';
import { withConfigContext } from '../../../../context/ConfigContext';
import { handleApiError } from '../../../../js/Utils';
import InstallModalFooter from './installModalFooter/InstallModalFooter';
const {Text} = Typography;
const {Option} = Select;
const { Text } = Typography;
const { Option } = Select;
class GroupUninstall extends React.Component {
constructor(props) {
super(props);
this.lastFetchId = 0;
this.fetchUser = debounce(this.fetchUser, 800);
}
constructor(props) {
super(props);
this.lastFetchId = 0;
this.fetchUser = debounce(this.fetchUser, 800);
}
state = {
data: [],
value: [],
fetching: false,
isForbidden: false,
};
state = {
data: [],
value: [],
fetching: false,
isForbidden: false
};
fetchUser = value => {
this.lastFetchId += 1;
const fetchId = this.lastFetchId;
const config = this.props.context;
this.setState({ data: [], fetching: true });
fetchUser = value => {
this.lastFetchId += 1;
const fetchId = this.lastFetchId;
const config = this.props.context;
this.setState({data: [], fetching: true});
const uuid = this.props.uuid;
const uuid = this.props.uuid;
axios
.get(
window.location.origin +
config.serverConfig.invoker.uri +
config.serverConfig.invoker.store +
'/subscription/' +
uuid +
'/' +
'/GROUP?',
)
.then(res => {
if (res.status === 200) {
if (fetchId !== this.lastFetchId) {
// for fetch callback order
return;
}
axios.get(
window.location.origin + config.serverConfig.invoker.uri + config.serverConfig.invoker.store + "/subscription/" + uuid + "/" +
"/GROUP?",
).then(res => {
if (res.status === 200) {
if (fetchId !== this.lastFetchId) {
// for fetch callback order
return;
}
const data = res.data.data.deviceGroups.map(group => ({
text: group,
value: group,
}));
const data = res.data.data.deviceGroups.map(group => ({
text: group,
value: group,
}));
this.setState({data, fetching: false});
}
}).catch((error) => {
handleApiError(error, "Error occurred while trying to load groups.", true);
if (error.hasOwnProperty("response") && error.response.status === 403) {
this.setState({
isForbidden: true,
loading: false
})
} else {
this.setState({
loading: false
});
}
});
};
handleChange = value => {
this.setState({
value,
data: [],
fetching: false,
});
};
uninstall = (timestamp = null) => {
const {value} = this.state;
const data = [];
value.map(val => {
data.push(val.key);
});
this.props.onUninstall("group", data, "uninstall", timestamp);
};
render() {
const {fetching, data, value} = this.state;
return (
<div>
<Text>Start uninstalling the application for one or more groups by entering the corresponding group
name. Select uninstall to automatically start uninstalling the application for the respective device
group/ groups.</Text>
{(this.state.isForbidden) && (
<Alert
message="You don't have permission to view installed groups."
type="warning"
banner
closable/>
)}
<br/>
<br/>
<Select
mode="multiple"
labelInValue
value={value}
placeholder="Search groups"
notFoundContent={fetching ? <Spin size="small"/> : null}
filterOption={false}
onSearch={this.fetchUser}
onChange={this.handleChange}
style={{width: '100%'}}>
{data.map(d => (
<Option key={d.value}>{d.text}</Option>
))}
</Select>
<InstallModalFooter type="Uninstall" operation={this.uninstall} disabled={value.length === 0}/>
</div>
this.setState({ data, fetching: false });
}
})
.catch(error => {
handleApiError(
error,
'Error occurred while trying to load groups.',
true,
);
}
if (error.hasOwnProperty('response') && error.response.status === 403) {
this.setState({
isForbidden: true,
loading: false,
});
} else {
this.setState({
loading: false,
});
}
});
};
handleChange = value => {
this.setState({
value,
data: [],
fetching: false,
});
};
uninstall = (timestamp = null) => {
const { value } = this.state;
const data = [];
value.map(val => {
data.push(val.key);
});
this.props.onUninstall('group', data, 'uninstall', timestamp);
};
render() {
const { fetching, data, value } = this.state;
return (
<div>
<Text>
Start uninstalling the application for one or more groups by entering
the corresponding group name. Select uninstall to automatically start
uninstalling the application for the respective device group/ groups.
</Text>
{this.state.isForbidden && (
<Alert
message="You don't have permission to view installed groups."
type="warning"
banner
closable
/>
)}
<br />
<br />
<Select
mode="multiple"
labelInValue
value={value}
placeholder="Search groups"
notFoundContent={fetching ? <Spin size="small" /> : null}
filterOption={false}
onSearch={this.fetchUser}
onChange={this.handleChange}
style={{ width: '100%' }}
>
{data.map(d => (
<Option key={d.value}>{d.text}</Option>
))}
</Select>
<InstallModalFooter
type="Uninstall"
operation={this.uninstall}
disabled={value.length === 0}
/>
</div>
);
}
}
export default withConfigContext(GroupUninstall);

View File

@ -16,123 +16,139 @@
* under the License.
*/
import React from "react";
import {Typography, Select, Spin, message, notification, Button, Alert} from "antd";
import React from 'react';
import { Typography, Select, Spin, Alert } from 'antd';
import debounce from 'lodash.debounce';
import axios from "axios";
import {withConfigContext} from "../../../../context/ConfigContext";
import {handleApiError} from "../../../../js/Utils";
import InstallModalFooter from "./installModalFooter/InstallModalFooter";
const {Text} = Typography;
const {Option} = Select;
import axios from 'axios';
import { withConfigContext } from '../../../../context/ConfigContext';
import { handleApiError } from '../../../../js/Utils';
import InstallModalFooter from './installModalFooter/InstallModalFooter';
const { Text } = Typography;
const { Option } = Select;
class RoleInstall extends React.Component {
constructor(props) {
super(props);
this.lastFetchId = 0;
this.fetchUser = debounce(this.fetchUser, 800);
}
constructor(props) {
super(props);
this.lastFetchId = 0;
this.fetchUser = debounce(this.fetchUser, 800);
}
state = {
data: [],
value: [],
fetching: false,
isForbidden: false,
};
state = {
data: [],
value: [],
fetching: false,
isForbidden: false
};
fetchUser = value => {
const config = this.props.context;
this.lastFetchId += 1;
const fetchId = this.lastFetchId;
this.setState({ data: [], fetching: true });
fetchUser = value => {
const config = this.props.context;
this.lastFetchId += 1;
const fetchId = this.lastFetchId;
this.setState({data: [], fetching: true});
axios
.get(
window.location.origin +
config.serverConfig.invoker.uri +
config.serverConfig.invoker.deviceMgt +
'/roles?filter=' +
value,
)
.then(res => {
if (res.status === 200) {
if (fetchId !== this.lastFetchId) {
// for fetch callback order
return;
}
axios.get(
window.location.origin+ config.serverConfig.invoker.uri + config.serverConfig.invoker.deviceMgt+"/roles?filter=" + value,
const data = res.data.data.roles.map(role => ({
text: role,
value: role,
}));
).then(res => {
if (res.status === 200) {
if (fetchId !== this.lastFetchId) {
// for fetch callback order
return;
}
const data = res.data.data.roles.map(role => ({
text: role,
value: role,
}));
this.setState({data, fetching: false});
}
}).catch((error) => {
handleApiError(error,"Error occurred while trying to load roles.", true);
if (error.hasOwnProperty("response") && error.response.status === 403) {
this.setState({
isForbidden: true,
loading: false
})
} else {
this.setState({
loading: false
});
}
});
};
handleChange = value => {
this.setState({
value,
data: [],
fetching: false,
});
};
install = (timestamp=null) =>{
const {value} = this.state;
const data = [];
value.map(val=>{
data.push(val.key);
});
this.props.onInstall("role", data, "install", timestamp);
};
render() {
const {fetching, data, value} = this.state;
return (
<div>
<Text>Start installing the application for one or more roles by entering the corresponding role name. Select install to automatically start downloading the application for the respective user role/roles.</Text>
{(this.state.isForbidden) && (
<Alert
message="You don't have permission to view roles."
type="warning"
banner
closable/>
)}
<br/>
<br/>
<Select
mode="multiple"
labelInValue
value={value}
placeholder="Search roles"
notFoundContent={fetching ? <Spin size="small"/> : null}
filterOption={false}
onSearch={this.fetchUser}
onChange={this.handleChange}
style={{width: '100%'}}>
{data.map(d => (
<Option key={d.value}>{d.text}</Option>
))}
</Select>
<InstallModalFooter type="Install" operation={this.install} disabled={value.length===0}/>
</div>
this.setState({ data, fetching: false });
}
})
.catch(error => {
handleApiError(
error,
'Error occurred while trying to load roles.',
true,
);
}
if (error.hasOwnProperty('response') && error.response.status === 403) {
this.setState({
isForbidden: true,
loading: false,
});
} else {
this.setState({
loading: false,
});
}
});
};
handleChange = value => {
this.setState({
value,
data: [],
fetching: false,
});
};
install = (timestamp = null) => {
const { value } = this.state;
const data = [];
value.map(val => {
data.push(val.key);
});
this.props.onInstall('role', data, 'install', timestamp);
};
render() {
const { fetching, data, value } = this.state;
return (
<div>
<Text>
Start installing the application for one or more roles by entering the
corresponding role name. Select install to automatically start
downloading the application for the respective user role/roles.
</Text>
{this.state.isForbidden && (
<Alert
message="You don't have permission to view roles."
type="warning"
banner
closable
/>
)}
<br />
<br />
<Select
mode="multiple"
labelInValue
value={value}
placeholder="Search roles"
notFoundContent={fetching ? <Spin size="small" /> : null}
filterOption={false}
onSearch={this.fetchUser}
onChange={this.handleChange}
style={{ width: '100%' }}
>
{data.map(d => (
<Option key={d.value}>{d.text}</Option>
))}
</Select>
<InstallModalFooter
type="Install"
operation={this.install}
disabled={value.length === 0}
/>
</div>
);
}
}
export default withConfigContext(RoleInstall);
export default withConfigContext(RoleInstall);

View File

@ -16,126 +16,143 @@
* under the License.
*/
import React from "react";
import {Typography, Select, Spin, message, notification, Button, Alert} from "antd";
import React from 'react';
import { Typography, Select, Spin, Alert } from 'antd';
import debounce from 'lodash.debounce';
import axios from "axios";
import {withConfigContext} from "../../../../context/ConfigContext";
import {handleApiError} from "../../../../js/Utils";
import InstallModalFooter from "./installModalFooter/InstallModalFooter";
import axios from 'axios';
import { withConfigContext } from '../../../../context/ConfigContext';
import { handleApiError } from '../../../../js/Utils';
import InstallModalFooter from './installModalFooter/InstallModalFooter';
const {Text} = Typography;
const {Option} = Select;
const { Text } = Typography;
const { Option } = Select;
class RoleUninstall extends React.Component {
constructor(props) {
super(props);
this.lastFetchId = 0;
this.fetchUser = debounce(this.fetchUser, 800);
}
constructor(props) {
super(props);
this.lastFetchId = 0;
this.fetchUser = debounce(this.fetchUser, 800);
}
state = {
data: [],
value: [],
fetching: false,
isForbidden: false,
};
state = {
data: [],
value: [],
fetching: false,
isForbidden: false
};
fetchUser = value => {
const config = this.props.context;
this.lastFetchId += 1;
const fetchId = this.lastFetchId;
this.setState({ data: [], fetching: true });
fetchUser = value => {
const config = this.props.context;
this.lastFetchId += 1;
const fetchId = this.lastFetchId;
this.setState({data: [], fetching: true});
const uuid = this.props.uuid;
const uuid = this.props.uuid;
axios
.get(
window.location.origin +
config.serverConfig.invoker.uri +
config.serverConfig.invoker.store +
'/subscription/' +
uuid +
'/' +
'/ROLE?',
)
.then(res => {
if (res.status === 200) {
if (fetchId !== this.lastFetchId) {
// for fetch callback order
return;
}
axios.get(
window.location.origin + config.serverConfig.invoker.uri + config.serverConfig.invoker.store + "/subscription/" + uuid + "/" +
"/ROLE?",
).then(res => {
if (res.status === 200) {
if (fetchId !== this.lastFetchId) {
// for fetch callback order
return;
}
const data = res.data.data.roles.map(role => ({
text: role,
value: role,
}));
const data = res.data.data.roles.map(role => ({
text: role,
value: role,
}));
this.setState({data, fetching: false});
}
}).catch((error) => {
handleApiError(error, "Error occurred while trying to load roles.", true);
if (error.hasOwnProperty("response") && error.response.status === 403) {
this.setState({
isForbidden: true,
loading: false
})
} else {
this.setState({
loading: false
});
}
});
};
handleChange = value => {
this.setState({
value,
data: [],
fetching: false,
});
};
uninstall = (timestamp = null) => {
const {value} = this.state;
const data = [];
value.map(val => {
data.push(val.key);
});
this.props.onUninstall("role", data, "uninstall", timestamp);
};
render() {
const {fetching, data, value} = this.state;
return (
<div>
<Text>Start uninstalling the application for one or more roles by entering the corresponding role name.
Select uninstall to automatically start uninstalling the application for the respective user
role/roles.</Text>
{(this.state.isForbidden) && (
<Alert
message="You don't have permission to view uninstalled roles."
type="warning"
banner
closable/>
)}
<br/>
<br/>
<Select
mode="multiple"
labelInValue
value={value}
placeholder="Search roles"
notFoundContent={fetching ? <Spin size="small"/> : null}
filterOption={false}
onSearch={this.fetchUser}
onChange={this.handleChange}
style={{width: '100%'}}
>
{data.map(d => (
<Option key={d.value}>{d.text}</Option>
))}
</Select>
<InstallModalFooter type="Uninstall" operation={this.uninstall} disabled={value.length === 0}/>
</div>
this.setState({ data, fetching: false });
}
})
.catch(error => {
handleApiError(
error,
'Error occurred while trying to load roles.',
true,
);
}
if (error.hasOwnProperty('response') && error.response.status === 403) {
this.setState({
isForbidden: true,
loading: false,
});
} else {
this.setState({
loading: false,
});
}
});
};
handleChange = value => {
this.setState({
value,
data: [],
fetching: false,
});
};
uninstall = (timestamp = null) => {
const { value } = this.state;
const data = [];
value.map(val => {
data.push(val.key);
});
this.props.onUninstall('role', data, 'uninstall', timestamp);
};
render() {
const { fetching, data, value } = this.state;
return (
<div>
<Text>
Start uninstalling the application for one or more roles by entering
the corresponding role name. Select uninstall to automatically start
uninstalling the application for the respective user role/roles.
</Text>
{this.state.isForbidden && (
<Alert
message="You don't have permission to view uninstalled roles."
type="warning"
banner
closable
/>
)}
<br />
<br />
<Select
mode="multiple"
labelInValue
value={value}
placeholder="Search roles"
notFoundContent={fetching ? <Spin size="small" /> : null}
filterOption={false}
onSearch={this.fetchUser}
onChange={this.handleChange}
style={{ width: '100%' }}
>
{data.map(d => (
<Option key={d.value}>{d.text}</Option>
))}
</Select>
<InstallModalFooter
type="Uninstall"
operation={this.uninstall}
disabled={value.length === 0}
/>
</div>
);
}
}
export default withConfigContext(RoleUninstall);

View File

@ -16,124 +16,139 @@
* under the License.
*/
import React from "react";
import {Typography, Select, Spin, message, notification, Button, Alert} from "antd";
import React from 'react';
import { Typography, Select, Spin, Alert } from 'antd';
import debounce from 'lodash.debounce';
import axios from "axios";
import {withConfigContext} from "../../../../context/ConfigContext";
import {handleApiError} from "../../../../js/Utils";
import InstallModalFooter from "./installModalFooter/InstallModalFooter";
const {Text} = Typography;
const {Option} = Select;
import axios from 'axios';
import { withConfigContext } from '../../../../context/ConfigContext';
import { handleApiError } from '../../../../js/Utils';
import InstallModalFooter from './installModalFooter/InstallModalFooter';
const { Text } = Typography;
const { Option } = Select;
class UserInstall extends React.Component {
constructor(props) {
super(props);
this.lastFetchId = 0;
this.fetchUser = debounce(this.fetchUser, 800);
}
constructor(props) {
super(props);
this.lastFetchId = 0;
this.fetchUser = debounce(this.fetchUser, 800);
}
state = {
data: [],
value: [],
fetching: false,
};
state = {
data: [],
value: [],
fetching: false,
};
fetchUser = value => {
const config = this.props.context;
this.lastFetchId += 1;
const fetchId = this.lastFetchId;
this.setState({ data: [], fetching: true });
fetchUser = value => {
const config = this.props.context;
this.lastFetchId += 1;
const fetchId = this.lastFetchId;
this.setState({data: [], fetching: true});
// send request to the invoker
axios
.get(
window.location.origin +
config.serverConfig.invoker.uri +
config.serverConfig.invoker.deviceMgt +
'/users/search?username=' +
value,
)
.then(res => {
if (res.status === 200) {
if (fetchId !== this.lastFetchId) {
// for fetch callback order
return;
}
const data = res.data.data.users.map(user => ({
text: user.username,
value: user.username,
}));
//send request to the invoker
axios.get(
window.location.origin+ config.serverConfig.invoker.uri + config.serverConfig.invoker.deviceMgt+"/users/search?username=" + value,
).then(res => {
if (res.status === 200) {
if (fetchId !== this.lastFetchId) {
// for fetch callback order
return;
}
const data = res.data.data.users.map(user => ({
text: user.username,
value: user.username,
}));
this.setState({data, fetching: false});
}
}).catch((error) => {
handleApiError(error,"Error occurred while trying to load users.", true);
if (error.hasOwnProperty("response") && error.response.status === 403) {
this.setState({
isForbidden: true,
loading: false
})
} else {
this.setState({
loading: false
});
}
});
};
handleChange = value => {
this.setState({
value,
data: [],
fetching: false,
isForbidden: false
});
};
install = (timestamp=null) => {
const {value} = this.state;
const data = [];
value.map(val => {
data.push(val.key);
});
this.props.onInstall("user", data, "install",timestamp);
};
render() {
const {fetching, data, value} = this.state;
return (
<div>
<Text>Start installing the application for one or more users by entering the corresponding user name. Select install to automatically start downloading the application for the respective user/users. </Text>
{(this.state.isForbidden) && (
<Alert
message="You don't have permission to view users."
type="warning"
banner
closable/>
)}
<p>Select users</p>
<Select
mode="multiple"
labelInValue
value={value}
placeholder="Enter the username"
notFoundContent={fetching ? <Spin size="small"/> : null}
filterOption={false}
onSearch={this.fetchUser}
onChange={this.handleChange}
style={{width: '100%'}}
>
{data.map(d => (
<Option key={d.value}>{d.text}</Option>
))}
</Select>
<InstallModalFooter type="Install" operation={this.install} disabled={value.length===0}/>
</div>
this.setState({ data, fetching: false });
}
})
.catch(error => {
handleApiError(
error,
'Error occurred while trying to load users.',
true,
);
}
if (error.hasOwnProperty('response') && error.response.status === 403) {
this.setState({
isForbidden: true,
loading: false,
});
} else {
this.setState({
loading: false,
});
}
});
};
handleChange = value => {
this.setState({
value,
data: [],
fetching: false,
isForbidden: false,
});
};
install = (timestamp = null) => {
const { value } = this.state;
const data = [];
value.map(val => {
data.push(val.key);
});
this.props.onInstall('user', data, 'install', timestamp);
};
render() {
const { fetching, data, value } = this.state;
return (
<div>
<Text>
Start installing the application for one or more users by entering the
corresponding user name. Select install to automatically start
downloading the application for the respective user/users.{' '}
</Text>
{this.state.isForbidden && (
<Alert
message="You don't have permission to view users."
type="warning"
banner
closable
/>
)}
<p>Select users</p>
<Select
mode="multiple"
labelInValue
value={value}
placeholder="Enter the username"
notFoundContent={fetching ? <Spin size="small" /> : null}
filterOption={false}
onSearch={this.fetchUser}
onChange={this.handleChange}
style={{ width: '100%' }}
>
{data.map(d => (
<Option key={d.value}>{d.text}</Option>
))}
</Select>
<InstallModalFooter
type="Install"
operation={this.install}
disabled={value.length === 0}
/>
</div>
);
}
}
export default withConfigContext(UserInstall);
export default withConfigContext(UserInstall);

View File

@ -16,123 +16,141 @@
* under the License.
*/
import React from "react";
import {Typography, Select, Spin, message, notification, Button, Alert} from "antd";
import React from 'react';
import { Typography, Select, Spin, Alert } from 'antd';
import debounce from 'lodash.debounce';
import axios from "axios";
import {withConfigContext} from "../../../../context/ConfigContext";
import {handleApiError} from "../../../../js/Utils";
import InstallModalFooter from "./installModalFooter/InstallModalFooter";
import axios from 'axios';
import { withConfigContext } from '../../../../context/ConfigContext';
import { handleApiError } from '../../../../js/Utils';
import InstallModalFooter from './installModalFooter/InstallModalFooter';
const {Text} = Typography;
const {Option} = Select;
const { Text } = Typography;
const { Option } = Select;
class UserUninstall extends React.Component {
constructor(props) {
super(props);
this.lastFetchId = 0;
this.fetchUser = debounce(this.fetchUser, 800);
}
constructor(props) {
super(props);
this.lastFetchId = 0;
this.fetchUser = debounce(this.fetchUser, 800);
}
state = {
data: [],
value: [],
fetching: false,
isForbidden: false,
};
state = {
data: [],
value: [],
fetching: false,
isForbidden: false
};
fetchUser = value => {
const config = this.props.context;
this.lastFetchId += 1;
const fetchId = this.lastFetchId;
this.setState({ data: [], fetching: true });
fetchUser = (value) => {
const config = this.props.context;
this.lastFetchId += 1;
const fetchId = this.lastFetchId;
this.setState({data: [], fetching: true});
const uuid = this.props.uuid;
const uuid = this.props.uuid;
axios
.get(
window.location.origin +
config.serverConfig.invoker.uri +
config.serverConfig.invoker.store +
'/subscription/' +
uuid +
'/' +
'/USER?',
)
.then(res => {
if (res.status === 200) {
if (fetchId !== this.lastFetchId) {
// for fetch callback order
return;
}
const data = res.data.data.users.map(user => ({
text: user,
value: user,
}));
axios.get(
window.location.origin + config.serverConfig.invoker.uri + config.serverConfig.invoker.store + "/subscription/" + uuid + "/" +
"/USER?",
).then(res => {
if (res.status === 200) {
if (fetchId !== this.lastFetchId) {
// for fetch callback order
return;
}
const data = res.data.data.users.map(user => ({
text: user,
value: user,
}));
this.setState({data, fetching: false});
}
}).catch((error) => {
handleApiError(error, "Error occurred while trying to load users.", true);
if (error.hasOwnProperty("response") && error.response.status === 403) {
this.setState({
isForbidden: true,
loading: false
})
} else {
this.setState({
loading: false
});
}
});
};
handleChange = value => {
this.setState({
value,
data: [],
fetching: false,
});
};
uninstall = (timestamp=null) => {
const {value} = this.state;
const data = [];
value.map(val => {
data.push(val.key);
});
this.props.onUninstall("user", data, "uninstall",timestamp);
};
render() {
const {fetching, data, value} = this.state;
return (
<div>
<Text>Start uninstalling the application for one or more users by entering the corresponding user name.
Select uninstall to automatically start uninstalling the application for the respective
user/users. </Text>
{(this.state.isForbidden) && (
<Alert
message="You don't have permission to view uninstalled users."
type="warning"
banner
closable/>
)}
<p>Select users</p>
<Select
mode="multiple"
labelInValue
value={value}
placeholder="Enter the username"
notFoundContent={fetching ? <Spin size="small"/> : null}
filterOption={false}
onSearch={this.fetchUser}
onChange={this.handleChange}
style={{width: '100%'}}>
{data.map(d => (
<Option key={d.value}>{d.text}</Option>
))}
</Select>
<InstallModalFooter type="Uninstall" operation={this.uninstall} disabled={value.length === 0}/>
</div>
this.setState({ data, fetching: false });
}
})
.catch(error => {
handleApiError(
error,
'Error occurred while trying to load users.',
true,
);
}
if (error.hasOwnProperty('response') && error.response.status === 403) {
this.setState({
isForbidden: true,
loading: false,
});
} else {
this.setState({
loading: false,
});
}
});
};
handleChange = value => {
this.setState({
value,
data: [],
fetching: false,
});
};
uninstall = (timestamp = null) => {
const { value } = this.state;
const data = [];
value.map(val => {
data.push(val.key);
});
this.props.onUninstall('user', data, 'uninstall', timestamp);
};
render() {
const { fetching, data, value } = this.state;
return (
<div>
<Text>
Start uninstalling the application for one or more users by entering
the corresponding user name. Select uninstall to automatically start
uninstalling the application for the respective user/users.{' '}
</Text>
{this.state.isForbidden && (
<Alert
message="You don't have permission to view uninstalled users."
type="warning"
banner
closable
/>
)}
<p>Select users</p>
<Select
mode="multiple"
labelInValue
value={value}
placeholder="Enter the username"
notFoundContent={fetching ? <Spin size="small" /> : null}
filterOption={false}
onSearch={this.fetchUser}
onChange={this.handleChange}
style={{ width: '100%' }}
>
{data.map(d => (
<Option key={d.value}>{d.text}</Option>
))}
</Select>
<InstallModalFooter
type="Uninstall"
operation={this.uninstall}
disabled={value.length === 0}
/>
</div>
);
}
}
export default withConfigContext(UserUninstall);

View File

@ -16,72 +16,84 @@
* under the License.
*/
import React from "react";
import {Button, DatePicker, Checkbox} from "antd";
import React from 'react';
import { Button, DatePicker, Checkbox } from 'antd';
class InstallModalFooter extends React.Component {
constructor(props) {
super(props);
this.state = {
scheduledTime: null,
isScheduledInstallVisible: false
}
constructor(props) {
super(props);
this.state = {
scheduledTime: null,
isScheduledInstallVisible: false,
};
}
onDateTimeChange = (value, dateString) => {
this.setState({
scheduledTime: dateString,
});
};
toggleScheduledInstall = () => {
this.setState({
isScheduledInstallVisible: !this.state.isScheduledInstallVisible,
});
};
triggerInstallOperation = () => {
const { scheduledTime, isScheduledInstallVisible } = this.state;
if (isScheduledInstallVisible && scheduledTime != null) {
this.props.operation(scheduledTime);
} else {
this.props.operation();
}
};
onDateTimeChange = (value, dateString) => {
this.setState({
scheduledTime: dateString
});
};
toggleScheduledInstall = () => {
this.setState({
isScheduledInstallVisible: !this.state.isScheduledInstallVisible
})
};
triggerInstallOperation = () => {
const {scheduledTime, isScheduledInstallVisible} = this.state;
if (isScheduledInstallVisible && scheduledTime != null) {
this.props.operation(scheduledTime);
} else {
this.props.operation();
}
};
render() {
const {scheduledTime, isScheduledInstallVisible} = this.state;
const {disabled, type} = this.props;
return (
<div>
<div style={{
textAlign: "right"
}}>
<div style={{margin: 8}}>
<Checkbox checked={this.state.isScheduledInstallVisible} onChange={this.toggleScheduledInstall}>
Schedule {type}
</Checkbox>
</div>
<span style={{
display: (isScheduledInstallVisible) ? 'inline' : 'none'
}}>
<DatePicker showTime
placeholder="Select Time"
format="YYYY-MM-DDTHH:mm"
onChange={this.onDateTimeChange}/>
</span>
<Button style={{margin: 5}}
disabled={disabled || (isScheduledInstallVisible && scheduledTime == null)}
htmlType="button"
type="primary"
onClick={this.triggerInstallOperation}>
{type}
</Button>
</div>
</div>
);
}
render() {
const { scheduledTime, isScheduledInstallVisible } = this.state;
const { disabled, type } = this.props;
return (
<div>
<div
style={{
textAlign: 'right',
}}
>
<div style={{ margin: 8 }}>
<Checkbox
checked={this.state.isScheduledInstallVisible}
onChange={this.toggleScheduledInstall}
>
Schedule {type}
</Checkbox>
</div>
<span
style={{
display: isScheduledInstallVisible ? 'inline' : 'none',
}}
>
<DatePicker
showTime
placeholder="Select Time"
format="YYYY-MM-DDTHH:mm"
onChange={this.onDateTimeChange}
/>
</span>
<Button
style={{ margin: 5 }}
disabled={
disabled || (isScheduledInstallVisible && scheduledTime == null)
}
htmlType="button"
type="primary"
onClick={this.triggerInstallOperation}
>
{type}
</Button>
</div>
</div>
);
}
}
export default InstallModalFooter;
export default InstallModalFooter;

View File

@ -16,155 +16,170 @@
* under the License.
*/
import React from "react";
import {Drawer, Button, Icon, Row, Col, Typography, Divider, Input, Spin, notification} from 'antd';
import StarRatings from "react-star-ratings";
import axios from "axios";
import {withConfigContext} from "../../../../context/ConfigContext";
import {handleApiError} from "../../../../js/Utils";
import React from 'react';
import {
Drawer,
Button,
Icon,
Row,
Col,
Typography,
Divider,
Input,
Spin,
notification,
} from 'antd';
import StarRatings from 'react-star-ratings';
import axios from 'axios';
import { withConfigContext } from '../../../../context/ConfigContext';
import { handleApiError } from '../../../../js/Utils';
const {Title} = Typography;
const {TextArea} = Input;
const { Title } = Typography;
const { TextArea } = Input;
class AddReview extends React.Component {
state = {
visible: false,
content: '',
rating: 0,
loading: false
state = {
visible: false,
content: '',
rating: 0,
loading: false,
};
showDrawer = () => {
this.setState({
visible: true,
content: '',
rating: 0,
loading: false,
});
};
onClose = () => {
this.setState({
visible: false,
});
};
changeRating = (newRating, name) => {
this.setState({
rating: newRating,
});
};
onChange = e => {
this.setState({ content: e.target.value });
};
onSubmit = () => {
const config = this.props.context;
const { content, rating } = this.state;
const { uuid } = this.props;
this.setState({
loading: true,
});
const payload = {
content: content,
rating: rating,
};
showDrawer = () => {
this.setState({
visible: true,
content: '',
rating: 0,
loading: false
});
};
onClose = () => {
this.setState({
axios
.post(
window.location.origin +
config.serverConfig.invoker.uri +
config.serverConfig.invoker.store +
'/reviews/' +
uuid,
payload,
)
.then(res => {
if (res.status === 201) {
this.setState({
loading: false,
visible: false,
});
};
changeRating = (newRating, name) => {
});
notification.success({
message: 'Done!',
description: 'Your review has been posted successfully.',
});
this.props.onUpdateReview();
} else {
this.setState({
loading: false,
visible: false,
});
notification.error({
message: 'There was a problem',
duration: 0,
description: 'We are unable to add your review right now.',
});
}
})
.catch(error => {
handleApiError(error, 'We are unable to add your review right now.');
this.setState({
rating: newRating
loading: false,
visible: false,
});
};
});
};
onChange = (e) => {
this.setState({content: e.target.value})
};
render() {
return (
<div>
<Button type="primary" onClick={this.showDrawer}>
<Icon type="star" /> Add a review
</Button>
onSubmit = () => {
const config = this.props.context;
const {content, rating} = this.state;
const {uuid} = this.props;
this.setState({
loading: true
});
const payload = {
content: content,
rating: rating
};
axios.post(
window.location.origin+ config.serverConfig.invoker.uri + config.serverConfig.invoker.store + "/reviews/" + uuid,
payload,
).then(res => {
if (res.status === 201) {
this.setState({
loading: false,
visible: false
});
notification["success"]({
message: 'Done!',
description:
'Your review has been posted successfully.',
});
this.props.onUpdateReview();
} else {
this.setState({
loading: false,
visible: false
});
notification["error"]({
message: "There was a problem",
duration: 0,
description:
"We are unable to add your review right now.",
});
}
}).catch((error) => {
handleApiError(error,"We are unable to add your review right now.");
this.setState({
loading: false,
visible: false
});
});
};
render() {
return (
<div>
<Button type="primary" onClick={this.showDrawer}>
<Icon type="star"/> Add a review
<Drawer
// title="Basic Drawer"
placement="bottom"
closable={false}
onClose={this.onClose}
visible={this.state.visible}
height={400}
>
<Spin spinning={this.state.loading} tip="Posting your review...">
<Row>
<Col lg={8} />
<Col lg={8}>
<Title level={4}>Add review</Title>
<Divider />
<TextArea
placeholder="Tell others what you think about this app. Would you recommend it, and why?"
onChange={this.onChange}
rows={4}
value={this.state.content || ''}
style={{ marginBottom: 20 }}
/>
<StarRatings
rating={this.state.rating}
changeRating={this.changeRating}
starRatedColor="#777"
starHoverColor="#444"
starDimension="20px"
starSpacing="2px"
numberOfStars={5}
name="rating"
/>
<br />
<br />
<Button onClick={this.onClose} style={{ marginRight: 8 }}>
Cancel
</Button>
<Drawer
// title="Basic Drawer"
placement="bottom"
closable={false}
onClose={this.onClose}
visible={this.state.visible}
height={400}
<Button
disabled={this.state.rating === 0}
onClick={this.onSubmit}
type="primary"
>
<Spin spinning={this.state.loading} tip="Posting your review...">
<Row>
<Col lg={8}/>
<Col lg={8}>
<Title level={4}>Add review</Title>
<Divider/>
<TextArea
placeholder="Tell others what you think about this app. Would you recommend it, and why?"
onChange={this.onChange}
rows={4}
value={this.state.content || ''}
style={{marginBottom: 20}}
/>
<StarRatings
rating={this.state.rating}
changeRating={this.changeRating}
starRatedColor="#777"
starHoverColor="#444"
starDimension="20px"
starSpacing="2px"
numberOfStars={5}
name='rating'
/>
<br/><br/>
<Button onClick={this.onClose} style={{marginRight: 8}}>
Cancel
</Button>
<Button disabled={this.state.rating === 0} onClick={this.onSubmit} type="primary">
Submit
</Button>
</Col>
</Row>
</Spin>
</Drawer>
</div>
);
}
Submit
</Button>
</Col>
</Row>
</Spin>
</Drawer>
</div>
);
}
}
export default withConfigContext(AddReview);
export default withConfigContext(AddReview);

View File

@ -16,81 +16,84 @@
* under the License.
*/
import React from "react";
import {List, message, Typography, Empty, Button, Row, Col, notification, Alert} from "antd";
import SingleReview from "./singleReview/SingleReview";
import axios from "axios";
import AddReview from "./AddReview";
import {withConfigContext} from "../../../../context/ConfigContext";
import {handleApiError} from "../../../../js/Utils";
import React from 'react';
import { List, Typography, Empty, Alert } from 'antd';
import SingleReview from './singleReview/SingleReview';
import AddReview from './AddReview';
import { withConfigContext } from '../../../../context/ConfigContext';
const {Text, Paragraph} = Typography;
const { Text } = Typography;
class CurrentUsersReview extends React.Component {
render() {
const { uuid, currentUserReviews } = this.props;
return (
<div>
<Text>MY REVIEW</Text>
{this.props.forbidden && (
<Alert
message="You don't have permission to add reviews."
type="warning"
banner
closable
/>
)}
{!this.props.forbidden && (
<div
style={{
overflow: 'auto',
paddingTop: 8,
paddingLeft: 24,
}}
>
{currentUserReviews.length > 0 && (
<div>
<List
dataSource={currentUserReviews}
renderItem={item => (
<List.Item key={item.id}>
<SingleReview
uuid={uuid}
review={item}
isDeletable={true}
isEditable={true}
deleteCallback={this.props.deleteCallback}
onUpdateReview={this.props.onUpdateReview}
isPersonalReview={true}
/>
</List.Item>
)}
/>
</div>
)}
render() {
const {uuid, currentUserReviews} = this.props;
return (
<div>
<Text>MY REVIEW</Text>
{(this.props.forbidden) && (
<Alert
message="You don't have permission to add reviews."
type="warning"
banner
closable/>
)}
{(!this.props.forbidden) && (
<div style={{
overflow: "auto",
paddingTop: 8,
paddingLeft: 24
}}>
{currentUserReviews.length > 0 && (
<div>
<List
dataSource={currentUserReviews}
renderItem={item => (
<List.Item key={item.id}>
<SingleReview
uuid={uuid}
review={item}
isDeletable={true}
isEditable={true}
deleteCallback={this.props.deleteCallback}
onUpdateReview={this.props.onUpdateReview}
isPersonalReview={true}/>
</List.Item>
)}
>
</List>
</div>
)}
{currentUserReviews.length === 0 && (
<div>
<Empty
image={Empty.PRESENTED_IMAGE_DEFAULT}
imagestyle={{
height: 60,
}}
description={
<span>Share your experience with your community by adding a review.</span>
}>
{/*<Button type="primary">Add review</Button>*/}
<AddReview
uuid={uuid}
onUpdateReview={this.props.onUpdateReview}/>
</Empty>
</div>
)}
</div>
)}
</div>
);
}
{currentUserReviews.length === 0 && (
<div>
<Empty
image={Empty.PRESENTED_IMAGE_DEFAULT}
imagestyle={{
height: 60,
}}
description={
<span>
Share your experience with your community by adding a
review.
</span>
}
>
{/* <Button type="primary">Add review</Button>*/}
<AddReview
uuid={uuid}
onUpdateReview={this.props.onUpdateReview}
/>
</Empty>
</div>
)}
</div>
)}
</div>
);
}
}
export default withConfigContext(CurrentUsersReview);
export default withConfigContext(CurrentUsersReview);

View File

@ -16,122 +16,141 @@
* under the License.
*/
import React from "react";
import CurrentUsersReview from "./CurrentUsersReview";
import {Col, Divider, Row, Typography} from "antd";
import DetailedRating from "../DetailedRating";
import Reviews from "./Reviews";
import axios from "axios";
import {handleApiError} from "../../../../js/Utils";
import {withConfigContext} from "../../../../context/ConfigContext";
import React from 'react';
import CurrentUsersReview from './CurrentUsersReview';
import { Col, Divider, Row, Typography } from 'antd';
import DetailedRating from '../DetailedRating';
import Reviews from './Reviews';
import axios from 'axios';
import { handleApiError } from '../../../../js/Utils';
import { withConfigContext } from '../../../../context/ConfigContext';
const {Text} = Typography;
const { Text } = Typography;
class ReviewContainer extends React.Component {
constructor(props) {
super(props);
this.state = {
currentUserReviews: [],
detailedRating: null,
forbiddenErrors: {
currentReview: false,
reviews: false,
rating: false
}
constructor(props) {
super(props);
this.state = {
currentUserReviews: [],
detailedRating: null,
forbiddenErrors: {
currentReview: false,
reviews: false,
rating: false,
},
};
}
componentDidMount() {
this.fetchCurrentUserReviews();
this.fetchDetailedRating('app', this.props.uuid);
}
fetchCurrentUserReviews = () => {
const { uuid } = this.props;
const config = this.props.context;
axios
.get(
window.location.origin +
config.serverConfig.invoker.uri +
config.serverConfig.invoker.store +
'/reviews/app/user/' +
uuid,
)
.then(res => {
if (res.status === 200) {
const currentUserReviews = res.data.data.data;
this.setState({ currentUserReviews });
}
}
})
.catch(error => {
handleApiError(
error,
'Error occurred while trying to get your review.',
true,
);
if (error.hasOwnProperty('response') && error.response.status === 403) {
const { forbiddenErrors } = this.state;
forbiddenErrors.currentReview = true;
this.setState({
forbiddenErrors,
loading: false,
});
} else {
this.setState({
loading: false,
});
}
});
};
componentDidMount() {
this.fetchCurrentUserReviews();
this.fetchDetailedRating("app", this.props.uuid);
}
deleteCurrentUserReviewCallback = () => {
this.setState({
currentUserReviews: [],
});
this.fetchDetailedRating('app', this.props.uuid);
};
fetchCurrentUserReviews = () => {
const {uuid} = this.props;
const config = this.props.context;
fetchDetailedRating = (type, uuid) => {
const config = this.props.context;
axios.get(
window.location.origin + config.serverConfig.invoker.uri + config.serverConfig.invoker.store + "/reviews/app/user/" + uuid,
).then(res => {
if (res.status === 200) {
const currentUserReviews = res.data.data.data;
this.setState({currentUserReviews});
}
axios
.get(
window.location.origin +
config.serverConfig.invoker.uri +
config.serverConfig.invoker.store +
'/reviews/' +
uuid +
'/' +
type +
'-rating',
)
.then(res => {
if (res.status === 200) {
let detailedRating = res.data.data;
this.setState({
detailedRating,
});
}
})
.catch(function(error) {
handleApiError(
error,
'Error occurred while trying to load ratings.',
true,
);
});
};
}).catch((error) => {
handleApiError(error, "Error occurred while trying to get your review.", true);
if (error.hasOwnProperty("response") && error.response.status === 403) {
const {forbiddenErrors} = this.state;
forbiddenErrors.currentReview = true;
this.setState({
forbiddenErrors,
loading: false
})
} else {
this.setState({
loading: false
});
}
});
};
onUpdateReview = () => {
this.fetchCurrentUserReviews();
this.fetchDetailedRating('app', this.props.uuid);
};
deleteCurrentUserReviewCallback = () => {
this.setState({
currentUserReviews: []
});
this.fetchDetailedRating("app", this.props.uuid);
};
fetchDetailedRating = (type, uuid) => {
const config = this.props.context;
axios.get(
window.location.origin + config.serverConfig.invoker.uri + config.serverConfig.invoker.store + "/reviews/" + uuid + "/" + type + "-rating",
).then(res => {
if (res.status === 200) {
let detailedRating = res.data.data;
this.setState({
detailedRating
})
}
}).catch(function (error) {
handleApiError(error, "Error occurred while trying to load ratings.", true);
});
};
onUpdateReview = () => {
this.fetchCurrentUserReviews();
this.fetchDetailedRating("app", this.props.uuid);
};
render() {
const {uuid} = this.props;
const {currentUserReviews,detailedRating, forbiddenErrors} = this.state;
return (
<div>
<CurrentUsersReview
forbidden={forbiddenErrors.currentReview}
uuid={uuid}
currentUserReviews={currentUserReviews}
onUpdateReview={this.onUpdateReview}
deleteCallback={this.deleteCurrentUserReviewCallback}/>
<Divider dashed={true}/>
<Text>REVIEWS</Text>
<Row>
<Col lg={18} md={24}>
<DetailedRating
type="app"
detailedRating={detailedRating}/>
</Col>
</Row>
<Reviews
type="app"
uuid={uuid}
deleteCallback={this.onUpdateReview}/>
</div>
)
}
render() {
const { uuid } = this.props;
const { currentUserReviews, detailedRating, forbiddenErrors } = this.state;
return (
<div>
<CurrentUsersReview
forbidden={forbiddenErrors.currentReview}
uuid={uuid}
currentUserReviews={currentUserReviews}
onUpdateReview={this.onUpdateReview}
deleteCallback={this.deleteCurrentUserReviewCallback}
/>
<Divider dashed={true} />
<Text>REVIEWS</Text>
<Row>
<Col lg={18} md={24}>
<DetailedRating type="app" detailedRating={detailedRating} />
</Col>
</Row>
<Reviews type="app" uuid={uuid} deleteCallback={this.onUpdateReview} />
</div>
);
}
}
export default withConfigContext(ReviewContainer);
export default withConfigContext(ReviewContainer);

View File

@ -16,156 +16,183 @@
* under the License.
*/
import React from "react";
import {List, message, Avatar, Spin, Button, notification, Alert} from 'antd';
import "./Reviews.css";
import React from 'react';
import { List, Spin, Button, Alert } from 'antd';
import './Reviews.css';
import InfiniteScroll from 'react-infinite-scroller';
import SingleReview from "./singleReview/SingleReview";
import axios from "axios";
import {withConfigContext} from "../../../../context/ConfigContext";
import {handleApiError} from "../../../../js/Utils";
import SingleReview from './singleReview/SingleReview';
import axios from 'axios';
import { withConfigContext } from '../../../../context/ConfigContext';
import { handleApiError } from '../../../../js/Utils';
const limit = 5;
class Reviews extends React.Component {
state = {
data: [],
loading: false,
hasMore: false,
loadMore: false,
forbiddenErrors: {
reviews: false
state = {
data: [],
loading: false,
hasMore: false,
loadMore: false,
forbiddenErrors: {
reviews: false,
},
};
componentDidMount() {
this.fetchData(0, limit, res => {
this.setState({
data: res,
});
});
}
fetchData = (offset, limit, callback) => {
const { uuid, type } = this.props;
const config = this.props.context;
axios
.get(
window.location.origin +
config.serverConfig.invoker.uri +
config.serverConfig.invoker.store +
'/reviews/' +
type +
'/' +
uuid,
{
headers: { 'X-Platform': config.serverConfig.platform },
},
)
.then(res => {
if (res.status === 200) {
let reviews = res.data.data.data;
callback(reviews);
}
};
componentDidMount() {
this.fetchData(0, limit, res => {
this.setState({
data: res,
});
});
}
fetchData = (offset, limit, callback) => {
const {uuid, type} = this.props;
const config = this.props.context;
axios.get(
window.location.origin + config.serverConfig.invoker.uri + config.serverConfig.invoker.store + "/reviews/" + type + "/" + uuid,
{
headers: {'X-Platform': config.serverConfig.platform}
}).then(res => {
if (res.status === 200) {
let reviews = res.data.data.data;
callback(reviews);
}
}).catch((error) => {
handleApiError(error, "Error occurred while trying to load reviews.", true);
if (error.hasOwnProperty("response") && error.response.status === 403) {
const {forbiddenErrors} = this.state;
forbiddenErrors.reviews = true;
this.setState({
forbiddenErrors,
loading: false
})
} else {
this.setState({
loading: false
});
}
});
};
handleInfiniteOnLoad = (count) => {
const offset = count * limit;
let data = this.state.data;
this.setState({
loading: true,
});
if (data.length > 149) {
this.setState({
hasMore: false,
loading: false,
});
return;
}
this.fetchData(offset, limit, res => {
if (res.length > 0) {
data = data.concat(res);
this.setState({
data,
loading: false,
});
} else {
this.setState({
hasMore: false,
loading: false
});
}
});
};
enableLoading = () => {
this.setState({
hasMore: true,
loadMore: true
});
};
deleteCallback = () => {
this.fetchData(0, limit, res => {
this.setState({
data: res,
});
});
this.props.deleteCallback();
};
render() {
const {loading, hasMore, data, loadMore} = this.state;
const {uuid} = this.props;
return (
<div>
{(this.state.forbiddenErrors.reviews) && (
<Alert
message="You don't have permission to view reviews."
type="warning"
banner
closable/>
)}
<div className="infinite-container">
<InfiniteScroll
initialLoad={false}
pageStart={0}
loadMore={this.handleInfiniteOnLoad}
hasMore={!loading && hasMore}
useWindow={true}>
<List
dataSource={data}
renderItem={item => (
<List.Item key={item.id}>
<SingleReview uuid={uuid} review={item} isDeletable={true} isEditable={false}
deleteCallback={this.deleteCallback}/>
</List.Item>
)}>
{loading && hasMore && (
<div className="loading-container">
<Spin/>
</div>
)}
</List>
</InfiniteScroll>
{!loadMore && (data.length >= limit) && (<div style={{textAlign: "center"}}>
<Button type="dashed" htmlType="button" onClick={this.enableLoading}>Read All Reviews</Button>
</div>)}
</div>
</div>
})
.catch(error => {
handleApiError(
error,
'Error occurred while trying to load reviews.',
true,
);
if (error.hasOwnProperty('response') && error.response.status === 403) {
const { forbiddenErrors } = this.state;
forbiddenErrors.reviews = true;
this.setState({
forbiddenErrors,
loading: false,
});
} else {
this.setState({
loading: false,
});
}
});
};
handleInfiniteOnLoad = count => {
const offset = count * limit;
let data = this.state.data;
this.setState({
loading: true,
});
if (data.length > 149) {
this.setState({
hasMore: false,
loading: false,
});
return;
}
this.fetchData(offset, limit, res => {
if (res.length > 0) {
data = data.concat(res);
this.setState({
data,
loading: false,
});
} else {
this.setState({
hasMore: false,
loading: false,
});
}
});
};
enableLoading = () => {
this.setState({
hasMore: true,
loadMore: true,
});
};
deleteCallback = () => {
this.fetchData(0, limit, res => {
this.setState({
data: res,
});
});
this.props.deleteCallback();
};
render() {
const { loading, hasMore, data, loadMore } = this.state;
const { uuid } = this.props;
return (
<div>
{this.state.forbiddenErrors.reviews && (
<Alert
message="You don't have permission to view reviews."
type="warning"
banner
closable
/>
)}
<div className="infinite-container">
<InfiniteScroll
initialLoad={false}
pageStart={0}
loadMore={this.handleInfiniteOnLoad}
hasMore={!loading && hasMore}
useWindow={true}
>
<List
dataSource={data}
renderItem={item => (
<List.Item key={item.id}>
<SingleReview
uuid={uuid}
review={item}
isDeletable={true}
isEditable={false}
deleteCallback={this.deleteCallback}
/>
</List.Item>
)}
>
{loading && hasMore && (
<div className="loading-container">
<Spin />
</div>
)}
</List>
</InfiniteScroll>
{!loadMore && data.length >= limit && (
<div style={{ textAlign: 'center' }}>
<Button
type="dashed"
htmlType="button"
onClick={this.enableLoading}
>
Read All Reviews
</Button>
</div>
)}
</div>
</div>
);
}
}
export default withConfigContext(Reviews);
export default withConfigContext(Reviews);

View File

@ -41,4 +41,4 @@ img.twemoji {
font-weight: normal;
font-size: 0.8em;
padding-left: 5px;
}
}

View File

@ -16,129 +16,155 @@
* under the License.
*/
import React from "react";
import {Avatar, notification} from "antd";
import {List, Typography, Popconfirm} from "antd";
import StarRatings from "react-star-ratings";
import Twemoji from "react-twemoji";
import "./SingleReview.css";
import EditReview from "./editReview/EditReview";
import axios from "axios";
import {withConfigContext} from "../../../../../context/ConfigContext";
import {handleApiError} from "../../../../../js/Utils";
import React from 'react';
import { Avatar, notification } from 'antd';
import { List, Typography, Popconfirm } from 'antd';
import StarRatings from 'react-star-ratings';
import Twemoji from 'react-twemoji';
import './SingleReview.css';
import EditReview from './editReview/EditReview';
import axios from 'axios';
import { withConfigContext } from '../../../../../context/ConfigContext';
import { handleApiError } from '../../../../../js/Utils';
const {Text, Paragraph} = Typography;
const colorList = ['#f0932b', '#badc58', '#6ab04c', '#eb4d4b', '#0abde3', '#9b59b6', '#3498db', '#22a6b3', '#e84393', '#f9ca24'];
const { Text, Paragraph } = Typography;
const colorList = [
'#f0932b',
'#badc58',
'#6ab04c',
'#eb4d4b',
'#0abde3',
'#9b59b6',
'#3498db',
'#22a6b3',
'#e84393',
'#f9ca24',
];
class SingleReview extends React.Component {
static defaultProps = {
isPersonalReview: false,
};
static defaultProps = {
isPersonalReview: false
constructor(props) {
super(props);
const { username } = this.props.review;
const color = colorList[username.length % 10];
this.state = {
content: '',
rating: 0,
color: color,
review: props.review,
};
}
constructor(props) {
super(props);
const {username} = this.props.review;
const color = colorList[username.length % 10];
this.state = {
content: '',
rating: 0,
color: color,
review: props.review
}
updateCallback = review => {
this.setState({
review,
});
this.props.onUpdateReview();
};
deleteReview = () => {
const { uuid } = this.props;
const { id } = this.state.review;
const config = this.props.context;
let url =
window.location.origin +
config.serverConfig.invoker.uri +
config.serverConfig.invoker.store;
// call as an admin api if the review is not a personal review
if (!this.props.isPersonalReview) {
url += '/admin';
}
updateCallback = (review) => {
this.setState({
review
});
this.props.onUpdateReview();
};
url += '/reviews/' + uuid + '/' + id;
deleteReview = () => {
const {uuid} = this.props;
const {id} = this.state.review;
const config = this.props.context;
axios
.delete(url)
.then(res => {
if (res.status === 200) {
notification.success({
message: 'Done!',
description: 'The review has been deleted successfully.',
});
let url =window.location.origin+ config.serverConfig.invoker.uri + config.serverConfig.invoker.store;
// call as an admin api if the review is not a personal review
if (!this.props.isPersonalReview) {
url += "/admin";
this.props.deleteCallback(id);
}
})
.catch(error => {
handleApiError(error, 'We were unable to delete the review..');
});
};
url += "/reviews/" + uuid + "/" + id;
render() {
const { isEditable, isDeletable, uuid } = this.props;
const { color, review } = this.state;
const { content, rating, username } = review;
const avatarLetter = username.charAt(0).toUpperCase();
const body = (
<div style={{ marginTop: -5 }}>
<StarRatings
rating={rating}
starRatedColor="#777"
starDimension="12px"
starSpacing="2px"
numberOfStars={5}
name="rating"
/>
<Text style={{ fontSize: 12, color: '#aaa' }} type="secondary">
{' '}
{review.createdAt}
</Text>
<br />
<Paragraph style={{ color: '#777' }}>
<Twemoji options={{ className: 'twemoji' }}>{content}</Twemoji>
</Paragraph>
</div>
);
axios.delete(url).then(res => {
if (res.status === 200) {
notification["success"]({
message: 'Done!',
description:
'The review has been deleted successfully.',
});
const title = (
<div>
{review.username}
{isEditable && (
<EditReview
uuid={uuid}
review={review}
updateCallback={this.updateCallback}
/>
)}
{isDeletable && (
<Popconfirm
title="Are you sure delete this review?"
onConfirm={this.deleteReview}
okText="Yes"
cancelText="No"
>
<span className="delete-button">delete</span>
</Popconfirm>
)}
</div>
);
this.props.deleteCallback(id);
}
}).catch((error) => {
handleApiError(error,"We were unable to delete the review..");
});
};
render() {
const {isEditable, isDeletable, uuid} = this.props;
const {color, review} = this.state;
const {content, rating, username} = review;
const avatarLetter = username.charAt(0).toUpperCase();
const body = (
<div style={{marginTop: -5}}>
<StarRatings
rating={rating}
starRatedColor="#777"
starDimension="12px"
starSpacing="2px"
numberOfStars={5}
name='rating'
/>
<Text style={{fontSize: 12, color: "#aaa"}} type="secondary"> {review.createdAt}</Text><br/>
<Paragraph style={{color: "#777"}}>
<Twemoji options={{className: 'twemoji'}}>
{content}
</Twemoji>
</Paragraph>
</div>
);
const title = (
<div>
{review.username}
{isEditable && (<EditReview uuid={uuid} review={review} updateCallback={this.updateCallback}/>)}
{isDeletable && (
<Popconfirm
title="Are you sure delete this review?"
onConfirm={this.deleteReview}
okText="Yes"
cancelText="No"
>
<span className="delete-button">delete</span>
</Popconfirm>)}
</div>
);
return (
<div>
<List.Item.Meta
avatar={
<Avatar style={{backgroundColor: color, verticalAlign: 'middle'}} size="large">
{avatarLetter}
</Avatar>
}
title={title}
description={body}
/>
</div>
);
}
return (
<div>
<List.Item.Meta
avatar={
<Avatar
style={{ backgroundColor: color, verticalAlign: 'middle' }}
size="large"
>
{avatarLetter}
</Avatar>
}
title={title}
description={body}
/>
</div>
);
}
}
export default withConfigContext(SingleReview);
export default withConfigContext(SingleReview);

View File

@ -16,166 +16,183 @@
* under the License.
*/
import React from "react";
import {Drawer, Button, Icon, Row, Col, Typography, Divider, Input, Spin, notification} from 'antd';
import StarRatings from "react-star-ratings";
import axios from "axios";
import "./EditReview.css";
import {withConfigContext} from "../../../../../../context/ConfigContext";
import {handleApiError} from "../../../../../../js/Utils";
import React from 'react';
import {
Drawer,
Button,
Row,
Col,
Typography,
Divider,
Input,
Spin,
notification,
} from 'antd';
import StarRatings from 'react-star-ratings';
import axios from 'axios';
import './EditReview.css';
import { withConfigContext } from '../../../../../../context/ConfigContext';
import { handleApiError } from '../../../../../../js/Utils';
const {Title} = Typography;
const {TextArea} = Input;
const { Title } = Typography;
const { TextArea } = Input;
class EditReview extends React.Component {
constructor(props) {
super(props);
this.state = {
visible: false,
content: '',
rating: 0,
loading: false,
};
}
constructor(props) {
super(props);
this.state = {
componentDidMount() {
const { content, rating } = this.props.review;
this.setState({
content,
rating,
});
}
showDrawer = () => {
this.setState({
visible: true,
loading: false,
});
};
onClose = () => {
this.setState({
visible: false,
});
};
changeRating = (newRating, name) => {
this.setState({
rating: newRating,
});
};
onChange = e => {
this.setState({ content: e.target.value });
};
onSubmit = () => {
const config = this.props.context;
const { content, rating } = this.state;
const { id } = this.props.review;
const { uuid } = this.props;
this.setState({
loading: true,
});
const payload = {
content: content,
rating: rating,
};
axios
.put(
window.location.origin +
config.serverConfig.invoker.uri +
config.serverConfig.invoker.store +
'/reviews/' +
uuid +
'/' +
id,
payload,
)
.then(res => {
if (res.status === 200) {
this.setState({
loading: false,
visible: false,
content: '',
rating: 0,
loading: false
};
}
});
notification.success({
message: 'Done!',
description: 'Your review has been update successfully.',
});
componentDidMount() {
const {content,rating,id} = this.props.review;
this.setState({
content,
rating
});
}
showDrawer = () => {
this.setState({
visible: true,
loading: false
});
};
onClose = () => {
this.setState({
this.props.updateCallback(res.data.data);
} else {
this.setState({
loading: false,
visible: false,
});
};
changeRating = (newRating, name) => {
});
notification.error({
message: 'There was a problem',
duration: 0,
description: 'We are unable to update your review right now.',
});
}
})
.catch(error => {
handleApiError(error, 'We are unable to add your review right now.');
this.setState({
rating: newRating
loading: false,
visible: false,
});
};
});
};
onChange = (e) => {
this.setState({content: e.target.value})
};
onSubmit = () => {
const config = this.props.context;
const {content, rating} = this.state;
const {id} = this.props.review;
const {uuid} = this.props;
this.setState({
loading: true
});
const payload = {
content: content,
rating: rating
};
axios.put(
window.location.origin+ config.serverConfig.invoker.uri + config.serverConfig.invoker.store + "/reviews/" + uuid+"/"+id,
payload,
).then(res => {
if (res.status === 200) {
this.setState({
loading: false,
visible: false
});
notification["success"]({
message: 'Done!',
description:
'Your review has been update successfully.',
});
this.props.updateCallback(res.data.data);
} else {
this.setState({
loading: false,
visible: false
});
notification["error"]({
message: "There was a problem",
duration: 0,
description:
"We are unable to update your review right now.",
});
}
}).catch((error) => {
handleApiError(error,"We are unable to add your review right now.");
this.setState({
loading: false,
visible: false
});
});
};
render() {
return (
<span>
<span className="edit-button" onClick={this.showDrawer}>edit</span>
<Drawer
// title="Basic Drawer"
placement="bottom"
closable={false}
onClose={this.onClose}
visible={this.state.visible}
height={400}
render() {
return (
<span>
<span className="edit-button" onClick={this.showDrawer}>
edit
</span>
<Drawer
// title="Basic Drawer"
placement="bottom"
closable={false}
onClose={this.onClose}
visible={this.state.visible}
height={400}
>
<Spin spinning={this.state.loading} tip="Posting your review...">
<Row>
<Col lg={8} />
<Col lg={8}>
<Title level={4}>Edit review</Title>
<Divider />
<TextArea
placeholder="Tell others what you think about this app. Would you recommend it, and why?"
onChange={this.onChange}
rows={6}
value={this.state.content || ''}
style={{ marginBottom: 20 }}
/>
<StarRatings
rating={this.state.rating}
changeRating={this.changeRating}
starRatedColor="#777"
starHoverColor="#444"
starDimension="20px"
starSpacing="2px"
numberOfStars={5}
name="rating"
/>
<br />
<br />
<Button onClick={this.onClose} style={{ marginRight: 8 }}>
Cancel
</Button>
<Button
disabled={this.state.rating === 0}
onClick={this.onSubmit}
type="primary"
>
<Spin spinning={this.state.loading} tip="Posting your review...">
<Row>
<Col lg={8}/>
<Col lg={8}>
<Title level={4}>Edit review</Title>
<Divider/>
<TextArea
placeholder="Tell others what you think about this app. Would you recommend it, and why?"
onChange={this.onChange}
rows={6}
value={this.state.content || ''}
style={{marginBottom: 20}}
/>
<StarRatings
rating={this.state.rating}
changeRating={this.changeRating}
starRatedColor="#777"
starHoverColor="#444"
starDimension="20px"
starSpacing="2px"
numberOfStars={5}
name='rating'
/>
<br/><br/>
<Button onClick={this.onClose} style={{marginRight: 8}}>
Cancel
</Button>
<Button disabled={this.state.rating === 0} onClick={this.onSubmit} type="primary">
Submit
</Button>
</Col>
</Row>
</Spin>
</Drawer>
</span>
);
}
Submit
</Button>
</Col>
</Row>
</Spin>
</Drawer>
</span>
);
}
}
export default withConfigContext(EditReview);
export default withConfigContext(EditReview);

View File

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

View File

@ -42,4 +42,4 @@
.ant-input-affix-wrapper .ant-input{
min-height: 0;
}
}

View File

@ -19,42 +19,39 @@
import React from 'react';
import ReactDOM from 'react-dom';
import * as serviceWorker from './serviceWorker';
import App from "./App";
import Login from "./pages/Login";
import Dashboard from "./pages/dashboard/Dashboard";
import Apps from "./pages/dashboard/apps/Apps";
import Release from "./pages/dashboard/apps/release/Release";
import App from './App';
import Login from './pages/Login';
import Dashboard from './pages/dashboard/Dashboard';
import Apps from './pages/dashboard/apps/Apps';
import Release from './pages/dashboard/apps/release/Release';
import './index.css';
const routes = [
{
path: '/store/login',
{
path: '/store/login',
exact: true,
component: Login,
},
{
path: '/store',
exact: false,
component: Dashboard,
routes: [
{
path: '/store/:deviceType',
component: Apps,
exact: true,
component: Login
},
{
path: '/store',
exact: false,
component: Dashboard,
routes: [
{
path: '/store/:deviceType',
component: Apps,
exact: true
},
{
path: '/store/:deviceType/apps/:uuid',
exact: true,
component: Release
}
]
}
},
{
path: '/store/:deviceType/apps/:uuid',
exact: true,
component: Release,
},
],
},
];
ReactDOM.render(
<App routes={routes}/>,
document.getElementById('root'));
ReactDOM.render(<App routes={routes} />, document.getElementById('root'));
// If you want your app e and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls.

View File

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

View File

@ -59,4 +59,4 @@
.login .content {
position: relative;
z-index: 1;
}
}

View File

@ -16,172 +16,193 @@
* under the License.
*/
import React from "react";
import {Typography, Row, Col, Form, Icon, Input, Button, Checkbox, notification} from 'antd';
import React from 'react';
import {
Typography,
Row,
Col,
Form,
Icon,
Input,
Button,
Checkbox,
notification,
} from 'antd';
import './Login.css';
import axios from 'axios';
import {withConfigContext} from "../context/ConfigContext";
import {handleApiError} from "../js/Utils";
import { withConfigContext } from '../context/ConfigContext';
const {Title} = Typography;
const {Text} = Typography;
const { Title } = Typography;
const { Text } = Typography;
class Login extends React.Component {
render() {
const config = this.props.context;
return (
<div className="login">
<div className="background">
</div>
<div className="content">
<Row>
<Col xs={3} sm={3} md={10}>
</Col>
<Col xs={18} sm={18} md={4}>
<Row style={{marginBottom: 20}}>
<Col style={{textAlign: "center"}}>
<img style={
{
marginTop: 36,
height: 60
}
}
src={config.theme.logo}/>
</Col>
</Row>
<Title level={2}>Login</Title>
<WrappedNormalLoginForm/>
</Col>
</Row>
<Row>
<Col span={4} offset={10}>
</Col>
</Row>
</div>
</div>
);
}
render() {
const config = this.props.context;
return (
<div className="login">
<div className="background"></div>
<div className="content">
<Row>
<Col xs={3} sm={3} md={10}></Col>
<Col xs={18} sm={18} md={4}>
<Row style={{ marginBottom: 20 }}>
<Col style={{ textAlign: 'center' }}>
<img
style={{
marginTop: 36,
height: 60,
}}
src={config.theme.logo}
/>
</Col>
</Row>
<Title level={2}>Login</Title>
<WrappedNormalLoginForm />
</Col>
</Row>
<Row>
<Col span={4} offset={10}></Col>
</Row>
</div>
</div>
);
}
}
class NormalLoginForm extends React.Component {
constructor(props) {
super(props);
this.state = {
inValid: false,
loading: false
};
}
handleSubmit = (e) => {
const thisForm = this;
const config = this.props.context;
e.preventDefault();
this.props.form.validateFields((err, values) => {
thisForm.setState({
inValid: false
});
if (!err) {
thisForm.setState({
loading: true
});
const parameters = {
username: values.username,
password: values.password,
platform: "store"
};
const request = Object.keys(parameters).map(key => key + '=' + parameters[key]).join('&');
axios.post(window.location.origin + config.serverConfig.loginUri, request
).then(res => {
if (res.status === 200) {
let redirectUrl = window.location.origin + "/store";
const searchParams = new URLSearchParams(window.location.search);
if (searchParams.has("redirect")) {
redirectUrl = searchParams.get("redirect");
}
window.location = redirectUrl;
}
}).catch(function (error) {
if (error.hasOwnProperty("response") && error.response.status === 401) {
thisForm.setState({
loading: false,
inValid: true
});
} else {
notification["error"]({
message: "There was a problem",
duration: 10,
description: message,
});
thisForm.setState({
loading: false,
inValid: false
});
}
});
}
});
constructor(props) {
super(props);
this.state = {
inValid: false,
loading: false,
};
}
render() {
const {getFieldDecorator} = this.props.form;
let errorMsg = "";
if (this.state.inValid) {
errorMsg = <Text type="danger">Invalid Login Details</Text>;
}
let loading = "";
if (this.state.loading) {
loading = <Text type="secondary">Loading..</Text>;
}
return (
<Form onSubmit={this.handleSubmit} className="login-form">
<Form.Item>
{getFieldDecorator('username', {
rules: [{required: true, message: 'Please input your username!'}],
})(
<Input name="username" style={{height: 32}}
prefix={<Icon type="user" style={{color: 'rgba(0,0,0,.25)'}}/>}
placeholder="Username"/>
)}
</Form.Item>
<Form.Item>
{getFieldDecorator('password', {
rules: [{required: true, message: 'Please input your Password!'}],
})(
<Input name="password" style={{height: 32}}
prefix={<Icon type="lock" style={{color: 'rgba(0,0,0,.25)'}}/>} type="password"
placeholder="Password"/>
)}
</Form.Item>
{loading}
{errorMsg}
<Form.Item>
{getFieldDecorator('remember', {
valuePropName: 'checked',
initialValue: true,
})(
<Checkbox>Remember me</Checkbox>
)}
<br/>
<a className="login-form-forgot" href="">Forgot password</a>
<Button loading={this.state.loading} block type="primary" htmlType="submit"
className="login-form-button">
Log in
</Button>
</Form.Item>
</Form>
);
handleSubmit = e => {
const thisForm = this;
const config = this.props.context;
e.preventDefault();
this.props.form.validateFields((err, values) => {
thisForm.setState({
inValid: false,
});
if (!err) {
thisForm.setState({
loading: true,
});
const parameters = {
username: values.username,
password: values.password,
platform: 'store',
};
const request = Object.keys(parameters)
.map(key => key + '=' + parameters[key])
.join('&');
axios
.post(window.location.origin + config.serverConfig.loginUri, request)
.then(res => {
if (res.status === 200) {
let redirectUrl = window.location.origin + '/store';
const searchParams = new URLSearchParams(window.location.search);
if (searchParams.has('redirect')) {
redirectUrl = searchParams.get('redirect');
}
window.location = redirectUrl;
}
})
.catch(function(error) {
if (
error.hasOwnProperty('response') &&
error.response.status === 401
) {
thisForm.setState({
loading: false,
inValid: true,
});
} else {
notification.error({
message: 'There was a problem',
duration: 10,
description: '',
});
thisForm.setState({
loading: false,
inValid: false,
});
}
});
}
});
};
render() {
const { getFieldDecorator } = this.props.form;
let errorMsg = '';
if (this.state.inValid) {
errorMsg = <Text type="danger">Invalid Login Details</Text>;
}
let loading = '';
if (this.state.loading) {
loading = <Text type="secondary">Loading..</Text>;
}
return (
<Form onSubmit={this.handleSubmit} className="login-form">
<Form.Item>
{getFieldDecorator('username', {
rules: [{ required: true, message: 'Please input your username!' }],
})(
<Input
name="username"
style={{ height: 32 }}
prefix={<Icon type="user" style={{ color: 'rgba(0,0,0,.25)' }} />}
placeholder="Username"
/>,
)}
</Form.Item>
<Form.Item>
{getFieldDecorator('password', {
rules: [{ required: true, message: 'Please input your Password!' }],
})(
<Input
name="password"
style={{ height: 32 }}
prefix={<Icon type="lock" style={{ color: 'rgba(0,0,0,.25)' }} />}
type="password"
placeholder="Password"
/>,
)}
</Form.Item>
{loading}
{errorMsg}
<Form.Item>
{getFieldDecorator('remember', {
valuePropName: 'checked',
initialValue: true,
})(<Checkbox>Remember me</Checkbox>)}
<br />
<a className="login-form-forgot" href="">
Forgot password
</a>
<Button
loading={this.state.loading}
block
type="primary"
htmlType="submit"
className="login-form-button"
>
Log in
</Button>
</Form.Item>
</Form>
);
}
}
const WrappedNormalLoginForm = withConfigContext(Form.create({name: 'normal_login'})(NormalLoginForm));
const WrappedNormalLoginForm = withConfigContext(
Form.create({ name: 'normal_login' })(NormalLoginForm),
);
export default withConfigContext(Login);

View File

@ -16,225 +16,261 @@
* under the License.
*/
import React from "react";
import {Layout, Menu, Icon, Drawer, Button, Alert} from 'antd';
import React from 'react';
import { Layout, Menu, Icon, Drawer, Button, Alert } from 'antd';
const {Header, Content, Footer} = Layout;
import {Link} from "react-router-dom";
import RouteWithSubRoutes from "../../components/RouteWithSubRoutes";
import {Switch} from 'react-router';
import axios from "axios";
import "./Dashboard.css";
import {withConfigContext} from "../../context/ConfigContext";
import Logout from "./logout/Logout";
import {handleApiError} from "../../js/Utils";
const { Header, Content, Footer } = Layout;
import { Link } from 'react-router-dom';
import RouteWithSubRoutes from '../../components/RouteWithSubRoutes';
import { Switch } from 'react-router';
import axios from 'axios';
import './Dashboard.css';
import { withConfigContext } from '../../context/ConfigContext';
import Logout from './logout/Logout';
import { handleApiError } from '../../js/Utils';
const {SubMenu} = Menu;
const { SubMenu } = Menu;
class Dashboard extends React.Component {
constructor(props) {
super(props);
this.state = {
routes: props.routes,
selectedKeys: [],
deviceTypes: [],
visible: false,
collapsed: false,
forbiddenErrors: {
deviceTypes: false
}
};
this.logo = this.props.context.theme.logo;
this.footerText = this.props.context.theme.footerText;
this.config = this.props.context;
}
componentDidMount() {
this.getDeviceTypes();
}
getDeviceTypes = () => {
const config = this.props.context;
axios.get(
window.location.origin + config.serverConfig.invoker.uri + config.serverConfig.invoker.deviceMgt + "/device-types"
).then(res => {
if (res.status === 200) {
const deviceTypes = JSON.parse(res.data.data);
this.setState({
deviceTypes,
loading: false,
});
}
}).catch((error) => {
handleApiError(error, "Error occurred while trying to load device types.", true);
if (error.hasOwnProperty("response") && error.response.status === 403) {
const {forbiddenErrors} = this.state;
forbiddenErrors.deviceTypes = true;
this.setState({
forbiddenErrors,
loading: false
})
} else {
this.setState({
loading: false
});
}
});
constructor(props) {
super(props);
this.state = {
routes: props.routes,
selectedKeys: [],
deviceTypes: [],
visible: false,
collapsed: false,
forbiddenErrors: {
deviceTypes: false,
},
};
this.logo = this.props.context.theme.logo;
this.footerText = this.props.context.theme.footerText;
this.config = this.props.context;
}
changeSelectedMenuItem = (key) => {
this.setState({
selectedKeys: [key]
})
};
componentDidMount() {
this.getDeviceTypes();
}
showMobileNavigationBar = () => {
this.setState({
visible: true,
collapsed: !this.state.collapsed,
});
};
onCloseMobileNavigationBar = () => {
this.setState({
visible: false,
});
};
render() {
const config = this.props.context;
const {selectedKeys, deviceTypes, forbiddenErrors} = this.state;
const DeviceTypesData = deviceTypes.map((deviceType) => {
const platform = deviceType.name;
const defaultPlatformIcons = config.defaultPlatformIcons;
let icon = defaultPlatformIcons.default.icon;
let theme = defaultPlatformIcons.default.theme;
if (defaultPlatformIcons.hasOwnProperty(platform)) {
icon = defaultPlatformIcons[platform].icon;
theme = defaultPlatformIcons[platform].theme;
}
return (
<Menu.Item key={platform}>
<Link to={"/store/" + platform}>
<Icon type={icon} theme={theme}/>
{platform}
</Link>
</Menu.Item>
);
});
return (
<div>
<Layout>
<Header style={{paddingLeft: 0, paddingRight: 0, backgroundColor: "white"}}>
<div className="logo-image">
<Link to="/store"><img alt="logo" src={this.logo}/></Link>
</div>
<div className="web-layout">
<Menu
theme="light"
mode="horizontal"
defaultSelectedKeys={selectedKeys}
style={{lineHeight: '64px'}}>
{DeviceTypesData}
<Menu.Item key="web-clip">
<Link to="/store/web-clip">
<Icon type="upload"/>
Web Clips
</Link>
</Menu.Item>
<SubMenu className="profile"
title={
<span className="submenu-title-wrapper">
<Icon type="user"/>
{this.config.user}
</span>}>
<Logout/>
</SubMenu>
</Menu>
</div>
</Header>
</Layout>
<Layout className="mobile-layout">
<div className="mobile-menu-button">
<Button type="link" onClick={this.showMobileNavigationBar}>
<Icon type={this.state.collapsed ? 'menu-fold' : 'menu-unfold'} className="bar-icon"/>
</Button>
</div>
</Layout>
<Drawer
title={<Link to="/store" onClick={this.onCloseMobileNavigationBar}>
<img alt="logo" src={this.logo} style={{marginLeft: 30}} width={"60%"}/>
</Link>}
placement="left"
closable={false}
onClose={this.onCloseMobileNavigationBar}
visible={this.state.visible}
getContainer={false}
style={{position: 'absolute'}}>
<Menu
theme="light"
mode="inline"
defaultSelectedKeys={selectedKeys}
style={{lineHeight: '64px', width: 231}}
onClick={this.onCloseMobileNavigationBar}>
{DeviceTypesData}
<Menu.Item key="web-clip">
<Link to="/store/web-clip">
<Icon type="upload"/>Web Clips
</Link>
</Menu.Item>
</Menu>
</Drawer>
<Layout className="mobile-layout">
<Menu
mode="horizontal"
defaultSelectedKeys={selectedKeys}
style={{lineHeight: '63px', position: 'fixed', marginLeft: '80%'}}>
<SubMenu
title={
<span className="submenu-title-wrapper">
<Icon type="user"/>
</span>}>
<Logout/>
</SubMenu>
</Menu>
</Layout>
<Layout className="dashboard-body">
{(forbiddenErrors.deviceTypes) && (
<Alert
message="You don't have permission to view device types."
type="warning"
banner
closable/>
)}
<Content style={{padding: '0 0'}}>
<Switch>
{this.state.routes.map((route) => (
<RouteWithSubRoutes changeSelectedMenuItem={this.changeSelectedMenuItem}
key={route.path} {...route} />
))}
</Switch>
</Content>
<Footer style={{textAlign: 'center'}}>
{this.footerText}
</Footer>
</Layout>
</div>
getDeviceTypes = () => {
const config = this.props.context;
axios
.get(
window.location.origin +
config.serverConfig.invoker.uri +
config.serverConfig.invoker.deviceMgt +
'/device-types',
)
.then(res => {
if (res.status === 200) {
const deviceTypes = JSON.parse(res.data.data);
this.setState({
deviceTypes,
loading: false,
});
}
})
.catch(error => {
handleApiError(
error,
'Error occurred while trying to load device types.',
true,
);
}
if (error.hasOwnProperty('response') && error.response.status === 403) {
const { forbiddenErrors } = this.state;
forbiddenErrors.deviceTypes = true;
this.setState({
forbiddenErrors,
loading: false,
});
} else {
this.setState({
loading: false,
});
}
});
};
changeSelectedMenuItem = key => {
this.setState({
selectedKeys: [key],
});
};
showMobileNavigationBar = () => {
this.setState({
visible: true,
collapsed: !this.state.collapsed,
});
};
onCloseMobileNavigationBar = () => {
this.setState({
visible: false,
});
};
render() {
const config = this.props.context;
const { selectedKeys, deviceTypes, forbiddenErrors } = this.state;
const DeviceTypesData = deviceTypes.map(deviceType => {
const platform = deviceType.name;
const defaultPlatformIcons = config.defaultPlatformIcons;
let icon = defaultPlatformIcons.default.icon;
let theme = defaultPlatformIcons.default.theme;
if (defaultPlatformIcons.hasOwnProperty(platform)) {
icon = defaultPlatformIcons[platform].icon;
theme = defaultPlatformIcons[platform].theme;
}
return (
<Menu.Item key={platform}>
<Link to={'/store/' + platform}>
<Icon type={icon} theme={theme} />
{platform}
</Link>
</Menu.Item>
);
});
return (
<div>
<Layout>
<Header
style={{
paddingLeft: 0,
paddingRight: 0,
backgroundColor: 'white',
}}
>
<div className="logo-image">
<Link to="/store">
<img alt="logo" src={this.logo} />
</Link>
</div>
<div className="web-layout">
<Menu
theme="light"
mode="horizontal"
defaultSelectedKeys={selectedKeys}
style={{ lineHeight: '64px' }}
>
{DeviceTypesData}
<Menu.Item key="web-clip">
<Link to="/store/web-clip">
<Icon type="upload" />
Web Clips
</Link>
</Menu.Item>
<SubMenu
className="profile"
title={
<span className="submenu-title-wrapper">
<Icon type="user" />
{this.config.user}
</span>
}
>
<Logout />
</SubMenu>
</Menu>
</div>
</Header>
</Layout>
<Layout className="mobile-layout">
<div className="mobile-menu-button">
<Button type="link" onClick={this.showMobileNavigationBar}>
<Icon
type={this.state.collapsed ? 'menu-fold' : 'menu-unfold'}
className="bar-icon"
/>
</Button>
</div>
</Layout>
<Drawer
title={
<Link to="/store" onClick={this.onCloseMobileNavigationBar}>
<img
alt="logo"
src={this.logo}
style={{ marginLeft: 30 }}
width={'60%'}
/>
</Link>
}
placement="left"
closable={false}
onClose={this.onCloseMobileNavigationBar}
visible={this.state.visible}
getContainer={false}
style={{ position: 'absolute' }}
>
<Menu
theme="light"
mode="inline"
defaultSelectedKeys={selectedKeys}
style={{ lineHeight: '64px', width: 231 }}
onClick={this.onCloseMobileNavigationBar}
>
{DeviceTypesData}
<Menu.Item key="web-clip">
<Link to="/store/web-clip">
<Icon type="upload" />
Web Clips
</Link>
</Menu.Item>
</Menu>
</Drawer>
<Layout className="mobile-layout">
<Menu
mode="horizontal"
defaultSelectedKeys={selectedKeys}
style={{ lineHeight: '63px', position: 'fixed', marginLeft: '80%' }}
>
<SubMenu
title={
<span className="submenu-title-wrapper">
<Icon type="user" />
</span>
}
>
<Logout />
</SubMenu>
</Menu>
</Layout>
<Layout className="dashboard-body">
{forbiddenErrors.deviceTypes && (
<Alert
message="You don't have permission to view device types."
type="warning"
banner
closable
/>
)}
<Content style={{ padding: '0 0' }}>
<Switch>
{this.state.routes.map(route => (
<RouteWithSubRoutes
changeSelectedMenuItem={this.changeSelectedMenuItem}
key={route.path}
{...route}
/>
))}
</Switch>
</Content>
<Footer style={{ textAlign: 'center' }}>{this.footerText}</Footer>
</Layout>
</div>
);
}
}
export default withConfigContext(Dashboard);

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -16,30 +16,31 @@
* under the License.
*/
import React from "react";
import AppList from "../../../components/apps/AppList";
import React from 'react';
import AppList from '../../../components/apps/AppList';
class Apps extends React.Component {
routes;
constructor(props) {
super(props);
this.routes = props.routes;
routes;
constructor(props) {
super(props);
this.routes = props.routes;
}
}
render() {
const {deviceType} = this.props.match.params;
return (
<div>
<div style={{background: '#f0f2f5', padding: 24, minHeight: 760}}>
{deviceType!==null && <AppList changeSelectedMenuItem={this.props.changeSelectedMenuItem} deviceType={deviceType}/>}
</div>
</div>
);
}
render() {
const { deviceType } = this.props.match.params;
return (
<div>
<div style={{ background: '#f0f2f5', padding: 24, minHeight: 760 }}>
{deviceType !== null && (
<AppList
changeSelectedMenuItem={this.props.changeSelectedMenuItem}
deviceType={deviceType}
/>
)}
</div>
</div>
);
}
}
export default Apps;

View File

@ -16,118 +16,131 @@
* under the License.
*/
import React from "react";
import React from 'react';
import '../../../../App.css';
import {Skeleton, Typography, Row, Col, Card, message, notification, Breadcrumb, Icon} from "antd";
import ReleaseView from "../../../../components/apps/release/ReleaseView";
import axios from "axios";
import {withConfigContext} from "../../../../context/ConfigContext";
import {Link} from "react-router-dom";
import {handleApiError} from "../../../../js/Utils";
import { Skeleton, Typography, Row, Col, Card, Breadcrumb, Icon } from 'antd';
import ReleaseView from '../../../../components/apps/release/ReleaseView';
import axios from 'axios';
import { withConfigContext } from '../../../../context/ConfigContext';
import { Link } from 'react-router-dom';
import { handleApiError } from '../../../../js/Utils';
const {Title} = Typography;
const { Title } = Typography;
class Release extends React.Component {
routes;
routes;
constructor(props) {
super(props);
this.routes = props.routes;
this.state = {
loading: true,
app: null,
uuid: null,
forbiddenErrors: {
app: false
}
};
}
componentDidMount() {
const {uuid, deviceType} = this.props.match.params;
this.fetchData(uuid);
this.props.changeSelectedMenuItem(deviceType);
}
componentDidUpdate(prevProps, prevState, snapshot) {
if (prevState.uuid !== this.state.uuid) {
const {uuid, deviceType} = this.props.match.params;
this.fetchData(uuid);
this.props.changeSelectedMenuItem(deviceType);
}
}
fetchData = (uuid) => {
const config = this.props.context;
//send request to the invoker
axios.get(
window.location.origin + config.serverConfig.invoker.uri + config.serverConfig.invoker.store + "/applications/" + uuid,
).then(res => {
if (res.status === 200) {
let app = res.data.data;
this.setState({
app: app,
loading: false,
uuid: uuid
})
}
}).catch((error) => {
handleApiError(error,"Error occurred while trying to load releases.", false);
if (error.hasOwnProperty("response") && error.response.status === 403) {
const {forbiddenErrors} = this.state;
forbiddenErrors.app = true;
this.setState({
forbiddenErrors,
loading: false
})
} else {
this.setState({
loading: false
});
}
});
constructor(props) {
super(props);
this.routes = props.routes;
this.state = {
loading: true,
app: null,
uuid: null,
forbiddenErrors: {
app: false,
},
};
}
render() {
const {app, loading} = this.state;
const {deviceType} = this.props.match.params;
componentDidMount() {
const { uuid, deviceType } = this.props.match.params;
this.fetchData(uuid);
this.props.changeSelectedMenuItem(deviceType);
}
let content = <Title level={3}>No Releases Found</Title>;
let appName = "loading...";
if (app != null && app.applicationReleases.length !== 0) {
content = <ReleaseView app={app} deviceType={deviceType}/>;
appName = app.name;
}
return (
<div style={{background: '#f0f2f5', minHeight: 780}}>
<Row style={{padding: 10}}>
<Col lg={4}>
</Col>
<Col lg={16} md={24} style={{padding: 3}}>
<Breadcrumb style={{paddingBottom: 16}}>
<Breadcrumb.Item>
<Link to={"/store/"+deviceType}><Icon type="home"/> {deviceType + " apps"} </Link>
</Breadcrumb.Item>
<Breadcrumb.Item>{appName}</Breadcrumb.Item>
</Breadcrumb>
<Card>
<Skeleton loading={loading} avatar={{size: 'large'}} active paragraph={{rows: 8}}>
{content}
</Skeleton>
</Card>
</Col>
</Row>
</div>
);
componentDidUpdate(prevProps, prevState, snapshot) {
if (prevState.uuid !== this.state.uuid) {
const { uuid, deviceType } = this.props.match.params;
this.fetchData(uuid);
this.props.changeSelectedMenuItem(deviceType);
}
}
fetchData = uuid => {
const config = this.props.context;
// send request to the invoker
axios
.get(
window.location.origin +
config.serverConfig.invoker.uri +
config.serverConfig.invoker.store +
'/applications/' +
uuid,
)
.then(res => {
if (res.status === 200) {
let app = res.data.data;
this.setState({
app: app,
loading: false,
uuid: uuid,
});
}
})
.catch(error => {
handleApiError(
error,
'Error occurred while trying to load releases.',
false,
);
if (error.hasOwnProperty('response') && error.response.status === 403) {
const { forbiddenErrors } = this.state;
forbiddenErrors.app = true;
this.setState({
forbiddenErrors,
loading: false,
});
} else {
this.setState({
loading: false,
});
}
});
};
render() {
const { app, loading } = this.state;
const { deviceType } = this.props.match.params;
let content = <Title level={3}>No Releases Found</Title>;
let appName = 'loading...';
if (app != null && app.applicationReleases.length !== 0) {
content = <ReleaseView app={app} deviceType={deviceType} />;
appName = app.name;
}
return (
<div style={{ background: '#f0f2f5', minHeight: 780 }}>
<Row style={{ padding: 10 }}>
<Col lg={4}></Col>
<Col lg={16} md={24} style={{ padding: 3 }}>
<Breadcrumb style={{ paddingBottom: 16 }}>
<Breadcrumb.Item>
<Link to={'/store/' + deviceType}>
<Icon type="home" /> {deviceType + ' apps'}{' '}
</Link>
</Breadcrumb.Item>
<Breadcrumb.Item>{appName}</Breadcrumb.Item>
</Breadcrumb>
<Card>
<Skeleton
loading={loading}
avatar={{ size: 'large' }}
active
paragraph={{ rows: 8 }}
>
{content}
</Skeleton>
</Card>
</Col>
</Row>
</div>
);
}
}
export default withConfigContext(Release);

View File

@ -16,54 +16,60 @@
* under the License.
*/
import React from "react";
import {notification, Menu, Icon} from 'antd';
import React from 'react';
import { Menu, Icon } from 'antd';
import axios from 'axios';
import {withConfigContext} from "../../../context/ConfigContext";
import {handleApiError} from "../../../js/Utils";
import { withConfigContext } from '../../../context/ConfigContext';
import { handleApiError } from '../../../js/Utils';
/*
This class for call the logout api by sending request
*/
class Logout extends React.Component {
constructor(props) {
super(props);
this.state = {
inValid: false,
loading: false
};
}
/*
constructor(props) {
super(props);
this.state = {
inValid: false,
loading: false,
};
}
/*
This function call the logout api when the request is success
*/
handleSubmit = () => {
handleSubmit = () => {
const thisForm = this;
const config = this.props.context;
const thisForm = this;
const config = this.props.context;
thisForm.setState({
inValid: false,
});
thisForm.setState({
inValid: false
});
axios.post(window.location.origin + config.serverConfig.logoutUri
).then(res => {
//if the api call status is correct then user will logout and then it goes to login page
if (res.status === 200) {
window.location = window.location.origin + "/store/login";
}
}).catch(function (error) {
handleApiError(error,"Error occurred while trying to get your review.");
});
};
render() {
return (
<Menu>
<Menu.Item key="1" onClick={this.handleSubmit}><Icon type="logout"/>Logout</Menu.Item>
</Menu>
axios
.post(window.location.origin + config.serverConfig.logoutUri)
.then(res => {
// if the api call status is correct then user will logout and then it goes to login page
if (res.status === 200) {
window.location = window.location.origin + '/store/login';
}
})
.catch(function(error) {
handleApiError(
error,
'Error occurred while trying to get your review.',
);
}
});
};
render() {
return (
<Menu>
<Menu.Item key="1" onClick={this.handleSubmit}>
<Icon type="logout" />
Logout
</Menu.Item>
</Menu>
);
}
}
export default withConfigContext(Logout);

View File

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

View File

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