Merge branch 'application-mgt-new' of gitlab.com:entgra/carbon-device-mgt into application-mgt-new

feature/appm-store/pbac
lasanthaDLPDS 5 years ago
commit db0b362244

@ -39,7 +39,8 @@
"redux-thunk": "^2.3.0", "redux-thunk": "^2.3.0",
"shade-blend-color": "^1.0.0", "shade-blend-color": "^1.0.0",
"storm-react-diagrams": "^5.2.1", "storm-react-diagrams": "^5.2.1",
"typescript": "^3.6.4" "typescript": "^3.6.4",
"lodash.debounce": "latest"
}, },
"devDependencies": { "devDependencies": {
"@babel/core": "^7.5.0", "@babel/core": "^7.5.0",

@ -61,7 +61,7 @@ class DetailedRating extends React.Component{
} }
}).catch(function (error) { }).catch(function (error) {
handleApiError(error, "Error occurred while trying to load rating for the release."); handleApiError(error, "Error occurred while trying to load rating for the release.", true);
}); });
}; };

@ -136,7 +136,7 @@ class AppDetailsDrawer extends React.Component {
} }
}).catch((error) => { }).catch((error) => {
handleApiError(error, "Error occurred while trying to load app details."); handleApiError(error, "Error occurred while trying to load categories.", true);
this.setState({ this.setState({
loading: false loading: false
}); });
@ -509,8 +509,8 @@ class AppDetailsDrawer extends React.Component {
title="Published" title="Published"
style={{ style={{
backgroundColor: '#52c41a', backgroundColor: '#52c41a',
borderRadius:"50%", borderRadius: "50%",
color:"white" color: "white"
}} }}
count={ count={
<Icon <Icon
@ -547,10 +547,10 @@ class AppDetailsDrawer extends React.Component {
/> />
</div> </div>
<Divider dashed={true}/>
{/*display add new release only if app type is enterprise*/} {/*display add new release only if app type is enterprise*/}
{(app.type === "ENTERPRISE") && ( {(app.type === "ENTERPRISE") && (
<div> <div>
<Divider dashed={true}/>
<div style={{paddingBottom: 16}}> <div style={{paddingBottom: 16}}>
<Text> <Text>
Add new release for the application Add new release for the application

@ -30,7 +30,7 @@ import {
Form, Form,
message, message,
Radio, Radio,
notification notification, Alert
} from "antd"; } from "antd";
import axios from "axios"; import axios from "axios";
import {withConfigContext} from "../../../context/ConfigContext"; import {withConfigContext} from "../../../context/ConfigContext";
@ -46,7 +46,12 @@ class FiltersForm extends React.Component {
this.state = { this.state = {
categories: [], categories: [],
tags: [], tags: [],
deviceTypes: [] deviceTypes: [],
forbiddenErrors: {
categories: false,
tags: false,
deviceTypes: false
}
}; };
} }
@ -59,11 +64,11 @@ class FiltersForm extends React.Component {
} }
} }
if(values.hasOwnProperty("deviceType") && values.deviceType==="ALL"){ if (values.hasOwnProperty("deviceType") && values.deviceType === "ALL") {
delete values["deviceType"]; delete values["deviceType"];
} }
if(values.hasOwnProperty("appType") && values.appType==="ALL"){ if (values.hasOwnProperty("appType") && values.appType === "ALL") {
delete values["appType"]; delete values["appType"];
} }
@ -73,16 +78,17 @@ class FiltersForm extends React.Component {
componentDidMount() { componentDidMount() {
this.getCategories(); this.getCategories();
this.getTags();
this.getDeviceTypes();
} }
getCategories = () => { getCategories = () => {
const config = this.props.context; const config = this.props.context;
axios.get( axios.get(
window.location.origin+ config.serverConfig.invoker.uri + config.serverConfig.invoker.publisher + "/applications/categories" window.location.origin + config.serverConfig.invoker.uri + config.serverConfig.invoker.publisher + "/applications/categories"
).then(res => { ).then(res => {
if (res.status === 200) { if (res.status === 200) {
let categories = JSON.parse(res.data.data); let categories = JSON.parse(res.data.data);
this.getTags();
this.setState({ this.setState({
categories: categories, categories: categories,
loading: false loading: false
@ -90,21 +96,29 @@ class FiltersForm extends React.Component {
} }
}).catch((error) => { }).catch((error) => {
handleApiError(error, "Error occurred while trying to load categories."); handleApiError(error, "Error occurred while trying to load categories.", true);
this.setState({ if (error.hasOwnProperty("response") && error.response.status === 403) {
loading: false const {forbiddenErrors} = this.state;
}); forbiddenErrors.categories = true;
this.setState({
forbiddenErrors,
loading: false
})
} else {
this.setState({
loading: false
});
}
}); });
}; };
getTags = () => { getTags = () => {
const config = this.props.context; const config = this.props.context;
axios.get( axios.get(
window.location.origin+ config.serverConfig.invoker.uri + config.serverConfig.invoker.publisher + "/applications/tags" window.location.origin + config.serverConfig.invoker.uri + config.serverConfig.invoker.publisher + "/applications/tags"
).then(res => { ).then(res => {
if (res.status === 200) { if (res.status === 200) {
let tags = JSON.parse(res.data.data); let tags = JSON.parse(res.data.data);
this.getDeviceTypes();
this.setState({ this.setState({
tags: tags, tags: tags,
loading: false, loading: false,
@ -112,10 +126,19 @@ class FiltersForm extends React.Component {
} }
}).catch((error) => { }).catch((error) => {
handleApiError(error, "Error occurred while trying to load tags."); handleApiError(error, "Error occurred while trying to load tags.", true);
this.setState({ if (error.hasOwnProperty("response") && error.response.status === 403) {
loading: false const {forbiddenErrors} = this.state;
}); forbiddenErrors.tags = true;
this.setState({
forbiddenErrors,
loading: false
})
} else {
this.setState({
loading: false
});
}
}); });
}; };
@ -123,7 +146,7 @@ class FiltersForm extends React.Component {
getDeviceTypes = () => { getDeviceTypes = () => {
const config = this.props.context; const config = this.props.context;
axios.get( axios.get(
window.location.origin+ config.serverConfig.invoker.uri + config.serverConfig.invoker.deviceMgt + "/device-types" window.location.origin + config.serverConfig.invoker.uri + config.serverConfig.invoker.deviceMgt + "/device-types"
).then(res => { ).then(res => {
if (res.status === 200) { if (res.status === 200) {
const deviceTypes = JSON.parse(res.data.data); const deviceTypes = JSON.parse(res.data.data);
@ -134,15 +157,24 @@ class FiltersForm extends React.Component {
} }
}).catch((error) => { }).catch((error) => {
handleApiError(error, "Error occurred while trying to load device types."); handleApiError(error, "Error occurred while trying to load device types.", true);
this.setState({ if (error.hasOwnProperty("response") && error.response.status === 403) {
loading: false const {forbiddenErrors} = this.state;
}); forbiddenErrors.deviceTypes = true;
this.setState({
forbiddenErrors,
loading: false
})
} else {
this.setState({
loading: false
});
}
}); });
}; };
render() { render() {
const {categories, tags, deviceTypes} = this.state; const {categories, tags, deviceTypes, forbiddenErrors} = this.state;
const {getFieldDecorator} = this.props.form; const {getFieldDecorator} = this.props.form;
return ( return (
@ -170,7 +202,13 @@ class FiltersForm extends React.Component {
</Form.Item> </Form.Item>
</Col> </Col>
</Row> </Row>
{(forbiddenErrors.categories) && (
<Alert
message="You don't have permission to view categories."
type="warning"
banner
closable/>
)}
<Form.Item label="Categories"> <Form.Item label="Categories">
{getFieldDecorator('categories', { {getFieldDecorator('categories', {
rules: [{ rules: [{
@ -182,8 +220,7 @@ class FiltersForm extends React.Component {
mode="multiple" mode="multiple"
style={{width: '100%'}} style={{width: '100%'}}
placeholder="Select a Category" placeholder="Select a Category"
onChange={this.handleCategoryChange} onChange={this.handleCategoryChange}>
>
{ {
categories.map(category => { categories.map(category => {
return ( return (
@ -198,7 +235,13 @@ class FiltersForm extends React.Component {
)} )}
</Form.Item> </Form.Item>
{(forbiddenErrors.deviceTypes) && (
<Alert
message="You don't have permission to view device types."
type="warning"
banner
closable/>
)}
<Form.Item label="Device Type"> <Form.Item label="Device Type">
{getFieldDecorator('deviceType', { {getFieldDecorator('deviceType', {
rules: [{ rules: [{
@ -208,8 +251,7 @@ class FiltersForm extends React.Component {
})( })(
<Select <Select
style={{width: '100%'}} style={{width: '100%'}}
placeholder="Select device types" placeholder="Select device types">
>
{ {
deviceTypes.map(deviceType => { deviceTypes.map(deviceType => {
return ( return (
@ -226,7 +268,13 @@ class FiltersForm extends React.Component {
</Select> </Select>
)} )}
</Form.Item> </Form.Item>
{(forbiddenErrors.tags) && (
<Alert
message="You don't have permission to view tags."
type="warning"
banner
closable/>
)}
<Form.Item label="Tags"> <Form.Item label="Tags">
{getFieldDecorator('tags', { {getFieldDecorator('tags', {
rules: [{ rules: [{

@ -17,7 +17,7 @@
*/ */
import React from "react"; import React from "react";
import {Avatar, Table, Tag, Icon, message, notification, Col, Badge} from "antd"; import {Avatar, Table, Tag, Icon, message, notification, Col, Badge, Alert} from "antd";
import axios from "axios"; import axios from "axios";
import pSBC from 'shade-blend-color'; import pSBC from 'shade-blend-color';
import "./AppsTable.css"; import "./AppsTable.css";
@ -58,7 +58,7 @@ const columns = [
avatar = (hasPublishedRelease) ? ( avatar = (hasPublishedRelease) ? (
<Badge <Badge
title="Published" title="Published"
style={{ backgroundColor: '#52c41a', borderRadius:"50%", color:"white"}} style={{backgroundColor: '#52c41a', borderRadius: "50%", color: "white"}}
count={ count={
<Icon <Icon
type="check-circle"/> type="check-circle"/>
@ -74,7 +74,6 @@ const columns = [
) : ( ) : (
<Avatar shape="square" size="large" <Avatar shape="square" size="large"
style={{ style={{
marginRight: 20,
borderRadius: "28%", borderRadius: "28%",
border: "1px solid #ddd" border: "1px solid #ddd"
}} }}
@ -148,7 +147,8 @@ class AppsTable extends React.Component {
isDrawerVisible: false, isDrawerVisible: false,
selectedApp: null, selectedApp: null,
selectedAppIndex: -1, selectedAppIndex: -1,
loading: false loading: false,
isForbiddenErrorVisible: false
}; };
config = this.props.context; config = this.props.context;
} }
@ -240,7 +240,12 @@ class AppsTable extends React.Component {
}); });
} }
}).catch((error) => { }).catch((error) => {
handleApiError(error, "Error occurred while trying to load apps."); handleApiError(error, "Error occurred while trying to load apps.", true);
if (error.hasOwnProperty("response") && error.response.status === 403) {
this.setState({
isForbiddenErrorVisible: true
})
}
this.setState({loading: false}); this.setState({loading: false});
}); });
}; };
@ -256,29 +261,37 @@ class AppsTable extends React.Component {
render() { render() {
const {isDrawerVisible, loading} = this.state; const {isDrawerVisible, loading} = this.state;
return ( return (
<div className="apps-table"> <div>
<Table {(this.state.isForbiddenErrorVisible) && (
rowKey={record => record.id} <Alert
dataSource={this.state.apps} message="You don't have permission to view apps."
columns={columns} type="warning"
pagination={this.state.pagination} banner
onChange={this.handleTableChange} closable/>
rowClassName="app-row" )}
loading={loading} <div className="apps-table">
onRow={(record, rowIndex) => { <Table
return { rowKey={record => record.id}
onClick: event => { dataSource={this.state.apps}
this.showDrawer(record, rowIndex); columns={columns}
}, pagination={this.state.pagination}
}; onChange={this.handleTableChange}
}}/> rowClassName="app-row"
<AppDetailsDrawer loading={loading}
visible={isDrawerVisible} onRow={(record, rowIndex) => {
onClose={this.closeDrawer} return {
app={this.state.selectedApp} onClick: event => {
onUpdateApp={this.onUpdateApp}/> this.showDrawer(record, rowIndex);
},
};
}}/>
<AppDetailsDrawer
visible={isDrawerVisible}
onClose={this.closeDrawer}
app={this.state.selectedApp}
onUpdateApp={this.onUpdateApp}/>
</div>
</div> </div>
); );
} }
} }

@ -24,6 +24,7 @@ import "../../../App.css";
import DetailedRating from "../detailed-rating/DetailedRating"; import DetailedRating from "../detailed-rating/DetailedRating";
import EditRelease from "./edit-release/EditRelease"; import EditRelease from "./edit-release/EditRelease";
import {withConfigContext} from "../../../context/ConfigContext"; import {withConfigContext} from "../../../context/ConfigContext";
import NewAppUploadForm from "../../new-app/subForms/NewAppUploadForm";
const {Title, Text, Paragraph} = Typography; const {Title, Text, Paragraph} = Typography;
@ -97,6 +98,7 @@ class ReleaseView extends React.Component {
<Text>Version : {release.version}</Text><br/> <Text>Version : {release.version}</Text><br/>
<EditRelease <EditRelease
forbiddenErrors={this.props.forbiddenErrors}
isAppUpdatable={isAppUpdatable} isAppUpdatable={isAppUpdatable}
type={app.type} type={app.type}
deviceType={app.deviceType} deviceType={app.deviceType}

@ -31,7 +31,7 @@ import {
Divider, Divider,
Row, Row,
Col, Col,
Select Select, Alert
} from 'antd'; } from 'antd';
import axios from "axios"; import axios from "axios";
import "@babel/polyfill"; import "@babel/polyfill";
@ -522,63 +522,72 @@ class EditReleaseModal extends React.Component {
)} )}
</Form.Item> </Form.Item>
{(config.deviceTypes.mobileTypes.includes(deviceType)) && ( {(config.deviceTypes.mobileTypes.includes(deviceType)) && (
<Form.Item {...formItemLayout} label="Supported OS Versions"> <div>
{getFieldDecorator('supportedOS')( {(this.props.forbiddenErrors.supportedOsVersions) && (
<div> <Alert
<InputGroup> message="You don't have permission to view supported OS versions."
<Row gutter={8}> type="warning"
<Col span={11}> banner
<Form.Item> closable/>
{getFieldDecorator('lowerOsVersion', {
rules: [{
required: true,
message: 'Please select Value'
}],
})(
<Select
placeholder="Lower version"
style={{width: "100%"}}
onChange={this.handleLowerOsVersionChange}>
{supportedOsVersions.map(version => (
<Option key={version.versionName}
value={version.versionName}>
{version.versionName}
</Option>
))}
</Select>
)}
</Form.Item>
</Col>
<Col span={2}>
<p> - </p>
</Col>
<Col span={11}>
<Form.Item>
{getFieldDecorator('upperOsVersion', {
rules: [{
required: true,
message: 'Please select Value'
}],
})(
<Select style={{width: "100%"}}
placeholder="Upper version"
onChange={this.handleUpperOsVersionChange}>
{supportedOsVersions.map(version => (
<Option key={version.versionName}
value={version.versionName}>
{version.versionName}
</Option>
))}
</Select>
)}
</Form.Item>
</Col>
</Row>
</InputGroup>
</div>
)} )}
</Form.Item> <Form.Item {...formItemLayout} label="Supported OS Versions">
{getFieldDecorator('supportedOS')(
<div>
<InputGroup>
<Row gutter={8}>
<Col span={11}>
<Form.Item>
{getFieldDecorator('lowerOsVersion', {
rules: [{
required: true,
message: 'Please select Value'
}],
})(
<Select
placeholder="Lower version"
style={{width: "100%"}}
onChange={this.handleLowerOsVersionChange}>
{supportedOsVersions.map(version => (
<Option key={version.versionName}
value={version.versionName}>
{version.versionName}
</Option>
))}
</Select>
)}
</Form.Item>
</Col>
<Col span={2}>
<p> - </p>
</Col>
<Col span={11}>
<Form.Item>
{getFieldDecorator('upperOsVersion', {
rules: [{
required: true,
message: 'Please select Value'
}],
})(
<Select style={{width: "100%"}}
placeholder="Upper version"
onChange={this.handleUpperOsVersionChange}>
{supportedOsVersions.map(version => (
<Option key={version.versionName}
value={version.versionName}>
{version.versionName}
</Option>
))}
</Select>
)}
</Form.Item>
</Col>
</Row>
</InputGroup>
</div>
)}
</Form.Item>
</div>
)} )}
<Form.Item {...formItemLayout} label="Meta Data"> <Form.Item {...formItemLayout} label="Meta Data">
{getFieldDecorator('meta', { {getFieldDecorator('meta', {

@ -17,7 +17,7 @@
*/ */
import React from "react"; import React from "react";
import {List, message, Avatar, Spin, Button, notification} from 'antd'; import {List, message, Avatar, Spin, Button, notification, Alert} from 'antd';
import "./Reviews.css"; import "./Reviews.css";
import InfiniteScroll from 'react-infinite-scroller'; import InfiniteScroll from 'react-infinite-scroller';
@ -33,7 +33,10 @@ class Reviews extends React.Component {
data: [], data: [],
loading: false, loading: false,
hasMore: false, hasMore: false,
loadMore: false loadMore: false,
forbiddenErrors: {
reviews: false
}
}; };
@ -49,17 +52,34 @@ class Reviews extends React.Component {
const config = this.props.context; const config = this.props.context;
const {uuid, type} = this.props; const {uuid, type} = this.props;
this.setState({
loading: true
});
axios.get( axios.get(
window.location.origin+ config.serverConfig.invoker.uri + config.serverConfig.invoker.publisher + "/admin/reviews/" + type + "/" + uuid window.location.origin +
config.serverConfig.invoker.uri +
config.serverConfig.invoker.publisher +
"/admin/reviews/" + type + "/" + uuid
).then(res => { ).then(res => {
if (res.status === 200) { if (res.status === 200) {
let reviews = res.data.data.data; let reviews = res.data.data.data;
callback(reviews); callback(reviews);
} }
}).catch(function (error) { }).catch((error) => {
handleApiError(error, "Error occurred while trying to load reviews."); 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
});
}
}); });
}; };
@ -101,32 +121,39 @@ class Reviews extends React.Component {
render() { render() {
return ( return (
<div className="demo-infinite-container"> <div>
<InfiniteScroll {(this.state.forbiddenErrors.reviews) && (
initialLoad={false} <Alert
pageStart={0} message="You don't have permission to view reviews."
loadMore={this.handleInfiniteOnLoad} type="warning"
hasMore={!this.state.loading && this.state.hasMore} banner
useWindow={true} closable/>
> )}
<List <div className="demo-infinite-container">
dataSource={this.state.data} <InfiniteScroll
renderItem={item => ( initialLoad={false}
<List.Item key={item.id}> pageStart={0}
<SingleReview review={item}/> loadMore={this.handleInfiniteOnLoad}
</List.Item> hasMore={!this.state.loading && this.state.hasMore}
)} useWindow={true}>
> <List
{this.state.loading && this.state.hasMore && ( dataSource={this.state.data}
<div className="demo-loading-container"> renderItem={item => (
<Spin/> <List.Item key={item.id}>
</div> <SingleReview review={item}/>
)} </List.Item>
</List> )}>
</InfiniteScroll> {this.state.loading && this.state.hasMore && (
{!this.state.loadMore && (this.state.data.length >= limit) && (<div style={{textAlign: "center"}}> <div className="demo-loading-container">
<Button type="dashed" htmlType="button" onClick={this.enableLoading}>Read All Reviews</Button> <Spin/>
</div>)} </div>
)}
</List>
</InfiniteScroll>
{!this.state.loadMore && (this.state.data.length >= limit) && (<div style={{textAlign: "center"}}>
<Button type="dashed" htmlType="button" onClick={this.enableLoading}>Read All Reviews</Button>
</div>)}
</div>
</div> </div>
); );
} }

@ -32,7 +32,7 @@ import {
Modal, Modal,
Row, Row,
Col, Col,
Typography Typography, Alert
} from "antd"; } from "antd";
import axios from "axios"; import axios from "axios";
import {TweenOneGroup} from 'rc-tween-one'; import {TweenOneGroup} from 'rc-tween-one';
@ -53,13 +53,16 @@ class ManageCategories extends React.Component {
isAddNewVisible: false, isAddNewVisible: false,
isEditModalVisible: false, isEditModalVisible: false,
currentlyEditingId: null, currentlyEditingId: null,
editingValue: null editingValue: null,
forbiddenErrors: {
categories: false
}
}; };
componentDidMount() { componentDidMount() {
const config = this.props.context; const config = this.props.context;
axios.get( axios.get(
window.location.origin+ config.serverConfig.invoker.uri + config.serverConfig.invoker.publisher + "/applications/categories", window.location.origin + config.serverConfig.invoker.uri + config.serverConfig.invoker.publisher + "/applications/categories",
).then(res => { ).then(res => {
if (res.status === 200) { if (res.status === 200) {
let categories = JSON.parse(res.data.data); let categories = JSON.parse(res.data.data);
@ -70,10 +73,19 @@ class ManageCategories extends React.Component {
} }
}).catch((error) => { }).catch((error) => {
handleApiError(error, "Error occured while trying to load categories"); handleApiError(error, "Error occured while trying to load categories", true);
this.setState({ if (error.hasOwnProperty("response") && error.response.status === 403) {
loading: false const {forbiddenErrors} = this.state;
}); forbiddenErrors.categories = true;
this.setState({
forbiddenErrors,
loading: false
})
} else {
this.setState({
loading: false
});
}
}); });
} }
@ -90,7 +102,7 @@ class ManageCategories extends React.Component {
loading: true loading: true
}); });
axios.delete( axios.delete(
window.location.origin+ config.serverConfig.invoker.uri + config.serverConfig.invoker.publisher + "/admin/applications/categories/" + id, window.location.origin + config.serverConfig.invoker.uri + config.serverConfig.invoker.publisher + "/admin/applications/categories/" + id,
).then(res => { ).then(res => {
if (res.status === 200) { if (res.status === 200) {
notification["success"]({ notification["success"]({
@ -125,8 +137,7 @@ class ManageCategories extends React.Component {
const tagElem = ( const tagElem = (
<Tag <Tag
color={pSBC(0.30, config.theme.primaryColor)} color={pSBC(0.30, config.theme.primaryColor)}
style={{marginTop:8}} style={{marginTop: 8}}>
>
{categoryName} {categoryName}
<Divider type="vertical"/> <Divider type="vertical"/>
<Tooltip title="edit"> <Tooltip title="edit">
@ -150,8 +161,7 @@ class ManageCategories extends React.Component {
} }
}} }}
okText="Yes" okText="Yes"
cancelText="No" cancelText="No">
>
<Icon type="delete"/> <Icon type="delete"/>
</Popconfirm> </Popconfirm>
</Tooltip> </Tooltip>
@ -168,7 +178,7 @@ class ManageCategories extends React.Component {
const config = this.props.context; const config = this.props.context;
const tagElem = ( const tagElem = (
<Tag <Tag
style={{marginTop:8}} style={{marginTop: 8}}
closable closable
onClose={e => { onClose={e => {
e.preventDefault(); e.preventDefault();
@ -229,7 +239,7 @@ class ManageCategories extends React.Component {
const data = tempElements.map(category => category.categoryName); const data = tempElements.map(category => category.categoryName);
axios.post( axios.post(
window.location.origin+ config.serverConfig.invoker.uri + config.serverConfig.invoker.publisher + "/admin/applications/categories", window.location.origin + config.serverConfig.invoker.uri + config.serverConfig.invoker.publisher + "/admin/applications/categories",
data, data,
).then(res => { ).then(res => {
if (res.status === 200) { if (res.status === 200) {
@ -287,7 +297,7 @@ class ManageCategories extends React.Component {
}); });
axios.put( axios.put(
window.location.origin+ config.serverConfig.invoker.uri + config.serverConfig.invoker.publisher + "/admin/applications/categories/rename?from=" + currentlyEditingId + "&to=" + editingValue, window.location.origin + config.serverConfig.invoker.uri + config.serverConfig.invoker.publisher + "/admin/applications/categories/rename?from=" + currentlyEditingId + "&to=" + editingValue,
{}, {},
).then(res => { ).then(res => {
if (res.status === 200) { if (res.status === 200) {
@ -324,11 +334,18 @@ class ManageCategories extends React.Component {
}; };
render() { render() {
const {categories, inputVisible, inputValue, tempElements, isAddNewVisible} = this.state; const {categories, inputVisible, inputValue, tempElements, isAddNewVisible, forbiddenErrors} = this.state;
const categoriesElements = categories.map(this.renderElement); const categoriesElements = categories.map(this.renderElement);
const temporaryElements = tempElements.map(this.renderTempElement); const temporaryElements = tempElements.map(this.renderTempElement);
return ( return (
<div style={{marginBottom: 16}}> <div style={{marginBottom: 16}}>
{(forbiddenErrors.categories) && (
<Alert
message="You don't have permission to view categories."
type="warning"
banner
closable/>
)}
<Card> <Card>
<Spin tip="Working on it..." spinning={this.state.loading}> <Spin tip="Working on it..." spinning={this.state.loading}>
<Row> <Row>

@ -31,7 +31,7 @@ import {
Popconfirm, Popconfirm,
Modal, Modal,
Row, Col, Row, Col,
Typography Typography, Alert
} from "antd"; } from "antd";
import axios from "axios"; import axios from "axios";
import {TweenOneGroup} from 'rc-tween-one'; import {TweenOneGroup} from 'rc-tween-one';
@ -51,13 +51,16 @@ class ManageTags extends React.Component {
isAddNewVisible: false, isAddNewVisible: false,
isEditModalVisible: false, isEditModalVisible: false,
currentlyEditingId: null, currentlyEditingId: null,
editingValue: null editingValue: null,
forbiddenErrors: {
tags: false
}
}; };
componentDidMount() { componentDidMount() {
const config = this.props.context; const config = this.props.context;
axios.get( axios.get(
window.location.origin+ config.serverConfig.invoker.uri + config.serverConfig.invoker.publisher + "/applications/tags", window.location.origin + config.serverConfig.invoker.uri + config.serverConfig.invoker.publisher + "/applications/tags",
).then(res => { ).then(res => {
if (res.status === 200) { if (res.status === 200) {
let tags = JSON.parse(res.data.data); let tags = JSON.parse(res.data.data);
@ -68,10 +71,19 @@ class ManageTags extends React.Component {
} }
}).catch((error) => { }).catch((error) => {
handleApiError(error, "Error occurred while trying to load tags."); handleApiError(error, "Error occurred while trying to load tags.", true);
this.setState({ if (error.hasOwnProperty("response") && error.response.status === 403) {
loading: false const {forbiddenErrors} = this.state;
}); forbiddenErrors.tags = true;
this.setState({
forbiddenErrors,
loading: false
})
} else {
this.setState({
loading: false
});
}
}); });
} }
@ -90,7 +102,7 @@ class ManageTags extends React.Component {
}); });
axios.delete( axios.delete(
window.location.origin+ config.serverConfig.invoker.uri + config.serverConfig.invoker.publisher + "/admin/applications/tags/" + id window.location.origin + config.serverConfig.invoker.uri + config.serverConfig.invoker.publisher + "/admin/applications/tags/" + id
).then(res => { ).then(res => {
if (res.status === 200) { if (res.status === 200) {
notification["success"]({ notification["success"]({
@ -124,7 +136,7 @@ class ManageTags extends React.Component {
const tagElem = ( const tagElem = (
<Tag <Tag
color="#34495e" color="#34495e"
style={{marginTop:8}} style={{marginTop: 8}}
> >
{tagName} {tagName}
<Divider type="vertical"/> <Divider type="vertical"/>
@ -167,7 +179,7 @@ class ManageTags extends React.Component {
const {tempElements} = this.state; const {tempElements} = this.state;
const tagElem = ( const tagElem = (
<Tag <Tag
style={{marginTop:8}} style={{marginTop: 8}}
closable closable
onClose={e => { onClose={e => {
e.preventDefault(); e.preventDefault();
@ -226,7 +238,7 @@ class ManageTags extends React.Component {
const data = tempElements.map(tag => tag.tagName); const data = tempElements.map(tag => tag.tagName);
axios.post(window.location.origin+ config.serverConfig.invoker.uri + config.serverConfig.invoker.publisher + "/applications/tags", axios.post(window.location.origin + config.serverConfig.invoker.uri + config.serverConfig.invoker.publisher + "/applications/tags",
data, data,
).then(res => { ).then(res => {
if (res.status === 200) { if (res.status === 200) {
@ -284,7 +296,7 @@ class ManageTags extends React.Component {
}); });
axios.put( axios.put(
window.location.origin+ config.serverConfig.invoker.uri + config.serverConfig.invoker.publisher + "/applications/tags/rename?from=" + currentlyEditingId + "&to=" + editingValue, window.location.origin + config.serverConfig.invoker.uri + config.serverConfig.invoker.publisher + "/applications/tags/rename?from=" + currentlyEditingId + "&to=" + editingValue,
{}, {},
).then(res => { ).then(res => {
if (res.status === 200) { if (res.status === 200) {
@ -321,11 +333,18 @@ class ManageTags extends React.Component {
}; };
render() { render() {
const {tags, inputVisible, inputValue, tempElements, isAddNewVisible} = this.state; const {tags, inputVisible, inputValue, tempElements, isAddNewVisible, forbiddenErrors} = this.state;
const tagsElements = tags.map(this.renderElement); const tagsElements = tags.map(this.renderElement);
const temporaryElements = tempElements.map(this.renderTempElement); const temporaryElements = tempElements.map(this.renderTempElement);
return ( return (
<div style={{marginBottom: 16}}> <div style={{marginBottom: 16}}>
{(forbiddenErrors.tags) && (
<Alert
message="You don't have permission to view tags."
type="warning"
banner
closable/>
)}
<Card> <Card>
<Spin tip="Working on it..." spinning={this.state.loading}> <Spin tip="Working on it..." spinning={this.state.loading}>
<Row> <Row>
@ -365,8 +384,7 @@ class ManageTags extends React.Component {
}, },
}} }}
leave={{opacity: 0, width: 0, scale: 0, duration: 200}} leave={{opacity: 0, width: 0, scale: 0, duration: 200}}
appear={false} appear={false}>
>
{temporaryElements} {temporaryElements}
{inputVisible && ( {inputVisible && (

@ -55,7 +55,10 @@ class AddNewAppFormComponent extends React.Component {
isError: false, isError: false,
deviceType: null, deviceType: null,
supportedOsVersions: [], supportedOsVersions: [],
errorText: "" errorText: "",
forbiddenErrors: {
supportedOsVersions: false
}
}; };
} }
@ -143,15 +146,24 @@ class AddNewAppFormComponent extends React.Component {
}); });
} }
}).catch((error) => { }).catch((error) => {
handleApiError(error, "Error occurred while trying to load supported OS versions."); handleApiError(error, "Error occurred while trying to load supported OS versions.", true);
this.setState({ if (error.hasOwnProperty("response") && error.response.status === 403) {
loading: false const {forbiddenErrors} = this.state;
}); forbiddenErrors.supportedOsVersions = true;
this.setState({
forbiddenErrors,
loading: false
})
} else {
this.setState({
loading: false
});
}
}); });
}; };
render() { render() {
const {loading, current, isError, supportedOsVersions, errorText} = this.state; const {loading, current, isError, supportedOsVersions, errorText, forbiddenErrors} = this.state;
const {formConfig} = this.props; const {formConfig} = this.props;
return ( return (
<div> <div>
@ -171,6 +183,7 @@ class AddNewAppFormComponent extends React.Component {
</div> </div>
<div style={{display: (current === 1 ? 'unset' : 'none')}}> <div style={{display: (current === 1 ? 'unset' : 'none')}}>
<NewAppUploadForm <NewAppUploadForm
forbiddenErrors={forbiddenErrors}
formConfig={formConfig} formConfig={formConfig}
supportedOsVersions={supportedOsVersions} supportedOsVersions={supportedOsVersions}
onSuccessReleaseData={this.onSuccessReleaseData} onSuccessReleaseData={this.onSuccessReleaseData}

@ -17,10 +17,11 @@
*/ */
import React from "react"; import React from "react";
import {Button, Col, Divider, Form, Icon, Input, notification, Row, Select, Switch, Upload} from "antd"; import {Alert, Button, Col, Divider, Form, Icon, Input, notification, Row, Select, Spin, Switch, Upload} from "antd";
import axios from "axios"; import axios from "axios";
import {withConfigContext} from "../../../context/ConfigContext"; import {withConfigContext} from "../../../context/ConfigContext";
import {handleApiError} from "../../../js/Utils"; import {handleApiError} from "../../../js/Utils";
import debounce from 'lodash.debounce';
const formItemLayout = { const formItemLayout = {
labelCol: { labelCol: {
@ -42,12 +43,21 @@ class NewAppDetailsForm extends React.Component {
this.state = { this.state = {
categories: [], categories: [],
tags: [], tags: [],
deviceTypes:[] deviceTypes: [],
fetching: false,
roleSearchValue: [],
unrestrictedRoles: [],
forbiddenErrors: {
categories: false,
tags: false,
deviceTypes: false,
roles: false
}
}; };
this.lastFetchId = 0;
this.fetchRoles = debounce(this.fetchRoles, 800);
} }
handleSubmit = e => { handleSubmit = e => {
e.preventDefault(); e.preventDefault();
const {formConfig} = this.props; const {formConfig} = this.props;
@ -58,13 +68,17 @@ class NewAppDetailsForm extends React.Component {
this.setState({ this.setState({
loading: true loading: true
}); });
const {name, description, categories, tags, price, isSharedWithAllTenants, binaryFile, icon, screenshots, releaseDescription, releaseType} = values; const {name, description, categories, tags, unrestrictedRoles} = values;
const unrestrictedRolesData = [];
unrestrictedRoles.map(val => {
unrestrictedRolesData.push(val.key);
});
const application = { const application = {
name, name,
description, description,
categories, categories,
tags, tags,
unrestrictedRoles: [], unrestrictedRoles: unrestrictedRolesData,
}; };
if (formConfig.installationType !== "WEB_CLIP") { if (formConfig.installationType !== "WEB_CLIP") {
@ -81,6 +95,8 @@ class NewAppDetailsForm extends React.Component {
componentDidMount() { componentDidMount() {
this.getCategories(); this.getCategories();
this.getTags();
this.getDeviceTypes();
} }
getCategories = () => { getCategories = () => {
@ -95,13 +111,20 @@ class NewAppDetailsForm extends React.Component {
loading: false loading: false
}); });
} }
this.getTags();
}).catch((error) => { }).catch((error) => {
handleApiError(error, "Error occurred while trying to load categories."); handleApiError(error, "Error occurred while trying to load categories.", true);
this.setState({ if (error.hasOwnProperty("response") && error.response.status === 403) {
loading: false const {forbiddenErrors} = this.state;
}); forbiddenErrors.categories = true;
this.setState({
forbiddenErrors,
loading: false
})
} else {
this.setState({
loading: false
});
}
}); });
}; };
@ -117,13 +140,20 @@ class NewAppDetailsForm extends React.Component {
loading: false, loading: false,
}); });
} }
this.getDeviceTypes();
}).catch((error) => { }).catch((error) => {
handleApiError(error, "Error occurred while trying to load tags."); handleApiError(error, "Error occurred while trying to load tags.", true);
this.setState({ if (error.hasOwnProperty("response") && error.response.status === 403) {
loading: false const {forbiddenErrors} = this.state;
}); forbiddenErrors.tags = true;
this.setState({
forbiddenErrors,
loading: false
})
} else {
this.setState({
loading: false
});
}
}); });
}; };
@ -141,15 +171,15 @@ class NewAppDetailsForm extends React.Component {
const allowedDeviceTypes = []; const allowedDeviceTypes = [];
// exclude mobile device types if installation type is custom // exclude mobile device types if installation type is custom
if(installationType==="CUSTOM"){ if (installationType === "CUSTOM") {
allDeviceTypes.forEach(deviceType=>{ allDeviceTypes.forEach(deviceType => {
if(!mobileDeviceTypes.includes(deviceType.name)){ if (!mobileDeviceTypes.includes(deviceType.name)) {
allowedDeviceTypes.push(deviceType); allowedDeviceTypes.push(deviceType);
} }
}); });
}else{ } else {
allDeviceTypes.forEach(deviceType=>{ allDeviceTypes.forEach(deviceType => {
if(mobileDeviceTypes.includes(deviceType.name)){ if (mobileDeviceTypes.includes(deviceType.name)) {
allowedDeviceTypes.push(deviceType); allowedDeviceTypes.push(deviceType);
} }
}); });
@ -161,16 +191,76 @@ class NewAppDetailsForm extends React.Component {
}); });
} }
}).catch((error) => { }).catch((error) => {
handleApiError(error, "Error occurred while trying to load device types."); handleApiError(error, "Error occurred while trying to load device types.", true);
this.setState({ if (error.hasOwnProperty("response") && error.response.status === 403) {
loading: false const {forbiddenErrors} = this.state;
}); forbiddenErrors.deviceTypes = true;
this.setState({
forbiddenErrors,
loading: false
})
} else {
this.setState({
loading: false
});
}
});
};
fetchRoles = value => {
const config = this.props.context;
this.lastFetchId += 1;
const fetchId = this.lastFetchId;
this.setState({data: [], fetching: true});
axios.get(
window.location.origin + config.serverConfig.invoker.uri + config.serverConfig.invoker.deviceMgt + "/roles?filter=" + value,
).then(res => {
if (res.status === 200) {
if (fetchId !== this.lastFetchId) {
// for fetch callback order
return;
}
const data = res.data.data.roles.map(role => ({
text: role,
value: role,
}));
this.setState({
unrestrictedRoles: data,
fetching: false
});
}
}).catch((error) => {
handleApiError(error, "Error occurred while trying to load roles.", true);
if (error.hasOwnProperty("response") && error.response.status === 403) {
const {forbiddenErrors} = this.state;
forbiddenErrors.roles = true;
this.setState({
forbiddenErrors,
fetching: false
})
} else {
this.setState({
fetching: false
});
}
});
};
handleRoleSearch = roleSearchValue => {
this.setState({
roleSearchValue,
unrestrictedRoles: [],
fetching: false,
}); });
}; };
render() { render() {
const {formConfig} = this.props; const {formConfig} = this.props;
const {categories, tags, deviceTypes} = this.state; const {categories, tags, deviceTypes, fetching, roleSearchValue, unrestrictedRoles, forbiddenErrors} = this.state;
const {getFieldDecorator} = this.props.form; const {getFieldDecorator} = this.props.form;
return ( return (
@ -185,34 +275,41 @@ class NewAppDetailsForm extends React.Component {
layout="horizontal" layout="horizontal"
onSubmit={this.handleSubmit}> onSubmit={this.handleSubmit}>
{formConfig.installationType !== "WEB_CLIP" && ( {formConfig.installationType !== "WEB_CLIP" && (
<Form.Item {...formItemLayout} label="Device Type"> <div>
{getFieldDecorator('deviceType', { {(forbiddenErrors.deviceTypes) && (
rules: [ <Alert
message="You don't have permission to view device types."
type="warning"
banner
closable/>
)}
<Form.Item {...formItemLayout} label="Device Type">
{getFieldDecorator('deviceType', {
rules: [
{
required: true,
message: 'Please select device type'
}
],
}
)(
<Select
style={{width: '100%'}}
placeholder="select device type">
{ {
required: true, deviceTypes.map(deviceType => {
message: 'Please select device type' return (
<Option
key={deviceType.name}>
{deviceType.name}
</Option>
)
})
} }
], </Select>
} )}
)( </Form.Item>
<Select </div>
style={{width: '100%'}}
placeholder="select device type"
onChange={this.handleCategoryChange}
>
{
deviceTypes.map(deviceType => {
return (
<Option
key={deviceType.name}>
{deviceType.name}
</Option>
)
})
}
</Select>
)}
</Form.Item>
)} )}
{/*app name*/} {/*app name*/}
@ -238,6 +335,43 @@ class NewAppDetailsForm extends React.Component {
<TextArea placeholder="Enter the description..." rows={7}/> <TextArea placeholder="Enter the description..." rows={7}/>
)} )}
</Form.Item> </Form.Item>
{/*Unrestricted Roles*/}
{(forbiddenErrors.roles) && (
<Alert
message="You don't have permission to view roles."
type="warning"
banner
closable/>
)}
<Form.Item {...formItemLayout} label="Unrestricted Roles">
{getFieldDecorator('unrestrictedRoles', {
rules: [],
initialValue: []
})(
<Select
mode="multiple"
labelInValue
// value={roleSearchValue}
placeholder="Search roles"
notFoundContent={fetching ? <Spin size="small"/> : null}
filterOption={false}
onSearch={this.fetchRoles}
onChange={this.handleRoleSearch}
style={{width: '100%'}}>
{unrestrictedRoles.map(d => (
<Option key={d.value}>{d.text}</Option>
))}
</Select>
)}
</Form.Item>
{(forbiddenErrors.categories) && (
<Alert
message="You don't have permission to view categories."
type="warning"
banner
closable/>
)}
<Form.Item {...formItemLayout} label="Categories"> <Form.Item {...formItemLayout} label="Categories">
{getFieldDecorator('categories', { {getFieldDecorator('categories', {
rules: [{ rules: [{
@ -249,8 +383,7 @@ class NewAppDetailsForm extends React.Component {
mode="multiple" mode="multiple"
style={{width: '100%'}} style={{width: '100%'}}
placeholder="Select a Category" placeholder="Select a Category"
onChange={this.handleCategoryChange} onChange={this.handleCategoryChange}>
>
{ {
categories.map(category => { categories.map(category => {
return ( return (
@ -264,6 +397,13 @@ class NewAppDetailsForm extends React.Component {
</Select> </Select>
)} )}
</Form.Item> </Form.Item>
{(forbiddenErrors.tags) && (
<Alert
message="You don't have permission to view tags."
type="warning"
banner
closable/>
)}
<Form.Item {...formItemLayout} label="Tags"> <Form.Item {...formItemLayout} label="Tags">
{getFieldDecorator('tags', { {getFieldDecorator('tags', {
rules: [{ rules: [{
@ -274,8 +414,7 @@ class NewAppDetailsForm extends React.Component {
<Select <Select
mode="tags" mode="tags"
style={{width: '100%'}} style={{width: '100%'}}
placeholder="Tags" placeholder="Tags">
>
{ {
tags.map(tag => { tags.map(tag => {
return ( return (

@ -17,7 +17,7 @@
*/ */
import React from "react"; import React from "react";
import {Button, Col, Form, Icon, Input, Row, Select, Switch, Upload, InputNumber, Modal} from "antd"; import {Button, Col, Form, Icon, Input, Row, Select, Switch, Upload, InputNumber, Modal, Alert} from "antd";
import "@babel/polyfill"; import "@babel/polyfill";
import axios from "axios"; import axios from "axios";
import {handleApiError} from "../../../js/Utils"; import {handleApiError} from "../../../js/Utils";
@ -117,7 +117,7 @@ class NewAppUploadForm extends React.Component {
osVersionsHelperText: 'Please select supported OS versions', osVersionsHelperText: 'Please select supported OS versions',
osVersionsValidateStatus: 'error', osVersionsValidateStatus: 'error',
}); });
} else if (this.lowerOsVersion >= this.upperOsVersion) { } else if (parseFloat(this.lowerOsVersion) >= parseFloat(this.upperOsVersion)) {
isFormValid = false; isFormValid = false;
this.setState({ this.setState({
osVersionsHelperText: 'Please select valid range', osVersionsHelperText: 'Please select valid range',
@ -221,7 +221,7 @@ class NewAppUploadForm extends React.Component {
}; };
handleLowerOsVersionChange = (lowerOsVersion) => { handleLowerOsVersionChange = (lowerOsVersion) => {
this.lowerOsVersion = parseFloat(lowerOsVersion); this.lowerOsVersion = lowerOsVersion;
this.setState({ this.setState({
osVersionsValidateStatus: 'validating', osVersionsValidateStatus: 'validating',
osVersionsHelperText: '' osVersionsHelperText: ''
@ -229,7 +229,11 @@ class NewAppUploadForm extends React.Component {
}; };
handleUpperOsVersionChange = (upperOsVersion) => { handleUpperOsVersionChange = (upperOsVersion) => {
this.upperOsVersion = parseFloat(upperOsVersion); if (upperOsVersion === "all") {
this.upperOsVersion = this.props.supportedOsVersions[this.props.supportedOsVersions.length - 1]["versionName"];
}else{
this.upperOsVersion = upperOsVersion;
}
this.setState({ this.setState({
osVersionsValidateStatus: 'validating', osVersionsValidateStatus: 'validating',
osVersionsHelperText: '' osVersionsHelperText: ''
@ -400,60 +404,69 @@ class NewAppUploadForm extends React.Component {
</Form.Item> </Form.Item>
{(formConfig.installationType !== "WEB_CLIP" && formConfig.installationType !== "CUSTOM") && ( {(formConfig.installationType !== "WEB_CLIP" && formConfig.installationType !== "CUSTOM") && (
<Form.Item <div>
{...formItemLayout} {(this.props.forbiddenErrors.supportedOsVersions) && (
label="Supported OS Versions" <Alert
validateStatus={osVersionsValidateStatus} message="You don't have permission to view supported OS versions."
help={osVersionsHelperText}> type="warning"
{getFieldDecorator('supportedOS', { banner
rules: [{ closable/>
required: true
}],
initialValue: false
})(
<div>
<InputGroup>
<Row gutter={8}>
<Col span={11}>
<Select
placeholder="Lower version"
style={{width: "100%"}}
onChange={this.handleLowerOsVersionChange}>
{supportedOsVersions.map(version => (
<Option key={version.versionName}
value={version.versionName}>
{version.versionName}
</Option>
))}
</Select>
</Col>
<Col span={2}>
<p> - </p>
</Col>
<Col span={11}>
<Select style={{width: "100%"}}
placeholder="Upper version"
defaultActiveFirstOption={true}
onChange={this.handleUpperOsVersionChange}>
{(supportedOsVersions.length > 0) &&(
<Option key="all"
value={supportedOsVersions[supportedOsVersions.length-1]["versionName"]}>
All
</Option>
)}
{supportedOsVersions.map(version => (
<Option key={version.versionName}
value={version.versionName}>
{version.versionName}
</Option>
))}
</Select>
</Col>
</Row>
</InputGroup>
</div>
)} )}
</Form.Item> <Form.Item
{...formItemLayout}
label="Supported OS Versions"
validateStatus={osVersionsValidateStatus}
help={osVersionsHelperText}>
{getFieldDecorator('supportedOS', {
rules: [{
required: true
}],
initialValue: false
})(
<div>
<InputGroup>
<Row gutter={8}>
<Col span={11}>
<Select
placeholder="Lower version"
style={{width: "100%"}}
onChange={this.handleLowerOsVersionChange}>
{supportedOsVersions.map(version => (
<Option key={version.versionName}
value={version.versionName}>
{version.versionName}
</Option>
))}
</Select>
</Col>
<Col span={2}>
<p> - </p>
</Col>
<Col span={11}>
<Select style={{width: "100%"}}
placeholder="Upper version"
defaultActiveFirstOption={true}
onChange={this.handleUpperOsVersionChange}>
{(supportedOsVersions.length > 0) && (
<Option key="all"
value="all">
All
</Option>
)}
{supportedOsVersions.map(version => (
<Option key={version.versionName}
value={version.versionName}>
{version.versionName}
</Option>
))}
</Select>
</Col>
</Row>
</InputGroup>
</div>
)}
</Form.Item>
</div>
)} )}
<Form.Item {...formItemLayout} label="Meta Data"> <Form.Item {...formItemLayout} label="Meta Data">
{getFieldDecorator('meta', {})( {getFieldDecorator('meta', {})(

@ -41,7 +41,10 @@ class AddNewReleaseFormComponent extends React.Component {
supportedOsVersions: [], supportedOsVersions: [],
application: null, application: null,
release: null, release: null,
deviceType: null deviceType: null,
forbiddenErrors: {
supportedOsVersions: false
}
}; };
} }
@ -63,10 +66,19 @@ class AddNewReleaseFormComponent extends React.Component {
}); });
} }
}).catch((error) => { }).catch((error) => {
handleApiError(error, "Error occurred while trying to load supported OS versions."); handleApiError(error, "Error occurred while trying to load supported OS versions.", true);
this.setState({ if (error.hasOwnProperty("response") && error.response.status === 403) {
loading: false const {forbiddenErrors} = this.state;
}); forbiddenErrors.supportedOsVersions = true;
this.setState({
forbiddenErrors,
loading: false
})
} else {
this.setState({
loading: false
});
}
}); });
}; };
@ -85,7 +97,7 @@ class AddNewReleaseFormComponent extends React.Component {
data.append("applicationRelease", blob); data.append("applicationRelease", blob);
const url = window.location.origin + config.serverConfig.invoker.uri + config.serverConfig.invoker.publisher + const url = window.location.origin + config.serverConfig.invoker.uri + config.serverConfig.invoker.publisher +
"/applications/" + deviceType + "/ent-app/" + appId; "/applications/" + deviceType + "/ent-app/" + appId;
axios.post( axios.post(
url, url,
data data
@ -122,14 +134,15 @@ class AddNewReleaseFormComponent extends React.Component {
}; };
render() { render() {
const {loading, supportedOsVersions} = this.state; const {loading, supportedOsVersions, forbiddenErrors} = this.state;
return ( return (
<div> <div>
<Spin tip="Uploading..." spinning={loading}> <Spin tip="Uploading..." spinning={loading}>
<Row> <Row>
<Col span={17} offset={4} > <Col span={17} offset={4}>
<Card> <Card>
<NewAppUploadForm <NewAppUploadForm
forbiddenErrors={forbiddenErrors}
formConfig={formConfig} formConfig={formConfig}
supportedOsVersions={supportedOsVersions} supportedOsVersions={supportedOsVersions}
onSuccessReleaseData={this.onSuccessReleaseData} onSuccessReleaseData={this.onSuccessReleaseData}

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

@ -24,6 +24,7 @@ import ReleaseView from "../../../../components/apps/release/ReleaseView";
import LifeCycle from "../../../../components/apps/release/lifeCycle/LifeCycle"; import LifeCycle from "../../../../components/apps/release/lifeCycle/LifeCycle";
import {withConfigContext} from "../../../../context/ConfigContext"; import {withConfigContext} from "../../../../context/ConfigContext";
import {handleApiError} from "../../../../js/Utils"; import {handleApiError} from "../../../../js/Utils";
import NewAppUploadForm from "../../../../components/new-app/subForms/NewAppUploadForm";
const {Title} = Typography; const {Title} = Typography;
@ -40,13 +41,19 @@ class Release extends React.Component {
release: null, release: null,
currentLifecycleStatus: null, currentLifecycleStatus: null,
lifecycle: null, lifecycle: null,
supportedOsVersions:[] supportedOsVersions: [],
forbiddenErrors: {
supportedOsVersions: false,
lifeCycle: false
}
}; };
} }
componentDidMount() { componentDidMount() {
const {uuid} = this.props.match.params; const {uuid} = this.props.match.params;
this.fetchData(uuid); this.fetchData(uuid);
this.getLifecycle();
} }
componentDidUpdate(prevProps, prevState, snapshot) { componentDidUpdate(prevProps, prevState, snapshot) {
@ -86,10 +93,8 @@ class Release extends React.Component {
loading: false, loading: false,
uuid: uuid uuid: uuid
}); });
if(config.deviceTypes.mobileTypes.includes(app.deviceType)){ if (config.deviceTypes.mobileTypes.includes(app.deviceType)) {
this.getSupportedOsVersions(app.deviceType); this.getSupportedOsVersions(app.deviceType);
}else{
this.getLifecycle();
} }
} }
@ -111,8 +116,15 @@ class Release extends React.Component {
}) })
} }
}).catch(function (error) { }).catch((error) => {
handleApiError(error, "Error occurred while trying to load lifecycle configuration."); handleApiError(error, "Error occurred while trying to load lifecycle configuration.", true);
if (error.hasOwnProperty("response") && error.response.status === 403) {
const {forbiddenErrors} = this.state;
forbiddenErrors.lifeCycle = true;
this.setState({
forbiddenErrors
})
}
}); });
}; };
@ -125,21 +137,28 @@ class Release extends React.Component {
if (res.status === 200) { if (res.status === 200) {
let supportedOsVersions = JSON.parse(res.data.data); let supportedOsVersions = JSON.parse(res.data.data);
this.setState({ this.setState({
supportedOsVersions, supportedOsVersions
loading: false,
}); });
this.getLifecycle();
} }
}).catch((error) => { }).catch((error) => {
handleApiError(error, "Error occurred while trying to load supported OS versions."); handleApiError(error, "Error occurred while trying to load supported OS versions.", true);
this.setState({ if (error.hasOwnProperty("response") && error.response.status === 403) {
loading: false const {forbiddenErrors} = this.state;
}); forbiddenErrors.supportedOsVersions = true;
this.setState({
forbiddenErrors,
loading: false
})
} else {
this.setState({
loading: false
});
}
}); });
}; };
render() { render() {
const {app, release, currentLifecycleStatus, lifecycle, loading} = this.state; const {app, release, currentLifecycleStatus, lifecycle, loading, forbiddenErrors} = this.state;
if (release == null && loading === false) { if (release == null && loading === false) {
return ( return (
@ -159,12 +178,13 @@ class Release extends React.Component {
<Skeleton loading={loading} avatar={{size: 'large'}} active paragraph={{rows: 18}}> <Skeleton loading={loading} avatar={{size: 'large'}} active paragraph={{rows: 18}}>
{(release !== null) && ( {(release !== null) && (
<ReleaseView <ReleaseView
forbiddenErrors={forbiddenErrors}
app={app} app={app}
release={release} release={release}
currentLifecycleStatus={currentLifecycleStatus} currentLifecycleStatus={currentLifecycleStatus}
lifecycle={lifecycle} lifecycle={lifecycle}
updateRelease={this.updateRelease} updateRelease={this.updateRelease}
supportedOsVersions = {[...this.state.supportedOsVersions]} supportedOsVersions={[...this.state.supportedOsVersions]}
/>) />)
} }
</Skeleton> </Skeleton>

@ -50,7 +50,7 @@ class AppCard extends React.Component {
</Col> </Col>
<Col span={24} style={{paddingTop:10}}> <Col span={24} style={{paddingTop:10}}>
<Text className="app-name" strong level={4}>{app.name}</Text><br/> <Text className="app-name" strong level={4}>{app.name}</Text><br/>
<Text type="secondary" level={4}>{app.subMethod.toLowerCase()}</Text><br/> <Text type="secondary" level={4}>{app.type.toLowerCase()}</Text><br/>
<StarRatings <StarRatings
rating={app.rating} rating={app.rating}
starRatedColor="#777" starRatedColor="#777"

@ -18,7 +18,7 @@
import React from "react"; import React from "react";
import AppCard from "./AppCard"; import AppCard from "./AppCard";
import {Col, message, notification, Row, Result, Skeleton} from "antd"; import {Col, message, notification, Row, Result, Skeleton, Alert} from "antd";
import axios from "axios"; import axios from "axios";
import {withConfigContext} from "../../context/ConfigContext"; import {withConfigContext} from "../../context/ConfigContext";
import {handleApiError} from "../../js/Utils"; import {handleApiError} from "../../js/Utils";
@ -28,7 +28,10 @@ class AppList extends React.Component {
super(props); super(props);
this.state = { this.state = {
apps: [], apps: [],
loading: true loading: true,
forbiddenErrors: {
apps: false
}
} }
} }
@ -60,7 +63,7 @@ class AppList extends React.Component {
}); });
//send request to the invoker //send request to the invoker
axios.post( axios.post(
window.location.origin+ config.serverConfig.invoker.uri + config.serverConfig.invoker.store + "/applications/", window.location.origin + config.serverConfig.invoker.uri + config.serverConfig.invoker.store + "/applications/",
payload, payload,
).then(res => { ).then(res => {
if (res.status === 200) { if (res.status === 200) {
@ -73,18 +76,37 @@ class AppList extends React.Component {
} }
}).catch((error) => { }).catch((error) => {
handleApiError(error,"Error occurred while trying to load apps."); handleApiError(error, "Error occurred while trying to load apps.", true);
this.setState({loading: false}); 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
});
}
}); });
}; };
render() { render() {
const {apps,loading} = this.state; const {apps, loading, forbiddenErrors} = this.state;
return ( return (
<Skeleton loading={loading} active> <Skeleton loading={loading} active>
<Row gutter={16}> <Row gutter={16}>
{apps.length === 0 && ( {(forbiddenErrors.apps) && (
<Result
status="403"
title="403"
subTitle="You don't have permission to view apps."
// extra={<Button type="primary">Back Home</Button>}
/>
)}
{!((forbiddenErrors.apps)) && apps.length === 0 && (
<Result <Result
status="404" status="404"
title="No apps, yet." title="No apps, yet."

@ -27,7 +27,7 @@ import AppUninstallModal from "./install/AppUninstallModal";
import {withConfigContext} from "../../../context/ConfigContext"; import {withConfigContext} from "../../../context/ConfigContext";
import {handleApiError} from "../../../js/Utils"; import {handleApiError} from "../../../js/Utils";
import ReviewContainer from "./review/ReviewContainer"; import ReviewContainer from "./review/ReviewContainer";
import InstalledDevicesTable from "./InstalledDevicesTable"; import SubscriptionDetails from "./SubscriptionDetails";
const {Title, Text, Paragraph} = Typography; const {Title, Text, Paragraph} = Typography;
const {TabPane} = Tabs; const {TabPane} = Tabs;
@ -205,7 +205,7 @@ class ReleaseView extends React.Component {
<ReviewContainer uuid={release.uuid}/> <ReviewContainer uuid={release.uuid}/>
</TabPane> </TabPane>
<TabPane tab="Subscription Details" key="2"> <TabPane tab="Subscription Details" key="2">
<InstalledDevicesTable uuid={release.uuid}/> <SubscriptionDetails uuid={release.uuid}/>
</TabPane> </TabPane>
</Tabs> </Tabs>
</div> </div>

@ -18,7 +18,20 @@
import React from "react"; import React from "react";
import axios from "axios"; import axios from "axios";
import {Tag, message, notification, Table, Typography, Tooltip, Icon, Divider, Button, Modal, Select} from "antd"; import {
Tag,
message,
notification,
Table,
Typography,
Tooltip,
Icon,
Divider,
Button,
Modal,
Select,
Alert
} from "antd";
import TimeAgo from 'javascript-time-ago' import TimeAgo from 'javascript-time-ago'
// Load locale-specific relative date/time formatting rules. // Load locale-specific relative date/time formatting rules.
@ -53,7 +66,16 @@ const columns = [
title: 'Action', title: 'Action',
dataIndex: 'action', dataIndex: 'action',
key: 'action', key: 'action',
render: action => action.toLowerCase() 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', title: 'Triggered By',
@ -128,7 +150,7 @@ const getTimeAgo = (time) => {
}; };
class InstalledDevicesTable extends React.Component { class SubscriptionDetails extends React.Component {
constructor(props) { constructor(props) {
super(props); super(props);
config = this.props.context; config = this.props.context;
@ -140,7 +162,8 @@ class InstalledDevicesTable extends React.Component {
selectedRows: [], selectedRows: [],
deviceGroups: [], deviceGroups: [],
groupModalVisible: false, groupModalVisible: false,
selectedGroupId: [] selectedGroupId: [],
isForbidden: false
}; };
} }
@ -171,18 +194,25 @@ class InstalledDevicesTable extends React.Component {
`/admin/subscription/${this.props.uuid}?` + encodedExtraParams, `/admin/subscription/${this.props.uuid}?` + encodedExtraParams,
).then(res => { ).then(res => {
if (res.status === 200) { if (res.status === 200) {
const pagination = {...this.state.pagination};
console.log(res.data.data.data); console.log(res.data.data.data);
this.setState({ this.setState({
loading: false, loading: false,
data: res.data.data.data, data: res.data.data.data
pagination,
}); });
} }
}).catch((error) => { }).catch((error) => {
handleApiError(error, "Something went wrong when trying to load subscription data."); handleApiError(error, "Something went wrong when trying to load subscription data.", true);
this.setState({loading: false}); if (error.hasOwnProperty("response") && error.response.status === 403) {
this.setState({
isForbidden: true,
loading: false
})
} else {
this.setState({
loading: false
});
}
}); });
}; };
@ -190,11 +220,23 @@ class InstalledDevicesTable extends React.Component {
const {data, pagination, loading, selectedRows} = this.state; const {data, pagination, loading, selectedRows} = this.state;
return ( return (
<div> <div>
{(this.state.isForbidden) && (
<Alert
message="You don't have permission to view subscription details."
type="warning"
banner
closable/>
)}
<div style={{paddingBottom: 24}}> <div style={{paddingBottom: 24}}>
<Text> <Text>
The following are the subscription details of the application in each respective device. The following are the subscription details of the application in each respective device.
</Text> </Text>
</div> </div>
<div style={{textAlign: "right", paddingBottom: 6}}>
<Button icon="sync" onClick={this.fetch}>
Refresh
</Button>
</div>
<Table <Table
columns={columns} columns={columns}
rowKey={record => (record.device.deviceIdentifier + record.device.enrolmentInfo.owner + record.device.enrolmentInfo.ownership)} rowKey={record => (record.device.deviceIdentifier + record.device.enrolmentInfo.owner + record.device.enrolmentInfo.ownership)}
@ -214,4 +256,4 @@ class InstalledDevicesTable extends React.Component {
} }
} }
export default withConfigContext(InstalledDevicesTable); export default withConfigContext(SubscriptionDetails);

@ -18,7 +18,7 @@
import React from "react"; import React from "react";
import axios from "axios"; import axios from "axios";
import {Button, message, DatePicker, Table, Typography} from "antd"; import {Button, message, DatePicker, Table, Typography, Alert} from "antd";
import TimeAgo from 'javascript-time-ago' import TimeAgo from 'javascript-time-ago'
// Load locale-specific relative date/time formatting rules. // Load locale-specific relative date/time formatting rules.
@ -112,7 +112,8 @@ class DeviceInstall extends React.Component {
loading: false, loading: false,
selectedRows: [], selectedRows: [],
scheduledTime: null, scheduledTime: null,
isScheduledInstallVisible: false isScheduledInstallVisible: false,
isForbidden: false
}; };
} }
@ -169,8 +170,17 @@ class DeviceInstall extends React.Component {
} }
}).catch((error) => { }).catch((error) => {
handleApiError(error, "Error occurred while trying to load devices."); handleApiError(error, "Error occurred while trying to load devices.", true);
this.setState({loading: false}); if (error.hasOwnProperty("response") && error.response.status === 403) {
this.setState({
isForbidden: true,
loading: false
})
} else {
this.setState({
loading: false
});
}
}); });
}; };
@ -209,6 +219,13 @@ class DeviceInstall extends React.Component {
Start installing the application for one or more users by entering the corresponding user name. 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. Select install to automatically start downloading the application for the respective user/users.
</Text> </Text>
{(this.state.isForbidden) && (
<Alert
message="You don't have permission to view devices."
type="warning"
banner
closable/>
)}
<Table <Table
style={{paddingTop: 20}} style={{paddingTop: 20}}
columns={columns} columns={columns}

@ -18,7 +18,7 @@
import React from "react"; import React from "react";
import axios from "axios"; import axios from "axios";
import {Button, Select, Table, Typography} from "antd"; import {Alert, Button, Select, Table, Typography} from "antd";
import TimeAgo from 'javascript-time-ago' import TimeAgo from 'javascript-time-ago'
// Load locale-specific relative date/time formatting rules. // Load locale-specific relative date/time formatting rules.
@ -109,7 +109,8 @@ class DeviceUninstall extends React.Component {
data: [], data: [],
pagination: {}, pagination: {},
loading: false, loading: false,
selectedRows: [] selectedRows: [],
isForbidden: false
}; };
} }
@ -164,8 +165,17 @@ class DeviceUninstall extends React.Component {
}); });
} }
}).catch((error) => { }).catch((error) => {
handleApiError(error, "Error occurred while trying to load devices."); handleApiError(error, "Error occurred while trying to load devices.", true);
this.setState({loading: false}); if (error.hasOwnProperty("response") && error.response.status === 403) {
this.setState({
isForbidden: true,
loading: false
})
} else {
this.setState({
loading: false
});
}
}); });
}; };
@ -204,6 +214,13 @@ class DeviceUninstall extends React.Component {
Start uninstalling the application for devices by selecting the corresponding devices. Start uninstalling the application for devices by selecting the corresponding devices.
Select uninstall to automatically start uninstalling the application for the respective devices. Select uninstall to automatically start uninstalling the application for the respective devices.
</Text> </Text>
{(this.state.isForbidden) && (
<Alert
message="You don't have permission to view installed devices."
type="warning"
banner
closable/>
)}
<Table <Table
style={{paddingTop: 20}} style={{paddingTop: 20}}
columns={columns} columns={columns}

@ -17,7 +17,7 @@
*/ */
import React from "react"; import React from "react";
import {Typography, Select, Spin, message, notification, Button} from "antd"; import {Typography, Select, Spin, message, notification, Button, Alert} from "antd";
import debounce from 'lodash.debounce'; import debounce from 'lodash.debounce';
import axios from "axios"; import axios from "axios";
import {withConfigContext} from "../../../../context/ConfigContext"; import {withConfigContext} from "../../../../context/ConfigContext";
@ -40,6 +40,7 @@ class GroupInstall extends React.Component {
data: [], data: [],
value: [], value: [],
fetching: false, fetching: false,
isForbidden: false
}; };
fetchUser = value => { fetchUser = value => {
@ -67,8 +68,17 @@ class GroupInstall extends React.Component {
} }
}).catch((error) => { }).catch((error) => {
handleApiError(error,"Error occurred while trying to load groups."); handleApiError(error,"Error occurred while trying to load groups.", true);
this.setState({fetching: false}); if (error.hasOwnProperty("response") && error.response.status === 403) {
this.setState({
isForbidden: true,
loading: false
})
} else {
this.setState({
loading: false
});
}
}); });
}; };
@ -96,6 +106,13 @@ class GroupInstall extends React.Component {
return ( return (
<div> <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> <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/>
<br/> <br/>
<Select <Select

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

@ -17,7 +17,7 @@
*/ */
import React from "react"; import React from "react";
import {Typography, Select, Spin, message, notification, Button} from "antd"; import {Typography, Select, Spin, message, notification, Button, Alert} from "antd";
import debounce from 'lodash.debounce'; import debounce from 'lodash.debounce';
import axios from "axios"; import axios from "axios";
import {withConfigContext} from "../../../../context/ConfigContext"; import {withConfigContext} from "../../../../context/ConfigContext";
@ -40,6 +40,7 @@ class RoleInstall extends React.Component {
data: [], data: [],
value: [], value: [],
fetching: false, fetching: false,
isForbidden: false
}; };
fetchUser = value => { fetchUser = value => {
@ -67,8 +68,17 @@ class RoleInstall extends React.Component {
} }
}).catch((error) => { }).catch((error) => {
handleApiError(error,"Error occurred while trying to load roles."); handleApiError(error,"Error occurred while trying to load roles.", true);
this.setState({fetching: false}); if (error.hasOwnProperty("response") && error.response.status === 403) {
this.setState({
isForbidden: true,
loading: false
})
} else {
this.setState({
loading: false
});
}
}); });
}; };
@ -96,6 +106,13 @@ class RoleInstall extends React.Component {
return ( return (
<div> <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> <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/>
<br/> <br/>
<Select <Select
@ -107,8 +124,7 @@ class RoleInstall extends React.Component {
filterOption={false} filterOption={false}
onSearch={this.fetchUser} onSearch={this.fetchUser}
onChange={this.handleChange} onChange={this.handleChange}
style={{width: '100%'}} style={{width: '100%'}}>
>
{data.map(d => ( {data.map(d => (
<Option key={d.value}>{d.text}</Option> <Option key={d.value}>{d.text}</Option>
))} ))}

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

@ -17,7 +17,7 @@
*/ */
import React from "react"; import React from "react";
import {Typography, Select, Spin, message, notification, Button} from "antd"; import {Typography, Select, Spin, message, notification, Button, Alert} from "antd";
import debounce from 'lodash.debounce'; import debounce from 'lodash.debounce';
import axios from "axios"; import axios from "axios";
import {withConfigContext} from "../../../../context/ConfigContext"; import {withConfigContext} from "../../../../context/ConfigContext";
@ -69,8 +69,17 @@ class UserInstall extends React.Component {
} }
}).catch((error) => { }).catch((error) => {
handleApiError(error,"Error occurred while trying to load users."); handleApiError(error,"Error occurred while trying to load users.", true);
this.setState({fetching: false}); if (error.hasOwnProperty("response") && error.response.status === 403) {
this.setState({
isForbidden: true,
loading: false
})
} else {
this.setState({
loading: false
});
}
}); });
}; };
@ -79,6 +88,7 @@ class UserInstall extends React.Component {
value, value,
data: [], data: [],
fetching: false, fetching: false,
isForbidden: false
}); });
}; };
@ -97,6 +107,13 @@ class UserInstall extends React.Component {
return ( return (
<div> <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> <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> <p>Select users</p>
<Select <Select
mode="multiple" mode="multiple"

@ -17,7 +17,7 @@
*/ */
import React from "react"; import React from "react";
import {Typography, Select, Spin, message, notification, Button} from "antd"; import {Typography, Select, Spin, message, notification, Button, Alert} from "antd";
import debounce from 'lodash.debounce'; import debounce from 'lodash.debounce';
import axios from "axios"; import axios from "axios";
import {withConfigContext} from "../../../../context/ConfigContext"; import {withConfigContext} from "../../../../context/ConfigContext";
@ -39,6 +39,7 @@ class UserUninstall extends React.Component {
data: [], data: [],
value: [], value: [],
fetching: false, fetching: false,
isForbidden: false
}; };
fetchUser = (value) => { fetchUser = (value) => {
@ -67,8 +68,17 @@ class UserUninstall extends React.Component {
} }
}).catch((error) => { }).catch((error) => {
handleApiError(error, "Error occurred while trying to load users."); handleApiError(error, "Error occurred while trying to load users.", true);
this.setState({fetching: false}); if (error.hasOwnProperty("response") && error.response.status === 403) {
this.setState({
isForbidden: true,
loading: false
})
} else {
this.setState({
loading: false
});
}
}); });
}; };
@ -97,6 +107,13 @@ class UserUninstall extends React.Component {
<Text>Start uninstalling the application for one or more users by entering the corresponding user name. <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 Select uninstall to automatically start uninstalling the application for the respective
user/users. </Text> 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> <p>Select users</p>
<Select <Select
mode="multiple" mode="multiple"

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

@ -32,7 +32,12 @@ class ReviewContainer extends React.Component {
super(props); super(props);
this.state = { this.state = {
currentUserReviews: [], currentUserReviews: [],
detailedRating: null detailedRating: null,
forbiddenErrors: {
currentReview: false,
reviews: false,
rating: false
}
} }
} }
@ -54,7 +59,19 @@ class ReviewContainer extends React.Component {
} }
}).catch((error) => { }).catch((error) => {
handleApiError(error, "Error occurred while trying to get your review."); 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
});
}
}); });
}; };
@ -79,7 +96,7 @@ class ReviewContainer extends React.Component {
} }
}).catch(function (error) { }).catch(function (error) {
handleApiError(error, "Error occurred while trying to load ratings."); handleApiError(error, "Error occurred while trying to load ratings.", true);
}); });
}; };
@ -90,10 +107,11 @@ class ReviewContainer extends React.Component {
render() { render() {
const {uuid} = this.props; const {uuid} = this.props;
const {currentUserReviews,detailedRating} = this.state; const {currentUserReviews,detailedRating, forbiddenErrors} = this.state;
return ( return (
<div> <div>
<CurrentUsersReview <CurrentUsersReview
forbidden={forbiddenErrors.currentReview}
uuid={uuid} uuid={uuid}
currentUserReviews={currentUserReviews} currentUserReviews={currentUserReviews}
onUpdateReview={this.onUpdateReview} onUpdateReview={this.onUpdateReview}

@ -17,7 +17,7 @@
*/ */
import React from "react"; import React from "react";
import {List, message, Avatar, Spin, Button, notification} from 'antd'; import {List, message, Avatar, Spin, Button, notification, Alert} from 'antd';
import "./Reviews.css"; import "./Reviews.css";
import InfiniteScroll from 'react-infinite-scroller'; import InfiniteScroll from 'react-infinite-scroller';
@ -33,7 +33,10 @@ class Reviews extends React.Component {
data: [], data: [],
loading: false, loading: false,
hasMore: false, hasMore: false,
loadMore: false loadMore: false,
forbiddenErrors: {
reviews: false
}
}; };
@ -51,7 +54,7 @@ class Reviews extends React.Component {
const config = this.props.context; const config = this.props.context;
axios.get( axios.get(
window.location.origin+ config.serverConfig.invoker.uri +config.serverConfig.invoker.store+"/reviews/"+type+"/"+uuid, window.location.origin + config.serverConfig.invoker.uri + config.serverConfig.invoker.store + "/reviews/" + type + "/" + uuid,
{ {
headers: {'X-Platform': config.serverConfig.platform} headers: {'X-Platform': config.serverConfig.platform}
}).then(res => { }).then(res => {
@ -60,8 +63,20 @@ class Reviews extends React.Component {
callback(reviews); callback(reviews);
} }
}).catch(function (error) { }).catch((error) => {
handleApiError(error,"Error occurred while trying to load reviews."); 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
});
}
}); });
}; };
@ -101,7 +116,7 @@ class Reviews extends React.Component {
}); });
}; };
deleteCallback = () =>{ deleteCallback = () => {
this.fetchData(0, limit, res => { this.fetchData(0, limit, res => {
this.setState({ this.setState({
data: res, data: res,
@ -114,30 +129,40 @@ class Reviews extends React.Component {
const {loading, hasMore, data, loadMore} = this.state; const {loading, hasMore, data, loadMore} = this.state;
const {uuid} = this.props; const {uuid} = this.props;
return ( return (
<div className="infinite-container"> <div>
<InfiniteScroll {(this.state.forbiddenErrors.reviews) && (
initialLoad={false} <Alert
pageStart={0} message="You don't have permission to view reviews."
loadMore={this.handleInfiniteOnLoad} type="warning"
hasMore={!loading && hasMore} banner
useWindow={true}> closable/>
<List )}
dataSource={data} <div className="infinite-container">
renderItem={item => ( <InfiniteScroll
<List.Item key={item.id}> initialLoad={false}
<SingleReview uuid={uuid} review={item} isDeletable={true} isEditable={false} deleteCallback={this.deleteCallback}/> pageStart={0}
</List.Item> loadMore={this.handleInfiniteOnLoad}
)}> hasMore={!loading && hasMore}
{loading && hasMore && ( useWindow={true}>
<div className="loading-container"> <List
<Spin/> dataSource={data}
</div> renderItem={item => (
)} <List.Item key={item.id}>
</List> <SingleReview uuid={uuid} review={item} isDeletable={true} isEditable={false}
</InfiniteScroll> deleteCallback={this.deleteCallback}/>
{!loadMore && (data.length >= limit) && (<div style={{textAlign: "center"}}> </List.Item>
<Button type="dashed" htmlType="button" onClick={this.enableLoading}>Read All Reviews</Button> )}>
</div>)} {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> </div>
); );
} }

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

@ -17,7 +17,8 @@
*/ */
import React from "react"; import React from "react";
import {Layout, Menu, Icon, Drawer, Button} from 'antd'; import {Layout, Menu, Icon, Drawer, Button, Alert} from 'antd';
const {Header, Content, Footer} = Layout; const {Header, Content, Footer} = Layout;
import {Link} from "react-router-dom"; import {Link} from "react-router-dom";
import RouteWithSubRoutes from "../../components/RouteWithSubRoutes"; import RouteWithSubRoutes from "../../components/RouteWithSubRoutes";
@ -38,7 +39,10 @@ class Dashboard extends React.Component {
selectedKeys: [], selectedKeys: [],
deviceTypes: [], deviceTypes: [],
visible: false, visible: false,
collapsed: false collapsed: false,
forbiddenErrors: {
deviceTypes: false
}
}; };
this.logo = this.props.context.theme.logo; this.logo = this.props.context.theme.logo;
this.config = this.props.context; this.config = this.props.context;
@ -62,10 +66,19 @@ class Dashboard extends React.Component {
} }
}).catch((error) => { }).catch((error) => {
handleApiError(error,"Error occurred while trying to load device types."); handleApiError(error, "Error occurred while trying to load device types.", true);
this.setState({ if (error.hasOwnProperty("response") && error.response.status === 403) {
loading: false const {forbiddenErrors} = this.state;
}); forbiddenErrors.deviceTypes = true;
this.setState({
forbiddenErrors,
loading: false
})
} else {
this.setState({
loading: false
});
}
}); });
}; };
@ -90,7 +103,7 @@ class Dashboard extends React.Component {
render() { render() {
const config = this.props.context; const config = this.props.context;
const {selectedKeys, deviceTypes} = this.state; const {selectedKeys, deviceTypes, forbiddenErrors} = this.state;
const DeviceTypesData = deviceTypes.map((deviceType) => { const DeviceTypesData = deviceTypes.map((deviceType) => {
const platform = deviceType.name; const platform = deviceType.name;
@ -116,7 +129,7 @@ class Dashboard extends React.Component {
<Layout> <Layout>
<Header style={{paddingLeft: 0, paddingRight: 0, backgroundColor: "white"}}> <Header style={{paddingLeft: 0, paddingRight: 0, backgroundColor: "white"}}>
<div className="logo-image"> <div className="logo-image">
<Link to="/store/android"><img alt="logo" src={this.logo}/></Link> <Link to="/store"><img alt="logo" src={this.logo}/></Link>
</div> </div>
<div className="web-layout"> <div className="web-layout">
@ -131,7 +144,7 @@ class Dashboard extends React.Component {
<Menu.Item key="web-clip"> <Menu.Item key="web-clip">
<Link to="/store/web-clip"> <Link to="/store/web-clip">
<Icon type="upload"/> <Icon type="upload"/>
Web Clips Web Clips
</Link> </Link>
</Menu.Item> </Menu.Item>
@ -140,7 +153,7 @@ class Dashboard extends React.Component {
<span className="submenu-title-wrapper"> <span className="submenu-title-wrapper">
<Icon type="user"/> <Icon type="user"/>
{this.config.user} {this.config.user}
</span> }> </span>}>
<Logout/> <Logout/>
</SubMenu> </SubMenu>
</Menu> </Menu>
@ -156,32 +169,32 @@ class Dashboard extends React.Component {
</Button> </Button>
</div> </div>
</Layout> </Layout>
<Drawer <Drawer
title={<Link to="/store/android" onClick={this.onCloseMobileNavigationBar}> title={<Link to="/store" onClick={this.onCloseMobileNavigationBar}>
<img alt="logo" src={this.logo} style={{marginLeft: 30}} width={"60%"}/> <img alt="logo" src={this.logo} style={{marginLeft: 30}} width={"60%"}/>
</Link>} </Link>}
placement="left" placement="left"
closable={false} closable={false}
onClose={this.onCloseMobileNavigationBar} onClose={this.onCloseMobileNavigationBar}
visible={this.state.visible} visible={this.state.visible}
getContainer={false} getContainer={false}
style={{position: 'absolute'}}> style={{position: 'absolute'}}>
<Menu <Menu
theme="light" theme="light"
mode="inline" mode="inline"
defaultSelectedKeys={selectedKeys} defaultSelectedKeys={selectedKeys}
style={{lineHeight: '64px', width: 231}} style={{lineHeight: '64px', width: 231}}
onClick={this.onCloseMobileNavigationBar}> onClick={this.onCloseMobileNavigationBar}>
{DeviceTypesData} {DeviceTypesData}
<Menu.Item key="web-clip"> <Menu.Item key="web-clip">
<Link to="/store/web-clip"> <Link to="/store/web-clip">
<Icon type="upload"/>Web Clips <Icon type="upload"/>Web Clips
</Link> </Link>
</Menu.Item> </Menu.Item>
</Menu> </Menu>
</Drawer> </Drawer>
<Layout className="mobile-layout"> <Layout className="mobile-layout">
<Menu <Menu
mode="horizontal" mode="horizontal"
@ -198,6 +211,13 @@ class Dashboard extends React.Component {
</Layout> </Layout>
<Layout className="dashboard-body"> <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'}}> <Content style={{padding: '0 0'}}>
<Switch> <Switch>
{this.state.routes.map((route) => ( {this.state.routes.map((route) => (

@ -36,7 +36,10 @@ class Release extends React.Component {
this.state = { this.state = {
loading: true, loading: true,
app: null, app: null,
uuid: null uuid: null,
forbiddenErrors: {
app: false
}
}; };
} }
@ -72,8 +75,19 @@ class Release extends React.Component {
} }
}).catch((error) => { }).catch((error) => {
handleApiError(error,"Error occurred while trying to load releases."); handleApiError(error,"Error occurred while trying to load releases.", false);
this.setState({loading: 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
});
}
}); });
}; };

Loading…
Cancel
Save