forked from community/device-mgt-core
Further integrate app management component with device mgtfeature/appm-store/pbac
parent
efc660adeb
commit
a5694371ea
@ -0,0 +1,72 @@
|
||||
.d-rating .numeric-data{
|
||||
box-sizing: border-box;
|
||||
display: inline-block;
|
||||
padding: 20px 0 20px 0;
|
||||
vertical-align: top;
|
||||
text-align: center;
|
||||
width: 30%;
|
||||
}
|
||||
|
||||
.d-rating .bar-containers{
|
||||
box-sizing: border-box;
|
||||
display: inline-block;
|
||||
padding: 20px 20px 20px 30px;
|
||||
vertical-align: top;
|
||||
width: 70%;
|
||||
}
|
||||
|
||||
.d-rating .bar-containers .bar-container{
|
||||
color: #737373;
|
||||
font-weight: 400;
|
||||
height: 20px;
|
||||
margin-bottom: 4px;
|
||||
position: relative;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.d-rating .bar-containers .bar-container .number{
|
||||
font-size: 11px;
|
||||
left: -16px;
|
||||
letter-spacing: 1px;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.d-rating .bar-containers .bar-container .bar{
|
||||
transition: width .25s ease;
|
||||
display: inline-block;
|
||||
height: 100%;
|
||||
opacity: .8;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
.bar-container .rate-5{
|
||||
background: #57bb8a;
|
||||
}
|
||||
|
||||
.bar-container .rate-4{
|
||||
background: #9ace6a;
|
||||
}
|
||||
|
||||
.bar-container .rate-3{
|
||||
background: #ffcf02;
|
||||
}
|
||||
|
||||
.bar-container .rate-2{
|
||||
background: #ff9f02;
|
||||
}
|
||||
|
||||
.bar-container .rate-1{
|
||||
background: #ff6f31;
|
||||
}
|
||||
|
||||
.d-rating .numeric-data .rate{
|
||||
color: #333;
|
||||
font-size: 64px;
|
||||
font-weight: 100;
|
||||
line-height: 64px;
|
||||
padding-bottom: 6px;
|
||||
}
|
||||
|
||||
.d-rating .numeric-data .people-count{
|
||||
padding-top: 6px;
|
||||
}
|
@ -0,0 +1,122 @@
|
||||
import React from "react";
|
||||
import {Row, Typography, Icon} from "antd";
|
||||
import StarRatings from "react-star-ratings";
|
||||
import "./DetailedRating.css";
|
||||
import config from "../../../../public/conf/config.json";
|
||||
import axios from "axios";
|
||||
|
||||
const { Text } = Typography;
|
||||
|
||||
|
||||
class DetailedRating extends React.Component{
|
||||
|
||||
constructor(props){
|
||||
super(props);
|
||||
this.state={
|
||||
detailedRating: null
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.getData(this.props.uuid);
|
||||
}
|
||||
|
||||
componentDidUpdate(nextProps) {
|
||||
if (nextProps !== this.props) {
|
||||
this.getData(this.props.uuid);
|
||||
}
|
||||
}
|
||||
|
||||
getData = (uuid)=>{
|
||||
const request = "method=get&content-type=application/json&payload={}&api-endpoint=/application-mgt-store/v1.0/reviews/"+uuid+"/rating";
|
||||
|
||||
return axios.post('https://' + config.serverConfig.hostname + ':' + config.serverConfig.httpsPort + config.serverConfig.invokerUri, request
|
||||
).then(res => {
|
||||
if (res.status === 200) {
|
||||
let detailedRating = res.data.data;
|
||||
this.setState({
|
||||
detailedRating
|
||||
})
|
||||
}
|
||||
|
||||
}).catch(function (error) {
|
||||
if (error.response.status === 401) {
|
||||
window.location.href = 'https://localhost:9443/store/login';
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
render() {
|
||||
const detailedRating = this.state.detailedRating;
|
||||
|
||||
console.log(detailedRating);
|
||||
|
||||
if(detailedRating ==null){
|
||||
return null;
|
||||
}
|
||||
|
||||
const totalCount = detailedRating.noOfUsers;
|
||||
const ratingVariety = detailedRating.ratingVariety;
|
||||
|
||||
const ratingArray = [];
|
||||
|
||||
for (let [key, value] of Object.entries(ratingVariety)) {
|
||||
ratingArray.push(value);
|
||||
}
|
||||
|
||||
const maximumRating = Math.max(...ratingArray);
|
||||
|
||||
const ratingBarPercentages = [0,0,0,0,0];
|
||||
|
||||
if(maximumRating>0){
|
||||
for(let i = 0; i<5; i++){
|
||||
ratingBarPercentages[i] = (ratingVariety[(i+1).toString()])/maximumRating*100;
|
||||
}
|
||||
}
|
||||
|
||||
console.log(ratingBarPercentages);
|
||||
|
||||
return (
|
||||
<Row className="d-rating">
|
||||
<div className="numeric-data">
|
||||
<div className="rate">{detailedRating.ratingValue.toFixed(1)}</div>
|
||||
<StarRatings
|
||||
rating={detailedRating.ratingValue}
|
||||
starRatedColor="#777"
|
||||
starDimension = "16px"
|
||||
starSpacing = "2px"
|
||||
numberOfStars={5}
|
||||
name='rating'
|
||||
/>
|
||||
<br/>
|
||||
<Text type="secondary" className="people-count"><Icon type="team" /> {totalCount} total</Text>
|
||||
</div>
|
||||
<div className="bar-containers">
|
||||
<div className="bar-container">
|
||||
<span className="number">5</span>
|
||||
<span className="bar rate-5" style={{width: ratingBarPercentages[4]+"%"}}> </span>
|
||||
</div>
|
||||
<div className="bar-container">
|
||||
<span className="number">4</span>
|
||||
<span className="bar rate-4" style={{width: ratingBarPercentages[3]+"%"}}> </span>
|
||||
</div>
|
||||
<div className="bar-container">
|
||||
<span className="number">3</span>
|
||||
<span className="bar rate-3" style={{width: ratingBarPercentages[2]+"%"}}> </span>
|
||||
</div>
|
||||
<div className="bar-container">
|
||||
<span className="number">2</span>
|
||||
<span className="bar rate-2" style={{width: ratingBarPercentages[1]+"%"}}> </span>
|
||||
</div>
|
||||
<div className="bar-container">
|
||||
<span className="number">1</span>
|
||||
<span className="bar rate-1" style={{width: ratingBarPercentages[0]+"%"}}> </span>
|
||||
</div>
|
||||
</div>
|
||||
</Row>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export default DetailedRating;
|
@ -0,0 +1,95 @@
|
||||
import React from 'react';
|
||||
import {Drawer, Row, Col, Typography, Divider, Tag, Avatar, List} from 'antd';
|
||||
import "../../../App.css";
|
||||
import DetailedRating from "../detailed-rating/DetailedRating";
|
||||
|
||||
const {Text, Title, Paragraph} = Typography;
|
||||
|
||||
class AppDetailsDrawer extends React.Component {
|
||||
|
||||
render() {
|
||||
const {app, visible, onClose} = this.props;
|
||||
if (app == null) {
|
||||
return null;
|
||||
}
|
||||
console.log(app);
|
||||
return (
|
||||
<div>
|
||||
|
||||
<Drawer
|
||||
placement="right"
|
||||
width={640}
|
||||
closable={false}
|
||||
onClose={onClose}
|
||||
visible={visible}
|
||||
>
|
||||
<div style={{textAlign: "center"}}>
|
||||
<img
|
||||
style={{
|
||||
marginBottom: 10,
|
||||
width: 100,
|
||||
borderRadius: "28%",
|
||||
border: "1px solid #ddd"
|
||||
}}
|
||||
src={app.applicationReleases[0].iconPath}
|
||||
/>
|
||||
<Title level={2}>{app.name}</Title>
|
||||
</div>
|
||||
<Divider/>
|
||||
<Paragraph type="secondary" ellipsis={{rows: 3, expandable: true}}>{app.description}</Paragraph>
|
||||
<Divider dashed={true}/>
|
||||
<Text strong={true}>Categories</Text>
|
||||
<br/>
|
||||
<br/>
|
||||
<span>
|
||||
{app.appCategories.map(category => {
|
||||
return (
|
||||
<Tag color="blue" key={category} style={{paddingBottom: 5}}>
|
||||
{category}
|
||||
</Tag>
|
||||
);
|
||||
})}
|
||||
</span>
|
||||
|
||||
<Divider dashed={true}/>
|
||||
<Text strong={true}>Tags</Text>
|
||||
<br/>
|
||||
<br/>
|
||||
<span>
|
||||
{app.tags.map(category => {
|
||||
return (
|
||||
<Tag color="gold" key={category} style={{paddingBottom: 5}}>
|
||||
{category}
|
||||
</Tag>
|
||||
);
|
||||
})}
|
||||
</span>
|
||||
<Divider dashed={true}/>
|
||||
<Text strong={true}>Releases</Text>
|
||||
<br/>
|
||||
<List
|
||||
itemLayout="horizontal"
|
||||
dataSource={app.applicationReleases}
|
||||
renderItem={release => (
|
||||
<List.Item>
|
||||
<List.Item.Meta
|
||||
title={<a href={"apps/releases/"+release.uuid}>{release.version}</a>}
|
||||
description={
|
||||
<div>
|
||||
Status : <Tag>{release.currentStatus}</Tag> Release Type <Tag color="green">{release.releaseType}</Tag>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
</List.Item>
|
||||
)}
|
||||
/>
|
||||
<Divider dashed={true}/>
|
||||
|
||||
<DetailedRating uuid={app.applicationReleases[0].uuid}/>
|
||||
</Drawer>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default AppDetailsDrawer;
|
@ -0,0 +1,180 @@
|
||||
import React from "react";
|
||||
import {Avatar, Card, Col, Row, Table, Typography, Tag, Icon, message} from "antd";
|
||||
import {connect} from "react-redux";
|
||||
import {getApps} from "../../../js/actions";
|
||||
import axios from "axios";
|
||||
import config from "../../../../public/conf/config.json";
|
||||
|
||||
const {Title} = Typography;
|
||||
|
||||
// connecting state.apps with the component
|
||||
const mapStateToProps = state => {
|
||||
return {apps: state.apps}
|
||||
};
|
||||
|
||||
const columns = [
|
||||
{
|
||||
title: '',
|
||||
dataIndex: 'name',
|
||||
render: (name, row) => {
|
||||
return (
|
||||
<div>
|
||||
<Avatar shape="square" size="large"
|
||||
style={{
|
||||
marginRight: 20,
|
||||
borderRadius: "28%",
|
||||
border: "1px solid #ddd"
|
||||
}}
|
||||
src={row.applicationReleases[0].iconPath}
|
||||
/>
|
||||
{name}
|
||||
</div>);
|
||||
}
|
||||
},
|
||||
{
|
||||
title: 'Categories',
|
||||
dataIndex: 'appCategories',
|
||||
render: appCategories => (
|
||||
<span>
|
||||
{appCategories.map(category => {
|
||||
return (
|
||||
<Tag color="blue" key={category}>
|
||||
{category}
|
||||
</Tag>
|
||||
);
|
||||
})}
|
||||
</span>
|
||||
)
|
||||
},
|
||||
{
|
||||
title: 'Platform',
|
||||
dataIndex: 'deviceType',
|
||||
render: platform => {
|
||||
const defaultPlatformIcons = config.defaultPlatformIcons;
|
||||
let icon = defaultPlatformIcons.default.icon;
|
||||
let color = defaultPlatformIcons.default.color;
|
||||
if (defaultPlatformIcons.hasOwnProperty(platform)) {
|
||||
icon = defaultPlatformIcons[platform].icon;
|
||||
color = defaultPlatformIcons[platform].color;
|
||||
}
|
||||
return (<span style={{fontSize: 20, color: color, textAlign: "center"}}><Icon type={icon}
|
||||
theme="filled"/></span>)
|
||||
}
|
||||
},
|
||||
{
|
||||
title: 'Type',
|
||||
dataIndex: 'type'
|
||||
},
|
||||
{
|
||||
title: 'Subscription',
|
||||
dataIndex: 'subType'
|
||||
},
|
||||
];
|
||||
|
||||
class ConnectedAppsTable extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
pagination: {
|
||||
total: 100
|
||||
},
|
||||
apps: []
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.fetch();
|
||||
}
|
||||
|
||||
handleTableChange = (pagination, filters, sorter) => {
|
||||
const pager = {...this.state.pagination};
|
||||
pager.current = pagination.current;
|
||||
|
||||
this.setState({
|
||||
pagination: pager,
|
||||
});
|
||||
this.fetch({
|
||||
results: pagination.pageSize,
|
||||
page: pagination.current,
|
||||
sortField: sorter.field,
|
||||
sortOrder: sorter.order,
|
||||
...filters,
|
||||
});
|
||||
};
|
||||
|
||||
fetch = (params = {}) => {
|
||||
this.setState({loading: true});
|
||||
|
||||
const extraParams = {
|
||||
offset: 10 * (params.page - 1),
|
||||
limit: 10
|
||||
};
|
||||
// note: encode with '%26' not '&'
|
||||
const encodedExtraParams = Object.keys(extraParams).map(key => key + '=' + extraParams[key]).join('%26');
|
||||
const parameters = {
|
||||
method: "post",
|
||||
'content-type': "application/json",
|
||||
payload: JSON.stringify({}),
|
||||
'api-endpoint': "/application-mgt-publisher/v1.0/applications?" + encodedExtraParams
|
||||
};
|
||||
|
||||
const request = Object.keys(parameters).map(key => key + '=' + parameters[key]).join('&');
|
||||
console.log(request);
|
||||
axios.post('https://' + config.serverConfig.hostname + ':' + config.serverConfig.httpsPort + config.serverConfig.invokerUri, request
|
||||
).then(res => {
|
||||
if (res.status === 200) {
|
||||
let apps = [];
|
||||
|
||||
if (res.data.data.hasOwnProperty("applications")) {
|
||||
apps = res.data.data.applications;
|
||||
}
|
||||
const pagination = {...this.state.pagination};
|
||||
// Read total count from server
|
||||
// pagination.total = data.totalCount;
|
||||
pagination.total = 200;
|
||||
this.setState({
|
||||
loading: false,
|
||||
apps: apps,
|
||||
pagination,
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
}).catch((error) => {
|
||||
if (error.response.status === 401) {
|
||||
message.error('You are not logged in');
|
||||
window.location.href = 'https://localhost:9443/publisher/login';
|
||||
} else {
|
||||
message.error('Something went wrong... :(');
|
||||
}
|
||||
|
||||
this.setState({loading: false});
|
||||
});
|
||||
};
|
||||
|
||||
render() {
|
||||
console.log("rendered");
|
||||
return (
|
||||
|
||||
<Table
|
||||
rowKey={record => record.id}
|
||||
dataSource={this.state.apps}
|
||||
columns={columns}
|
||||
pagination={this.state.pagination}
|
||||
onChange={this.handleTableChange}
|
||||
onRow={(record, rowIndex) => {
|
||||
return {
|
||||
onClick: event => {
|
||||
this.props.showDrawer(record);
|
||||
},
|
||||
};
|
||||
}}
|
||||
/>
|
||||
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const AppsTable = connect(mapStateToProps, {getApps})(ConnectedAppsTable);
|
||||
|
||||
export default AppsTable;
|
@ -0,0 +1,74 @@
|
||||
import React from "react";
|
||||
import {Avatar, Card, Col, Row, Table, Typography, Input, Divider, Checkbox, Select, Button} from "antd";
|
||||
|
||||
const {Option} = Select;
|
||||
const {Title, Text} = Typography;
|
||||
|
||||
class Filters extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
|
||||
render() {
|
||||
return (
|
||||
|
||||
<Card>
|
||||
<Row>
|
||||
<Col span={6}>
|
||||
<Title level={4}>Filter</Title>
|
||||
</Col>
|
||||
</Row>
|
||||
<Divider/>
|
||||
|
||||
<Text strong={true}>Category</Text>
|
||||
<br/><br/>
|
||||
<Select
|
||||
mode="multiple"
|
||||
style={{width: '100%'}}
|
||||
placeholder="All Categories"
|
||||
>
|
||||
<Option key={1}>IoT</Option>
|
||||
<Option key={2}>EMM</Option>
|
||||
</Select>
|
||||
<Divider/>
|
||||
|
||||
<Text strong={true}>Platform</Text>
|
||||
<br/><br/>
|
||||
<Checkbox>Android</Checkbox><br/>
|
||||
<Checkbox>iOS</Checkbox><br/>
|
||||
<Checkbox>Windows</Checkbox><br/>
|
||||
<Checkbox>Default</Checkbox><br/>
|
||||
<Divider/>
|
||||
|
||||
<Text strong={true}>Tags</Text>
|
||||
<br/><br/>
|
||||
<Select
|
||||
mode="multiple"
|
||||
style={{width: '100%'}}
|
||||
placeholder="All Tags"
|
||||
>
|
||||
<Option key={1}>test tag</Option>
|
||||
</Select>
|
||||
|
||||
<Divider/>
|
||||
<Text strong={true}>Type</Text>
|
||||
<br/><br/>
|
||||
<Checkbox>Enterprise</Checkbox><br/>
|
||||
<Checkbox>Public</Checkbox><br/>
|
||||
<Checkbox>Web APP</Checkbox><br/>
|
||||
<Checkbox>Web Clip</Checkbox><br/>
|
||||
<Divider/>
|
||||
|
||||
<Text strong={true}>Subscription</Text>
|
||||
<br/><br/>
|
||||
<Checkbox>Free</Checkbox><br/>
|
||||
<Checkbox>Paid</Checkbox><br/>
|
||||
<Divider/>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export default Filters;
|
@ -0,0 +1,76 @@
|
||||
import React from "react";
|
||||
import {Avatar, Card, Col, Row, Table, Typography, Input, Divider, Checkbox, Select, Button} from "antd";
|
||||
import {connect} from "react-redux";
|
||||
import {getApps} from "../../../js/actions";
|
||||
import AppsTable from "./AppsTable";
|
||||
import Filters from "./Filters";
|
||||
import AppDetailsDrawer from "./AppDetailsDrawer";
|
||||
|
||||
const {Option} = Select;
|
||||
const {Title, Text} = Typography;
|
||||
const Search = Input.Search;
|
||||
// connecting state.apps with the component
|
||||
const mapStateToProps = state => {
|
||||
return {apps: state.apps}
|
||||
};
|
||||
|
||||
|
||||
class ConnectedListApps extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
isDrawerVisible: false,
|
||||
selectedApp: null
|
||||
}
|
||||
}
|
||||
|
||||
//handler to show app drawer
|
||||
showDrawer = (app) => {
|
||||
console.log(app);
|
||||
this.setState({
|
||||
isDrawerVisible: true,
|
||||
selectedApp: app
|
||||
});
|
||||
};
|
||||
|
||||
// handler to close the app drawer
|
||||
closeDrawer = () => {
|
||||
this.setState({
|
||||
isDrawerVisible: false
|
||||
})
|
||||
};
|
||||
|
||||
render() {
|
||||
const {isDrawerVisible} = this.state;
|
||||
return (
|
||||
<Row gutter={28}>
|
||||
<Col md={6}>
|
||||
<Filters/>
|
||||
</Col>
|
||||
<Col md={18}>
|
||||
<Card>
|
||||
<Row>
|
||||
<Col span={6}>
|
||||
<Title level={4}>Apps</Title>
|
||||
</Col>
|
||||
<Col span={18} style={{textAlign: "right"}}>
|
||||
<Search
|
||||
placeholder="input search text"
|
||||
onSearch={value => console.log(value)}
|
||||
style={{width: 200}}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
<Divider dashed={true}/>
|
||||
<AppsTable showDrawer={this.showDrawer} />
|
||||
<AppDetailsDrawer visible={isDrawerVisible} onClose={this.closeDrawer} app={this.state.selectedApp}/>
|
||||
</Card>
|
||||
</Col>
|
||||
</Row>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const ListApps = connect(mapStateToProps, {getApps})(ConnectedListApps);
|
||||
|
||||
export default ListApps;
|
@ -0,0 +1,60 @@
|
||||
import React from "react";
|
||||
import "antd/dist/antd.css";
|
||||
import {PageHeader, Typography,Input, Button, Row, Col} from "antd";
|
||||
import AppList from "../../../components/apps/AppList";
|
||||
import ReleaseModal from "../../../components/apps/ReleaseModal";
|
||||
|
||||
const Search = Input.Search;
|
||||
|
||||
const routes = [
|
||||
{
|
||||
path: 'index',
|
||||
breadcrumbName: 'Publisher',
|
||||
},
|
||||
{
|
||||
path: 'first',
|
||||
breadcrumbName: 'Dashboard',
|
||||
},
|
||||
{
|
||||
path: 'second',
|
||||
breadcrumbName: 'Apps',
|
||||
},
|
||||
];
|
||||
|
||||
|
||||
class Apps extends React.Component {
|
||||
routes;
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.routes = props.routes;
|
||||
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
<PageHeader
|
||||
breadcrumb={{routes}}
|
||||
/>
|
||||
<div style={{background: '#f0f2f5', padding: 24, minHeight: 780}}>
|
||||
<Row style={{padding:10}}>
|
||||
<Col span={6} offset={18}>
|
||||
<Search
|
||||
placeholder="search"
|
||||
onSearch={value => console.log(value)}
|
||||
style={{ width: 200}}
|
||||
/>
|
||||
<Button style={{margin:5}}>Advanced Search</Button>
|
||||
</Col>
|
||||
</Row>
|
||||
<ReleaseModal/>
|
||||
<AppList/>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default Apps;
|
Loading…
Reference in new issue