From 6c784914824e859e4cdf5c06c1ae89e79363b251 Mon Sep 17 00:00:00 2001 From: Jayasanka <jayasanka.sack@gmail.com> Date: Mon, 21 Oct 2019 17:00:46 +0530 Subject: [PATCH 01/10] Add loading animation to filter & lifecycle components in APPM --- .../components/apps/list-apps/appsTable/AppsTable.js | 8 +++++--- .../components/apps/release/lifeCycle/LifeCycle.js | 12 ++++-------- 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/src/components/apps/list-apps/appsTable/AppsTable.js b/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/src/components/apps/list-apps/appsTable/AppsTable.js index aae593fb2a..24e43a89c6 100644 --- a/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/src/components/apps/list-apps/appsTable/AppsTable.js +++ b/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/src/components/apps/list-apps/appsTable/AppsTable.js @@ -123,7 +123,8 @@ class AppsTable extends React.Component { filters: {}, isDrawerVisible: false, selectedApp: null, - selectedAppIndex: -1 + selectedAppIndex: -1, + loading: false }; config = this.props.context; } @@ -222,14 +223,14 @@ class AppsTable extends React.Component { onUpdateApp = (key, value) => { const apps = [...this.state.apps]; - apps[this.state.selectedAppIndex][key]= value; + apps[this.state.selectedAppIndex][key] = value; this.setState({ apps }); }; render() { - const {isDrawerVisible} = this.state; + const {isDrawerVisible, loading} = this.state; return ( <div className="apps-table"> <Table @@ -239,6 +240,7 @@ class AppsTable extends React.Component { pagination={this.state.pagination} onChange={this.handleTableChange} rowClassName="app-row" + loading={loading} onRow={(record, rowIndex) => { return { onClick: event => { diff --git a/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/src/components/apps/release/lifeCycle/LifeCycle.js b/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/src/components/apps/release/lifeCycle/LifeCycle.js index 3230939c72..34384f7f24 100644 --- a/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/src/components/apps/release/lifeCycle/LifeCycle.js +++ b/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/src/components/apps/release/lifeCycle/LifeCycle.js @@ -132,7 +132,7 @@ class LifeCycle extends React.Component { render() { - const {currentStatus, selectedStatus} = this.state; + const {currentStatus, selectedStatus, isConfirmButtonLoading} = this.state; const {lifecycle} = this.props; const selectedValue = selectedStatus == null ? [] : selectedStatus; let proceedingStates = []; @@ -180,21 +180,17 @@ class LifeCycle extends React.Component { type="primary" htmlType="button" onClick={this.showReasonModal} - disabled={selectedStatus == null} - > + loading={isConfirmButtonLoading} + disabled={selectedStatus == null}> Change </Button> - - <Divider/> - <Modal title="Confirm changing lifecycle state" visible={this.state.isReasonModalVisible} onOk={this.addLifeCycle} onCancel={this.closeReasonModal} - okText="Confirm" - > + okText="Confirm"> <Text> You are going to change the lifecycle state from,<br/> <Tag color="blue">{currentStatus}</Tag>to <Tag From 6a5e21dd9be5aab1a342fd09acb725d8f1de9590 Mon Sep 17 00:00:00 2001 From: Jayasanka <jayasanka.sack@gmail.com> Date: Mon, 21 Oct 2019 21:50:12 +0530 Subject: [PATCH 02/10] Create UI to display installed devices --- .../apps/release/InstalledDevicesTable.js | 199 ++++++++++++++++++ .../components/apps/release/ReleaseView.js | 67 +++--- 2 files changed, 240 insertions(+), 26 deletions(-) create mode 100644 components/application-mgt/org.wso2.carbon.device.application.mgt.store.ui/react-app/src/components/apps/release/InstalledDevicesTable.js diff --git a/components/application-mgt/org.wso2.carbon.device.application.mgt.store.ui/react-app/src/components/apps/release/InstalledDevicesTable.js b/components/application-mgt/org.wso2.carbon.device.application.mgt.store.ui/react-app/src/components/apps/release/InstalledDevicesTable.js new file mode 100644 index 0000000000..6284808ef8 --- /dev/null +++ b/components/application-mgt/org.wso2.carbon.device.application.mgt.store.ui/react-app/src/components/apps/release/InstalledDevicesTable.js @@ -0,0 +1,199 @@ +/* + * Copyright (c) 2019, Entgra (pvt) Ltd. (http://entgra.io) All Rights Reserved. + * + * Entgra (pvt) Ltd. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import React from "react"; +import axios from "axios"; +import {Tag, message, notification, Table, Typography, Tooltip, Icon, Divider, Button, Modal, Select} from "antd"; +import TimeAgo from 'javascript-time-ago' + +// Load locale-specific relative date/time formatting rules. +import en from 'javascript-time-ago/locale/en' +import {withConfigContext} from "../../../context/ConfigContext"; + +const {Text} = Typography; + +let config = null; + +const columns = [ + { + title: 'Device', + dataIndex: 'name', + width: 100, + }, + { + title: 'Owner', + dataIndex: 'enrolmentInfo', + key: 'owner', + render: enrolmentInfo => enrolmentInfo.owner + // todo add filtering options + }, + { + title: 'Ownership', + dataIndex: 'enrolmentInfo', + key: 'ownership', + render: enrolmentInfo => enrolmentInfo.ownership + // todo add filtering options + }, + { + title: 'Status', + dataIndex: 'enrolmentInfo', + key: 'status', + render: (enrolmentInfo) => { + const status = enrolmentInfo.status.toLowerCase(); + let color = "#f9ca24"; + switch (status) { + case "active": + color = "#badc58"; + break; + case "created": + color = "#6ab04c"; + break; + case "removed": + color = "#ff7979"; + break; + case "inactive": + color = "#f9ca24"; + break; + case "blocked": + color = "#636e72"; + break; + } + return <Tag color={color}>{status}</Tag>; + } + // todo add filtering options + }, + { + title: 'Last Updated', + dataIndex: 'enrolmentInfo', + key: 'dateOfLastUpdate', + render: (data) => { + const {dateOfLastUpdate} = data; + const timeAgoString = getTimeAgo(dateOfLastUpdate); + return <Tooltip title={new Date(dateOfLastUpdate).toString()}>{timeAgoString}</Tooltip>; + } + // todo add filtering options + } +]; + +const getTimeAgo = (time) => { + const timeAgo = new TimeAgo('en-US'); + return timeAgo.format(time); +}; + + +class InstalledDevicesTable extends React.Component { + constructor(props) { + super(props); + config = this.props.context; + TimeAgo.addLocale(en); + this.state = { + data: [], + pagination: {}, + loading: false, + selectedRows: [], + deviceGroups: [], + groupModalVisible: false, + selectedGroupId: [] + }; + } + + componentDidMount() { + this.fetch(); + } + + //fetch data from api + fetch = (params = {}) => { + const config = this.props.context; + this.setState({loading: true}); + // get current page + const currentPage = (params.hasOwnProperty("page")) ? params.page : 1; + + const extraParams = { + offset: 10 * (currentPage - 1), //calculate the offset + limit: 10, + requireDeviceInfo: true, + }; + + const encodedExtraParams = Object.keys(extraParams) + .map(key => key + '=' + extraParams[key]).join('&'); + + //send request to the invoker + axios.get( + window.location.origin + config.serverConfig.invoker.uri + + config.serverConfig.invoker.deviceMgt + + "/devices?" + encodedExtraParams, + ).then(res => { + if (res.status === 200) { + const pagination = {...this.state.pagination}; + this.setState({ + loading: false, + data: res.data.data.devices, + pagination, + }); + } + + }).catch((error) => { + if (error.hasOwnProperty("response") && error.response.status === 401) { + //todo display a popop with error + message.error('You are not logged in'); + window.location.href = window.location.origin + '/entgra/login'; + } else { + notification["error"]({ + message: "There was a problem", + duration: 0, + description: + "Error occurred while trying to load devices.", + }); + } + + this.setState({loading: false}); + }); + }; + + render() { + const {data, pagination, loading, selectedRows} = this.state; + return ( + <div> + <div style={{paddingBottom:24}}> + <Text> + Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque + laudantium, + totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae + dicta sunt explicabo. + </Text> + </div> + <Table + columns={columns} + rowKey={record => (record.deviceIdentifier + record.enrolmentInfo.owner + record.enrolmentInfo.ownership)} + dataSource={data} + pagination={{ + ...pagination, + size: "small", + // position: "top", + showTotal: (total, range) => `showing ${range[0]}-${range[1]} of ${total} devices` + // showQuickJumper: true + }} + loading={loading} + scroll={{x: 1000}} + /> + </div> + ); + } +} + +export default withConfigContext(InstalledDevicesTable); \ No newline at end of file diff --git a/components/application-mgt/org.wso2.carbon.device.application.mgt.store.ui/react-app/src/components/apps/release/ReleaseView.js b/components/application-mgt/org.wso2.carbon.device.application.mgt.store.ui/react-app/src/components/apps/release/ReleaseView.js index 6994f16e47..a35ccb5e51 100644 --- a/components/application-mgt/org.wso2.carbon.device.application.mgt.store.ui/react-app/src/components/apps/release/ReleaseView.js +++ b/components/application-mgt/org.wso2.carbon.device.application.mgt.store.ui/react-app/src/components/apps/release/ReleaseView.js @@ -17,7 +17,7 @@ */ import React from "react"; -import {Divider, Row, Col, Typography, Button, Dropdown, notification, Menu, Icon, Spin} from "antd"; +import {Divider, Row, Col, Typography, Button, Dropdown, notification, Menu, Icon, Spin, Tabs} from "antd"; import "../../../App.css"; import ImgViewer from "../../apps/release/images/ImgViewer"; import StarRatings from "react-star-ratings"; @@ -30,8 +30,10 @@ import CurrentUsersReview from "./review/CurrentUsersReview"; import {withConfigContext} from "../../../context/ConfigContext"; import {handleApiError} from "../../../js/Utils"; import ReviewContainer from "./review/ReviewContainer"; +import InstalledDevicesTable from "./InstalledDevicesTable"; const {Title, Text, Paragraph} = Typography; +const {TabPane} = Tabs; class ReleaseView extends React.Component { constructor(props) { @@ -118,6 +120,12 @@ class ReleaseView extends React.Component { metaData = JSON.parse(release.metaData); } catch (e) { + } + if (app.hasOwnProperty("packageName")) { + metaData.push({ + key: "Package Name", + value: app.packageName + }); } const menu = ( <Menu onClick={this.handleSubscribeClick}> @@ -171,31 +179,38 @@ class ReleaseView extends React.Component { </div> </Col> </Row> - <Divider/> - <Row> - <ImgViewer images={release.screenshots}/> - </Row> - <Divider/> - <Paragraph type="secondary" ellipsis={{rows: 3, expandable: true}}> - {release.description} - </Paragraph> - <Divider/> - <Text>META DATA</Text> - <Row> - { - metaData.map((data, index) => { - return ( - <Col key={index} lg={8} md={6} xs={24} style={{marginTop: 15}}> - <Text>{data.key}</Text><br/> - <Text type="secondary">{data.value}</Text> - </Col> - ) - }) - } - {(metaData.length === 0) && (<Text type="secondary">No meta data available.</Text>)} - </Row> - <Divider/> - <ReviewContainer uuid={release.uuid}/> + <Divider dashed={true}/> + <Tabs> + <TabPane tab="App" key="1"> + <Row> + <ImgViewer images={release.screenshots}/> + </Row> + <Divider/> + <Paragraph type="secondary" ellipsis={{rows: 3, expandable: true}}> + {release.description} + </Paragraph> + <Divider/> + <Text>META DATA</Text> + <Row> + { + metaData.map((data, index) => { + return ( + <Col key={index} lg={8} md={6} xs={24} style={{marginTop: 15}}> + <Text>{data.key}</Text><br/> + <Text type="secondary">{data.value}</Text> + </Col> + ) + }) + } + {(metaData.length === 0) && (<Text type="secondary">No meta data available.</Text>)} + </Row> + <Divider/> + <ReviewContainer uuid={release.uuid}/> + </TabPane> + <TabPane tab="Installed devices" key="2"> + <InstalledDevicesTable/> + </TabPane> + </Tabs> </div> </div> ); From 951325ee75ff7d93ede759640b22345bb06d9e76 Mon Sep 17 00:00:00 2001 From: Jayasanka <jayasanka.sack@gmail.com> Date: Tue, 22 Oct 2019 16:08:09 +0530 Subject: [PATCH 03/10] Add ui improvements to APPM Publisher UI The following changes are with this commit - View subscription status of devices in release view - Display server error response in add new app form --- .../src/components/new-app/AddNewAppForm.js | 12 ++- .../apps/release/InstalledDevicesTable.js | 94 +++++++++++++------ .../components/apps/release/ReleaseView.js | 2 +- 3 files changed, 71 insertions(+), 37 deletions(-) diff --git a/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/src/components/new-app/AddNewAppForm.js b/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/src/components/new-app/AddNewAppForm.js index 6e731679e1..340d28aa93 100644 --- a/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/src/components/new-app/AddNewAppForm.js +++ b/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/src/components/new-app/AddNewAppForm.js @@ -54,7 +54,8 @@ class AddNewAppFormComponent extends React.Component { release: null, isError: false, deviceType: null, - supportedOsVersions: [] + supportedOsVersions: [], + errorText: "" }; } @@ -112,11 +113,12 @@ class AddNewAppFormComponent extends React.Component { } }).catch((error) => { - handleApiError(error, "Sorry, we were unable to complete your request.") + handleApiError(error, error.response.data.data); this.setState({ loading: false, isError: true, - current: 2 + current: 2, + errorText: error.response.data.data }); }); @@ -149,7 +151,7 @@ class AddNewAppFormComponent extends React.Component { }; render() { - const {loading, current, isError, supportedOsVersions} = this.state; + const {loading, current, isError, supportedOsVersions, errorText} = this.state; const {formConfig} = this.props; return ( <div> @@ -190,7 +192,7 @@ class AddNewAppFormComponent extends React.Component { {isError && (<Result status="500" - title="Error occurred while creating the application." + title={errorText} subTitle="Go back to edit the details and submit again." extra={<Button onClick={this.onClickBackButton}>Back</Button>} />)} diff --git a/components/application-mgt/org.wso2.carbon.device.application.mgt.store.ui/react-app/src/components/apps/release/InstalledDevicesTable.js b/components/application-mgt/org.wso2.carbon.device.application.mgt.store.ui/react-app/src/components/apps/release/InstalledDevicesTable.js index 6284808ef8..1b6642f3d4 100644 --- a/components/application-mgt/org.wso2.carbon.device.application.mgt.store.ui/react-app/src/components/apps/release/InstalledDevicesTable.js +++ b/components/application-mgt/org.wso2.carbon.device.application.mgt.store.ui/react-app/src/components/apps/release/InstalledDevicesTable.js @@ -32,29 +32,72 @@ let config = null; const columns = [ { title: 'Device', - dataIndex: 'name', + dataIndex: 'device', width: 100, + render: device => device.name }, { title: 'Owner', - dataIndex: 'enrolmentInfo', + dataIndex: 'device', key: 'owner', - render: enrolmentInfo => enrolmentInfo.owner - // todo add filtering options + render: device => device.enrolmentInfo.owner }, { - title: 'Ownership', - dataIndex: 'enrolmentInfo', - key: 'ownership', - render: enrolmentInfo => enrolmentInfo.ownership - // todo add filtering options + title: 'Action Type', + dataIndex: 'actionType', + key: 'actionType', + render: actionType => actionType.toLowerCase() }, { - title: 'Status', - dataIndex: 'enrolmentInfo', - key: 'status', - render: (enrolmentInfo) => { - const status = enrolmentInfo.status.toLowerCase(); + title: 'Action', + dataIndex: 'action', + key: 'action', + render: action => action.toLowerCase() + }, + { + title: 'Triggered By', + dataIndex: 'actionTriggeredBy', + key: 'actionTriggeredBy' + }, + { + title: 'Action Triggered At', + dataIndex: 'actionTriggeredTimestamp', + key: 'actionTriggeredTimestamp' + }, + { + title: 'Action Status', + dataIndex: 'status', + key: 'actionStatus', + render: (status) => { + let color = "#f9ca24"; + switch (status) { + case "COMPLETED": + color = "#badc58"; + break; + case "REPEATED": + color = "#6ab04c"; + break; + case "ERROR": + case "INVALID": + case "UNAUTHORIZED": + color = "#ff7979"; + break; + case "IN_PROGRESS": + color = "#f9ca24"; + break; + case "PENDING": + color = "#636e72"; + break; + } + return <Tag color={color}>{status.toLowerCase()}</Tag>; + } + }, + { + title: 'Device Status', + dataIndex: 'device', + key: 'deviceStatus', + render: (device) => { + const status = device.enrolmentInfo.status.toLowerCase(); let color = "#f9ca24"; switch (status) { case "active": @@ -75,18 +118,6 @@ const columns = [ } return <Tag color={color}>{status}</Tag>; } - // todo add filtering options - }, - { - title: 'Last Updated', - dataIndex: 'enrolmentInfo', - key: 'dateOfLastUpdate', - render: (data) => { - const {dateOfLastUpdate} = data; - const timeAgoString = getTimeAgo(dateOfLastUpdate); - return <Tooltip title={new Date(dateOfLastUpdate).toString()}>{timeAgoString}</Tooltip>; - } - // todo add filtering options } ]; @@ -135,14 +166,15 @@ class InstalledDevicesTable extends React.Component { //send request to the invoker axios.get( window.location.origin + config.serverConfig.invoker.uri + - config.serverConfig.invoker.deviceMgt + - "/devices?" + encodedExtraParams, + config.serverConfig.invoker.store + + `/admin/subscription/${this.props.uuid}?` + encodedExtraParams, ).then(res => { if (res.status === 200) { const pagination = {...this.state.pagination}; + console.log(res.data.data.data); this.setState({ loading: false, - data: res.data.data.devices, + data: res.data.data.data, pagination, }); } @@ -169,7 +201,7 @@ class InstalledDevicesTable extends React.Component { const {data, pagination, loading, selectedRows} = this.state; return ( <div> - <div style={{paddingBottom:24}}> + <div style={{paddingBottom: 24}}> <Text> Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, @@ -179,7 +211,7 @@ class InstalledDevicesTable extends React.Component { </div> <Table columns={columns} - rowKey={record => (record.deviceIdentifier + record.enrolmentInfo.owner + record.enrolmentInfo.ownership)} + rowKey={record => (record.device.deviceIdentifier + record.device.enrolmentInfo.owner + record.device.enrolmentInfo.ownership)} dataSource={data} pagination={{ ...pagination, diff --git a/components/application-mgt/org.wso2.carbon.device.application.mgt.store.ui/react-app/src/components/apps/release/ReleaseView.js b/components/application-mgt/org.wso2.carbon.device.application.mgt.store.ui/react-app/src/components/apps/release/ReleaseView.js index a35ccb5e51..51ab619df2 100644 --- a/components/application-mgt/org.wso2.carbon.device.application.mgt.store.ui/react-app/src/components/apps/release/ReleaseView.js +++ b/components/application-mgt/org.wso2.carbon.device.application.mgt.store.ui/react-app/src/components/apps/release/ReleaseView.js @@ -208,7 +208,7 @@ class ReleaseView extends React.Component { <ReviewContainer uuid={release.uuid}/> </TabPane> <TabPane tab="Installed devices" key="2"> - <InstalledDevicesTable/> + <InstalledDevicesTable uuid={release.uuid}/> </TabPane> </Tabs> </div> From e762d35a74a07a72b09ba8831bd93e5439a290cc Mon Sep 17 00:00:00 2001 From: Jayasanka <jayasanka.sack@gmail.com> Date: Tue, 22 Oct 2019 22:28:44 +0530 Subject: [PATCH 04/10] Remove subscription type from APPM publisher filter UI --- .../src/components/apps/list-apps/Filters.js | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/src/components/apps/list-apps/Filters.js b/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/src/components/apps/list-apps/Filters.js index de8bbf8357..e042def20d 100644 --- a/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/src/components/apps/list-apps/Filters.js +++ b/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/src/components/apps/list-apps/Filters.js @@ -63,9 +63,6 @@ class FiltersForm extends React.Component { delete values["deviceType"]; } - if(values.hasOwnProperty("subscriptionType") && values.subscriptionType==="ALL"){ - delete values["subscriptionType"]; - } if(values.hasOwnProperty("appType") && values.appType==="ALL"){ delete values["appType"]; } @@ -271,17 +268,6 @@ class FiltersForm extends React.Component { )} </Form.Item> <Divider/> - - <Form.Item label="Subscription Type"> - {getFieldDecorator('subscriptionType', {})( - <Radio.Group style={{width: '100%'}}> - <Radio value="FREE">Free</Radio> - <Radio value="PAID">Paid</Radio> - <Radio value="ALL">All</Radio> - </Radio.Group>, - )} - </Form.Item> - <Divider/> </Form> </Card> ); From cd062f0788d37954d5d4bf869d71b0dfb1a219a3 Mon Sep 17 00:00:00 2001 From: Jayasanka <jayasanka.sack@gmail.com> Date: Thu, 24 Oct 2019 16:56:24 +0530 Subject: [PATCH 05/10] Add UI improvements to APPM UI The following changes are with this commit - Fix wrong 401 redirection in Subscription details table - Add pagination to releases list in App Details Drawer - Show published badge on app icon in apps table and releases list --- .../AppDetailsDrawer/AppDetailsDrawer.js | 55 +++++++++++++++---- .../apps/list-apps/appsTable/AppsTable.js | 32 +++++++++-- .../apps/release/InstalledDevicesTable.js | 20 +------ .../components/apps/release/ReleaseView.js | 5 +- 4 files changed, 76 insertions(+), 36 deletions(-) diff --git a/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/src/components/apps/list-apps/AppDetailsDrawer/AppDetailsDrawer.js b/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/src/components/apps/list-apps/AppDetailsDrawer/AppDetailsDrawer.js index 335e1c46dd..fc54ab399b 100644 --- a/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/src/components/apps/list-apps/AppDetailsDrawer/AppDetailsDrawer.js +++ b/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/src/components/apps/list-apps/AppDetailsDrawer/AppDetailsDrawer.js @@ -30,7 +30,7 @@ import { Spin, message, Icon, - Card + Card, Badge } from 'antd'; import DetailedRating from "../../detailed-rating/DetailedRating"; import {Link} from "react-router-dom"; @@ -487,18 +487,14 @@ class AppDetailsDrawer extends React.Component { )} <Text strong={true}>Releases </Text> - {/*display add new release only if app type is enterprise*/} - <div className="releases-details"> - - {(app.type === "ENTERPRISE") && ( - <Link to={`/publisher/apps/${app.deviceType}/${app.id}/add-release`}><Button - htmlType="button" - size="small">Add - new release</Button></Link>)} <List style={{paddingTop: 16}} grid={{gutter: 16, column: 2}} + pagination={{ + pageSize: 4, // number of releases per page + size: "small", + }} dataSource={app.applicationReleases} renderItem={release => ( <div className="app-release-cards"> @@ -507,7 +503,27 @@ class AppDetailsDrawer extends React.Component { <Card className="release-card"> <Meta avatar={ - <Avatar size="large" shape="square" src={release.iconPath}/> + <div> + {(release.currentStatus === "PUBLISHED") ? ( + <Badge + title="Published" + style={{ + backgroundColor: '#52c41a', + borderRadius:"50%", + color:"white" + }} + count={ + <Icon + type="check-circle"/> + }> + <Avatar size="large" shape="square" + src={release.iconPath}/> + </Badge> + ) : ( + <Avatar size="large" shape="square" + src={release.iconPath}/> + )} + </div> } title={release.version} description={ @@ -529,10 +545,27 @@ class AppDetailsDrawer extends React.Component { </div> )} /> - </div> <Divider dashed={true}/> + {/*display add new release only if app type is enterprise*/} + {(app.type === "ENTERPRISE") && ( + <div> + <div style={{paddingBottom: 16}}> + <Text> + Add new release for the application + </Text> + </div> + <Link to={`/publisher/apps/${app.deviceType}/${app.id}/add-release`}> + <Button + htmlType="button" + type="primary" + size="small"> + Add + </Button> + </Link> + </div>)} + <Divider dashed={true}/> <Text strong={true}>Description </Text> {!isDescriptionEditEnabled && ( diff --git a/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/src/components/apps/list-apps/appsTable/AppsTable.js b/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/src/components/apps/list-apps/appsTable/AppsTable.js index 24e43a89c6..d008cf5609 100644 --- a/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/src/components/apps/list-apps/appsTable/AppsTable.js +++ b/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/src/components/apps/list-apps/appsTable/AppsTable.js @@ -17,7 +17,7 @@ */ import React from "react"; -import {Avatar, Table, Tag, Icon, message, notification, Col} from "antd"; +import {Avatar, Table, Tag, Icon, message, notification, Col, Badge} from "antd"; import axios from "axios"; import pSBC from 'shade-blend-color'; import "./AppsTable.css"; @@ -47,7 +47,31 @@ const columns = [ </Avatar> ); } else { - avatar = ( + const {applicationReleases} = row; + let hasPublishedRelease = false; + for (let i = 0; i < applicationReleases.length; i++) { + if (applicationReleases[i].currentStatus === "PUBLISHED") { + hasPublishedRelease = true; + break; + } + } + avatar = (hasPublishedRelease) ? ( + <Badge + title="Published" + style={{ backgroundColor: '#52c41a', borderRadius:"50%", color:"white"}} + count={ + <Icon + type="check-circle"/> + }> + <Avatar shape="square" size="large" + style={{ + borderRadius: "28%", + border: "1px solid #ddd" + }} + src={row.applicationReleases[0].iconPath} + /> + </Badge> + ) : ( <Avatar shape="square" size="large" style={{ marginRight: 20, @@ -56,13 +80,13 @@ const columns = [ }} src={row.applicationReleases[0].iconPath} /> - ) + ); } return ( <div> {avatar} - {name} + <span style={{marginLeft: 20}}>{name}</span> </div>); } }, diff --git a/components/application-mgt/org.wso2.carbon.device.application.mgt.store.ui/react-app/src/components/apps/release/InstalledDevicesTable.js b/components/application-mgt/org.wso2.carbon.device.application.mgt.store.ui/react-app/src/components/apps/release/InstalledDevicesTable.js index 1b6642f3d4..39fd3ef27c 100644 --- a/components/application-mgt/org.wso2.carbon.device.application.mgt.store.ui/react-app/src/components/apps/release/InstalledDevicesTable.js +++ b/components/application-mgt/org.wso2.carbon.device.application.mgt.store.ui/react-app/src/components/apps/release/InstalledDevicesTable.js @@ -24,6 +24,7 @@ import TimeAgo from 'javascript-time-ago' // Load locale-specific relative date/time formatting rules. import en from 'javascript-time-ago/locale/en' import {withConfigContext} from "../../../context/ConfigContext"; +import {handleApiError} from "../../../js/Utils"; const {Text} = Typography; @@ -180,19 +181,7 @@ class InstalledDevicesTable extends React.Component { } }).catch((error) => { - if (error.hasOwnProperty("response") && error.response.status === 401) { - //todo display a popop with error - message.error('You are not logged in'); - window.location.href = window.location.origin + '/entgra/login'; - } else { - notification["error"]({ - message: "There was a problem", - duration: 0, - description: - "Error occurred while trying to load devices.", - }); - } - + handleApiError(error, "Something went wrong when trying to load subscription data."); this.setState({loading: false}); }); }; @@ -203,10 +192,7 @@ class InstalledDevicesTable extends React.Component { <div> <div style={{paddingBottom: 24}}> <Text> - Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque - laudantium, - totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae - dicta sunt explicabo. + The following are the subscription details of the application in each respective device. </Text> </div> <Table diff --git a/components/application-mgt/org.wso2.carbon.device.application.mgt.store.ui/react-app/src/components/apps/release/ReleaseView.js b/components/application-mgt/org.wso2.carbon.device.application.mgt.store.ui/react-app/src/components/apps/release/ReleaseView.js index 51ab619df2..83fce13416 100644 --- a/components/application-mgt/org.wso2.carbon.device.application.mgt.store.ui/react-app/src/components/apps/release/ReleaseView.js +++ b/components/application-mgt/org.wso2.carbon.device.application.mgt.store.ui/react-app/src/components/apps/release/ReleaseView.js @@ -21,12 +21,9 @@ import {Divider, Row, Col, Typography, Button, Dropdown, notification, Menu, Ico import "../../../App.css"; import ImgViewer from "../../apps/release/images/ImgViewer"; import StarRatings from "react-star-ratings"; -import DetailedRating from "./DetailedRating"; -import Reviews from "./review/Reviews"; import axios from "axios"; import AppInstallModal from "./install/AppInstallModal"; import AppUninstallModal from "./install/AppUninstallModal"; -import CurrentUsersReview from "./review/CurrentUsersReview"; import {withConfigContext} from "../../../context/ConfigContext"; import {handleApiError} from "../../../js/Utils"; import ReviewContainer from "./review/ReviewContainer"; @@ -207,7 +204,7 @@ class ReleaseView extends React.Component { <Divider/> <ReviewContainer uuid={release.uuid}/> </TabPane> - <TabPane tab="Installed devices" key="2"> + <TabPane tab="Subscription Details" key="2"> <InstalledDevicesTable uuid={release.uuid}/> </TabPane> </Tabs> From 9dd7444cd7b78c0735266a174a631280b0089601 Mon Sep 17 00:00:00 2001 From: Jayasanka <jayasanka.sack@gmail.com> Date: Sat, 26 Oct 2019 11:33:03 +0530 Subject: [PATCH 06/10] Add unrestricted roles field to Add new app form --- .../react-app/package.json | 3 +- .../AppDetailsDrawer/AppDetailsDrawer.js | 6 +- .../apps/list-apps/appsTable/AppsTable.js | 1 - .../new-app/subForms/NewAppDetailsForm.js | 106 ++++++++++++++---- .../apps/release/install/RoleInstall.js | 3 +- 5 files changed, 93 insertions(+), 26 deletions(-) diff --git a/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/package.json b/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/package.json index dece5f9363..dbba25e052 100644 --- a/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/package.json +++ b/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/package.json @@ -39,7 +39,8 @@ "redux-thunk": "^2.3.0", "shade-blend-color": "^1.0.0", "storm-react-diagrams": "^5.2.1", - "typescript": "^3.6.4" + "typescript": "^3.6.4", + "lodash.debounce": "latest" }, "devDependencies": { "@babel/core": "^7.5.0", diff --git a/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/src/components/apps/list-apps/AppDetailsDrawer/AppDetailsDrawer.js b/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/src/components/apps/list-apps/AppDetailsDrawer/AppDetailsDrawer.js index fc54ab399b..6e5e6b4816 100644 --- a/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/src/components/apps/list-apps/AppDetailsDrawer/AppDetailsDrawer.js +++ b/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/src/components/apps/list-apps/AppDetailsDrawer/AppDetailsDrawer.js @@ -509,8 +509,8 @@ class AppDetailsDrawer extends React.Component { title="Published" style={{ backgroundColor: '#52c41a', - borderRadius:"50%", - color:"white" + borderRadius: "50%", + color: "white" }} count={ <Icon @@ -547,10 +547,10 @@ class AppDetailsDrawer extends React.Component { /> </div> - <Divider dashed={true}/> {/*display add new release only if app type is enterprise*/} {(app.type === "ENTERPRISE") && ( <div> + <Divider dashed={true}/> <div style={{paddingBottom: 16}}> <Text> Add new release for the application diff --git a/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/src/components/apps/list-apps/appsTable/AppsTable.js b/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/src/components/apps/list-apps/appsTable/AppsTable.js index d008cf5609..faaaf7b99d 100644 --- a/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/src/components/apps/list-apps/appsTable/AppsTable.js +++ b/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/src/components/apps/list-apps/appsTable/AppsTable.js @@ -74,7 +74,6 @@ const columns = [ ) : ( <Avatar shape="square" size="large" style={{ - marginRight: 20, borderRadius: "28%", border: "1px solid #ddd" }} diff --git a/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/src/components/new-app/subForms/NewAppDetailsForm.js b/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/src/components/new-app/subForms/NewAppDetailsForm.js index 1667f2bb4c..881dd9f14a 100644 --- a/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/src/components/new-app/subForms/NewAppDetailsForm.js +++ b/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/src/components/new-app/subForms/NewAppDetailsForm.js @@ -17,10 +17,11 @@ */ import React from "react"; -import {Button, Col, Divider, Form, Icon, Input, notification, Row, Select, Switch, Upload} from "antd"; +import {Button, Col, Divider, Form, Icon, Input, notification, Row, Select, Spin, Switch, Upload} from "antd"; import axios from "axios"; import {withConfigContext} from "../../../context/ConfigContext"; import {handleApiError} from "../../../js/Utils"; +import debounce from 'lodash.debounce'; const formItemLayout = { labelCol: { @@ -42,12 +43,15 @@ class NewAppDetailsForm extends React.Component { this.state = { categories: [], tags: [], - deviceTypes:[] + deviceTypes: [], + fetching: false, + roleSearchValue: [], + unrestrictedRoles: [] }; - + this.lastFetchId = 0; + this.fetchUser = debounce(this.fetchRoles, 800); } - handleSubmit = e => { e.preventDefault(); const {formConfig} = this.props; @@ -58,13 +62,17 @@ class NewAppDetailsForm extends React.Component { this.setState({ 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 = { name, description, categories, tags, - unrestrictedRoles: [], + unrestrictedRoles: unrestrictedRolesData, }; if (formConfig.installationType !== "WEB_CLIP") { @@ -141,15 +149,15 @@ class NewAppDetailsForm extends React.Component { const allowedDeviceTypes = []; // exclude mobile device types if installation type is custom - if(installationType==="CUSTOM"){ - allDeviceTypes.forEach(deviceType=>{ - if(!mobileDeviceTypes.includes(deviceType.name)){ + if (installationType === "CUSTOM") { + allDeviceTypes.forEach(deviceType => { + if (!mobileDeviceTypes.includes(deviceType.name)) { allowedDeviceTypes.push(deviceType); } }); - }else{ - allDeviceTypes.forEach(deviceType=>{ - if(mobileDeviceTypes.includes(deviceType.name)){ + } else { + allDeviceTypes.forEach(deviceType => { + if (mobileDeviceTypes.includes(deviceType.name)) { allowedDeviceTypes.push(deviceType); } }); @@ -168,9 +176,49 @@ class NewAppDetailsForm extends React.Component { }); }; + 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."); + this.setState({fetching: false}); + }); + }; + + handleRoleSearch = roleSearchValue => { + this.setState({ + roleSearchValue, + unrestrictedRoles: [], + fetching: false, + }); + }; + render() { const {formConfig} = this.props; - const {categories, tags, deviceTypes} = this.state; + const {categories, tags, deviceTypes, fetching, roleSearchValue, unrestrictedRoles} = this.state; const {getFieldDecorator} = this.props.form; return ( @@ -198,8 +246,7 @@ class NewAppDetailsForm extends React.Component { <Select style={{width: '100%'}} placeholder="select device type" - onChange={this.handleCategoryChange} - > + onChange={this.handleCategoryChange}> { deviceTypes.map(deviceType => { return ( @@ -238,6 +285,29 @@ class NewAppDetailsForm extends React.Component { <TextArea placeholder="Enter the description..." rows={7}/> )} </Form.Item> + + {/*Unrestricted Roles*/} + <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> <Form.Item {...formItemLayout} label="Categories"> {getFieldDecorator('categories', { rules: [{ @@ -249,8 +319,7 @@ class NewAppDetailsForm extends React.Component { mode="multiple" style={{width: '100%'}} placeholder="Select a Category" - onChange={this.handleCategoryChange} - > + onChange={this.handleCategoryChange}> { categories.map(category => { return ( @@ -274,8 +343,7 @@ class NewAppDetailsForm extends React.Component { <Select mode="tags" style={{width: '100%'}} - placeholder="Tags" - > + placeholder="Tags"> { tags.map(tag => { return ( diff --git a/components/application-mgt/org.wso2.carbon.device.application.mgt.store.ui/react-app/src/components/apps/release/install/RoleInstall.js b/components/application-mgt/org.wso2.carbon.device.application.mgt.store.ui/react-app/src/components/apps/release/install/RoleInstall.js index 276ba34ab3..1b29d7dcb6 100644 --- a/components/application-mgt/org.wso2.carbon.device.application.mgt.store.ui/react-app/src/components/apps/release/install/RoleInstall.js +++ b/components/application-mgt/org.wso2.carbon.device.application.mgt.store.ui/react-app/src/components/apps/release/install/RoleInstall.js @@ -107,8 +107,7 @@ class RoleInstall extends React.Component { filterOption={false} onSearch={this.fetchUser} onChange={this.handleChange} - style={{width: '100%'}} - > + style={{width: '100%'}}> {data.map(d => ( <Option key={d.value}>{d.text}</Option> ))} From 6245e564a4be01c7fb1135cbcc98276f6b145d3a Mon Sep 17 00:00:00 2001 From: Jayasanka <jayasanka.sack@gmail.com> Date: Sat, 26 Oct 2019 13:04:02 +0530 Subject: [PATCH 07/10] Add refresh button to subscription details table --- .../react-app/src/components/apps/AppCard.js | 2 +- .../components/apps/release/ReleaseView.js | 4 +-- ...DevicesTable.js => SubscriptionDetails.js} | 26 ++++++++++++++----- 3 files changed, 22 insertions(+), 10 deletions(-) rename components/application-mgt/org.wso2.carbon.device.application.mgt.store.ui/react-app/src/components/apps/release/{InstalledDevicesTable.js => SubscriptionDetails.js} (88%) diff --git a/components/application-mgt/org.wso2.carbon.device.application.mgt.store.ui/react-app/src/components/apps/AppCard.js b/components/application-mgt/org.wso2.carbon.device.application.mgt.store.ui/react-app/src/components/apps/AppCard.js index e60e8652a1..64d155299f 100644 --- a/components/application-mgt/org.wso2.carbon.device.application.mgt.store.ui/react-app/src/components/apps/AppCard.js +++ b/components/application-mgt/org.wso2.carbon.device.application.mgt.store.ui/react-app/src/components/apps/AppCard.js @@ -50,7 +50,7 @@ class AppCard extends React.Component { </Col> <Col span={24} style={{paddingTop:10}}> <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 rating={app.rating} starRatedColor="#777" diff --git a/components/application-mgt/org.wso2.carbon.device.application.mgt.store.ui/react-app/src/components/apps/release/ReleaseView.js b/components/application-mgt/org.wso2.carbon.device.application.mgt.store.ui/react-app/src/components/apps/release/ReleaseView.js index 83fce13416..7fd5c6eee1 100644 --- a/components/application-mgt/org.wso2.carbon.device.application.mgt.store.ui/react-app/src/components/apps/release/ReleaseView.js +++ b/components/application-mgt/org.wso2.carbon.device.application.mgt.store.ui/react-app/src/components/apps/release/ReleaseView.js @@ -27,7 +27,7 @@ import AppUninstallModal from "./install/AppUninstallModal"; import {withConfigContext} from "../../../context/ConfigContext"; import {handleApiError} from "../../../js/Utils"; import ReviewContainer from "./review/ReviewContainer"; -import InstalledDevicesTable from "./InstalledDevicesTable"; +import SubscriptionDetails from "./SubscriptionDetails"; const {Title, Text, Paragraph} = Typography; const {TabPane} = Tabs; @@ -205,7 +205,7 @@ class ReleaseView extends React.Component { <ReviewContainer uuid={release.uuid}/> </TabPane> <TabPane tab="Subscription Details" key="2"> - <InstalledDevicesTable uuid={release.uuid}/> + <SubscriptionDetails uuid={release.uuid}/> </TabPane> </Tabs> </div> diff --git a/components/application-mgt/org.wso2.carbon.device.application.mgt.store.ui/react-app/src/components/apps/release/InstalledDevicesTable.js b/components/application-mgt/org.wso2.carbon.device.application.mgt.store.ui/react-app/src/components/apps/release/SubscriptionDetails.js similarity index 88% rename from components/application-mgt/org.wso2.carbon.device.application.mgt.store.ui/react-app/src/components/apps/release/InstalledDevicesTable.js rename to components/application-mgt/org.wso2.carbon.device.application.mgt.store.ui/react-app/src/components/apps/release/SubscriptionDetails.js index 39fd3ef27c..f6bace7dd0 100644 --- a/components/application-mgt/org.wso2.carbon.device.application.mgt.store.ui/react-app/src/components/apps/release/InstalledDevicesTable.js +++ b/components/application-mgt/org.wso2.carbon.device.application.mgt.store.ui/react-app/src/components/apps/release/SubscriptionDetails.js @@ -53,7 +53,16 @@ const columns = [ title: 'Action', dataIndex: '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', @@ -128,7 +137,7 @@ const getTimeAgo = (time) => { }; -class InstalledDevicesTable extends React.Component { +class SubscriptionDetails extends React.Component { constructor(props) { super(props); config = this.props.context; @@ -171,12 +180,10 @@ class InstalledDevicesTable extends React.Component { `/admin/subscription/${this.props.uuid}?` + encodedExtraParams, ).then(res => { if (res.status === 200) { - const pagination = {...this.state.pagination}; console.log(res.data.data.data); this.setState({ loading: false, - data: res.data.data.data, - pagination, + data: res.data.data.data }); } @@ -192,9 +199,14 @@ class InstalledDevicesTable extends React.Component { <div> <div style={{paddingBottom: 24}}> <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> </div> + <div style={{textAlign: "right", paddingBottom: 6}}> + <Button icon="sync" onClick={this.fetch}> + Refresh + </Button> + </div> <Table columns={columns} rowKey={record => (record.device.deviceIdentifier + record.device.enrolmentInfo.owner + record.device.enrolmentInfo.ownership)} @@ -214,4 +226,4 @@ class InstalledDevicesTable extends React.Component { } } -export default withConfigContext(InstalledDevicesTable); \ No newline at end of file +export default withConfigContext(SubscriptionDetails); \ No newline at end of file From 84ad27f29937154437673bef63d5e5abd165ac24 Mon Sep 17 00:00:00 2001 From: Jayasanka <jayasanka.sack@gmail.com> Date: Sat, 26 Oct 2019 16:15:55 +0530 Subject: [PATCH 08/10] Add custom forbidden alerts messages in APPM publisher UI --- .../apps/detailed-rating/DetailedRating.js | 2 +- .../AppDetailsDrawer/AppDetailsDrawer.js | 2 +- .../src/components/apps/list-apps/Filters.js | 106 ++++++++--- .../apps/list-apps/appsTable/AppsTable.js | 66 ++++--- .../components/apps/release/ReleaseView.js | 2 + .../apps/release/edit-release/EditRelease.js | 123 +++++++------ .../components/apps/release/review/Reviews.js | 91 ++++++---- .../manage/categories/ManageCategories.js | 49 +++-- .../manage/categories/ManageTags.js | 48 +++-- .../src/components/new-app/AddNewAppForm.js | 25 ++- .../new-app/subForms/NewAppDetailsForm.js | 169 +++++++++++++----- .../new-app/subForms/NewAppUploadForm.js | 117 ++++++------ .../components/new-release/AddReleaseForm.js | 29 ++- .../react-app/src/js/Utils.js | 5 +- .../pages/dashboard/apps/release/Release.js | 50 ++++-- 15 files changed, 573 insertions(+), 311 deletions(-) diff --git a/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/src/components/apps/detailed-rating/DetailedRating.js b/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/src/components/apps/detailed-rating/DetailedRating.js index 2ef88827be..dba024f2ed 100644 --- a/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/src/components/apps/detailed-rating/DetailedRating.js +++ b/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/src/components/apps/detailed-rating/DetailedRating.js @@ -61,7 +61,7 @@ class DetailedRating extends React.Component{ } }).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); }); }; diff --git a/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/src/components/apps/list-apps/AppDetailsDrawer/AppDetailsDrawer.js b/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/src/components/apps/list-apps/AppDetailsDrawer/AppDetailsDrawer.js index 6e5e6b4816..7eaea07d32 100644 --- a/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/src/components/apps/list-apps/AppDetailsDrawer/AppDetailsDrawer.js +++ b/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/src/components/apps/list-apps/AppDetailsDrawer/AppDetailsDrawer.js @@ -136,7 +136,7 @@ class AppDetailsDrawer extends React.Component { } }).catch((error) => { - handleApiError(error, "Error occurred while trying to load app details."); + handleApiError(error, "Error occurred while trying to load categories.", true); this.setState({ loading: false }); diff --git a/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/src/components/apps/list-apps/Filters.js b/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/src/components/apps/list-apps/Filters.js index e042def20d..14c76e24a7 100644 --- a/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/src/components/apps/list-apps/Filters.js +++ b/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/src/components/apps/list-apps/Filters.js @@ -30,7 +30,7 @@ import { Form, message, Radio, - notification + notification, Alert } from "antd"; import axios from "axios"; import {withConfigContext} from "../../../context/ConfigContext"; @@ -46,7 +46,12 @@ class FiltersForm extends React.Component { this.state = { categories: [], 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"]; } - if(values.hasOwnProperty("appType") && values.appType==="ALL"){ + if (values.hasOwnProperty("appType") && values.appType === "ALL") { delete values["appType"]; } @@ -73,16 +78,17 @@ class FiltersForm extends React.Component { componentDidMount() { this.getCategories(); + this.getTags(); + this.getDeviceTypes(); } getCategories = () => { const config = this.props.context; 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 => { if (res.status === 200) { let categories = JSON.parse(res.data.data); - this.getTags(); this.setState({ categories: categories, loading: false @@ -90,21 +96,29 @@ class FiltersForm extends React.Component { } }).catch((error) => { - handleApiError(error, "Error occurred while trying to load categories."); - this.setState({ - loading: false - }); + handleApiError(error, "Error occurred while trying to load categories.", true); + if (error.hasOwnProperty("response") && error.response.status === 403) { + const {forbiddenErrors} = this.state; + forbiddenErrors.categories = true; + this.setState({ + forbiddenErrors, + loading: false + }) + } else { + this.setState({ + loading: false + }); + } }); }; getTags = () => { const config = this.props.context; 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 => { if (res.status === 200) { let tags = JSON.parse(res.data.data); - this.getDeviceTypes(); this.setState({ tags: tags, loading: false, @@ -112,10 +126,19 @@ class FiltersForm extends React.Component { } }).catch((error) => { - handleApiError(error, "Error occurred while trying to load tags."); - this.setState({ - loading: false - }); + handleApiError(error, "Error occurred while trying to load tags.", true); + if (error.hasOwnProperty("response") && error.response.status === 403) { + const {forbiddenErrors} = this.state; + forbiddenErrors.tags = true; + this.setState({ + forbiddenErrors, + loading: false + }) + } else { + this.setState({ + loading: false + }); + } }); }; @@ -123,7 +146,7 @@ class FiltersForm extends React.Component { getDeviceTypes = () => { const config = this.props.context; 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 => { if (res.status === 200) { const deviceTypes = JSON.parse(res.data.data); @@ -134,15 +157,24 @@ class FiltersForm extends React.Component { } }).catch((error) => { - handleApiError(error, "Error occurred while trying to load device types."); - this.setState({ - loading: false - }); + handleApiError(error, "Error occurred while trying to load device types.", true); + if (error.hasOwnProperty("response") && error.response.status === 403) { + const {forbiddenErrors} = this.state; + forbiddenErrors.deviceTypes = true; + this.setState({ + forbiddenErrors, + loading: false + }) + } else { + this.setState({ + loading: false + }); + } }); }; render() { - const {categories, tags, deviceTypes} = this.state; + const {categories, tags, deviceTypes, forbiddenErrors} = this.state; const {getFieldDecorator} = this.props.form; return ( @@ -170,7 +202,13 @@ class FiltersForm extends React.Component { </Form.Item> </Col> </Row> - + {(forbiddenErrors.categories) && ( + <Alert + message="You don't have permission to view categories." + type="warning" + banner + closable/> + )} <Form.Item label="Categories"> {getFieldDecorator('categories', { rules: [{ @@ -182,8 +220,7 @@ class FiltersForm extends React.Component { mode="multiple" style={{width: '100%'}} placeholder="Select a Category" - onChange={this.handleCategoryChange} - > + onChange={this.handleCategoryChange}> { categories.map(category => { return ( @@ -198,7 +235,13 @@ class FiltersForm extends React.Component { )} </Form.Item> - + {(forbiddenErrors.deviceTypes) && ( + <Alert + message="You don't have permission to view device types." + type="warning" + banner + closable/> + )} <Form.Item label="Device Type"> {getFieldDecorator('deviceType', { rules: [{ @@ -208,8 +251,7 @@ class FiltersForm extends React.Component { })( <Select style={{width: '100%'}} - placeholder="Select device types" - > + placeholder="Select device types"> { deviceTypes.map(deviceType => { return ( @@ -226,7 +268,13 @@ class FiltersForm extends React.Component { </Select> )} </Form.Item> - + {(forbiddenErrors.tags) && ( + <Alert + message="You don't have permission to view tags." + type="warning" + banner + closable/> + )} <Form.Item label="Tags"> {getFieldDecorator('tags', { rules: [{ diff --git a/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/src/components/apps/list-apps/appsTable/AppsTable.js b/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/src/components/apps/list-apps/appsTable/AppsTable.js index faaaf7b99d..01d6c8c159 100644 --- a/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/src/components/apps/list-apps/appsTable/AppsTable.js +++ b/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/src/components/apps/list-apps/appsTable/AppsTable.js @@ -17,7 +17,7 @@ */ 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 pSBC from 'shade-blend-color'; import "./AppsTable.css"; @@ -58,7 +58,7 @@ const columns = [ avatar = (hasPublishedRelease) ? ( <Badge title="Published" - style={{ backgroundColor: '#52c41a', borderRadius:"50%", color:"white"}} + style={{backgroundColor: '#52c41a', borderRadius: "50%", color: "white"}} count={ <Icon type="check-circle"/> @@ -147,7 +147,8 @@ class AppsTable extends React.Component { isDrawerVisible: false, selectedApp: null, selectedAppIndex: -1, - loading: false + loading: false, + isForbiddenErrorVisible: false }; config = this.props.context; } @@ -239,7 +240,12 @@ class AppsTable extends React.Component { }); } }).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}); }); }; @@ -255,29 +261,37 @@ class AppsTable extends React.Component { render() { const {isDrawerVisible, loading} = this.state; return ( - <div className="apps-table"> - <Table - rowKey={record => record.id} - dataSource={this.state.apps} - columns={columns} - pagination={this.state.pagination} - onChange={this.handleTableChange} - rowClassName="app-row" - loading={loading} - onRow={(record, rowIndex) => { - return { - onClick: event => { - this.showDrawer(record, rowIndex); - }, - }; - }}/> - <AppDetailsDrawer - visible={isDrawerVisible} - onClose={this.closeDrawer} - app={this.state.selectedApp} - onUpdateApp={this.onUpdateApp}/> + <div> + {(this.state.isForbiddenErrorVisible) && ( + <Alert + message="You don't have permission to view apps." + type="warning" + banner + closable/> + )} + <div className="apps-table"> + <Table + rowKey={record => record.id} + dataSource={this.state.apps} + columns={columns} + pagination={this.state.pagination} + onChange={this.handleTableChange} + rowClassName="app-row" + loading={loading} + onRow={(record, rowIndex) => { + return { + onClick: event => { + this.showDrawer(record, rowIndex); + }, + }; + }}/> + <AppDetailsDrawer + visible={isDrawerVisible} + onClose={this.closeDrawer} + app={this.state.selectedApp} + onUpdateApp={this.onUpdateApp}/> + </div> </div> - ); } } diff --git a/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/src/components/apps/release/ReleaseView.js b/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/src/components/apps/release/ReleaseView.js index 0c208c776b..6ea67eee20 100644 --- a/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/src/components/apps/release/ReleaseView.js +++ b/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/src/components/apps/release/ReleaseView.js @@ -24,6 +24,7 @@ import "../../../App.css"; import DetailedRating from "../detailed-rating/DetailedRating"; import EditRelease from "./edit-release/EditRelease"; import {withConfigContext} from "../../../context/ConfigContext"; +import NewAppUploadForm from "../../new-app/subForms/NewAppUploadForm"; const {Title, Text, Paragraph} = Typography; @@ -97,6 +98,7 @@ class ReleaseView extends React.Component { <Text>Version : {release.version}</Text><br/> <EditRelease + forbiddenErrors={this.props.forbiddenErrors} isAppUpdatable={isAppUpdatable} type={app.type} deviceType={app.deviceType} diff --git a/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/src/components/apps/release/edit-release/EditRelease.js b/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/src/components/apps/release/edit-release/EditRelease.js index 062fa56542..f4c6e84670 100644 --- a/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/src/components/apps/release/edit-release/EditRelease.js +++ b/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/src/components/apps/release/edit-release/EditRelease.js @@ -31,7 +31,7 @@ import { Divider, Row, Col, - Select + Select, Alert } from 'antd'; import axios from "axios"; import "@babel/polyfill"; @@ -522,63 +522,72 @@ class EditReleaseModal extends React.Component { )} </Form.Item> {(config.deviceTypes.mobileTypes.includes(deviceType)) && ( - <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> + <div> + {(this.props.forbiddenErrors.supportedOsVersions) && ( + <Alert + message="You don't have permission to view supported OS versions." + type="warning" + banner + closable/> )} - </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"> {getFieldDecorator('meta', { diff --git a/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/src/components/apps/release/review/Reviews.js b/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/src/components/apps/release/review/Reviews.js index 0e75a3d663..53aa43d298 100644 --- a/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/src/components/apps/release/review/Reviews.js +++ b/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/src/components/apps/release/review/Reviews.js @@ -17,7 +17,7 @@ */ 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 InfiniteScroll from 'react-infinite-scroller'; @@ -33,7 +33,10 @@ class Reviews extends React.Component { data: [], loading: 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 {uuid, type} = this.props; - + this.setState({ + loading: true + }); 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 => { if (res.status === 200) { let reviews = res.data.data.data; callback(reviews); } - }).catch(function (error) { - handleApiError(error, "Error occurred while trying to load reviews."); + }).catch((error) => { + handleApiError(error, "Error occurred while trying to load reviews.", true); + if (error.hasOwnProperty("response") && error.response.status === 403) { + const {forbiddenErrors} = this.state; + forbiddenErrors.reviews = true; + this.setState({ + forbiddenErrors, + loading: false + }) + } else { + this.setState({ + loading: false + }); + } }); }; @@ -101,32 +121,39 @@ class Reviews extends React.Component { render() { return ( - <div className="demo-infinite-container"> - <InfiniteScroll - initialLoad={false} - pageStart={0} - loadMore={this.handleInfiniteOnLoad} - hasMore={!this.state.loading && this.state.hasMore} - useWindow={true} - > - <List - dataSource={this.state.data} - renderItem={item => ( - <List.Item key={item.id}> - <SingleReview review={item}/> - </List.Item> - )} - > - {this.state.loading && this.state.hasMore && ( - <div className="demo-loading-container"> - <Spin/> - </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> + {(this.state.forbiddenErrors.reviews) && ( + <Alert + message="You don't have permission to view reviews." + type="warning" + banner + closable/> + )} + <div className="demo-infinite-container"> + <InfiniteScroll + initialLoad={false} + pageStart={0} + loadMore={this.handleInfiniteOnLoad} + hasMore={!this.state.loading && this.state.hasMore} + useWindow={true}> + <List + dataSource={this.state.data} + renderItem={item => ( + <List.Item key={item.id}> + <SingleReview review={item}/> + </List.Item> + )}> + {this.state.loading && this.state.hasMore && ( + <div className="demo-loading-container"> + <Spin/> + </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> ); } diff --git a/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/src/components/manage/categories/ManageCategories.js b/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/src/components/manage/categories/ManageCategories.js index 984cd5d67d..a4de159263 100644 --- a/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/src/components/manage/categories/ManageCategories.js +++ b/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/src/components/manage/categories/ManageCategories.js @@ -32,7 +32,7 @@ import { Modal, Row, Col, - Typography + Typography, Alert } from "antd"; import axios from "axios"; import {TweenOneGroup} from 'rc-tween-one'; @@ -53,13 +53,16 @@ class ManageCategories extends React.Component { isAddNewVisible: false, isEditModalVisible: false, currentlyEditingId: null, - editingValue: null + editingValue: null, + forbiddenErrors: { + categories: false + } }; componentDidMount() { const config = this.props.context; 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 => { if (res.status === 200) { let categories = JSON.parse(res.data.data); @@ -70,10 +73,19 @@ class ManageCategories extends React.Component { } }).catch((error) => { - handleApiError(error, "Error occured while trying to load categories"); - this.setState({ - loading: false - }); + handleApiError(error, "Error occured while trying to load categories", true); + if (error.hasOwnProperty("response") && error.response.status === 403) { + const {forbiddenErrors} = this.state; + forbiddenErrors.categories = true; + this.setState({ + forbiddenErrors, + loading: false + }) + } else { + this.setState({ + loading: false + }); + } }); } @@ -90,7 +102,7 @@ class ManageCategories extends React.Component { loading: true }); 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 => { if (res.status === 200) { notification["success"]({ @@ -125,8 +137,7 @@ class ManageCategories extends React.Component { const tagElem = ( <Tag color={pSBC(0.30, config.theme.primaryColor)} - style={{marginTop:8}} - > + style={{marginTop: 8}}> {categoryName} <Divider type="vertical"/> <Tooltip title="edit"> @@ -150,8 +161,7 @@ class ManageCategories extends React.Component { } }} okText="Yes" - cancelText="No" - > + cancelText="No"> <Icon type="delete"/> </Popconfirm> </Tooltip> @@ -168,7 +178,7 @@ class ManageCategories extends React.Component { const config = this.props.context; const tagElem = ( <Tag - style={{marginTop:8}} + style={{marginTop: 8}} closable onClose={e => { e.preventDefault(); @@ -229,7 +239,7 @@ class ManageCategories extends React.Component { const data = tempElements.map(category => category.categoryName); 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, ).then(res => { if (res.status === 200) { @@ -287,7 +297,7 @@ class ManageCategories extends React.Component { }); 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 => { if (res.status === 200) { @@ -324,11 +334,18 @@ class ManageCategories extends React.Component { }; 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 temporaryElements = tempElements.map(this.renderTempElement); return ( <div style={{marginBottom: 16}}> + {(forbiddenErrors.categories) && ( + <Alert + message="You don't have permission to view categories." + type="warning" + banner + closable/> + )} <Card> <Spin tip="Working on it..." spinning={this.state.loading}> <Row> diff --git a/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/src/components/manage/categories/ManageTags.js b/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/src/components/manage/categories/ManageTags.js index 69c269bc17..8b9ed800b1 100644 --- a/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/src/components/manage/categories/ManageTags.js +++ b/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/src/components/manage/categories/ManageTags.js @@ -31,7 +31,7 @@ import { Popconfirm, Modal, Row, Col, - Typography + Typography, Alert } from "antd"; import axios from "axios"; import {TweenOneGroup} from 'rc-tween-one'; @@ -51,13 +51,16 @@ class ManageTags extends React.Component { isAddNewVisible: false, isEditModalVisible: false, currentlyEditingId: null, - editingValue: null + editingValue: null, + forbiddenErrors: { + tags: false + } }; componentDidMount() { const config = this.props.context; 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 => { if (res.status === 200) { let tags = JSON.parse(res.data.data); @@ -68,10 +71,19 @@ class ManageTags extends React.Component { } }).catch((error) => { - handleApiError(error, "Error occurred while trying to load tags."); - this.setState({ - loading: false - }); + handleApiError(error, "Error occurred while trying to load tags.", true); + if (error.hasOwnProperty("response") && error.response.status === 403) { + const {forbiddenErrors} = this.state; + forbiddenErrors.tags = true; + this.setState({ + forbiddenErrors, + loading: false + }) + } else { + this.setState({ + loading: false + }); + } }); } @@ -90,7 +102,7 @@ class ManageTags extends React.Component { }); 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 => { if (res.status === 200) { notification["success"]({ @@ -124,7 +136,7 @@ class ManageTags extends React.Component { const tagElem = ( <Tag color="#34495e" - style={{marginTop:8}} + style={{marginTop: 8}} > {tagName} <Divider type="vertical"/> @@ -167,7 +179,7 @@ class ManageTags extends React.Component { const {tempElements} = this.state; const tagElem = ( <Tag - style={{marginTop:8}} + style={{marginTop: 8}} closable onClose={e => { e.preventDefault(); @@ -226,7 +238,7 @@ class ManageTags extends React.Component { const data = tempElements.map(tag => tag.tagName); - axios.post(window.location.origin+ config.serverConfig.invoker.uri + config.serverConfig.invoker.publisher + "/applications/tags", + axios.post(window.location.origin + config.serverConfig.invoker.uri + config.serverConfig.invoker.publisher + "/applications/tags", data, ).then(res => { if (res.status === 200) { @@ -284,7 +296,7 @@ class ManageTags extends React.Component { }); 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 => { if (res.status === 200) { @@ -321,11 +333,18 @@ class ManageTags extends React.Component { }; 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 temporaryElements = tempElements.map(this.renderTempElement); return ( <div style={{marginBottom: 16}}> + {(forbiddenErrors.tags) && ( + <Alert + message="You don't have permission to view tags." + type="warning" + banner + closable/> + )} <Card> <Spin tip="Working on it..." spinning={this.state.loading}> <Row> @@ -365,8 +384,7 @@ class ManageTags extends React.Component { }, }} leave={{opacity: 0, width: 0, scale: 0, duration: 200}} - appear={false} - > + appear={false}> {temporaryElements} {inputVisible && ( diff --git a/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/src/components/new-app/AddNewAppForm.js b/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/src/components/new-app/AddNewAppForm.js index 340d28aa93..39dc99ddb1 100644 --- a/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/src/components/new-app/AddNewAppForm.js +++ b/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/src/components/new-app/AddNewAppForm.js @@ -55,7 +55,10 @@ class AddNewAppFormComponent extends React.Component { isError: false, deviceType: null, supportedOsVersions: [], - errorText: "" + errorText: "", + forbiddenErrors: { + supportedOsVersions: false + } }; } @@ -143,15 +146,24 @@ class AddNewAppFormComponent extends React.Component { }); } }).catch((error) => { - handleApiError(error, "Error occurred while trying to load supported OS versions."); - this.setState({ - loading: false - }); + handleApiError(error, "Error occurred while trying to load supported OS versions.", true); + if (error.hasOwnProperty("response") && error.response.status === 403) { + const {forbiddenErrors} = this.state; + forbiddenErrors.supportedOsVersions = true; + this.setState({ + forbiddenErrors, + loading: false + }) + } else { + this.setState({ + loading: false + }); + } }); }; render() { - const {loading, current, isError, supportedOsVersions, errorText} = this.state; + const {loading, current, isError, supportedOsVersions, errorText, forbiddenErrors} = this.state; const {formConfig} = this.props; return ( <div> @@ -171,6 +183,7 @@ class AddNewAppFormComponent extends React.Component { </div> <div style={{display: (current === 1 ? 'unset' : 'none')}}> <NewAppUploadForm + forbiddenErrors={forbiddenErrors} formConfig={formConfig} supportedOsVersions={supportedOsVersions} onSuccessReleaseData={this.onSuccessReleaseData} diff --git a/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/src/components/new-app/subForms/NewAppDetailsForm.js b/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/src/components/new-app/subForms/NewAppDetailsForm.js index 881dd9f14a..1a0ac43127 100644 --- a/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/src/components/new-app/subForms/NewAppDetailsForm.js +++ b/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/src/components/new-app/subForms/NewAppDetailsForm.js @@ -17,7 +17,7 @@ */ import React from "react"; -import {Button, Col, Divider, Form, Icon, Input, notification, Row, Select, Spin, 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 {withConfigContext} from "../../../context/ConfigContext"; import {handleApiError} from "../../../js/Utils"; @@ -46,10 +46,16 @@ class NewAppDetailsForm extends React.Component { deviceTypes: [], fetching: false, roleSearchValue: [], - unrestrictedRoles: [] + unrestrictedRoles: [], + forbiddenErrors: { + categories: false, + tags: false, + deviceTypes: false, + roles: false + } }; this.lastFetchId = 0; - this.fetchUser = debounce(this.fetchRoles, 800); + this.fetchRoles = debounce(this.fetchRoles, 800); } handleSubmit = e => { @@ -64,7 +70,7 @@ class NewAppDetailsForm extends React.Component { }); const {name, description, categories, tags, unrestrictedRoles} = values; const unrestrictedRolesData = []; - unrestrictedRoles.map(val=>{ + unrestrictedRoles.map(val => { unrestrictedRolesData.push(val.key); }); const application = { @@ -89,6 +95,8 @@ class NewAppDetailsForm extends React.Component { componentDidMount() { this.getCategories(); + this.getTags(); + this.getDeviceTypes(); } getCategories = () => { @@ -103,13 +111,20 @@ class NewAppDetailsForm extends React.Component { loading: false }); } - this.getTags(); - }).catch((error) => { - handleApiError(error, "Error occurred while trying to load categories."); - this.setState({ - loading: false - }); + handleApiError(error, "Error occurred while trying to load categories.", true); + if (error.hasOwnProperty("response") && error.response.status === 403) { + const {forbiddenErrors} = this.state; + forbiddenErrors.categories = true; + this.setState({ + forbiddenErrors, + loading: false + }) + } else { + this.setState({ + loading: false + }); + } }); }; @@ -125,13 +140,20 @@ class NewAppDetailsForm extends React.Component { loading: false, }); } - this.getDeviceTypes(); - }).catch((error) => { - handleApiError(error, "Error occurred while trying to load tags."); - this.setState({ - loading: false - }); + handleApiError(error, "Error occurred while trying to load tags.", true); + if (error.hasOwnProperty("response") && error.response.status === 403) { + const {forbiddenErrors} = this.state; + forbiddenErrors.tags = true; + this.setState({ + forbiddenErrors, + loading: false + }) + } else { + this.setState({ + loading: false + }); + } }); }; @@ -169,10 +191,19 @@ class NewAppDetailsForm extends React.Component { }); } }).catch((error) => { - handleApiError(error, "Error occurred while trying to load device types."); - this.setState({ - loading: false - }); + handleApiError(error, "Error occurred while trying to load device types.", true); + if (error.hasOwnProperty("response") && error.response.status === 403) { + const {forbiddenErrors} = this.state; + forbiddenErrors.deviceTypes = true; + this.setState({ + forbiddenErrors, + loading: false + }) + } else { + this.setState({ + loading: false + }); + } }); }; @@ -203,8 +234,19 @@ class NewAppDetailsForm extends React.Component { } }).catch((error) => { - handleApiError(error, "Error occurred while trying to load roles."); - this.setState({fetching: false}); + 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 + }); + } }); }; @@ -218,7 +260,7 @@ class NewAppDetailsForm extends React.Component { render() { const {formConfig} = this.props; - const {categories, tags, deviceTypes, fetching, roleSearchValue, unrestrictedRoles} = this.state; + const {categories, tags, deviceTypes, fetching, roleSearchValue, unrestrictedRoles, forbiddenErrors} = this.state; const {getFieldDecorator} = this.props.form; return ( @@ -233,33 +275,41 @@ class NewAppDetailsForm extends React.Component { layout="horizontal" onSubmit={this.handleSubmit}> {formConfig.installationType !== "WEB_CLIP" && ( - <Form.Item {...formItemLayout} label="Device Type"> - {getFieldDecorator('deviceType', { - rules: [ + <div> + {(forbiddenErrors.deviceTypes) && ( + <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, - message: 'Please select device type' + deviceTypes.map(deviceType => { + return ( + <Option + key={deviceType.name}> + {deviceType.name} + </Option> + ) + }) } - ], - } - )( - <Select - style={{width: '100%'}} - placeholder="select device type" - onChange={this.handleCategoryChange}> - { - deviceTypes.map(deviceType => { - return ( - <Option - key={deviceType.name}> - {deviceType.name} - </Option> - ) - }) - } - </Select> - )} - </Form.Item> + </Select> + )} + </Form.Item> + </div> )} {/*app name*/} @@ -287,6 +337,13 @@ class NewAppDetailsForm extends React.Component { </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: [], @@ -295,7 +352,7 @@ class NewAppDetailsForm extends React.Component { <Select mode="multiple" labelInValue - value={roleSearchValue} + // value={roleSearchValue} placeholder="Search roles" notFoundContent={fetching ? <Spin size="small"/> : null} filterOption={false} @@ -308,6 +365,13 @@ class NewAppDetailsForm extends React.Component { </Select> )} </Form.Item> + {(forbiddenErrors.categories) && ( + <Alert + message="You don't have permission to view categories." + type="warning" + banner + closable/> + )} <Form.Item {...formItemLayout} label="Categories"> {getFieldDecorator('categories', { rules: [{ @@ -333,6 +397,13 @@ class NewAppDetailsForm extends React.Component { </Select> )} </Form.Item> + {(forbiddenErrors.tags) && ( + <Alert + message="You don't have permission to view tags." + type="warning" + banner + closable/> + )} <Form.Item {...formItemLayout} label="Tags"> {getFieldDecorator('tags', { rules: [{ diff --git a/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/src/components/new-app/subForms/NewAppUploadForm.js b/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/src/components/new-app/subForms/NewAppUploadForm.js index c7916c2c13..a0aa1e6efa 100644 --- a/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/src/components/new-app/subForms/NewAppUploadForm.js +++ b/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/src/components/new-app/subForms/NewAppUploadForm.js @@ -17,7 +17,7 @@ */ 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 axios from "axios"; import {handleApiError} from "../../../js/Utils"; @@ -400,60 +400,69 @@ class NewAppUploadForm extends React.Component { </Form.Item> {(formConfig.installationType !== "WEB_CLIP" && formConfig.installationType !== "CUSTOM") && ( - <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={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> + <div> + {(this.props.forbiddenErrors.supportedOsVersions) && ( + <Alert + message="You don't have permission to view supported OS versions." + type="warning" + banner + closable/> )} - </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={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> + </div> )} <Form.Item {...formItemLayout} label="Meta Data"> {getFieldDecorator('meta', {})( diff --git a/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/src/components/new-release/AddReleaseForm.js b/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/src/components/new-release/AddReleaseForm.js index 1fa47170e9..abaea5dbe9 100644 --- a/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/src/components/new-release/AddReleaseForm.js +++ b/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/src/components/new-release/AddReleaseForm.js @@ -41,7 +41,10 @@ class AddNewReleaseFormComponent extends React.Component { supportedOsVersions: [], application: null, release: null, - deviceType: null + deviceType: null, + forbiddenErrors: { + supportedOsVersions: false + } }; } @@ -63,10 +66,19 @@ class AddNewReleaseFormComponent extends React.Component { }); } }).catch((error) => { - handleApiError(error, "Error occurred while trying to load supported OS versions."); - this.setState({ - loading: false - }); + handleApiError(error, "Error occurred while trying to load supported OS versions.", true); + if (error.hasOwnProperty("response") && error.response.status === 403) { + const {forbiddenErrors} = this.state; + forbiddenErrors.supportedOsVersions = true; + this.setState({ + forbiddenErrors, + loading: false + }) + } else { + this.setState({ + loading: false + }); + } }); }; @@ -85,7 +97,7 @@ class AddNewReleaseFormComponent extends React.Component { data.append("applicationRelease", blob); 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( url, data @@ -122,14 +134,15 @@ class AddNewReleaseFormComponent extends React.Component { }; render() { - const {loading, supportedOsVersions} = this.state; + const {loading, supportedOsVersions, forbiddenErrors} = this.state; return ( <div> <Spin tip="Uploading..." spinning={loading}> <Row> - <Col span={17} offset={4} > + <Col span={17} offset={4}> <Card> <NewAppUploadForm + forbiddenErrors={forbiddenErrors} formConfig={formConfig} supportedOsVersions={supportedOsVersions} onSuccessReleaseData={this.onSuccessReleaseData} diff --git a/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/src/js/Utils.js b/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/src/js/Utils.js index 2ef4f8bf0f..9be2810b0f 100644 --- a/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/src/js/Utils.js +++ b/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/src/js/Utils.js @@ -18,11 +18,12 @@ import {notification} from "antd"; -export const handleApiError = (error, message) => { +export const handleApiError = (error, message, isForbiddenMessageSilent) => { if (error.hasOwnProperty("response") && error.response.status === 401) { const redirectUrl = encodeURI(window.location.href); 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"]({ message: "There was a problem", duration: 10, diff --git a/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/src/pages/dashboard/apps/release/Release.js b/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/src/pages/dashboard/apps/release/Release.js index 40fd63a0ba..fc653d7bf4 100644 --- a/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/src/pages/dashboard/apps/release/Release.js +++ b/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/src/pages/dashboard/apps/release/Release.js @@ -24,6 +24,7 @@ import ReleaseView from "../../../../components/apps/release/ReleaseView"; import LifeCycle from "../../../../components/apps/release/lifeCycle/LifeCycle"; import {withConfigContext} from "../../../../context/ConfigContext"; import {handleApiError} from "../../../../js/Utils"; +import NewAppUploadForm from "../../../../components/new-app/subForms/NewAppUploadForm"; const {Title} = Typography; @@ -40,13 +41,19 @@ class Release extends React.Component { release: null, currentLifecycleStatus: null, lifecycle: null, - supportedOsVersions:[] + supportedOsVersions: [], + forbiddenErrors: { + supportedOsVersions: false, + lifeCycle: false + } }; } componentDidMount() { const {uuid} = this.props.match.params; this.fetchData(uuid); + this.getLifecycle(); + } componentDidUpdate(prevProps, prevState, snapshot) { @@ -86,10 +93,8 @@ class Release extends React.Component { loading: false, uuid: uuid }); - if(config.deviceTypes.mobileTypes.includes(app.deviceType)){ + if (config.deviceTypes.mobileTypes.includes(app.deviceType)) { this.getSupportedOsVersions(app.deviceType); - }else{ - this.getLifecycle(); } } @@ -111,8 +116,15 @@ class Release extends React.Component { }) } - }).catch(function (error) { - handleApiError(error, "Error occurred while trying to load lifecycle configuration."); + }).catch((error) => { + handleApiError(error, "Error occurred while trying to load lifecycle configuration.", true); + if (error.hasOwnProperty("response") && error.response.status === 403) { + const {forbiddenErrors} = this.state; + forbiddenErrors.lifeCycle = true; + this.setState({ + forbiddenErrors + }) + } }); }; @@ -125,21 +137,28 @@ class Release extends React.Component { if (res.status === 200) { let supportedOsVersions = JSON.parse(res.data.data); this.setState({ - supportedOsVersions, - loading: false, + supportedOsVersions }); - this.getLifecycle(); } }).catch((error) => { - handleApiError(error, "Error occurred while trying to load supported OS versions."); - this.setState({ - loading: false - }); + handleApiError(error, "Error occurred while trying to load supported OS versions.", true); + if (error.hasOwnProperty("response") && error.response.status === 403) { + const {forbiddenErrors} = this.state; + forbiddenErrors.supportedOsVersions = true; + this.setState({ + forbiddenErrors, + loading: false + }) + } else { + this.setState({ + loading: false + }); + } }); }; render() { - const {app, release, currentLifecycleStatus, lifecycle, loading} = this.state; + const {app, release, currentLifecycleStatus, lifecycle, loading, forbiddenErrors} = this.state; if (release == null && loading === false) { return ( @@ -159,12 +178,13 @@ class Release extends React.Component { <Skeleton loading={loading} avatar={{size: 'large'}} active paragraph={{rows: 18}}> {(release !== null) && ( <ReleaseView + forbiddenErrors={forbiddenErrors} app={app} release={release} currentLifecycleStatus={currentLifecycleStatus} lifecycle={lifecycle} updateRelease={this.updateRelease} - supportedOsVersions = {[...this.state.supportedOsVersions]} + supportedOsVersions={[...this.state.supportedOsVersions]} />) } </Skeleton> From 9303d37451c41bfe3e90c58c240158b59ff0aa5d Mon Sep 17 00:00:00 2001 From: Jayasanka <jayasanka.sack@gmail.com> Date: Sat, 26 Oct 2019 17:06:35 +0530 Subject: [PATCH 09/10] Add custom forbidden alerts messages in APPM store UI --- .../react-app/src/js/Utils.js | 4 +- .../react-app/src/components/apps/AppList.js | 36 +++++-- .../apps/release/SubscriptionDetails.js | 38 ++++++- .../apps/release/install/DeviceInstall.js | 25 ++++- .../apps/release/install/DeviceUninstall.js | 25 ++++- .../apps/release/install/GroupInstall.js | 23 ++++- .../apps/release/install/GroupUninstall.js | 76 ++++++++------ .../apps/release/install/RoleInstall.js | 23 ++++- .../apps/release/install/RoleUninstall.js | 85 +++++++++------- .../apps/release/install/UserInstall.js | 23 ++++- .../apps/release/install/UserUninstall.js | 23 ++++- .../apps/release/review/CurrentUsersReview.js | 99 ++++++++++--------- .../apps/release/review/ReviewContainer.js | 26 ++++- .../components/apps/release/review/Reviews.js | 85 ++++++++++------ .../react-app/src/js/Utils.js | 7 +- .../src/pages/dashboard/Dashboard.js | 92 ++++++++++------- .../pages/dashboard/apps/release/Release.js | 20 +++- 17 files changed, 493 insertions(+), 217 deletions(-) diff --git a/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/src/js/Utils.js b/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/src/js/Utils.js index 9be2810b0f..d5b976499e 100644 --- a/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/src/js/Utils.js +++ b/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/src/js/Utils.js @@ -18,12 +18,12 @@ import {notification} from "antd"; -export const handleApiError = (error, message, isForbiddenMessageSilent) => { +export const handleApiError = (error, message, isForbiddenMessageSilent = false) => { if (error.hasOwnProperty("response") && error.response.status === 401) { const redirectUrl = encodeURI(window.location.href); window.location.href = window.location.origin + `/publisher/login?redirect=${redirectUrl}`; // silence 403 forbidden message - } else if(!(isForbiddenMessageSilent && error.hasOwnProperty("response") && error.response.status === 403)){ + } else if (!(isForbiddenMessageSilent && error.hasOwnProperty("response") && error.response.status === 403)) { notification["error"]({ message: "There was a problem", duration: 10, diff --git a/components/application-mgt/org.wso2.carbon.device.application.mgt.store.ui/react-app/src/components/apps/AppList.js b/components/application-mgt/org.wso2.carbon.device.application.mgt.store.ui/react-app/src/components/apps/AppList.js index 241ea12acd..710821f324 100644 --- a/components/application-mgt/org.wso2.carbon.device.application.mgt.store.ui/react-app/src/components/apps/AppList.js +++ b/components/application-mgt/org.wso2.carbon.device.application.mgt.store.ui/react-app/src/components/apps/AppList.js @@ -18,7 +18,7 @@ import React from "react"; 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 {withConfigContext} from "../../context/ConfigContext"; import {handleApiError} from "../../js/Utils"; @@ -28,7 +28,10 @@ class AppList extends React.Component { super(props); this.state = { apps: [], - loading: true + loading: true, + forbiddenErrors: { + apps: false + } } } @@ -60,7 +63,7 @@ class AppList extends React.Component { }); //send request to the invoker 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, ).then(res => { if (res.status === 200) { @@ -73,18 +76,37 @@ class AppList extends React.Component { } }).catch((error) => { - handleApiError(error,"Error occurred while trying to load apps."); - this.setState({loading: false}); + handleApiError(error, "Error occurred while trying to load apps.", true); + if (error.hasOwnProperty("response") && error.response.status === 403) { + const {forbiddenErrors} = this.state; + forbiddenErrors.apps = true; + this.setState({ + forbiddenErrors, + loading: false + }) + } else { + this.setState({ + loading: false + }); + } }); }; render() { - const {apps,loading} = this.state; + const {apps, loading, forbiddenErrors} = this.state; return ( <Skeleton loading={loading} active> <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 status="404" title="No apps, yet." diff --git a/components/application-mgt/org.wso2.carbon.device.application.mgt.store.ui/react-app/src/components/apps/release/SubscriptionDetails.js b/components/application-mgt/org.wso2.carbon.device.application.mgt.store.ui/react-app/src/components/apps/release/SubscriptionDetails.js index f6bace7dd0..be71eb4e8f 100644 --- a/components/application-mgt/org.wso2.carbon.device.application.mgt.store.ui/react-app/src/components/apps/release/SubscriptionDetails.js +++ b/components/application-mgt/org.wso2.carbon.device.application.mgt.store.ui/react-app/src/components/apps/release/SubscriptionDetails.js @@ -18,7 +18,20 @@ import React from "react"; 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' // Load locale-specific relative date/time formatting rules. @@ -149,7 +162,8 @@ class SubscriptionDetails extends React.Component { selectedRows: [], deviceGroups: [], groupModalVisible: false, - selectedGroupId: [] + selectedGroupId: [], + isForbidden: false }; } @@ -188,8 +202,17 @@ class SubscriptionDetails extends React.Component { } }).catch((error) => { - handleApiError(error, "Something went wrong when trying to load subscription data."); - this.setState({loading: false}); + handleApiError(error, "Something went wrong when trying to load subscription data.", true); + if (error.hasOwnProperty("response") && error.response.status === 403) { + this.setState({ + isForbidden: true, + loading: false + }) + } else { + this.setState({ + loading: false + }); + } }); }; @@ -197,6 +220,13 @@ class SubscriptionDetails extends React.Component { const {data, pagination, loading, selectedRows} = this.state; return ( <div> + {(this.state.isForbidden) && ( + <Alert + message="You don't have permission to view subscription details." + type="warning" + banner + closable/> + )} <div style={{paddingBottom: 24}}> <Text> The following are the subscription details of the application in each respective device. diff --git a/components/application-mgt/org.wso2.carbon.device.application.mgt.store.ui/react-app/src/components/apps/release/install/DeviceInstall.js b/components/application-mgt/org.wso2.carbon.device.application.mgt.store.ui/react-app/src/components/apps/release/install/DeviceInstall.js index 3e67c54c03..ed69db96ef 100644 --- a/components/application-mgt/org.wso2.carbon.device.application.mgt.store.ui/react-app/src/components/apps/release/install/DeviceInstall.js +++ b/components/application-mgt/org.wso2.carbon.device.application.mgt.store.ui/react-app/src/components/apps/release/install/DeviceInstall.js @@ -18,7 +18,7 @@ import React from "react"; 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' // Load locale-specific relative date/time formatting rules. @@ -112,7 +112,8 @@ class DeviceInstall extends React.Component { loading: false, selectedRows: [], scheduledTime: null, - isScheduledInstallVisible: false + isScheduledInstallVisible: false, + isForbidden: false }; } @@ -169,8 +170,17 @@ class DeviceInstall extends React.Component { } }).catch((error) => { - handleApiError(error, "Error occurred while trying to load devices."); - this.setState({loading: false}); + handleApiError(error, "Error occurred while trying to load devices.", true); + if (error.hasOwnProperty("response") && error.response.status === 403) { + this.setState({ + isForbidden: true, + loading: false + }) + } else { + this.setState({ + loading: false + }); + } }); }; @@ -209,6 +219,13 @@ class DeviceInstall extends React.Component { Start installing the application for one or more users by entering the corresponding user name. Select install to automatically start downloading the application for the respective user/users. </Text> + {(this.state.isForbidden) && ( + <Alert + message="You don't have permission to view devices." + type="warning" + banner + closable/> + )} <Table style={{paddingTop: 20}} columns={columns} diff --git a/components/application-mgt/org.wso2.carbon.device.application.mgt.store.ui/react-app/src/components/apps/release/install/DeviceUninstall.js b/components/application-mgt/org.wso2.carbon.device.application.mgt.store.ui/react-app/src/components/apps/release/install/DeviceUninstall.js index 4daf769792..fa449ccd37 100644 --- a/components/application-mgt/org.wso2.carbon.device.application.mgt.store.ui/react-app/src/components/apps/release/install/DeviceUninstall.js +++ b/components/application-mgt/org.wso2.carbon.device.application.mgt.store.ui/react-app/src/components/apps/release/install/DeviceUninstall.js @@ -18,7 +18,7 @@ import React from "react"; 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' // Load locale-specific relative date/time formatting rules. @@ -109,7 +109,8 @@ class DeviceUninstall extends React.Component { data: [], pagination: {}, loading: false, - selectedRows: [] + selectedRows: [], + isForbidden: false }; } @@ -164,8 +165,17 @@ class DeviceUninstall extends React.Component { }); } }).catch((error) => { - handleApiError(error, "Error occurred while trying to load devices."); - this.setState({loading: false}); + handleApiError(error, "Error occurred while trying to load devices.", true); + if (error.hasOwnProperty("response") && error.response.status === 403) { + this.setState({ + isForbidden: true, + loading: false + }) + } else { + this.setState({ + loading: false + }); + } }); }; @@ -204,6 +214,13 @@ class DeviceUninstall extends React.Component { Start uninstalling the application for devices by selecting the corresponding devices. Select uninstall to automatically start uninstalling the application for the respective devices. </Text> + {(this.state.isForbidden) && ( + <Alert + message="You don't have permission to view installed devices." + type="warning" + banner + closable/> + )} <Table style={{paddingTop: 20}} columns={columns} diff --git a/components/application-mgt/org.wso2.carbon.device.application.mgt.store.ui/react-app/src/components/apps/release/install/GroupInstall.js b/components/application-mgt/org.wso2.carbon.device.application.mgt.store.ui/react-app/src/components/apps/release/install/GroupInstall.js index be8c598a23..7d1d41a048 100644 --- a/components/application-mgt/org.wso2.carbon.device.application.mgt.store.ui/react-app/src/components/apps/release/install/GroupInstall.js +++ b/components/application-mgt/org.wso2.carbon.device.application.mgt.store.ui/react-app/src/components/apps/release/install/GroupInstall.js @@ -17,7 +17,7 @@ */ 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 axios from "axios"; import {withConfigContext} from "../../../../context/ConfigContext"; @@ -40,6 +40,7 @@ class GroupInstall extends React.Component { data: [], value: [], fetching: false, + isForbidden: false }; fetchUser = value => { @@ -67,8 +68,17 @@ class GroupInstall extends React.Component { } }).catch((error) => { - handleApiError(error,"Error occurred while trying to load groups."); - this.setState({fetching: false}); + handleApiError(error,"Error occurred while trying to load groups.", true); + if (error.hasOwnProperty("response") && error.response.status === 403) { + this.setState({ + isForbidden: true, + loading: false + }) + } else { + this.setState({ + loading: false + }); + } }); }; @@ -96,6 +106,13 @@ class GroupInstall extends React.Component { return ( <div> <Text>Start installing the application for one or more groups by entering the corresponding group name. Select install to automatically start downloading the application for the respective device group/ groups.</Text> + {(this.state.isForbidden) && ( + <Alert + message="You don't have permission to view groups." + type="warning" + banner + closable/> + )} <br/> <br/> <Select diff --git a/components/application-mgt/org.wso2.carbon.device.application.mgt.store.ui/react-app/src/components/apps/release/install/GroupUninstall.js b/components/application-mgt/org.wso2.carbon.device.application.mgt.store.ui/react-app/src/components/apps/release/install/GroupUninstall.js index 82061b434c..b7f998a9d7 100644 --- a/components/application-mgt/org.wso2.carbon.device.application.mgt.store.ui/react-app/src/components/apps/release/install/GroupUninstall.js +++ b/components/application-mgt/org.wso2.carbon.device.application.mgt.store.ui/react-app/src/components/apps/release/install/GroupUninstall.js @@ -17,7 +17,7 @@ */ 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 axios from "axios"; import {withConfigContext} from "../../../../context/ConfigContext"; @@ -39,6 +39,7 @@ class GroupUninstall extends React.Component { data: [], value: [], fetching: false, + isForbidden: false }; fetchUser = value => { @@ -50,9 +51,8 @@ class GroupUninstall extends React.Component { const uuid = this.props.uuid; axios.get( - window.location.origin+ config.serverConfig.invoker.uri + config.serverConfig.invoker.store+ "/subscription/" + uuid + "/"+ - "/GROUP?", - + window.location.origin + config.serverConfig.invoker.uri + config.serverConfig.invoker.store + "/subscription/" + uuid + "/" + + "/GROUP?", ).then(res => { if (res.status === 200) { if (fetchId !== this.lastFetchId) { @@ -69,8 +69,17 @@ class GroupUninstall extends React.Component { } }).catch((error) => { - handleApiError(error,"Error occurred while trying to load groups."); - this.setState({fetching: false}); + handleApiError(error, "Error occurred while trying to load groups.", true); + if (error.hasOwnProperty("response") && error.response.status === 403) { + this.setState({ + isForbidden: true, + loading: false + }) + } else { + this.setState({ + loading: false + }); + } }); }; @@ -82,13 +91,13 @@ class GroupUninstall extends React.Component { }); }; - uninstall = (timestamp=null) =>{ + uninstall = (timestamp = null) => { const {value} = this.state; const data = []; - value.map(val=>{ + value.map(val => { data.push(val.key); }); - this.props.onUninstall("group", data, "uninstall",timestamp); + this.props.onUninstall("group", data, "uninstall", timestamp); }; render() { @@ -96,26 +105,35 @@ class GroupUninstall extends React.Component { const {fetching, data, value} = this.state; return ( - <div> - <Text>Start uninstalling the application for one or more groups by entering the corresponding group name. Select uninstall to automatically start uninstalling the application for the respective device group/ groups.</Text> - <br/> - <br/> - <Select - mode="multiple" - labelInValue - value={value} - placeholder="Search groups" - notFoundContent={fetching ? <Spin size="small"/> : null} - filterOption={false} - onSearch={this.fetchUser} - onChange={this.handleChange} - style={{width: '100%'}}> - {data.map(d => ( - <Option key={d.value}>{d.text}</Option> - ))} - </Select> - <InstallModalFooter type="Uninstall" operation={this.uninstall} disabled={value.length===0}/> - </div> + <div> + <Text>Start uninstalling the application for one or more groups by entering the corresponding group + name. Select uninstall to automatically start uninstalling the application for the respective device + group/ groups.</Text> + {(this.state.isForbidden) && ( + <Alert + message="You don't have permission to view installed groups." + type="warning" + banner + closable/> + )} + <br/> + <br/> + <Select + mode="multiple" + labelInValue + value={value} + placeholder="Search groups" + notFoundContent={fetching ? <Spin size="small"/> : null} + filterOption={false} + onSearch={this.fetchUser} + onChange={this.handleChange} + style={{width: '100%'}}> + {data.map(d => ( + <Option key={d.value}>{d.text}</Option> + ))} + </Select> + <InstallModalFooter type="Uninstall" operation={this.uninstall} disabled={value.length === 0}/> + </div> ); } } diff --git a/components/application-mgt/org.wso2.carbon.device.application.mgt.store.ui/react-app/src/components/apps/release/install/RoleInstall.js b/components/application-mgt/org.wso2.carbon.device.application.mgt.store.ui/react-app/src/components/apps/release/install/RoleInstall.js index 1b29d7dcb6..063311953a 100644 --- a/components/application-mgt/org.wso2.carbon.device.application.mgt.store.ui/react-app/src/components/apps/release/install/RoleInstall.js +++ b/components/application-mgt/org.wso2.carbon.device.application.mgt.store.ui/react-app/src/components/apps/release/install/RoleInstall.js @@ -17,7 +17,7 @@ */ 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 axios from "axios"; import {withConfigContext} from "../../../../context/ConfigContext"; @@ -40,6 +40,7 @@ class RoleInstall extends React.Component { data: [], value: [], fetching: false, + isForbidden: false }; fetchUser = value => { @@ -67,8 +68,17 @@ class RoleInstall extends React.Component { } }).catch((error) => { - handleApiError(error,"Error occurred while trying to load roles."); - this.setState({fetching: false}); + handleApiError(error,"Error occurred while trying to load roles.", true); + if (error.hasOwnProperty("response") && error.response.status === 403) { + this.setState({ + isForbidden: true, + loading: false + }) + } else { + this.setState({ + loading: false + }); + } }); }; @@ -96,6 +106,13 @@ class RoleInstall extends React.Component { return ( <div> <Text>Start installing the application for one or more roles by entering the corresponding role name. Select install to automatically start downloading the application for the respective user role/roles.</Text> + {(this.state.isForbidden) && ( + <Alert + message="You don't have permission to view roles." + type="warning" + banner + closable/> + )} <br/> <br/> <Select diff --git a/components/application-mgt/org.wso2.carbon.device.application.mgt.store.ui/react-app/src/components/apps/release/install/RoleUninstall.js b/components/application-mgt/org.wso2.carbon.device.application.mgt.store.ui/react-app/src/components/apps/release/install/RoleUninstall.js index 5e7462de3c..0076e80b70 100644 --- a/components/application-mgt/org.wso2.carbon.device.application.mgt.store.ui/react-app/src/components/apps/release/install/RoleUninstall.js +++ b/components/application-mgt/org.wso2.carbon.device.application.mgt.store.ui/react-app/src/components/apps/release/install/RoleUninstall.js @@ -17,7 +17,7 @@ */ 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 axios from "axios"; import {withConfigContext} from "../../../../context/ConfigContext"; @@ -39,6 +39,7 @@ class RoleUninstall extends React.Component { data: [], value: [], fetching: false, + isForbidden: false }; fetchUser = value => { @@ -50,8 +51,8 @@ class RoleUninstall extends React.Component { const uuid = this.props.uuid; axios.get( - window.location.origin+ config.serverConfig.invoker.uri + config.serverConfig.invoker.store+ "/subscription/" + uuid + "/"+ - "/ROLE?", + window.location.origin + config.serverConfig.invoker.uri + config.serverConfig.invoker.store + "/subscription/" + uuid + "/" + + "/ROLE?", ).then(res => { if (res.status === 200) { if (fetchId !== this.lastFetchId) { @@ -68,53 +69,71 @@ class RoleUninstall extends React.Component { } }).catch((error) => { - handleApiError(error,"Error occurred while trying to load roles."); - this.setState({fetching: false}); + handleApiError(error, "Error occurred while trying to load roles.", true); + if (error.hasOwnProperty("response") && error.response.status === 403) { + this.setState({ + isForbidden: true, + loading: false + }) + } else { + this.setState({ + loading: false + }); + } }); }; handleChange = value => { this.setState({ - value, - data: [], - fetching: false, - }); + value, + data: [], + fetching: false, + }); }; - uninstall = (timestamp=null) =>{ + uninstall = (timestamp = null) => { const {value} = this.state; const data = []; - value.map(val=>{ + value.map(val => { data.push(val.key); }); - this.props.onUninstall("role", data, "uninstall",timestamp); + this.props.onUninstall("role", data, "uninstall", timestamp); }; render() { const {fetching, data, value} = this.state; return ( - <div> - <Text>Start uninstalling the application for one or more roles by entering the corresponding role name. Select uninstall to automatically start uninstalling the application for the respective user role/roles.</Text> - <br/> - <br/> - <Select - mode="multiple" - labelInValue - value={value} - placeholder="Search roles" - notFoundContent={fetching ? <Spin size="small"/> : null} - filterOption={false} - onSearch={this.fetchUser} - onChange={this.handleChange} - style={{width: '100%'}} - > - {data.map(d => ( - <Option key={d.value}>{d.text}</Option> - ))} - </Select> - <InstallModalFooter type="Uninstall" operation={this.uninstall} disabled={value.length===0}/> - </div> + <div> + <Text>Start uninstalling the application for one or more roles by entering the corresponding role name. + Select uninstall to automatically start uninstalling the application for the respective user + role/roles.</Text> + {(this.state.isForbidden) && ( + <Alert + message="You don't have permission to view uninstalled roles." + type="warning" + banner + closable/> + )} + <br/> + <br/> + <Select + mode="multiple" + labelInValue + value={value} + placeholder="Search roles" + notFoundContent={fetching ? <Spin size="small"/> : null} + filterOption={false} + onSearch={this.fetchUser} + onChange={this.handleChange} + style={{width: '100%'}} + > + {data.map(d => ( + <Option key={d.value}>{d.text}</Option> + ))} + </Select> + <InstallModalFooter type="Uninstall" operation={this.uninstall} disabled={value.length === 0}/> + </div> ); } } diff --git a/components/application-mgt/org.wso2.carbon.device.application.mgt.store.ui/react-app/src/components/apps/release/install/UserInstall.js b/components/application-mgt/org.wso2.carbon.device.application.mgt.store.ui/react-app/src/components/apps/release/install/UserInstall.js index 82f91bddff..ddac89bcab 100644 --- a/components/application-mgt/org.wso2.carbon.device.application.mgt.store.ui/react-app/src/components/apps/release/install/UserInstall.js +++ b/components/application-mgt/org.wso2.carbon.device.application.mgt.store.ui/react-app/src/components/apps/release/install/UserInstall.js @@ -17,7 +17,7 @@ */ 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 axios from "axios"; import {withConfigContext} from "../../../../context/ConfigContext"; @@ -69,8 +69,17 @@ class UserInstall extends React.Component { } }).catch((error) => { - handleApiError(error,"Error occurred while trying to load users."); - this.setState({fetching: false}); + handleApiError(error,"Error occurred while trying to load users.", true); + if (error.hasOwnProperty("response") && error.response.status === 403) { + this.setState({ + isForbidden: true, + loading: false + }) + } else { + this.setState({ + loading: false + }); + } }); }; @@ -79,6 +88,7 @@ class UserInstall extends React.Component { value, data: [], fetching: false, + isForbidden: false }); }; @@ -97,6 +107,13 @@ class UserInstall extends React.Component { return ( <div> <Text>Start installing the application for one or more users by entering the corresponding user name. Select install to automatically start downloading the application for the respective user/users. </Text> + {(this.state.isForbidden) && ( + <Alert + message="You don't have permission to view users." + type="warning" + banner + closable/> + )} <p>Select users</p> <Select mode="multiple" diff --git a/components/application-mgt/org.wso2.carbon.device.application.mgt.store.ui/react-app/src/components/apps/release/install/UserUninstall.js b/components/application-mgt/org.wso2.carbon.device.application.mgt.store.ui/react-app/src/components/apps/release/install/UserUninstall.js index cc53ca8504..935d2f47a0 100644 --- a/components/application-mgt/org.wso2.carbon.device.application.mgt.store.ui/react-app/src/components/apps/release/install/UserUninstall.js +++ b/components/application-mgt/org.wso2.carbon.device.application.mgt.store.ui/react-app/src/components/apps/release/install/UserUninstall.js @@ -17,7 +17,7 @@ */ 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 axios from "axios"; import {withConfigContext} from "../../../../context/ConfigContext"; @@ -39,6 +39,7 @@ class UserUninstall extends React.Component { data: [], value: [], fetching: false, + isForbidden: false }; fetchUser = (value) => { @@ -67,8 +68,17 @@ class UserUninstall extends React.Component { } }).catch((error) => { - handleApiError(error, "Error occurred while trying to load users."); - this.setState({fetching: false}); + handleApiError(error, "Error occurred while trying to load users.", true); + if (error.hasOwnProperty("response") && error.response.status === 403) { + this.setState({ + isForbidden: true, + loading: false + }) + } else { + this.setState({ + loading: false + }); + } }); }; @@ -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. Select uninstall to automatically start uninstalling the application for the respective user/users. </Text> + {(this.state.isForbidden) && ( + <Alert + message="You don't have permission to view uninstalled users." + type="warning" + banner + closable/> + )} <p>Select users</p> <Select mode="multiple" diff --git a/components/application-mgt/org.wso2.carbon.device.application.mgt.store.ui/react-app/src/components/apps/release/review/CurrentUsersReview.js b/components/application-mgt/org.wso2.carbon.device.application.mgt.store.ui/react-app/src/components/apps/release/review/CurrentUsersReview.js index 8489a26800..44d464b128 100644 --- a/components/application-mgt/org.wso2.carbon.device.application.mgt.store.ui/react-app/src/components/apps/release/review/CurrentUsersReview.js +++ b/components/application-mgt/org.wso2.carbon.device.application.mgt.store.ui/react-app/src/components/apps/release/review/CurrentUsersReview.js @@ -17,7 +17,7 @@ */ 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 axios from "axios"; import AddReview from "./AddReview"; @@ -34,52 +34,59 @@ class CurrentUsersReview extends React.Component { return ( <div> <Text>MY REVIEW</Text> - <div style={{ - overflow: "auto", - paddingTop: 8, - paddingLeft: 24 - }}> - {currentUserReviews.length > 0 && ( - <div> - <List - dataSource={currentUserReviews} - renderItem={item => ( - <List.Item key={item.id}> - <SingleReview - uuid={uuid} - review={item} - isDeletable={true} - isEditable={true} - deleteCallback={this.props.deleteCallback} - onUpdateReview={this.props.onUpdateReview} - isPersonalReview={true}/> - </List.Item> - )} - > - </List> - </div> - )} + {(this.props.forbidden) && ( + <Alert + message="You don't have permission to add reviews." + type="warning" + banner + closable/> + )} + {(!this.props.forbidden) && ( + <div style={{ + overflow: "auto", + paddingTop: 8, + paddingLeft: 24 + }}> + {currentUserReviews.length > 0 && ( + <div> + <List + dataSource={currentUserReviews} + renderItem={item => ( + <List.Item key={item.id}> + <SingleReview + uuid={uuid} + review={item} + isDeletable={true} + isEditable={true} + deleteCallback={this.props.deleteCallback} + onUpdateReview={this.props.onUpdateReview} + isPersonalReview={true}/> + </List.Item> + )} + > + </List> + </div> + )} - {currentUserReviews.length === 0 && ( - <div> - <Empty - image={Empty.PRESENTED_IMAGE_DEFAULT} - imagestyle={{ - height: 60, - }} - description={ - <span>Share your experience with your community by adding a review.</span> - } - > - {/*<Button type="primary">Add review</Button>*/} - <AddReview - uuid={uuid} - onUpdateReview={this.props.onUpdateReview}/> - </Empty> - </div> - )} - - </div> + {currentUserReviews.length === 0 && ( + <div> + <Empty + image={Empty.PRESENTED_IMAGE_DEFAULT} + imagestyle={{ + height: 60, + }} + description={ + <span>Share your experience with your community by adding a review.</span> + }> + {/*<Button type="primary">Add review</Button>*/} + <AddReview + uuid={uuid} + onUpdateReview={this.props.onUpdateReview}/> + </Empty> + </div> + )} + </div> + )} </div> ); } diff --git a/components/application-mgt/org.wso2.carbon.device.application.mgt.store.ui/react-app/src/components/apps/release/review/ReviewContainer.js b/components/application-mgt/org.wso2.carbon.device.application.mgt.store.ui/react-app/src/components/apps/release/review/ReviewContainer.js index 2b8f124cb2..291d032939 100644 --- a/components/application-mgt/org.wso2.carbon.device.application.mgt.store.ui/react-app/src/components/apps/release/review/ReviewContainer.js +++ b/components/application-mgt/org.wso2.carbon.device.application.mgt.store.ui/react-app/src/components/apps/release/review/ReviewContainer.js @@ -32,7 +32,12 @@ class ReviewContainer extends React.Component { super(props); this.state = { currentUserReviews: [], - detailedRating: null + detailedRating: null, + forbiddenErrors: { + currentReview: false, + reviews: false, + rating: false + } } } @@ -54,7 +59,19 @@ class ReviewContainer extends React.Component { } }).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) { - 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() { const {uuid} = this.props; - const {currentUserReviews,detailedRating} = this.state; + const {currentUserReviews,detailedRating, forbiddenErrors} = this.state; return ( <div> <CurrentUsersReview + forbidden={forbiddenErrors.currentReview} uuid={uuid} currentUserReviews={currentUserReviews} onUpdateReview={this.onUpdateReview} diff --git a/components/application-mgt/org.wso2.carbon.device.application.mgt.store.ui/react-app/src/components/apps/release/review/Reviews.js b/components/application-mgt/org.wso2.carbon.device.application.mgt.store.ui/react-app/src/components/apps/release/review/Reviews.js index 7c74574b6b..fff6364c78 100644 --- a/components/application-mgt/org.wso2.carbon.device.application.mgt.store.ui/react-app/src/components/apps/release/review/Reviews.js +++ b/components/application-mgt/org.wso2.carbon.device.application.mgt.store.ui/react-app/src/components/apps/release/review/Reviews.js @@ -17,7 +17,7 @@ */ 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 InfiniteScroll from 'react-infinite-scroller'; @@ -33,7 +33,10 @@ class Reviews extends React.Component { data: [], loading: false, hasMore: false, - loadMore: false + loadMore: false, + forbiddenErrors: { + reviews: false + } }; @@ -51,7 +54,7 @@ class Reviews extends React.Component { const config = this.props.context; 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} }).then(res => { @@ -60,8 +63,20 @@ class Reviews extends React.Component { callback(reviews); } - }).catch(function (error) { - handleApiError(error,"Error occurred while trying to load reviews."); + }).catch((error) => { + handleApiError(error, "Error occurred while trying to load reviews.", true); + if (error.hasOwnProperty("response") && error.response.status === 403) { + const {forbiddenErrors} = this.state; + forbiddenErrors.reviews = true; + this.setState({ + forbiddenErrors, + loading: false + }) + } else { + this.setState({ + loading: false + }); + } }); }; @@ -101,7 +116,7 @@ class Reviews extends React.Component { }); }; - deleteCallback = () =>{ + deleteCallback = () => { this.fetchData(0, limit, res => { this.setState({ data: res, @@ -114,30 +129,40 @@ class Reviews extends React.Component { const {loading, hasMore, data, loadMore} = this.state; const {uuid} = this.props; return ( - <div className="infinite-container"> - <InfiniteScroll - initialLoad={false} - pageStart={0} - loadMore={this.handleInfiniteOnLoad} - hasMore={!loading && hasMore} - useWindow={true}> - <List - dataSource={data} - renderItem={item => ( - <List.Item key={item.id}> - <SingleReview uuid={uuid} review={item} isDeletable={true} isEditable={false} deleteCallback={this.deleteCallback}/> - </List.Item> - )}> - {loading && hasMore && ( - <div className="loading-container"> - <Spin/> - </div> - )} - </List> - </InfiniteScroll> - {!loadMore && (data.length >= limit) && (<div style={{textAlign: "center"}}> - <Button type="dashed" htmlType="button" onClick={this.enableLoading}>Read All Reviews</Button> - </div>)} + <div> + {(this.state.forbiddenErrors.reviews) && ( + <Alert + message="You don't have permission to view reviews." + type="warning" + banner + closable/> + )} + <div className="infinite-container"> + <InfiniteScroll + initialLoad={false} + pageStart={0} + loadMore={this.handleInfiniteOnLoad} + hasMore={!loading && hasMore} + useWindow={true}> + <List + dataSource={data} + renderItem={item => ( + <List.Item key={item.id}> + <SingleReview uuid={uuid} review={item} isDeletable={true} isEditable={false} + deleteCallback={this.deleteCallback}/> + </List.Item> + )}> + {loading && hasMore && ( + <div className="loading-container"> + <Spin/> + </div> + )} + </List> + </InfiniteScroll> + {!loadMore && (data.length >= limit) && (<div style={{textAlign: "center"}}> + <Button type="dashed" htmlType="button" onClick={this.enableLoading}>Read All Reviews</Button> + </div>)} + </div> </div> ); } diff --git a/components/application-mgt/org.wso2.carbon.device.application.mgt.store.ui/react-app/src/js/Utils.js b/components/application-mgt/org.wso2.carbon.device.application.mgt.store.ui/react-app/src/js/Utils.js index 7bdcef3eea..aea3333985 100644 --- a/components/application-mgt/org.wso2.carbon.device.application.mgt.store.ui/react-app/src/js/Utils.js +++ b/components/application-mgt/org.wso2.carbon.device.application.mgt.store.ui/react-app/src/js/Utils.js @@ -18,14 +18,15 @@ import {notification} from "antd"; -export const handleApiError = (error, message) => { +export const handleApiError = (error, message, isForbiddenMessageSilent = false) => { if (error.hasOwnProperty("response") && error.response.status === 401) { const redirectUrl = encodeURI(window.location.href); window.location.href = window.location.origin + `/store/login?redirect=${redirectUrl}`; - } else { + // silence 403 forbidden message + } else if (!(isForbiddenMessageSilent && error.hasOwnProperty("response") && error.response.status === 403)) { notification["error"]({ message: "There was a problem", - duration: 2, + duration: 10, description: message, }); } diff --git a/components/application-mgt/org.wso2.carbon.device.application.mgt.store.ui/react-app/src/pages/dashboard/Dashboard.js b/components/application-mgt/org.wso2.carbon.device.application.mgt.store.ui/react-app/src/pages/dashboard/Dashboard.js index 30c7da2df1..b0a5108ddf 100644 --- a/components/application-mgt/org.wso2.carbon.device.application.mgt.store.ui/react-app/src/pages/dashboard/Dashboard.js +++ b/components/application-mgt/org.wso2.carbon.device.application.mgt.store.ui/react-app/src/pages/dashboard/Dashboard.js @@ -17,7 +17,8 @@ */ 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; import {Link} from "react-router-dom"; import RouteWithSubRoutes from "../../components/RouteWithSubRoutes"; @@ -38,7 +39,10 @@ class Dashboard extends React.Component { selectedKeys: [], deviceTypes: [], visible: false, - collapsed: false + collapsed: false, + forbiddenErrors: { + deviceTypes: false + } }; this.logo = this.props.context.theme.logo; this.config = this.props.context; @@ -62,10 +66,19 @@ class Dashboard extends React.Component { } }).catch((error) => { - handleApiError(error,"Error occurred while trying to load device types."); - this.setState({ - loading: false - }); + handleApiError(error, "Error occurred while trying to load device types.", true); + if (error.hasOwnProperty("response") && error.response.status === 403) { + const {forbiddenErrors} = this.state; + forbiddenErrors.deviceTypes = true; + this.setState({ + forbiddenErrors, + loading: false + }) + } else { + this.setState({ + loading: false + }); + } }); }; @@ -81,7 +94,7 @@ class Dashboard extends React.Component { collapsed: !this.state.collapsed, }); }; - + onCloseMobileNavigationBar = () => { this.setState({ visible: false, @@ -90,7 +103,7 @@ class Dashboard extends React.Component { render() { const config = this.props.context; - const {selectedKeys, deviceTypes} = this.state; + const {selectedKeys, deviceTypes, forbiddenErrors} = this.state; const DeviceTypesData = deviceTypes.map((deviceType) => { const platform = deviceType.name; @@ -116,7 +129,7 @@ class Dashboard extends React.Component { <Layout> <Header style={{paddingLeft: 0, paddingRight: 0, backgroundColor: "white"}}> <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 className="web-layout"> @@ -131,7 +144,7 @@ class Dashboard extends React.Component { <Menu.Item key="web-clip"> <Link to="/store/web-clip"> <Icon type="upload"/> - Web Clips + Web Clips </Link> </Menu.Item> @@ -140,7 +153,7 @@ class Dashboard extends React.Component { <span className="submenu-title-wrapper"> <Icon type="user"/> {this.config.user} - </span> }> + </span>}> <Logout/> </SubMenu> </Menu> @@ -156,32 +169,32 @@ class Dashboard extends React.Component { </Button> </div> </Layout> - <Drawer - title={<Link to="/store/android" onClick={this.onCloseMobileNavigationBar}> - <img alt="logo" src={this.logo} style={{marginLeft: 30}} width={"60%"}/> - </Link>} - placement="left" - closable={false} - onClose={this.onCloseMobileNavigationBar} - visible={this.state.visible} - getContainer={false} - style={{position: 'absolute'}}> - <Menu - theme="light" - mode="inline" - defaultSelectedKeys={selectedKeys} - style={{lineHeight: '64px', width: 231}} - onClick={this.onCloseMobileNavigationBar}> - - {DeviceTypesData} - - <Menu.Item key="web-clip"> - <Link to="/store/web-clip"> + <Drawer + title={<Link to="/store" onClick={this.onCloseMobileNavigationBar}> + <img alt="logo" src={this.logo} style={{marginLeft: 30}} width={"60%"}/> + </Link>} + placement="left" + closable={false} + onClose={this.onCloseMobileNavigationBar} + visible={this.state.visible} + getContainer={false} + style={{position: 'absolute'}}> + <Menu + theme="light" + mode="inline" + defaultSelectedKeys={selectedKeys} + style={{lineHeight: '64px', width: 231}} + onClick={this.onCloseMobileNavigationBar}> + + {DeviceTypesData} + + <Menu.Item key="web-clip"> + <Link to="/store/web-clip"> <Icon type="upload"/>Web Clips - </Link> - </Menu.Item> - </Menu> - </Drawer> + </Link> + </Menu.Item> + </Menu> + </Drawer> <Layout className="mobile-layout"> <Menu mode="horizontal" @@ -198,6 +211,13 @@ class Dashboard extends React.Component { </Layout> <Layout className="dashboard-body"> + {(forbiddenErrors.deviceTypes) && ( + <Alert + message="You don't have permission to view device types." + type="warning" + banner + closable/> + )} <Content style={{padding: '0 0'}}> <Switch> {this.state.routes.map((route) => ( diff --git a/components/application-mgt/org.wso2.carbon.device.application.mgt.store.ui/react-app/src/pages/dashboard/apps/release/Release.js b/components/application-mgt/org.wso2.carbon.device.application.mgt.store.ui/react-app/src/pages/dashboard/apps/release/Release.js index adac67e737..80cfe2b9cb 100644 --- a/components/application-mgt/org.wso2.carbon.device.application.mgt.store.ui/react-app/src/pages/dashboard/apps/release/Release.js +++ b/components/application-mgt/org.wso2.carbon.device.application.mgt.store.ui/react-app/src/pages/dashboard/apps/release/Release.js @@ -36,7 +36,10 @@ class Release extends React.Component { this.state = { loading: true, app: null, - uuid: null + uuid: null, + forbiddenErrors: { + app: false + } }; } @@ -72,8 +75,19 @@ class Release extends React.Component { } }).catch((error) => { - handleApiError(error,"Error occurred while trying to load releases."); - this.setState({loading: false}); + handleApiError(error,"Error occurred while trying to load releases.", false); + if (error.hasOwnProperty("response") && error.response.status === 403) { + const {forbiddenErrors} = this.state; + forbiddenErrors.app = true; + this.setState({ + forbiddenErrors, + loading: false + }) + } else { + this.setState({ + loading: false + }); + } }); }; From c5bc96d8cc0cd68219f6b3fa296b255786b5cc09 Mon Sep 17 00:00:00 2001 From: Jayasanka <jayasanka.sack@gmail.com> Date: Sat, 26 Oct 2019 19:40:20 +0530 Subject: [PATCH 10/10] Fix invalid support OS version issue in APPM publisher UI --- .../components/new-app/subForms/NewAppUploadForm.js | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/src/components/new-app/subForms/NewAppUploadForm.js b/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/src/components/new-app/subForms/NewAppUploadForm.js index a0aa1e6efa..ebb761c322 100644 --- a/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/src/components/new-app/subForms/NewAppUploadForm.js +++ b/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/src/components/new-app/subForms/NewAppUploadForm.js @@ -117,7 +117,7 @@ class NewAppUploadForm extends React.Component { osVersionsHelperText: 'Please select supported OS versions', osVersionsValidateStatus: 'error', }); - } else if (this.lowerOsVersion >= this.upperOsVersion) { + } else if (parseFloat(this.lowerOsVersion) >= parseFloat(this.upperOsVersion)) { isFormValid = false; this.setState({ osVersionsHelperText: 'Please select valid range', @@ -221,7 +221,7 @@ class NewAppUploadForm extends React.Component { }; handleLowerOsVersionChange = (lowerOsVersion) => { - this.lowerOsVersion = parseFloat(lowerOsVersion); + this.lowerOsVersion = lowerOsVersion; this.setState({ osVersionsValidateStatus: 'validating', osVersionsHelperText: '' @@ -229,7 +229,11 @@ class NewAppUploadForm extends React.Component { }; 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({ osVersionsValidateStatus: 'validating', osVersionsHelperText: '' @@ -445,7 +449,7 @@ class NewAppUploadForm extends React.Component { onChange={this.handleUpperOsVersionChange}> {(supportedOsVersions.length > 0) && ( <Option key="all" - value={supportedOsVersions[supportedOsVersions.length - 1]["versionName"]}> + value="all"> All </Option> )}